iT邦幫忙

2021 iThome 鐵人賽

DAY 16
0
自我挑戰組

Re: 新手讓網頁 act 起來系列 第 16

Re: 新手讓網頁 act 起來: Day16 - 探索 useState (2)

  • 分享至 

  • xImage
  •  

昨天我們成功的完成一個超簡略版的 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 吧!如果對於今天的內容有任何問題都歡迎在下方留言!


上一篇
Re: 新手讓網頁 act 起來: Day15 - 探索 useState (1)
下一篇
Re: 新手讓網頁 act 起來: Day17 - 探索 useEffect
系列文
Re: 新手讓網頁 act 起來30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言