让我们来看看React.memo

本文探讨了React.memo的使用场景,解释了其在优化React应用中的作用,以及何时可能会失效。文章还介绍了useMemo和useCallback在处理复杂数据类型和函数时的作用,并提供了关于何时使用React.memo的建议。同时,解释了浅比较的概念及其在对象比较中的实现原理。
摘要由CSDN通过智能技术生成

React.memo的使用背景

关于这个使用和使用背景,网上是有清晰的答案的。前提是通常情况下props发生改变时,react就会re-render。也就是说当你的子组件不想被父组件影响重新渲染时,你就需要给子组件一个react.memo包裹起来,使其当props发生改变时 才会re-render。但是什么时候才使用它呢,是不是所有子组件都要使用它,网上没有很清晰的答案。笔者以下将会做出自己的总结。

react.memo

能减少重绘次数,把组件传入的props做一个lodash缓存,每次会做一个浅比较,当props改变时,会与缓存的内容做一个浅比较(当然浅比较也是要消费性能的,memo还是要慎用)也就是说当传入的值时useState定义的内容(useState 是不会在每次重新渲染时都重新初始化的,只有在组件重载时才会初始化,所以引用地址一样),数值,字符串,布尔值时,能避免不必要的re-render。但是因为做了浅比较,这又产生了一个问题。react.memo不生效的情况。

注:react 内部流程,其实跟vue差不多只不过没有双向数据流绑定的这个过程:Data->component->VDOM->DOM
memo比较的其实时虚拟dom,这个过程相对于re-render非常快,所以不用担心效率问题,能很好的阻止页面无效重绘重排

react.memo不生效的情况

复杂数据类型当比较到存储地址不一致时,因为父组件的重新render导致了状态行为重新定义,就会判断不一致从而导致子组件re-render,这里我试验了一些例子:

    const [content, setContent] = useState(0);
    const [other, setOther] = useState(0);
    const [contentObj, setContentObj] = useState([{a: 1}, {a: 2}]);
    const handleClick = () => {
	    console.log('父组件点击');
	    setContent((content) => {
	        return content + 1;
	    })
	    setContentObj([{a: 2}, {a: 4}])
	}
	const otherHandle = () => {
	    setOther(other=>(other+1))
	}
	const obj = useMemo(() => {
        return {a: content}
    }, [content])
    const func = function () {
        console.log('点击点击')
    }
    
	<Button onClick={handleClick}>父组件点击{content}</Button>
    <Button onClick={otherHandle}>额外点击{other}</Button>
    {/* 传数值,子组件不触发 */}
    {/* <NewTestComponent params={content}/>  */}
    {/* 子组件不触发 */}
    <NewTestComponent params={contentObj}/> 
    {/* 传对象 params={{a: content}},子组件触发了,用react.memo包裹起来就不触发了 */}
    {/* <NewTestComponent params={obj}/>  */}
    {/* 传函数,子组件触发 */}
    {/* <NewTestComponent params={func}/> */}
    {/* 传内联函数,子组件触发 */}
    {/* <NewTestComponent params={() => {}}/> */}

不生效的情况下,就用到了useMemo和useCallback。还有一种情况,如果是静态的复杂类型参数,也就是说不需要在props中更改的静态复杂类型参数,还要用useMemo嘛,不,这里不是要记住一个值是要在重新渲染时保持对值的引用不变,所以这里用useRef()更好一点。

 const obj = useRef(['参数1', '参数2']);

useMemo

当处理react.memo不生效的情况时,不需要使用useMemo, 也就是当传入的数值是object, array时要用useMemo。
其他情况,是为了缓存在渲染过程中比较繁重的计算过程,当处理复杂的计算逻辑时要用到useMemo。

useCallback

当处理react.memo不生效的情况时,当传入的值是函数时,都要用useCallback。
当组件刷新时,未被usecallback包裹的方法将被垃圾回收并重新定义,但被usecallback所制造的闭包将保持对回调函数喝依赖项的引用。
usecallback,设计的初衷并非解决组件内部函数多次创建的问题,而是减少子组件的不必要重复渲染。
react优化思路:
1.减少重新 render 的次数。因为 React 最耗费性能的就是调和过程(reconciliation),只要不 render 就不会触发 reconciliation。
2.减少计算量
总结:
useCallBack不要每个函数都包一下,否则就会变成反向优化,useCallBack本身就是需要一定性能的
useCallBack并不能阻止函数重新创建,它只能通过依赖决定返回新的函数还是旧的函数,从而在依赖不变的情况下保证函数地址不变
useCallBack需要配合React.memo使用

是否每个子组件都需要使用react.memo

答案是参差不齐的,应该说没有一个标准答案,但是就我本人而言,当props极其复杂且render函数又很简单的时候直接re-render就好了,没必要重新使用memo缓存,因为重新计算props再进行浅比较可能代价比re-render还要高。当组件执行 render 函数代价很高(比如每次 render 需要跑一些有的没的运算),那就可以考虑用 React.memo 缓存组件的状态。每个人有每个人不同的优化方式和开发习惯,这也是为什么memo没有被react内置的原因吧。

上面既然提到了浅比较,那么浅比较又是什么呢?

浅比较中两个对象相同指的是:两个对象的属性个数相同且两个对象的属性相同,并且两个对象的属性值也一样。属性值一样,指的是引用一样,不再深度比较这两个对象里面的东西是否一样了。
比如:

var obj = {a:1};
obj1 = obj;
obj1 === obj; // true
这是引用地址一样
{a: 1} === {a: 1}; //false
这是引用地址不一样

浅比较源码:

const hasOwnProperty = Object.prototype.hasOwnProperty;

/**

  • Performs equality by iterating through keys on an object and returning false
  • when any key has values which are not strictly equal between the arguments.
  • Returns true when the values of all keys are strictly equal.
    */
    function shallowEqual(objA: mixed, objB: mixed): boolean {
    // 调用Object.is判断是否相等,相同返回true,不同返回false
    if (Object.is(objA, objB)) { // 判断简单数据类型,+0,-0, NaN
    return true;
    }
    // object.is比较发现不等,但并不代表真的不等,object对象还需要比较
    // 这里判断是否是object,如果不是,那直接返回false
    if (
    typeof objA !== ‘object’ ||
    objA === null ||
    typeof objB !== ‘object’ ||
    objB === null
    ) {
    return false;
    }
    // 判断负责数据类型
    const keysA = Object.keys(objA);
    const keysB = Object.keys(objB);
    // 比较对象中的keys长度,不等返回false
    if (keysA.length !== keysB.length) {
    return false;
    }
    // 比较对象中相同的key的val是否相等
    for (let i = 0; i < keysA.length; i++) {
    if (
    !hasOwnProperty.call(objB, keysA[i]) || // 判断在objB中是否有objA中的所有key,比较key是否相同
    !Object.is(objA[keysA[i]], objB[keysA[i]]) // 判断同key的value是否相同
    ) {
    return false;
    }
    }
    return true;
    }
// 浅比较函数
// 比较了props和nextProps,state和nextState

function shallowCompare(instance, nextProps, nextState) {
return (
!shallowEqual(instance.props, nextProps) ||
!shallowEqual(instance.state, nextState)
);
}

// Object.is
// 如果x === y相等时,返回x !== 0 || 1 / x === 1 / y
// 否则返回x !== x && y !== y
function is(x: any, y: any) {
return (
(x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y) // eslint-disable-line no-self-compare
);
}

/*

  • 为什么要这么比较?可以参考下面的图
    */
    // x === y 的时候,比较了类型和值
    // 但,+0 === -0 ,结果为true,但我们希望结果应该是false
    // NaN === NaN,结果为false,但我们希望结果应该是true
    // 当 +0 === -0 进入判断体中,再比较x!==0,结果为false,+1/0 === -1/0 => Infinity === -Infinity,结果就为false
    // 当不相等的时候,进入第二个判断体,此时NaN比较则返回了true,然后x与y做&&比较,返回结果
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值