React Hooks 使用總結

引言

設計Hooks主要是解決ClassComponent的幾個問題:html

  • 很難複用邏輯(只能用 HOC,或者 render props),會致使組件樹層級很深
  • 會產生巨大的組件(指不少代碼必須寫在類裏面)
  • 類組件很難理解,好比方法須要 bind,this 指向不明確

同時,也爲了讓 FunctionalComponent 也擁有 ClassComponent 的一些特性。node

使用注意:react

  • 不能將 hooks 放在循環、條件語句或者嵌套方法內。react 是根據 hooks 出現順序來記錄對應狀態的。
  • 只在 function 組件和自定義 hooks 中使用 hooks。
  • 命名規範:
    • useState 返回數組的第二項以 set 開頭(僅做爲約定)。
    • 自定義 hooks 以 use 開頭(可被 lint 校驗)。

React 中提供的 hooks:web

  • useState:setState
  • useReducer:setState,同時 useState 也是該方法的封裝
  • useRef: ref
  • useImperativeHandle: 給 ref 分配特定的屬性
  • useContext: context,需配合 createContext 使用
  • useMemo: 能夠對 setState 的優化
  • useCallback: useMemo 的變形,對函數進行優化
  • useEffect: 相似 componentDidMount/Update, componentWillUnmount,當效果爲 componentDidMount/Update 時,老是在整個更新週期的最後(頁面渲染完成後)才執行
  • useLayoutEffect: 用法與 useEffect 相同,區別在於該方法的回調會在數據更新完成後,頁面渲染以前進行,該方法會阻礙頁面的渲染
  • useDebugValue:用於在 React 開發者工具中顯示自定義 hook 的標籤

1.State Hooks

1.1 useState

const [state, setState] = useState(initialState)
複製代碼
  • useState 有一個參數,該參數可傳如 任意類型的值或者 返回任意類型值的函數
  • useState 返回值爲一個數組,數組的 第一個參數爲咱們須要使用的 state,第二個參數爲一個setter函數,可傳任意類型的變量,或者一個接收 state 舊值的函數,其返回值做爲 state 新值。
function Counter({ initialCount }) {
 const [count, setCount] = useState(initialCount)  // Lazy initialization  const [state, setState] = useState(() => {  const initialState = someExpensiveComputation(props)  return initialState  })  return (  <>  Count: {count}  <button onClick={() => setCount(0)}>Reset</button>  <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>  <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>  </>  ) } 複製代碼

注意: set 方法不會像類組件的 setState 同樣作 merge,因此建議:redux

  • 若是數據結構簡單,能夠將變量根據數據結構須要放在不一樣的 useState 中,避免放入一個對象中大量使用相似 {...state, value}形勢。
  • 若是數據結構複雜,建議使用 useReducer 管理組件的 state。

1.2 useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init)
複製代碼
  • useReducer 接收三個參數, 第一個參數爲一個 reducer 函數第二個參數是reducer的初始值第三個參數爲可選參數,值爲一個函數,能夠用來惰性提供初始狀態。這意味着咱們可使用使用一個 init 函數來計算初始狀態/值,而不是顯式的提供值。若是初始值可能會不同,這會很方便,最後會用計算的值來代替初始值。
    • reducer 接受兩個參數一個是 state 另外一個是 action ,用法原理和 redux 中的 reducer 一致。
  • useReducer 返回一個數組,數組中包含一個 state 和 dispath**,state 是返回狀態中的值,而 dispatch 是一個能夠發佈事件來更新 state 的函數**。

注意: React 不使用 state = initialState 這一由 Redux 推廣開來的參數約定。有時候初始值依賴於 props,所以須要在調用 Hook 時指定。若是你特別喜歡上述的參數約定,能夠經過調用 useReducer(reducer, undefined, reducer) 來模擬 Redux 的行爲,但不鼓勵你這麼作。api

function init(initialCount) { 
 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);  return (  <>  Count: {state.count} <button  onClick={() => dispatch({type: 'reset', payload: initialCount})}>  Reset </button> <button onClick={() => dispatch({type: 'increment'})}>+</button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> </> ); }  function render () {  ReactDOM.render(<Counter initialCount={0} />, document.getElementById('root')); } 複製代碼

同時,useReucer 也是 useState 的內部實現,useState 和 useReucer 的實現原理:數組

let memoizedState
function useReducer(reducer, initialArg, init) {  let initState = void 0  if (typeof init !== 'undefined') {  initState = init(initialArg)  } else {  initState = initialArg  }  function dispatch(action) {  memoizedState = reducer(memoizedState, action)  // React的渲染  // render()  }  memoizedState = memoizedState || initState  return [memoizedState, dispatch] }  function useState(initState) {  return useReducer((oldState, newState) => {  if (typeof newState === 'function') {  return newState(oldState)  }  return newState  }, initState) } 複製代碼

在某些場景下,useReducer 比 useState 更加適用。Kent C. Dodds 提供了一個 useReducer 的最佳實踐:當你一個元素中的狀態,依賴另外一個元素中的狀態,最好使用 useReducer。瀏覽器

2.Effect Hooks

2.1 useEffect

useEffect(effect, array);
複製代碼

useEffect 接收兩個參數,沒有返回值。緩存

  • 第一個參數爲 effect 函數,該函數將在 componentDidMmount 時觸發和 componentDidUpdate 時有條件觸發(該添加爲 useEffect 的第二個數組參數)。同時該 effect 函數能夠返回一個函數(returnFunction),returnFunction 將會 在 componentWillUnmount 時觸發在 componentDidUpdate 時先於 effect 有條件觸發(先執行 returnFuncton 再執行 effect,好比須要作定時器的清除)注意: 與 componentDidMount 和 componentDidUpdate 不一樣之處是,effect 函數觸發時間爲在瀏覽器完成渲染以後。 若是須要在渲染以前觸發,須要使用 useLayoutEffect。
  • 第二個參數 array 做爲有條件觸發狀況時的條件限制:
    • 若是不傳,則每次 componentDidUpdate 時都會先觸發 returnFunction(若是存在),再觸發 effect。
    • 若是爲空數組 [],componentDidUpdate 時不會觸發 returnFunction 和 effect。
    • 若是隻須要在指定變量變動時觸發 returnFunction 和 effect,將該變量放入數組。

2.2 useLayoutEffect

useLayoutEffect(effect, array);
複製代碼

與 useEffect 使用方法同樣,只是執行回調函數的時機有着略微區別,運行時機更像是 componentDidMount 和 componentDidUpdate。可是要注意的是,該方法是同步方法,在瀏覽器 paint 以前執行,會阻礙瀏覽器 paint,只有當咱們須要進行DOM的操做時才使用該函數(好比設定 DOM 佈局尺寸,這樣能夠防抖動)。性能優化

useLayoutEffect 與 useEffect

正常狀況用默認的 useEffect 鉤子就夠了,這能夠保證狀態變動不阻塞渲染過程,但若是 effect 更新(清理)中涉及 DOM 更新操做,用 useEffect 就會有意想不到的效果,這時咱們最好使用 useLayoutEffect 。

好比逐幀動畫 requestAnimationFrame ,要作一個 useRaf hook 就得用上後者,須要保證同步變動。這也符合做者說到的 useEffect的時期是很是晚,能夠保證頁面是穩定下來再作事情

鉤子的執行順序:useLayoutEffect > requestAnimationFrame > useEffect

3.Context Hooks

要理解 Context Hooks 中的 api,首先須要瞭解 context 和其使用場景。

設計目的: context 設計目的是爲共享那些被認爲對於一個組件樹而言是「全局」的數據。

使用場景: context 經過組件樹提供了一個傳遞數據的方法,從而避免了在每個層級手動的傳遞 props 屬性

注意點: 不要僅僅爲了不在幾個層級下的組件傳遞 props 而使用 context,它是被用於在多個層級的多個組件須要訪問相同數據的情景。

3.1 createContext

const {Provider, Consumer} = React.createContext(defaultValue, calculateChangedBits)
複製代碼
  • 該方法建立一對{ Provider, Consumer }。當 React 渲染 context 組件 Consumer 時,它將從組件樹的上層中最接近的匹配的 Provider 讀取當前的 context 值。Consumer 是 Provider 提供數據的使用者。

  • 若是上層的組件樹沒有一個匹配的 Provider,而此時你須要渲染一個 Consumer 組件,那麼你能夠用到 defaultValue 。這有助於在不封裝它們的狀況下對組件進行測試。例如:

    import React, { useContext} from 'react';
    import ReactDOM from 'react-dom'; /* 結果讀取爲123,由於沒有找到Provider */ const { Provider, Consumer } = React.createContext(123); function Bar() {  return <Consumer>{color => <div>{color}</div>}</Consumer>; } function Foo() {  return <Bar />; } function App() {  return (  <Foo />  ); } ReactDOM.render(  <App />,  document.getElementById('root') ) 複製代碼

3.1.1 Provider

React 組件容許 Consumers 訂閱 context 的改變。而 Provider 就是發佈這種狀態的組件,該組件接收一個 value 屬性傳遞給 Provider 的後代 Consumers。一個 Provider 能夠聯繫到多個 Consumers。Providers 能夠被嵌套以覆蓋組件樹內更深層次的值。

export const ProviderComponent = props => {
 return (  <Provider value={}>  {props.children}  </Provider>  ) } 複製代碼

createContext()函數中的第二個參數爲calculateChangedBits,它是一個接受 newValue 與 oldValue 的函數,返回值做爲 changedBits,在 Provider 中,當 changedBits = 0,將再也不觸發更新。而在 Consumer 中有一個不穩定的 props,unstable_observedBits,若 Provider 的changedBits & observedBits = 0,也將不觸發更新。

const Context = React.createContext({foo: 0, bar: 0}, (a, b) => {
 let result = 0  if (a.foo !== b.foo) {  result |= 0b01  }  if (a.bar !== b.bar) {  result |= 0b10  }  return result }) 複製代碼

3.1.2 Consumer

<Consumer>
 {value => /* render something based on the context value */} </Consumer> 複製代碼
  • 一個能夠訂閱 context 變化的 React 組件。當 context 值發生改變時,Consumer 值也會改變
  • 接收一個 函數做爲子節點,該函數接收當前 context 的值並返回一個 React 節點。 傳遞給函數的 value 將等於組件樹中上層 context 的最近的 Provider 的 value 屬性。若是 context 沒有 Provider ,那麼 value 參數將等於被傳遞給 createContext() 的 defaultValue 。

每當 Provider 的值發生改變時, 做爲 Provider 後代的全部 Consumers 都會從新渲染。 從 Provider 到其後代的Consumers 傳播不受 shouldComponentUpdate 方法的約束,所以即便祖先組件退出更新時,後代Consumer也會被更新。

// 建立一個 theme Context, 默認 theme 的值爲 light
const ThemeContext = React.createContext('light');  function ThemedButton(props) {  // ThemedButton 組件從 context 接收 theme  return (  <ThemeContext.Consumer>  {theme => <Button {...props} theme={theme} />}  </ThemeContext.Consumer>  ) }  // 中間組件 function Toolbar(props) {  return (  <div>  <ThemedButton />  </div>  ) }  class App extends React.Component {  render() {  return (  <ThemeContext.Provider value="dark">  <Toolbar />  </ThemeContext.Provider>  )  } } 複製代碼

3.2 useContext

const context = useContext(Context)
複製代碼

使用效果和 Consumer 相似,可是是函數式的使用方式,仍然須要與 Provider 配合使用。

該函數接收一個 Context 類型的參數(就是包裹了 Provider 和 Consumer 的那個對象),返回 Provider 中的 value 屬性對象的值。

const Context = React.createContext('light');
 // Provider class Provider extends Component {  render() {  return (  <Context.Provider value={'dark'}>  <DeepTree />  </Context.Provider>  )  } } 複製代碼
// Consumer
function Consumer(props) {  const context = useContext(Context)  return (  <div>  {context} // dark  </div>  ) } 複製代碼

3.3 配合 useReducer 使用

// Color.jsx
import React, { createContext, useReducer } from 'react'  export const ColorContext = createContext() export const UPDATE_COLOR = 'UPDATE_COLOR'  function reducer(state, action) {  switch (action.type) {  case UPDATE_COLOR:  return action.color  default:  return state  } }  export const Color = props => {  const [color, dispatch] = useReducer(reducer, 'blue')  return (  <ColorContext.Provider value={{ color, dispatch }}>  {props.children}  </ColorContext.Provider>  ) } 複製代碼
// Button.jsx
import React, { useContext } from 'react' import { ColorContext, UPDATE_COLOR } from './Color' function Buttons() {  const { dispatch } = useContext(ColorContext)  return (  <div>  <button  onClick={() => {  dispatch({ type: UPDATE_COLOR, color: 'red' })  }}  >  red  </button>  <button  onClick={() => {  dispatch({ type: UPDATE_COLOR, color: 'yellow' })  }}  >  yellow  </button>  </div>  ) }  export default Buttons 複製代碼
// ShowArea.jsx
import React, { useContext } from 'react' import { ColorContext } from './Color' function ShowArea() {  const { color } = useContext(ColorContext)  return <div style={{ color }}>color:{color}</div> }  export default ShowArea 複製代碼
// index.jsx
import React from 'react' import ShowArea from './ShowArea' import Buttons from './Buttons' import { Color } from './Color' function Demo() {  return (  <div>  <Color>  <ShowArea />  <Buttons />  </Color>  </div>  ) }  export default Demo 複製代碼

4.Ref Hooks

4.1 useRef

const RefElement = createRef(initialValue)
複製代碼

4.1.1 組件引用

useRef 能夠須要傳遞一個參數,該參數通常是用於 useRef 的另外一種用法,若是是引用元素對象通常不傳參數,返回一個可變的 ref 對象,該對象下面有一個 current 屬性指向被引用對象的實例。

要說到 useRef,咱們須要說到 createRef ,以及爲何要有這個 api 出現。(createRef 使用方法和 useRef 一致,返回的是一個 ref 對象)

二者當作 ref 正常使用時效果基本徹底同樣:

  • createRef

    import { React, createRef } from 'react'
     const FocusInput = () => {  const inputElement = createRef()  const handleFocusInput = () => {  inputElement.current.focus()  }  return (  <>  <input type='text' ref={inputElement} />  <button onClick={handleFocusInput}>Focus Input</button>  </>  ) }  export default FocusInput 複製代碼
  • useRef

    import { React, useRef } from 'react'
     const FocusInput = () => {  const inputElement = useRef()  const handleFocusInput = () => {  inputElement.current.focus()  }  return (  <>  <input type='text' ref={inputElement} />  <button onClick={handleFocusInput}>Focus Input</button>  </>  ) }  export default FocusInput 複製代碼

可是,這二者對應 ref 的引用實際上是有着本質區別的:createRef 每次渲染都會返回一個新的引用,而 useRef 每次都會返回相同的引用。

像這樣:

const App = () => {
 const [renderIndex, setRenderIndex] = React.useState(1)  const refFromUseRef = React.useRef()  const refFromCreateRef = createRef()   if (!refFromUseRef.current) {  refFromUseRef.current = renderIndex  }   if (!refFromCreateRef.current) {  refFromCreateRef.current = renderIndex  }   return (  <>  <p>Current render index: {renderIndex}</p>  <p>  <b>refFromUseRef</b> value: {refFromUseRef.current}  </p>  <p>  <b>refFromCreateRef</b> value:{refFromCreateRef.current}  </p>   <button onClick={() => setRenderIndex(prev => prev + 1)}>  Cause re-render  </button>  </>  ) } 複製代碼
img
img

由於一直都存在 refFromUseRef.current,因此並不會改變值。

4.1.2 替代 this

那麼,爲何要賦予 useRef 這種特性,在什麼場景下咱們須要這種特性呢?

一個經典案例:

import React, { useRef, useState } from 'react'
 function App() {  const [count, setCount] = useState()  function handleAlertClick() {  setTimeout(() => {  alert(`Yout clicked on ${count}`)  }, 3000)  }  return (  <div>  <p>You click {count} times</p>  <button onClick={() => setCount(count + 1)}>Click me</button>  <button onClick={handleAlertClick}>Show alert</button>  </div>  ) }  export default App 複製代碼
img
img

當咱們更新狀態的時候, React 會從新渲染組件, 每一次渲染都會拿到獨立的 count 狀態, 並從新渲染一個 handleAlertClick 函數. 每個 handleAlertClick 裏面都有它本身的 count。

你會發現,count 的值並不可以實時的顯示更新的數據,這個是因爲 JS 中一值就存在的閉包機制致使的,當點擊顯示彈窗的按鈕時,此時的 count 的值已經肯定,而且傳入到了alert方法的回調中,造成閉包,後續值的改變不會影響到定時器的觸發。

而若是在類組件中,若是咱們使用的是this.state.count,獲得的結果又會是實時的,由於它們都是指向的同一個引用對象。

在函數組件中,咱們可使用 useRef 來實現實時獲得新的值,這就是 useRef 的另一種用法,它還至關於 this , 能夠存聽任何變量。useRef 能夠很好的解決閉包帶來的不方便性。

import React, { useRef, useState } from 'react'
 function App() {  const [count, setCount] = useState(0)  const lastestCount = useRef()  lastestCount.current = count  function handleAlertClick() {  setTimeout(() => {  alert(`You clicked on ${lastestCount.current}`) // 實時的結果  }, 3000)  }  return (  <div>  <p>Yout click {count} times</p>  <button onClick={() => setCount(count + 1)}>Click me</button>  <button onClick={handleAlertClick}>Show alert</button>  </div>  ) }  export default App 複製代碼

要值得注意的是,若是咱們在 useRef 中傳入參數(通常 useRef 中傳值就用在這裏),使用下面這種方法來訪問值,結果又會不一樣:

import React, { useRef, useState } from 'react'
 function App() {  const [count, setCount] = useState(0)   const lastestCount = useRef(count) // 直接傳入count   function handleAlertClick() {  setTimeout(() => {  alert(`You clicked on ${lastestCount.current}`)  }, 3000)  }  return (  <div>  <p>Yout click {count} times</p>  <button onClick={() => setCount(count + 1)}>Click me</button>  <button onClick={handleAlertClick}>Show alert</button>  </div>  ) }  export default App 複製代碼

點擊的時候咱們會發現彈出來的值永遠是0,正如咱們所說,useRef 返回的都是相同的引用,參數在第一個傳入進去的時候已經賦值給了 current 屬性,返回了一個實例回來,後續由於已經有了實例了,因此會直接將原來的實例返回,傳入的參數也就再也不起做用了。

4.2 forwardRef

forwardRef((props, ref) => {
 // dosomething  return (  <div ref={ref}></div>  ) }) 複製代碼

forwardRef 準確來講不是 hooks 中的內容,可是若是咱們要使用 useImperativeHandle,就須要使用它來進行搭配。

該方法的做用是:引用父組件的 ref 實例,成爲子組件的一個參數,能夠引用父組件的 ref 綁定到子組件自身的節點上。

該方法能夠看作是一個高階組件,自己 props 只帶有 children 這個參數,它能將從父組件拿到的 ref 和 props 傳入給子組件,由子組件來調用父組件傳入的 ref。

傳入的組件會接收到兩個參數,一個是父組件傳遞的 props,另外一個就是 ref 的引用。

// 咱們可使用三層組件嵌套,把傳入forwardRef的函數當作傳值的中間層
function InputWithLabel(props) {  // 這裏的myRef爲經過外部打入的父級ref節點  const { label, myRef } = props  const [value, setValue] = useState("")  const handleChange = e => {  const value = e.target.value  setValue(value)  }   return (  <div>  <span>{label}:</span>  <input type="text" ref={myRef} value={value} onChange={handleChange} />  </div>  ) }  // 這裏用forwardRef來承接獲得父級傳入的ref節點,並將其以參數的形式傳給子節點 const RefInput = React.forwardRef((props, ref) => (  <InputWithLabel {...props} myRef={ref} /> ))  // 調用該RefInput的過程 function App() {  // 經過useRef hook 得到相應的ref節點  const myRef = useRef(null)   const handleFocus = () => {  const node = myRef.current  console.log(node)  node.focus()  }   return (  <div className="App">  <RefInput label={"姓名"} ref={myRef} />  <button onClick={handleFocus}>focus</button>  </div>  ) } 複製代碼

4.3 useImperativeHandle

useImperativeHandle(ref, () => ({
 a:1,  b:2,  c:3 })) 複製代碼

官方建議useImperativeHandle和forwardRef同時使用,減小暴露給父組件的屬性,避免使用 ref 這樣的命令式代碼。

useImperativeHandle 有三個參數:

  • 第一個參數,接收一個經過 forwardRef 引用父組件的 ref 實例
  • 第二個參數一個回調函數,返回一個對象,對象裏面存儲須要暴露給父組件的屬性或方法
  • 第三個參數爲一個可選參數,該參數是一個依賴項數組,就像 useEffect 那樣
function Example(props, ref) {
 const inputRef = useRef()  useImperativeHandle(ref, () => ({  // 父組件能夠經過this.xxx.current.focus的方式使用子組件傳遞出去的focus方法  focus: () => {  inputRef.current.focus()  }  }))  return <input ref={inputRef} /> }  export default forwardRef(Example) 複製代碼
class App extends Component {
 constructor(props){  super(props)  this.inputRef = createRef()  }   render() {  return (  <>  <Example ref={this.inputRef}/>  <button onClick={() => {this.inputRef.current.focus()}}>Click</button>  </>  )  } } 複製代碼

5.性能優化

5.1 memo

MemoComponent = memo(Component)
複製代碼

咱們都知道,對於類組件來講,有 PureComponent 能夠經過判斷父組件傳入的 props 是否進行改變來優化渲染性能。因此,在函數式組件中,React 也有一個相似 PureComponent 功能的高階組件 memo,效果同 PureComponent,都會判斷父組件傳入的 props 是否發生改變來從新渲染當前組件。

使用方法很簡單:

import React, { memo } from 'react'
 function Demo(props){  return (  <div>{props.name}</div>  ) }  export default memo(Demo) 複製代碼

5.2 useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])
複製代碼

useMemo 是 React 推出用於優化函數式組件性能的 hooks,它能夠傳入兩個參數:

  • 第一個參數爲一個工廠函數,返回一個緩存的值,也就是僅當從新渲染時數組中的值發生改變時,回調函數纔會從新計算緩存數據,這可使得咱們避免在每次從新渲染時都進行復雜的數據計算。
  • 第二個參數爲一個依賴項數組,只有依賴項中的數據發生改變時才從新計算值,用法同 useEffect 的依賴項數組
import React, { useState, useMemo } from 'react'
 function Child({ color }) {  // color值不發生改變不會打印console,可是依舊會觸發從新渲染,若是連這個函數都不執行,在最外層加上memo  const actionColor = useMemo(() => {  console.log('color update')  return color  }, [color])   return <div style={{ actionColor }}>{actionColor}</div> }  function MemoCount() {  const [count, setCount] = useState(0)  const [color, setColor] = useState('blue')  return (  <div>  <button  onClick={() => {  setCount(count + 1)  }}  >  Update Count  </button>  <button  onClick={() => {  setColor('green')  }}  >  Update Color  </button>  <div>{count}</div>  <Child color={color} />  </div>  ) }  export default MemoCount 複製代碼

上面的例子其實並非 useMemo 最經常使用的場景,就像以前說的,在 props 發生改變的時候纔會觸發被 memo 的組件的從新渲染,可是若是隻是 props 的引用對象發生改變,實際的值並無發生改變,組件仍是會被從新渲染。就像下面這樣:

import React, { useState, memo } from 'react'
 const Child = memo(({ config }) => {  console.log(config)  return <div style={{ color:config.color }}>{config.text}</div> })  function MemoCount() {  const [count, setCount] = useState(0)  const [color, setColor] = useState('blue')  const config = {  color,  text:color  }  return (  <div>  <button  onClick={() => {  setCount(count + 1)  }}  >  Update Count  </button>  <button  onClick={() => {  setColor('green')  }}  >  Update Color  </button>  <div>{count}</div>  <Child config={config} />  </div>  ) }  export default MemoCount 複製代碼

當咱們改變 count 值的時候,咱們發現這其實和 config 對象是無關的,可是 Child 組件依舊會從新渲染,由於因爲父組件的從新渲染,config 被從新賦值了新的對象,雖然新的對象裏面的值都是相同的,但因爲是引用類型對象,因此依舊會改變值,要改變這種情況,咱們須要:

// 使用useMemo
import React, { useState,useMemo, memo } from 'react'  const Child = memo(({ config }) => {  console.log(config)  return <div style={{ color:config.color }}>{config.text}</div> })  function MemoCount() {  const [count, setCount] = useState(0)  const [color, setColor] = useState('blue')  // 只會根據color的改變來返回不一樣的對象,不然都會返回同一個引用對象  const config = useMemo(()=>({  color,  text:color  }),[color])   return (  <div>  <button  onClick={() => {  setCount(count + 1)  }}  >  Update Count  </button>  <button  onClick={() => {  setColor('green')  }}  >  Update Color  </button>  <div>{count}</div>  <Child config={config} />  </div>  ) }  export default MemoCount 複製代碼

這樣,當 count 的值發生改變時,子組件就不會再從新渲染了。

5.3 useCallback

const memoizedCallback = useCallback(
 () => {  doSomething(a, b)  },  [a, b], ) 複製代碼

useCallback 的用法和 useMemo 相似,是專門用來緩存函數的 hooks,也是接收兩個參數,同時,咱們第一個參數傳入額回調函數就是要緩存的函數。

注意:第二個參數目前只用於指定須要判斷是否變化的參數,並不會做爲形參傳入回調函數。建議回調函數中使用到的變量都應該在數組中列出。

要在回調函數中傳入參數,咱們最好使用高階函數的方法,useCallback 會幫咱們緩存這個高階函數,如上所示。

能夠看出,都是當依賴項方式改變時,才觸發回調函數。所以,咱們能夠認爲:useCallback(fn, inputs) 等同於 useMemo(() => fn, inputs)

// useCallback的實現原理
let memoizedState = null function useCallback(callback, inputs) {  const nextInputs =  inputs !== undefined && inputs !== null ? inputs : [callback]  const prevState = memoizedState;  if (prevState !== null) {  const prevInputs = prevState[1]  if (areHookInputsEqual(nextInputs, prevInputs)) {  return prevState[0]  }  }  memoizedState = [callback, nextInputs]  return callback }  // useMemo的實現原理 function useMemo(callback, inputs){  return useCallback(callbak(),inputs) } 複製代碼

更多狀況,useCallback通常用於在 React 中給事件綁定函數並須要傳入參數的時候:

// 下面的狀況能夠保證組件從新渲染獲得的方法都是同一個對象,避免在傳給onClick的時候每次都傳不一樣的函數引用
import React, { useState, useCallback } from 'react'  function MemoCount() {  const [count, setCount] = useState(0)   memoSetCount = useCallback(()=>{  setCount(count + 1)  },[])   return (  <div>  <button  onClick={memoSetCount}  >  Update Count  </button>  <div>{color}</div>  </div>  ) }  export default MemoCount 複製代碼

6.Debug

6.1 useDebugValue

useDebugValue(value)
// or useDebugValue(date, date => date.toDateString()); 複製代碼

useDebugValue 可用於在 React 開發者工具中顯示自定義 hook 的標籤。

useDebugValue 接收兩個參數,根據傳入參數數量的不一樣有不一樣的使用方式:

  • 直接傳 debug 值

    function useFriendStatus(friendID) {
     const [isOnline, setIsOnline] = useState(null);   // ...   // 在開發者工具中的這個 Hook 旁邊顯示標籤  // e.g. "FriendStatus: Online"  useDebugValue(isOnline ? 'Online' : 'Offline');   return isOnline; } 複製代碼
  • 延遲格式化 debug 值

    const date = new Date()
    useDebugValue(date, date => date.toDateString()) 複製代碼

7.自定義 Hooks

自定義 Hook 是一個函數,其名稱以use開頭,函數內部能夠調用其餘的 Hook

// myhooks.js
// 下面自定義了一個獲取窗口長寬值的hooks import React, { useState, useEffect, useCallback } from 'react'  function useWinSize() {  const [size, setSize] = useState({  width: document.documentElement.clientWidth,  height: document.documentElement.clientHeight  })  const onResize = useCallback(() => {  setSize({  width: document.documentElement.clientWidth,  height: document.documentElement.clientHeight  })  }, [])   useEffect(() => {  window.addEventListener('resize', onResize)  return () => {  window.removeEventListener('reisze', onResize)  }  }, [onResize])  return size }  export const useWinSize 複製代碼
import { useWinSize } from './myhooks'
function MyHooksComponent() {  const size = useWinSize()  return (  <div>  頁面Size:{size.width}x{size.height}  </div>  ) }  export default MyHooksComponent 複製代碼

參考

本文使用 mdnice 排版

相關文章
相關標籤/搜索