例如,在单击按钮时,我很难决定如何强制地 触发API调用。
我不确定采用钩子的正确方法是什么,因为似乎有不止一种方法,但是我不知道哪种方法是“最佳”方法以及最终的含义。
我发现以下示例非常简单,可以满足我的要求:
function SomeFunctionComponent() {
const [fakeData, setFakeData] = useState(0);
const [trigger, setTrigger] = useState(false);
async function fetchData() {
if (!trigger) return;
const newData = await someAPI.fetch();
setTrigger(false);
setFakeData(newData);
}
useEffect(() => {
fetchData();
}, [trigger]);
return (
<React.Fragment>
<p>{fakeData}</p>
<button onClick={() => setTrigger(!trigger)}>Refresh</button>
</React.Fragment>
);
}
function SomeFunctionComponent() {
const [fakeData, setFakeData] = useState(0);
async function fetchData() {
const newData = await someAPI.fetch();
setFakeData(newData);
}
return (
<React.Fragment>
<p>{fakeData}</p>
<button onClick={fetchData}>Refresh</button>
</React.Fragment>
);
}
还有其他利用useCallback()的方法,但是据我所知,它们在避免向下传递回调时避免重新渲染子组件很有用,它等效于第二个示例。
我认为useEffect方法仅在必须在组件安装和上以编程方式运行某些东西时才有用,但实际上具有触发副作用的虚拟值看起来很冗长。
仅仅调用函数看起来就足够实用和简单了,但是我不确定在渲染过程中是否允许函数组件执行副作用。
哪种方法最常见,最正确的方法是在React中使用钩子进行命令式调用?
答案 0 :(得分:3)
当我试图找出最好的书写方式时,我要做的第一件事就是看我如何使用它。对于您的情况,此代码:
<React.Fragment>
<p>{fakeData}</p>
<button onClick={fetchData}>Refresh</button>
</React.Fragment>
似乎是最直接,最简单的。 <button onClick={() => setTrigger(!trigger)}>Refresh</button>
之类的东西掩盖了您的实现细节。
关于您的问题,请注意“我不确定在渲染过程中是否允许功能组件执行副作用”。 ,该功能组件在渲染期间不会产生副作用,因为当您单击按钮时不会发生渲染。只有当您调用setFakeData
时,渲染才会真正发生。在这方面,实现1和实现2之间没有实际区别,因为只有在您调用setFakeData
时,渲染才会发生。
开始进一步推广时,您可能会希望将此实现全部更改为更通用的内容,例如:
function useApi(action,initial){
const [data,setData] = useState({
value:initial,
loading:false
});
async function doLoad(...args){
setData({
value:data.value,
loading:true
});
const res = await action(...args);
setData({
value:res,
loading:false
})
}
return [data.value,doLoad,data.loading]
}
function SomeFunctionComponent() {
const [data,doLoad,loading] = useApi(someAPI.fetch,0)
return <React.Fragment>
<p>{data}</p>
<button onClick={doLoad}>Refresh</button>
</React.Fragment>
}
答案 1 :(得分:3)
接受的答案实际上确实破坏了rules of hooks。由于单击是 Asynchronous ,这意味着在获取调用期间可能会发生其他渲染,这将创建 SideEffects ,并可能创建可怕的 Invalid Hook Call Warning
我们可以通过在调用setState()
函数之前检查组件是否已安装来对其进行修复。下面是我的解决方案,它很容易使用。
挂钩功能
function useApi(actionAsync, initialResult) {
const [loading, setLoading] = React.useState(false);
const [result, setResult] = React.useState(initialResult);
const [fetchFlag, setFetchFlag] = React.useState(0);
React.useEffect(() => {
if (fetchFlag == 0) {
// Run only after triggerFetch is called
return;
}
let mounted = true;
setLoading(true);
actionAsync().then(res => {
if (mounted) {
// Only modify state if component is still mounted
setLoading(false);
setResult(res);
}
})
// Signal that compnoent has been 'cleaned up'
return () => mounted = false;
}, [fetchFlag])
function triggerFetch() {
// Set fetchFlag to indirectly trigger the useEffect above
setFetchFlag(Math.random());
}
return [result, triggerFetch, loading];
}
在React挂钩中的使用
function MyComponent() {
async function fetchUsers() {
const data = await fetch("myapi").then((r) => r.json());
return data;
}
const [fetchResult, fetchTrigger, fetchLoading] = useApi(fetchUsers, null);
return (
<div>
<button onClick={fetchTrigger}>Refresh Users</button>
<p>{fetchLoading ? "Is Loading" : "Done"}</p>
<pre>{JSON.stringify(fetchResult)}</pre>
</div>
);
}