一文详解React渲染优化之useImmer
作者:Ryun
在React日常开发中,我们常常被重复渲染或无意义渲染所折磨,穷尽脑汁,做各种优化:memo、useMemo、useCallback、immutable等,本文主要讲述immutable的简约版Immer,感兴趣的同学可以一起来学习
从一个例子开始
import { FC, useState, useEffect } from 'react';
const App: FC = () => {
  const [list, setList] = useState({ a: 1 });
  useEffect(() => {
    setList({ a: 1 });
  }, []);
  console.log('测试');
  return (
    <>
      <h1>Hello Web3 React {list.a}</h1>
    </>
  );
};
export default App;- 看控制台输出结果
 

- 因为list是引用对象,虽然内部的值相同,但引用地址变化了,因此react认为state发生了变化,因此触发了渲染,这也联想到如果我们传递props时为对象或数组时,会造成我们非期望渲染的缘由,那我们该如何解决这个问题呢?
 - 在此之前,先介绍一个react渲染检查工具 why-did-you-render
 
渲染检查工具
why-did-you-render, 可以在开发时,帮你检测无意义的渲染
- 安装 
yarn add @welldone-software/why-did-you-render -D - src下创建 wdyr.tsx
 
/// <reference types="@welldone-software/why-did-you-render" />
import React from 'react';
if (process.env.NODE_ENV === 'development') {
  // eslint-disable-next-line
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React, {
    onlyLogs: true,
    titleColor: 'green',
    diffNameColor: 'darkturquoise',
    trackHooks: true,
    trackAllPureComponents: true,
  });
}- 在入口文件 index.tsx 引入
 

- 4.上面的例子,输出结果,显示无意义渲染
 

要解决这个问题,需要我们使用到Immer
Immer 与 UseImmer
- 不可变数据:不可变数据的特点就是产出地址不同的对象引用,同时尽可能保留没必要更新的属性或者子属性,这样在你的组件树中如果有数据的其他属性或者子熟悉的依赖存在的话,可以避免一些rerender
 - immutable:不改变原数据,将变化的部分与不变的部分组成一个新的数据对象**
 - Immer:简化了不可变数据结构的处理,与immutable代码简单,去掉树的压缩,使用proxy代理,组合新的树
- immer 可以在需要使用不可变数据结构的任何上下文中使用,例如:React state、Redux等
 - 不可变的数据结构允许(高效)的变化检测:如果对象的引用没有改变,那么对象本身也没有改变。此外,它使克隆对象相对便宜:数据树的未更改部分不需要复制,并且在内存中与相同状态的旧版本共享
 
 

- 利用 produce 函数,它将我们要更改的 state 作为第一个参数,对于第二个参数,我们传递一个名为 recipe 的函数,该函数传递一个 draft 参数,我们可以对其应用直接的 mutations。一旦 recipe 执行完成,这些 mutations 被记录并用于产生下一个状态。 produce 将负责所有必要的复制,并通过冻结数据来防止未来的意外修改
 
Immer是如何工作的?
- 使用 Immer,您会将所有更改应用到临时 draft,它是 currentState 的代理。一旦你完成了所有的 mutations,Immer 将根据对 draft state 的 mutations 生成 nextState。这意味着您可以通过简单地修改数据来与数据交互,同时保留不可变数据的所有好处
 

- 使用 Immer 就像拥有一个私人助理。助手拿一封信(当前状态)并给您一份副本(草稿)以记录更改。完成后,助手将接受您的草稿并为您生成真正不变的最终字母(下一个状态)
 
Immer优点
- 深度更新轻而易举
 - 开箱即用的结构共享
 - 开箱即用的对象冻结
 - 代码简洁
 - 小巧:3KB gzip
 
使用
state + Immer


useImmer
- 替代 state
 

- 本文开始的例子使用 useImmer
 
import { FC, useEffect } from 'react';
import { useImmer } from '@hooks/useImmer';
const App: FC = () => {
  const [list, setList] = useImmer({ a: 1 });
  useEffect(() => {
    // setList({ a: 1 });
    setList(draft => {
      draft.a = 1;
    });
  }, []);
  console.log('测试');
  return (
    <>
      <h1>Hello Web3 React {list.a}</h1>
    </>
  );
};
export default App;观察控制台,发现已经没有无意义渲染的提示了,It's wonderful!
- 注意:Immer 并没有 Immutable 解决的那么彻底,我们不能直接给draft赋值,而在draft上操作是极好的
 
useEffect(() => { 
  // setList({ a: 1 });
  setList(draft => {
    draft.a = 1;
  }); 
},[]);
自己动手实现hooks-useImmer
import { useCallback, useState } from 'react';
import { produce, Draft, freeze } from 'immer';
/**
 * 定义函数的签名
 * initialState 可以是一个数据,也可以是一个函数
 * useImmer 返回元组,类似 [state, setState] = useImmer(data);
 * Draft 中间状态
 * (() => S) 自执行函数
 */
export type DraftFunction<S> = (draft: Draft<S>) => void;
export type Updater<S> = (arg: S | DraftFunction<S>) => void;
export type ImmerHook<S> = [S, Updater<S>];
export function useImmer<S = any>(initialState: S | (() => S)): ImmerHook<S>;
/**
 * 实现
 * @param initialState
 */
export function useImmer<T>(initialState: T) {
  // const [state, setState] = useState(initialState);
  // 冻结 state,第二参数 true,表示深度冻结,对象不能修改了
  const [value, updateValue] = useState(() =>
    freeze(typeof initialState === 'function' ? initialState() : initialState, true)
  );
  // 使用 useCallback,放置组件间传递,产生句柄
  return [
    value,
    useCallback((updater: Updater<T>) => {
      if (typeof updater === 'function') {
        updateValue(produce(updater));
      } else {
        updateValue(freeze(updater));
      }
    }, []),
  ];
}以上就是一文详解React渲染优化之useImmer的详细内容,更多关于React渲染优化useImmer的资料请关注脚本之家其它相关文章!
