昨天我们成功的完成一个超简略版的 myUseState ,今天就让我们再来把它写完整一点吧!
let newStateValue
function myUseState(initialValue){
if (newStateValue) return [newStateValue, setState]
let state = initialValue
function setState(newValue){
if (newStateValue === newValue) return
newStateValue = newValue
callRender()
}
return [state, setState]
}
目前的 myUseState 虽然在计数器的范例中可以运作,但它还存在一个很大的问题。当我们想要再设置一个 count2 的 state 时,当呼叫第一个 setCount 时会去修改外面的 newStateValue 然後 re-render ,结果第二个 count2 会因为 newStateValue 有值了而拿到跟 count 一样的值。
所以单一设置一个变数是不够的,我们必须分别纪录第一次呼叫与第二次呼叫的 myUseState,来区分说这个 state 是哪一个 myUseState 回传的值。
为了纪录每一次 myUseState 的呼叫,我们可以将 newStateValue 变数修改成阵列,并多设置一个 callId 来纪录呼叫的顺序。
const hooks = [] // 使用阵列来收集每一次 hook 呼叫
let callId = 0 // 用来纪录每一次 hook 呼叫的顺序
所以,接下来当我们呼叫一次 myUseState 的时候,将 callId 作为 index 并将 state 与 setState 纪录在 hooks 阵列中。
function myUseState(initialValue){
const id = callId++
if (hooks[id]) return hooks[id]
function setState(newValue){
if (hooks[id][0] === newValue) return
hooks[id][0] = newValue
callRender()
}
const stateArray = [initialValue, setState]
hooks[id] = stateArray
return stateArray
}
最後当我们触发 re-render 的时候必须将 callId 重置,不然还是会一直拿到 initialValue 。
function callRender(){
callId = 0
ReactDOM.render(<Counter />, document.getElementById('root'))
}
接下来我们就可以在我们的元件中新增一个 count2 的 state 来看看 myUseState 能不能正常运行。
const Counter = () => {
const [count, setCount] = myUseState(0)
const [count2, setCount2] = myUseState(10)
const increaseHandler = () => {
setCount(count + 1)
}
const decreaseHandler = () => {
if (count === 0) return
setCount(count - 1)
}
return (
<div className="container">
<button className="minus" onClick={decreaseHandler}>-</button>
<span className="number">{count}</span>
<button className="plus" onClick={increaseHandler}>+</button>
<button className="minus" onClick={() => { setCount2(count2 - 1) }}>-</button>
<span className="number">{count2}</span>
<button className="plus" onClick={() => { setCount2(count2 + 1) }}>+</button>
</div>
)
}
function callRender() {
callId = 0
ReactDOM.render(<Counter />, document.getElementById('root'))
}
callRender()
顺利的话,画面上两个计数器都会正常的运作。
最後 setState 要能够接收一个 callback ,来帮我们更新 state :
function myUseState(initialValue){
const id = callId++
if (hooks[id]) return hooks[id]
function setState(newValue){
let nextValue
typeof newValue === 'function' ? nextValue = newValue(hooks[id][0]) : nextValue = newValue
if (hooks[id][0] === nextValue) return
hooks[id][0] = nextValue
callRender()
}
const stateArray = [initialValue, setState]
hooks[id] = stateArray
return stateArray
}
将传进来的 value 先做判断是不是 function ,是的话就将目前的 state 传入,并准备接收新的值 ,然後更新 state ,这样就可以将原本的 setCount 换掉:
const Counter = () => {
const [count, setCount] = myUseState(0)
const [count2, setCount2] = myUseState(10)
const increaseHandler = () => {
setCount(prev => prev + 1)
}
const decreaseHandler = () => {
if (count === 0) return
setCount(prev => prev - 1)
}
return (
<div className="container">
<button className="minus" onClick={decreaseHandler}>-</button>
<span className="number">{count}</span>
<button className="plus" onClick={increaseHandler}>+</button>
</div>
)
}
function callRender() {
callId = 0
ReactDOM.render(<Counter />, document.getElementById('root'))
}
callRender()
以上我们就完成了一个简单的 useState 了,明天就让我们来试着完成 useEffect 吧!如果对於今天的内容有任何问题都欢迎在下方留言!
<<: 110/16 - 整合Android 6到Android 11
>>: 【PHP Telegram Bot】Day22 - ReplyKeyboardMarkup:让输入框下方出现按钮区域
EditText与TextView相似,但EditText用於APP需要输入资料时,例如:输入姓名、...
Hi Dai Gei Ho~ 我是Winnie~ 在今天文章中, 我们要来说的Composition...
【前言】 最後这个 Deploy NFT 才是真正真正真正的大魔王,比我想像中还要难超级多,难到我...
想要在html表格中完成合并储存格的效果,需要用到rowspan和colspan分别为垂直和水平合并...
目前Android Studio预设的布局是ConstraintLayout 它的效能比起其他布局还...