最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • react Hook 之 memo,useCallback,useMemo 性能优化

    正文概述 掘金(webInRun)   2021-05-20   591

    前言

    基础示例

    需求: 编写个父子组件

    父组件

    import React, { useState } from 'react'
    import Child from './child'
    export default function Parent(props: any) {
    	const [num, setNum] = useState(0)
    	const handleClick = () => {
    		setNum(num + 1)
    	}
    	return (
    		<div>
    			<h2>{num}</h2>
    			<button onClick={handleClick}>更改num</button>
    			{/* 引用子组件 */}
    			<Child />
    		</div>
    	)
    }
    

    子组件

    export default function Child(props: any) {
    	console.info('子组件渲染了')
    	return <div>我是子组件</div>
    }
    
    

    现象:

    每次点击"更改num"按钮,控制台都弹出"子组件渲染了" react Hook 之 memo,useCallback,useMemo 性能优化

    React.memo

    上面子组件里并没有依赖父组件任何属性,却在每次父组件更改时,都会被重新渲染,此时可以用 React.memo 优化。

    定义

    const MyComponent = React.memo(function MyComponent(props) {
      /* 使用 props 渲染 */
    });
    

    如果想要自定义对比过程,可以通过第二个参数来实现:

    function MyComponent(props) {
      /* 使用 props 渲染 */
    }
    function areEqual(prevProps, nextProps) {
      /*
      如果把 nextProps 传入 render 方法的返回结果与
      将 prevProps 传入 render 方法的返回结果一致则返回 true,
      否则返回 false
      */
    }
    export default React.memo(MyComponent, areEqual);
    

    优化

    import React, { useState } from 'react'
    import Child from './child'
    const MemoChild = React.memo(Child) // 新增代码
    export default function Parent(props: any) {
    	const [num, setNum] = useState(0)
    	const handleClick = () => {
    		setNum(num + 1)
    	}
    	return (
    		<div>
    			<h2>{num}</h2>
    			<button onClick={handleClick}>更改num</button>
    			{/* 引用子组件 */}
    			{/* <Child />   */}
    			{/* 新增代码 */}
    			<MemoChild /> 
    		</div>
    	)
    }
    
    

    现象: 每次点击"更改num"按钮,控制台都不再弹出"子组件渲染了" react Hook 之 memo,useCallback,useMemo 性能优化 那么这个示例是否完美了呢?如果说父子组件之间不通讯,那么这样就可行了。但父组件要传数据跟事件给子组件时,就仍然会有问题。

    父组件传递事件给子组件示例

    更改父组件代码为:

    import React, { useState } from 'react'
    import Child from './child'
    const MemoChild = React.memo(Child)
    export default function Parent(props: any) {
    	const [num, setNum] = useState<number>(0)
    	const handleClick = () => {
    		setNum(num + 1)
    	}
    	const handleChange = () => {
    		console.info(2323)
    	}
    	return (
    		<div>
    			<h2>{num}</h2>
    			<button onClick={handleClick}>更改num</button>
    			{/* 引用子组件 */}
    			{/* 新增代码 */}
    			{/* 父组件传递 handleChange 事件给子组件 */}
    			<MemoChild handleChange={handleChange}/> 
    		</div>
    	)
    }
    

    现象:

    每次点击"更改num"按钮,控制台都弹出"子组件渲染了" react Hook 之 memo,useCallback,useMemo 性能优化 当传递事件给子组件时,点击更改 num ,控制台会再次打印出"子组件渲染了",说明子组件又重新渲染了。

    分析原因:

    首先一个组件重新渲染,一般是三种情况导致的:

    1. 组件自己的状态改变了
    2. 父组件重新渲染,但父组件的 props 没有改变
    3. 父组件重新渲染,父组件传递的 props 改变

    很明显,第一种不是,因为子组件并没有任何状态。

    第二种,已经用 React.memo 优化掉了,所以也不是。

    那说明就是第三种, 传递的 props 改了

    那为什么传递的 handleChange 函数会发生改变呢?

    因为在函数式组件里每次重新渲染,都会重新从头开始执行函数调用,那么这两次创建的 handleChange 函数肯定发生了改变,所以导致子组件重新渲染。

    流程:

    1. 用户点击"更改num"按钮
    2. 父组件修改 num 的状态值
    3. 状态值改变导致父组件重新渲染
    4. handleChange 函数体会重新注册一遍
    5. 传递给 handleChange props 也就变了
    6. 子组件重新渲染

    useCallback

    找到原因,那么就要想法子,在函数没有改变的情况下,重新渲染的时候保持函数的引用一致

    定义

    const memoizedCallback = useCallback(
      () => {
        doSomething(a, b);
      },
      [a, b],
    );
    

    优化

    // 新增引入 useCallback
    import React, { useState, useCallback } from 'react'
    import Child from './child'
    const MemoChild = React.memo(Child) 
    export default function Parent(props: any) {
    	const [num, setNum] = useState<number>(0)
    	const handleClick = () => {
    		setNum(num + 1)
    	}
      // 修改代码
      // 通过 useCallback 进行记忆 handleChange, 并将记忆的 handleChange 传递给 MemoChild
    	const memoizedCallback = useCallback(() => {
    		console.info(2323)
    	},[])
    	return (
    		<div>
    			<h2>{num}</h2>
    			<button onClick={handleClick}>更改num</button>
    			{/* 引用子组件 */}
    			<MemoChild handleChange={memoizedCallback} />
    		</div>
    	)
    }
    
    

    现象:

    每次点击"更改num"按钮,控制台都不再弹出"子组件渲染了" react Hook 之 memo,useCallback,useMemo 性能优化 可以看到子组件并不会重新渲染,那么是不是可以结束了呢?

    不,父子组件之间除了传递事件,还会传递状态

    父组件传递状态给子组件示例

    import React, { useState } from 'react'
    import Child from './child'
    const MemoChild = React.memo(Child) // 新增代码
    export default function Parent(props: any) {
    	const [num, setNum] = useState<number>(0)
    	const [name] = useState<string>()
    	const handleClick = () => {
    		setNum(num + 1)
    	}
    	return (
    		<div>
    			<h2>{num}</h2>
    			<button onClick={handleClick}>更改num</button>
    			{/* 引用子组件 */}
    			{/* 新增代码 */}
    			{/* 父组件传递 name 状态给子组件 */}
    			<MemoChild name={name} />
    		</div>
    	)
    }
    

    现象:

    每次点击"更改num"按钮,控制台都不会弹出"子组件渲染了"。 react Hook 之 memo,useCallback,useMemo 性能优化 这不是正常的吗?这不是在逗我吗?不是的,请继续看下来 react Hook 之 memo,useCallback,useMemo 性能优化

    上面传递的 name 是基本类型,如果是对象呢? 更改代码如下:

    import React, { useState } from 'react'
    import Child from './child'
    const MemoChild = React.memo(Child) 
    export default function Parent(props: any) {
        const [num, setNum] = useState<number>(0)
        const handleClick = () => {
            setNum(num + 1)
        }
        return (
            <div>
                <h2>{num}</h2>
                <button onClick={handleClick}>更改num</button>
                {/* 引用子组件 */}
                {/* 修改代码 */}
                {/* 父组件传递 name 状态给子组件 */}
                {/* <MemoChild name={name} /> */}
                <MemoChild person={{name:"张三"}} />
            </div>
        )
    }
    
    

    现象:

    每次点击"更改num"按钮,控制台都弹出"子组件渲染了"。 react Hook 之 memo,useCallback,useMemo 性能优化 实际业务中,传递的对象肯定更复杂,我这里只是为了说明只要是个对象,就会导致子组件重新渲染。

    原因跟上面传递事件是一样的,传递对象,每次重新渲染都指向新的引用,子组件 react.memo 进行浅比较,会得出传递的 props 不等了,所以重新渲染。

    useCallback 是用来处理父子传递事件的优化,至于传递状态就要用到 useMemo。

    useMemo

    定义

     const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    

    优化

    import React, { useState,useMemo } from 'react'
    import Child from './child'
    const MemoChild = React.memo(Child)
    export default function Parent(props: any) {
    	const [num, setNum] = useState<number>(0)
    	const handleClick = () => {
    		setNum(num + 1)
    	}
    	return (
    		<div>
    			<h2>{num}</h2>
    			<button onClick={handleClick}>更改num</button>
    			{/* 采用 useMemo */}
    			<MemoChild person={useMemo(()=>({name:"张三"}),[])} />
    		</div>
    	)
    }
    
    

    现象:

    每次点击"更改num"按钮,控制台都不会弹出"子组件渲染了"。 react Hook 之 memo,useCallback,useMemo 性能优化

    这下可以放心传递状态跟事件了。

    总结

    React hook 比起类组件有了更大的灵活度和自由,但同时对开发者要求也更高了。因为 Hooks 使用不恰当很容易出现性能问题。 memo,useMemo,useCallback 都是用来提升性能的。

    1. memo 相当于 shouldComponentUpdate
    2. useCallback 让 shouldComponentUpdate 可以正常发挥作用
    3. useMemo 避免频繁的昂贵计算,当然也可以用在像本文 props 状态上

    那什么时候用这几个 api 呢?

    实际上在简单的应用中,尽量少用,因为对于简单应用,就算重新渲染也不会消费多少资源,而采用这几个 api 时,每次都会对第二个参数进行比较,反而消耗了资源。

    当然对于开销比较大的组件就尽量用 memo。


    起源地下载网 » react Hook 之 memo,useCallback,useMemo 性能优化

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元