React useCallback使用教程
折桂怀橘 人气:0开始之前请注意这句话:任何优化都会增加复杂性,任何过早添加的优化都会带来风险,因为优化后的代码可能会多次更改
useEffect
相关作用:监听 & 初始化
//最简单用法 useEffect(() => { //只有方法体,相当于componentDidMount和componentDidUpdate中的代码 document.title = count; }) //加返回值用法 useEffect(() => { //添加监听事件,相当于componentDidMount和componentDidUpdate中的代码 window.addEventListener('resize', onChange, false); //返回的函数用于解绑事件,相当于componentWillUnmount中的代码 return () => { window.removeEventListener('resize', onChange, false) } }) //加空数组参数用法 useEffect(() => { // 相当于 componentDidMount window.addEventListener('resize', onChange, false) return () => { // 相当于 componentWillUnmount window.removeEventListener('resize', onChange, false) } }, []); //加监听值用法 useEffect(() => { //只有当count的值发生变化,此函数才会执行 console.log(`count change: count is ${count}`) }, [ count ]);
useCallback
先看一个最简单的例子:
// 用于记录 getData 调用次数 let count = 0; function App() { const [val, setVal] = useState(""); function getData() { setTimeout(()=>{ setVal('new data '+count); count++; }, 500) } useEffect(()=>{ getData(); }, []); return ( <div>{val}</div> );}
getData模拟发起网络请求。在这种场景下,没有useCallback什么事,组件本身是高内聚的。
如果涉及到组件通讯,情况就不一样了:
// 用于记录 getData 调用次数 let count = 0; function App() { const [val, setVal] = useState(""); function getData() { setTimeout(() => { setVal("new data " + count); count++; }, 500); } return <Child val={val} getData={getData} />;}function Child({val, getData}) { useEffect(() => { getData(); }, [getData]); return <div>{val}</div>;}
就这么轻轻松松,一个死循环就诞生了…
先来分析下这段代码的用意,Child组件是一个纯展示型组件,其业务逻辑都是通过外部传进来的,这种场景在实际开发中很常见。
再分析下代码的执行过程:
- App渲染Child,将val和getData传进去
- Child使用useEffect获取数据。因为对getData有依赖,于是将其加入依赖列表
- getData执行时,调用setVal,导致App重新渲染
- App重新渲染时生成新的getData方法,传给Child
- Child发现getData的引用变了,又会执行getData
- 3 -> 5 是一个死循环
如果明确getData只会执行一次,最简单的方式当然是将其从依赖列表中删除。但如果装了 hook 的lint 插件,会提示:React Hook useEffect has a missing dependency
useEffect(() => { getData();}, []);
实际情况很可能是当getData改变的时候,是需要重新获取数据的。这时就需要通过useCallback来将引用固定住:
const getData = useCallback(() => { setTimeout(() => { setVal("new data " + count); count++; }, 500);}, []);
上面例子中getData的引用永远不会变,因为他它的依赖列表是空。可以根据实际情况将依赖加进去,就能确保依赖不变的情况下,函数的引用保持不变。
还有一个要注意的是
在开始监听一个鼠标的移动的时候,想要删除这个监听不生效,由于是, 加入useState导致组件再次渲染 handleMouse 函数在次渲染, handleMouse 作为组件内的方法, 会跟着一同再次渲染, 并且在内存里, 再次渲染出的 clickFunc !== 前clickFunc.
所以removeEventListener无法解除绑定, 再次addEventListener则会绑定一个新方法.
document.addEventListener('mousemove',handleMouse,true)
解决方案 : useCallback 缓存改方法 这时候的 document.removeEventListener(‘mousemove’,handleMouse,true) 中的handleMouse 和添加中的方法就是一个了,就能删除了。
onst handleMouse= useCallback(() => { //xxxx console.log("clicking"); }, []);
加载全部内容