const App = () => {
const [n, setN] = useState(0)
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
)
}
export default App;
首次渲染<App />——调用App(),得到虚拟DOM1,把虚拟DOM1渲染成真实DOM
用户点击button后,调用setN(n+1),要更新页面,一定会再次渲染<App/>——调用App(),根据虚拟DOM1得到虚拟DOM2,经过DOM Diff对比,局部更新(patch)真实DOM
两次调用App(),都会运行useState(0)
执行setN会发生什么?n变了吗?App会重新执行吗?
每次App执行,useState(0),n的值每次一样吗?
console.log()获得答案
分析
setState
- setState一定会修改中间变量state,将n+1存入state
- setState一定会触发<App/>重新渲染(re-render)
useState
- useState肯定会从state读取n的最新值
myUseState
这是错误的写法,每次点击button,n的值没变化
错误的原因:每次点击button,state的值变为1,调用render(),由于state在myUseState内部声明,每次初始值0都会覆盖
const myUseState = (initialState) => {
let state = initialState
const setState = (newState) => {
state = newState
render()
}
return [state,setState]
}
const render = () => {
ReactDOM.render(<App/>,root);
}
const App = () => {
const [n, setN] = myUseState(0)
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n + 1)}>+1</button>
</p>
</div>
)
}
利用闭包储存上次的数据,防止被覆盖
let _state
const myUseState = (initialState) => {
_state = _state === undefined ? initialState : _state
const setState = (newState) => {
_state = newState
render()
}
return [_state, setState]
}
const render = () => {
ReactDOM.render(<App/>, root);
}
const App = () => {
const [n, setN] = myUseState(2)
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n - 1)}>-1</button>
</p>
</div>
)
}
setState是一个回调函数
不能写成_state = _state || initialState
,当state为0时,判定为falsy值,自动返回initialState的值
如果一个组件,用两个myUseState怎么办?
由于所有的数据都放在_state里,后面的会覆盖前面的_state,发生冲突
多个myUseState
- 把_state变成一个对象
每次传值都得传递一个属性名,例如:
const [n, setN] = myUseState('m':2)
但是原来的react并没有传递属性名,所以_state不是对象
- 把_state做成一个数组
- 例如
_state=[0,0]
,看着还可以,试一试
let _state = []
let index = 0
const myUseState = (initialState) => {
const currentIndex = index//保证数组的下标固定
_state[currentIndex] = _state[currentIndex] === undefined ? initialState : _state[currentIndex]
const setState = (newState) => {
_state[currentIndex] = newState
console.log(_state)
render()
}
index += 1
return [_state[currentIndex], setState]
}
const render = () => {
index = 0//index重置
ReactDOM.render(<App/>, root);
}
const currentIndex = index
每次调用myUseState时,myUseState内的_state下标固定,不被闭包影响
思路:每次调用render(),只不过是载入的_state发生变化,其他都要保持不变
每次render都是重新调用<App/>,所以index重置在调用<App/>之前
现在代码存在的问题
- _state数组会改变顺序,与useState不符合
React里,useState是有调用顺序,必须完全一样的调用,也不能有条件的调用
例如:若第一次渲染时,n是第一个,m是第二个,k是第三个
下次渲染时,必须保证n,m,k的顺序完全一样
Vue3借鉴React,但是完全克服这个问题
- App组件用了_state和index,那么其他的组件用什么?
解决办法:给每个组件创建一个_state和index,放到组件对应的虚拟DOM节点对象上
总结
- 每个函数组件对应一个FiberNode(虚拟节点)
- 每个节点都保存着state和index
- useState会读取对应节点的state[index]
- index由useState调用(出现)的顺序决定
- 在setState里修改state和触发更新
- React里state的名字叫memorizedState,index是用链表实现的
每个组件都有一个虚拟节点,虚拟节点保存一个自己的_state和index,每次操作后,得到一个新的虚拟节点,DomDiff后,更新虚拟节点,更新虚拟DOM,渲染到页面
新的虚拟节点根据旧的虚拟节点得到,不然无法理解setState的接受函数和改变对象的区别
网友评论