<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我與hooks的這一年, 萬字長文總結(jié)

          共 10720字,需瀏覽 22分鐘

           ·

          2021-02-04 10:00


          轉(zhuǎn)自:掘金 -?又在吃魚
          https://juejin.cn/post/6912309761066729485

          前言

          這一年注定是不平凡的一年,經(jīng)歷了疫情在家兩個月封城。剛好 hooks 出來了,學(xué)習(xí)了一下,發(fā)現(xiàn)真香,根本停不下來,分享一下用了將近一年的心得,在 2020 年最后一天上了末班車

          動機(官方)

          • 組件之間很難重用有狀態(tài)邏輯
          • 復(fù)雜的組件變得難以理解
          • 類 class 混淆了人和機器
          • 更符合 FP 的理解, React 組件本身的定位就是函數(shù),一個吃進數(shù)據(jù)、吐出 UI 的函數(shù)

          常用 hook

          useState

          ???const?[state,?setState]?=?useState(initialState)

          • useState 有一個參數(shù),該參數(shù)可以為任意數(shù)據(jù)類型,一般用作默認值
          • useState 返回值為一個數(shù)組,數(shù)組的第一個參數(shù)為我們需要使用的 state,第二個參數(shù)為一個 setFn。
          • 完整例子
          function?Love()?{
          ????const?[like,?setLike]?=?useState(false)
          ????const?likeFn?=?()?=>?(newLike)?=>?setLike(newLike)
          ????return?(
          ??????<>
          ????????你喜歡我嗎:?{like???'yes'?:?'no'}
          ????????<button?onClick={likeFn(true)}>喜歡button>

          ????????<button?onClick={likeFn(false)}>不喜歡button>
          ??????
          ????)
          ??}

          關(guān)于使用規(guī)則:

          1. 只在 React 函數(shù)中調(diào)用 Hook;
          2. 不要在循環(huán)、條件或嵌套函數(shù)中調(diào)用 Hook。讓我們來看看規(guī)則 2 為什么會有這個現(xiàn)象, 先看看 hook 的組成
          function?mountWorkInProgressHook()?{
          ?//?注意,單個?hook?是以對象的形式存在的
          ?var?hook?=?{
          ??memoizedState:?null,
          ??baseState:?null,
          ??baseQueue:?null,
          ??queue:?null,
          ??next:?null
          ?};
          ?if?(workInProgressHook?===?null)?{
          ????????firstWorkInProgressHook?=?workInProgressHook?=?hook;
          ????????/*?等價
          ????????????let?workInProgressHook?=?hooks
          ????????????firstWorkInProgressHook?=?workInProgressHook
          ????????*/

          ?}?else?{
          ??workInProgressHook?=?workInProgressHook.next?=?hook;
          ?}
          ?//?返回當前的?hook
          ?return?workInProgressHook;
          }

          每個 hook 都會有一個 next 指針,hook 對象之間以單向鏈表的形式相互串聯(lián), 同時也能發(fā)現(xiàn) useState 底層依然是 useReducer?再看看更新階段發(fā)生了什么

          //?ReactFiberHooks.js
          const?HooksDispatcherOnUpdate:?Dispatcher?=?{
          ??????//?...
          ?????useState:?updateState,
          ??}
          ??function?updateState(initialState)?{
          ????return?updateReducer(basicStateReducer,?initialState);
          ??}

          function?updateReducer(reducer,?initialArg,?init)?{
          ????const?hook?=?updateWorkInProgressHook();
          ????const?queue?=?hook.queue;
          ????if?(numberOfReRenders?>?0)?{
          ????????const?dispatch?=?queue.dispatch;
          ????????if?(renderPhaseUpdates?!==?null)?{
          ????????????//?獲取Hook對象上的?queue,內(nèi)部存有本次更新的一系列數(shù)據(jù)
          ????????????const?firstRenderPhaseUpdate?=?renderPhaseUpdates.get(queue);
          ????????????if?(firstRenderPhaseUpdate?!==?undefined)?{
          ????????????????renderPhaseUpdates.delete(queue);
          ????????????????let?newState?=?hook.memoizedState;
          ????????????????let?update?=?firstRenderPhaseUpdate;
          ????????????????//?獲取更新后的state
          ????????????????do?{
          ????????????????????//?useState?第一個參數(shù)會被轉(zhuǎn)成?useReducer
          ????????????????????const?action?=?update.action;
          ????????????????????newState?=?reducer(newState,?action);
          ????????????????????//按照當前鏈表位置更新數(shù)據(jù)
          ????????????????????update?=?update.next;
          ????????????????}?while?(update?!==?null);
          ????????????????hook.memoizedState?=?newState;
          ????????????????//?返回新的?state?以及?dispatch
          ????????????????return?[newState,?dispatch];
          ????????????}
          ????????}
          ????}
          ????//?...
          }

          結(jié)合實際讓我們看下面一組 hooks

          ????let?isMounted?=?false
          ????if(!isMounted)?{
          ????????[name,?setName]?=?useState("張三");
          ????????[age]?=?useState("25");
          ????????isMounted?=?true
          ????}
          ????[sex,?setSex]?=?useState("男");
          ????return?(
          ????????????????????onClick={()?=>?{
          ????????????setName(李四");
          ????????????}}
          ????????>
          ????????????修改姓名
          ????????
          ??);

          首次渲染時 hook 順序為

          name => age => sex

          二次渲染的時根據(jù)上面的例子,調(diào)用的 hook 的只有一個

          setSex

          所以總結(jié)一下初始化階段構(gòu)建鏈表,更新階段按照順序去遍歷之前構(gòu)建好的鏈表,取出對應(yīng)的數(shù)據(jù)信息進行渲染當兩次順序不一樣的時候就會造成渲染上的差異。

          為了避免出現(xiàn)上面這種情況我們可以安裝?eslint-plugin-react-hooks


          //?你的?ESLint?配置
          {
          ??"plugins":?[
          ????//?...
          ????"react-hooks"
          ??],
          ??"rules":?{
          ????//?...
          ????"react-hooks/rules-of-hooks":?"error",?//?檢查?Hook?的規(guī)則
          ????"react-hooks/exhaustive-deps":?"warn"?//?檢查?effect?的依賴
          ??}
          }


          useEffect

          useEffect(effect,?array)

          effect 每次完成渲染之后觸發(fā), 配合 array 去模擬類的生命周期

          • 如果不傳,則每次 componentDidUpdate 時都會先觸發(fā) returnFunction(如果存在),再觸發(fā) effect
          • [] 模擬 componentDidMount
          • [id] 僅在 id 的值發(fā)生變化以后觸發(fā)
          • 清除 effect
          useEffect(()?=>?{
          ??ChatAPI.subscribeToFriendStatus(props.id,?handleStatusChange);
          ??return?()?=>?{
          ????ChatAPI.unsubscribeFromFriendStatus(props.id,?handleStatusChange);
          ??};
          });

          useLayoutEffect

          • 跟 useEffect 使用差不多,通過同步執(zhí)行狀態(tài)更新可解決一些特性場景下的頁面閃爍問題
          • useLayoutEffect 會阻塞渲染,請謹慎使用 對比看看?
          import?React,?{?useLayoutEffect,?useEffect,?useState?}?from?'react';
          import?'./App.css'
          function?App()?{
          ????const?[value,?setValue]?=?useState(0);
          ????useEffect(()?=>?{
          ????????if?(value?===?0)?{
          ????????????setValue(10?+?Math.random()?*?200);
          ????????}
          ??????},?[value]);
          ????const?test?=?()?=>?{
          ????????setValue(0)
          ????}
          ????const?color?=?!value????'red'?:?'yellow'
          ?return?(
          ??<React.Fragment>
          ????????????<p?style={{?background:?color}}>value:?{value}p>

          ???<button?onClick={test}>點我button>
          ??React.Fragment>
          ?);
          }
          export?default?App;

          useContext

          const?context?=?useContext(Context)

          useContext 從名字上就可以看出,它是以 Hook 的方式使用 React Context, 先簡單介紹 Context 的概念和使用方式

          import?React,?{?useContext,?useState,?useEffect?}?from?"react";
          const?ThemeContext?=?React.createContext(null);
          const?Button?=?()?=>?{
          ??const?{?color,?setColor?}?=?React.useContext(ThemeContext);
          ??useEffect(()?=>?{
          ????console.info("Context?changed:",?color);
          ??},?[color]);
          ??const?handleClick?=?()?=>?{
          ????console.info("handleClick");
          ????setColor(color?===?"blue"???"red"?:?"blue");
          ??};
          ??return?(
          ????<button
          ??????type="button"
          ??????onClick={handleClick}
          ??????style={{?backgroundColor:?color,?color:?"white"?}}
          ????>

          ??????toggle?color?in?Child
          ????button>

          ??);
          };
          //?app.js
          const?App?=?()?=>?{
          ??const?[color,?setColor]?=?useState("blue");

          ??return?(
          ????<ThemeContext.Provider?value={{?color,?setColor?}}>
          ??????<h3>
          ????????Color?in?Parent:?<span?style={{?color:?color?}}>{color}span>

          ??????h3>
          ??????<Button?/>
          ????ThemeContext.Provider>
          ??);
          };


          useReducer

          const?[state,?dispatch]?=?useReducer(reducer,?initialArg,?init)

          語法糖跟 redux 差不多,放個基礎(chǔ) ??

          function?init(initialCount)?{
          ????return?{count:?initialCount};
          }
          function?reducer(state,?action)?{
          ????switch?(action.type)?{
          ????????case?'increment':
          ????????????return?{count:?state.count?+?1};
          ????????case?'decrement':
          ????????????return?{count:?state.count?-?1};
          ????????case?'reset':
          ????????????return?init(action.payload);
          ????????default:
          ????????????throw?new?Error();
          ????}
          }
          function?Counter({initialCount})?{
          ????const?[state,?dispatch]?=?useReducer(reducer,?initialCount,?init);
          ????return?(
          ????????<>
          ????????Count:?{state.count}
          <button
          ????onClick={()?=>
          ?dispatch({type:?'reset',?payload:?initialCount})}>
          ????Reset
          button>

          <button?onClick={()?=>?dispatch({type:?'increment'})}>+button>
          <button?onClick={()?=>?dispatch({type:?'decrement'})}>-button>

          );
          }

          useRef

          const?refContainer?=?useRef(initialValue);

          useRef 返回一個可變的 ref 對象,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)。返回的 ref 對象在組件的整個生命周期內(nèi)保持不變

          • 解決引用問題--useRef 會在每次渲染時返回同一個 ref 對象

          • 解決一些 this 指向問題

          • 對比 createRef -- 在初始化階段兩個是沒區(qū)別的,但是在更新階段兩者是有區(qū)別的。

          • 我們知道,在一個局部函數(shù)中,函數(shù)每一次 update,都會在把函數(shù)的變量重新生成一次。所以我們每更新一次組件, 就重新創(chuàng)建一次 ref, 這個時候繼續(xù)使用 createRef 顯然不合適,所以官方推出?useRef。useRef 創(chuàng)建的 ref 仿佛就像在函數(shù)外部定義的一個全局變量,不會隨著組件的更新而重新創(chuàng)建。但組件銷毀,它也會消失,不用手動進行銷毀

            總結(jié)下就是 ceateRef 每次渲染都會返回一個新的引用,而 useRef 每次都會返回相同的引用

          useMemo

          const?memoizedValue?=?useMemo(()?=>?computeExpensiveValue(a,?b),?[a,?b]);

          一個常用來做性能優(yōu)化的 hook,看個 ??

          const?MemoDemo?=?({?count,?color?})?=>?{
          ???useEffect(()?=>?{
          ???????console.log('count?effect')
          ???},?[count])
          ???const?newCount?=?useMemo(()?=>?{
          ???????console.log('count?觸發(fā)了')
          ???????return?Math.round(count)
          ???},?[count])
          ???const?newColor?=?useMemo(()?=>?{
          ???????console.log('color?觸發(fā)了')
          ???????return?color
          ???},?[color])
          ???return?<div>
          ???????<p>{count}p>

          ???????<p>{newCount}p>
          ???{newColor}div>
          }

          我們這個時候?qū)魅氲?count 值改變 的,log 執(zhí)行循序

          count 觸發(fā)了

          count effect

          • 可以看出有點類似 effect, 監(jiān)聽 a、b 的值根據(jù)值是否變化來決定是否更新 UI
          • memo 是在 DOM 更新前觸發(fā)的,就像官方所說的,類比生命周期就是 shouldComponentUpdate
          • 對比?React.Memo?默認是是基于 props 的淺對比,也可以開啟第二個參數(shù)進行深對比。在最外層包裝了整個組件,并且需要手動寫一個方法比較那些具體的 props 不相同才進行 re-render。使用?useMemo 可以精細化控制,進行局部 Pure

          useCallback

          const?memoizedCallback?=?useCallback(
          ?()?=>?{
          ???doSomething(a,?b);
          ?},
          ?[a,?b],
          );

          useCallback 的用法和上面 useMemo 差不多,是專門用來緩存函數(shù)的 hooks

          //?下面的情況可以保證組件重新渲染得到的方法都是同一個對象,避免在傳給onClick的時候每次都傳不同的函數(shù)引用
          import?React,?{?useState,?useCallback?}?from?'react'

          function?MemoCount()?{
          ???const?[value,?setValue]?=?useState(0)

          ???memoSetCount?=?useCallback(()=>{
          ???????setValue(value?+?1)
          ???},[])

          ???return?(
          ???????<div>
          ???????????<button
          ???????????????onClick={memoSetCount}
          ???????????????>

          ???????????????Update?Count
          ???????????button>

          ???????????<div>{value}div>
          ???????div>
          ???)
          }
          export?default?MemoCount


          自定義 hooks

          自定義 Hook 是一個函數(shù),其名稱以 “use” 開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook 一般我將 hooks 分為這幾類

          util

          顧名思義工具類,比如 useDebounce、useInterval、useWindowSize 等等。例如下面 useWindowSize

          import?{?useEffect,?useState?}?from?'react';
          export?default?function?useWindowSize(el)?{
          ???const?[windowSize,?setWindowSize]?=?useState({
          ???????width:?undefined,
          ???????height:?undefined,
          ???});
          ???useEffect(
          ???????()?=>?{

          ???????????function?handleResize()?{
          ???????????????setWindowSize({
          ???????????????????width:?window.innerWidth,
          ???????????????????height:?window.innerHeight,
          ???????????????});
          ???????????}

          ???????????window.addEventListener('resize',?handleResize);
          ???????????handleResize();
          ???????????return?()?=>?window.removeEventListener('resize',?handleResize);
          ???????},
          ???????[el],
          ???);
          ???return?windowSize;
          }

          API

          像之前的我們有一個公用的城市列表接口,在用 redux 的時候可以放在全局公用,不用的話我們就可能需要復(fù)制粘貼了。有了 hooks 以后我們只需要 use 一下就可以在其他地方復(fù)用了

          import?{?useState,?useEffect?}?from?'react';
          import?{?getCityList?}?from?'@/services/static';
          const?useCityList?=?(params)?=>?{
          ???const?[cityList,?setList]?=?useState([]);
          ???const?[loading,?setLoading]?=?useState(true)
          ???const?getList?=?async?()?=>?{
          ???????const?{?success,?data?}?=?await?getCityList(params);
          ???????if?(success)?setList(data);
          ???????setLoading(false)
          ???};
          ???useEffect(
          ???????()?=>?{getList();},
          ???????[],
          ???);
          ???return?{
          ???????cityList,
          ???????loading
          ???};
          };
          export?default?useCityList;
          //?bjs
          function?App()?{
          ???//?...
          ???const?{?cityList,?loading?}?=?useCityList()
          ???//?...
          }

          logic

          邏輯類,比如我們有一個點擊用戶頭像關(guān)注用戶或者取消關(guān)注的邏輯,可能在評論列表、用戶列表都會用到,我們可以這樣做

          import?{?useState,?useEffect?}?from?'react';
          import?{?followUser?}?from?'@/services/user';
          const?useFollow?=?({?accountId,?isFollowing?})?=>?{
          ????const?[isFollow,?setFollow]?=?useState(false);
          ????const?[operationLoading,?setLoading]?=?useState(false)
          ????const?toggleSection?=?async?()?=>?{
          ????????setLoading(true)
          ????????const?{?success?}?=?await?followUser({?accountId?});
          ????????if?(success)?{
          ????????????setFollow(!isFollow);
          ????????}
          ????????setLoading(false)
          ????};
          ????useEffect(
          ????????()?=>?{
          ????????????setFollow(isFollowing);
          ????????},
          ????????[isFollowing],
          ????);
          ????return?{
          ????????isFollow,
          ????????toggleSection,
          ????????operationLoading
          ????};
          };
          export?default?useFollow;

          只需暴露三個參數(shù)就能滿足大部分場景

          UI

          還有一些和 UI 一起綁定的 hook, 但是這里有點爭議要不要和 ui 一起混用。就我個人而言一起用確實幫我解決了部分復(fù)用問題,我還是分享出來。

          import?React,?{?useState?}?from?'react';
          import?{?Modal?}?from?'antd';
          //?TODO?為了兼容一個頁面有多個?modal,?目前想法通過唯一?key?區(qū)分,后續(xù)優(yōu)化
          export?default?function?useModal(key?=?'open')?{
          ????const?[opens,?setOpen]?=?useState({
          ????????[key]:?false,
          ????});
          ????const?onCancel?=?()?=>?{
          ????????setOpen({?[key]:?false?});
          ????};
          ????const?showModal?=?(type?=?key)?=>?{
          ????????setOpen({?[type]:?true?});
          ????};
          ????const?MyModal?=?(props)?=>?{
          ????????return?<Modal?key={key}?visible={opens[key]}?onCancel={onCancel}?{...props}?/>;
          ????};
          ????return?{
          ????????showModal,
          ????????MyModal,
          ????};
          }
          //?使用
          function?App()?{
          ????const?{?showModal,?MyModal?}?=?useModal();
          ????return?<>
          ??????????<button?onClick={showModal}>展開button>

          ??????????<MyModal?onOk={console.log}?/>
          ???????
          }

          邏輯跨端

          最近聽了第十五屆的 D2 大會當軒大佬的《跨端的另一種思路》——Write Once 的分享,核心就是如何渲染、如何布局等 UI 層面的變化要遠遠大于業(yè)務(wù)邏輯層面,甚至是小程序和 Flutter,其大致的開發(fā)范式都沒有發(fā)生太大的改變,F(xiàn)lutter 開發(fā)范式和 React 非常相似,同樣是聲明式 UI,同樣存在 VirtualDOM。

          同樣一段 useCount 的代碼,通過抽象 AST 到 dart, 如下

          上面的算是高級應(yīng)用,我們?nèi)张e個簡單例子。一個項目要做 pc 站點又要做移動端,在不考慮雙端業(yè)務(wù)是否合理的情況下,這種情況 ui 能復(fù)用的地方不太多,但是業(yè)務(wù)邏輯能大量通過 hooks 進行復(fù)用,也算是是一個偽邏輯跨端

          總結(jié)

          越來越多的 react 配套的三方庫都上了 hooks 版,像 react-router、redux 都出了 hooks。同時也出現(xiàn)了一些好用的 hooks 庫,比如 ahooks 這種。自從用了 hooks 以后我就兩個字,真香。





          推薦閱讀




          我的公眾號能帶來什么價值?(文末有送書規(guī)則,一定要看)

          每個前端工程師都應(yīng)該了解的圖片知識(長文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?

          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  无码一区二区黑人猛烈视频网站 | 北条麻妃成人网站 | 一区二区三区水蜜桃 | 无码人妻一区二区三区免费九色 | 最近日韩中文字幕 |