「React」React全解-Hooks原理解析

190 阅读3分钟

1.实现useState

这是React自带功能实现的useState

import React from "react"
import ReactDOM from "react-dom"
const rootElement = document.getElementById("root")
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>
    )
}

ReactDOM.render(<App />, rootElement)

下面来分析下这段代码的运行过程..
1.首次渲染先执行render函数,调用App,这个时候React.useState(0)调用执行,并把0赋值给state(这里的state是 React.useState()函数里内置的变量),然后把state的值返回给n。这时候n为0,并得到一个虚拟DOM,然后创建了真实的DOM显示在页面。
2.当用户点击按钮时,调用setN(n + 1),执行setN的时候,这个时候React.useState(0)调用执行,但是setN(n+1)并不是改变n,而是把n+1赋值给state ,当React.useState(0)执行的时候,返回的n是state的值。执行render函数,这时候得到一个虚拟DOM,并进行DOMDiff,更新真实的DOM

总结:

  • setN
    setN一定会修改数据x,将n+1存入x
    setN一定会触发重新渲染(re-render)
  • useState
    useState肯定会读取x的值,赋给n
  • x
    每个组件都有自己的数据x,暂时将其命名为state

2.useState背后的逻辑

import React from "react"
import ReactDOM from "react-dom"
const rootElement = document.getElementById("root")
let _state;
const myUseState = initialValue => {
    console.log("myUseState run")
    _state = _state === undefined ? initialValue : _state
    const setState = newValue => {
        _state = newValue
        render()
    }
    return [_state, setState]
}

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)
export default App

2.解决两个useState冲突

import React from "react"
import ReactDOM from "react-dom"
const rootElement = document.getElementById("root")
let _state = [];
let index = 0

const myUseState = initialValue => {
    console.log(`最外层${index}`)
    const currentIndex = index
    console.log(`currentIndex2:${currentIndex}`)

    _state[currentIndex] = _state[currentIndex] === undefined ? initialValue : _state[currentIndex]
    const setState = newValue => {
        console.log(`currentIndex1:${currentIndex}`)
        _state[currentIndex] = newValue
        console.log(_state)
        render()
    }
    index += 1
    console.log(`index:${index}`)
    console.log(`_state:${_state}`)
    console.log('--------')
    return [_state[currentIndex], setState]
}

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


function App() {
    const [n, setN] = myUseState(0)
    const [m, 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>
    )
}

export default App

注意: currentIndex用到了闭包的思想,每次点击之后需要重置index=0,这是核心

3._state数组方案缺点

第一次渲染n是第一个,m是第二个,k是第三个 则第二次渲染时必须保证顺序完全一致 所以不能出现有选择的n,比如说n为偶数才调用uesState函数,那么它们的顺序会改变,数据就乱了

4.组件更新过程

react会维护一个虚拟的dom树,tag是div,type是App,App里面包含了2个子组件,都是childA, 虚拟DOM是函数组件运行得出的,App组件运行,运行时会使用useState useState用到的_state和index放到虚拟DOM,虚拟DOM中本来就有这两个变量来存储值 运行完后会得到记为App1,App1会渲染到虚拟DOM树中,虚拟DOM树会映射到div上面 在用户点击按钮之后会执行setN(n+1),会再次执行App组件,App组件会再次执行useState, useState会读取虚拟DOM树里面的_state和index,执行后得到App2 App1与App2进行DOM Diff,将两个差别的地方做成一个叫Patch的函数 将Patch运行到虚拟DOM树上,react就会改变n的值

5.组件更新总结

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