27-react年度总结

43 阅读4分钟

Hooks

函数组件 与 类组件

  • 侧重点:
    • 类组件: 面相对象的编程思想 (对象的创建 => 继承和复用)
    • 函数组件: 面向函数的编程思想 (函数执行)

react组件是干嘛的?

  • render(data) => UI => 浏览器
    • 渲染UI

类组件

基于面相对象思想构建的组件体系,主要是更好的组织代码

  • 优点:
    • 有内部状态
    • 完整的生命周期(后续去看看)
    • 单独的render函数
  • 缺点:
    • 限制了组件方法逻辑的抽离
    • 复杂的this的指向与生命周期的使用
    • 组件的状态很难复用(因为状态是服务于当前组件的)
class Test {
    state = {}
    render() {
        return <>render</>
    }
}

函数组件

基于函数编程构建组件的方式,主要是为了增加组件代码的自由度

  • 优点:
    • 从组件render的特性出发,一次执行一次返回视图
    • 希望开发者能把功能能更好的抽离出去
    • 更好逻辑复用,拥有更高的自由度
  • 缺点:
    • 组件无状态
    • 生命周期不完整
    • 每次更新都需要执行函数

React Hooks

16.8推出的新功能,都是服务于函数组件的

  • useState: 创建状态
  • useReducer: 创建数据与复杂的数据操作
  • useEffect: 处理程序中的副作用
  • useLayoutEffect:
  • useContext: 创建组件体系的上下文对象
  • useMemo: 创建可以计算的数据(computed)
  • useCallback: 缓存方法
  • useRef: 获取节点实例
  • useImprativeHandle
  • useSyncExternalStore
  • useTransition
  • ...

特点:

  • 只能在函数组件中使用
  • hook都已已 use开头的函数(包括自定义hook)
  • hook应该在函数内部最顶层使用(顺序定义、顺序调用)
  • hook不能写在判读语句中

demo1

import { useState } from "react";

function State() {
  /**
   * [状态, 设置状态的方法]
   * setCount(value)
   * setCount((state) => newState)
   * 注意:
   * 1. count: 是只读的,必须通过 setCount去更新值
   * 2. 设置值的时候,必须是新值或者新的引用
   * 3. react对比值是浅对比
   */
  const [count, setCount] = useState(0);
  const [info, setInfo] = useState({
    name: "xiaobai",
    age: 18
  })
  const none = () => {
    info.name = "不起效果";
    setInfo(info);
  }
  return (
    <div>
      <h4>
        {info.name}-{count}
      </h4>
      <button onClick={() => setCount(count + 1)}>增</button>
      <button onClick={() => setCount((count) => count - 1)}>减</button>
      <button onClick={() => setInfo({ ...info, name: "大白" })}>
        changeName
      </button>
      <button onClick={none}>不起效果</button>
    </div>
  );
}

export default State;

image.png

demo2 todo

  • **useState源码模拟实现
#### 源码模拟
```js
import _ from 'lodash';
const states = [];
const stateSetters = [];
let stateIndex = 0;

function useState(data) {
  state[stateIndex] = states[stateIndex] || _.cloneDeep(data);

  if(typeof stateSetters[stateIndex] !== 'function') {
    stateSetters[stateIndex] = ((stateIndex) => {
      return (value) => {
        if(state[stateIndex] !== value) {
          state[stateIndex] = value;
          render();//模拟的方法
        }
      }
    })(stateIndex);
  }

  const state = states[stateIndex];
  const setState = stateSetters[stateIndex];
  stateIndex++;

  return [state, setState];
}

#### useReducer
```jsx
// 下面的操作是集成非常低的
setNum(num => num - 1);
setNum(num => num + 1);
  • 如果一个状态的操作方式有很多种的情况下,useReducer是最佳的选择

demo1

import { useReducer } from "react";

function Reducer() {
  /**
   * dipatch({type: 'plus'}) => setNum(num => num + 1);
   * dipatch({type: 'minus'}) => setNum(num => num - 1);
   * reducer: dispath需要的方法集合
   * 0: 叫做初始值
   */
  const [num, dispatch] = useReducer(reducer, 0);
  /**
   * @param {*} state num
   * @param {*} action 
   */
  function reducer(state, action = {}) {
    let step = Number(action.payload) || 1;
    switch(action.type) {
        case "PLUS":
            return state + step;
        case "MINUS":
            return state - step;
        default:
            return state;
    }
  }
  return (
    <div>
      <h4>{num}</h4>
      <button onClick={() => dispatch({ type: "PLUS" })}>增</button>
      <button onClick={() => dispatch({ type: "MINUS" })}>减</button>
      <button onClick={() => dispatch({ type: "PLUS", payload: 5 })}>增5</button>
      <button onClick={() => dispatch({ type: "MINUS", payload: 4 })}>减4</button>
    </div>
  );
}

export default Reducer;

image.png

  • useReducer流程示意图如下: image.png

useEffect

  • 处理副作用的回调: 与视图渲染无关的一些操作
  • 依赖项:
    • 依赖项中的依赖改变后,回调会重新执行
    • useEffect(() => {}, [依赖项])

demo1

import { useEffect, useState } from "react"

function Effect() {
    const [num, setNum] = useState(0);
    const [age, setAge] = useState(14);
    useEffect(() => {
        // 一般用来请求数据,初始化组件的设置
        console.log("没有依赖,只在组件挂载完成后执行一次")
    }, []);

    useEffect(() => {
        // 不推荐使用,极为不安全的
        console.log("没有依赖项参数,组件重新渲染完成后都会执行")
    });
    useEffect(() => {
        console.log("有依赖项age,组件重新渲染完成后都会执行")
    }, [age]);

    console.log("render");
    return (
      <div>
        <h4>num:{num}</h4>
        <h4>age:{age}</h4>
        <button onClick={() => setNum(num + 1)}>+</button>
        <button onClick={() => setAge(age + 1)}>长大</button>
      </div>
    );
}

export default Effect

第一次执行函数组件 image.png 点击+:第二次执行函数组件 image.png 点击长大: 第三次执行函数组件 image.png

  • 注意:
    • 在render完成后执行
    • 任意一个依赖项中依赖发生变化,有清除副作用函数执行上一个清理函数,再回调重新执行
    • 填入依赖的标准是回调的内部需要依赖项参与程序,一般是props 或者 state
      • 不用的东西不要去填
    • 回调不能写异步函数,因为回调内部会返回一个清除副作用函数
    • 组件卸载的时候,不管有没有依赖项都会执行掉清理函数

当你的项目足够大并且复杂时候尽量把数据封装redux中