React Hooks使用要注意哪些?一文带你深入了解Hooks
Admin 2021-09-10 群英技术资讯 677 次浏览
今天给大家分享的是React Hooks使用要注意问题的内容,本文有实例和详细注释供大家参考,对大家理解Hooks的使用有一定的帮助,接下来跟随小编一起看看吧。
介绍Hooks之前,首先要给大家说一下React的组件创建方式,一种是类组件,一种是纯函数组件,并且React团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。也就是说组件的最佳写法应该是函数,而不是类。。
函数组件比类组件更加方便实现业务逻辑代码的分离和组件的复用,函数组件也比类组件轻量,没有react hooks之前,函数组件是无法实现LocalState的,这导致有localstate状态的组件无法用函数组件来书写,这限制了函数组件的应用范围,而react hooks扩展了函数组件的能力。可是在使用的过程中,也要注意下面这些问题,否则就会掉进坑里,造成性能损失。按照下面的方法做,,才能避开这些陷阱。
每次状态改变时,整个函数组件都会重新执行一遍。导致函数组件内部定义的方法和变量,都会重新创建,重新给它们分配内存,这会导致性能受到影响。
import React, {useState,useCallback} from "react"; // 测试每次状态改变时,方法是不是重新分配内存 let testFooMemoAlloc = new Set(); const Page = (props:any) => { console.log('每次状态改变,函数组件从头开始执行') const [count, setCount] = useState(0); const calc = () => { setCount(count + 1); } const bar = { a:1, b:2, c: '与状态无关的变量定义' } const doFoo = () => { console.log('与状态无关的方法'); } testFooMemoAlloc.add(doFoo) return ( <> <button onClick={calc}>加1</button> <p>count:{count}</p> <p>testFooMemoAlloc.size增加的话,说明每次都重新分配了内存:{testFooMemoAlloc.size}</p> </> ) } export default Page;
与改变状态相关的变量和方法,必须放在hooks组件内,而无状态无关的变量和方法,可以提取到函数组件外,避免每次状态更新,都重新分配内存。也可以分别使用useMemo和useCallback包裹变量与函数,也能达到同样的效果,后面会讲。
import React, {useState,useCallback} from "react"; // 测试每次状态改变时,方法是不是重新分配内存 let testFooMemoAlloc = new Set(); const bar = { a:1, b:2, c: '与状态无关的变量定义' } const doFoo = () => { console.log('与状态无关的方法'); } const Page = (props:any) => { console.log('每次状态改变,函数组件从头开始执行') const [count, setCount] = useState(0); const calc = () => { setCount(count + 1); } testFooMemoAlloc.add(doFoo) return ( <> <button onClick={calc}>加1</button> <p>count:{count}</p> <p>testFooMemoAlloc.size增加的话,说明每次都重新分配了内存:{testFooMemoAlloc.size}</p> </> ) } export default Page;
父组件引入子组件,会造成一些不必要的重复渲染,每次父组件更新count,子组件都会更新。
import React,{useState} from "react"; const Child = (props:any) => { console.log('子组件?') return( <div>我是一个子组件</div> ); } const Page = (props:any) => { const [count, setCount] = useState(0); return ( <> <button onClick={(e) => { setCount(count+1) }}>加1</button> <p>count:{count}</p> <Child /> </> ) } export default Page;
使用memo,count变化子组件没有更新
import React,{useState,memo} from "react"; const Child = memo((props:any) => { console.log('子组件?') return( <div>我是一个子组件</div> ); }) const Page = (props:any) => { const [count, setCount] = useState(0); return ( <> <button onClick={(e) => { setCount(count+1) }}>加1</button> <p>count:{count}</p> <Child /> </> ) } export default Page;
给memo传入第二个参数,开启对象深度比较。当子组件传递的属性值未发生改变时,子组件不会做无意义的render。
memo不仅适用于函数组件,也适用于class组件,是一个高阶组件,默认情况下只会对复杂对象做浅层比较,如果想做深度比较,可以传入第二个参数。与shouldComponentUpdate
不同的是,deepCompare返回true
时,不会触发 render,如果返回false
,则会。而shouldComponentUpdate
刚好与其相反。
import React, {useState, memo } from "react"; import deepCompare from "./deepCompare"; const Child = memo((props:any) => { console.log('子组件') return ( <> <div>我是一个子组件</div> <div>{ props.fooObj.a}</div> </> ); }, deepCompare) const Page = (props:any) => { const [count, setCount] = useState(0); const [fooObj, setFooObj] = useState({ a: 1, b: { c: 2 } }) console.log('页面开始渲染') const calc = () => { setCount(count + 1); if (count === 3) { setFooObj({ b: { c: 2 }, a: count }) } } const doBar = () => { console.log('给子组件传递方法,测试一下是否会引起不必须的渲染') } return ( <> <button onClick={calc}>加1</button> <p>count:{count}</p> <Child fooObj={fooObj} doBar={doBar} /> </> ) } export default Page;
// 深度比较两个对象是否相等 export default function deepCompare(prevProps: any, nextProps: any) { const len: number = arguments.length; let leftChain: any[] = []; let rightChain: any = []; // // console.log({ arguments }); // if (len < 2) { // console.log('需要传入2个对象,才能进行两个对象的属性对比'); return true; } // for (let i = 1; i < len; i++) { // leftChain = []; // rightChain = []; console.log({ prevProps, nextProps }); if (!compare2Objects(prevProps, nextProps, leftChain, rightChain)) { // console.log('两个对象不相等'); return false; } // } // console.log('两个对象相等'); return true; } function compare2Objects(prevProps: any, nextProps: any, leftChain: any, rightChain: any) { var p; // 两个值都为为NaN时,在js中是不相等的, 而在这里认为相等才是合理的 if (isNaN(prevProps) && isNaN(nextProps) && typeof prevProps === 'number' && typeof nextProps === 'number') { return true; } // 原始值比较 if (prevProps === nextProps) { console.log('原始值', prevProps, nextProps); return true; } // 构造类型比较 if ( (typeof prevProps === 'function' && typeof nextProps === 'function') || (prevProps instanceof Date && nextProps instanceof Date) || (prevProps instanceof RegExp && nextProps instanceof RegExp) || (prevProps instanceof String && nextProps instanceof String) || (prevProps instanceof Number && nextProps instanceof Number) ) { console.log('function', prevProps.toString() === nextProps.toString()); return prevProps.toString() === nextProps.toString(); } // 两个比较变量的值如果是null和undefined,在这里会退出 if (!(prevProps instanceof Object && nextProps instanceof Object)) { console.log(prevProps, nextProps, 'prevProps instanceof Object && nextProps instanceof Object'); return false; } if (prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)) { console.log('prevProps.isPrototypeOf(nextProps) || nextProps.isPrototypeOf(prevProps)'); return false; } // 构造器不相等则两个对象不相等 if (prevProps.constructor !== nextProps.constructor) { console.log('prevProps.constructor !== nextProps.constructor'); return false; } // 原型不相等则两个对象不相等 if (prevProps.prototype !== nextProps.prototype) { console.log('prevProps.prototype !== nextProps.prototype'); return false; } if (leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1) { console.log('leftChain.indexOf(prevProps) > -1 || rightChain.indexOf(nextProps) > -1'); return false; } // 遍历下次的属性对象,优先比较不相等的情形 for (p in nextProps) { if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) { console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)'); return false; } else if (typeof nextProps[p] !== typeof prevProps[p]) { console.log('typeof nextProps[p] !== typeof prevProps[p]'); return false; } } // console.log('p in prevProps'); // 遍历上次的属性对象,优先比较不相等的情形 for (p in prevProps) { // 是否都存在某个属性值 if (nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)) { console.log('nextProps.hasOwnProperty(p) !== prevProps.hasOwnProperty(p)'); return false; } // 属性值的类型是否相等 else if (typeof nextProps[p] !== typeof prevProps[p]) { console.log('typeof nextProps[p] !== typeof prevProps[p]'); return false; } console.log('typeof prevProps[p]', typeof prevProps[p]); switch (typeof prevProps[p]) { // 对象类型和函数类型的处理 case 'object': case 'function': leftChain.push(prevProps); rightChain.push(nextProps); if (!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)) { console.log('!compare2Objects(prevProps[p], nextProps[p], leftChain, rightChain)'); return false; } leftChain.pop(); rightChain.pop(); break; default: // 基础类型的处理 if (prevProps[p] !== nextProps[p]) { return false; } break; } } return true; }
当父组件传递方法给子组件的时候,memo好像没什么效果,无论是用const定义的方法,还在用箭头函数或者bind定义的方法,子组件还是执行了
import React, { useState,memo } from 'react'; //子组件会有不必要渲染的例子 interface ChildProps { changeName: ()=>void; } const FunChild = ({ changeName}: ChildProps): JSX.Element => { console.log('普通函数子组件') return( <> <div>我是普通函数子组件</div> <button onClick={changeName}>普通函数子组件按钮</button> </> ); } const FunMemo = memo(FunChild); const ArrowChild = ({ changeName}: ChildProps): JSX.Element => { console.log('箭头函数子组件') return( <> <div>我是箭头函数子组件</div> <button onClick={changeName.bind(null,'test')}>箭头函数子组件按钮</button> </> ); } const ArrowMemo = memo(ArrowChild); const BindChild = ({ changeName}: ChildProps): JSX.Element => { console.log('Bind函数子组件') return( <> <div>我是Bind函数子组件</div> <button onClick={changeName}>Bind函数子组件按钮</button> </> ); } const BindMemo = memo(BindChild); const Page = (props:any) => { const [count, setCount] = useState(0); const name = "test"; const changeName = function() { console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染'); } return ( <> <button onClick={(e) => { setCount(count+1) }}>加1</button> <p>count:{count}</p> <ArrowMemo changeName={()=>changeName()}/> <BindMemo changeName={changeName.bind(null)}/> <FunMemo changeName={changeName} /> </> ) } export default Page;
使用useCallback,参数为[],页面初始渲染后,改变count的值,传递普通函数的子组件不再渲染, 传递箭头函数和bind方式书写的方法的子组件还是会渲染
import React, { useState,memo ,useCallback} from 'react'; //子组件会有不必要渲染的例子 interface ChildProps { changeName: ()=>void; } const FunChild = ({ changeName}: ChildProps): JSX.Element => { console.log('普通函数子组件') return( <> <div>我是普通函数子组件</div> <button onClick={changeName}>普通函数子组件按钮</button> </> ); } const FunMemo = memo(FunChild); const ArrowChild = ({ changeName}: ChildProps): JSX.Element => { console.log('箭头函数子组件') return( <> <div>我是箭头函数子组件</div> <button onClick={changeName.bind(null,'test')}>箭头函数子组件按钮</button> </> ); } const ArrowMemo = memo(ArrowChild); const BindChild = ({ changeName}: ChildProps): JSX.Element => { console.log('Bind函数子组件') return( <> <div>我是Bind函数子组件</div> <button onClick={changeName}>Bind函数子组件按钮</button> </> ); } const BindMemo = memo(BindChild); const Page = (props:any) => { const [count, setCount] = useState(0); const name = "test"; const changeName = useCallback(() => { console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染'); },[]) return ( <> <button onClick={(e) => { setCount(count+1) }}>加1</button> <p>count:{count}</p> <ArrowMemo changeName={()=>changeName()}/> <BindMemo changeName={changeName.bind(null)}/> <FunMemo changeName={changeName} /> </> ) } export default Page;
在子组件使用了memo,useCallback的情况下,给子组件传递一个对象属性,对象值和方法都未发生改变的情况下,父组件无关状态变更,子组件也会重新渲染。
import React, { useState,memo ,useCallback} from 'react'; //子组件会有不必要渲染的例子-使用了memo,useCallback的情况下,给子组件传递一个对象属性值 interface ChildProps { childStyle: { color: string; fontSize: string;}; changeName: ()=>void; } const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => { console.log('普通函数子组件') return( <> <div style={childStyle}>我是普通函数子组件</div> <button onClick={changeName}>普通函数子组件按钮</button> </> ); } const FunMemo = memo(FunChild); const Page = (props:any) => { const [count, setCount] = useState(0); const childStyle = {color:'green',fontSize:'16px'}; const changeName = useCallback(() => { console.log('测试给子组件传递方法,使用useCallback后,子组件是否还会进行无效渲染'); },[]) return ( <> <button onClick={(e) => { setCount(count+1) }}>加1</button> <p>count:{count}</p> <FunMemo childStyle={childStyle} changeName={changeName} /> </> ) } export default Page;
使用useMemo可以解决给子组件传递对象属性时的不必要更新问题。
import React, { useState,memo, useMemo, useCallback} from 'react'; //子组件会有不必要渲染的例子 interface ChildProps { childStyle: { color: string; fontSize: string;}; changeName: ()=>void; } const FunChild = ({ childStyle,changeName}: ChildProps): JSX.Element => { console.log('普通函数子组件') return( <> <div style={childStyle}>我是普通函数子组件</div> <button onClick={changeName}>普通函数子组件按钮</button> </> ); } const FunMemo = memo(FunChild); const Page = (props:any) => { const [count, setCount] = useState(0); const [name, setName] = useState(""); const childStyle = {color:'green',fontSize:'16px'}; const changeName = useCallback(() => { setName('变一下名称') }, []) const childStyleMemo = useMemo(() => { return { color: name === '变一下名称' ? 'red':'green', fontSize: '16px' } }, [name]) return ( <> <button onClick={(e) => { setCount(count+1) }}>加1</button> <p>count:{count}</p> <FunMemo childStyle={childStyleMemo} changeName={changeName} /> </> ) } export default Page;
关于React Hooks使用要注意的点就介绍到这,上述实例具有一定的借鉴价值,感兴趣的朋友可以参考学习,希望能对大家有帮助,想要了解更多React Hooks的内容,大家可以关注群英网络其它相关文章。
文本转载自脚本之家
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要为大家详细介绍了vue+elementUI实现分页效果,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
这篇文章主要为大家详细介绍了用js控制电灯开关,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
JavaScript中FontFace对象的使用方式 目录 介绍 兼容性 创建FontFace对象 FontFace对象属性 FontFace对象方法 将创建的FontFace字体添加到页面中 通过字体Promise回调添加 通过Ajax获取字体文件后回调添加 页面中使用我们添加的字体 css事先定义好 通过js更改元素的字体 FontFace字面理解就是字体脸,也就是文字字体样式的意思,它是通过使用javascript来定义字体对象,并且引入客户端没有安装得字体文件,可以是者服务器端,或者是第三方字体库文件. 介绍 基本语法: concat fo
这篇文章主要为大家详细介绍了JavaScript编写猜拳游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本文实例为大家分享了vue递归实现树形组件的具体代码,供大家参考,具体内容如下1. 先来看一下效果:2. 代码部分 (myTree.vue)图片可以自己引一下自己的图片,或者使用iconfont的css引入。template div class=tree ul class=ul
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008