part4-m3

  • 本阶段将带你学习前端圈子中口碑极佳的 React 框架以及它的一些高阶用法、组件库封装、数据流方案、服务端渲染(SSR)、静态站点生成(SSG),同时深入 React 框架内部,剖析 React 部分核心源码和实现,最后还会结合 TypeScript 和蚂蚁金服的 Ant Design 库做出实战。

模块三 React Hooks、Chakra-UI、组件性能优化、封装组件库

  • 本模块中围绕 React Hooks、Chakra-UI、组件性能优化、封装组件库四个小专题,对 React 的使用和了解做一个全面进阶,首先 React Hooks 作为最新的函数式组件状态管理办法,越来越多的被使用。其次 React 组件的封装也能够带我们更深入的认识 React 的组件化开发。

任务一:Hooks

  1. ReactHooks专题内容介绍
  • 对函数型组件进行增强 React 16.8中新添加的特性,让函数组件能做类组件能做的事情
  • React Hooks 介绍
  • React Hooks 使用 本质上就是一对钩子函数
  • 自定义 Hook React 提供的 Hooks函数,加上自定义的业务逻辑就是自定义 Hook
  1. ReactHooks功能介绍
  • React Hooks 是用来做什么的
    • 对函数型组件进行增强,让函数型可以储存状态,可以拥有处理副作用的能力
    • 让开发者在不使用类组件的情况下,实现相同的功能。
    • 从 React 16.8 开始推荐使用函数去创建组件
    • 副作用就是为按钮添加点击事件,设置定时器,发送Ajax请求,都是副作用,在类组件中,通常使用生命周期处理副作用,在函数型组件中就要使用 hooks 处理这些副作用
  1. 类组件的不足(ReactHooks要解决的问题)
  • 缺少逻辑复用机制
    • 为了复用逻辑增加无实际渲染效果的组件,增加了组件层级,显示十分臃肿
    • 增加了调试的难度以及运行效率的降低
  • 类组件经常会变得很复杂难以维护
  • 将一组相干爷业务逻辑拆分到了多个生命周期函数中
  • 在一个生命周期函数内存在多个不相干的业务逻辑
  • 类成员方法不能保证this指向的正确性
  • 当给一个元素绑定事件,在事件处理函数中要处理状态的时候,通常要更正这个函数内部的this指向,否则会指向undefined。
  1. 使用useState让函数组件保存状态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-euHdBVO1-1629978935891)(./img/1629785783743.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qUNSjJgn-1629978935893)(./img/1629785847452.jpg)]

  • useState 内部是通过闭包来实现保存状态的
  • 点击按钮,状态改变,组件重新渲染,但状态依然存在
  1. useState方法的使用细节

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CeaXXbgx-1629978935894)(./img/1629787221623.jpg)]

  • useState 参数可以是函数,只有组件第一次执行的时候执行一次,后面组件重新渲染不会再执行
  1. 设置状态值方法的使用细节
  • 设置状态值方法的参数可以是一个值也可以只一个函数
  • 设置状态值的方法本身值异步的
  1. 钩子函数useReducer
  • useReducer 是另一种让函数组件保存状态的方式
  • 相对于 useState,useReducer 的好处在于,如果子组件想改改变值不要传递多个方法,只需要传递 dispatch 就可以了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-popnOsw5-1629978935897)(./img/1629790477601.jpg)]

  1. 钩子函数useContext

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UabH6nUV-1629978935899)(./img/1629791582019.jpg)]

  • 使用 countContext.Consumer 组件包裹也能拿到值 { value => { } }
  1. useEffect钩子函数执行时机分析
  • 让函数型组件拥有处理副作用的能力,类似生命周期函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLonrruf-1629978935900)(./img/1629792710175.jpg)]

  • 第三种是在组件卸载前执行,完成一些清理工作
  1. useEffect使用方式
  • 为 window 对象添加滚动事件 要给页面设置高度
  • 设置定时器让count数值每隔一秒加1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n6ahxGLK-1629978935901)(./img/1629793539607.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sTcD2wmy-1629978935902)(./img/1629794856509.jpg)]

  • useEffect 解决的问题
    • 按照用途将代码进行分类(将一组想干的业务逻辑归置到了同一个副作用的函数中)
    • 简化重复代码,使组件内部代码更加清晰;在类组件,通常情况下,组件挂载后的事情和组件更新要做的事情都是一样的
  1. useEffect钩子函数的第二个参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ofEh5HaF-1629978935903)(./img/1629795476184.jpg)]

  1. useEffect钩子函数结合异步函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H7i8J3Fm-1629978935904)(./img/1629795689156.jpg)]

  1. 钩子函数useMemo

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBD7oJj7-1629978935905)(./img/1629795788185.jpg)]

  1. 使用memo方法提高组件性能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fCoo73As-1629978935906)(./img/1629796279022.jpg)]

  1. useCallback钩子函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DwYX1uPu-1629978935907)(./img/1629796463746.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ib6DpLzr-1629978935907)(./img/1629796799086.jpg)]

  • 不会导致被缓存的里层组件被重新渲染
  1. 使用useRef钩子函数获取DOM元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XXFdjLqj-1629978935909)(./img/1629797004078.jpg)]

  1. 使用useRef钩子函数保存数据(跨组件周期)
  • 保存数据(跨组件周期)
    • 即使组件重新渲染,保存的数据仍然还在,保存的数据被更改不会触发组件重新渲染

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yjnBgv94-1629978935909)(./img/1629797614586.jpg)]

  1. 自定义hook函数(一)
  • 自定义 Hook 是标准的封装和共享逻辑的方式
  • 自定义 Hook 是一个函数,其名称以 use 开头
  • 自定义 Hook 其实就是逻辑和内置 Hook 的组合
  • 类组件用渲染属性和高阶组件做逻辑共享

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QQkuhABl-1629978935910)(./img/1629798228370.jpg)]

  1. 自定义hook函数(二)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SqP9KZtN-1629978935911)(./img/1629798562519.jpg)]

  1. 路由钩子函数的使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YQ3JbFja-1629978935912)(./img/1629798663326.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8yW1lsLW-1629978935915)(./img/1629798774276.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4cyC21jT-1629978935916)(./img/1629798806030.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IXtdlnV4-1629978935917)(./img/1629798941091.jpg)]

  1. useState钩子函数的实现原理

  2. useEffect钩子函数的实现原理

  3. useReducer钩子函数的实现原理

import React from 'react';
import ReactDOM from 'react-dom';

let state = [];
let setters = [];
let stateIndex = 0;

function createSetter (index) {
  return function (newState) {
    state[index] = newState;
    render ();
  }
}

function useState (initialState) {
  state[stateIndex] = state[stateIndex] ? state[stateIndex] : initialState;
  setters.push(createSetter(stateIndex));
  let value = state[stateIndex];
  let setter = setters[stateIndex];
  stateIndex++;
  return [value, setter];
}

function render () {
  stateIndex = 0;
  effectIndex = 0;
  ReactDOM.render(<App />, document.getElementById('root'));
}

// 上一次的依赖值
let prevDepsAry = [];
let effectIndex = 0;

function useEffect(callback, depsAry) {
  // 判断callback是不是函数
  if (Object.prototype.toString.call(callback) !== '[object Function]') throw new Error('useEffect函数的第一个参数必须是函数');
  // 判断depsAry有没有被传递
  if (typeof depsAry === 'undefined') {
    // 没有传递
    callback();
  } else {
    // 判断depsAry是不是数组
    if (Object.prototype.toString.call(depsAry) !== '[object Array]') throw new Error('useEffect函数的第二个参数必须是数组');
    // 获取上一次的状态值
    let prevDeps = prevDepsAry[effectIndex];
    // 将当前的依赖值和上一次的依赖值做对比 如果有变化 调用callback
    let hasChanged = prevDeps ? depsAry.every((dep, index) => dep === prevDeps[index]) === false : true;
    // 判断值是否有变化
    if (hasChanged) {
      callback();
    }
    // 同步依赖值
    prevDepsAry[effectIndex] = depsAry;
    effectIndex++;
  }
}

function useReducer (reducer, initialState) {
  const [state, setState] = useState(initialState);
  function dispatch (action) {
    const newState = reducer(state, action);
    setState(newState);
  }
  return [state, dispatch];
}

function App() {
  function reducer (state, action) {
    switch (action.type) {
      case 'increment':
        return state + 1;
      case 'decrement':
        return state - 1;
      default:
        return state;
    }
  }
  const [count, dispatch] = useReducer(reducer, 0);
  return <div>
    {count}
    <button onClick={() => dispatch({type: 'increment'})}>+1</button>
    <button onClick={() => dispatch({type: 'decrement'})}>-1</button>
  </div>;
}

export default App;

任务一:Formik

  1. formik介绍及基本使用
  • React 表单增强
  • 增强表单处理能力,简化表单处理流程
  • npm install formik

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CkjVwySb-1629978935918)(./img/1629856074967.jpg)]

  1. formik表单验证(一)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e4nPyoXg-1629978935919)(./img/1629856387304.jpg)]

  1. formik表单验证(二)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IYOXfjT4-1629978935920)(./img/1629856775141.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOFYDELn-1629978935921)(./img/1629856804666.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w47eqBLL-1629978935921)(./img/1629856855910.jpg)]

  1. formik配合yup进行表单验证
  • npm install yup
  • import * as Yup from ‘yup’

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jjdf3lNA-1629978935923)(./img/1629857111329.jpg)]

  1. 使用getFieldProps方法简化表单代码
  • name: ‘username’ {…formik.getFieldProps(‘username’)} 保持一致
  1. 使用组件的方式构建表单

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wKDdFpCU-1629978935925)(./img/1629857665336.jpg)]

  1. field组件as属性的用法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JWeIVvUa-1629978935928)(./img/1629858115566.jpg)]

  1. 构建自定义表单控件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YhTt066H-1629978935929)(./img/1629858279819.jpg)]

  1. 构建自定义表单控件复选框

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8XeXPSFk-1629978935932)(./img/1629859357147.jpg)]

任务三:Component

  1. 受控组件与非受控组件的选用标准

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UADrBcYK-1629978935933)(./img/1629859609425.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sSRisTcK-1629978935935)(./img/1629859708636.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qTD1jc8s-1629978935939)(./img/1629859758959.jpg)]

  • 需要时时获取数据,去进行处理,选择受控组件,受控组件数据由state管理

任务四:CSS-IN-JS

  1. 专题内容介绍
  • 集成css 代码在JavaScript文件
  • 为什么会有CSS-IN-JS
  • CSS-IN-JS介绍
  • Emotion 库
  1. 为什么会有CSS-IN-JS这种解决方案
  • CSS-IN-JS 是WEB 项目中将CSS 代码捆绑在JavaScript代码中的解决方案
  • 这种方案旨在解决CSS的局限性,例如缺乏动态功能,作用域和可移植性
  • 因为以前开发前端项目都是以页面为单位,css都是通过link标签引入页面中,由于css没有作用域的概念,css会被应用在整个页面。这在当时,没有问题
  • 现在开发都是以组件为单位,希望这个css代码只应用于某个组件,这样,组件与组件之间的css代码不会产生冲突,要实现这样的功能,要css有作用域的概念。
  • 使用 CSS-IN-JS 就可以通过JavaScript模拟CSS作用域
  • 如果在移动组件的时候,只需要移动这个组件本身就可以了,不要担心它是否有其他依赖文件
  • css 不能根据条件动态给元素添加某种样式
  1. CSS-IN-JS解决方案的优缺点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DyAXWIkh-1629978935942)(./img/1629875416828.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QfLLgHcD-1629978935943)(./img/1629875498984.jpg)]

  1. babel配置以支持css属性的两种方式
  • Emotion 介绍
  • Emotion 是一个旨在使用JavaScript编写CSS样式的库
  • npm install @emotion/core @emotion/styled

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UoTJv4E9-1629978935952)(./img/1629875829522.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rt4OjjLr-1629978935957)(./img/1629875965199.jpg)]

  • npm run eject 弹射底层配置
  • npm install @emotion/babel-preset-css-prop
  1. css方法的使用方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lUw85FWd-1629978935959)(./img/1629876446785.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3P8McAUh-1629978935962)(./img/1629876572314.jpg)]

  • { css } from ‘@emotion/core’
  1. emotion中css属性优先级
  • props 对象中的css属性优先级高于组件内部的css样式
  • 在调用组件时可以覆盖组件默认样式
  1. 创建样式化组件
  • Styled Components 样式化组件
  • 样式化组件就是用来构建用户界面的额,是 emotion 库提供的另一种为元素添加样式的方式
  • 创建样式化组件
    • import styled from ‘@emotion/styled’
    • const Button = styled.buttomcolor: red;
    • const Button = styled.button({color: ‘green’});
  1. 样式化组件默认样式的覆盖方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G3g9hHai-1629978935965)(./img/1629878041587.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jyuXRZa-1629978935970)(./img/1629878120787.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K7txSjhv-1629978935971)(./img/1629878269552.jpg)]

  1. 为任何组件添加样式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AVC2HNap-1629978935972)(./img/1629878392181.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y2Pu1Gia-1629978935973)(./img/1629878454414.jpg)]

  1. 为特定父级下的子组件添加样式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxAq3NPl-1629978935974)(./img/1629878640087.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T0y1urdG-1629978935975)(./img/1629878727973.jpg)]

  1. css选择器&

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F3hNahut-1629978935976)(./img/1629878913892.jpg)]

  1. 样式化组件属性as的用法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pkVuiNPe-1629978935977)(./img/1629879073137.jpg)]

  1. 样式组合

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y8mKmsvy-1629978935978)(./img/1629879229427.jpg)]

  1. Global组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qNUbrDrq-1629978935979)(./img/1629879404750.jpg)]

  1. 使用keyframes方法定义关键帧动画

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C9fgcZ15-1629978935980)(./img/1629879587138.jpg)]

  1. 创建主题
  • npm install emotion-theming
  • import { ThemeProvider } from ‘emotion-theming’
  • 将 ThemeProvider 放在视图最外层

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gzuYmM1j-1629978935981)(./img/1629879926086.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OGPlSpms-1629978935982)(./img/1629879960312.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HlJPvJq3-1629978935982)(./img/1629879999089.jpg)]

任务五:ChakraUI

  1. Chakra-ui 组件库介绍
  • 现代化 React UI 框架 Chakra-UI

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y0aab7qn-1629978935983)(./img/1629882427739.jpg)]

  1. Chakra-UI快速开始
  • npm install @chakra-ui/core@1.0.0-next.2
  • 克隆默认主题
    • Chakra-UI 提供的组件是建立在主题基础之上的,只有先引入了主题组件才能够使用其他组件
    • npm install @chakra-ui/theme

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-883OCezj-1629978935986)(./img/1629883104205.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SoEAiTAJ-1629978935986)(./img/1629883534982.jpg)]

  1. 样式属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A1Fgic7N-1629978935987)(./img/1629883602854.jpg)]

  1. 实现暗色和浅色两种模式的切换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y1Fdg0rH-1629978935988)(./img/1629883893542.jpg)]

  1. useColorModeValue钩子函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1GBBhXK-1629978935989)(./img/1629884632481.jpg)]

  1. 强制组件的颜色模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qZhwHcOQ-1629978935989)(./img/1629884632481.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vh9Qt8Ng-1629978935990)(./img/1629884844485.jpg)]

  1. 颜色模式通用设置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k79dKTmi-1629978935991)(./img/1629884632481.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RW2rm7YL-1629978935992)(./img/1629885052766.jpg)]

  1. 主题对象–颜色
  • Colors
  1. 主题对象–间距&大小

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iabpAFav-1629978935993)(./img/1629885683183.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d6Dzsj49-1629978935994)(./img/1629885764531.jpg)]

  1. 主题对象-响应式断点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9bZKfVFq-1629978935995)(./img/1629885914878.jpg)]

  1. 创建标准的chakra-ui组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TEPBxUYE-1629978935996)(./img/1629886188084.jpg)]

  1. 全局化chakra-ui组件样式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LRneARj6-1629978935997)(./img/1629886670365.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FMhd4aRk-1629978935998)(./img/1629887115224.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dwldzSNR-1629978935998)(./img/1629886782136.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1TBNxvDz-1629978936000)(./img/1629886848095.jpg)]

  1. 构建注册表单
  • npm install react-icons --save
  1. 选项卡组件的使用方式

  2. 布局组件板式组件的使用

  3. 表单和卡片的颜色兼容

任务六:React组件性能优化

React 组件性能优化最佳实践

React 组件性能优化的核心是减少渲染真实 DOM 节点的频率,减少 Virtual DOM 比对的频率。

1. 组件卸载前进行清理操作

  • 2.01-组件卸载前执行清理操作

在组件中为 window 注册的全局事件, 以及定时器, 在组件卸载前要清理掉, 防止组件卸载后继续执行影响应用性能.

需求:开启定时器然后卸载组件,查看组件中的定时器是否还在运行。

import React, { useState, useEffect } from "react"
import ReactDOM from "react-dom"

const App = () => {
  let [index, setIndex] = useState(0)
  useEffect(() => {
    let timer = setInterval(() => {
      setIndex(prev => prev + 1)
      console.log('timer is running...')
    }, 1000)
    return () => clearInterval(timer)
  }, [])
  return (
    <button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById("root"))}>
      {index}
    </button>
  )
}

export default App
  • 3.02-通过纯组件提升组件性能(类组件)

2. PureComponent

  1. 什么是纯组件

    纯组件会对组件输入数据进行浅层比较,如果当前输入数据和上次输入数据相同,组件不会重新渲染。

  2. 什么是浅层比较

    比较引用数据类型在内存中的引用地址是否相同,比较基本数据类型的值是否相同。

  3. 如何实现纯组件

    类组件继承 PureComponent 类,函数组件使用 memo 方法

  4. 为什么不直接进行 diff 操作, 而是要先进行浅层比较,浅层比较难道没有性能消耗吗

    和进行 diff 比较操作相比,浅层比较将消耗更少的性能。diff 操作会重新遍历整颗 virtualDOM 树, 而浅层比较只操作当前组件的 state 和 props。

  5. 需求:在状态对象中存储 name 值为张三,组件挂载完成后将 name 属性的值再次更改为张三,然后分别将 name 传递给纯组件和非纯组件,查看结果。

    import React from "react"
    export default class App extends React.Component {
      constructor() {
        super()
        this.state = {name: "张三"}
      }
      updateName() {
        setInterval(() => this.setState({name: "张三"}), 1000)
      }
      componentDidMount() {
        this.updateName()
      }
      render() {
        return (
          <div>
            <RegularComponent name={this.state.name} />
            <PureChildComponent name={this.state.name} />
          </div>
        )
      }
    }
    
    class RegularComponent extends React.Component {
      render() {
        console.log("RegularComponent")
        return <div>{this.props.name}</div>
      }
    }
    
    class PureChildComponent extends React.PureComponent {
      render() {
        console.log("PureChildComponent")
        return <div>{this.props.name}</div>
      }
    }
    
  • 4.03-通过shouldComponentUpdate生命周期函数提升组件性能

3. shouldComponentUpdate

纯组件只能进行浅层比较,要进行深层比较,使用 shouldComponentUpdate,它用于编写自定义比较逻辑。

返回 true 重新渲染组件,返回 false 阻止重新渲染。

函数的第一个参数为 nextProps, 第二个参数为 nextState.

需求: 在页面中展示员工信息, 员工信息包括, 姓名, 年龄, 职位. 但是在页面中只想展示姓名和年龄. 也就是说只有姓名和年龄发生变化时才有必要重新渲染组件, 如果员工的其他信息发生了变化没必要重新渲染组件.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三", age: 20, job: "waiter"}
  }
  componentDidMount() {
    setTimeout(() => this.setState({ job: "chef" }), 1000)
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.state.name !== nextState.name || this.state.age !== nextState.age) {
      return true
    }
    return false
  }

  render() {
    console.log("rendering")
    let { name, age } = this.state
    return <div>{name} {age}</div>
  }
}
  • 5.04-通过纯组件提升组件性能(函数组件)

4. React.memo

  1. memo 基本使用

    将函数组件变为纯组件,将当前 props 和上一次的 props 进行浅层比较,如果相同就阻止组件重新渲染。

    需求:父组件维护两个状态,index 和 name,开启定时器让 index 不断发生变化,name 传递给子组件,查看父组件更新子组件是否也更新了。

    import React, { memo, useEffect, useState } from "react"
    
    function ShowName({ name }) {
      console.log("showName render...")
      return <div>{name}</div>
    }
    
    const ShowNameMemo = memo(ShowName)
    
    function App() {
      const [index, setIndex] = useState(0)
      const [name] = useState("张三")
      useEffect(() => {
        setInterval(() => {
          setIndex(prev => prev + 1)
        }, 1000)
      }, [])
      return (
        <div>
          {index}
          <ShowNameMemo name={name} />
        </div>
      )
    }
    
    export default App
    
  • 6.05-为memo 方法传递自定义比较逻辑
  1. 为 memo 传递比较逻辑

    使用 memo方法自定义比较逻辑,用于执行深层比较。

    比较函数的第一个参数为上一次的 props, 比较函数的第二个参数为下一次的 props, 比较函数返回 true, 不进行渲染, 比较函数返回 false, 组件重新渲染.

    function App() {
      const [person, setPerson] = useState({ name: "张三", age: 20, job: "waiter" })
      return <>
      	<ShowPerson person={person} />
      	<button onClick={() => setPerson({...person, job: "chef"})}>button</button>
      </>
    }
    export default App
    
    function ShowPerson({ person }) {
      console.log("ShowPerson render...")
      return (
        <div>
          {person.name} {person.age}
        </div>
      )
    }
    
    import React, { memo, useEffect, useState } from "react"
    
    const ShowPersonMemo = memo(ShowPerson, comparePerson)
    
    function comparePerson(prevProps, nextProps) {
      if (
        prevProps.person.name !== nextProps.person.name ||
        prevProps.person.age !== nextProps.person.age
      ) {
        return false
      }
      return true
    }
    
    function App() {
      const [person, setPerson] = useState({ name: "张三", age: 20, job: "waiter" })
      return <>
      	<ShowPersonMemo person={person} />
      	<button onClick={() => setPerson({...person, job: "chef"})}>button</button>
      </>
    }
    export default App
    
    1. 06-通过组件懒加载提供应用性能

5. 使用组件懒加载

使用组件懒加载可以减少 bundle 文件大小, 加快组件呈递速度.

  1. 路由组件懒加载

    import React, { lazy, Suspense } from "react"
    import { BrowserRouter, Link, Route, Switch } from "react-router-dom"
    
    const Home = lazy(() => import(/* webpackChunkName: "Home" */ "./Home"))
    const List = lazy(() => import(/* webpackChunkName: "List" */ "./List"))
    
    function App() {
      return (
        <BrowserRouter>
          <Link to="/">Home</Link>
          <Link to="/list">List</Link>
          <Switch>
            <Suspense fallback={<div>Loading</div>}>
              <Route path="/" component={Home} exact />
              <Route path="/list" component={List} />
            </Suspense>
          </Switch>
        </BrowserRouter>
      )
    }
       
    export default App
    
  • 8.07-根据条件进行组件懒加载
  1. 根据条件进行组件懒加载

    适用于组件不会随条件频繁切换

    import React, { lazy, Suspense } from "react"
    
    function App() {
      let LazyComponent = null
      if (true) {
        LazyComponent = lazy(() => import(/* webpackChunkName: "Home" */ "./Home"))
      } else {
        LazyComponent = lazy(() => import(/* webpackChunkName: "List" */ "./List"))
      }
      return (
        <Suspense fallback={<div>Loading</div>}>
          <LazyComponent />
        </Suspense>
      )
    }
    
    export default App
    
  • 9.08-通过使用占位符标记提升React组件的渲染性能

6. 使用 Fragment 避免额外标记

React 组件中返回的 jsx 如果有多个同级元素, 多个同级元素必须要有一个共同的父级.

function App() {
  return (
    <div>
      <div>message a</div>
      <div>message b</div>
    </div>
  )
}

为了满足这个条件我们通常都会在最外层添加一个div, 但是这样的话就会多出一个无意义的标记, 如果每个组件都多出这样的一个无意义标记的话, 浏览器渲染引擎的负担就会加剧.

为了解决这个问题, React 推出了 fragment 占位符标记. 使用占位符标记既满足了拥有共同父级的要求又不会多出额外的无意义标记.

import { Fragment } from "react"

function App() {
  return (
    <Fragment>
      <div>message a</div>
      <div>message b</div>
    </Fragment>
  )
}
function App() {
  return (
    <>
      <div>message a</div>
      <div>message b</div>
    </>
  )
}
  • 10.09-通过避免使用内联函数提升组件性能

7. 不要使用内联函数定义

在使用内联函数后, render 方法每次运行时都会创建该函数的新实例, 导致 React 在进行 Virtual DOM 比对时, 新旧函数比对不相等,导致 React 总是为元素绑定新的函数实例, 而旧的函数实例又要交给垃圾回收器处理.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  render() {
    return (
      <input
        value={this.state.inputValue}
        onChange={e => this.setState({ inputValue: e.target.value })}
        />
    )
  }
}

正确的做法是在组件中单独定义函数, 将函数绑定给事件.

import React from "react"

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {
      inputValue: ""
    }
  }
  setInputValue = e => {
    this.setState({ inputValue: e.target.value })
  }
  render() {
    return (
      <input value={this.state.inputValue} onChange={this.setInputValue} />
    )
  }
}
  • 11.10-在构造函数中进行this指向的更正

8. 在构造函数中进行函数this绑定

在类组件中如果使用 fn() {} 这种方式定义函数, 函数 this 默认指向 undefined. 也就是说函数内部的 this 指向需要被更正.

可以在构造函数中对函数的 this 进行更正, 也可以在行内进行更正, 两者看起来没有太大区别, 但是对性能的影响是不同的.

export default class App extends React.Component {
   constructor() {
    super()
     // 方式一
     // 构造函数只执行一次, 所以函数 this 指向更正的代码也只执行一次.
    this.handleClick = this.handleClick.bind(this)
  }
  handleClick() {
    console.log(this)
  }
  render() {
    // 方式二 
    // 问题: render 方法每次执行时都会调用 bind 方法生成新的函数实例.
    return <button onClick={this.handleClick.bind(this)}>按钮</button>
  }
}
  • 12.11-类组件中的箭头函数

9. 类组件中的箭头函数

在类组件中使用箭头函数不会存在 this 指向问题, 因为箭头函数本身并不绑定 this.

export default class App extends React.Component {
  handleClick = () => console.log(this)
  render() {
    return <button onClick={this.handleClick}>按钮</button>
  }
}

箭头函数在 this 指向问题上占据优势, 但是同时也有不利的一面.

当使用箭头函数时, 该函数被添加为类的实例对象属性, 而不是原型对象属性. 如果组件被多次重用, 每个组件实例对象中都将会有一个相同的函数实例, 降低了函数实例的可重用性造成了资源浪费.

综上所述, 更正函数内部 this 指向的最佳做法仍是在构造函数中使用 bind 方法进行绑定

10. 避免使用内联样式属性

  • 13.12-避免使用内联样式属性以提升组件性能

当使用内联 style 为元素添加样式时, 内联 style 会被编译为 JavaScript 代码, 通过 JavaScript 代码将样式规则映射到元素的身上, 浏览器就会花费更多的时间执行脚本和渲染 UI, 从而增加了组件的渲染时间.

function App() {
  return <div style={{ backgroundColor: "skyblue" }}>App works</div>
}

在上面的组件中, 为元素附加了内联样式, 添加的内联样式为 JavaScript 对象, backgroundColor 需要被转换为等效的 CSS 样式规则, 然后将其应用到元素, 这样涉及到脚本的执行.

更好的办法是将 CSS 文件导入样式组件. 能通过 CSS 直接做的事情就不要通过 JavaScript 去做,因为 JavaScript 操作 DOM 非常慢.

  • 14.13-优化条件渲染以提升组件性能

11. 优化条件渲染

频繁的挂载和卸载组件是一项耗性能的操作, 为了确保应用程序的性能, 应该减少组件挂载和卸载的次数.

在 React 中我们经常会根据条件渲染不同的组件. 条件渲染是一项必做的优化操作.

function App() {
  if (true) {
    return (
      <>
        <AdminHeader />
        <Header />
        <Content />
      </>
    )
  } else {
    return (
      <>
        <Header />
        <Content />
      </>
    )
  }
}

在上面的代码中, 当渲染条件发生变化时, React 内部在做 Virtual DOM 比对时发现, 刚刚第一个组件是 AdminHeader, 现在第一个组件是 Header, 刚刚第二个组件是 Header, 现在第二个组件是 Content, 组件发生了变化, React 就会卸载 AdminHeader、Header、Content, 重新挂载 Header 和 Content, 这种挂载和卸载就是没有必要的.

function App() {
  return (
    <>
      {true && <AdminHeader />}
      <Header />
      <Content />
    </>
  )
}
  • 15.14-避免重复的无限渲染

12. 避免重复无限渲染

当应用程序状态发生更改时, React 会调用 render 方法, 如果在 render 方法中继续更改应用程序状态, 就会发生 render 方法递归调用导致应用报错.

export default class App extends React.Component {
  constructor() {
    super()
    this.state = {name: "张三"}
  }
  render() {
    this.setState({name: "李四"})
    return <div>{this.state.name}</div>
  }
}

与其他生命周期函数不同, render 方法应该被作为纯函数. 这意味着, 在 render 方法中不要做以下事情, 比如不要调用 setState 方法, 不要使用其他手段查询更改原生 DOM 元素, 以及其他更改应用程序的任何操作. render 方法的执行要根据状态的改变, 这样可以保持组件的行为和渲染方式一致.

  • 16.15-为应用程序创建错误边界

13. 为组件创建错误边界

默认情况下, 组件渲染错误会导致整个应用程序中断, 创建错误边界可确保在特定组件发生错误时应用程序不会中断.

错误边界是一个 React 组件, 可以捕获子级组件在渲染时发生的错误, 当错误发生时, 可以将错误记录下来, 可以显示备用 UI 界面.

错误边界涉及到两个生命周期函数, 分别为 getDerivedStateFromError 和 componentDidCatch.

getDerivedStateFromError 为静态方法, 方法中需要返回一个对象, 该对象会和state对象进行合并, 用于更改应用程序状态.

componentDidCatch 方法用于记录应用程序错误信息. 该方法的参数就是错误对象.

// ErrorBoundaries.js
import React from "react"
import App from "./App"

export default class ErrorBoundaries extends React.Component {
  constructor() {
    super()
    this.state = {
      hasError: false
    }
  }
  componentDidCatch(error) {
    console.log("componentDidCatch")
  }
  static getDerivedStateFromError() {
    console.log("getDerivedStateFromError")
    return {
      hasError: true
    }
  }
  render() {
    if (this.state.hasError) {
      return <div>发生了错误</div>
    }
    return <App />
  }
}
// App.js
import React from "react"

export default class App extends React.Component {
  render() {
    // throw new Error("lalala")
    return <div>App works</div>
  }
}
// index.js
import React from "react"
import ReactDOM from "react-dom"
import ErrorBoundaries from "./ErrorBoundaries"

ReactDOM.render(<ErrorBoundaries />, document.getElementById("root"))

注意: 错误边界不能捕获异步错误, 比如点击按钮时发生的错误.

  • 17.16-避免数据结构突变

14. 避免数据结构突变

组件中 props 和 state 的数据结构应该保持一致, 数据结构突变会导致输出不一致.

import React, { Component } from "react"

export default class App extends Component {
  constructor() {
    super()
    this.state = {
      employee: {
        name: "张三",
        age: 20
      }
    }
  }
  render() {
    const { name, age } = this.state.employee
    return (
      <div>
        {name}
        {age}
        <button
          onClick={() =>
            this.setState({
              ...this.state,
              employee: {
                ...this.state.employee,
                age: 30
              }
            })
          }
        >
          change age
        </button>
      </div>
    )
  }
}
  • 18.17-优化依赖项大小

15. 依赖优化

在应用程序中经常会依赖第三方包, 但我们不想引用包中的所有代码, 我们只想用到哪些代码就包含哪些代码. 此时可以使用插件对依赖项进行优化. 优化资源

当前我们就使用 lodash 举例子. 应用基于 create-react-app 脚手架创建。

  1. 下载依赖 yarn add react-app-rewired customize-cra lodash babel-plugin-lodash

    1. react-app-rewired: 覆盖 create-react-app 的默认配置

      module.exports = function (oldConfig) {
        return newConfig
      }
      // 参数中的 oldConfig 就是默认的 webpack config
      
    2. customize-cra: 导出了一些辅助方法, 可以让以上写法更加简洁

      const { override, useBabelRc } = require("customize-cra")
      
      module.exports = override(
        (oldConfig) => newConfig,
        (oldConfig) => newConfig
      )
      

      override:可以接收多个参数, 每个参数都是一个配置函数, 函数接收 oldConfig, 返回 newConfig

      useBabelRc: 允许使用 .babelrc 文件进行 babel配置

    3. babel-plugin-lodash: 对应用中的 lodash 进行精简

  2. 在项目的根目录下新建 config-overrides.js 并加入配置代码

    const { override, useBabelRc } = require("customize-cra")
    
    module.exports = override(useBabelRc())
    
  3. 修改 package.json 文件中的构建命令

    "scripts": {
      "start": "react-app-rewired start",
      "build": "react-app-rewired build",
      "test": "react-app-rewired test --env=jsdom",
      "eject": "react-scripts eject"
    }
    
  4. 创建 .babelrc 文件并加入配置

    {
      "plugins": ["lodash"]
    }
    
  5. 生产环境下的三种 JS 文件

    1. main.[hash].chunk.js: 这是你的应用程序代码, App.js 等.

    2. 1.[hash].chunk.js: 这是第三方库的代码, 包含你在 node_modules 中导入的模块

    3. runtime~main.[hash].js webpack运行时代码

  6. App 组件

    import React from "react"
    import _ from "lodash"
    
    function App() {
      console.log(_.chunk(["a", "b", "c", "d"], 2))
      return <div>App works</div>
    }
    
    export default App
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值