繁体   English   中英

当只有一个效果的 deps 发生变化时,反应 useEffect Hook,而不是其他的

[英]React useEffect Hook when only one of the effect's deps changes, but not the others

我有一个使用 Hooks 的功能组件:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, []);

  // I want this effect to run only when 'props.itemId' changes,
  // not when 'items' changes
  useEffect(() => {
    if (items) {
      const item = items.find(item => item.id === props.itemId);
      console.log("Item changed to " item.name);
    }
  }, [ items, props.itemId ])

  // Clicking the button should NOT log anything to console
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

该组件在挂载时获取一些items并将它们保存到 state。

该组件接收一个itemId道具(来自 React Router)。

每当props.itemId更改时,我希望它触发效果,在这种情况下将其记录到控制台。


问题是,由于效果也依赖于items ,所以效果也会在items发生变化时运行,例如当通过按下按钮重新获取items时。

这可以通过将先前的props.itemId存储在单独的 state 变量中并比较两者来解决,但这似乎是一种 hack 并添加了样板。 使用 Component 类可以通过比较componentDidUpdate中的当前和以前的 props 来解决,但是使用功能组件是不可能的,这是使用 Hooks 的要求。


仅当其中一个参数发生变化时,触发依赖于多个参数的效果的最佳方法是什么?


PS。 Hooks 是一种新事物,我认为我们都在尽最大努力弄清楚如何正确使用它们,所以如果我的思考方式对你来说似乎是错误的或尴尬的,请指出。

我有一个使用Hooks的功能组件:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, []);

  // I want this effect to run only when 'props.itemId' changes,
  // not when 'items' changes
  useEffect(() => {
    if (items) {
      const item = items.find(item => item.id === props.itemId);
      console.log("Item changed to " item.name);
    }
  }, [ items, props.itemId ])

  // Clicking the button should NOT log anything to console
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

该组件会获取安装中的某些items并将其保存到状态。

该组件接收一个itemId (来自React Router)。

每当props.itemId更改时,我都希望它触发一个效果,在这种情况下,将其记录到控制台。


问题是,既然效果也依赖于items ,效果也将运行时items的变化,例如当items是重新获取的按下按钮。

可以通过将先前的props.itemId存储在单独的状态变量中并进行比较来解决此问题,但这似乎很简单,并且增加了样板。 通过使用Component类,可以通过比较componentDidUpdate当前和先前的道具来解决,但是使用功能组件是不可能的,这是使用Hooks的要求。


仅当其中一个参数发生更改时,触发取决于多个参数的效果的最佳方法是什么?


PS。 挂钩是一种新事物,我想我们所有人都在尽力找出如何正确使用挂钩的功能,因此,如果我的想法对您来说似乎是错误的或尴尬的,请指出。

我有一个使用Hooks的功能组件:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, []);

  // I want this effect to run only when 'props.itemId' changes,
  // not when 'items' changes
  useEffect(() => {
    if (items) {
      const item = items.find(item => item.id === props.itemId);
      console.log("Item changed to " item.name);
    }
  }, [ items, props.itemId ])

  // Clicking the button should NOT log anything to console
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

该组件会获取安装中的某些items并将其保存到状态。

该组件接收一个itemId (来自React Router)。

每当props.itemId更改时,我都希望它触发一个效果,在这种情况下,将其记录到控制台。


问题是,既然效果也依赖于items ,效果也将运行时items的变化,例如当items是重新获取的按下按钮。

可以通过将先前的props.itemId存储在单独的状态变量中并进行比较来解决此问题,但这似乎很简单,并且增加了样板。 通过使用Component类,可以通过比较componentDidUpdate当前和先前的道具来解决,但是使用功能组件是不可能的,这是使用Hooks的要求。


仅当其中一个参数发生更改时,触发取决于多个参数的效果的最佳方法是什么?


PS。 挂钩是一种新事物,我想我们所有人都在尽力找出如何正确使用挂钩的功能,因此,如果我的想法对您来说似乎是错误的或尴尬的,请指出。

我有一个使用Hooks的功能组件:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, []);

  // I want this effect to run only when 'props.itemId' changes,
  // not when 'items' changes
  useEffect(() => {
    if (items) {
      const item = items.find(item => item.id === props.itemId);
      console.log("Item changed to " item.name);
    }
  }, [ items, props.itemId ])

  // Clicking the button should NOT log anything to console
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

该组件会获取安装中的某些items并将其保存到状态。

该组件接收一个itemId (来自React Router)。

每当props.itemId更改时,我都希望它触发一个效果,在这种情况下,将其记录到控制台。


问题是,既然效果也依赖于items ,效果也将运行时items的变化,例如当items是重新获取的按下按钮。

可以通过将先前的props.itemId存储在单独的状态变量中并进行比较来解决此问题,但这似乎很简单,并且增加了样板。 通过使用Component类,可以通过比较componentDidUpdate当前和先前的道具来解决,但是使用功能组件是不可能的,这是使用Hooks的要求。


仅当其中一个参数发生更改时,触发取决于多个参数的效果的最佳方法是什么?


PS。 挂钩是一种新事物,我想我们所有人都在尽力找出如何正确使用挂钩的功能,因此,如果我的想法对您来说似乎是错误的或尴尬的,请指出。

我有一个使用Hooks的功能组件:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, []);

  // I want this effect to run only when 'props.itemId' changes,
  // not when 'items' changes
  useEffect(() => {
    if (items) {
      const item = items.find(item => item.id === props.itemId);
      console.log("Item changed to " item.name);
    }
  }, [ items, props.itemId ])

  // Clicking the button should NOT log anything to console
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

该组件会获取安装中的某些items并将其保存到状态。

该组件接收一个itemId (来自React Router)。

每当props.itemId更改时,我都希望它触发一个效果,在这种情况下,将其记录到控制台。


问题是,既然效果也依赖于items ,效果也将运行时items的变化,例如当items是重新获取的按下按钮。

可以通过将先前的props.itemId存储在单独的状态变量中并进行比较来解决此问题,但这似乎很简单,并且增加了样板。 通过使用Component类,可以通过比较componentDidUpdate当前和先前的道具来解决,但是使用功能组件是不可能的,这是使用Hooks的要求。


仅当其中一个参数发生更改时,触发取决于多个参数的效果的最佳方法是什么?


PS。 挂钩是一种新事物,我想我们所有人都在尽力找出如何正确使用挂钩的功能,因此,如果我的想法对您来说似乎是错误的或尴尬的,请指出。

我有一个使用Hooks的功能组件:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, []);

  // I want this effect to run only when 'props.itemId' changes,
  // not when 'items' changes
  useEffect(() => {
    if (items) {
      const item = items.find(item => item.id === props.itemId);
      console.log("Item changed to " item.name);
    }
  }, [ items, props.itemId ])

  // Clicking the button should NOT log anything to console
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

该组件会获取安装中的某些items并将其保存到状态。

该组件接收一个itemId (来自React Router)。

每当props.itemId更改时,我都希望它触发一个效果,在这种情况下,将其记录到控制台。


问题是,既然效果也依赖于items ,效果也将运行时items的变化,例如当items是重新获取的按下按钮。

可以通过将先前的props.itemId存储在单独的状态变量中并进行比较来解决此问题,但这似乎很简单,并且增加了样板。 通过使用Component类,可以通过比较componentDidUpdate当前和先前的道具来解决,但是使用功能组件是不可能的,这是使用Hooks的要求。


仅当其中一个参数发生更改时,触发取决于多个参数的效果的最佳方法是什么?


PS。 挂钩是一种新事物,我想我们所有人都在尽力找出如何正确使用挂钩的功能,因此,如果我的想法对您来说似乎是错误的或尴尬的,请指出。

我有一个使用Hooks的功能组件:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, []);

  // I want this effect to run only when 'props.itemId' changes,
  // not when 'items' changes
  useEffect(() => {
    if (items) {
      const item = items.find(item => item.id === props.itemId);
      console.log("Item changed to " item.name);
    }
  }, [ items, props.itemId ])

  // Clicking the button should NOT log anything to console
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

该组件会获取安装中的某些items并将其保存到状态。

该组件接收一个itemId (来自React Router)。

每当props.itemId更改时,我都希望它触发一个效果,在这种情况下,将其记录到控制台。


问题是,既然效果也依赖于items ,效果也将运行时items的变化,例如当items是重新获取的按下按钮。

可以通过将先前的props.itemId存储在单独的状态变量中并进行比较来解决此问题,但这似乎很简单,并且增加了样板。 通过使用Component类,可以通过比较componentDidUpdate当前和先前的道具来解决,但是使用功能组件是不可能的,这是使用Hooks的要求。


仅当其中一个参数发生更改时,触发取决于多个参数的效果的最佳方法是什么?


PS。 挂钩是一种新事物,我想我们所有人都在尽力找出如何正确使用挂钩的功能,因此,如果我的想法对您来说似乎是错误的或尴尬的,请指出。

基于前面的答案,并受到react-use 的 useCustomCompareEffect implementation 的启发,我继续编写useGranularEffect钩子来解决类似的问题:

// I want this effect to run only when 'props.itemId' changes,
// not when 'items' changes
useGranularEffect(() => {
  if (items) {
    const item = items.find(item => item.id === props.itemId);
    console.log("Item changed to " item.name);
  }
}, [ items ], [ props.itemId ])

实现为(打字稿):

export const useGranularEffect = (
  effect: EffectCallback,
  primaryDeps: DependencyList,
  secondaryDeps: DependencyList
) => {
  const ref = useRef<DependencyList>();

  if (!ref.current || !primaryDeps.every((w, i) => Object.is(w, ref.current[i]))) {
    ref.current = [...primaryDeps, ...secondaryDeps];
  }

  // eslint-disable-next-line react-hooks/exhaustive-deps
  return useEffect(effect, ref.current);
};

在codeandbox上试试

的签名useGranularEffect相同useEffect ,除了依赖的名单已分成两个:

  1. 主要依赖项:效果仅在这些依赖项更改时运行
  2. 次要依赖项:效果中使用的所有其他依赖项

在我看来,它使仅在某些依赖项更改时才运行效果的情况更易于阅读。

笔记:

  • 不幸的是,没有 linting 规则可以帮助您确保这两个依赖项数组是详尽无遗的,因此您有责任确保没有遗漏任何
  • 忽略useGranularEffect实现中的useGranularEffect警告是安全的,因为effect不是实际的依赖项(它是 effect 函数本身)并且ref.current包含所有依赖项的列表(主要 + 次要, ref.current无法猜测)
  • 我正在使用 Object.is 来比较依赖项,以便它与useEffect的行为一致,但可以随意使用您自己的比较函数,或者更好的是添加一个比较器作为参数

2022答案

我知道这是一个老问题,但仍然值得回答。 特效在今年受到了很多关注,现在你应该/不应该用特效做什么比当时更清楚了。 仍处于测试阶段的新 React 文档详细涵盖了该主题。

因此,在回答“如何”之前,您必须首先回答“为什么”,因为实际上只有在某些依赖项发生变化时才运行效果的真正用例并不多。

你可能不需要效果

您可能不需要效果”实际上是页面的标题,表明您可能出于错误的原因使用了效果。 你给我们的例子实际上是在' 当道具改变时调整一些 state '

您的代码可以简单地重写而没有第二种效果:

function Component(props) {
  const [ items, setItems ] = useState([]);

  // In a callback Hook to prevent unnecessary re-renders 
  const handleFetchItems = useCallback(() => {
    fetchItemsFromApi().then(setItems);
  }, []);

  // Fetch items on mount
  useEffect(() => {
    handleFetchItems();
  }, [handleFetchItems]);

  // Find the item that matches in the list
  const item = items.find(item => item.id === props.itemId);
  
  return (
    <Button onClick={handleFetchItems}>Fetch items</Button>
  );
}

实际上,您不需要一个效果来识别列表中的项目。 React 已经在items更改时重新渲染,因此可以直接在组件中重新计算item

注意:如果您真的想在项目更改时登录到控制台,您仍然可以将此代码添加到您的组件中:

  useEffect(() => {
    console.log("Item changed to ", item?.name);
  }, [item])

请注意item是 effect 的唯一依赖项,因此它仅在item更改时运行。 但这又不是一个效果的好用例。

相反,如果您想对应用程序中发生的事件做出响应,您应该这样做

将事件与效果分离

是的,“ 从效果中分离事件”也是新 React 文档中页面的标题,在您给我们的示例中,您希望在item更改时写入控制台。 但我并不天真,我知道你可能想在练习中做更多的事情。

如果你想在其他事情发生变化做某事,那么你可以使用新的useEvent钩子为它创建一个事件处理程序(或者至少使用它的 polyfill,因为它还没有发布)。 这里有问题的事件是itemId道具的变化。

然后,您可以将第二个效果更改为:

// Handle a change of item ID
const onItemIdChange = useEvent((itemId) => {
  const item = items?.find(item => item.id === itemId);
  console.log("Item changed to " item.name);
});

// Trigger onItemIdChange when item ID changes
useEffect(() => {
  onItemIdChange(props.itemId);
}, [props.itemId]);

这种方法的优点是useEvent返回一个稳定的function,所以你不需要将它作为依赖添加到你的效果中(它永远不会改变)。 此外,传递给useEvent的 function 没有依赖数组:它可以访问组件内的所有属性、状态或变量,并且它始终是新鲜的(没有陈旧的值)。

我仍然认为根据你给我们的例子,你真的不需要效果。 但是对于其他真实情况,至少您现在知道如何从效果中提取事件,因此您可以避免破解依赖数组。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM