学习内容:
- 分析useState原理和源码
- useRef 的作用
- useContext 的作用
- 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的原理探究
- setN的时候会发生什么?它会修改n的值吗?它会执行App()吗?
- 重新执行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冲突的情况
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不仅可以贯穿始终,还可以贯穿不同的组件