React Hooks原理解析

90 阅读2分钟

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>
  );
}

image.png

image.png

答:执行setN的时候会渲染UI,n不会变(setN不会改变n),App()会重新执行

image.png

分析

setN

  • setN一定会修改数据x,将n+1存入x

  • setN一定会触发 重新渲染(re-render)

useState

  • useState 肯定会从x 读取n 的最新值

x

  • 每个组件有组件的数据x,我们将其命名为state

实现React.useState

image.png

没有变化,因为myUseState 会将state重置,我们需要一个不会被myUseState重置的变量,那么这个变量只要声明在myUseState外面即可

image.png

如果一个组件用了两个useState怎么办?

把_state做成一个对象

  • 比如_state={n:0,m:0}

  • 不行,因为useState(0)并不知道变量叫n还是m

把_state做成数组

  • 比如_state=[0,0]

image.png

_ state数组方案缺点

  • useState调用顺序

若第一次渲染时n是第一个,m是第二个,k是第三个;则第二次渲染时必须保证顺序完全一致,所以React不允许出现下面的代码

image.png

image.png

image.png

总结:

  • 每个函数组件对应一个React节点

  • 每个节点保存着state和index

  • useState会读取state[index]

  • index由useState出现的顺序决定

  • setState会修改state,并触发更新

n的分身

function App() {
  const [n, setN] = React.useState(0)
  const log = () => setTimeout(() => console.log(`n: ${n}`), 3000)
  return (
    <div className="App">
      <p>{n}</p>
      <p>
        <button onClick = {()=>setN(n + 1)}>+1</button>
        <button onClick = {log}>log</button>
      </p>
    </div>
  )
}

两种操作

  • 一、点击+1再点击log--无bug

  • 二、点击log再点击+1--有bug

  • 是因为

image.png

贯穿始终的状态

全局变量

  • 用window.xxx即可,但是太low了

useRef

  • useRef不仅可以用于div,还能用于任意数据
function APP() {
  const nRef = React.useRef(0)//{current:0}
  const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000)
  return (
    <div className="App">
      <p>{nRef.current} 这里并不能实时更新</p>
      <p>
        <button onClick={() => (nRef.current +=1)}>+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  )
}
  • 上面的代码点击的时候不会改变,但是打印出的值改变了,因为NRef.current+1时,不会进行页面上的渲染(不会让APP重新渲染)

修改后的代码:

function APP() {
  const nRef = React.useRef(0)//{current:0}
  const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000)
  const update = React.useState(null)[1]
  return (
    <div className="App">
      <p>{nRef.current} 这里并不能实时更新</p>
      <p>
        <button 
          onClick={() => {
            nRef.current +=1
            update(nRef.current)
            }}>+1</button>
        <button onClick={log}>log</button>
      </p>
    </div>
  )
}

useContext

  • useContext不仅能贯穿始终,还能贯穿不同组件
const themeContext = React.createContext(null)
function App() {
  const [theme, setTheme] = React.useState("red");
  return (
    <themeContext.Provider value={{ theme, setTheme }}>
      <div className={`App ${theme}`}>
        <p>{theme}</p>
        <div>
          <ChildA />
        </div>
        <div>
          <ChildB />
        </div>
      </div>
    </themeContext.Provider>
  );
}
function ChildA() {
  const { setTheme } = React.useContext(themeContext);
  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>
  );
}

总结

  • 每次重新渲染,组件函数就会执行

  • 对应的所有state都会出现【分身】

  • 如果不希望分身出现,可以用useRef/useContext等