React个人实践和日常性能优化(一)

1,091 阅读4分钟

Author: arcsin1
time: 2020.07.14

深夜行文,想想,写了这么久react了,还是有些感想的。 以下主要讨论 React个人的一些实践,性能优化和一些小技巧相关 。如果你觉得可以,请多点赞,鼓励我写出更精彩的文章🙏。

先讲一个小故事

目前我是主专注可视化的前端,所以用antv,d3比较多,我们先看看我同事用g2更新图表

// 假设这一个组件图表的初始config
 const styleJson = {
    data,
    title: {
      visible: true,
      text: '带数据点的折线图',
    },
    xField: 'year',
    yField: 'value',
    point: {
      visible: true,
      size: 5,
      shape: 'diamond',
      style: {
        fill: 'white',
        stroke: '#2593fc',
        lineWidth: 2,
      },
    },
  };
    const data = [
    { year: '1991', value: 3 },
    { year: '1992', value: 4 },
    { year: '1993', value: 3.5 },
    { year: '1994', value: 5 },
    { year: '1995', value: 4.9 },
    { year: '1996', value: 6 },
    { year: '1997', value: 7 },
    { year: '1998', value: 9 },
    { year: '1999', value: 13 },
  ];
// 真实渲染
import React from 'react';
import { Line } from '@ant-design/charts';
const Page: React.FC = () => {

  const [config, setConfig] = useState<any>({});
  
  useEffect(() => {
     setConfig(styleJson);
  }, [styleJson]);
  
  return (
  	<div>
  		<Line {...config} />
  </div>;
  )
};
export default Page;

当然上面可以好好渲染(伪代码),但是你可以想到我们做BI产品等等之类的config的配置肯定需要灵魂动态配置,这样暴力依赖styleJson性能好吗?

当然你觉得动态更新styleJson不会更新?会更新的图表的,因为 g2写了一段代码

 // 很巧妙的JSON.stringify,当然也可以用memoData去更新data
 
 useEffect(() => {
    if (chart.current) {
      if (config.onlyChangeData) {
        chart.current.changeData(config?.data || []);
      } else {
        chart.current.updateConfig(config);
        chart.current.render();
      }
    }
  }, [config?.memoData ? config.memoData : JSON.stringify(config)]);
  

但是对我来说,其实是很暴力的 ,我不只是想更新data,还想更新config啊!对比config这个对象深层了肯定性能不够好,至少我是这么认为,那么有没有好的办法呢?

当然有,我也会经常用到,当然也是我个人用法:

import React from 'react';
import { Line } from '@ant-design/charts';
const Page: React.FC = () => {

  const [config, setConfig] = useState<any>({});
  const [updateKey, setUpdateKey] = useState<number>(1);
  useEffect(() => {
    setConfig(styleJson);
  }, []);
  
  // 当我要change--styleJson的时候
  const changeConfig =() => {
  		setConfig(newConfig)
  		setUpdateKey(updateKey+1)
  }
  return (
    <div key={String(updateKey)}>
  	  <Line {...config} />
    </div>;
  )
};
export default Page;
 

当然你在想为什么不直接用immer 或者 深度clone。我反过来问你,你套路那么深,为什么不利用key的性质简单点呢?(当然这样写或许不是最优解,但是简单直接利用了key的性质),我是不是更暴力!哈哈哈~~

这是我遇到在团队小伙伴写代码中的一个故事!

减少计算量

0. 减少不必要的嵌套可好?

先看看这个嵌套:

我们这边团队重度使用styled-components ,其实大部分情况下我们都不需要这个玩意,除了性能问题,另外一个困扰我们的是它带来的节点嵌套地狱(如上图)。 说实话有时候webpack的热更新都更不动,说明什么? React 运行时的负担太重了!(我是16寸mac)

所以我们需要理性地选择一些工具,比如使用原生的 CSS,减少 React 运行时的负担.

一般不必要的节点嵌套都是滥用高阶组件/RenderProps 导致的。”只有在必要时才使用 xxx“。 有很多种方式来代替高阶组件/RenderProps,例如优先使用 props、React Hooks

可以去读读这篇文章 CSS in JS的好与坏

1. 不要在渲染函数都进行不必要的计算

  • 比如不要在渲染函数(render)中进行数组排序、数据转换、订阅事件、创建事件处理器等等.
  • 渲染函数中不应该放置太多副作用
  • 先去算好在给到render吧,尽量纯净!!!纯净!!!

2. 虚拟列表

虚拟列表是常见的‘长列表’和’复杂组件树’优化方式,它优化的本质就是减少渲染的节点。

推介一些常用的:

3. 惰性渲染 或者 惰性加载

惰性渲染的初衷本质上和虚表一样,也就是说我们只在必要时才去渲染对应的节点。

  • Tab切换,我没不需要一开始渲染所有tab的内容,还有很多场景
  • 下拉刷新这种,特别是react-grid-layout渲染的仪表盘,如何做到下拉再去请求每个gird容器或者渲染他的内容,这是我做BI可视化遇到的;

避免重新渲染

1. 特别是事件处理

有时候我们需要处理事件,会携带很多东西,以下是很常见很多人的写法:

const handleDelete =(id) => {
 // 
}
<List>
  {list.map(d => (
    <li key={d.id} onClick={() => handleDelete(d.id)} />
  ))}
</List>

其实我很烦上面这个写法,让我们用更好的写法吧:直接用data-*不香吗?

const handleDelete =(e) => {
  // 这样获取不香吗?
  const id = e.currentTarget.dataset.id;
 	
}
<ul>
  {list.map(d => (
    <li key={d.id} onClick={handleDelete} data-id={d.id}/>
  ))}
</ul>

尽量避免使用箭头函数形式的事件处理器

当然我们还可以优化:

 const handleDelete = useCallback(e => {
  const id = e.currentTarget.dataset.id;
  // 
}, []);

  <ul>
    {list.map(d => (
      <li key={d.id} data-id={d.id} onClick={handleDelete} />
    ))}
  </ul>

先写到这里吧,夜深了,该睡了!

后面再写第二章吧!