React useState 原理

416 阅读3分钟

学习内容:

  1. 分析useState原理和源码
  2. useRef 的作用
  3. useContext 的作用
  4. Vue 3 对比 React

一、useState的原理

1. useState的作用

useState示例代码

import React from 'react'
import ReactDOM from 'react-dom'

const App = ()=>{
    //state
    const [n,setN] = React.useState(0)
    const onClick = ()=>{
        setN(n+1) 
    }
    //虚拟DOM
    return (
        <div>
            <p>n:{n}</p>
            <p><button onClick={onClick}>+1</button></p>
        </div>
    )
}
//页面真实元素
ReactDOM.render(<App/>,document.getElementById('root'))

useState和其余代码执行过程和页面逻辑

state、虚拟DOM、页面真实元素

  • state:n来存放原始数据,setN来修改state;
  • 虚拟DOM:App()中return就会render然后创建一个虚拟DOM
  • 页面真实元素:ReactDOM.render就会将元素渲染进页面

整个的过程是这样的: 首次渲染: render :会调用App(),App()得到虚拟DOM,然后创建真实Div

再次渲染: 用户点击button,调用setN(n+1),再次render :调用App()得到虚拟DOM,进行Div diff,然后更新真实Div

每次调用App(),都会运行useState(0)

useState的原理探究

  1. setN的时候会发生什么?它会修改n的值吗?它会执行App()吗?
  2. 重新执行App()后,useState(0)时候,每次n的值会不一样吗? 通过console.log(n)寻找答案 答案: 1.setN不会修改n的值,它一定会修改state中的数据,将新得到的n存入state中;setN后APP(),重新渲染re-render,一定会执行App(),就是一定会得到新的虚拟DOM,但是div diff就不一定 2.useState(0),每次都会从state中读取新的n的值

手写React.useState

import React from 'react'
import ReactDOM from 'react-dom'
let state
function myUseState(initialValue){
    state = state===undefined ? initialValue :state
    function setState(newValue){
        state = newValue
        render()
    }
    return [state,setState]
}

function render(){
        ReactDOM.render(<App />,document.getElementById('root'))
    }

const App = ()=>{
    //state
    const [n,setN] = React.useState(0)
    const onClick = ()=>{
        setN(n+1) 
    }
    //虚拟DOM
    return (
        <div>
            <p>n:{n}</p>
            <p><button onClick={onClick}>+1</button></p>
        </div>
    )
}
//页面真实元素
ReactDOM.render(<App/>,document.getElementById('root'))

但是这样只能接受一次使用,因为state就只能存放一个值

两个useState冲突的情况

手写React.useState改进版

import React from 'react'
import ReactDOM from 'react-dom'
let state =[]
let index = 0
function myUseState(initialValue){
    curIndex = index
    state[curIndex] = state[curIndex]===undefined ? initialValue :state[curIndex]
    function setState(newValue){
        state[curIndex] = newValue
        render()
    }
    return [state[curIndex],setState]
}

function render(){   
    index = 0
    ReactDOM.render(<App />,document.getElementById('root'))
}

const App = ()=>{
    //state
    const [n,setN] = React.useState(0)
    const onClick = ()=>{
        setN(n+1) 
    }
    //虚拟DOM
    return (
        <div>
            <p>n:{n}</p>
            <p><button onClick={onClick}>+1</button></p>
        </div>
    )
}
//页面真实元素
ReactDOM.render(<App/>,document.getElementById('root'))

2.useState使用的注意事项

useState使用顺序

state数组变量的一个缺点,非常依赖顺序,如果第一次渲染,n是第一个,m第二个,k第三个,第二次渲染时必须保证顺序一致

所以react中绝对不允许出现if-else中写useState的代码

为什么会这样呢?

  • 因为编译是最先开始的,是很死板的固定工作,预知不了运行时的具体情况
  • hook在编译阶段得确定顺序,是固定死的,而if else是运行时才能进行判断,是不确定的
  • 编译时看不到运行时的具体顺序,处理时不会按照if else来

全局变量

state和index都是全局变量,就不会和其他的重名吗?把这两个变量放到虚拟DOM上。

3.总结一下useState

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

二、n的分身

因为setState修改n不是修改原来的n,所以,n是存在分身的,如何解决这个伪命题,让n贯穿始终呢?

1. useRef

import React from 'react'
import ReactDOM from 'react-dom'

const App = ()=>{
    const ref = React.useRef(0)
    const log = ()=>setTimeOut(()=>{console.log('n:',ref.current)},2000)
    return (
        <div>
            <p>{ref.current}</p>
            <p><button onClick={()=>{ref.current+=1}}>+1</button></p>
             <p><button onClick={log}>log</button></p>
        </div>
    )
}

ReactDOM.render(<App/>,document.getElementById('root'))

但是这样改变数据,不会去render。

2.useRef强制更新

import React from 'react'
import ReactDOM from 'react-dom'

const App = ()=>{
    const ref = React.useRef(0)
    const setN = React.useState(null)[1]
    const log = ()=>setTimeOut(()=>{console.log('n:',ref.current)},2000)
    return (
        <div>
            <p>{ref.current}</p>
            <p><button onClick={()=>{ref.current+=1;setN(ref.current)}}>+1</button></p>
             <p><button onClick={log}>log</button></p>
        </div>
    )
}

ReactDOM.render(<App/>,document.getElementById('root'))

3. useContext

useContext不仅可以贯穿始终,还可以贯穿不同的组件

useContext使用案例