fix: caching and reactivity

This commit is contained in:
hypercross 2026-02-26 14:51:26 +08:00
parent 9bb48e0388
commit 588ae49f5f
3 changed files with 49 additions and 52 deletions

View File

@ -1,4 +1,4 @@
import { Component, createSignal, onMount } from "solid-js";
import { Component, createMemo, createSignal } from "solid-js";
import { useLocation } from "@solidjs/router";
// 导入组件以注册自定义元素
@ -7,10 +7,9 @@ import { Article, Sidebar } from "./components";
const App: Component = () => {
const location = useLocation();
const [currentPath, setCurrentPath] = createSignal("");
const [isSidebarOpen, setIsSidebarOpen] = createSignal(false);
onMount(() => {
const currentPath = createMemo(() => {
// 根据路由加载对应的 markdown 文件
let path = decodeURIComponent(location.pathname);
@ -18,7 +17,7 @@ const App: Component = () => {
if (path.endsWith("/")) path += "index";
if (!path.endsWith(".md")) path += ".md";
setCurrentPath(path);
return path;
});
return (

View File

@ -1,4 +1,4 @@
import { Component, createSignal, onMount, onCleanup, Show } from 'solid-js';
import { Component, createSignal, createEffect, onCleanup, Show, createResource } from 'solid-js';
import { parseMarkdown } from '../markdown';
import { fetchData, extractSection } from '../data-loader';
@ -9,51 +9,45 @@ export interface ArticleProps {
onError?: (error: Error) => void;
}
async function fetchArticleContent(params: { src: string; section?: string }): Promise<string> {
const text = await fetchData(params.src);
// 如果指定了 section提取对应内容
return params.section ? extractSection(text, params.section) : text;
}
/**
* Article
* src md markdown
*/
export const Article: Component<ArticleProps> = (props) => {
const [content, setContent] = createSignal('');
const [loading, setLoading] = createSignal(true);
const [error, setError] = createSignal<Error | null>(null);
const [content, { refetch }] = createResource(
() => ({ src: props.src, section: props.section }),
fetchArticleContent
);
let articleRef: HTMLArticleElement | undefined;
onMount(async () => {
setLoading(true);
try {
const text = await fetchData(props.src);
// 如果指定了 section提取对应内容
const finalContent = props.section
? extractSection(text, props.section)
: text;
setContent(finalContent);
setLoading(false);
createEffect(() => {
const data = content();
if (data) {
props.onLoaded?.();
} catch (err) {
const errorObj = err instanceof Error ? err : new Error(String(err));
setError(errorObj);
setLoading(false);
props.onError?.(errorObj);
}
});
onCleanup(() => {
// 清理时清空内容,触发内部组件的销毁
setContent('');
});
return (
<article ref={articleRef} class="prose" data-src={props.src}>
<Show when={loading()}>
<Show when={content.loading}>
<div class="text-gray-500">...</div>
</Show>
<Show when={error()}>
<div class="text-red-500">{error()?.message}</div>
<Show when={content.error}>
<div class="text-red-500">{content.error?.message}</div>
</Show>
<Show when={!loading() && !error()}>
<div innerHTML={parseMarkdown(content())} />
<Show when={!content.loading && !content.error && content()}>
<div innerHTML={parseMarkdown(content()!)} />
</Show>
</article>
);

View File

@ -1,5 +1,5 @@
import { customElement, noShadowDOM } from 'solid-element';
import { createSignal, For, Show, createEffect, createMemo } from 'solid-js';
import { createSignal, For, Show, createEffect, createMemo, createResource } from 'solid-js';
import { parse } from 'csv-parse/browser/esm/sync';
import { marked } from '../markdown';
@ -10,12 +10,14 @@ interface TableRow {
[key: string]: string;
}
// 全局缓存已加载的 CSV 内容
const csvCache = new Map<string, TableRow[]>();
customElement('md-table', { roll: false, remix: false }, (props, { element }) => {
noShadowDOM();
const [rows, setRows] = createSignal<TableRow[]>([]);
const [activeTab, setActiveTab] = createSignal(0);
const [activeGroup, setActiveGroup] = createSignal<string | null>(null);
const [loaded, setLoaded] = createSignal(false);
const [bodyHtml, setBodyHtml] = createSignal('');
// 从 element 的 textContent 获取 CSV 路径
@ -41,34 +43,36 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
const resolvedSrc = resolvePath(articlePath, src);
// 解析 CSV 内容
const parseCSV = (content: string) => {
// 加载 CSV 文件的函数
const loadCSV = async (path: string): Promise<TableRow[]> => {
// 先从缓存获取
if (csvCache.has(path)) {
return csvCache.get(path)!;
}
const response = await fetch(path);
const content = await response.text();
const records = parse(content, {
columns: true,
comment: '#',
trim: true,
skipEmptyLines: true
});
setRows(records as TableRow[]);
setLoaded(true);
const result = records as TableRow[];
// 缓存结果
csvCache.set(path, result);
return result;
};
// 加载 CSV 文件
const loadCSV = async () => {
try {
const response = await fetch(resolvedSrc);
const content = await response.text();
parseCSV(content);
} catch (error) {
console.error('Failed to load CSV:', error);
setLoaded(true);
// 使用 createResource 加载 CSV自动响应路径变化并避免重复加载
const [csvData] = createResource(() => resolvedSrc, loadCSV);
// 当数据加载完成后更新 rows
createEffect(() => {
const data = csvData();
if (data) {
setRows(data);
}
};
// 初始化加载
if (!loaded()) {
loadCSV();
}
});
// 检测是否有 group 列
const hasGroup = createMemo(() => {
@ -119,7 +123,7 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
// 更新 body 内容
const updateBodyContent = () => {
const filtered = filteredRows();
if (loaded() && filtered.length > 0) {
if (!csvData.loading && filtered.length > 0) {
const index = Math.min(activeTab(), filtered.length - 1);
const currentRow = filtered[index];
setBodyHtml(processBody(currentRow.body, currentRow));
@ -207,7 +211,7 @@ customElement('md-table', { roll: false, remix: false }, (props, { element }) =>
</div>
</div>
<div class="p-1 prose max-w-none">
<Show when={loaded() && filteredRows().length > 0}>
<Show when={!csvData.loading && filteredRows().length > 0}>
<div innerHTML={bodyHtml()} />
</Show>
</div>