最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

React深入淺出分析Hooks源碼

 更新時間:2022年11月04日 12:01:00   作者:goClient1992  
在react類組件(class)寫法中,有setState和生命周期對狀態(tài)進(jìn)行管理,但是在函數(shù)組件中不存在這些,故引入hooks(版本:>=16.8),使開發(fā)者在非class的情況下使用更多react特性

useState 解析

useState 使用

通常我們這樣來使用 useState 方法

function App() {
  const [num, setNum] = useState(0);
  const add = () => {
    setNum(num + 1);
  };
  return (
    <div>
      <p>數(shù)字: {num}</p>
      <button onClick={add}> +1 </button>
    </div>
  );
}

useState 的使用過程,我們先模擬一個大概的函數(shù)

function useState(initialValue) {
  var value = initialValue
  function setState(newVal) {    
    value = newVal
  }
  return [value, setState]
}

這個代碼有一個問題,在執(zhí)行 useState 的時候每次都會 var _val = initialValue,初始化數(shù)據(jù);

于是我們可以用閉包的形式來保存狀態(tài)。

const MyReact = (function() {
   // 定義一個 value 保存在該模塊的全局中
  let value
  return {
    useState(initialValue) {
      value = value || initialValue 
      function setState(newVal) {
        value = newVal
      }
      return [value, setState]
    }
  }
})()

這樣在每次執(zhí)行的時候,就能夠通過閉包的形式 來保存 value。

不過這個還是不符合 react 中的 useState。因為在實際操作中會出現(xiàn)多次調(diào)用,如下。

function App() {
  const [name, setName] = useState('Kevin');
  const [age, setAge] = useState(0);
  const handleName = () => {
    setNum('Dom');
  };
  const handleAge = () => {
    setAge(age + 1);
  };
  return (
    <div>
      <p>姓名: {name}</p>
      <button onClick={handleName}> 改名字 </button>
       <p>年齡: {age}</p>
      <button onClick={handleAge}> 加一歲 </button>
    </div>
  );
}

因此我們需要在改變 useState 儲存狀態(tài)的方式

useState 模擬實現(xiàn)

const MyReact = (function() {
  // 開辟一個儲存 hooks 的空間
  let hooks = []; 
  // 指針從 0 開始
  let currentHook = 0 
  return {
    // 偽代碼 解釋重新渲染的時候 會初始化 currentHook
    render(Component) {
      const Comp = Component()
      Comp.render()
      currentHook = 0 // 重新渲染時候改變 hooks 指針
      return Comp
    },      
    useState(initialValue) {
      hooks[currentHook] = hooks[currentHook] || initialValue
      const setStateHookIndex = currentHook
      // 這里我們暫且默認(rèn) setState 方式第一個參數(shù)不傳 函數(shù),直接傳狀態(tài)
      const setState = newState => (hooks[setStateHookIndex] = newState)
      return [hooks[currentHook++], setState]
    }
  }
})()

因此當(dāng)重新渲染 App 的時候,再次執(zhí)行 useState 的時候傳入的參數(shù) kevin , 0 也就不會去使用,而是直接拿之前 hooks 存儲好的值。

hooks 規(guī)則

官網(wǎng) hoos 規(guī)則中明確的提出 hooks 不要再循環(huán),條件或嵌套函數(shù)中使用。

為什么不可以?

我們來看下

下面這樣一段代碼。執(zhí)行 useState 重新渲染,和初始化渲染 順序不一樣就會出現(xiàn)如下問題

如果了解了上面 useState 模擬寫法的存儲方式,那么這個問題的原因就迎刃而解了。相關(guān)參考視頻:傳送門

useEffect 解析

useEffect 使用

初始化會 打印一次 ‘useEffect_execute’, 改變年齡重新render,會再打印, 改變名字重新 render, 不會打印。因為依賴數(shù)組里面就監(jiān)聽了 age 的值

import React, { useState, useEffect } from 'react';
function App() {
  const [name, setName] = useState('Kevin');
  const [age, setAge] = useState(0);
  const handleName = () => {
    setName('Don');
  };
  const handleAge = () => {
    setAge(age + 1);
  };
  useEffect(()=>{
    console.log('useEffect_execute')
  }, [age])
  return (
    <div>
      <p>姓名: {name}</p>
      <button onClick={handleName}> 改名字 </button>
      <p>年齡: {age}</p>
      <button onClick={handleAge}> 加一歲 </button>
    </div>
  );
}
export default App;

useEffect 的模擬實現(xiàn)

const MyReact = (function() {
  // 開辟一個儲存 hooks 的空間
  let hooks = []; 
  // 指針從 0 開始
  let currentHook = 0 ;
  // 定義個模塊全局的 useEffect 依賴
  let deps;
  return {
    // 偽代碼 解釋重新渲染的時候 會初始化 currentHook
    render(Component) {
      const Comp = Component()
      Comp.render()
      currentHook = 0 // 重新渲染時候改變 hooks 指針
      return Comp
    },      
    useState(initialValue) {
      hooks[currentHook] = hooks[currentHook] || initialValue
      const setStateHookIndex = currentHook
      // 這里我們暫且默認(rèn) setState 方式第一個參數(shù)不傳 函數(shù),直接傳狀態(tài)
      const setState = newState => (hooks[setStateHookIndex] = newState)
      return [hooks[currentHook++], setState]
    }
    useEffect(callback, depArray) {
      const hasNoDeps = !depArray
      // 如果沒有依賴,說明是第一次渲染,或者是沒有傳入依賴參數(shù),那么就 為 true
      // 有依賴 使用 every 遍歷依賴的狀態(tài)是否變化, 變化就會 true
      const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true
      // 如果沒有依賴, 或者依賴改變
      if (hasNoDeps || hasChangedDeps) {
        // 執(zhí)行 
        callback()
        // 更新依賴
        deps = depArray
      }
    },

  }
})()

useEffect 注意事項

依賴項要真實

依賴需要想清楚。

剛開始使用 useEffect 的時候,我只有想重新觸發(fā) useEffect 的時候才會去設(shè)置依賴

那么也就會出現(xiàn)如下的問題。

希望的效果是界面中一秒增加一歲

import React, { useState, useEffect } from 'react';
function App() {
  const [name, setName] = useState('Kevin');
  const [age, setAge] = useState(0);
  const handleName = () => {
    setName('Don');
  };
  const handleAge = () => {
    setAge(age + 1);
  };
  useEffect(() => {
    setInterval(() => {
      setAge(age + 1);
      console.log(age)
    }, 1000);
  }, []);
  return (
    <div>
      <p>姓名: {name}</p>
      <button onClick={handleName}> 改名字 </button>
      <p>年齡: {age}</p>
      <button onClick={handleAge}> 加一歲 </button>
    </div>
  );
}
export default App;

其實你會發(fā)現(xiàn) 這里界面就增加了 一次 年齡。究其原因:

**在第一次渲染中,age0。因此,setAge(age+ 1)在第一次渲染中等價于setAge(0 + 1)。然而我設(shè)置了0依賴為空數(shù)組,那么之后的 useEffect 不會再重新運行,它后面每一秒都會調(diào)用setAge(0 + 1) **

也就是當(dāng)我們需要 依賴 age 的時候我們 就必須再 依賴數(shù)組中去記錄他的依賴。這樣useEffect 才會正常的給我們?nèi)ミ\行。

所以我們想要每秒都遞增的話有兩種方法

方法一:

真真切切的把你所依賴的狀態(tài)填寫到 數(shù)組中

  // 通過監(jiān)聽 age 的變化。來重新執(zhí)行 useEffect 內(nèi)的函數(shù)
  // 因此這里也就需要記錄定時器,當(dāng)卸載的時候我們?nèi)デ蹇斩〞r器,防止多個定時器重新觸發(fā)
  useEffect(() => {
    const id = setInterval(() => {
      setAge(age + 1);
    }, 1000);
    return () => {
      clearInterval(id)
    };
  }, [age]);

方法二

useState 的參數(shù)傳入 一個方法。

注:上面我們模擬的 useState 并沒有做這個處理 后面我會講解源碼中去解析。

useEffect(() => {
    setInterval(() => {
      setAge(age => age + 1);
    }, 1000);
  }, []);

useEffect 只運行了一次,通過 useState 傳入函數(shù)的方式它不再需要知道當(dāng)前的age值。因為 React render 的時候它會幫我們處理

這正是setAge(age => age + 1)做的事情。再重新渲染的時候他會幫我們執(zhí)行這個方法,并且傳入最新的狀態(tài)。

所以我們做到了去時刻改變狀態(tài),但是依賴中卻不用寫這個依賴,因為我們將原本的使用到的依賴移除了。(這句話表達(dá)感覺不到位)

接口無限請求問題

剛開始使用 useEffect 的我,在接口請求的時候常常會這樣去寫代碼。

props 里面有 頁碼,通過切換頁碼,希望監(jiān)聽頁碼的變化來重新去請求數(shù)據(jù)

// 以下是偽代碼 
// 這里用 dva 發(fā)送請求來模擬
import React, { useState, useEffect } from 'react';
import { connect } from 'dva';
function App(props) {
  const { goods, dispatch, page } = props;
  useEffect(() => {
    // 頁面完成去發(fā)情請求
   dispatch({
      type: '/goods/list',
      payload: {page, pageSize:10},
    });
    // xxxx 
  }, [props]);
  return (
    <div>
      <p>商品: {goods}</p>
     <button>點擊切下一頁</button>
    </div>
  );
}
export default connect(({ goods }) => ({
  goods,
}))(App);

然后得意洋洋的刷新界面,發(fā)現(xiàn) Network 中瘋狂循環(huán)的請求接口,導(dǎo)致頁面的卡死。

究其原因是因為在依賴中,我們通過接口改變了狀態(tài) props 的更新, 導(dǎo)致重新渲染組件,導(dǎo)致會重新執(zhí)行 useEffect 里面的方法,方法執(zhí)行完成之后 props 的更新, 導(dǎo)致重新渲染組件,依賴項目是對象,引用類型發(fā)現(xiàn)不相等,又去執(zhí)行 useEffect 里面的方法,又重新渲染,然后又對比,又不相等, 又執(zhí)行。因此產(chǎn)生了無限循環(huán)。

Hooks 源碼解析

該源碼位置: react/packages/react-reconciler/src/ReactFiberHooks.js

const Dispatcher={
  useReducer: mountReducer,
  useState: mountState,
  // xxx 省略其他的方法
}

mountState 源碼

function mountState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
    /*    mountWorkInProgressHook 方法 返回初始化對象    {        memoizedState: null,        baseState: null,         queue: null,        baseUpdate: null,        next: null,      }    */
  const hook = mountWorkInProgressHook();
 // 如果傳入的是函數(shù) 直接執(zhí)行,所以第一次這個參數(shù)是 undefined
  if (typeof initialState === 'function') {
    initialState = initialState();
  }
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    last: null,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: (initialState: any),
  });
    /*    定義 dispatch 相當(dāng)于    const dispatch = queue.dispatch =    dispatchAction.bind(null,currentlyRenderingFiber,queue);    */ 
  const dispatch: Dispatch<
    BasicStateAction<S>,
  > = (queue.dispatch = (dispatchAction.bind(
    null,
    // Flow doesn't know this is non-null, but we do.
    ((currentlyRenderingFiber: any): Fiber),
    queue,
  ): any));
 // 可以看到這個dispatch就是dispatchAction綁定了對應(yīng)的 currentlyRenderingFiber 和 queue。最后return:
  return [hook.memoizedState, dispatch];
}

dispatchAction 源碼

function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A) {
  //... 省略驗證的代碼
  const alternate = fiber.alternate;
    /*    這其實就是判斷這個更新是否是在渲染過程中產(chǎn)生的,currentlyRenderingFiber只有在FunctionalComponent更新的過程中才會被設(shè)置,在離開更新的時候設(shè)置為null,所以只要存在并更產(chǎn)生更新的Fiber相等,說明這個更新是在當(dāng)前渲染中產(chǎn)生的,則這是一次reRender。所有更新過程中產(chǎn)生的更新記錄在renderPhaseUpdates這個Map上,以每個Hook的queue為key。對于不是更新過程中產(chǎn)生的更新,則直接在queue上執(zhí)行操作就行了,注意在最后會發(fā)起一次scheduleWork的調(diào)度。    */
  if (
    fiber === currentlyRenderingFiber ||
    (alternate !== null && alternate === currentlyRenderingFiber)
  ) {
    didScheduleRenderPhaseUpdate = true;
    const update: Update<A> = {
      expirationTime: renderExpirationTime,
      action,
      next: null,
    };
    if (renderPhaseUpdates === null) {
      renderPhaseUpdates = new Map();
    }
    const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
    if (firstRenderPhaseUpdate === undefined) {
      renderPhaseUpdates.set(queue, update);
    } else {
      // Append the update to the end of the list.
      let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
      while (lastRenderPhaseUpdate.next !== null) {
        lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
      }
      lastRenderPhaseUpdate.next = update;
    }
  } else {
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, fiber);
    const update: Update<A> = {
      expirationTime,
      action,
      next: null,
    };
    flushPassiveEffects();
    // Append the update to the end of the list.
    const last = queue.last;
    if (last === null) {
      // This is the first update. Create a circular list.
      update.next = update;
    } else {
      const first = last.next;
      if (first !== null) {
        // Still circular.
        update.next = first;
      }
      last.next = update;
    }
    queue.last = update;
    scheduleWork(fiber, expirationTime);
  }
}

mountReducer 源碼

多勒第三個參數(shù),是函數(shù)執(zhí)行,默認(rèn)初始狀態(tài) undefined

其他的和 上面的 mountState 大同小異

function mountReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = mountWorkInProgressHook();
  let initialState;
  if (init !== undefined) {
    initialState = init(initialArg);
  } else {
    initialState = ((initialArg: any): S);
  }
    // 其他和 useState 一樣
  hook.memoizedState = hook.baseState = initialState;
  const queue = (hook.queue = {
    last: null,
    dispatch: null,
    lastRenderedReducer: reducer,
    lastRenderedState: (initialState: any),
  });
  const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
    null,
    // Flow doesn't know this is non-null, but we do.
    ((currentlyRenderingFiber: any): Fiber),
    queue,
  ): any));
  return [hook.memoizedState, dispatch];
}

通過 react 源碼中,可以看出 useState 是特殊的 useReducer

  • 可見useState不過就是個語法糖,本質(zhì)其實就是useReducer
  • updateState 復(fù)用了 updateReducer(區(qū)別只是 updateState 將 reducer 設(shè)置為 updateReducer)
  • mountState 雖沒直接調(diào)用 mountReducer,但是幾乎大同小異(區(qū)別只是 mountState 將 reducer 設(shè)置為basicStateReducer)

注:這里僅是 react 源碼,至于重新渲染這塊 react-dom 還沒有去深入了解。

更新:

分兩種情況,是否是 reRender,所謂reRender就是說在當(dāng)前更新周期中又產(chǎn)生了新的更新,就繼續(xù)執(zhí)行這些更新知道當(dāng)前渲染周期中沒有更新為止

他們基本的操作是一致的,就是根據(jù) reducerupdate.action 來創(chuàng)建新的 state,并賦值給Hook.memoizedState 以及 Hook.baseState。

注意這里,對于非reRender得情況,我們會對每個更新判斷其優(yōu)先級,如果不是當(dāng)前整體更新優(yōu)先級內(nèi)得更新會跳過,第一個跳過得Update會變成新的baseUpdate,他記錄了在之后所有得Update,即便是優(yōu)先級比他高得,因為在他被執(zhí)行得時候,需要保證后續(xù)的更新要在他更新之后的基礎(chǔ)上再次執(zhí)行,因為結(jié)果可能會不一樣。

來源

preact 中的 hooks

Preact 最優(yōu)質(zhì)的開源 React 替代品?。ㄝp量級 3kb)

注意:這里的替代是指如果不用 react 的話,可以使用這個。而不是取代。

useState 源碼解析

調(diào)用了 useReducer 源碼

export function useState(initialState) {
    return useReducer(invokeOrReturn, initialState);
}

useReducer 源碼解析

// 模塊全局定義
/** @type {number} */
let currentIndex; // 狀態(tài)的索引,也就是前面模擬實現(xiàn) useState 時候所說的指針
let currentComponent; // 當(dāng)前的組件
export function useReducer(reducer, initialState, init) {
    /** @type {import('./internal').ReducerHookState} */
    // 通過 getHookState 方法來獲取 hooks 
    const hookState = getHookState(currentIndex++);
    // 如果沒有組件 也就是初始渲染
    if (!hookState._component) {
        hookState._component = currentComponent;
        hookState._value = [
            // 沒有 init 執(zhí)行 invokeOrReturn
                // invokeOrReturn 方法判斷 initialState 是否是函數(shù)
                // 是函數(shù) initialState(null) 因為初始化沒有值默認(rèn)為null
                // 不是函數(shù) 直接返回 initialState
            !init ? invokeOrReturn(null, initialState) : init(initialState),
            action => {
                // reducer == invokeOrReturn
                const nextValue = reducer(hookState._value[0], action);
                // 如果當(dāng)前的值,不等于 下一個值
                // 也就是更新的狀態(tài)的值,不等于之前的狀態(tài)的值
                if (hookState._value[0]!==nextValue) {
                    // 儲存最新的狀態(tài)
                    hookState._value[0] = nextValue;
                    // 渲染組件
                    hookState._component.setState({});
                }
            }
        ];
    }
    // hookState._value 數(shù)據(jù)格式也就是 [satea:any, action:Function] 的數(shù)據(jù)格式拉
    return hookState._value;
}

getHookState 方法

function getHookState(index) {
    if (options._hook) options._hook(currentComponent);
    const hooks = currentComponent.__hooks || (currentComponent.__hooks = { _list: [], _pendingEffects: [], _pendingLayoutEffects: [] });

    if (index >= hooks._list.length) {
        hooks._list.push({});
    }
    return hooks._list[index];
}

invokeOrReturn 方法

function invokeOrReturn(arg, f) {
    return typeof f === 'function' ? f(arg) : f;
}

總結(jié)

使用 hooks 幾個月了。基本上所有類組件我都使用函數(shù)式組件來寫?,F(xiàn)在 react 社區(qū)的很多組件,都也開始支持hooks。大概了解了點重要的源碼,做到知其然也知其所以然,那么在實際工作中使用他可以減少不必要的 bug,提高效率。

到此這篇關(guān)于React深入淺出分析Hooks源碼的文章就介紹到這了,更多相關(guān)React Hooks內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 一文帶你掌握React類式組件中setState的應(yīng)用

    一文帶你掌握React類式組件中setState的應(yīng)用

    這篇文章主要為大家詳細(xì)介紹了介紹了React類式組件中setState的三種寫法以及簡單討論下setState?到底是同步的還是異步的,感興趣的可以了解下
    2024-02-02
  • 詳解各版本React路由的跳轉(zhuǎn)的方法

    詳解各版本React路由的跳轉(zhuǎn)的方法

    這篇文章主要介紹了詳解各版本React路由的跳轉(zhuǎn)的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-05-05
  • React組件refs的使用詳解

    React組件refs的使用詳解

    這篇文章主要介紹了React組件refs的使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-02-02
  • react MPA 多頁配置詳解

    react MPA 多頁配置詳解

    這篇文章主要介紹了react MPA 多頁配置詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10
  • React配置子路由的實現(xiàn)

    React配置子路由的實現(xiàn)

    本文主要介紹了React配置子路由的實現(xiàn),我們來通過一個簡單的例子解釋一下如何配置子路由,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • React Native react-navigation 導(dǎo)航使用詳解

    React Native react-navigation 導(dǎo)航使用詳解

    本篇文章主要介紹了React Native react-navigation 導(dǎo)航使用詳解,詳解的介紹了react-navigation導(dǎo)航的使用,具有一定的參考價值,有興趣的可以了解一下
    2017-12-12
  • React更新渲染原理深入分析

    React更新渲染原理深入分析

    什么是re-render(重新渲染)?哪些是必要的re-render?哪些是非必要的re-render?如果你對這些問題還不是很明白,那么可以在這篇文章中找到答案
    2022-12-12
  • React之Hooks詳解

    React之Hooks詳解

    這篇文章主要介紹了React hooks的優(yōu)缺點詳解,幫助大家更好的理解和學(xué)習(xí)使用React,感興趣的朋友可以了解下, 希望能夠給你帶來幫助
    2021-09-09
  • 40行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理

    40行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理

    這篇文章主要介紹了40行代碼把Vue3的響應(yīng)式集成進(jìn)React做狀態(tài)管理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • React Native 啟動流程詳細(xì)解析

    React Native 啟動流程詳細(xì)解析

    這篇文章主要介紹了React Native 啟動流程簡析,文以 react-native-cli 創(chuàng)建的示例工程(安卓部分)為例,給大家分析 React Native 的啟動流程,需要的朋友可以參考下
    2021-08-08

最新評論

自贡市| 绥阳县| 惠州市| 通榆县| 浦城县| 武定县| 屯门区| 黑龙江省| 德昌县| 民权县| 滦南县| 柳州市| 中江县| 汽车| 榕江县| 红安县| 岫岩| 北海市| 永清县| 阳高县| 招远市| 淮阳县| 南投市| 新竹县| 麟游县| 甘泉县| 江达县| 元氏县| 马龙县| 增城市| 大余县| 龙江县| 民丰县| 佛冈县| 盐城市| 区。| 鹤峰县| 若尔盖县| 海兴县| 江门市| 伊吾县|