<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>

          React Hooks使用一年后,個人使用心得!

          共 23035字,需瀏覽 47分鐘

           ·

          2021-03-13 08:24

          原文:juejin.cn/post/6912309761066729485#heading-9


          關(guān)注公眾號 前端人,回復(fù)“加群

          添加無廣告優(yōu)質(zhì)學(xué)習(xí)群

          前言

          分享一下用了將近一年hooks使用心得

          動機(jī)(官方)

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

          常用 hook

          useState
             const [state, setState] = useState(initialState)
          • useState 有一個參數(shù),該參數(shù)可以為任意數(shù)據(jù)類型,一般用作默認(rèn)值
          • 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ī)則:

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

           } else {
            workInProgressHook = workInProgressHook.next = hook;
           }
           // 返回當(dāng)前的 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);
                              //按照當(dāng)前鏈表位置更新數(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 (
                  <button
                      onClick={() =>
           {
                      setName(李四");
                      }}
                  >
                      修改姓名
                  </button>

            );

          首次渲染時 hook 順序為

          name => age => sex

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

          setSex

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

          為了避免出現(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 會阻塞渲染,請謹(jǐn)慎使用

          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)建。但組件銷毀,它也會消失,不用手動進(jìn)行銷毀

          • 總結(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 默認(rèn)是是基于 props 的淺對比,也可以開啟第二個參數(shù)進(jìn)行深對比。在最外層包裝了整個組件,并且需要手動寫一個方法比較那些具體的 props 不相同才進(jìn)行 re-render。使用 useMemo 可以精細(xì)化控制,進(jìn)行局部 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({
                 widthundefined,
                 heightundefined,
             });
             useEffect(
                 () => {

                     function handleResize({
                         setWindowSize({
                             widthwindow.innerWidth,
                             heightwindow.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} />
                 </>

          }

          總結(jié)

          越來越多的 react 配套的三方庫都上了 hooks 版,像 react-router、redux 都出了 hooks。

          同時也出現(xiàn)了一些好用的 hooks 庫,比如 ahooks 這種。自從用了 hooks 以后我就兩個字,真香


          • 回復(fù)資料包領(lǐng)取我整理的進(jìn)階資料包
          • 回復(fù)加群,加入前端進(jìn)階群
          • console.log("點贊===看===你我都快樂")
          • Bug離我更遠(yuǎn)了,下班離我更近了










          瀏覽 69
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(jī)掃一掃分享

          分享
          舉報
          <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>
                  乱伦操骚逼视频 | 狠狠躁夜夜躁人人爽天天高潮 | 男女专区免费 | 久久久久久蜜桃 | 色图欧美色图 |