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,并触发更新