React Hooks

JOKEthel發表於2020-12-27

是什麼

Hook是React16.8的新增特性,可以在不使用類元件的情況下使用state及React的其它特性。

七大Hook

  1. useState 狀態
  2. useEffect 鉤子,還有它的兄弟 useLayoutEffect
  3. useContext 上下文
  4. useReducer 代替 Redux
  5. useMemo 快取,還有它的小弟 useCallback
  6. useRef 引用
  7. 自定義 Hook 混合

useState

基本語法

const [X, setX] = React.useState(X的初始值)

簡單示例

function App() {
  const [user,setUser] = useState({name:'Varian', age: 18})
  const onClick = ()=>{
    setUser({
      name: 'Janye'
    })
  }
  return (
    <div className="App">
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      <button onClick={onClick}>Click</button>
    </div>
  );
}

我們會發現,點選按鈕之後,age 消失了,而我們明明只改了 name 呀,為什麼呢?

簡單來說就是前後是兩個完全不相關的物件。

展開講的話 React 在資料變化時會建立新的虛擬 DOM 物件,然後將這個虛擬 DOM 物件跟原虛擬 DOM 進行一個 DOM Diff,得到一個最小的變化過程 Patch,並把這個 Patch 渲染到頁面上,Diff 的時候發現新物件沒有 age 這個屬性,於是就把它刪除了。

於是在使用 useState 的時候我們需要注意兩個地方:

  1. 想要原來的值,必須在 setX 裡先進行復制,類似這樣setUser({...user, name: 'Janye'})
  2. setX(obj) 時,obj 的地址必須改變

useEffect

useEffect 的作用主要是用來解決函式元件如何像類元件一樣使用生命週期鉤子的問題。

它有三個使用場景:

  1. 作為 componentDidMount 使用,第二個引數為空陣列 []
  2. 作為 componentDidUpdate 使用,第二個引數為指定依賴
  3. 作為 componentWillUnmount 使用,通過 return

簡單示例

const BlinkyRender = () => {
  const [value, setValue] = useState(0);

  useEffect(() => {
    document.querySelector('#x').innerText = `value: 1000`
  }, [value]);

  return (
    <div id="x" onClick={() => setValue(0)}>value: {value}</div>
  );
};

ReactDOM.render(
  <BlinkyRender />,
  document.querySelector("#root")
);

它跟 useLayoutEffect 有什麼區別?

useEffect 在瀏覽器渲染完成後執行,useLayoutEffect 在瀏覽器渲染前執行,useLayoutEffect 總是比 useEffect 先執行。

為了使用者體驗(先渲染就能先看到),通常我們應該先用useEffect

useContext

如果我們想在元件之間共享狀態的話,可以使用 useContext

它的使用可以分為三個步驟:

  1. 使用C = createContext(initial) 建立上下文
  2. 使用<C.provider> 圈定作用域
  3. 在作用域內使用 useContext(C) 來使用上下文

簡單示例

const C = createContext(null);

function App() {
  console.log("App 執行了");
  const [n, setN] = useState(0);
  return (
    <C.Provider value={{ n, setN }}>
      <div className="App">
        <Baba />
      </div>
    </C.Provider>
  );
}

function Baba() {
  const { n, setN } = useContext(C);
  return (
    <div>
      我是爸爸 n: {n} <Child />
    </div>
  );
}

function Child() {
  const { n, setN } = useContext(C);
  const onClick = () => {
    setN(i => i + 1);
  };
  return (
    <div>
      我是兒子 我得到的 n: {n}
      <button onClick={onClick}>+1</button>
    </div>
  );
}

useReducer

如果要一句話解釋 useReducer 的話,它是用來代替 Redux 的,或者說,是一個加強版的 useState

使用上來說,一共有四步:

  1. 建立初始值 initialState
  2. 建立所有操作 reducer(state, action)
  3. 傳給 useReducer,得到讀和寫 API
  4. 呼叫 寫({type: '操作型別'})

簡單示例

const initial = {
  n: 0
};

const reducer = (state, action) => {
  if (action.type === "add") {
    return { n: state.n + action.number };
  } else if (action.type === "multi") {
    return { n: state.n * 2 };
  } else {
    throw new Error("unknown type");
  }
};

function App() {
  const [state, dispatch] = useReducer(reducer, initial);
  const { n } = state;
  const onClick = () => {
    dispatch({ type: "add", number: 1 });
  };
  const onClick2 = () => {
    dispatch({ type: "add", number: 2 });
  };
  return (
    <div className="App">
      <h1>n: {n}</h1>

      <button onClick={onClick}>+1</button>
      <button onClick={onClick2}>+2</button>
    </div>
  );
}

useMemo

基本語法:

useMemo(回撥函式, [依賴])

類似與 Vue 的計算屬性 computeduseMemo 具有快取,依賴改變才重新渲染的功能。

跟它的小弟 useCallback 的唯一區別是:useMemo可以快取所有物件,useCallback只能快取函式。

useCallback(x => log(x), [m]) 等價於 useMemo(() => x => log(x), [m])

useRef

主要作用是建立一個資料的引用,並讓這個資料在 render 過程中始終保持不變。

基本語法:

const count = useRef(0),讀取用 count.current

封裝 Echarts 時的例子

export function ReactEcharts(props) {
  const {option, loading} = props
  const container = useRef(null)
  const chart = useRef(null)

  useEffect(() => {
    const width = document.documentElement.clientWidth
    const c = container.current
    console.log(c)
    c.style.width = `${width - 20}px`
    c.style.height = `${(width - 20) * 1.2}px`
    chart.current = echarts.init(c, 'dark')

  }, []) // [] - mounted on first time

  useEffect(() => {
    chart.current.setOption(option)
  }, [option]) // when option change 類似 vue 的 watch

  useEffect(() => {
    if (loading) chart.current.showLoading()
    else chart.current.hideLoading()
  }, [loading])
  return (
    <div ref={container}/>
  )
}

自定義Hook

可以理解為我們可以把上面的 Hook 按照實際的需求混合起來,封裝成一個函式,給一個簡單示例:

const useList = () => {
  const [list, setList] = useState(null);
  useEffect(() => {
    ajax("/list").then(list => {
      setList(list);
    });
  }, []); // [] 確保只在第一次執行
  return {
    list: list,
    setList: setList
  };
};
export default useList;

相關文章