我有一个使用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。钩子是一种新事物,我想我们都在努力弄清楚如何正确地使用它们,所以如果我对你的思考方式对你来说似乎是错误或尴尬的,请指出它。
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]);
我自己试过这个,在我看来,你不需要把东西放在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>
从提供的示例中,您的效果不依赖于items
和itemId
,而是该集合中的一个项目。
是的,您需要items
和itemId
来获取该项目,但这并不意味着您必须在依赖项数组中指定它们。
要确保仅在目标项更改时执行,您应该使用相同的查找逻辑将该项传递给依赖项数组。
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) ])