useState 与useReducer源码浅析

时间:2020-9-23 作者:admin

react hooks源码 感觉有点复杂。现在只能学习用法和写个简易版的hooks。 在此之前,想先了解下几个出现的名词

状态组件 VS 非状态组件

推荐阅读有状态和无状态组件之间的区别

状态组件

无状态组件

没有自己的state和生命周期函数。接受一个props,只是纯props展示组件,不涉及状态的更新

export const Button=(props)=>{  
    return( 
        <Button className={this.props.className)/>
    )
}

状态组件

有自己的state和生命周期函数。比如下面的内部的状态会受到外部传的props改变而改变

export class Header extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
          title: ''
        }
    };
    render() {
        return (
            <Header title={this.state.title}/> 
        )
    }
}

什么情况用状态组件和无状态组件

当不涉及state的更新时,用无状态组件,效率高,复用性高;反之亦然。

受控组件 VS 非受控组件

推荐阅读controlled-vs-uncontrolled-inputs-react

受控组件

非受控组件

表单数据由DOM节点来处理,换句话来说就是使用ref来获取值,如

class Form extends React.Component {
  constructor(props) {
    super(props);
    this.handleInputChange = this.handleInputChange.bind(this);
    this.input = React.createRef();
  }

  handleInputChange() {
    alert( this.input.current.value);
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" ref={this.input} onChange={this.handleInputChange}/>
        </label>
      </form>
    );
  }
}

受控组件

受控组件接受当前的value值作为prop,并且可以通过回调改变value,如

class Form extends React.Component {
  constructor() {
    super();
    this.state = {
      name: '',
    };
  }

  handleInputChange = (event) => {
    this.setState({ name: event.target.value });
  };

  render() {
    return (
      <div>
        <input
          type="text"
          value={this.state.name}
          onChange={this.handleInputChange}
        />
      </div>
    );
  }
}

受控组件和非受控组件什么时候用

特征 非受控组件 受控组件
一次性获取(如submit)
提交时验证
实时验证 x
有条件地禁用submit按钮 x
强制输入格式 x
一个数据的多次输入 x
动态的输入 x

useState

用法

 const [number, setNumber] = useState(0);
  return (
    <div>
      <div>{number}</div>
      <button onClick={() => setNumber(number)}>点我</button>
    </div>
  );

看起来比较简单。返回一个state和更新state的函数。每次state改变的时候,加入渲染更新队列,即视图发生改变。每一次更新state的函数返回最后更新的值。哪么问题来了,如何加入更新队列?如何返回最后更新的值,其实实现方式很简单

简易版的useState

let index = 0;
let hookState = [];
function useState(initalState){
  let currentIndex = index;
  hookState[currentIndex] = initalState;
  function setState(newState){
    hookState[currentIndex] = newState   // 加入到更新队列
    render()                            //  渲染
  }
// 返回最后最后一次更新的值
return [ hookState[index++],setState]
}

function Counter() {
  const [number, setNumber] = useStates(0);
  return (
    <div>
      <div>{number}</div>
      <button onClick={() => setNumber(number + 1)}>点我</button>
    </div>
  );
}

function render() {
 index = 0; // 每次渲染之后 index = 0
  ReactDOM.render(
    <React.StrictMode>
      <Counter></Counter>
    </React.StrictMode>,
    document.getElementById("root")
  );
}
render();

源码

type BasicStateAction<S> = (S => S) | S;
type Dispatch<A> = A => void;

function basicStateReducer<S>(state: S, action: BasicStateAction<S>): S {
  return typeof action === 'function' ? action(state) : action;
}


export function useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return useReducer(
    basicStateReducer,
    // useReducer has a special case to support lazy useState initializers
    (initialState: any),
  );
}

以上源代码用Typescript标识了入参和返回参。useStateuseReducer的语法糖。所以要看useState的源码,不妨先看useRecuder

useRecuder

基本用法

语法

const [state, dispatch] = useReducer(reducer, initialArg, init);

reducerreduxreducer是一模一样的用法,接收stateaction,返回state和当前匹配的dispatch的方式

示例

import React, { useReducer } from "react";
import ReactDOM from "react-dom";

const initialState = { count: 0 };

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

function Counter() {
  let [state, dispatch] = useReducer(counterReducer, initialState);
  console.log(state)
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </div>
  );
}
function render() {
  ReactDOM.render(<Counter />, document.getElementById("root"));
}
render();

有很多state的时候可以用useReducer,而非useState,不然useState要写很多。

简易版的useReducer

// 以下是简易版的
let hookState = [];
let hookIndex = 0;

function useReducer(reducer, initialState) {
  hookState[hookIndex] = hookState[hookIndex] || initialState;
  let currentIndex = hookIndex;
  function dispatch(action) {
    console.log(action);
    // 区分useState和useReducer的情况
    hookState[currentIndex] = reducer
      ? reducer(hookState[currentIndex], action)
      : action;
    render();
  }
  return [hookState[hookIndex++], dispatch];
}

function useState(initialState) {
  return useReducer(null, initialState);
}

// function Counter1() {
//   let [number, setNumber] = useState(0);
//   return (
//     <div>
//       <p>{number}</p>
//       <button onClick={() => setNumber(number + 1)}>+</button>
//     </div>
//   );
// }

function Counter() {
  let [state, dispatch] = useReducer(counterReducer, initialState);
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: "decrement" })}>+</button>
    </div>
  );
}

function render() {
  hookIndex = 0;
  ReactDOM.render(<Counter1 />, document.getElementById("root"));
}

render();

源码

源码基于Fiber写的,有点复杂。源码

后续计划

接下来的计划,useCallbackuseMemo,这个是踩坑最多,因为一不留心,容易造成循环调用

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。