React性能优化整理

摘要:通过判断减少数据变化触发的重新渲染, 以及之后的 DOM diff;函数式语言当中, 语言设计允许两个对象一样, 举例 Clojure:;每个函数体当中生成的对象都会有新的引用, useMemo 可以保留一致的引用.

class 组件的优化

通过判断减少数据变化触发的重新渲染, 以及之后的 DOM diff

shouldComponentUpdate(nextProps, nextState) {
  if (this.props.color !== nextProps.color) {
    return true;
  }
  if (this.state.count !== nextState.count) {
    return true;
  }
  return false;
}

JavaScript 对象引用问题

函数式语言当中, 语言设计允许两个对象一样, 举例 Clojure:

(= {:a 1} {:a 1}) ; true

(identical? {:a 1} {:a 1}) ; false

递归匹配, 性能并不高.

JavaScript 对象基于引用传值, 比较单一

{a: 1} === {a: 1} // false

大体方案, 通过手动维护, 让相同的数据尽量保证引用一致, 控制性能.

function updateColorMap(colormap) {
  return {...colormap, right: 'blue'};
}

useMemo 优化

每个函数体当中生成的对象都会有新的引用, useMemo 可以保留一致的引用.

const myObject = useMemo(() => ({ key: "value" }), [])

注意: 用花括号直接写对象基本上就是新的引用了,

{}
{a: 1}

{...obj}

一般组件内部不变的对象, 都是从 state, ref, 再或者组件外全局有一个引用.


React.memo 优化

判断参数是否改变, 如果没有改变, 就直接复用已有的组件, 不重新生成:

const MyComponent = React.memo(function MyComponent(props) {
  /* only rerenders if props change */
});

React.memo 有第二个参数, 用于自定义判断的规则:

const MemoItem = React.memo(Item, (prevProps, nextProps) => {
  if (prevProps.item.selected === nextProps.item.selected) {
    return true;
  }
  return false;
});

useCallback 优化

使用 React.memo 包裹组件:

let Inner: FC<{
  onClick: () => void
}> = React.memo((props) => {
  return <div>
    <span>inner</span>
    </div>;
});

使用 useCallback

let Outer: FC<{}> = React.memo((props) => {
  const [counter, setCounter] = useState(0);
  const onClick = useCallback(()=>{ 
    setCounter(prevState => ++prevState)
  },[]);
  return <div>
    <span>outer: {counter}</span>
    <Inner onClick={onClick} />
  </div>;
});

避免 render 当中的 DOM 操作

let NewComponent: FC<{}> = React.memo((props) => {

  let elRef = useRef<HTMLDivElement>()

  // 错误写法
  if (elRef.current) {
    elRef.current.style.color = 'red'
  }

  return <div ref={elRef}></div>;
});

DOM 发生改变的时候, 一般会有比较多后续的布局和 compose 计算去绘制新的界面.

特别是在脚本执行过程当中发生的话, 会对性能有明显影响.

脚本执行完再执行, 让浏览器自动处理(合并, 避免频繁 DOM 操作).


业务相关

  • immer 对优化方案的影响
  • Rex 组件当中优化的坑
  • 路由相关的优化
  • 性能调试

Immer 对优化方案的影响

let a = {}
let b = produce(a, draft => {
  draft.b = 1
})

a === b // false

如果数据不发生改变, 直接用原始数据.

(Hooks API 之后, 数据被拆散了, 可以减少 immer 的使用.)


Rex 当中优化的相关

class 组件, 高阶组件当中自动做了基础的优化.

shouldComponentUpdate(nextProps: IRexDataLayerProps, nextState: any) {
  if (!shallowequal(nextProps.parentProps, this.props.parentProps)) return true;
  if (!shallowequal(nextProps.computedProps, this.props.computedProps)) return true;
  return false;
}

Hook API, 没有中间一层组件, 直接触发当前组件更新, 存在性能问题.(还要考虑优化方案)

let contextData = useRexContext((store: IGlobalStore) => {
  return {
    data: store.data,
    homeData: store.homeData,
  };
});

业务当中一般可以接受, 因为数据通常都是在更新的. 新能敏感场景需要额外考虑.


ruled-router 提供的优化

/home/plant/123/shop/456/789

解析为

{
  "raw": "home",
  "name": "home",
  "matches": true,
  "restPath": ["plant", "123", "shop", "456", "789"],
  "params": {},
  "data": {},
  "next": {
    "raw": "plant/:plantId",
    "name": "plant",
    "matches": true,
    "restPath": ["shop", "456", "789"],
    "params": {
      "plantId": "123"
    },
    "data": {
      "plantId": "123"
    },
    "next": {
      "raw": "shop/:shopId/:corner",
      "name": "shop",
      "matches": true,
      "next": null,
      "restPath": [],
      "data": {
        "shopId": "456",
        "corner": "789"
      },
      "params": {
        "plantId": "123",
        "shopId": "456",
        "corner": "789"
      }
    }
  }
}

生成对象保存起来, 路由发生变更时再重新解析. 这样对象引用一般保持一致.

来自:https://segmentfault.com/a/1190000020769066


本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!

链接: https://shenqiku.cn/article/FLY_6081