React Hook


React Hook是React16.8.0引入的。使可以在不引入class的情況下,可以使用state和其他React特性。

hooks本質上是一些函數。

1. 為什么引入Hook?

1. hooks中的useEffect可以解決class中各邏輯在生命周期函數中管理混亂的問題。

2.hooks中的自定義Hook使得可以不修改組件的結構的基礎上,靈活的復用組件邏輯。

3.class組件不能很好的壓縮,並且熱重載不穩定。不利於組件優化。使用Hook的函數組件不存在這些問題

2. Hook的規則

1. 只能在函數組件中使用hooks

    類組件中無效。

2. 只能在函數最外層使用

   不能用於if,for等語句中,也不能用於普通js函數中。因為ReactHook通過調用順序確定對應的state等對應的hook方法。使用語句等會改變順序。

3. 只能在以名字use開頭的自定義Hook中使用

3. useState

接受一個參數作為初始值,參數可以是常量,也可以是一個返回值的函數。

初始值如果是一個函數,在初次渲染執行;如果是一個函數的執行,每次渲染都會執行

//  初始值是一個函數
const [count, setCount] = useState(function init(){......;return ..})
// 初始值是一個函數調用
const [count, setCount] = useState(init())

以數組形式返回兩個值,第一個是狀態值(初次渲染創建變量),一個是改變狀態的函數。

例如:

const [count, setCount] = useState(0);
// 0是初始值

修改狀態的函數(如setCount)和class中的setState類似,可以接受兩種參數:

setCount(fn/exp)

1)表達式

可以是常量, 也可以是一個帶state值的表達式

<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setCount(count+1)}>-</button>

2)函數

<button onClick={() => setCount(prevCount => prevCount + 1)}>-</button>

⚠️如果修改后的狀態不變,重復調用只會刷新一次。

<button onClick={() => setCount(count)}>+</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button> 
<!-- 每次單擊第一個按鈕會渲染一次,繼續單擊不繼續渲染;
       但是如果再單擊第二個按鈕,切換到第一個按鈕,還是會渲染一次-->

模擬getDerivedStateFromProps

在render前進行setState更新

應用:在異步函數中獲取state的值

在setTimeout等異步函數中獲取的狀態值,是調用setTimeout方法時的狀態值,不是執行時的狀態值。

function Example() {
  const [count, setCount] = useState(0);

  function handleAlertClick() {
    // count的取值形成了一個Example的閉包,每次刷新都是一個新的閉包
    setTimeout(() => {
      alert('You clicked on: ' + count);
    }, 3000);
  }
  
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
      <button onClick={handleAlertClick}>Show alert</button>
    </div>
  );
}
ReactDOM.render(<Example/>, window.root);

4. useEffect

它相當於是componentDidMount,componentDidUpdate,componentWillUnMount三個生命周期的合成體。

它接受兩個參數, 第一個是一個函數(effect),第二個參數可選,是一個數組(第一個函數中用到的可變數據集合)。

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 僅在 count 更改時更新;每次渲染,第一個函數都相當於一個新生成的函數

1. 執行時機

和生命周期不同的是,componentDidMount,componentDidUpdate是DOM渲染完成,屏幕更新前觸發執行,會阻礙屏幕渲染;useEffect中的effect函數(非變更DOM的操作)在瀏覽器完成布局和繪制后,會延遲執行(異步,但是肯定在下一次渲染前執行),不會阻礙屏幕更新。但是如果是變更DOM的操作,需要同步執行。

1) 如果只在初次加載的時候運行,模擬componentDIdMount

  useEffect(() => { 
    //TODO
  }, []);

2)想要只在更新的時候運行,模擬componentDidUpdata

//使用useRef()存儲的實例變量作為是否是第一次執行的標識
  const [count, setCount] = useState(0);
  const first = useRef(true);
  useEffect(() => { 
    if (first.current) {
      first.current = false;
    } else {
      console.error(count);
      document.title = `You clicked ${count} times!`;
    }
  }, [count]);

2. 執行副作用

如果副作用需要取消,在傳入useEffect的函數中返回一個函數,在返回的函數中執行取消操作,該返回函數會在組件卸載的時候執行。

 useEffect(() => {
  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
  return () => {
    ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
  };
}, [props.friend.id]); // 僅在 props.friend.id 發生變化時,重新訂閱

3. 性能優化

第二個參數傳入的變量,會作為比較的依據。如果變量值不變,就會跳過本次副作用的執行。

作為參數的值是在組件域(即函數組件對應的函數域)中的可變量(props/state),而且在useEffect中使用。

5. useContext

使函數組件可以具有和Class組件中的contextType屬性(使可以通過this.context訪問值)一樣的功能。

用法和Class中contextType基本一致。接受一個context對象作為參數,返回最近的Provider提供的值。

示例代碼:

const ThemeContext = React.createContext('dark');
function App() {
  const [theme, setTheme] = useState('dark');
  return (
    <ThemeContext.Provider value={theme}>
      <Child />
      <button onClick={() => setTheme(preTheme => preTheme === 'dark' ? 'light' : 'dark')}>切換主題</button>
    </ThemeContext.Provider>
  );
}
function Child() {
  const value = useContext(ThemeContext);
  return (
    <div>{value}</div>
  );
}

6.useReducer

可以看做useState的復合版。當應用中需要多個狀態時,一般需要調用多次useState(便於組合邏輯)。隨着應用逐漸擴展,會越來越復雜,此時可以使用useReducer。

const [state, dispatch] = useReducer(reducer, initialArg, init);
// 第三個值可以不傳,是一個接受第二個參數,返回一個初始值的函數

示例:

import React, { useReducer } from 'react';
import ReactDOM from 'react-dom';

function App() {
  function reducer(state, action) {
    switch(action.type) {
    case 'add':
      return state + 1;
    case 'minuse':
      return state - 1;
    case 'reset':
      return 0;
    default: 
      throw new Error('error');
    }
  }
  const [count, dispatch] = useReducer(reducer, 0);
  return (
    <div>
      <p>You clicked {count} times!</p>
      <button onClick={() => dispatch({type: 'reset'})}>Reset</button>
      <button onClick={() => dispatch({type: 'add'})}>+</button>
      <button onClick={() => dispatch({type: 'minuse'})}>-</button>    
    </div>
  );
}

模擬forceUpdate()

function Example() {
  const [count, forceUpdate] = useReducer(x=>x+1, 0);
  return (
    <div>
      <button onClick={() => forceUpdate()}>Click me</button>
    </div>
  );
}

7. useMemo

本質上是memorization技術。用於優化在渲染階段,計算數據的函數。

它接受一個計算函數作為第一參數;第二個是個依賴項數組。

返回一個值,該值是第一個計算函數的返回結果。

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

示例:

import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import React, {useState, useMemo, useEffect} from 'react';

function App() {
  const inputRef = React.createRef();
  const [data, setData] = useState([]);
  useEffect(() => {
    inputRef.current.value = null;
  });
  return ( 
    <div>
      <input type='text' ref={inputRef}/>
      <button onClick={() => setData(data.concat(inputRef.current.value))}>添加</button>
      <Child data={data}/>
    </div>
  );
}
function Child(props) {
  const [filterText, setFilterText] = useState('');
  const lists = useMemo(() => props.data.filter(i => i && i.includes(filterText)), [filterText, props]);
  return(
    <div>
      <input onChange={(e) => setFilterText(e.target.value)} />
      <ul>
        {
          lists.map((i,inx) => <li key={inx}>{i}</li>)
        }
      </ul>
    </div>
  );
}
Child.propTypes = {
  data: PropTypes.array
};

ReactDOM.render(<App/>, window.root);

8. useCallback

和 useMemo基本相同;

不同點在於它返回一個函數,這個和'memorize-one'的功能相同。

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

代碼示例:

// 用useCallback替換上面useMemo的示例
  const listFilter = useCallback(() => props.data.filter(i => i && i.includes(filterText)), [filterText, props]);
  const lists = listFilter(filterText, props);

該方法比useRef的優點是,因為返回一個函數,它更容易抽取邏輯,形成自定義Hook

function MeasureDOM() {
  const [rect, measureRef] = useClientRec();
  return (
    <>
      <h1 ref={measureRef}>Hello, world</h1>
      {rect && <h2>The above header is {Math.round(rect.height)}px tall</h2>}
    </>
  );
}
function useClientRec() {
  const [rect, setRect] = useState();
  // 此時ref屬性指向一個回調函數
  const ref = useCallback(node => {
    if (node !== null) {
      setRect(node.getBoundingClientRect());
    }
  }, []);
  return [rect, ref];
}

9 .useRef

useRef模擬的是Class組件中的實例屬性。可以在函數組件中很方便的保存任何可變值,不引起渲染。

// initialValue是current屬性的初始值
const refContainer = useRef(initialValue);

使用useRef和React.createRef的區別是:

1)useRef返回一個refContainer對象({current: XXX}),並且重新渲染,訪問的都是同一個對象。

2)React.createRef在函數組件中是每次重新渲染,相當於重新創建了一個對象,每次都是一個新對象。

示例:

// 單擊一次后,inputRef1為1, inputRef2仍然為0
import React, {useState,useEffect, useRef} from 'react';
import ReactDOM from 'react-dom';

function App() {
  const [count, setCount] = useState(0);
  const inputRef1 = useRef(null);
  const inputRef2 = React.createRef();
  useEffect(() => {
    if(count === 1) {
      inputRef1.current.value = count;
      inputRef2.current.value = count;
    }
  }, [count,inputRef1,inputRef2]);
  return (
    <div>
      <input ref={inputRef1} /><br/>
      <input ref={inputRef2} /><br/>
      inputRef1:{inputRef1.current && inputRef1.current.value || 0}<br/>
      inputRef2:{inputRef2.current && inputRef2.current.value || 0}<br/>
      <button onClick={() => setCount(count+1)}>Add</button>
    </div>
  );
}

ReactDOM.render(<App/>, window.root);

應用: 獲取prevState或者prevProps

// 考慮到通用性,將其提取到自定義Hook中usePrevious
function App() {
  const [count, setCount] = useState(0);
  const previous = usePrevious(count);
  return (
    <div>
      <p>previous: {previous}</p>  
      <p>current: {count}</p>  
      <button onClick={() => {setCount(count+1);}}>Add</button>
    </div>
  );
}
// 自定義Hook-獲取prevState/prevProps
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

10. useImperativeHandle

該方法可以自定義(默認是DOM節點)子組件暴露給父組件的內容。

useImperativeHandle(ref, createHandle, [deps])

該方法基於Refs轉發,需要和React.forwardRef結合使用。

示例:

import React, { useRef,useEffect, useImperativeHandle } from 'react';
import ReactDOM from 'react-dom';

function App() {
  const inputRef = useRef(null);
  useEffect(() => {
    // 在父組件查看獲取到的子組件中傳遞的ref的內容
    console.log(inputRef); // {current: {focus: () => {...}}}
  });
  return (
    <div>
      <FancyInputWithRef ref={inputRef} />
    </div>
  );
}
// 子組件
function FancyInput(props, ref) {
  // 自定義ref暴露的內容
  useImperativeHandle(ref, () => ({
    focus: () => ref.current.focus()
  }), [ref]);
  return(
    <div>
      <input ref={ref} />
    </div>
  );
}
// 轉發ref
var FancyInputWithRef = React.forwardRef(FancyInput);

ReactDOM.render(<App/>, window.root);

11. useLayoutEffect

和useEffect功能基本相同。

主要區別是它的執行時機和componentDidMount,componentDidUpdate相同,都在瀏覽器繪制之前執行。

建議: 先使用useEffect, 有問題再用useLayoutEffect

12. useDebugValue

用於調試時在自定義Hook中添加標簽。

useDebugValue(date, date => date.toDateString());

示例:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // 在開發者工具中的這個 Hook 旁邊顯示標簽
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM