什么会触发获取反应?用户操作,状态更改或缺少有效数据?

时间:2019-02-18 05:08:36

标签: reactjs react-hooks

在此线程中,我想使用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 ); 钩子强烈告诉我们不要区分useEffectdidMount,因此有一个独立的didUpdate应该努力避免效果。

状态更改

第二种可能的解决方案是在状态变化时触发获取:

didMount

这很简单:

  1. 用户单击按钮时,将提供当前输入值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状态。
  2. 由于filterKeyword的参数已更改,因此应使用新值来调用它。
  3. 因此触发了获取操作。

我发现此解决方案在概念上很明确,但是仍然存在一些问题:

  1. 它会导致同步状态更新生效,我们在早期版本的react(no-did-update-set-state)中难以避免
  2. 如果fetch的参数更复杂,例如数组或对象,则引用相等可能会失败,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个解决方案在实现方式上并不仅仅是彼此不同,它们是更多的概念模型,我想知道最好选择哪种方法。

1 个答案:

答案 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就足够了。

对于您的其余问题,我不清楚您要问的是什么,因为您报告了一些具有不同行为的不同示例……您在问哪个是最好的……?