react 中组件的形式有两种 函数组件以及类组件,函数组件很好理解只要最终返回一个虚拟树就可以了,这中间的逻辑完全就是 js 的逻辑,没有什么好记录的,但类组件涉及到父类以及 state 需要记录一下。

类组件

state

state 可以说是 react 实现数据变化更新视图的最重要的一部分。

state 干嘛用?

state 表示一个组件的内部可变数据的集合,为什么说是可变的数据呢?

不变的数据用 this 保存即可,因为不会发生变化,所以不用触发视图更新。

没了,作用就是保存数据。那么如何触发视图更新呢?

这就需要用到 setState

setState 是什么?

setState 是一个函数,该函数接收一个对象或接收一个函数。

setState 做了什么?

this.setState({...})

执行之后发生了什么?总的来说,有以下步骤:

  1. 获取原先 state 保存的数据:prevState
  2. 将传入的对象合并到 prevState 生成:nextState
  3. this.state = nextState
  4. 通知虚拟树的根组件重新生成虚拟树(调用函数组件或是类组件的 render 方法)
  5. 将新的虚拟树与之前的虚拟树作对比(diff 算法),找出差别,生成更新程序(patch
  6. 执行更新程序 html 页面需要更新的地方就会发生变化

setState 参数为函数的情况

this.setState((prevState, props) => ({...}))

与对象形式的 setState 区别在于第三点,函数形式为

  1. this.state = 函数调用的结果

setState 的内容是异步执行

以上不管是对象形式还是函数形式,上面所说的 6 步都会异步执行。

this.state = {a: 1};

this.setState({a: this.state.a + 1})
this.setState({a: this.state.a + 1})

由于是异步执行,当执行到第二个 setState 时,this.state 的状态还未改变,因此设置的值仍为 2 。问题出在获取 state 的方式,如果为函数形式,那么 state 就能被正确的获取(因为 state 会被当做参数传入,每次传入的都是最新的 state)。

this.setState(prevState => ({a: prevState.a + 1}))
this.setState(prevState => ({a: prevState.a + 1}))

state 总结 & 补充

  1. state 中存放变动的数据,不变动的放在 this 下即可
  2. setState 会触发梗元素重新生成虚拟树
  3. 重新生成整颗虚拟树的结构就是所有的组件都重新被调用了
  4. setState 是类继承过来的方法,不应该被覆盖

生命周期

生命周期函数的意义:组件获取虚拟树过程中会执行的一系列方法。

react生命周期

react 生命周期函数具体含义

函数组件

函数组件在 v16 版本之前确实没什么好记的,只需要记住组件的传入参数是组件的 props 即可。

v16 版本之后出现了 hooks 记录一下

useState

useStatereact 函数组件提供了保存状态的能力。使用如下

import { useState } from 'react';

function Example() {
    const [count, setCount] = useState(0);
    
    return (
        <div>
            <p>You clicked {count} times</p>
            <button onClick={() => setCount(count + 1)}>
                Click me
            </button>
        </div>
    );
}

函数需传入一个参数,返回一个数组,数组第一项为传入的值,数组第二项为一个函数,可以修改原始值。

该函数的目的在于让函数组件也拥有 state ,大概可以知道具体的执行过程。

  1. 通过数组返回的第二个函数修改原始值
  2. useState 内部状态变化
  3. 通知虚拟树的重新生成虚拟树
  4. 将新的虚拟树与之前的虚拟树作对比(diff 算法),找出差别,生成更新程序(patch
  5. 执行更新程序 html 页面需要更新的地方就会发生变化

其实和类组件的效果差不多,就是代码量变少了,但如果涉及复杂的组件,感觉还是类组件好用,类组件有个 this

useEffect

useEffectreact 函数组件拥有生命周期的钩子。使用如下

import { useState, useEffect } from 'react';

function FriendStatus(props) {
    const [isOnline, setIsOnline] = useState(null);
    
    function handleStatusChange(status) {
        setIsOnline(status.isOnline);
    }
    
    useEffect(() => {
        ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
        // 明确在这个 effect 之后如何清理它
        return function cleanup() {
            ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
        };
    });
    
    if (isOnline === null) {
        return 'Loading...';
    }
    return isOnline ? 'Online' : 'Offline';
}

useEffect 中传入的函数将会在组件初始化完成的时候调用,而调用的结果(结果是一个函数),将会在组件销毁的时候调用。

useRef

useRefreact 函数组件能使用 ref

function TextInputWithFocusButton() {
    const inputEl = useRef(null);
    const onButtonClick = () => {
        inputEl.current.focus();
    };
    return (
        <>
          <input ref={inputEl} type="text" />
          <button onClick={onButtonClick}>Focus the input</button>
        </>
    );
}

用法和 React.createRef() 一样,感觉这个 api 可以直接用于函数组件,哪天试一下。

useContext

useContext 需要搭配 React.createContext 使用,用于直接获取 Context

和组件的概念不一样,组件内部一般是不主动去获取 Context 而是通过 ProviderConsumer 来获取后,通过 props 传入组件内部,useContext 直接获取到最近的 Provider 处,拿到提供的值,如果没有 Provider 则取默认值,也就是传入 React.createContext 这个方法的值。

useReducer

useReducer 提供一个类似 reduxapi ,需要一个 reducer

import React, { useReducer } from "react";

function reducer(state, action) {
    switch (action.type) {
        case "increment":
            return { count: state.count + 1 };
        case "decrement":
            return { count: state.count - 1 };
        default:
            return state;
    }
}


const Com = props => {
    const [state, dispatch] = useReducer(reducer, { count: 0 });
    return (
        <div value={{ state, dispatch }}>
            <botton onClick={()=>dispatch({type: 'increment'})}>+</botton>
            <botton onClick={()=>dispatch({type: 'decrement'})}>-</botton>
            {state.count}
        </div>
    );
};

当然 dispatch 也可通过 props 向子元素传递。

其他 hooks

一些目的性不强,个人感觉为了 hookhook 的就不写了,总之如果逻辑简单,而函数组件满足不了时,使用这些 hook ,但逻辑复杂还是使用类组件比较好。