gpt4 book ai didi

reactjs - React Hooks useCallback 导致子进程重新渲染

转载 作者:行者123 更新时间:2023-12-03 13:29:13 26 4
gpt4 key购买 nike

我正在尝试使用新的 Hooks 从类组件转向功能组件。然而,与类组件中的类函数不同,我感觉使用 useCallback 会得到不必要的子级渲染。

下面我有两个相对简单的片段。第一个是我编写为类的示例,第二个是我重写为功能组件的示例。目标是使函数组件获得与类组件相同的行为。

类组件测试用例

class Block extends React.PureComponent {
render() {
console.log("Rendering block: ", this.props.color);

return (
<div onClick={this.props.onBlockClick}
style = {
{
width: '200px',
height: '100px',
marginTop: '12px',
backgroundColor: this.props.color,
textAlign: 'center'
}
}>
{this.props.text}
</div>
);
}
};

class Example extends React.Component {
state = {
count: 0
}


onClick = () => {
console.log("I've been clicked when count was: ", this.state.count);
}

updateCount = () => {
this.setState({ count: this.state.count + 1});
};

render() {
console.log("Rendering Example. Count: ", this.state.count);

return (
<div style={{ display: 'flex', 'flexDirection': 'row'}}>
<Block onBlockClick={this.onClick} text={'Click me to log the count!'} color={'orange'}/>
<Block onBlockClick={this.updateCount} text={'Click me to add to the count'} color={'red'}/>
</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>

功能组件测试用例

const Block = React.memo((props) => {
console.log("Rendering block: ", props.color);

return (
<div onClick={props.onBlockClick}
style = {
{
width: '200px',
height: '100px',
marginTop: '12px',
backgroundColor: props.color,
textAlign: 'center'
}
}>
{props.text}
</div>
);
});

const Example = () => {
const [ count, setCount ] = React.useState(0);
console.log("Rendering Example. Count: ", count);

const onClickWithout = React.useCallback(() => {
console.log("I've been clicked when count was: ", count);
}, []);

const onClickWith = React.useCallback(() => {
console.log("I've been clicked when count was: ", count);
}, [ count ]);

const updateCount = React.useCallback(() => {
setCount(count + 1);
}, [ count ]);

return (
<div style={{ display: 'flex', 'flexDirection': 'row'}}>
<Block onBlockClick={onClickWithout} text={'Click me to log with empty array as input'} color={'orange'}/>
<Block onBlockClick={onClickWith} text={'Click me to log with count as input'} color={'cyan'}/>
<Block onBlockClick={updateCount} text={'Click me to add to the count'} color={'red'}/>
</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>

在第一个(类组件)中,我可以通过红色 block 更新计数,而无需重新渲染任何一个 block ,并且我可以通过橙色 block 自由地控制台记录当前计数。

在第二个(功能组件)中,通过红色 block 更新计数将触发红色和青色 block 的重新渲染。这是因为 useCallback 将创建其函数的新实例,因为计数已更改,导致 block 获得新的 onClick 属性,从而重新渲染。橙色 block 不会重新渲染,因为用于橙色 onClickuseCallback 不依赖于计数值。这很好,但是当您单击橙色 block 时,它不会显示计数的实际值。

我认为使用 useCallback 的目的是让 children 不会获得相同函数的新实例,也不会进行不必要的重新渲染,但无论如何,这种情况似乎都会在第二个函数发生时发生。回调函数使用单个变量,根据我的经验,这种情况经常发生(如果不是总是如此)。

那么我该如何在功能组件中创建这个 onClick 函数而不让子组件重新渲染呢?这有可能吗?

更新(解决方案):使用下面 Ryan Cogswell 的答案,我制作了一个自定义 Hook ,可以轻松创建类似类的函数。

const useMemoizedCallback = (callback, inputs = []) => {
// Instance var to hold the actual callback.
const callbackRef = React.useRef(callback);

// The memoized callback that won't change and calls the changed callbackRef.
const memoizedCallback = React.useCallback((...args) => {
return callbackRef.current(...args);
}, []);

// The callback that is constantly updated according to the inputs.
const updatedCallback = React.useCallback(callback, inputs);

// The effect updates the callbackRef depending on the inputs.
React.useEffect(() => {
callbackRef.current = updatedCallback;
}, inputs);

// Return the memoized callback.
return memoizedCallback;
};

然后我可以像这样在函数组件中非常轻松地使用它,并将 onClick 传递给子组件。它将不再重新渲染子级,但仍然使用更新的变量。

const onClick = useMemoizedCallback(() => {
console.log("NEW I've been clicked when count was: ", count);
}, [count]);

最佳答案

useCallback 将避免由于父级中的某些内容发生更改而导致不必要的子级重新渲染,而这些更改属于回调的依赖项。为了避免在涉及回调依赖项时子进程重新渲染,您需要使用 ref。 Ref 是相当于实例变量的钩子(Hook)。

下面我有 onClickMemoized 使用 onClickRef 指向当前 onClick (通过 useEffect 设置),所以它委托(delegate)给知道状态当前值的函数版本。

我还更改了 updateCount 以使用功能更新语法,这样它就不需要依赖于 count

const Block = React.memo(props => {
console.log("Rendering block: ", props.color);

return (
<div
onClick={props.onBlockClick}
style={{
width: "200px",
height: "100px",
marginTop: "12px",
backgroundColor: props.color,
textAlign: "center"
}}
>
{props.text}
</div>
);
});

const Example = () => {
const [count, setCount] = React.useState(0);
console.log("Rendering Example. Count: ", count);

const onClick = () => {
console.log("I've been clicked when count was: ", count);
};
const onClickRef = React.useRef(onClick);
React.useEffect(
() => {
// By leaving off the dependency array parameter, it means that
// this effect will execute after every committed render, so
// onClickRef.current will stay up-to-date.
onClickRef.current = onClick;
}
);

const onClickMemoized = React.useCallback(() => {
onClickRef.current();
}, []);

const updateCount = React.useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);

return (
<div style={{ display: "flex", flexDirection: "row" }}>
<Block
onBlockClick={onClickMemoized}
text={"Click me to log with empty array as input"}
color={"orange"}
/>
<Block
onBlockClick={updateCount}
text={"Click me to add to the count"}
color={"red"}
/>
</div>
);
};

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

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

当然,钩子(Hook)的美妙之处在于您可以将这种有状态逻辑分解到自定义钩子(Hook)中:

import React from "react";
import ReactDOM from "react-dom";

const Block = React.memo(props => {
console.log("Rendering block: ", props.color);

return (
<div
onClick={props.onBlockClick}
style={{
width: "200px",
height: "100px",
marginTop: "12px",
backgroundColor: props.color,
textAlign: "center"
}}
>
{props.text}
</div>
);
});

const useCount = () => {
const [count, setCount] = React.useState(0);

const logCount = () => {
console.log("I've been clicked when count was: ", count);
};
const logCountRef = React.useRef(logCount);
React.useEffect(() => {
// By leaving off the dependency array parameter, it means that
// this effect will execute after every committed render, so
// logCountRef.current will stay up-to-date.
logCountRef.current = logCount;
});

const logCountMemoized = React.useCallback(() => {
logCountRef.current();
}, []);

const updateCount = React.useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []);
return { count, logCount: logCountMemoized, updateCount };
};
const Example = () => {
const { count, logCount, updateCount } = useCount();
console.log("Rendering Example. Count: ", count);

return (
<div style={{ display: "flex", flexDirection: "row" }}>
<Block
onBlockClick={logCount}
text={"Click me to log with empty array as input"}
color={"orange"}
/>
<Block
onBlockClick={updateCount}
text={"Click me to add to the count"}
color={"red"}
/>
</div>
);
};

const rootElement = document.getElementById("root");
ReactDOM.render(<Example />, rootElement);

Edit useCallback and useRef

关于reactjs - React Hooks useCallback 导致子进程重新渲染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55045566/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com