我正在尝试使用新的 Hooks 从类组件转向功能组件。但是,感觉与useCallback
类组件中的类函数不同,使用I 将获得不必要的子级渲染。
下面我有两个相对简单的片段。第一个是我编写为类的示例,第二个是我将示例重写为功能组件。目标是让功能组件获得与类组件相同的行为。
类组件测试用例
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>
在第一个(类组件)中,我可以通过红色块更新计数而无需重新渲染任何一个块,并且我可以通过橙色块自由控制台记录当前计数。
在第二个(功能组件)中,通过红色块更新计数将触发红色和青色块的重新渲染。这是因为useCallback
将创建其函数的新实例,因为计数已更改,导致块获得新的onClick
props并因此重新渲染。橙色块不会重新渲染,因为useCallback
用于橙色的onClick
不依赖于计数值。这会很好,但是当您单击橙色块时,它不会显示计数的实际值。
我认为useCallback
这样做的目的是为了让孩子们不会获得相同函数的新实例,也不会进行不必要的重新渲染,但这似乎发生在回调函数使用单个变量的第二个,如果并非总是根据我的经验。
那么我将如何onClick
在一个功能组件中创建这个函数而不让孩子重新渲染?有可能吗?
更新(解决方案):
使用下面 Ryan Cogswell 的回答,我制作了一个自定义钩子,可以轻松创建类函数。
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]);