原文链接:www.developerway.com/posts/fanta…
问题
从useCallback入手
背景:在form表单中,有一个不得不依赖的很重的外部组件,这个组件需要传递两个属性,title和onclick,onlick的作用的是打印表单中的值。
- 因为组件很重,所以我们使用了react.memo来缓存避免无效渲染。
- 因为react.memo中的组件需要保证所有函数props被缓存了,memo才生效,所以我们使用useCallback来包裹onclick。
- 我们知道在useCallback中的变量都需要被添加到依赖,否则useCallback中的东西将永远不会更新,所以在useCallback的依赖中我们添加了value。
const HeavyComponentMemo = React.memo(HeavyComponent);
const Form = () => {
const [value, setValue] = useState();
const onClick = useCallback(() => {
// submit data here
console.log(value);
// adding value to the dependency
}, [value]);
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<HeavyComponentMemo
title="Welcome to the form"
onClick={onClick}
/>
</>
);
};
这段代码看起来很平常,但问题在于,用户一输入,value就变了,意味着onClick的缓存便失效了,这样的话useCallback看起来并没有发挥作用,因此,HeavyComponent外层的memo也就失效了。
从react.memo入手
我们知道,react.memo可以有第二个参数,用来手动定义props的比较方法。我们可以下面这样:忽略onClick的比较。
const HeavyComponentMemo = React.memo(
HeavyComponent,
(before, after) => {
return before.title === after.title;
},
);
完整代码如下:
const HeavyComponentMemo = React.memo(
HeavyComponent,
(before, after) => {
return before.title === after.title;
},
);
const Form = () => {
const [value, setValue] = useState();
const onClick = () => {
// submit our form data here
console.log(value);
};
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<HeavyComponentMemo
title="Welcome to the form"
onClick={onClick}
/>
</>
);
};
现在HeavyComponent被真正的缓存了,onClick已经影响不到HeavyComponent了。但是你会发现,如论你在input怎么输入,点击onClick中打印出来的,用于都是undefined,这就引出了今天的主角:闭包。
原因分析:闭包
在react中,我们一直在无意识的创造闭包,react中的任何一个函数其实都形成了闭包。
const Component = () => {
const [state, setState] = useState();
const onClick1 = () => {
// 闭包!
console.log(state); // 正常打印
};
const onClick2 = useCallback(() => {
// 闭包!
console.log(state); // 正常打印
});
const onClick3 = useCallback(() => {
// 闭包!
console.log(state); // 这个闭包永远不会更新,永远只会打印undefined
// 没加依赖
}, []);
useEffect(() => {
// 闭包!
console.log(state); // 正常打印
});
return <>
<input
type="text"
value={state}
onChange={(e) => setValue(e.target.value)}
/>
<button onClick={onClick} />
</>;
};
最后的解决办法
const Form = () => {
const [value, setValue] = useState();
const ref = useRef();
useEffect(() => {
ref.current = () => {
// 可以获取到最新value
console.log(value);
};
// 因为下面没有加依赖,所以每次都会更新ref
});
const onClick = useCallback(() => {
// 可以获取到最新ref
ref.current?.();
}, []);
return (
<>
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
<HeavyComponentMemo
title="Welcome closures"
onClick={onClick}
/>
</>
);
};