本质上也是一个观察者模式的运用
"use client"; // React 19 / Next.js 必备,标记为客户端组件
import { useSyncExternalStore, useEffect } from "react";
// ==========================================
// 1. 核心层:Query (保持不变 - 逻辑纯粹)
// ==========================================
interface QueryState<T> {
data: T | undefined;
status: "loading" | "success" | "error";
error: any;
}
class Query<T> {
queryKey: string;
queryFn: () => Promise<T>;
state: QueryState<T>;
subscribers: Set<() => void>;
promise: Promise<T> | null;
constructor(key: string, fn: () => Promise<T>) {
this.queryKey = key;
this.queryFn = fn;
this.subscribers = new Set();
this.promise = null;
this.state = { data: undefined, status: "loading", error: null };
}
// 这里的 subscribe 签名完全符合 useSyncExternalStore 的要求
subscribe = (callback: () => void) => {
this.subscribers.add(callback);
return () => {
this.subscribers.delete(callback);
};
};
// 获取当前快照 (Snapshot)
getSnapshot = () => {
return this.state;
};
setState(updater: (old: QueryState<T>) => QueryState<T>) {
this.state = updater(this.state);
this.subscribers.forEach((cb) => cb());
}
fetch() {
if (this.promise) return this.promise;
this.setState((s) => ({ ...s, status: "loading" }));
this.promise = this.queryFn()
.then((data) => {
this.setState((s) => ({ ...s, data, status: "success" }));
return data;
})
.catch((error) => {
this.setState((s) => ({ ...s, error, status: "error" }));
throw error;
})
.finally(() => {
this.promise = null;
});
return this.promise;
}
}
// ==========================================
// 2. 管理层:QueryClient (保持不变)
// ==========================================
export class QueryClient {
queries: Map<string, Query<any>>;
constructor() {
this.queries = new Map();
}
getQuery<T>(key: string[], fn: () => Promise<T>): Query<T> {
const queryHash = JSON.stringify(key);
if (!this.queries.has(queryHash)) {
this.queries.set(queryHash, new Query(queryHash, fn));
}
return this.queries.get(queryHash)!;
}
}
// ==========================================
// 3. 适配层:useQuery Hook (React 19 升级版)
// ==========================================
export const client = new QueryClient();
export function useQuery<T>(key: string[], fn: () => Promise<T>) {
// 1. 获取 Query 实例
const query = client.getQuery(key, fn);
// 2. 使用 React 官方的外部存储同步钩子
// 参数1: subscribe 函数 (当 store 变化时通知 React)
// 参数2: getSnapshot 函数 (React 获取当前数据的快照)
const state = useSyncExternalStore(
query.subscribe,
query.getSnapshot
);
// 3. 触发请求的副作用
// 注意:useSyncExternalStore 只负责"读"和"更新视图","发起请求"依然是副作用
useEffect(() => {
// 只有当:正在 loading + 没数据 + 没有正在进行的请求 时,才发起
if (state.status === "loading" && !state.data && !query.promise) {
query.fetch();
}
}, [query, state.status, state.data]);
return state;
}