React Hooks используют яму, обнаруженную в практическом проекте

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

React Hooks — это новая функция React, обновленная в версии 16.8. В React всегда пропагандировалось использование функциональных компонентов. В старой версии функциональные компоненты не имеют экземпляров компонентов, состояний и функций жизненного цикла, что приводит ко многим ситуации, когда необходимо использовать компоненты класса, но хуки. Когда он выйдет, мы сможем использовать состояние и другие функции React без использования компонентов класса!

1. состояние использования

1. Базовое использование useState

import React, { useState } from 'react';

function Example() {
  // 声明一个叫 "count" 的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
  • Это официальный базовый пример использования, импорт представляет функцию useState.
  • Передать инициализированное значение при вызове функции
  • Функция возвращает массив: один для инициализированного состояния и один для функции, которая обновляет состояние.
  • Наконец, используйте состояние в другом месте или вызовите функцию, которая обновляет состояние.
  • Примечание. Функция, которая обновляет состояние, заменяет состояние напрямую, а не объединяет старое и новое состояние, как в предыдущем setState.

2. Используйте push, pop, splice и т. д., чтобы напрямую изменить яму объекта массива.

Поскольку функция обновления useState будет напрямую заменять старое состояние, когда мы добавляем или удаляем состояние объекта или массива, мы не можем напрямую использовать push, pop, splice и т. д. для прямого изменения массива, как раньше.

Пример ошибки:

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2]);
  const handleAdd = () => {
    const randomCount = Math.round(Math.random()*100)
    // 在此地方我们使用push增加一个随机数,程序报错
    setCounts(counts.push(randomCount))
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleAdd}>增加</button>
    </div>
  );
}

export default Comment;

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

Дополнения массива:

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2]);
  const handleAdd = () => {
    const randomCount = Math.round(Math.random()*100)
    // 在此我们用数组结构生成新数组,并在后面加上我们要新增的随机数
    setCounts([
      ...counts,
      randomCount
    ])
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleAdd}>增加</button>
    </div>
  );
}

export default Comment;

Удалите это

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2, 3, 4]);
  const handleDel = () => {
    // 使用数组filter方法,过滤删除其中不需要的项
    setCounts(counts.filter((count, index) => index !== counts.length - 1))
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleDel}>删除</button>
    </div>
  );
}

export default Comment;

Кроме того, еще один метод заключается в том, чтобы сделать глубокую копию старого объекта массива в редюсере, который раньше использовал редукцию, а затем выполнить операции добавления и удаления и, наконец, вернуть

import React, { useState } from "react";

function Comment() {
  const [counts, setCounts] = useState([1, 2]);
  const handleAdd = () => {
    setCounts(counts => {
      const randomCount = Math.round(Math.random()*100)
      // 简单使用JSON.parse及JSON.stringify深拷贝一个新的数组和对象(实际项目中建议自己写递归深拷贝函数),然后对其操作返回
      let newCounts = JSON.parse(JSON.stringify(counts))
      newCounts.push(randomCount)
      return newCounts
    })
  }
  return (
    <div>
      {counts.map((count) => (
        <div key={count}>{count}</div>
      ))}
      <button onClick={handleAdd}>增加</button>
    </div>
  );
}

export default Comment;

3. Каждый рендер — это яма независимых замыканий

Когда мы сначала выполняем асинхронную функцию добавления (handleSyncAdd), затем выполняем синхронную функцию (handleAdd), а затем выполняем асинхронную функцию после синхронного выполнения, счетчик в асинхронной функции равен значению (0) в замыкании во время предыдущего выполнение и пример ошибки:

import React, { useState } from "react";

function Comment() {
  const [count, setCount] = useState(0);
  const handleAdd = () => setCount(count + 1);
  const handleSyncAdd = () => {
    setTimeout(() => {
    // 获取的是闭包中的state
      setCount(count + 1);
    }, 1000);
  };
  return (
    <div>
      <p>{count}</p>
      <button onClick={handleAdd}>增加</button>
      <button onClick={handleSyncAdd}>异步增加</button>
    </div>
  );
}

export default Comment;

В этом случае нам нужно использовать функцию обратного вызова для обновления

Правильный пример:

import React, { useState } from "react";

function Comment() {
  const [count, setCount] = useState(0);
  const handleAdd = () => setCount(count + 1);
  const handleSyncAdd = () => {
    setTimeout(() => {
    // 改成回调函数更新,每次回调函数执行时会接收之前的state,而不是闭包中的state
      setCount(count => count + 1);
    }, 1000);
  };
  return (
    <div>
      <p>{count}</p>
      <button onClick={handleAdd}>增加</button>
      <button onClick={handleSyncAdd}>异步增加</button>
    </div>
  );
}

export default Comment;

2. использоватьЭффект

  • эффект (побочный эффект), который можно понимать как функцию жизненного цикла, когда мы используем компоненты класса
  • useEffect может реализовать то, что у нас есть в компонентах классаcomponentDidMount,ComponentDidUpdateа такжеcomponentWillUnmountЕго функциональность просто объединена в один API.
  • а такжеcomponentDidMountилиcomponentDidUpdateОтличие в том, что с помощьюuseEffectОн не блокирует обновление экрана браузером, благодаря чему ваше приложение выглядит более отзывчивым. В большинстве случаев эффекты не должны выполняться синхронно. В отдельных случаях (например, схемы измерения) существует отдельнаяuseLayoutEffectДля вашего использования его API идентиченuseEffectтакой же.

1. useEffect реализует componentDidMount и ComponentDidUpdate

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

import React, { useState, useEffect } from 'react'

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 使用useEffect传入一个回调函数使用类组件componentDidMount和componentDidUpdate功能
  useEffect(() => {
    console.log('parent effect');
  })
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增加</button>
    </div>
  )
}

export default Parent

2. Используйте useEffect для реализации функции componentDidMount.

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

import React, { useState, useEffect } from 'react'

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 第二个参数传入空数组,不需要根据其他值执行effect,只会在组件初次加载执行
  useEffect(() => {
    console.log('parent didMount');
  }, [])
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增加</button>
    </div>
  )
}

export default Parent

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

import React, { useState, useEffect } from 'react'

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 第二个参数传入含有count的数组,count更新时执行effect
  useEffect(() => {
    console.log('count update');
  }, [count])
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增加</button>
    </div>
  )
}

export default Parent

3. Используйте useEffect для реализации функции componentWillUnmout.

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

import React, { useState, useEffect } from 'react'

function Parent() {
  const [count, setCount] = useState(0)
  const handleAdd = () => setCount(count + 1)
  // 在useEffect中返回一个函数完成componentWillUnmoun的功能
  useEffect(() => {
    console.log('component mount');
    return () => {
      console.log('component unmount');
    }
  })
  return (
    <div>
      parent, {count}
      <button onClick={handleAdd}>增加</button>
    </div>
  )
}

export default Parent

3. использовать памятку

useMemo можно грубо понять как вычисляемое свойство в Vue.При изменении зависимого свойства автоматически выполняется вычисление внутри и возвращается окончательное значение (и кэшируется, и пересчитывается только при изменении зависимости).Это относительно дорого для производительности. Обязательно используйте useMemo, иначе он будет пересчитываться при каждом обновлении.

Пример:

import React, { useState, useMemo } from 'react'

function Parent() {
  const [count, setCount] = useState(0)
  const [price, setPrice] = useState(1)
  const handleCountAdd = () => setCount(count + 1)
  const handlePriceAdd = () => setPrice(price + 1)
  // 使用useMemo在count和price改变时自动计算总价
  const all = useMemo(() => count * price, [count, price])
  return (
    <div>
      parent, {count}
      <button onClick={handleCountAdd}>增加数量</button>
      <button onClick={handlePriceAdd}>增加价格</button>
      <p>count: {count}, price: {price} all: {all}</p>
    </div>
  )
}

export default Parent

Четыре, используйте обратный вызов

Разница между useCallback и useMemo заключается в том, что useMemo — это кешированное значение, а useCallback — это кешированная функция.Когда родительский компонент передает параметр дочернему компоненту как обычная функция, родительский компонент будет обновляться каждый раз, когда обновляется дочерний компонент, но в большинстве случаев обновление дочернего компонента не обновляется.Необходимо, в это время мы используем useCallback для определения функции и передаем эту функцию подкомпоненту, а подкомпонент будет обновляться в соответствии с зависимостями.

Пример:

import React, { useState, useCallback, useEffect } from 'react';
function Parent() {
    const [count, setCount] = useState(1);
    const [val, setVal] = useState('');
 
    const callback = useCallback(() => {
        return count;
    }, [count]);
    return <div>
        <h4>{count}</h4>
        <Child callback={callback}/>
        <div>
            <button onClick={() => setCount(count + 1)}>+</button>
            <input value={val} onChange={event => setVal(event.target.value)}/>
        </div>
    </div>;
}
 
function Child({ callback }) {
    const [count, setCount] = useState(() => callback());
    useEffect(() => {
      console.log(123);
        setCount(callback());
    }, [callback]);
    return <div>
        {count}
    </div>
}

export default Parent

Пять, используйте редьюсер

useReducer похож на редуктор в Redux, альтернативу useState. Он принимает редюсер вида (состояние, действие) => newState и возвращает текущее состояние и его метод отправки. (Если вы знакомы с Redux, вы уже знаете, как он работает.)

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

import React, { useReducer } from 'react'

function Parent() {
  const reducer = (state, action) => {
    switch (action.type) {
      case 'add':
        return {count: state.count + 1}
      case 'reduce':
        return {count: state.count - 1}
      default:
        throw new Error()
    }
  }
  let initialState = 0
  const init = (initialState) => ({
    count: initialState
  })
  // 第三个参数为惰性初始化函数,可以用来进行复杂计算返回最终的initialState,如果initialState较简单可以忽略此参数
  const [state, dispatch] = useReducer(reducer, initialState, init)
  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({type: 'add'})}>add</button>
      <button onClick={() => dispatch({type: 'reduce'})}>reduce</button>
    </div>
  )
}

export default Parent

Шесть, используйте контекст

useContext может реализовывать функции, аналогичные плагину react-redux.Компонент верхнего уровня использует createContext для создания контекста и использует для передачи контекста, а компонент нижнего уровня использует useContext для получения контекста.

Пример:

import React, { useState, createContext, useContext } from "react";

// 使用createContext来创建一个context
const CounterContext = createContext();

function Parent() {
  const [count, setCount] = useState(0);

  return (
    // 父组件使用<MyContext.Provider>传递context
    <CounterContext.Provider value={{ count, setCount }}>
      {count}
      <Child />
    </CounterContext.Provider>
  );
}

function Child() {
  // 子组件使用useContext来接收context
  const { count, setCount } = useContext(CounterContext);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>add</button>
    </div>
  );
}

export default Parent;

Семь, используйтеLayoutEffect

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

Восемь, используйтеRef

Используется для получения узлов DOM в React Hooks.

Пример:

import React, { useRef } from 'react'

function Parent() {
  // 使用useRef创建一个ref,并在标签中绑定到ref属性上
  const pRef = useRef(null)
  return (
    <div>
      <p ref={pRef}>content</p>
    </div>
  )
}

export default Parent

Девять нестандартных крючков

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

Пример:

import React, { useState } from "react";

// 自定义useCount的Hooks
function useCount() {
  const [count, setCount] = useState(0);
  return { count, setCount };
}

function Parent() {
  // 父组件使用,状态独立
  const { count, setCount } = useCount()
  return (
    <div>
      <p>parent</p>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>add</button>
      <Child />
    </div>
  );
}

function Child() {
  // 子组件使用,状态独立
  const { count, setCount } = useCount()
  return (
    <div>
      <p>child</p>
      <h1>{count}</h1>
      <button onClick={() => setCount(count + 2)}>add</button>
    </div>
  );
}

export default Parent;

Вышеизложенное является подробным объяснением базового использования React Hooks. По сути, React Hooks можно использовать для покрытия использования компонентов нашего класса. Чиновник также рекомендует использовать React Hooks для разработки новых проектов!