useController()
Controller 提供类型安全的方法,用于访问 store 并向其分发 action。
例如 fetch、invalidate 和 setResponse
import { useCallback } from 'react';
import { useController } from '@data-client/react';
import { MyResource } from './resources';
function MyComponent({ id }: { id: string }) {
const ctrl = useController();
const handleRefresh = useCallback(
async e => {
await ctrl.fetch(MyResource.get, { id });
},
[ctrl, id],
);
const handleSuspend = useCallback(
async e => {
await ctrl.invalidate(MyResource.get, { id });
},
[ctrl, id],
);
const handleLogout = useCallback(
async e => {
ctrl.resetEntireStore();
},
[ctrl],
);
}
示例
表单提交
fetch 返回反规范化后的响应,与 useSuspense() 的返回类型一致。这样就可以使用 pk() 等 Entity 方法。
import type { FormEvent } from 'react';
import { useNavigate } from 'react-router';
import { useController } from '@data-client/react';
import { PostResource } from './PostResource';
function CreatePost() {
const ctrl = useController();
const navigate = useNavigate();
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
const post = await ctrl.fetch(
PostResource.getList.push,
new FormData(e.target as HTMLFormElement),
);
post.title;
post.computedField;
navigate(`/post/${post.pk()}`);
};
return <form onSubmit={handleSubmit}>{/* fields */}</form>;
}
直接更新 Entity
使用 set 可以在不发起网络请求的情况下立即更新。它支持函数式更新,以避免竞态条件。
import { useController } from '@data-client/react';
import { Article } from './Article';
function VoteButton({ articleId }: { articleId: string }) {
const ctrl = useController();
return (
<button
onClick={() =>
ctrl.set(Article, { id: articleId }, article => ({
...article,
votes: article.votes + 1,
}))
}
>
Vote
</button>
);
}
变更后使数据失效
使用 invalidate 或 expireAll 强制重新获取相关数据。
import { useController } from '@data-client/react';
import { UserResource } from './UserResource';
function ClearUserCache({ userId }: { userId: string }) {
const ctrl = useController();
const handleClear = async () => {
// invalidate() causes suspense; expireAll() refetches silently
ctrl.expireAll(UserResource.get);
ctrl.expireAll(UserResource.getList);
};
return <button onClick={handleClear}>Refresh user data</button>;
}
提示
为了获得更好的性能和一致性,优先在变更响应中包含副作用更新。
预取
使用 fetchIfStale 进行预取,而不会重复获取仍然新鲜的数据。
import { Link } from 'react-router';
import { useController } from '@data-client/react';
import { ArticleResource } from './ArticleResource';
function ArticleLink({ id }: { id: string }) {
const ctrl = useController();
return (
<Link
to={`/article/${id}`}
onMouseEnter={() => ctrl.fetchIfStale(ArticleResource.get, { id })}
>
Read more
</Link>
);
}
Websocket 更新
通过 set 用外部数据填充缓存。
import { useEffect } from 'react';
import { useController } from '@data-client/react';
import { EntityMap } from './resources';
function useWebsocket(url: string) {
const ctrl = useController();
useEffect(() => {
const ws = new WebSocket(url);
ws.onmessage = event => {
const { entity, args, data } = JSON.parse(event.data);
ctrl.set(EntityMap[entity], args, data);
};
return () => ws.close();
}, [ctrl, url]);
}
注意
在生产环境中,请实现一个用于数据流的 Manager,而不是使用组件级的 effect。Manager 会在全局处理连接的生命周期,并且支持 SSR。
Todo 应用
探索 todo-app 示例