当只有一个效果的deps改变时,React useEffect Hook,而不是其他的

问题描述 投票:1回答:3

我有一个使用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>
  );
}

该组件在mount上获取一些items并将它们保存到状态。

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

每当props.itemId发生变化时,我希望这会触发一个效果,在这种情况下将其记录到控制台。


问题是,由于效果也依赖于items,每当items改变时效果也会运行,例如当按下按钮重新获取items时。

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


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


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

javascript reactjs react-hooks
3个回答
3
投票

React团队表示,获得prev值的最佳方法是使用useRef:https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state

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

  const prevItemIdRef = useRef();
  useEffect(() => {
    prevItemIdRef.current = props.itemId;
  });
  const prevItemId = prevItemIdRef.current;

  // 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(prevItemId !== props.itemId) {
      console.log('diff itemId');
    }

    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>
  );
}

我认为这对你的情况有帮助。

注意:如果您不需要以前的值,另一种方法是为props.itemId写一个useEffect

React.useEffect(() => {
  console.log('track changes for itemId');
}, [props.itemId]);

0
投票

我自己试过这个,在我看来,你不需要把东西放在useEffect依赖列表中,以便获得更新版本。意思是你可以只放入props.itemId并仍然使用items效果。

我在这里创建了一个片段,试图证明/说明这一点。如果出现问题,请告诉我。

const Child = React.memo(props => {
  const [items, setItems] = React.useState([]);
  const fetchItems = () => {
    setTimeout(() => {
      setItems((old) => {
        const newItems = [];
        for (let i = 0; i < old.length + 1; i++) {
          newItems.push(i);
        }
        return newItems;
      })
    }, 1000);
  }
  
  React.useEffect(() => {
    console.log('OLD (logs on both buttons) id:', props.id, 'items:', items.length);
  }, [props.id, items]);
  
  React.useEffect(() => {
    console.log('NEW (logs on only the red button) id:', props.id, 'items:', items.length);
  }, [props.id]);

  return (
    <div
      onClick={fetchItems}
      style={{
        width: "200px",
        height: "100px",
        marginTop: "12px",
        backgroundColor: 'orange',
        textAlign: "center"
      }}
    >
      Click me to add a new item!
    </div>
  );
});

const Example = () => {
  const [id, setId] = React.useState(0);

  const updateId = React.useCallback(() => {
    setId(old => old + 1);
  }, []);

  return (
    <div style={{ display: "flex", flexDirection: "row" }}>
      <Child
        id={id}
      />
      <div
        onClick={updateId}
        style={{
          width: "200px",
          height: "100px",
          marginTop: "12px",
          backgroundColor: 'red',
          textAlign: "center"
        }}
      >Click me to update the id</div>
    </div>
  );
};

ReactDOM.render(<Example />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>

<div id='root' style='width: 100%; height: 100%'>
</div>

0
投票

从提供的示例中,您的效果不依赖于itemsitemId,而是该集合中的一个项目。

是的,您需要itemsitemId来获取该项目,但这并不意味着您必须在依赖项数组中指定它们。

要确保仅在目标项更改时执行,您应该使用相同的查找逻辑将该项传递给依赖项数组。

useEffect(() => {
  if (items) {
    const item = items.find(item => item.id === props.itemId);
    console.log("Item changed to " item.name);
  }
}, [ items.find(item => item.id === props.itemId) ])
© www.soinside.com 2019 - 2024. All rights reserved.