在此线程中,我想使用react钩子演示每个想法。
问题是当使用react来构建应用程序时,如果需要异步提取,提取调用的触发应该是什么?
作为示例,我们有一个MemberList
组件,其中包括一个<input>
来键入filter关键字,一个<button>
来按关键字过滤列表结果,以及一个<ul>
来显示每个成员的详细信息:
<div>
<header>
<input value={keyword} onChange={syncKeywordToState} />
<button onClick={handleFilter}>Filter Members</button>
</header>
{
isLoading
? (
<ul>
{data.map(m => <li key={m.id}>{m.name}</li>)}
</ul>
)
: <Loading />
}
</div>
还提供了listMember(keyword: string) => Promise<Member[]>
函数,用于通过关键字加载成员,当抓取未决时,我们应该显示加载动画而不是列表。
结果,问题变为“何时应该调用listMember
函数并触发加载动画”。
最直接的想法是在单击按钮时触发提取,可能是:
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState('');
const [isLoading, setLoading] = useState(true);
const handleFilter = useCallback(
async () => {
const data = await listMember(keyword);
setMembers(data);
setLoading(false);
},
[listMember, keyword]
);
return (
<div>
...
</div>
);
};
这在使用时单击按钮时效果很好,但是在组件安装时它将不会获取初始成员列表,我们需要一个与较旧的componentDidMount
生命周期相对应的效果,因为react没有提供内置的{{ 1}}钩子,我们必须小心地做到这一点:
useDidMount
我不认为这是一种鼓励的模式,useEffect(
async () => {
setLoading(true);
const data = await listMember(keyword);
setMembers(data);
setLoading(false);
},
[true] // The didMount trick
);
钩子强烈告诉我们不要区分useEffect
和didMount
,因此有一个独立的didUpdate
应该努力避免效果。
第二种可能的解决方案是在状态变化时触发获取:
didMount
这很简单:
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState('');
const [filterKeyword, setFilterKeyword] = usetState('');
const handleFilter = () => setFilterKeyword(keyword); // Apply keyword to filter
useEffect(
async () => {
setLoading(true);
const data = await listMember(filterKeyword);
setMembers(data);
setLaoding(false);
},
[listMember, filterKeyword]
);
return (
<div>
...
</div>
);
}
作为keyword
状态。filterKeyword
的参数已更改,因此应使用新值来调用它。我发现此解决方案在概念上很明确,但是仍然存在一些问题:
listMember
在这里无济于事,这可能会导致fetch调用意外复制。最后一个概念是“我们只是在没有数据时才获取数据”,在涉及缓存的系统中,状态更改是“更改获取参数”的触发条件,而不是“缺少获取结果”的触发条件我们可能会遇到这样的情况,即使更改了过滤器,结果列表仍然有效:
useMemo
在这种情况下,当用户单击按钮时,const cacheReducer = (caches, {key, value}) => {
return {
...caches,
[key]: {
time: Date.now(),
value,
},
};
};
// Cache valid for 5 seconds
const isDataValid = ({time, value}) => !value || (time - Date.now() > 5000);
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState('');
const [filterKeyword, setFilterKeyword] = usetState('');
const [caches, addCache] = useReducer({});
const data = caches[filterKeyword];
const handleFilter = () => setFilterKeyword(keyword); // Apply keyword to filter
useEffect(
async () => {
if (!isDataValid(data)) {
setLoading(true);
const data = await listMember(filterKeyword);
addCache({key: filterKeyword, value: data});
setLaoding(false);
}
},
[listMember, filterKeyword, data]
);
return (
<div>
...
</div>
);
}
发生了变化,但我们还验证了缓存的数据,仅在数据无效(未找到或已过期)时才提取数据。
这是一个更精细的模型,用于管理参数,提取及其触发器之间的关系。
这3个解决方案在实现方式上并不仅仅是彼此不同,它们是更多的概念模型,我想知道最好选择哪种方法。
答案 0 :(得分:0)
您的评论后,我了解了更多。无论如何,我不同意您无法复制componentDidMount
的行为,因为even the docs表示
如果您要运行一个效果并仅将其清理一次(在挂载和卸载时),则可以将空数组([])作为第二个参数传递
首先,从您的第一个Hooks代码段开始(以下)
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState('');
const [isLoading, setLoading] = useState(true);
const handleFilter = useCallback(
async () => {
const data = await listMember(keyword);
setMembers(data);
setLoading(false);
},
[listMember, keyword]
);
return (
<div>
...
</div>
);
};
您写了
但是在组件的安装上它将不会获取初始成员列表,我们需要一个与较旧的componentDidMount相对应的效果
那不是100%正确的...如果使用useCallback
是正确的,但是如果使用useEffect
,您将得到与componentDidMount
相同的行为。
看看my codesandbox,代码如下:
import React, { useState, useCallback, useEffect } from "react";
const MemberList = () => {
const [members, setMembers] = useState([]);
const [keyword, setKeyword] = useState("");
const [isLoading, setLoading] = useState(true);
const handleFilter = useEffect(
async () => {
console.log("useCallback");
},
[/*listMember, */ keyword]
);
return <div>...</div>;
};
export default MemberList;
我更改了一下是因为:
listMember
函数,因此我将其替换为console.log
useCallback
对您要实现的目标没有用,您希望在过滤器更改时调用listMember
函数,但是useCallback
仅返回一个已记忆的函数...它不会调用它! (请参见docs)useEffect
实现了您的目标,每次keyword
过滤器更改时,它都会调用传递的函数。它总是在第一次挂载后调用(如果您查看控制台,则会找到我的日志)listMember
参数,因为当您尝试避免副作用ID触发时,您必须使用值,而不是引用!即使函数总是一样,它也可以在每次渲染时改变(尤其是在React上)...因此副作用被重新触发,因为严格的===相等表明它已经改变(对于函数,即使是==会返回该值,但我想向您解释其中的区别)...因此,例如,不要使用数组,而是映射其值,不要使用对象,而要使用它们的ID ...就可以使用它们。显然,但是您需要知道您在做什么。通过keyword
就足够了。对于您的其余问题,我不清楚您要问的是什么,因为您报告了一些具有不同行为的不同示例……您在问哪个是最好的……?