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 排版

相关文章
相关标签/搜索