React Hooks原型解析

132 阅读4分钟

useState的用法

function App(){
  const [n, setN] = React.useState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={()=> setN(n + 1)}>+1</button>
      </p>
    </div>
  )
}

思考问题

执行setN的时候发生了什么?n会变吗? App()会重新执行吗?
如果App()会重新执行,那么useState(0)的时候,n每次的值都会不同吗?

实现简单的useState

import React from "react";
import ReactDOM from "react-dom";
import rootElement = document.getElementById("root");

let _state; 
//声明一个特殊变量,来存储state的值,这个变量声明在外面,不会被myUseState重置
function myUseState(initiaValue){ 
  _state = state === undefined ? initiaValue : _state;
  //声明一个初始值,每次传一个初始值,就把初始值给_state,或者把之前的_state给_state
  //如果_state 没有以前的状态,就让它等于初始值;如果有之前的状态,就等于自己
  function setState(newState){      
  //setState就是把你的值(newState)传给_state
    _state = newState;
    render();
  }
  return [_state, setState];    
  //把state, setState返回给n和setN
}

const render = () => ReactDom.render(<App />, rootElement);
function App(){
  const [n, setN] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={()=> setN(n + 1)}>+1</button>
      </p>
    </div>
  )
}
ReactDOM.render(<App />, rootElement);

让两个useState不冲突

import React from "react";
import ReactDOM from "react-dom";
import rootElement = document.getElementById("root");

let _state = [];
//声明一个空数组,用来存储stae的值
let index = 0;
//声明一个下标,来表示当前时第几个,默认第0个

function myUseState(initiaValue){
  const currentIndex = index;
  //声明一个中间的index,先把index的值复制到currentIndex上,然后在对index+1,就不会影响当前index
  index += 1;
  _state[currentIndex] =__state[currentIndex]===undefined ? initiaValue : _state[currentIndex]
  //如果_state[index]第0个没有之前的状态,就让它等于初始值;否则_state[index]第0个还是等于第0个
  const setState = newState =>{
    _state[currentIndex] = newState;
    render();
  };
  return [_state[currentIndex], setState];
}

const render = () => {
  index = 0;   
  //每次渲染app时,重置index,
  ReactDom.render(<App />, rootElement);
}

function App(){
  const [n, setN] = myUseState(0);
  const [n, setM] = myUseState(0);
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={()=> setN(n + 1)}>+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={()=> setM(m + 1)}>+1</button>
      </p>
    </div>
  )
}
ReactDOM.render(<App />, rootElement);

不能写if,会改变state的顺序

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

let _state = [];
let index = 0;

function myUseState(initiaValue){
  const currentIndex = index;
  index += 1;
   _state[currentIndex] =__state[currentIndex]===undefined ? initiaValue : _state[currentIndex]
  const setState = newState =>{
    _state[currentIndex] = newState;
    render();
  };
  return [_state[currentIndex], setstate];
}

const render = () =>{
  index = 0;
  ReactDOM.render(<App />, rootElement);
};

fuction App(){
  const [n, setN] = React.useState(0);
  let m, setM
  if(n % 2 === 1){
    [m, setM] = React.useState(0);      
    //改变了state的顺序
  };
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={()=> setN(n + 1)}>+1</button>
      </p>
      <p>{m}</p>
      <p>
        <button onClick={()=> setM(m + 1)}>+1</button>
      </p>
    </div>
  )
}

2个警告

Warning: React has detected a change in the order of Hooks called ba App, 
This will lead to bugs and errors if not fixed, for more information, read the Rules of Hooks
react检测到一个变化,app调用的hooks它的顺序变了,这将会导致bug和错误,更多信息读hooks的规则

React Hooks "React.useState" is called conditionally, React Hooks must be called 
in the exact same order in every component render
React Hooks "React.useState"被调用了,被有条件的调用了,
hooks必须,在完全一样的顺序,在每次渲染时,被调用

结论

每个函数组件对应一个React节点 FiberNode
每个节点保存这state和index
useState 会读取state[index]
index 由useState出现的顺序觉得
setState会修改state,并触发更新

n的分身(有几个n?)

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

function App() {
  const [n, setN] = React.useState(0);
  const log = () => setTimeout(() => console.log(`n: ${n}`), 3000);
  //3秒后打印出n的值
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick={() => setN(n + 1)}>+1</button>
        //setN不会修改n,而是产生新的n,先log(n)后加1,得到的是之前的n
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

先点击+1在点击log 无bug
先点击log在点击+1 有bug,log出旧数据

useRef,可用于div和任意数据

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

function App() {
  const nRef = React.useRef(0); 
  //nRef是个简单对象值是{current: 0},只有一个值,不会随着你的变化更新
  const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
  //打印n的值是nRef.current(n的当前值)
  return (
    <div className="App">
      <p>{nRef.current} 这里并不能实时更新</p>
      <p>
        <button onClick={() => (nRef.current += 1)}>+1</button>//(n的当前值)+1
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}

手动更新n

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

function App() {
  const nRef = React.useRef(0);
  const update = React.useState(null)[1];   
  //声明一个update,开始是空值,取下标1的值
  const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
  return (
    <div className="App">
      <p>{nRef.current} 这里并不能实时更新</p>
      <p>
        <button onClick={() => ((nRef.current += 1), update(nRef.current))}>
        //nRef.current += 1之后,手动使用update更新
          +1
        </button>
        <button onClick={log}>log</button>
      </p>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

组件间通用useContext

import React from "react";
import ReactDOM from "react-dom";
const rootElement = document.getElementById("root");

const themeContext = React.createContext(null);
//初始化上下文(局部的全局变量)

function App() {
  const [theme, setTheme] = React.useState("red"); 
  //切换主题
  return (
    <themeContext.Provider value={{ theme, setTheme }}>
    //对全局变量进行赋值,值是对象,有2个属性{ theme, setTheme }
    //themeContext.Provider表示,上下文全局变量的作用域在这里,只有这里面的组件或子组件可以使用这个属性
      <div className={`App ${theme}`}>
        <p>{theme}</p>
        <div>
          <ChildA />
        </div>
        <div>
          <ChildB />
        </div>
      </div>
    </themeContext.Provider>
  );
}

function ChildA() {
  const { setTheme } = React.useContext(themeContext);
  //从themeContext)里读取setTheme,即使是组件里面里面的组件,也能使用这个全局变量
  return (
    <div>
      <button onClick={() => setTheme("red")}>red</button>
    </div>
  );
}

function ChildB() {
  const { setTheme } = React.useContext(themeContext);
  return (
    <div>
      <button onClick={() => setTheme("blue")}>blue</button>
    </div>
  );
}

ReactDOM.render(<App />, rootElement);

总结

每次从新渲染,组件函数就会从新执行
对应的所有state都会出现(分身)
如果不希望出现分身(产生新的n)
可以使用 useRef/useContext

详细资料点击:React hooks