React Hooks實踐應(yīng)用

React-Hooks.png

React16.8 發(fā)布已經(jīng)很長世間, 這段時間項目不忙, 正好準(zhǔn)備使用 React Hooks 進(jìn)行重構(gòu)升級堂鲜。React Hooks 的特性是它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 的特性,下面來讓我們一起進(jìn)入 React Hooks的新特性實踐吧猾编!

React Hook 特性

  1. 完全可選的空免。 你無需重寫任何已有代碼就可以在一些組件中嘗試 Hook棚辽。但是如果你不想,你不必現(xiàn)在就去學(xué)習(xí)或使用 Hook疏橄。
  2. 100% 向后兼容的伟叛。 Hook 不包含任何破壞性改動。
  3. 現(xiàn)在可用淤击。 Hook 已發(fā)布于 v16.8.0

官方 10 種 React Hooks

1.useState

useState 可以讓我們在不編寫 class 的情況下使用 state,以此可以達(dá)到讓函數(shù)組建重新渲染

initialState 參數(shù)只會在組件的初始渲染中起作用匠抗,后續(xù)渲染時會被忽略。如果初始 state 需要通過復(fù)雜計算獲得污抬,則可以傳入一個函數(shù)汞贸,在函數(shù)中計算并返回初始的 state绳军,此函數(shù)只在初始渲染時被調(diào)用:

function useState<S = undefined>(): [
  S | undefined,
  Dispatch<SetStateAction<S | undefined>>
]

const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props)
  return initialState
})
const App = () => {
  const [count, setCount] = useState(0) // number 類型
  const [obj, setObject] = React.useState({
    count: 0,
    name: "alex",
  }) // object 類型
  const [todos, setTodos] = useState([{ text: "Learn Hooks" }]) // 數(shù)組類型

  return (
    <div>
      <p>current value {obj.count}</p>
      <button onClick={() => setCount({ ...obj, count: count + 1 })}>+</button>
      <button onClick={() => setCount({ ...obj, count: count + 1 })}>-</button>
    </div>
  )
}

setState 可以局部的更新,但是 useState 必須把真?zhèn)€對象修改后的只丟進(jìn)去來進(jìn)行更新,useState 覆蓋式更新 setState 調(diào)合 Object.assign()

const App = () => {
  const [name, setName] = React.useState('jack')
  return (
    <div>
      <p>{name}</p>
      <button onClick={() => setName('rose')}>SET NEW NAME</button>
    </div>
  )
}

const AppChild = React.memo({{ data }:{ data: string }}) => {
  const [name, setName] = React.useState(data)
  return (
    <div>{name}---{data}</div>
  )
}

const AppChild = React.memo({{ data }:{ data: string }}) => {
  const [name, setName] = React.useState(data)

  React.useEffect(() => {
    setName(data)
  }, [data])

  return (
    <div>{name}---{data}</div>
  )
}

initialState 參數(shù)只會在組件的初始渲染中起作用, 可以使用 useEffect

2、9.useEffect矢腻、 useLayoutEffect

useEffect:

該 Hook 接收一個包含命令式门驾、且可能有副作用代碼的函數(shù)。

React 會等待瀏覽器完成畫面渲染之后才會延遲調(diào)用 useEffect多柑,因此會使得處理額外操作很方便奶是。

在函數(shù)組件主體內(nèi)(這里指在 React 渲染階段)改變 DOM、添加訂閱竣灌、設(shè)置定時器聂沙、記錄日志以及執(zhí)行其他包含副作用的操作都是不被允許的,因為這可能會產(chǎn)生莫名其妙的 bug 并破壞 UI 的一致性初嘹。

使用 useEffect 完成副作用操作及汉。賦值給 useEffect 的函數(shù)會在組件渲染到屏幕之后執(zhí)行。你可以把 effect 看作從 React 的純函數(shù)式世界通往命令式世界的逃生通道屯烦。

useLayoutEffect:
其函數(shù)簽名與 useEffect 相同坷随,但它會在所有的 DOM 變更之后同步調(diào)用 effect∽す辏可以使用它來讀取 DOM 布局并同步觸發(fā)重渲染温眉。在瀏覽器執(zhí)行繪制之前,useLayoutEffect 內(nèi)部的更新計劃將被同步刷新迅脐。

useLayoutEffect 與 componentDidMount芍殖、componentDidUpdate 的調(diào)用階段是一樣的。但是谴蔑,我們推薦你一開始先用 useEffect,只有當(dāng)它出問題的時候再嘗試使用 useLayoutEffect.

function useEffect(effect: EffectCallback, deps?: DependencyList): void
let timer = null
const App = () => {
  const [count, setCount] = React.useState(0)

  React.useEffect(() => {
    document.title = "componentDidMount" + count
  }, [count])

  React.useEffect(() => {
    timer = setInterval(() => {
      setCount((prevCount) => prevCount + 1)
    }, 1000)
    // 一定注意下這個順序:
    // 告訴react在下次重新渲染組件之后龟梦,同時是下次執(zhí)行上面setInterval之前調(diào)用
    return () => {
      document.title = "componentWillUnmount"
      clearInterval(timer)
    }
  }, [])

  return (
    <div>
      Count: {count}
      <button onClick={() => clearInterval(timer)}>clear</button>
    </div>
  )
}
  1. 比如第一個 useEffect 中,理解起來就是一旦 count 值發(fā)生改變隐锭,則修改 documen.title 值.
  2. 而第二個 useEffect 中傳遞了一個空數(shù)組[],這種情況下只有在組件初始化或銷毀的時候才會觸發(fā),用來代替 componentDidMount 和 componentWillUnmount 慎用.
  3. 還有另外一個情況,就是不傳遞第二個參數(shù),也就是 useEffect 只接收了第一個函數(shù)參數(shù),代表不監(jiān)聽任何參數(shù)變化.每次渲染 DOM 之后,都會執(zhí)行 useEffect 中的函數(shù),類似替代 componentDidUpdate.

注意點

  1. useEffect 里面使用到的 state 的值, 固定在了 useEffect 內(nèi)部, 不會被改變计贰,除非 useEffect 刷新钦睡,重新固定 state 的值
  2. useEffect 不能被判斷包裹
if (x < y) {
  React.useEffect(() => {
    document.title = "hello world"
  })
}
  1. 不能被打斷, 在函數(shù)中提前使用 return, 會提前結(jié)束躁倒,不會去執(zhí)行 return 之后的代碼
const App = () => {
  return <div>hello world</div>

  React.useEffect(() => {
    document.title = "hello world"
  })
}

3.useContext

接收一個 context 對象(React.createContext 的返回值)并返回該 context 的當(dāng)前值荞怒。當(dāng)前的 context 值由上層組件中距離當(dāng)前組件最近的 <MyContext.Provider> 的 value prop 決定。

當(dāng)組件上層最近的 <MyContext.Provider> 更新時秧秉,該 Hook 會觸發(fā)重渲染褐桌,并使用最新傳遞給 MyContext provider 的 context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate象迎,也會在組件本身使用 useContext 時重新渲染荧嵌。

如果你在接觸 Hook 前已經(jīng)對 context API 比較熟悉呛踊,那應(yīng)該可以理解,useContext(MyContext) 相當(dāng)于 class 組件中的 static contextType = MyContext 或者 <MyContext.Consumer>啦撮。

useContext(MyContext) 只是讓你能夠讀取 context 的值以及訂閱 context 的變化谭网。你仍然需要在上層組件樹中使用 <MyContext.Provider> 來為下層組件提供 context。

function useContext<T>(
  context: Context<T> /*, (not public API) observedBits?: number|boolean */
): T
const colorContext = React.createContext("gray")

const Bar = () => {
  // useContext 的參數(shù)必須是 context 對象本身
  const color = React.useContext(colorContext)
  return <div>{color}</div>
}

const Foo = () => <Bar />

const App = () => {
  return (
    <colorContext.Provider value={"red"}>
      <Foo />
    </colorContext.Provider>
  )
}

useContext 可以解決 Consumer 多狀態(tài)嵌套的問題

4.useReducer

useState 的替代方案赃春。它接收一個形如 (state, action) => newState 的 reducer愉择,并返回當(dāng)前的 state 以及與其配套的 dispatch 方法。(如果你熟悉 Redux 的話织中,就已經(jīng)知道它如何工作了薄辅。)

在某些場景下,useReducer 會比 useState 更適用抠璃,例如 state 邏輯較復(fù)雜且包含多個子值站楚,或者下一個 state 依賴于之前的 state 等。并且搏嗡,使用 useReducer 還能給那些會觸發(fā)深更新的組件做性能優(yōu)化窿春,因為你可以向子組件傳遞 dispatch 而不是回調(diào)函數(shù) 。

function useReducer<R extends Reducer<any, any>>(
  reducer: R,
  initialState: ReducerState<R>,
  initializer?: undefined
): [ReducerState<R>, Dispatch<ReducerAction<R>>]
interface IState{
  count: number
}

interface IAction{
  type: 'increment' | 'decrement'
}

const initialState: IState = { count: 0 }

const init = (initialCount) => {
  return { count: 0 }
}

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

const App = () => {
  const [state, dispatch] = React.useReducer(reducer, initialState)

  // 惰性傳值
  const [state, dispatch] = React.useReducer(reducer, initialCount, init)
  return (
    <>
      <p>{state.count}</p>
      <button onClick={() => dispatch('decrement')}>-</button>
      <button onClick={() => dispatch('increment')}>+</button>
    <>
  )
}

useReducer 可以直接傳值,也可以惰性傳值,惰性傳值可以達(dá)到重置,直接訪問外部 Reducer
如果 Reducer Hook 的返回值與當(dāng)前 state 相同采盒,React 將跳過子組件的渲染及副作用的執(zhí)行

5旧乞、6 useCallback、useMemo

useMemo 返回一個 memoized 值
useCallback 返回一個 memoized 回調(diào)函數(shù)磅氨。
當(dāng)傳入的依賴項改變時,函數(shù)才會執(zhí)行更新,當(dāng)你把回調(diào)函數(shù)傳遞給經(jīng)過優(yōu)化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子組件時尺栖,它將非常有用

function useCallback<T extends (...args: any[]) => any>(
  callback: T,
  deps: DependencyList
): T

function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T

useCallbacl(fn, deps) === useMemo(() => fn, deps)
const memoizedCallback = useCallback(() => {
  doSomething(a, b)
}, [a, b])

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

useMemo 和 useCallback 實踐

const App = () => {
  const [count, setCount] = React.useState(0)
  const [name, setName] = React.useState("jack")
  const [text, setText] = React.useState("hello world")
  // 每次setCount時都會在內(nèi)存中生成一個新的地址去存儲name,所以發(fā)現(xiàn)應(yīng)用的地址不同,AppChild會重新渲染
  const data = {
    name: name === "jack" ? "rose" : "rose",
  }
  // 使用useMemo暫存name的值,只有當(dāng)name變化時才data才會在內(nèi)存中生成一個新的地址去存儲name,在去重新渲染
  const data = React.useMemo(() => {
    return {
      name: name === "jack" ? "rose" : "jack",
    }
  }, [name])

  // 同理
  const handleOnChange = (e: Event) => {
    setText(e.target.value)
  }

  const handleOnChange = React.useCallback((e: Event) => {
    setText(e.target.value)
  })

  return (
    <div>
      <p>{count}</p>
      <p>{name}</p>
      <button onClick={() => setCount(count - 1)}>decrement</button>
      <button onClick={() => setName("jack")}>set new name</button>
      <AppChild text={text} data={data} onChange={handleOnChange} />
    </div>
  )
}

interface IFunctionProps {
  text: string
  data: { name: string }
  onChange: (e: Event) => void
}

// React.memo 淺比較
const AppChild = React.memo(({ data, text, onChange }: IFunctionProps) => {
  return (
    <div>
      <p>{data.name}</p>
      <input value={text} onChange={onChange} />
    </div>
  )
})

useCallback 作用于緩存一個函數(shù),useMemo 作用于緩存計算后的值

7 useRef

useRef 返回一個可變的 ref 對象烦租,其 .current 屬性被初始化為傳入的參數(shù)(initialValue)延赌。返回的 ref 對象在組件的整個生命周期內(nèi)保持不變。

useRef 就像是可以在其 .current 屬性中保存一個可變值的"盒子"

ref 這是一個普通 Javascript 對象叉橱。而 useRef() 和自建一個 {current: ...} 對象的唯一區(qū)別是挫以,useRef 會在每次渲染時返回同一個 ref 對象。

當(dāng) ref 對象內(nèi)容發(fā)生變化時窃祝,useRef 并不會通知你掐松。變更 .current 屬性不會引發(fā)組件重新渲染。如果想要在 React 綁定或解綁 DOM 節(jié)點的 ref 時運行某些代碼粪小,則需要使用回調(diào) ref 來實現(xiàn)大磺。

function useRef<T>(initialValue: T): MutableRefObject<T>

useEffect 里面的 state 的值,是固定的, 可以使用 useRef,作為全局變量

const App = () => {
  const [count, setCount] = React.useState(0)
  const countRef = React.useRef(0)
  React.useEffect(() => {
    const timer = setInterval(() => {
      setCount(++countRef.current)
    }, 1000)
    return () => clearInterval(timer)
  }, [])
  return <div>{count}</div>
}

基本用法探膊,用來操作 dom

function TextInputWithFocusButton() {
  const inputEl = useRef(null)
  const onButtonClick = () => {
    // `current` 指向已掛載到 DOM 上的文本輸入元素
    inputEl.current.focus()
  }
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  )
}

8 useImperativeHandle

useImperativeHandle 可以讓你在使用 ref 時自定義暴露給父組件的實例值杠愧。在大多數(shù)情況下,應(yīng)當(dāng)避免使用 ref 這樣的命令式代碼突想。useImperativeHandle 應(yīng)當(dāng)與 forwardRef 一起使用

function useImperativeHandle<T, R extends T>(
  ref: Ref<T> | undefined,
  init: () => R,
  deps?: DependencyList
): void
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

渲染 <FancyInput ref={inputRef} /> 的父組件可以調(diào)用 inputRef.current.focus()

10.useDebugValue

useDebugValue 可用于在 React 開發(fā)者工具中顯示自定義 hook 的標(biāo)簽殴蹄。

function useDebugValue<T>(value: T, format?: (value: T) => any): void

這個我用的比較少,就暫不贅敘究抓、有興趣可以查看官網(wǎng)的 demo

11.自定義 Hooks 定時器

const useIntervalTime = (callback, delay) => {
  React.useEffect(() => {
    if (delay !== null) {
      const timer = setInterval(callback, delay)
      return () => clearInterval(timer)
    }
  }, [delay])
}

const App = () => {
  const [count, setCount] = React.useState(0)

  useIntervalTime(() => {
    setCount(count + 1)
  }, 2000)

  return <div>{count}</div>
}

此時你會發(fā)現(xiàn) count 顯示始終是 0,但是 setInterval 會一直引用舊的狀態(tài),count 值始終為 0
可以改為 setCount(++count),每一次定時到點去獲取最新的 count 賦值給 count,再去更新渲染

同樣也可以使用 useRef 全局作用域

const useIntervalTime = (callback, delay) => {
  const saveCallback = React.useRef(0)

  React.useEffect(() => {
    saveCallback.current = callback
  })

  React.useEffect(() => {
    if (delay !== null) {
      const timer = setInterval(() => saveCallback.current(), delay)
      return () => clearInterval(timer)
    }
  }, [delay])
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末袭灯,一起剝皮案震驚了整個濱河市刺下,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稽荧,老刑警劉巖橘茉,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異姨丈,居然都是意外死亡畅卓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進(jìn)店門蟋恬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來翁潘,“玉大人,你說我怎么就攤上這事歼争“萋恚” “怎么了?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵沐绒,是天一觀的道長俩莽。 經(jīng)常有香客問我,道長乔遮,這世上最難降的妖魔是什么扮超? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮蹋肮,結(jié)果婚禮上出刷,老公的妹妹穿的比我還像新娘。我一直安慰自己括尸,他們只是感情好巷蚪,可當(dāng)我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著濒翻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪啦膜。 梳的紋絲不亂的頭發(fā)上有送,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天,我揣著相機(jī)與錄音僧家,去河邊找鬼雀摘。 笑死,一個胖子當(dāng)著我的面吹牛八拱,可吹牛的內(nèi)容都是我干的阵赠。 我是一名探鬼主播涯塔,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼清蚀!你這毒婦竟也來了匕荸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤枷邪,失蹤者是張志新(化名)和其女友劉穎榛搔,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體东揣,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡践惑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘶卧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尔觉。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芥吟,靈堂內(nèi)的尸體忽然破棺而出侦铜,到底是詐尸還是另有隱情,我是刑警寧澤运沦,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布泵额,位于F島的核電站,受9級特大地震影響携添,放射性物質(zhì)發(fā)生泄漏嫁盲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一烈掠、第九天 我趴在偏房一處隱蔽的房頂上張望羞秤。 院中可真熱鬧,春花似錦左敌、人聲如沸瘾蛋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哺哼。三九已至,卻和暖如春叼风,著一層夾襖步出監(jiān)牢的瞬間取董,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工无宿, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留茵汰,地道東北人。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓孽鸡,卻偏偏與公主長得像蹂午,于是被迫代替她去往敵國和親栏豺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,922評論 2 361