醜話說在前面
強烈建議至少刷一遍 《官方文檔》,反覆研讀 《Hooks FAQ》
這裏主要以本人關注點聚合,方便理解用於實踐
如下是上一代標準寫法類組件的缺點,也正是hook要解決的問題css
設計目的html
Function Component中每次Render都會造成一個快照並保留下來,這樣就確保了狀態可控,hook默認每次都更新,會致使重複請求等一系列問題,若是給[]就會一塵不變,所以用好hooks最重要就是學會控制它的變化react
在大部分狀況下咱們只要遵循 React 的默認行爲,由於 React 只更新改變了的 DOM 節點,不太重新渲染仍然花費了一些時間,除非它已經慢到讓人注意了git
基於上面的兩點,咱們一般的解決方案是:github
傳統上認爲,在 React 中使用內聯函數對性能的影響,與每次渲染都傳遞新的回調會如何破壞子組件的 shouldComponentUpdate 優化有關, 使用useCallback緩存函數引用,再傳遞給通過優化的並使用引用相等性去避免非必要渲染的子組件時,它將很是有用編程
const Button = React.memo((props) => { // 你的組件 }, fn);// 也能夠自定義比較函數
function Table(props) { // ⚠️ createRows() 每次渲染都會被調用 const [rows, setRows] = useState(createRows(props.count)); // ... // ✅ createRows() 只會被調用一次 const [rows, setRows] = useState(() => createRows(props.count)); // ... }
function Image(props) { // ⚠️ IntersectionObserver 在每次渲染都會被建立 const ref = useRef(new IntersectionObserver(onIntersect)); // ... } function Image(props) { const ref = useRef(null); // ✅ IntersectionObserver 只會被惰性建立一次 function getObserver() { if (ref.current === null) { ref.current = new IntersectionObserver(onIntersect); } return ref.current; } // 當你須要時,調用 getObserver() // ... }
useEffect(function persistForm() { if (name !== '') { localStorage.setItem('formData', name); } });
useEffect(() => { document.title = "Hello, " + name; }, [name]); // 以useEffect爲示例,適用於全部hook
直到 name 改變時的 Rerender,useEffect 纔會再次執行,保證了性能且狀態可控segmentfault
useEffect(() => { const id = setInterval(() => { setCount(count + 1); }, 1000); return () => clearInterval(id); }, [count]);// 以useEffect爲示例,適用於全部hook
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
實際應用場景每每不是一個hook能搞定的,長篇大論未必說的清楚,直接上例子(來源於官網摘抄,網絡收集,自我總結)數組
【將更新與動做解耦】-【useEffect,useReducer,useState】瀏覽器
該函數將接收先前的 state,並返回一個更新後的值緩存
useEffect(() => { const id = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(id); }, []);
import React, { useReducer, useEffect } from "react"; const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; if (action.type === 'tick') { return { count: count + step, step }; } else if (action.type === 'step') { return { count, step: action.step }; } else { throw new Error(); } } export default function Counter() { const [state, dispatch] = useReducer(reducer, initialState); const { count, step } = state; console.log(count); useEffect(() => { const id = setInterval(() => { dispatch({ type: 'tick' }); }, 1000); return () => clearInterval(id); }, [dispatch]); return ( <> <h1>{count}</h1> <input value={step} onChange={e => { dispatch({ type: 'step', step: Number(e.target.value) }); }} /> </> ); }
【經過 context 往下傳一個 dispatch 函數】-【createContext,useReducer,useContext】
/**index.js**/ import React, { useReducer } from "react"; import Count from './Count' export const StoreDispatch = React.createContext(null); const initialState = { count: 0, step: 1, }; function reducer(state, action) { const { count, step } = state; switch (action.type) { case 'tick': return { count: count + step, step }; case 'step': return { count, step: action.step }; default: throw new Error(); } } export default function Counter() { // 提示:`dispatch` 不會在從新渲染之間變化 const [state, dispatch] = useReducer(reducer, initialState); return ( <StoreDispatch.Provider value={dispatch}> <Count state={state} /> </StoreDispatch.Provider> ); } /**Count.js**/ import React, { useEffect,useContext } from 'react'; import {StoreDispatch} from '../index' import styles from './index.css'; export default function(props) { const { count, step } = props.state; const dispatch = useContext(StoreDispatch); useEffect(() => { const id = setInterval(() => { dispatch({ type: 'tick' }); }, 1000); return () => clearInterval(id); }, [dispatch]); return ( <div className={styles.normal}> <h1>{count}</h1> <input value={step} onChange={e => { dispatch({ type: 'step', step: Number(e.target.value) }); }} /> </div> ); }
【層層依賴,各自管理】-【useEffect,useCallback,useContext】
function App() { const [count, setCount] = useState(1); const countRef = useRef();// 在組件生命週期內保持惟一實例,可穿透閉包傳值 useEffect(() => { countRef.current = count; // 將 count 寫入到 ref }); // 只有countRef變化時,纔會從新建立函數 const callback = useCallback(() => { const currentCount = countRef.current //保持最新的值 console.log(currentCount); }, [countRef]); return ( <Parent callback={callback} count={count}/> ) } function Parent({ count, callback }) { // count變化纔會從新渲染 const child1 = useMemo(() => <Child1 count={count} />, [count]); // callback變化纔會從新渲染,count變化不會 Rerender const child2 = useMemo(() => <Child2 callback={callback} />, [callback]); return ( <> {child1} {child2} </> ) }
function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }); return ref.current; }
function useUpdate(fn) { const mounting = useRef(true); useEffect(() => { if (mounting.current) { mounting.current = false; } else { fn(); } }); }
function useIsMounted(fn) { const [isMount, setIsMount] = useState(false); useEffect(() => { if (!isMount) { setIsMount(true); } return () => setIsMount(false); }, []); return isMount; }
function useInertRef(obj) { // 傳入一個實例 new IntersectionObserver(onIntersect) const ref = useRef(null); if (ref.current === null) { // ✅ IntersectionObserver 只會被惰性建立一次 ref.current = obj; } return ref.current; }
參考文章