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;
分析
setState
-
setState
一定会修改中间变量state
,将n+1
存入state
-
setState
一定会触发<App/>
重新渲染(re-render)
useState
-
useState
肯定会从state
读取n
的最新值
setParam(Object.assign({},param,{name:event.target.name}))
/*上下两种写法等价*/
setParam({...param,name: event.target.name})
myUseState
根据上面的分析模拟一个myUseState
下面是错误的写法
错误的原因:每次重新渲染
App
都会重新执行一次,myUseState
也重新执行一次let state = initialState
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 || initialState _state 为 0 时产生 bug
_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>
)
}
不能写成
_state = _state || initialState
,当state
为0
时,判定为falsy
值,自动返回initialState
的值
解决了上面的问题,又出现新的问题
如果一个组件,使用两次
myUseState
怎么办?
由于所有的数据都放在_state
里,后面的会覆盖前面的_state
多个myUseState
- 思路一:把
_state
变成一个对象
每次传值都得传递一个
key
(属性名),例如:const [n, setN] = myUseState('m':2)
但是useSate
并没有传递属性名,所以_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 App = () => {
const [n, setN] = myUseState(2)
const [m, setM] = myUseState(2)
return (
<div className="App">
<p>{n}</p>
<p>
<button onClick={() => setN(n - 1)}>-1</button>
</p>
<p>{m}</p>
<p>
<button onClick={() => setN(m - 1)}>-1</button>
</p>
</div>
)
}
每次
render
都会调用<App/>
,读取_state
内的数据,重新渲染页面,因此index
要在调用<App/>
之前重置为 0
总结
- 每个函数组件对应一个FiberNode(虚拟节点)
- 每个节点都保存着
state
和index
-
useState
会读取对应节点的state[index]
-
index
由useState
调用(出现)的顺序决定,因此,React Hook 只能在顶层调用,不能条件调用和在纯函数内嵌套调用 - 在
setState
里修改state
并触发更新 - React 中
state
的名字叫memorizedState
,index
是用链表实现的