zoukankan      html  css  js  c++  java
  • React优化点滴

    谈到React优化,估计说的最多的就是减少子组件渲染,减少真实DOM操作等。

    一  减少渲染

    1. shouldComponentUpdate

    通过对Props和State的浅比较,如果没有变化,return false,避免重复多余的render方法调用,省去虚拟DOM的生成和对比过程,提高性能。

    早期类似的技术有pureRender,16版本中可以直接让class组件继承PureComponent,它的实现其实很简单,只是做浅比较,因为过深的比较也会消耗很多的时间,或许还比render方法带来的消耗大,所以这其实是权衡和取舍。

     注意这里的浅比较,基础类型比较值是否相等,不等则需要再次渲染;对于引用类型,先比较引用的地址,地址相同,不渲染,地址不同,判断两个对象的keys的长度,长度不等,则需要渲染,相等则对第一层属性比较,如果有不同,则渲染,否则不渲染。下面的code则是核心主要逻辑。

    注意object.is(ES6推出的)和===的主要区别,是object.is(+0,-0) === false, Object.is(NaN,Nan) === false

    function is(x, y) { //处理了基本类型的比较
      //1,针对+0===-0的情况
      //2. 针对NaN!==NanN的情况
      return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y 
      ;
    }
    var is$1 = typeof Object.is === 'function' ? Object.is : is;
    var hasOwnProperty$2 = Object.prototype.hasOwnProperty;
    
    //返回值:false更新  true不更新
    function shallowEqual(objA, objB) {
      if (is$1(objA, objB)) {//基本数据类型 不更新
        return true;
      }
     //由于Object.is 可以对基本数据类型做一个精确的比较 如果不等只有一种情况 那就是object, objA/objB中,只要有一个不是object或为null则返回false
      if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) {
        return false;
      }
      //过滤掉基本数据类型 就是对象比较 首先比较长度 优化性能
     //比较oldProps和新的Props以及oldState和newState长度是否相同 如果长度不同则重新更新渲染 如果长度相同则不用重新渲染 如果不相等 不会往下执行 优化性能
      var keysA = Object.keys(objA);
      var keysB = Object.keys(objB);
      if (keysA.length !== keysB.length) {
        return false;  
      }
       //如果key相等
       //如果objA的属性不在objB里,或者是objA的属性值和objB的属性值不等 则重新渲染 不考虑当keysA[i]为对象的多层问题 浅显比较 提高性能
       for (var i = 0; i < keysA.length; i++) { 
           if (!hasOwnProperty$2.call(objB, keysA[i]) || !is$1(objA[keysA[i]], objB[keysA[i]])) {
          return false;
        }
      return true;
     }
    }

    2. React.memo()

    由于PureComponent是只能用于class组件的继承,react为了支持函数组件,所以又推出了一个高级组件React.memo(), 当props变化时做浅比较。注意如果用useState或者useContext等hooks导致state状态变化时,函数组件依然会重新渲染,否则复用上一次的渲染结果(复用上一次生成的虚拟子节点Dom)。

    const Child = memo(props => {
      useEffect(() => {
        console.log("schema", props.schema);
      }, [JSON.stringify(props.schema)]);
    
      return <div>Child</div>;
    });
    
    //another case
    const ChildMedo = React.memo(() => {
        return <Child1 step={step} count={count}  /> 
    });

     3. useMemo

    用于函数组件,减少计算一些中间值,废话别太多,Show you code。

    export default function useMemoDemo() {
        const [count, setCount] = useState(1);
        const [val, setValue] = useState('');
        
        // 使用useMemo的话 当input的值改变了,count没有变化,func 还是拿到之前的缓存的值,如果计算过程很复杂,就节省了性能的再次计算的开销
        const func= useMemo(() => {
           let result = Math.random() * count;
            return result;
        }, [count]);
     
        return <div>
            <h4>{count}-{func}</h4>
            {val}
            <div>
                <button onClick={() => setCount(count + 1)}>+c1</button>
                <input value={val} onChange={event => setValue(event.target.value)}/>
            </div>
        </div>;
    }

    4. redux中的优化

    对于 Redux 来说,每当 store 发生改变时,所有的 connect 都会重新计算。在一个大型应用中,浪费的重复计算可想而知。为了减少性能浪费, 我们想到对 connect 中的这些 selector函数做缓存。

    Redux 拥抱了函数式编程,而在函数式编程中,纯函数的众多好处之一就是方便做缓存。那么,如何用纯函数做缓存呢?在数学上,如果自变量不变,因变量总是不变。同样,用相同的参数执行纯函数多次,每次返回的结果一定相同。也就是说,如果纯函数的参数不变的话,可以把之前用同样的参数计算出来的结果直接返回。 (摘自《深入React技术栈》)

    我们可以直接使用reselect库帮我们完成。

    export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
    let lastArgs = null
    let lastResult = null
    return (...args) => {
    if (
    lastArgs !== null &&
    lastArgs.length === args.length &&
    args.every((value, index) => equalityCheck(value, lastArgs[index]))
    ) {
    return lastResult
    }
    lastResult = func(...args)
    lastArgs = args
    return lastResult
    }
    }

    defaultMemoize 函数运用了闭包的原理,使纯函数的参数和结果缓存在内存中。为了让
    defaultMemoize 函数中缓存的数据常驻内存,我们需要让 defaultMemoize 处于全局作用域,或者
    用其他作用域链连接到全局作用域。

    5.高阶reducer分析出哪些reducer或action耗费的时间太长,预警, 这样之后就可以有针对性优化。

    export default function logSlowReducers(reducers, thresholdInMs = 8) {
    Object.keys(reducers).forEach((name) => {
    const reducer = reducers[name];
    // 将每个 reducer 用高阶函数包装
    reducers[name] = (state, action) => {
    const start = Date.now();
    const result = originalReducer(state, action);
    const diffInMs = Date.now() - start;
    if (diffInMs >= thresholdInMs) {
    console.warn(`Reducer ${name} took ${diffInMs} ms for ${action.type}`);
    }
    return result;
    };
    });
    return reducers;
    }

    6. 特定的action

    Redux 中,每个 action 被分发,所有的 reducer 都会被执行一次。虽然每个 reducer 仅仅只
    是执行一个 switch 判断,但所有的 reducer 加起来的执行时间也不容小觑。
    大多数情况下,应用的 action 都是和某个 reducer 对应。因此,我们可以指定特殊情况,让
    Redux 在特殊情况之外只执行与 action 对应的那个 reducer 。

    7. 合并多个action

    比如有5个同步action,依次触发,每一个action都需要一个很大的redux流程,以及dom树的更新,我们可以通过一些办法合并他们,只产生一个最终的state,最后再一次性更新组件树。

    8. 提高编程技巧,避免属性每次都传入一个新对象

    比如字面量prop = {a:b},每次都会产生要给新的对象

    比如我们经常使用的合成事件的绑定。()=> {}, 或者onchange.bind(this), 最好存储为类的成员属性做缓存。

    9. 其他

    使用redux标准接入组件方式,connect方法的第4个参数options={pure:true},默认也会对传入属性做浅比较,避免被包装组件重复不必要的渲染。

    其实还有很多。

    二 刻意去迎合diff算法

    1.  同级别的元素使用唯一key,减少diff比较,增强复用,比如使用绑定元素的id。

    2.  避免(组件结构)节点的跳跃变动,因为diff算法只会同级别比较,一旦发现不同就会大刀阔斧地剪枝或增枝。

    3. 同级别节点中,避免因为代码问题,产生太多的移动。

     比如有5个兄弟元素,只有最后一个元素(索引为4)调到第一个位置来,其他节点索引都变大1位,react的diff算法是,索引变小不移动,索引变大需要移动,so,这就会增加出其余4个元素向后移动4次真实        DOM操作。

    当然react的diff算法的时间复杂度已经从O(n^3)优化到 O(n),从2/8原则的角度来看,绝大多数场景的性能已经很棒了。

  • 相关阅读:
    wmware虚拟系统光盘的问题
    ORM框架SQLAlchemy
    python关于二分查找
    Python的各种推导式合集
    远程连接腾讯云服务器MySQL数据库
    Django配置404页面
    使用Python将Excel中的数据导入到MySQL
    MySQLl导入导出SQL文件
    数据结构(十三)排序
    数据结构(十二)散列表
  • 原文地址:https://www.cnblogs.com/roy1/p/13832424.html
Copyright © 2011-2022 走看看