相关文章推荐
会开车的西装  ·  conda常用基础命令(环境管理和包的安装, ...·  1 年前    · 
留胡子的鼠标  ·  word导航窗格应用:如何对文档进行快速查找 ...·  1 年前    · 
热情的登山鞋  ·  JS与jQuery中html-与-text方 ...·  1 年前    · 
爱吹牛的烤红薯  ·  Spring Boot:基本应用和源码解析 ...·  2 年前    · 
傻傻的大熊猫  ·  JavaScript ...·  2 年前    · 
Code  ›  《机器学习与R语言(原书第2版)》一导读-阿里云开发者社区
机器学习 阿里 社区功能 模型云
https://developer.aliyun.com/article/90124
沉着的火车
1 年前

sinoui开发指南

  • 基础知识
  • 应用开发指南
  • 前端资源
  • GitHub
  • Help

› React进阶

React

  • 教程开篇
  • React入门
  • JSX概述
  • 组件属性和状态
  • React Hook
  • 状态提升
  • 加载数据
  • ref

TypeScript/ES6

  • ES6简明教程
  • TypeScript简明教程
  • 在React中使用TypeScript
  • TypeScript高级类型

样式

  • styled-components
  • 动画

状态管理

  • React Context
  • Redux入门
  • 使用Context和hook做状态管理
  • 在React中使用redux做状态管理
  • 不可变数据

React进阶

  • React性能优化

React性能优化

注意:此文档还在修改状态。

由于 React 并不知道在父组件中的更新是否会影响到其子代,所以 React 会在父组件更新时,将其所有子代也重绘,这会导致很大的性能消耗。为了减少这种不必要的性能损耗,我们可以使用 缓存 的方式处理子组件。这样当 props 浅层比较的结果相同时,父组件发生变化时,React 会去缓存中重用子组件先前的渲染结果,而不是重新渲染子组件。

使用 React.memo() 做组件缓存。

下面我们来对比看下使用缓存前后对性能的影响:

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
interface Props {
  title: string;
function Child(props: Props) {
  return <div>{props.title}</div>;
function Parent() {
  const [, setCount] = useState(0);
  useEffect(() => {
    // 每隔一秒钟重绘一次组件
    setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);
  }, []);
  return (
      <Child title="child1" />
      <Child title="child2" />
ReactDOM.render(<Parent />, document.getElementById('root'));

Parent组件每隔一秒钟发生一次状态变更,两个Child组件也会发生重绘,如下面的 React 渲染火焰图所示(绿色方格代表发生了重绘):

为了避免不必要的渲染,我们可以对上述示例稍加调整:

import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
interface Props {
  title: string;
function Child(props: Props) {
  return <div>{props.title}</div>;
const MemoChild = React.memo(Child);
function Parent() {
  const [, setCount] = useState(0);
  useEffect(() => {
    // 每隔一秒钟重绘一次组件
    setInterval(() => {
      setCount((prev) => prev + 1);
    }, 1000);
  }, []);
  return (
      <MemoChild title="child1" />
      <MemoChild title="child2" />
ReactDOM.render(<Parent />, document.getElementById('root'));

这里我们使用React.memo做了组件缓存,因此在Parent组件状态发生变化时,两个Child组件因为属性没有发生变化,React 会从缓存中取其上次的渲染结果,而不是重新传染。渲染效果如下(火焰图中的两个Memo(Child)小方格都是灰色的,代表未发生重绘):

props浅层比较

上文提到,React.memo(ChildComponent)的缓存依赖于props浅层比较,在两次重绘中,如果给ChildComponent的属性对象通过浅层比较是一致的,那么 React 会从缓存中取上次的渲染结果,缓存有效。我们看看什么是浅层比较。

const objA = {
  a: '1',
  b: '2',
const objB = {
  a: '1',
  b: '2',
Object.keys(objA).every((key) => objA[key] === objB[key]); // true

对象objA和objB都具有同样值的属性,所以这两个对象是相同的。但如果对象中再有对象属性,那么很有可能就不再相等,因为浅层比较只会比较对象的属性值是否相等,而如果对象属性也是对象,浅层比较不会进入这个属性对象再比较。如下所示:

const objA = {
  a: '1',
  b: '2',
  c: {
    t: '3',
const objB = {
  a: '1',
  b: '2',
  c: {
    t: '3',
Object.keys(objA).every((key) => objA[key] === objB[key]); // false

上面两个对象的浅层比较结果为false,因为objA.c和objB.c都是对象,但是objA.c !== objB.c。

接下来,我们来看看各种类型的属性如何配合React.memo(),达到缓存的目的。

函数属性是常见的一种类型的属性,处理不好函数类型属性,极容易破坏掉属性浅层比较,让组件缓存失效。

const MemoButton = React.memo(Button);
function ButtonDemo() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(prev => prev + 1);
  return <div>
    <MemoButton onClick={handleClick}>点击我</MenuButton>
    <span>被点击次数:{count}</span>
  </div>;

每次点击按钮时,本期望MemoButton组件不会发生重绘,实际上却每次都发生了重绘。重点在于每次重绘ButtonMenu时,都会产生新的handleClick。

我们根据函数属性本身与组件是否有关系分成两类:

  • 动态函数属性 - 函数实现与组件相关,需要在组件内部定义。如按钮点击事件处理器。
  • 静态函数属性 - 函数实现与组件无关,可以在组件外部定义。如表单值校验函数。
  • 我们逐个看看这两种情况如何处理。

    动态函数属性

    这里我们首先来看一个示例:

    import React, { useState, ChangeEvent } from 'react';
    import ReactDOM from 'react-dom';
    function Input(props: {
      value?: string;
      onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
      return <input type="text" value={props.value} onChange={props.onChange} />;
    const TextInput = React.memo(Input);
    function Demo() {
      const [value1, setValue1] = useState('');
      const [value2, setValue2] = useState('');
      const onChangeValue1 = (event: ChangeEvent<HTMLInputElement>) => {
        setValue1(event.target.value);
      const onChangeValue2 = (event: ChangeEvent<HTMLInputElement>) => {
        setValue2(event.target.value);
      return (
          <TextInput value={value1} onChange={onChangeValue1} />
          <TextInput value={value2} onChange={onChangeValue2} />
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    思考一个问题:此时如果改变第二个TextInput值,第一个TextInput会重新渲染吗?

    一定会的,看下渲染结果:

    可能你会有疑问:不是已经用了React.memo缓存了吗?为什么还会重新渲染呢?

    其实这是因为TextInput的onChange属性,这是一个用来监听值变化的回调函数,输入框的值发生改变时,Demo组件会重绘,而此时onChange属性会指向一个新的方法,此时TextInput的属性浅层比较会返回false,组件就会重新渲染。

    解决上述问题有两种方式:

    方式一:使用React.useCallback缓存回调函数

    import React, { useState, ChangeEvent, useCallback } from 'react';
    import ReactDOM from 'react-dom';
    function Input(props: {
      value?: string;
      onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
      return <input type="text" value={props.value} onChange={props.onChange} />;
    const TextInput = React.memo(Input);
    function Demo() {
      const [value1, setValue1] = useState('');
      const [value2, setValue2] = useState('');
      const onChangeValue1 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue1(event.target.value);
      }, []);
      const onChangeValue2 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue2(event.target.value);
      }, []);
      return (
          <TextInput value={value1} onChange={onChangeValue1} />
          <TextInput value={value2} onChange={onChangeValue2} />
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    上述示例中,useCallback的依赖项数组为[],所以,onChangeValue1与onChangeValue2这两个方法只会在组件初始化时创建一次,后续组件值发生改变时,会直接使用它的memoized版本。因此当其中一个TextInput的值发生改变时,另一个TextInput组件的属性满足浅层比较,React 会从缓存读取其上次渲染结果,而非重新渲染。

    方式二:使用React.useReducer代替React.useState管理状态

    import React, { ChangeEvent, useReducer } from 'react';
    import ReactDOM from 'react-dom';
    import produce from 'immer';
    interface Action {
      type: string;
      payload: any;
    interface State {
      [name: string]: any;
    const reducer = produce((state: State, action: Action) => {
      switch (action.type) {
        case 'CHANGE_VALUE':
          state[action.payload.name] = action.payload.value;
          return state;
        default:
          return state;
    function Input(props: any) {
      const onChangeValue = (event: ChangeEvent<HTMLInputElement>) => {
        props.dispatch({
          type: 'CHANGE_VALUE',
          payload: { name: props.name, value: event.target.value },
      return <input type="text" value={props.value} onChange={onChangeValue} />;
    const TextInput = React.memo(Input);
    function Demo() {
      const [state, dispatch] = useReducer(reducer, {});
      return (
          <TextInput value={state.value1} name="value1" dispatch={dispatch} />
          <TextInput value={state.value2} name="value2" dispatch={dispatch} />
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    上述示例中,我们向子组件传递dispatch而不是回调函数。这样当第一个TextInput值发生改变时,第二个TextInput组件的属性并没有发生改变,因此其不会重新渲染。

    静态函数属性

    当组件属性为一个静态函数时,为了减少不必要的渲染,我们可以将函数提升到组件外部,下面我们将通过两个实例对比提升前和提升后的影响。

    组件内部处理:

    import React, { useState, ChangeEvent, useCallback } from 'react';
    import ReactDOM from 'react-dom';
    function Input(props: any) {
      const error = props.validate(props.value);
      return (
          <input type="text" value={props.value} onChange={props.onChange} />
          <p style={{ color: 'red' }}>{error}</p>
    const TextInput = React.memo(Input);
    function Demo() {
      const [value1, setValue1] = useState('');
      const [value2, setValue2] = useState('');
      const onChangeValue1 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue1(event.target.value);
      }, []);
      const onChangeValue2 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue2(event.target.value);
      }, []);
      const validate = (value: any) => {
        if (!value) {
          return '必填';
      return (
          <TextInput value={value1} onChange={onChangeValue1} validate={validate} />
          <TextInput value={value2} onChange={onChangeValue2} validate={validate} />
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    此时只要任意一个输入框的值发生改变,父组件就会重新渲染,创建新的 validate 函数传给子组件,这样就破坏了属性浅层比较,子组件会重新渲染。

    我们对上述示例稍加调整,将validate提升至组件外部:

    import React, { useState, ChangeEvent, useCallback } from 'react';
    import ReactDOM from 'react-dom';
    function Input(props: any) {
      const error = props.validate(props.value);
      return (
          <input type="text" value={props.value} onChange={props.onChange} />
          <p style={{ color: 'red' }}>{error}</p>
    const TextInput = React.memo(Input);
    function validate(value: any) {
      if (!value) {
        return '必填';
    function Demo() {
      const [value1, setValue1] = useState('');
      const [value2, setValue2] = useState('');
      const onChangeValue1 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue1(event.target.value);
      }, []);
      const onChangeValue2 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue2(event.target.value);
      }, []);
      return (
          <TextInput value={value1} onChange={onChangeValue1} validate={validate} />
          <TextInput value={value2} onChange={onChangeValue2} validate={validate} />
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    我们来看下渲染结果的分析图:

    从火焰图中我们可以看出,当我们改变第一个TextInput值时,第二个TextInput并没有重新渲染。这是因为当我们把校验函数提升到组件外部,即全局作用域中,所以无论Demo内部如何变化,validate都不会发生改变,这样第二个TextInput组件的属性满足浅层比较,因此不会重新渲染。

    数组、对象属性

    不正确指定数组、对象属性,也会破坏掉React.memo,看几个缓存失效的示例。

    示例 1:

    function ButtonDemo() {
      return (
        <MemoButton
          style={{
            color: 'red',
        </MemoButton>
    

    示例 2:

    function TodoDemo() {
      const completedItems = items.filter((item) => item.completed);
      return <List items={completedItems} />;
    

    我们根据对象、数组的来源分成两个类别:

  • 动态计算的对象属性 - 如示例 2 中的completedItems
  • 静态对象属性 - 如示例 1 中的style
  • 接下来我们分别看看怎么处理,以达到缓存的目的。

    动态计算出的对象属性

    import React, { useState, useEffect } from 'react';
    import ReactDOM from 'react-dom';
    function UserInfo(props: any) {
      const { userName, age, duty, fav, birthday } = props.userInfo;
      return (
          <div>姓名:{userName}</div>
          <div>年龄:{age}</div>
          <div>爱好:{fav}</div>
          <div>职务:{duty}</div>
          <div
    
    
    
    
        
    >出生年份:{birthday}</div>
        </div>
    const User = React.memo(UserInfo);
    const data = {
      userName: '张三',
      age: 19,
      fav: '篮球、排球',
      duty: '处员',
    function Demo() {
      const currentYear = new Date().getFullYear();
      const [, setCount] = useState(0);
      useEffect(() => {
        // 每隔一秒钟重绘一次组件
        setInterval(() => {
          setCount((prev) => prev + 1);
        }, 1000);
      }, []);
      const userInfo = {
        ...data,
        age: `${data.age}岁`,
        birthday: currentYear - data.age,
      return <User userInfo={userInfo} />;
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    我们来思考一个问题:每次Demo组件重绘时,UserInfo会不会重绘?

    很显然,答案是会的,因为Demo每次重绘,都会定义一个新的userInfo对象传给UserInfo组件,不满足其属性浅层比较,因此UserInfo会发生重绘。

    这种情况很容易处理,我们只需要使用React.useMemo缓存一下userInfo这个对象属性即可:

    import React, { useState, useEffect, useMemo } from 'react';
    import ReactDOM from 'react-dom';
    function UserInfo(props: any) {
      const { userName, age, duty, fav, birthday } = props.userInfo;
      return (
          <div>姓名:{userName}</div>
          <div>年龄:{age}</div>
          <div>爱好:{fav}</div>
          <div>职务:{duty}</div>
          <div>出生年份:{birthday}</div>
        </div>
    const User = React.memo(UserInfo);
    const data = {
      userName: '张三',
      age: 19,
      fav: '篮球、排球',
      duty: '处员',
    function Demo() {
      const currentYear = new Date().getFullYear();
      const [, setCount] = useState(0);
      useEffect(() => {
        // 每隔一秒钟重绘一次组件
        setInterval(() => {
          setCount((prev) => prev + 1);
        }, 1000);
      }, []);
      const userInfo = useMemo(
        () => ({
          ...data,
          age: `${data.age}岁`,
          birthday: currentYear - data.age,
        [data, currentYear],
      return <User userInfo={userInfo} />;
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    此时,在父组件发生重绘时,子组件是不会重新渲染的,原因是因为每次重绘,currentYear和data都没发生改变,userInfo一直都是第一次渲染时定义的那个,不会发生改变,这样就满足了UserInfo组件的浅层比较,因此不会重新渲染。

    静态对象属性

    当组件属性为静态对象时,我们可以将其提升到组件外部的全局作用域,以此来满足子组件属性的浅层比较。这里我们给输入框添加 label,并指定其样式,通过对比下面两个示例,看下静态对象属性对组件渲染的影响:

    直接传递静态对象属性

    import React, { useState, ChangeEvent, useCallback } from 'react';
    import ReactDOM from 'react-dom';
    function Input(props: any) {
      return (
          <label style={props.labelStyle}>{props.label}</label>
          <input type="text" value={props.value} onChange={props.onChange} />
    const TextInput = React.memo(Input);
    function Demo() {
      const [value1, setValue1] = useState('');
      const [value2, setValue2] = useState('');
      const onChangeValue1 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue1(event.target.value);
      }, []);
      const onChangeValue2 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue2(event.target.value);
      }, []);
      return (
          <TextInput
            labelStyle={{ color: 'blue' }}
            label="用户名"
            value={value1}
            onChange={onChangeValue1}
          <TextInput
            labelStyle={{ color: 'blue' }}
            label="密码"
            value={value2}
            onChange={onChangeValue2}
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    改变一个输入框的值,由于labelStyle为对象属性,不满足TextInput属性的浅层比较,因此组件会重新渲染:

    我们尝试将上述示例中的lableStyle提升至Demo组件外部的全局作用域中,再看下效果:

    import React, { useState, ChangeEvent, useCallback } from 'react';
    import ReactDOM from 'react-dom';
    function Input(props: any) {
      return (
          <label style={props.labelStyle}>{props.label}</label>
          <input type="text" value={props.value} onChange={props.onChange} />
    const TextInput = React.memo(Input);
    const labelStyle = { color: 'blue' };
    function Demo() {
      const [value1, setValue1] = useState('');
      const [value2, setValue2] = useState('');
      const onChangeValue1 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue1(event.target.value);
      }, []);
      const onChangeValue2 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue2(event.target.value);
      }, []);
      return (
          <TextInput
            labelStyle={labelStyle}
            label="用户名"
            value={value1}
            onChange={onChangeValue1}
          <TextInput
            labelStyle={labelStyle}
            label="密码"
            value={value2}
            onChange={onChangeValue2}
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    将labelStyle提升至全局作用域,这样每次组件内部发生变化时,labelStyle并没有变,满足TextInput属性的浅层比较,因此在第一个TextInput值发生改变时,父组件会从缓存中取第二个TextInput的渲染结果,而不是重新渲染。

    children 属性

    静态JSX的优化

    静态JSX优化,可以将静态的Jsx部分提升到组件外部定义:

    import React from 'react';
    import ReactDOM from 'react-dom';
    function Child(props: any) {
      return <p>{props.children}</p>;
    const childs = (
        <Child>123</Child>
        <Child>这是文本</Child>
    function Parent() {
      return <div>{childs}</div>;
    ReactDOM.render(<Parent />, document.getElementById('root'));
    

    在 React 应用中我们无需手动处理,可以借助react-constant-elements 工具完成,需要注意的是此插件只作用于生产环境。

    使用工具度量 React 组件性能

    react-devtools

    这里我们主要是用 react-devtools 的火焰图来做性能分析。

    我们以一个具体示例来说明:

    import React, { useState, ChangeEvent, useCallback } from 'react';
    import ReactDOM from 'react-dom';
    function Input(props: any) {
      const error = props.validate(props.value);
      return (
          <input type="text" value={props.value} onChange={props.onChange} />
          <p style={{ color: 'red' }}>{error}</p>
    const TextInput = React.memo(Input);
    function validate(value: any) {
      if (!value) {
        return '必填';
    function Demo() {
      const [value1, setValue1] = useState('');
      const [value2, setValue2] = useState('');
      const onChangeValue1 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue1(event.target.value);
      }, []);
      const onChangeValue2 = useCallback((event: ChangeEvent<HTMLInputElement>) => {
        setValue2(event.target.value);
      }, []);
      return (
          <TextInput value={value1} onChange={onChangeValue1} validate={validate} />
          <TextInput value={value2} onChange={onChangeValue2} validate={validate} />
    ReactDOM.render(<Demo />, document.getElementById('root'));
    

    react-devtools 使用方式:

  • 运行上述示例
  • 打开 F12 控制台,将页签切换至React
  • 将工具条从Elements切换至Profilter
  • 点击小圆点开始分析,改变值使组件重绘点击stop按钮,收集分析结果
  • 分析结果如下:

    图中有三块区域:

  • 组件渲染次数
  • 每次渲染时相关组件的渲染情况(灰色代表不渲染,绿色或黄色代表重新渲染)
  • 渲染信息,包括组件的总渲染次数,渲染耗时,组件属性等信息
  • 从上图结果不难看出,当我们改变第一个输入框的值时,父组件发生重绘,此时第一个输入框也会重新渲染,第二个输入框组件则不会重新渲染。

    performance 分析

    这是 Chrome 浏览器自带的性能分析工具。

    打开控制台,切换到Performance页签,确保 screenshots checkbox 是选中的,然后点击 controls,开始记录(windows 快捷键 shift + E),这时候 Devtools 就开始录制各种性能指标,你可以点击页面等进行各种操作,所有的操作都会被浏览器录制下来。录制期间, 可以点击 stop 进行快速操作结束录制,然后等待显示性能报告,stop 按钮位置如下图:

    分析结果如下:

    上图一共有三个区域:

  • overview 总览图,高度概括随时间线的变动,包括 FPS,CPU,NET
  • 火焰图,从不同的角度分析框选区域 。例如:Network,Frames, Interactions, Main 等
  • 总结区域:精确到毫秒级的分析,以及按调用层级,事件分类的整理
  • overview

    Overview 窗格包含以下三个图表:

  • FPS。每秒帧数。绿色竖线越高,FPS 越高。 FPS 图表上的红色块表示长时间帧,很可能会出现卡顿
  • CPU。 CPU 资源。此面积图指示消耗 CPU 资源的事件类型
  • NET。每条彩色横杠表示一种资源。横杠越长,检索资源所需的时间越长。 每个横杠的浅色部分表示等待时间(从请求资源到第一个字节下载完成的时间)
  • 可以放大显示一部分记录,以便简化分析。使用 Overview 窗格可以放大显示一部分记录。 放大后,火焰图会自动缩放以匹配同一部分

    在火焰图上看到一到三条垂直的虚线。蓝线代表 DOMContentLoaded 事件。 绿线代表首次绘制的时间。 红线代表 load 事件

    在火焰图中选择事件时,Details 窗格会显示与事件相关的其他信息。

    蓝色(Loading):网络通信和 HTML 解析 黄色(Scripting):JavaScript 执行 紫色(Rendering):样式计算和布局,即重排 绿色(Painting):重绘 灰色(other):其它事件花费的时间 白色(Idle):空闲时间

    react perf devtools

    class 组件的 shouldComponentUpdate

    使用shouldComponentUpdate()可以让 React 知道当前状态或属性的改变是否不影响组件的输出,默认返回 ture,返回 false 时不会重新渲染。

    class CounterButton extends React.Component {
      constructor(props) {
        super(props);
        this.state = {count: 1};
      shouldComponentUpdate(nextProps, nextState) {
        if (this.props.color !== nextProps.color) {
          return true;
        if (this.state.count !== nextState.count) {
          return true;
        return false;
      render() {
        return (
          <button
            color={this.props.color}
            onClick={() => this.setState(state => ({count: state.count + 1}))}>
            Count: {this.state.count}
          </button>
    

    上述示例中只有当props.color或者state.count值发生变化时,组件才会重新渲染。

    如果更复杂一些的组件,我们可以使用类似“浅比较”的模式来检查 props 和 state 中所有的字段,以此来决定是否组件需要更新。React 已经提供了一位好帮手来帮你实现这种常见的模式 - 你只要继承 React.PureComponent 就行了。所以这段代码可以改成以下这种更简洁的形式:

    class CounterButton extends React.PureComponent {
      constructor(props) {
        super(props);
        this.state = { count: 1 };
      render() {
        return (
          <button
            color={this.props.color}
            onClick={() => this.setState((state) => ({ count: state.count + 1 }))}
            Count: {this.state.count}
          </button>
    

    减小视图大小

    减小视图大小也是一个非常有用的优化手段。

    比如我们有一个数据非常多的长列表,如果我们一次性渲染,肯定会性能非常差,但是如果我们只渲染看得见的部分,性能就得到了极大的提升。

    下面举一个通过windowing的例子说明一下:

    import { FixedSizeList as List } from 'react-window';
    const Row = ({ index, style }) => <div style={style}>Row {index}</div>;
    const Example = () => (
      <List height={150} itemCount={1000} itemSize={35} width={300}>
        {Row}
      </List>
    

    在CodeSandBox上看一下运行效果。

    这个长列表有 1000 个数据项,但是一次性只渲染 10 条数据项。这个列表的性能就会非常好。

    对于不可见的 UI 部分,我们都可以采用延迟渲染的技巧来减少视图大小,提升性能。

    状态更新合并

    React 对状态更新做了一个优化:同时多次设置状态,不会引起多次重绘,而只会合并为一次重绘。当然这个优化是有前提的。我们来看两个例子。

    例子 1:

    import ReactDOM from 'react-dom';
    import React, { useState } from 'react';
    function Demo1() {
      const [A, setA] = useState();
      const [B, setB] = useState();
      const handleClick = () => {
        setA(1);
        setB(2);
      return (
          <button onClick={handleClick}>更新</button>
            {A} - {B}
          </div>
        </div>
    ReactDOM.render(<Demo1 />, document.getElementById('root'));
    

    点击例子 1 中的按钮,它会分别更新A和B两个状态,但是却只重绘了一次Demo1组件:

    再看看例子 2:

    import ReactDOM from 'react-dom';
    import React, { useState } from 'react';
    function Demo2() {
      const [A, setA] = useState();
      const [B, setB] = useState();
      const handleClick = () => {
        setTimeout(() => {
          setA(1);
          setB(2);
      return (
          <button onClick={handleClick}>更新</button>
            {A} - {B}
          </div>
        </div>
    ReactDOM.render(<Demo2 />, document.getElementById('root'));
    

    点击例子 2 中的按钮,你会发现Demo2组件重绘了两次:

    分析例子 2 与例子 1 的代码不同:

    import ReactDOM from 'react-dom';
    import React, { useState } from 'react';
    function Demo2() {
      const [A, setA] = useState();
      const [B, setB] = useState();
      const handleClick = () => {
    +   setTimeout(() => {
          setA(1);
          setB(2);
    +   });
      return (
          <button onClick={handleClick}>更新</button>
            {A} - {B}
    ReactDOM.render(<Demo2 />, document.getElementById('root'));
    

    最重要的区别是:

    setTimeout(() => {
      setA(1);
      setB(2);
    

    在setTimeout()中执行状态更新,每一次状态更新都会引起重绘,而不会合并为一次重绘。不仅仅是setTimeout(),还包括setInterval()、Promise、web socket、ajax、Observable等都是这样的。这是因为这些状态更新不是在 React Scheduler 中而是在其他环境中执行的。这里不深入展开对 React Scheduler 的分析,大家感兴趣的可以了解一下相关知识。

    目前有两种方式解决:

    方式一:使用useReducer:

    import ReactDOM from 'react-dom';
    import React, { useReducer } from 'react';
    function reducer(
      state: { A?: number; B?: number },
      action: { type: 'CHANGE' },
      switch (action.type) {
        case 'CHANGE':
          return {
            A: 1,
            B: 2,
        default:
          return state;
    function Demo3() {
      const [state, dispatch] = useReducer(reducer, {});
      const handleClick = () => {
        setTimeout(() => {
          dispatch({ type: 'CHANGE' });
      return (
          <button onClick={handleClick}>更新</button>
            {state.A} - {state.B}
          </div>
        </div>
    ReactDOM.render(<Demo3 />, document.getElementById('root'));
    

    方式二:使用ReactDOM.unstable_batchedUpdates():

    import ReactDOM from 'react-dom';
    import React, { useState } from 'react';
    function Demo2() {
      const [A, setA] = useState();
      const [B, setB] = useState();
      const handleClick = () => {
        setTimeout(() => {
          ReactDOM.unstable_batchedUpdates(() => {
            setA(1);
            setB(2);
      return (
          <button onClick={handleClick}>更新</button>
            {A} - {B}
          </div>
        </div>
    ReactDOM.render(<Demo2 />, document.getElementById('root'));
    

    ReactDOM.unstable_batchedUpdates(fn)会在 React Scheduler 上下文中执行fn函数,所以setA(1)和setB(2)就会得到 React Scheduler 的优化,只会引发一次重绘。

    但是ReactDOM.unstable_batchedUpdates() API 还处于不稳定状态,而且是从ReactDOM中引出来的,就会有React Native的兼容性问题。建议使用import { batch } from 'react-redux';中的batch代替ReactDOM.unstable_batchedUpdates。

  • 将 React 作为 UI 运行时
  •  
    推荐文章
    会开车的西装  ·  conda常用基础命令(环境管理和包的安装,卸载及更新)_conda安装包命令-CSDN博客
    1 年前
    留胡子的鼠标  ·  word导航窗格应用:如何对文档进行快速查找定位? - 腾讯云开发者社区-腾讯云
    1 年前
    热情的登山鞋  ·  JS与jQuery中html-与-text方法的区别 - QiaoZhi - 博客园
    1 年前
    爱吹牛的烤红薯  ·  Spring Boot:基本应用和源码解析 - 简书
    2 年前
    傻傻的大熊猫  ·  JavaScript 价格正则表达式_js价格正则_zgljl2012的博客-CSDN博客
    2 年前
    今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
    删除内容请联系邮箱 2879853325@qq.com
    Code - 代码工具平台
    © 2024 ~ 沪ICP备11025650号