Re: 新手让网页 act 起来: Day16 - 探索 useState (2)

昨天我们成功的完成一个超简略版的 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:让输入框下方出现按钮区域

[Android Studio 30天自我挑战] EditText的元件介绍

EditText与TextView相似,但EditText用於APP需要输入资料时,例如:输入姓名、...

Day_20 : 让 Vite 来开启你的Vue 之 watch & watchEffect

Hi Dai Gei Ho~ 我是Winnie~ 在今天文章中, 我们要来说的Composition...

Day 25【Deploy NFT - Layers Blending & MetaData】Read the License

【前言】 最後这个 Deploy NFT 才是真正真正真正的大魔王,比我想像中还要难超级多,难到我...

html表格-合并储存格

想要在html表格中完成合并储存格的效果,需要用到rowspan和colspan分别为垂直和水平合并...

Day10 - LinearLayout线性布局

目前Android Studio预设的布局是ConstraintLayout 它的效能比起其他布局还...