react hooks —— memo的性能优化

1,337 阅读5分钟

前言

大家好 我是橙子,今天给大家带来一个memo的应用场景,就是下面所看到的笑脸。 它会有 喜 怒 哀 乐 五种状态,我们慢慢的移动进度条,笑脸也会发生变化。 这里是放了5张图片如下图, 用我们熟悉的useStatevlue里面设置值,它的值分别对应 0 100 200 300 400, 那它跟我们的react hooks的哪个函数相关呢,那就是memo

理解memo

  • memo它是一个高阶组件,它与 React.PureComponent 非常相似,但只适用于函数组件,而不适用 class 组件。
  • 那我们怎么来理解memo呢,memomemory的简写,所有可以理解成它是一个记忆组件,可以缓存值,memo针对的是一个组件的渲染是否重复执行,用来优化函数组件的重复渲染行为。当你传入属性值都不变的情况下不会触发组件。那怎么使用memo来实现性能优化呢?
  • 在学习react中,性能优化的点主要在于:调用 setState,就会触发组件的重新渲染,无论前后 state 是否相同,父组件更新,子组件也会自动更新。
  • hooks 出来之后,函数组件中没有 shouldComponentUpdate 生命周期,我们无法通过判断前后状态来决定是否更新。useEffect 不再区分 mount update 两个状态,这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。所以,hooks中学习memo来减少重复渲染,实现性能优化。接下来通过这个demo来帮助理解。

memo的应用 笑脸

1. 看效果图,我们要缓存5个图片要改变的位置,当移动滑动条,每次Change 都会触发MVVM视图的重新渲染, 这将会极大的消耗性能。

  • 当我们拖动的时候 ,其实我们每次要拖动100 才会变成下一张图片, 没有必要在这个过程之中,发生组件的重新渲染。
  • 我们可以把图片看成我们要缓存的组件,也就是memo组件,即使我们的滑动范围超过几百次, 我们的子组件也不会跟随着发生几百次的改变,而是仅仅几次,这是不是有点有趣。

2. 接下来我们将5张图片放在src目录下。 接着写入口文件index.js。 引入react, react-dom 和样式文件 style.css(样式文件地址)。

import React, { useState } from 'react';
import ReactDom from 'react-dom';
import './styles.css';
import { FaceComponent } from './demo.js';

function App() {
        const [satisfactionLevel, setSatisfactionLevel] = useState(300)
        return (
            <div className="App">
                <input 
                    type="range" 
                    min="0" 
                    max="500" 
                    value={satisfactionLevel}
                    onChange = {(e)=>{setSatisfactionLevel(+e.target.value)}}
                />
                <br/>
                <span>{satisfactionLevel}</span>
                <br/>
                <FaceComponent level={satisfactionLevel} />
            </div>
        )
    }
const rootElement = document.getElementById('root');
ReactDom.render(<App/>, rootElement);

声明了一个变量satisfactionLevel,表示满意度的等级,我们的表情从不开心到有点开心到很开心 的状态改变,初始值为300。所有这里的setSatisfactionLevel作用并不大 因为我们不需要去修改值去做其他的事情,而是只是放一张图片在这里。input type="range" 是H5一个新特性,自定义滑动条。

3. 新建demo.js文件,引入 reactmemo 然后在写一个函数式组件,看memo的应用场景。

//这是一个函数式组件  交给memo 进行高阶组件化,它可以帮我们缓存值
export const FaceComponent = memo((props) => {
    //解构level
    //props会频繁的发生改变,使用memo 传入第二个参数
    const { level } = props; 
    return (
        //盒子
        <div className={setSatisfactionClass(level)}></div>
    )
}, isSameRange)

这里的isSameRange是什么呢,这是memo的第二个参数,动态的改变类名,比较参数来判断前后状态是否要发生改变。通过第二个参数指定一个自定义的比较函数来比较新旧 props。如果函数返回 true,就会跳过更新,useMemo Hook 允许你通过「记住」上一次计算结果的方式在多次渲染的之间缓存计算结果. 声明一个 isSameRange 方法:

const isSameRange = (prevValue, nextValue) => {
    //setSatisfactionClass 设置class  表示要它生成的一个类名
    // 5个类名表示不同的位置 类名不变 位置就不变
    const prevValueClass = setSatisfactionClass(prevValue.level)
    const nextValueClass = setSatisfactionClass(nextValue.level)
    return prevValueClass === nextValueClass
}

isSameRange 方法来判断两次 props 有什么不同,memo会帮我们缓存上一个值,当我们接收一个新的值之后,两个值进行比较,相同的话,拒绝修改,直接使用已经缓存的值,不同的话, 则改变。 达到即缓存又更新的能力,这是一个很抽象的函数,也会让我们对memo有更深的理解。

4. 写业务函数 setSatisfactionClass

const setSatisfactionClass = (level) => {
	//结合css理解一下
    //0-99只需要发生一次改变
    if (level < 100) {
        return "very-dissatisfied"
    }
    if (level < 200) {
        return "somewhat-dissatisfied"
    }
    if (level < 300) {
        return "neither"
    }
    if (level < 400) {
        return "somewhat-satisfied"
    }
    return "very-satisfied"
}

通过level的值,返回相应图片的类名,达到笑脸变化的效果。

5. 完整demo.js代码

import React, { memo } from 'react';

const setSatisfactionClass = (level) => {
    if (level < 100) {
        return "very-dissatisfied"
    }
    if (level < 200) {
        return "somewhat-dissatisfied"
    }
    if (level < 300) {
        return "neither"
    }
    if (level < 400) {
        return "somewhat-satisfied"
    }
    return "very-satisfied"
}

const isSameRange = (prevValue, nextValue) => {
    const prevValueClass = setSatisfactionClass(
        prevValue.level
    )
    const nextValueClass = setSatisfactionClass(
        nextValue.level
    )
    return prevValueClass === nextValueClass
}

export const FaceComponent = memo((props) => {
    const { level } = props; 
    return (
        <div className={setSatisfactionClass(level)}></div>
    )
}, isSameRange)

结语

参考文档