來聊一下作項目時用到的React Hook

1、React Hook是什麼

這是官網的解釋:_Hook_ 是 React 16.8 的新增特性。它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。html

阮一峯大神的解釋:React Hooks 的意思是,組件儘可能寫成純函數,若是須要外部功能和反作用,就用鉤子把外部代碼"鉤"進來。node

個人理解:Hook是能夠提供給咱們更加簡潔的方式去使用React的其餘特性。react

2、React Hook的性質

  • 徹底可選的。 你無需重寫任何已有代碼就能夠在一些組件中嘗試 Hook。可是若是你不想,你沒必要如今就去學習或使用 Hook。
  • 100% 向後兼容的。 Hook 不包含任何破壞性改動。
  • 如今可用。 Hook 已發佈於 v16.8.0。

3、爲何要使用React Hook

  • 在組件之間複用狀態邏輯很難

    可使用 Hook 從組件中提取狀態邏輯,使得這些邏輯能夠單獨測試並複用。Hook 使你在無需修改組件結構的狀況下複用狀態邏輯。 這使得在組件間或社區內共享 Hook 變得更便捷。編程

  • 複雜組件變得難以理解

    咱們常常維護一些組件,組件起初很簡單,可是逐漸會被狀態邏輯和反作用充斥。爲了解決這個問題,Hook 將組件中相互關聯的部分拆分紅更小的函數(好比設置訂閱或請求數據),而並不是強制按照生命週期劃分。你還可使用 reducer 來管理組件的內部狀態,使其更加可預測。json

  • 難以理解的class

    Hook 使你在非 class 的狀況下可使用更多的 React 特性。 從概念上講,React 組件一直更像是函數。而 Hook 則擁抱了函數,同時也沒有犧牲 React 的精神原則。Hook 提供了問題的解決方案,無需學習複雜的函數式或響應式編程技術。api

4、目前遇到的Hook

1.useState

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

返回值:state,更新state的函數setState數組

setState 函數用於更新 state。它接收一個新的 state 值並將組件的一次從新渲染加入隊列。setState(newState);瀏覽器

React 會確保 setState 函數的標識是穩定的,而且不會在組件從新渲染時發生變化。性能優化

參數:初始的state:initialStatedom

在初始渲染期間,返回的狀態 (state) 與傳入的第一個參數 (initialState) 值相同。

示例代碼:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

注意:與 class 組件中的 setState 方法不一樣,useState 不會自動合併更新對象。你能夠用函數式的 setState 結合展開運算符來達到合併更新對象的效果。

setState(prevState => {
  // 也可使用 Object.assign
  return {...prevState, ...updatedValues};
});

2.useEffect

該Hook接收一個包含命令式、且有可能有反作用代碼的函數

useEffect(didUpdate);        //didUpdate指的是包含命令式、且有可能有反作用代碼的函數

在函數組件主體內(這裏指在 React 渲染階段)改變 DOM、添加訂閱、設置定時器、記錄日誌以及執行其餘包含反作用的操做都是不被容許的,由於這可能會產生莫名其妙的 bug 並破壞 UI 的一致性。

使用 useEffect 完成反作用操做。賦值給 useEffect 的函數會在組件渲染到屏幕以後執行。你能夠把 effect 看做從 React 的純函數式世界通往命令式世界的逃生通道。

默認狀況下,effect 將在每輪渲染結束後執行,但你能夠選擇讓它 在只有某些值改變的時候 才執行。

[](https://zh-hans.reactjs.org/d...組件卸載時須要清除 effect 建立的諸如訂閱或計時器 ID 等資源。要實現這一點,useEffect 函數需返回一個清除函數。如下就是一個建立訂閱的例子:

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // 清除訂閱
    subscription.unsubscribe();
  };
});

默認狀況下,effect 會在每輪組件渲染完成後執行。這樣的話,一旦 effect 的依賴發生變化,它就會被從新建立

然而,在某些場景下這麼作可能會矯枉過正。好比,在上面的例子中,咱們不須要在每次組件更新時都建立新的訂閱,而是僅須要在 source prop 改變時從新建立。

要實現這一點,能夠給 useEffect 傳遞第二個參數,它是 effect 所依賴的值數組。更新後的示例以下:

useEffect(
  () => {
    const subscription = props.source.subscribe();
    return () => {
      subscription.unsubscribe();
    };
  },
  [props.source],            //第二個參數是Effect的依賴項,在這裏,只有當該數組發生變化的時候,纔去執行useEffect
);

此時,只有當 props.source 改變後纔會從新建立訂閱

3.useContext

const value = useContext(MyContext);    //MYContext是建立好的context對象
  • 接收一個 context 對象(React.createContext 的返回值)並返回該 context 的當前值。當前的 context 值由上層組件中距離當前組件最近的 <MyContext.Provider> 的 value prop 決定。
  • 當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 MyContext provider 的 context value 值。即便祖先使用 React.memoshouldComponentUpdate,也會在組件自己使用 useContext 時從新渲染。
  • 調用了 useContext 的組件總會在 context 值變化時從新渲染。
  • useContext(MyContext) 至關於 class 組件中的 static contextType = MyContext 或者 <MyContext.Consumer>

注意:

1.useContext(MyContext) 只是讓你可以_讀取_ context 的值以及訂閱 context 的變化。你仍然須要在上層組件樹中使用 <MyContext.Provider> 來爲下層組件_提供_ context。

示例代碼:

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);        //這裏訂閱到了provider傳遞下來的value值
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

4.useReducer

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

它接收一個形如 (state, action) => newState 的 reducer,並返回當前的 state 以及與其配套的 dispatch 方法。

useReducer和useState的比較:這二者都是返回了新的狀態和一個函數。在某些場景下,useReducer 會比 useState 更適用,例如 state 邏輯較複雜且包含多個子值,或者下一個 state 依賴於以前的 state 等。而且,使用 useReducer 還能給那些會觸發深更新的組件作性能優化。

如下是用 reducer 重寫 useState 一節的計數器示例:

const initialState = {count: 0};

function reducer(state, action) {            //reducer
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

第三個參數:init

你能夠選擇惰性地建立初始 state。爲此,須要將 init 函數做爲 useReducer 的第三個參數傳入,這樣初始 state 將被設置爲 init(initialArg)。

仍是上一個計數器的代碼,這裏使用了第三個參數添加了一個reset功能:

function init(initialCount) {            //init函數:用於惰性建立初始state
  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);            //init函數做爲第三個參數傳入useReducer
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

5.useRef

const refContainer = useRef(initialValue);

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

一個常見的用例即是命令式地訪問子組件:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);        //建立一個ref對象
  const onButtonClick = () => {
    // `current` 指向已掛載到 DOM 上的文本輸入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />            //將建立好的ref對象放進ref屬性中
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useRef和ref屬性的區別:

  • 相同點:以咱們以前對refs的瞭解,若是將 ref 對象以 <div ref={myRef} /> 形式傳入組件,則不管該節點如何改變,React 都會將 ref 對象的 .current 屬性設置爲相應的 DOM 節點
  • 不一樣點:然而,useRef() 比 ref 屬性更有用。它能夠很方便地保存任何可變值,其相似於在 class 中使用實例字段的方式。這是由於它建立的是一個普通 Javascript 對象。而 useRef() 和自建一個 {current: ...} 對象的惟一區別是,useRef 會在每次渲染時返回同一個 ref 對象。

注意:

ref 對象內容發生變化時useRef 並_不會_通知你。變動 .current 屬性不會引起組件從新渲染。若是想要在 React 綁定或解綁 DOM 節點的 ref 時運行某些代碼,則須要使用回調 ref 來實現。

6.useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 參數:
  1. 一個函數,執行一些複雜的計算操做
  2. 一個數組,是第一個參數執行的依賴值組成的數組
  • 返回值:一個memoized值
  • 注意點:

    • 把「建立」函數和依賴項數組做爲參數傳入 useMemo,它僅會在某個依賴項改變時才從新計算 memoized 值。這種優化有助於避免在每次渲染時都進行高開銷的計算
    • 記住,傳入 useMemo 的函數會在渲染期間執行(useEffect是在渲染以後才執行)請不要在這個函數內部執行與渲染無關的操做,諸如反作用這類的操做屬於 useEffect 的適用範疇,而不是 useMemo。
    • 若是沒有提供依賴項數組,useMemo在每次渲染時都會計算新的值。
    • 你能夠把 useMemo 做爲性能優化的手段,但不要把它當成語義上的保證。未來,React 可能會選擇「遺忘」之前的一些 memoized 值,並在下次渲染時從新計算它們,好比爲離屏組件釋放內存。先編寫在沒有 useMemo 的狀況下也能夠執行的代碼 —— 以後再在你的代碼中添加 useMemo,以達到優化性能的目的。
    • 依賴項數組不會做爲參數傳給「建立」函數。雖然從概念上來講它表現爲:全部「建立」函數中引用的值都應該出如今依賴項數組中。將來編譯器會更加智能,屆時自動建立數組將成爲可能。
  • 與useEffect的區別:

    • useMemo是在組件渲染的時候執行,而useEffect是在組件渲染完以後延遲執行
    • 不要在傳入useMemo的函數中執行與渲染無關的操做,諸如反作用之類的操做屬於useEffect的適用範圍,而不是useMemo

補充:反作用

在計算機科學中,函數反作用指當調用函數時,除了返回函數值以外,還對主調用函數產生附加的影響。例如修改全局變量(函數外的變量)或修改參數。

對於運行在瀏覽器的JS來講,反作用包括且不限於:

  • console.log( )
  • 操做DOM
  • http請求
  • 修改全局變量
  • ...

5、建立本身的Hook

上例的Hooks代碼還能夠封裝起來,變成一個自定義的Hook,便於共享。

const usePerson = (personId) => {
  const [loading, setLoading] = useState(true);
  const [person, setPerson] = useState({});
  useEffect(() => {
    setLoading(true);
    fetch(`https://swapi.co/api/people/${personId}/`)
      .then(response => response.json())
      .then(data => {
        setPerson(data);
        setLoading(false);
      });
  }, [personId]);  
  return [loading, person];
};

上面代碼中,usePerson()就是一個自定義的Hook。

Person組件就改用這個新的鉤子,引入封裝的邏輯。

const Person = ({ personId }) => {
  const [loading, person] = usePerson(personId);

  if (loading === true) {
    return <p>Loading ...</p>;
  }

  return (
    <div>
      <p>You're viewing: {person.name}</p>
      <p>Height: {person.height}</p>
      <p>Mass: {person.mass}</p>
    </div>
  );
};

本文未完持續更新中...

相關文章
相關標籤/搜索