初探hooks實(shí)現(xiàn)與原理

在使用hooks的過程中,大家可能會(huì)有一些疑惑。比如為什么useState只能在函數(shù)最外層調(diào)用,useEffect第二個(gè)參數(shù)的作用等。今天,我們來實(shí)現(xiàn)幾個(gè)簡(jiǎn)單的hooks,并從中了解一些其原理。
1.useState
我們首先來實(shí)現(xiàn)useState。函數(shù)式組件沒有實(shí)例,每次渲染都會(huì)重新執(zhí)行useState函數(shù)。
我們聲明一個(gè)lastState來保存上一次的狀態(tài)。第一次渲染的時(shí)候執(zhí)行useState,此時(shí)lastState沒有值,將useState的參數(shù)作為初始值。在useState中定義一個(gè)setState的方法,來改變lastState的值。另外每次setState后要重新render。最終useState返回一個(gè)數(shù)組,里面包含上一個(gè)狀態(tài)和改變狀態(tài)的方法。以下為useState的簡(jiǎn)單實(shí)現(xiàn)。

let lastState;
function useState(initialState) {
  lastState = lastState || initialState;
  function setState(newState) {
    lastState = newState;
    render();
  }
  return [lastState, setState];
}
function Counter() {
  let [state, setState] = useState(0);
  return (
    <>
      <p>{state}</p>
      <button onClick={() => setState(state + 1)}>+</button>
    </>
  )
}
function render() {
  ReactDOM.render(
    <Counter />,
    document.getElementById('root')
  );
}
render();

如果有多個(gè)state的時(shí)候,那么上面的寫法就不適用了。lastState應(yīng)該聲明為一個(gè)數(shù)組,并且需要相對(duì)應(yīng)有一個(gè)索引。要注意的是每次執(zhí)行useState后需要讓index加一,并且每次render后索引需要重置為0。

let lastState = [];
let index = 0;
function useState(beginState) {
  lastState[index] = lastState[index[|| beginState;
  const currentIndex = index;
  function setState(newState) {
    lastState[currentIndex] = newState;
    render();
  }
  return [lastState[index++], setState];
}
function App() {
  let [state, setState] = useState(0);
  return (
    <div>
      <span>{state}</span>
      <button onClick={() => setState(state + 1)}>+</button>
    </div>
  )
}
function render() {
  index = 0;
  ReactDOM.render(
    <App />,
    document.getElementById('root')
  );
}
render();

可以看出,每次渲染時(shí)state跟index是一一對(duì)應(yīng)的,所以這也是不能把useState放到條件語句中的原因。所以在使用 Hook 的時(shí)候,我們應(yīng)該在函數(shù)組件最外層使用。

2.useReducer
reducer接收兩個(gè)參數(shù),原有狀態(tài)和動(dòng)作,通過派發(fā)動(dòng)作來改變狀態(tài),最后返回一個(gè)新狀態(tài)。useReducer的實(shí)現(xiàn)和useState相似。

function reducer(state, action) {
  if (action.type === 'add') {
    return state + 1;
  } else {
    return state;
  }
}
let lastState;
function useReducer(reducer, beginState) {
  lastState = lastState || beginState;
  function dispatch(action) {
    lastState = reducer(lastState, action);
    render();
  }
  return [lastState, dispatch];
}
function Counter() {
  let [state, dispatch] = useReducer(reducer, 0);
  return (
    <div>
      <span>{state}</span>
      <button onClick={() => dispatch({ type: 'add' })}>+</button>
    </div>
  )
}

3、useEffect
effect在這里是副作用的意思,我們可以在這個(gè)hooks中執(zhí)行一些有副作用的行為,比如操作dom,通過ajax發(fā)送網(wǎng)絡(luò)請(qǐng)求等。它里邊的函數(shù)會(huì)在組件每次render后執(zhí)行。而第二個(gè)參數(shù)我們稱之為依賴項(xiàng),如果其中的元素在每次渲染時(shí)和前一次相比沒有發(fā)生變化,就不會(huì)觸發(fā)這個(gè)副作用。下邊的實(shí)現(xiàn)沒有包括銷毀副作用的功能。

let lastDependencies;
function useEffect(callback, dependencies) {
  if (lastDependencies) {
    let changed = !dependencies.every((item, index) => {
      return item == lastDependencies[index];
    });
    if (changed) {
      callback();
      lastDependencies = dependencies;
    }
  } else {//沒有渲染過
    callback();
    lastDependencies = dependencies;
  }
}
function App() {
  let [number, setNumber] = useState(0);
  useEffect(() => {
    console.log('看看數(shù)字是否變化了呢', number);
  }, [number]);
  return (
    <div>
      <span>{number}</span>
      <button onClick={() => setNumber(number + 1)}>+</button>
    </div>
  )
}
function render() {
  ReactDOM.render(
    <Counter />,
    document.getElementById('root')
  );
}
render();

當(dāng)依賴項(xiàng)為空的時(shí)候,我們會(huì)發(fā)現(xiàn)它只會(huì)執(zhí)行一次,此時(shí)我們可以用useEffect來模擬componentDidMount生命周期。依賴項(xiàng)的比較是用的‘==’而不是‘===’,因此我們可以得知依賴項(xiàng)的對(duì)比是淺比較。
我們對(duì)于effect的實(shí)現(xiàn)比較簡(jiǎn)單,還有很多細(xì)節(jié)沒有體現(xiàn)出來。在上面的代碼中,每次執(zhí)行useEffect都會(huì)打印最新的number值,那么它是如何讀取到最新的state的呢。并不是number的值在“不變”的effect中發(fā)生了改變,而是每一次渲染中的effect的count值都來自于它屬于的那次渲染。因?yàn)閑ffect是在渲染完成后執(zhí)行的,我們可以把它當(dāng)作渲染結(jié)果的一部分。

4、useCallback和useMemo
在之前的文章中我們有了解過這兩個(gè)性能優(yōu)化的hooks。它們的實(shí)現(xiàn)和前面的hooks也很相似。

let lastCallback;
let lastCallbackDependencies;
function useCallback(callback, dependencies) {
  if (lastCallbackDependencies) {
    let changed = !dependencies.every((item, index) => {
      return item == lastCallbackDependencies[index];
    });
    if (changed) {
      lastCallback = callback;
      lastCallbackDependencies = dependencies;
    }
  } else {
    lastCallback = callback;
    lastCallbackDependencies = dependencies;
  }
  return lastCallback;
}
let lastMemo;
let lastMemoDependencies;
function useMemo(callback, dependencies) {
  if (lastMemoDependencies) {
    let changed = !dependencies.every((item, index) => {
      return item == lastMemoDependencies[index];
    });
    if (changed) {
      lastMemo = callback();
      lastMemoDependencies = dependencies;
    }
  } else {//沒有渲染過
    lastMemo = callback();
    lastMemoDependencies = dependencies;
  }
  return lastMemo;
}

在本文中我們實(shí)現(xiàn)了一個(gè)最簡(jiǎn)單的hooks,主要借助的是數(shù)組這個(gè)簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),一定成程度上了解了hooks的原理。但是在React的源碼中,它是通過類似單鏈表的形式而不是數(shù)組。有空應(yīng)該去讀一讀源碼,菜會(huì)有更深入的理解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容