useEffect 使用指南

书接上文:juejin.cn/post/739520... 继续翻译 Dan Abramov的文章 overreacted.io/a-complete-... 文章采用意译,融合了我自己的理解,欢迎大家留言讨论。

🤔 如何在 useEffect 中正确地获取数据?什么是 []?

这篇文章是关于使用 useEffect 进行数据获取的好入门指南。一定要读到最后!它没有这篇文章长。[] 表示该 effect 不使用任何参与 React 数据流的值,因此可以安全地只应用一次。依赖项要如实加入,不能对 React 撒谎。 两种减少依赖项的方法:

  • setCount(c => c + 1)

  • 使用 useReducer

🤔 问题:我需要将函数作为 effect 的依赖项指定吗?

  • 建议是将不需要 props 或 state 的函数提升到组件外部。

  • 将只在 effect 内使用的函数移到该 effect 内。

  • effect 使用了渲染作用域中的函数(包括从 props 中传递的函数),使用 useCallback 包裹

告诉 React 区分 Effects

当我们更新代码,从:

js 复制代码
<h1 className="Greeting"> Hello, Dan</h1>

变为:

js 复制代码
<h1 className="Greeting"> Hello, Yuzhi</h1>

React 会比较如下两个对象(虚拟DOM):

js 复制代码
const oldProps = {className: 'Greeting', children: 'Hello, Dan'};
const newProps = {className: 'Greeting', children: 'Hello, Yuzhi'};

它会遍历每个属性,确定 children 发生了变化,需要更新 DOM,但 className 没有变化。所以它只需要执行:

js 复制代码
domNode.innerText = 'Hello, Yuzhi';
// 不需要触碰 domNode.className

那么,effects 也可以实现类似的效果吗?如果 effects 没有改变,则没有必要重新执行它们。

例如,我们的组件可能因为状态变化而重新渲染:

js 复制代码
  function Greeting({ name }) {
    const [counter, setCounter] = useState(0);
  
    useEffect(() => {
      document.title = 'Hello, ' + name;
    });
  
    return (
      <h1 className="Greeting">
        Hello, {name}
        <button onClick={() => setCounter(counter + 1)}>
          Increment
        </button>
      </h1>
    );
  }

这里effects没有使用 counter 状态。在 effects 中,我们只是同步 document.titlename 属性,但 name 属性是相同的。每次 counter 变化时重新赋值 document.title 显然是没必要的。

那么,React 能不能区分 Effects 呢?

js 复制代码
let oldEffect = () => { document.title = 'Hello, Dan'; }; 
let newEffect = () => { document.title = 'Hello, Dan'; }; 
// React 能不能看到这些函数做了同样的事情?

并不能。React 无法在不调用函数的情况下猜测函数会做什么。(源代码并不真正包含具体值,它只是闭包了 name 属性。)

如果我们想想避免不必要地重新运行效果,可以给 useEffect 提供一个依赖数组(也叫"deps")参数:

js 复制代码
  useEffect(() => {
    document.title = 'Hello, ' + name;
  }, [name]); // 依赖数组

就像我们告诉 React:"嘿,我知道你看不见这个函数内部,但我保证它只使用 name,而且没有其他的渲染作用域的变量。"

如果这些值每次效果运行时都是相同的,那么就没有什么需要同步的,React 可以跳过这个效果:

js 复制代码
const oldEffect = () => { document.title = 'Hello, Dan'; };
const oldDeps = ['Dan'];

const newEffect = () => { document.title = 'Hello, Dan'; };
const newDeps = ['Dan'];

// React 无法窥探函数内部,但它可以比较依赖项。
// 因为所有依赖项都是相同的,所以它不需要运行新的效果。

如果依赖数组中的任一值在渲染之间不同,我们就知道不能跳过运行 effects,需要同步所有的东西!

依赖项不要对 Rect 撒谎

例如,我们正在写一个每秒递增的计数器。对于一个类组件,我们的直觉是:"设置一次 interval 并销毁一次"。当我们用 useEffect 翻译这段代码时,我们本能地在依赖项中添加 []。"我想让它运行一次",对吗? 文章 juejin.cn/post/693020... 详细说明了该问题。

js 复制代码
function Counter() {
  const [count, setCount] = useState(0);
 
  useEffect(() => {
    const id = setInterval(() => {
      setCount(count + 1);
    }, 1000);
    return () => clearInterval(id);
  }, []);
 
  return <h1>{count}</h1>;
}

然而,这么写 count 只递增一次!

如果你的思维模式是"依赖项让我指定何时重新触发 effects",则这个例子会反应这种思维模式的问题。

依赖项的用途是告诉 React,effects 使用了哪些依赖。并且,我们的代码不应该对 React 撒谎。

在第一次渲染中,count 是 0。因此,在第一次渲染的效果中的 setCount(count + 1) 意味着 setCount(0 + 1)[] 依赖项使得 effects 并不会重新执行,它将每秒调用 setCount(0 + 1)

js 复制代码
// 第一次渲染,状态是 0
function Counter() {
  // ...
  useEffect(
    // 第一次渲染的效果
    () => {
      const id = setInterval(() => {
        setCount(0 + 1); // 总是 setCount(1)
      }, 1000);
      return () => clearInterval(id);
    },
    [] // 永不重新运行
  );
  // ...
}
 
// 每次后续渲染,状态是 1
function Counter() {
  // ...
  useEffect(
    // 这个效果总是被忽略,因为
    // 我们对 React 撒谎了,使用了空依赖项。
    () => {
      const id = setInterval(() => {
        setCount(1 + 1);
      }, 1000);
      return () => clearInterval(id);
    },
    []
  );
  // ...
}

我们对 React 撒谎,我们的 effects 依赖了组件内的值 count,但是我们并没有加入到依赖项 [] 中。

解决方法:提供了一个 lint 规则强制执行这一点:始终诚实地指定 effects 依赖项,并指定所有依赖项。

两种方法对依赖项保持诚实

第一种方法是:把 effects用到组件内部的值都放到依赖项中。

js 复制代码
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(id);
}, [count]);

依赖数组正确了。虽然可能不是最理想的,但是我们解决了刚说的问题。现在对 count 的更改将重新运行 effect,每个下一个间隔引用 setCount(count + 1) 中的 count

js 复制代码
// 第一次渲染,状态是 0
function Counter() {
  // ...
  useEffect(
    // 第一次渲染的 effect
    () => {
      const id = setInterval(() => {
        setCount(0 + 1); // setCount(count + 1)
      }, 1000);
      return () => clearInterval(id);
    },
    [0] // [count]
  );
  // ...
}

// 第二次渲染,状态是 1
function Counter() {
  // ...
  useEffect(
    // 第二次渲染的 effect
    () => {
      const id = setInterval(() => {
        setCount(1 + 1); // setCount(count + 1)
      }, 1000);
      return () => clearInterval(id);
    },
    [1] // [count]
  );
  // ...
}

这可以解决问题,但我们的定时器 interval 会在 count 变化时被清除并重新设置。这是不理想的:

第二种策略是修改我们的 effect 代码,使其减少依赖项。我们不能对依赖项撒谎------我们只是希望通过减少依赖来改变我们的 effect。

几种常见的减少依赖的方法如下:

让 Effects 自给自足

我们想要去掉 effect 中对 count 的依赖。

js 复制代码
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(id);
}, [count]);

为此,我们需要问自己:我们使用 count 是为了什么?看起来我们只是在 setCount 调用中使用了它。在这种情况下,我们实际上不需要在作用域中使用 count。当我们想要根据之前的状态更新状态时,可以使用 setState 的函数更新形式:

js 复制代码
useEffect(() => {
  const id = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
  return () => clearInterval(id);
}, []);

我喜欢把这些情况看作"伪依赖"。是的,count 是一个必要的依赖,因为我们在 effect 内部写了 setCount(count + 1)。但是,我们实际上只需要 count 将其转换为 count + 1 并"传回"给 React。但 React 已经知道当前的 count。我们只需要告诉 React 增加状态------无论它现在是什么。

这正是 setCount(c => c + 1) 所做的。你可以把它看作是"发送一个指令"给 React,告诉它状态应该如何变化。这种"更新器形式"在其他情况下也有帮助,比如当你批量进行多个更新时。

请注意,我们实际上做了移除依赖的工作。我们没有作弊。我们的 effect 不再从渲染作用域读取计数器值:

即使这个 effect 只运行一次,属于第一次渲染的间隔回调完全能够在每次间隔触发时发送 c => c + 1 更新指令。它不再需要知道当前的计数器状态。React 已经知道它。

Actions 中解耦操作

修改之前的例子,使用两个状态变量:count 和 step。定时器 interval 将根据 step 输入的值增加 count:

js 复制代码
function Counter() {
  const [count, setCount] = useState(0);
  const [step, setStep] = useState(1);

  useEffect(() => {
    const id = setInterval(() => {
      setCount(c => c + step);
    }, 1000);
    return () => clearInterval(id);
  }, [step]);

  return (
    <>
      <h1>{count}</h1>
      <input value={step} onChange={e => setStep(Number(e.target.value))} />
    </>
  );
}

请注意,我们没有作弊。在 effect 中使用 step 后,所以将其添加到了依赖项中。这就是代码运行正确的原因。

当前示例中的行为是修改 step 会导致重新启动定时器 interval,因为它是依赖项之一。

但是,假设我们希望更改 step 定时器 interval 不会重置。我们怎么才能从 effect 中移除 step 依赖项?

当设置一个状态变量依赖于另一个状态变量的当前值时,你可能想尝试用 useReducer 替换它们。

当你发现自己在编写 setSomething(something => ...) 时,考虑使用 reducer 可能是个好时机。reducer 让你能够将组件中发生的 "操作" 与响应这些操作的状态更新解耦。

让我们在 effect 中用 dispatch 依赖项替换 step 依赖项:

js 复制代码
const [state, dispatch] = useReducer(reducer, initialState);
const { count, step } = state;
 
useEffect(() => {
  const id = setInterval(() => {
    dispatch({ type: 'tick' }); // 替代 setCount(c => c + step);
  }, 1000);
  return () => clearInterval(id);
}, [dispatch]);

你可能会问:"这有什么更好的呢?"答案是 React 保证 dispatch 函数在组件生命周期中是恒定的。所以上述示例永远不需要重新订阅定时器 interval。

我们解决了问题!

(你可以省略 dispatchsetStateuseRef 容器值的依赖项,因为 React 保证它们是静态的。但指定它们也无妨。)

代替在 effect 中读取状态,它会分派一个编码了发生了什么信息的操作。这使得我们的 effect 与 step 状态解耦。我们的 effect 不关心我们如何更新状态,它只告诉我们发生了什么。reducer 集中更新逻辑:

js 复制代码
const initialState = {
  count: 0,
  step: 1,
};
 
function reducer(state, action) {
  const { count, step } = state;
  if (action.type === 'tick') {
    return { count: count + step, step };
  } else if (action.type === 'step') {
    return { count, step: action.step };
  } else {
    throw new Error();
  }
}

demo 地址: codesandbox.io/p/sandbox/x...

使用 useReducer

我们已经学会了当一个 effect 需要基于一个状态来设置另一个状态时,如何去除依赖项。但是,如果我们需要根据 props 计算下一个状态呢 ?例如,我们的 API 可能是 <Counter step={1} />。在这种情况下,我们肯定无法避免将 props.step 作为依赖项吗?

实际上,我们可以通过将 reducer 放在组件内部来读取 props:

js 复制代码
function Counter({ step }) {
  const [count, dispatch] = useReducer(reducer, 0);
 
  function reducer(state, action) {
    if (action.type === 'tick') {
      return state + step;
    } else {
      throw new Error();
    }
  }
 
  useEffect(() => {
    const id = setInterval(() => {
      dispatch({ type: 'tick' });
    }, 1000);
    return () => clearInterval(id);
  }, [dispatch]);
 
  return <h1>{count}</h1>;
}

这种情况下,dispatch 的身份在重新渲染之间仍然是稳定的。我们也可以省略 dispatch 作为 effect 的依赖项。这不会导致 effect 重新运行。

你可能会想:这怎么可能工作?在属于另一个渲染的 effect 内部调用时,reducer 如何"知道" props?答案是,当你 dispatch 时,React 只是记住了 action,但它将在下次渲染期间调用你的 reducer。届时,新鲜的 props 将在作用域内,而不在 effect 内部

使用 useReducer 能够将更新逻辑与描述发生的事情分离开来。同时去除 effect 中不必要的依赖项,并避免不必要地重新运行它们。

将函数移到 Effect 内部

一个常见的错误是认为函数不应该作为依赖项。例如,这段代码看起来好像是可以工作的:

js 复制代码
function SearchResults() {
  const [data, setData] = useState({ hits: [] });
 
  async function fetchData() {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=react',
    );
    setData(result.data);
  }
 
  useEffect(() => {
    fetchData();
  }, []); // 这样可以吗?
 
  // ...
}

这个例子改编自 Robin Wieruch 的一篇优秀文章------可以去看看!www.robinwieruch.de/react-hooks...

明确地说,这段代码确实能工作。但简单地省略本地函数的问题在于,随着组件的增长,很难判断我们是否处理了所有情况!

假设我们的代码是这样分割的,并且每个函数都比现在长五倍:

js 复制代码
function SearchResults() {
  // 假设这个函数很长
  function getFetchUrl() {
    return 'https://hn.algolia.com/api/v1/search?query=react';
  }
 
  // 假设这个函数也很长
  async function fetchData() {
    const result = await axios(getFetchUrl());
    setData(result.data);
  }
 
  useEffect(() => {
    fetchData();
  }, []);
 
  // ...
}

现在假设我们后来在这些函数中使用了一些状态或 prop:

js 复制代码
function SearchResults() {
  const [query, setQuery] = useState('react');
 
  // 假设这个函数也很长
  function getFetchUrl() {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }
 
  // 假设这个函数也很长
  async function fetchData() {
    const result = await axios(getFetchUrl());
    setData(result.data);
  }
 
  useEffect(() => {
    fetchData();
  }, []);
 
  // ...
}

如果我们忘记更新调用这些函数的任意 effect 的依赖项(可能通过其他函数调用),我们的 effect 将无法同步来自我们的 props 和状态的更改。

幸运的是,有一个简单的解决方案。如果你只在 effect 内部使用某些函数,请直接将它们移到该 effect 中:

js 复制代码
function SearchResults() {
  // ...
  useEffect(() => {
    // 我们将这些函数移到了内部!
    function getFetchUrl() {
      return 'https://hn.algolia.com/api/v1/search?query=react';
    }
 
    async function fetchData() {
      const result = await axios(getFetchUrl());
      setData(result.data);
    }
 
    fetchData();
  }, []); // ✅ 依赖项是正确的
  // ...
}

这样做的好处是什么?我们不再需要考虑"传递依赖项"。我们的依赖数组不再撒谎:我们的 effect 确实没有使用组件外部的任何东西。

如果我们以后编辑 getFetchUrl 以使用 query 状态,我们更有可能注意到我们在 effect 内部编辑它------因此,我们需要将 query 添加到 effect 依赖项中:

js 复制代码
function SearchResults() {
  const [query, setQuery] = useState('react');
 
  useEffect(() => {
    function getFetchUrl() {
      return 'https://hn.algolia.com/api/v1/search?query=' + query;
    }
 
    async function fetchData() {
      const result = await axios(getFetchUrl());
      setData(result.data);
    }
 
    fetchData();
  }, [query]); // ✅ 依赖项是正确的
 
  // ...
}

通过添加这个依赖项,当 query 更改时重新获取数据是有意义的。useEffect 的设计迫使你注意到数据流中的变化,并选择我们的 effect 应该如何同步它------而不是忽略它,直到我们的产品用户遇到 bug。

感谢 eslint-plugin-react-hooks 插件中的 exhaustive-deps 规则,你可以在编辑器中键入代码时分析 effect 并接收有关缺失依赖项的建议。换句话说,机器可以告诉你组件未正确处理的数据流更改。

当函数不能移到 Effect 内部时

有时你可能不想将函数移到 effect 内。例如,同一个组件中的多个 effect 可能会调用相同的函数,而你不想复制和粘贴它的逻辑。或者它可能是一个 prop。

你应该在 effect 的依赖项中跳过这样的函数吗?我认为不应该。再次强调,effect 不应该对它们的依赖项撒谎。通常有更好的解决方案。一个常见的误解是"一个函数永远不会改变"。实际上,在组件内定义的函数在每次渲染时都会改变!

这本身就带来了一个问题。假设两个 effect 调用了 getFetchUrl

js 复制代码
function SearchResults() {
  function getFetchUrl(query) {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }
 
  useEffect(() => {
    const url = getFetchUrl('react');
    // ... Fetch data and do something ...
  }, []); // 🔴 缺少依赖:getFetchUrl
 
  useEffect(() => {
    const url = getFetchUrl('redux');
    // ... Fetch data and do something ...
  }, []); // 🔴 缺少依赖:getFetchUrl
 
  // ...
}

在这种情况下,你可能不想将 getFetchUrl 移到任何一个 effect 中,因为你不能共享逻辑。

另一方面,如果你对 effect 的依赖项保持"诚实",你可能会遇到问题。由于我们的两个 effect 都依赖于 getFetchUrl(每次渲染时都不同),我们的依赖数组变得无用:

js 复制代码
function SearchResults() {
  // 🔴 每次渲染都会重新触发所有的 effect
  function getFetchUrl(query) {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }
 
  useEffect(() => {
    const url = getFetchUrl('react');
    // ... Fetch data and do something ...
  }, [getFetchUrl]); // 🚧 依赖项是正确的,但它们变化太频繁
 
  useEffect(() => {
    const url = getFetchUrl('redux');
    // ... Fetch data and do something ...
  }, [getFetchUrl]); // 🚧 依赖项是正确的,但它们变化太频繁
 
  // ...
}

一个诱人的解决方案是跳过依赖列表中的 getFetchUrl 函数。然而,我认为这不是一个好的解决方案。这使得我们难以注意到我们添加了需要由 effect 处理的数据流更改。这导致了我们之前看到的"从不更新的定时器"这类 bug。

相反,有两个更简单的解决方案。

方案一:如果一个函数不使用组件范围内的任何内容,你可以将其提升到组件外部,然后在你的 effect 内部自由使用它:

js 复制代码
// ✅ 不受数据流的影响
function getFetchUrl(query) {
  return 'https://hn.algolia.com/api/v1/search?query=' + query;
}
 
function SearchResults() {
  useEffect(() => {
    const url = getFetchUrl('react');
    // ... Fetch data and do something ...
  }, []); // ✅ 依赖项是正确的
 
  useEffect(() => {
    const url = getFetchUrl('redux');
    // ... Fetch data and do something ...
  }, []); // ✅ 依赖项是正确的
 
  // ...
}

没有必要在依赖项中指定它,因为它不在渲染范围内,并且不会受到数据流的影响。它不会意外地依赖于 props 或 state。

方案二 :将函数包装到 useCallback Hook 中:

js 复制代码
function SearchResults() {
  // ✅ 当自身的依赖项相同时保留标识
  const getFetchUrl = useCallback((query) => {
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }, []);  // ✅ Callback 依赖项是正确的
 
  useEffect(() => {
    const url = getFetchUrl('react');
    // ... Fetch data and do something ...
  }, [getFetchUrl]); // ✅ Effect 依赖项是正确的
 
  useEffect(() => {
    const url = getFetchUrl('redux');
    // ... Fetch data and do something ...
  }, [getFetchUrl]); // ✅ Effect 依赖项是正确的
 
  // ...
}

useCallback 本质上就像添加了另一层依赖检查。它解决了另一个的问题------我们不是避免函数依赖,而是让函数本身只在必要时变化。 之前,我们的例子展示了两个搜索结果('react' 和 'redux' 搜索查询)。但是假设我们想添加一个输入,以便你可以搜索任意查询。因此,getFetchUrl 将不再接受查询作为参数,而是从本地状态中读取它。

我们会立即看到它缺少一个 query 依赖项:

js 复制代码
function SearchResults() {
  const [query, setQuery] = useState('react');
  const getFetchUrl = useCallback(() => { // 没有 query 参数
    return 'https://hn.algolia.com/api/v1/search?query=' + query;
  }, []); // 🔴 缺少依赖:query
  // ...
}

直接修复 useCallback 的依赖项以包括 query,那么任何包含 getFetchUrl 的 effect 依赖项都会在 query 更改时重新运行:

如果 query 是相同的,getFetchUrl 也会保持相同,并且我们的 effect 不会重新运行。但是如果 query 发生变化,getFetchUrl 也会变化,我们将重新获取数据。这很像当你更改 Excel 电子表格中的某个单元格时,使用它的其他单元格会自动重新计算。

这只是拥抱数据流和同步思维的结果。同样的解决方案也适用于从父组件传递的函数 prop:

js 复制代码
function Parent() {
  const [query, setQuery] = useState('react');
 
  // ✅ 在 query 更改之前保持标识
  const fetchData = useCallback(() => {
    const url = 'https://hn.algolia.com/api/v1/search?query=' + query;
    // ... Fetch data and return it ...
  }, [query]);  // ✅ Callback 依赖项是正确的
 
  return <Child fetchData={fetchData} />
}
 
function Child({ fetchData }) {
  let [data, setData] = useState(null);
 
  useEffect(() => {
    fetchData().then(setData);
  }, [fetchData]); // ✅ Effect 依赖项是正确的
 
  // ...
}

由于 fetchData 只在 Parentquery 状态变化时变化,所以我们的 Child 不会重新获取数据,除非对于应用程序来说确实有必要。

关于竞争条件

一个经典的类组件数据获取示例可能看起来像这样:

js 复制代码
class Article extends Component {
  state = {
    article: null
  };
  componentDidMount() {
    this.fetchData(this.props.id);
  }
  async fetchData(id) {
    const article = await API.fetchArticle(id);
    this.setState({ article });
  }
  // ...
}

如你所知,这段代码是有问题的。它没有处理更新。因此,你在网上可能找到的第二个经典示例是这样的:

js 复制代码
class Article extends Component {
  state = {
    article: null
  };
  componentDidMount() {
    this.fetchData(this.props.id);
  }
  componentDidUpdate(prevProps) {
    if (prevProps.id !== this.props.id) {
      this.fetchData(this.props.id);
    }
  }
  async fetchData(id) {
    const article = await API.fetchArticle(id);
    this.setState({ article });
  }
  // ...
}

这确实更好!但它仍然有问题。问题在于请求可能会乱序。如果我正在获取 {id: 10},切换到 {id: 20},但 {id: 20} 的请求先到,那么早先开始但后来完成的请求将错误地覆盖我的状态。

这就是所谓的竞争条件,在混合了 async/await(假设某些东西在等待结果)和自上而下的数据流(props 或 state 在我们进行异步函数中间时可能会改变)的代码中很常见。

effect 并不能神奇地解决这个问题,虽然它会在你尝试将异步函数直接传递给 effect 时发出警告。

如果你使用的异步方法支持取消,那很好!你可以在清理函数中取消异步请求。

或者,最简单的临时解决方法是用一个布尔值来跟踪:

js 复制代码
function Article({ id }) {
  const [article, setArticle] = useState(null);
 
  useEffect(() => {
    let didCancel = false;
 
    async function fetchData() {
      const article = await API.fetchArticle(id);
      if (!didCancel) {
        setArticle(article);
      }
    }
 
    fetchData();
 
    return () => {
      didCancel = true;
    };
  }, [id]);
 
  // ...
}

这篇文章详细介绍了如何处理错误和加载状态,以及如何将这些逻辑提取到自定义 Hook 中。如果你对使用 Hooks 进行数据获取感兴趣,我推荐你去看看。 www.robinwieruch.de/react-hooks...

提高标准

跟生命周期心智模型不一样,副作用的行为与渲染输出不同。渲染 UI 是由 props 和 state 驱动的,并且保证与它们一致,但副作用不是。这是一个常见的 bug 来源。

useEffect 的心智模型是,事情默认是同步的。副作用成为 React 数据流的一部分。对于每个 useEffect 调用,一旦你做对了,你的组件会更好地处理边缘情况。

然而,做对的前期成本更高。这可能会令人恼火。编写处理边缘情况良好的同步代码本质上比触发与渲染不一致的一次性副作用更困难。

如果 useEffect 是你大部分时间使用的工具,这可能会令人担忧。然而,它是一个低级构建块。对于 Hooks 来说,现在是一个早期阶段,所以每个人总是使用低级 Hooks,特别是在教程中。但在实践中,随着好的 API 获得关注,社区可能会开始转向高级 Hooks。

我看到不同的应用程序创建了自己的 Hooks,如 useFetch,它封装了一些应用程序的身份验证逻辑,或者 useTheme 使用主题上下文。一旦你有了这些工具箱,你就不常使用 useEffect 了。但它带来的弹性使每个构建在其上的 Hook 受益。

到目前为止,useEffect 最常用于数据获取。但数据获取并不是一个同步问题。这尤其明显,因为我们的依赖项通常是 []。我们在同步什么?

从长远来看,数据获取的 Suspense 将允许第三方库拥有一种一流的方法来告诉 React 暂停渲染,直到某些异步内容(任何东西:代码、数据、图像)准备就绪。

随着 Suspense 逐渐覆盖更多的数据获取用例,我预计 useEffect 将退居背景,作为一种在你实际想将 props 和 state 同步到某个副作用时的高级用户工具。与数据获取不同,它自然地处理这种情况,因为它是为此设计的。但在那之前,如本文所示的自定义 Hooks 是重用数据获取逻辑的好方法。

相关推荐
excel5 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子12 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构18 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep20 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss23 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风24 分钟前
html二次作业
前端·html
江城开朗的豌豆28 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵28 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae
画月的亮31 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf
江城开朗的豌豆37 分钟前
拆解Redux:从零手写一个状态管理器,彻底搞懂它的魔法!
前端·javascript·react.js