Резюме использования React Hooks

внешний интерфейс React.js

введение

Разработка хуков в основном предназначена для решения нескольких проблем ClassComponent:

  • Сложно повторно использовать логику (используйте только HOC или реквизиты рендеринга), что приводит к глубокому дереву компонентов.
  • Будет генерировать огромные компоненты (это означает, что в классе должно быть написано много кода)
  • Компоненты класса сложны для понимания, например, методы нужно биндить, а этот момент не ясен

В то же время, для того, чтобы FunctionalComponent также имел некоторые возможности ClassComponent.

Осторожность:

  • Хуки нельзя размещать внутри циклов, условных операторов или вложенных методов. React записывает соответствующее состояние в соответствии с порядком появления хуков.
  • Используйте хуки только в функциональных компонентах и ​​пользовательские хуки.
  • Соглашения об именах:
    • Второй элемент массива, возвращаемый useState, начинается с set (только по соглашению).
    • Пользовательские хуки начинаются с использования (проверяется lint).

Хуки, представленные в React:

  • useState: setState
  • useReducer: setState, а useState также является инкапсуляцией этого метода.
  • useRef: ref
  • useImperativeHandle: назначить определенные свойства для ссылки
  • useContext: контекст, который необходимо использовать вместе с createContext.
  • useMemo: может оптимизировать setState
  • useCallback: вариант useMemo для оптимизации функции
  • useEffect: Подобно componentDidMount/Update, componentWillUnmount, когда эффектом является componentDidMount/Update, он всегда выполняется в конце всего цикла обновления (после завершения рендеринга страницы).
  • UseLayouteffect: использование такое же, как и используется, а разница в том, что обратный вызов метода будет выполняться до завершения обновления данных, партия отображается страницей.
  • useDebugValue: метка, используемая для отображения пользовательских хуков в React DevTools.

1.State Hooks

1.1 useState

const [state, setState] = useState(initialState)
  • useState имеет параметр, который можно передать каклюбой тип значенияилиФункция, которая возвращает значение любого типа.
  • Возвращаемое значение useState представляет собой массивПервый параметр — это состояние, которое нам нужно использовать, а второй параметр — этоsetterФункция, которой может быть передан любой тип переменной, или функция, которая получает старое значение состояния и возвращает значение как новое значение состояния.
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 компонента класса, поэтому рекомендуется:

  • Если структура данных проста, вы можете поместить переменные в разные useState в соответствии с потребностями структуры данных, чтобы не помещать их в один объект и не использовать большое количество похожих{...state, value}ситуация.
  • Если структура данных сложна, рекомендуется использовать USERDUCER для управления состоянием компонента.

1.2 useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init)
  • useReducer получает три параметра,Первый параметр — это функция редуктора, второй параметр — начальное значение редуктора, а третий параметр — необязательный параметр, значение которого — функция, которую можно использовать для ленивого предоставления начального состояния.Это означает, что мы можем использоватьinitфункция для вычисления начального состояния/значения вместо явного указания значения. Это удобно, если начальное значение может быть другим, а вычисленное значение используется вместо начального значения в конце.
    • Редюсер принимает два параметра, один — состояние, а другой — действие, принцип использования такой же, как у редьюсера в редуксе.
  • useReducer возвращает массив, содержащий состояние и отправку, состояние — это значение в возвращаемом состоянии, а отправка — это функция, которая может публиковать события для обновления состояния.

Уведомление:Реакт не используетstate = initialStateЭто соглашение о параметрах было популяризировано Redux. Иногда начальное значение зависит от реквизита, поэтому его нужно указывать при вызове хука. Если вам особенно нравятся приведенные выше соглашения о параметрах, вы можете сделать это, вызвавuseReducer(reducer, undefined, reducer)для имитации поведения Redux, но вам не рекомендуется этого делать.

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 === 'function') {
        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. Кент С. Доддс представилuseReducerлучшая практика:Когда вы находитесь в состоянии элемента, зависят от состояния других элементов, предпочтительно используют Userucer.

2.Effect Hooks

2.1 useEffect

useEffect(effect, array);

useEffect принимает два параметра и не возвращает значения.

  • Первый параметр — это функция эффекта, которая будет запускаться для componentDidMmount и условно для componentDidUpdate (это добавляется как второй параметр массива useEffect ). В то же время функция эффекта может возвращать функцию (returnFunction), а функция returnFunction будетЗапущено на компонентеWillUnmountа такжеУсловный запуск перед эффектом в componentDidUpdate (сначала выполнить returnFuncton, а затем выполнить эффект, например, необходимость очистки таймера).Уведомление:В отличие от componentDidMount и componentDidUpdate, функция эффекта срабатывает после того, как браузер завершит рендеринг. Если вам нужно запустить перед рендерингом, вам нужно использовать useLayoutEffect.
  • Второй массив параметров — это условный предел при срабатывании условия:
    • Если не передать, returnFunction (если она существует) будет запускаться каждый раз, когда вызывается componentDidUpdate, а затем будет запускаться эффект.
    • если пустой массив[], returnFunction и эффект не будут запущены, когда произойдет componentDidUpdate.
    • Если вам нужна только returnFunction и эффект, который срабатывает при изменении указанной переменной, поместите эту переменную в массив.

2.2 useLayoutEffect

useLayoutEffect(effect, array);

Как и использование USEFECHECT, просто выполните небольшую разницу во времени выполнения функции обратного вызова, более похоже на компонентуDIDMount и ComponentDiDUpdate. Однако следует отметить, что этот метод представляет собой синхронный метод, выполняющий перед краской браузера, препятствует краску браузера, только используя эту функцию, когда нам нужно выполнить операцию DOM (например, настроить размер макета DOM, так что вы можете имитировать перемещение) Отказ

useEffect и useLayoutEffect

В обычных обстоятельствах достаточно хука useEffect по умолчанию, который может гарантировать, что изменение состояния не блокирует процесс рендеринга, но если в обновлении эффекта (очистке) участвует операция обновления DOM, использование useEffect будет иметь неожиданные эффекты, тогда мы лучше использовать useLayoutEffect .

Например, покадровая анимация requestAnimationFrame, вам нужно использовать последний, чтобы сделать хук useRaf, и вам нужно обеспечить синхронность изменений. Это также соответствует тому, что сказал авторПериод useEffect очень поздний, что может гарантировать, что страница стабильна, прежде чем что-то делать..

Порядок выполнения крючков:useLayoutEffect > requestAnimationFrame > useEffect

3.Context Hooks

Чтобы понять API в Context Hooks, вам сначала нужно понять контекст и сценарии его использования.

цель дизайна:Контекст предназначен для обмена данными, которые считаются «глобальными», с деревом компонентов.

используемые сцены:контекст обеспечиваетпередавать данныеметод, так чтоИзбегает ручной передачи реквизита на каждом уровне.

будь осторожен:Не используйте контекст только для того, чтобы избежать передачи реквизитов между компонентами на нескольких уровнях, он используется, когда нескольким компонентам на разных уровнях требуется доступ к одним и тем же данным.

3.1 createContext

const {Provider, Consumer} = React.createContext(defaultValue, calculateChangedBits)
  • Этот метод создает пару{ Provider, Consumer }. Когда React визуализирует контекстный компонент Consumer, он будет считывать текущее значение контекста из ближайшего соответствующего Provider выше в дереве компонентов. Потребители — это потребители данных, предоставляемых Поставщиками.

  • Если в верхнем дереве компонентов нет соответствующего поставщика и вам необходимо отобразить компонент-потребитель, вы можете использовать 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 的改变. Провайдер — это компонент, который публикует это состояние, компонент接收一个 value 属性Передается потомкам Потребителей Провайдера.一个 Provider 可以联系到多个 Consumers. Провайдеры могут быть вложены, чтобы переопределить значения глубже в дереве компонентов.

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

существуетcreateContext()Второй параметр функцииcalculateChangedBits, которая является функцией, которая принимает newValue и oldValue и возвращает значение как changeBits.В Provider, когда changeBits = 0, обновление больше не будет запускаться. В Потребителе есть нестабильный реквизит, нестабильное_обсерведБитс, если у Провайдера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>
  • Актуальный компонент, который может подписаться на изменения контекста. Когда значение контекста меняется, стоимость потребителей также меняется
  • Получает в качестве ребенка функция, которая принимает значение текущего контекста и возвращает реактивный узел.Значение, переданное в функцию, будет равно значению свойства ближайшего Provider верхнего контекста в дереве компонентов.. Если в контексте нет Provider , то параметр value будет равен значению, переданному вcreateContext()значение по умолчанию.

Все потребители, которые являются потомками провайдера, перерисовываются всякий раз, когда изменяется значение провайдера. Распространение потребителей от поставщика к его потомкам не ограничивается методом shouldComponentUpdate, поэтому получатели-потомки обновляются, даже когда компонент-предок выходит из обновления.

// 创建一个 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)

Эффект использования аналогичен Потребителю, но это функциональный способ использования, который по-прежнему необходимо использовать в сочетании с Провайдером.

Эта функция получает параметр типа Context (то есть объект, обертывающий Provider и Consumer) и возвращает значение объекта атрибута value в Provider.

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. Существует текущий атрибут под объектом, указывающим к указанному объекту.экземпляр.

Чтобы поговорить о useRef, нам нужно поговорить о createRef и о том, почему существует этот API. (Метод использования createRef такой же, как и useRef, и он возвращает объект 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

Поскольку refFromUseRef.current присутствует всегда, он не изменяет значение.

4.1.2 Замена этого

Итак, зачем давать 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

Когда мы обновляем состояние,React будет повторно отображать компонент, каждый рендеринг получит отдельное состояние счетчика и повторно отобразит функцию handleAlertClick.Каждый handleAlertClick имеет свой собственный счетчик.

Вы обнаружите, что значение 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 возвращает ту же ссылку. Параметр был назначен текущему свойству при его первой передаче, и возвращается экземпляр. , поскольку экземпляр уже есть, исходный экземпляр будет возвращен напрямую, и входящие параметры больше не будут работать.

4.2 forwardRef

forwardRef((props, ref) => {
    // dosomething
    return (
     <div ref={ref}></div>
    )
})

ForwardRef — это не совсем то, что есть в хуках, но если мы хотим использовать useImperativeHandle, нам нужно использовать его для сопоставления.

Что делает этот метод:Ссылается на экземпляр ref родительского компонента и становится параметром дочернего компонента.Вы можете обратиться к ref родительского компонента и привязать его к узлу самого дочернего компонента.

Этот метод можно рассматривать как компонент более высокого порядка.Его собственные реквизиты имеют только параметр дочерних элементов.Он может передавать ref и реквизиты, полученные от родительского компонента, дочернему компоненту, а дочерний компонент будет вызывать ref, переданный в родительский компонент.

Входящий компонент получит два параметра: один — реквизит, переданный родительским компонентом, а другой — ссылка 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 имеет три параметра:

  • Первый параметр получает экземпляр ref, который ссылается на родительский компонент через forwardRef.
  • Второй параметр — это функция обратного вызова, которая возвращает объект, в котором хранятся свойства или методы, которые необходимо предоставить родительскому компоненту.
  • Третий параметр является необязательным и представляет собой массив зависимостей, как и 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 может оптимизировать производительность рендеринга, определяя, изменились ли реквизиты, переданные родительским компонентом. Поэтому в функциональных компонентах React также имеет памятку компонента более высокого порядка, аналогичную PureComponent. Эффект такой же, как у PureComponent. Он будет судить, изменились ли реквизиты, переданные родительским компонентом, для повторного рендеринга текущего компонента.

Использование очень простое:

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 для оптимизации производительности функциональных компонентов, он может передавать два параметра:

  • Первый параметр — это фабричная функция, которая возвращает кэшированное значение, то есть callback-функция будет пересчитывать кэшированные данные только тогда, когда значение в массиве изменится при повторном рендеринге, что позволяет нам каждый раз избегать повторного рендеринга. сложные расчеты данных.
  • Второй параметр — это массив зависимостей. Значение пересчитывается только при изменении данных в зависимости. Использование такое же, как у массива зависимостей 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.Как упоминалось ранее, повторный рендеринг компонента memo будет запущен при изменении реквизита, но если изменится только ссылочный объект реквизита, фактическое значение не изменится. Происходят изменения, и компонент все равно будет перерисовываться. Как следующее:

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

Когда мы меняем значение счетчика, мы обнаруживаем, что это не имеет никакого отношения к объекту конфига, но дочерний компонент все равно будет перерендерен, потому что из-за перерендеринга родительского компонента конфигу переназначается новый объект, хотя новый объект имеет значение такое же, но поскольку это объект ссылочного типа, значение все равно изменится. Чтобы изменить это, нам нужно:

// 使用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. Это хук, специально используемый для кэширования функций. Он также получает два параметра. В то же время первый параметр, который мы передаем в функцию обратного вызова, — это функция, которую нужно кэшировать.

Уведомление:Второй параметр в настоящее время используется только для указания параметра, который необходимо решить, следует ли изменить, и он не будет передан в функцию обратного вызова в качестве формального параметра.Рекомендуется, чтобы все переменные, используемые в функции обратного вызова, были перечислены в массиве.

Чтобы передать параметры в функцию обратного вызова, мы можем использовать метод функции высшего порядка, и useCallback поможет нам кэшировать эту функцию высшего порядка, как показано выше.

Конечно, можно иcallbackЗапишите формальные параметры в:

const memoizedCallback = useCallback(
  (a, b) => {
    doSomething(a, b)
  },
  [],
)
// memoizedCallback 其实就是传入的回调函数

Видно, что функция обратного вызова срабатывает только при изменении режима зависимости. Следовательно, мы можем рассмотреть: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)
    },[count])
    
    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 DevTools.

useDebugValue получает два параметра, которые можно использовать по-разному в зависимости от количества переданных параметров:

  • Передайте значение отладки напрямую

    function useFriendStatus(friendID) {
      const [isOnline, setIsOnline] = useState(null);
    
      // ...
    
      // 在开发者工具中的这个 Hook 旁边显示标签
      // e.g. "FriendStatus: Online"
      useDebugValue(isOnline ? 'Online' : 'Offline');
    
      return isOnline;
    }
    
  • Ленивое форматирование значений отладки

    const date = new Date()
    useDebugValue(date, date => date.toDateString())
    

7. Пользовательские хуки

Пользовательский хук — это функция, имя которой начинается сuseВначале внутри функции могут быть вызваны другие хуки.

// 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

Если вы хотите узнать больше о пользовательских хуках, рекомендуется прочитатьahooksа такжеreact-useИсходный код, я думаю, заставит ваши глаза сиять.

Ссылаться на