Серия React Advanced: как использовать хуки

React.js

Это первая статья из серии React Advanced. Эта серия будет включать в себя некоторые новые знания и принципы React. Те, кому интересно, могут продолжать обращать внимание.

Примечание. Хуки были официально выпущены только в React 16.8.

Зачем использовать хуки

Проблема вложения компонентов

Раньше, если нам нужно было извлечь некоторую повторяющуюся логику, мы выбирали HOC или реквизиты рендеринга. Но реализуя компоненты таким образом, когда вы открываете React DevTools, вы обнаружите, что компоненты заключены в различные другие компоненты. Этот метод сначала усложняет отладку, а также трудно добиться общего состояния.

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

проблема компонента класса

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

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

Как использовать хуки

Ранее я упоминал о некоторых преимуществах хуков, а теперь давайте перейдем к делу и изучим несколько часто используемых хуков, реализуя счетчик.

useState

useStateИспользование очень простое, введите начальныйstate, который возвращаетstateи изменитьstateФункция.

// useState 返回的 state 是个常量
// 每次组件重新渲染之后,当前 state 和之前的 state 都不相同
// 即使这个 state 是个对象
const [count, setCount] = useState(1)

setCountиспользование иsetStateТочно так же можно передать новое состояние или функцию.

setCount(2)
setCount(prevCount => prevCount + 1)

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

function Counter() {
  const [count, setCount] = React.useState(0)
  return (
    <div>
      Count: {count}
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </div>
  );
}

useEffect

Теперь наши требования к таймеру снова были обновлены и должны быть вобновление компонентаЧтобы распечатать текущий счет позже, в это время мы можем передатьuseEffectреализовать

function Counter() {
  const [count, setCount] = React.useState(0)
  
  React.useEffect(() => {
    console.log(count)
  })
  
  return (
    <div>
      Count: {count}
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
    </div>
  );
}

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

Кроме тогоuseEffectВы также можете вернуть функцию, которая работает какcomponentWillUnmount

function Counter() {
  const [count, setCount] = React.useState(0)
  
  React.useEffect(() => {
    console.log(count)
    return () => console.log('clean', count)
  })
  
  // ...
}

Каждый раз, когда мы обновляем счетчик, он печатается первымcleanэта строка журнала

Теперь наши требования снова были обновлены, требуя, чтобы мы распечатывали счет с задержкой в ​​​​две секунды после обновления счетчика. Достичь этого не может быть проще, давайте изменим егоuseEffectкод внутри

React.useEffect(() => {
    setTimeout(() => {
        console.log(count)
    }, 2000)
})

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

componentDidUpdate() {
    setTimeout(() => {
        console.log(this.state.count)
    }, 2000)
}

Для этого кода, если мы быстро нажмем кнопку, вы увидите те же несколько отсчетов, напечатанных после двухсекундной задержки. Это потому, что вuseEffectМы фиксируем правильный счет каждый раз с помощью замыкания. Но когдаcomponentDidUpdateв, черезthis.state.countТаким образом вы можете получить только самое последнее состояние, так как это объект.

Конечно, если вы просто хотите получить последниеstateтогда вы можете использоватьuseRefреализовать.

function Counter() {
  const [count, setCount] = React.useState(0)
  const ref = React.useRef(count)
  
  React.useEffect(() => {
    ref.current = count
    setTimeout(() => {
        console.log(ref.current)
    }, 2000)
  })
  
  //...
}

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

function Counter() {
  const [count, setCount] = React.useState(0)
  const ref = React.useRef()
  
  React.useEffect(() => {
    // 可以在重新赋值之前判断先前存储的数据和当前数据的区别
    ref.current = count
  })
  
  <div>
      Count: {count}
      PreCount: {ref.current}
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
  </div>
  
  //...
}

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

function Counter() {
  const [count, setCount] = React.useState();
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    setLoading(true);
    setTimeout(() => {
      setCount(1);
      setLoading(false);
    }, 2000);
  });
  return (
    <div>
      {!loading ? (
        <div>
          Count: {count}
          <button onClick={() => setCount(pre => pre + 1)}>+</button>
          <button onClick={() => setCount(pre => pre - 1)}>-</button>
        </div>
      ) : (
        <div>loading</div>
      )}
    </div>
  );
}

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

Для решения этой проблемы мы можемuseEffectВторой параметр решения

React.useEffect(() => {
    setLoading(true);
    setTimeout(() => {
      setCount(1);
      setLoading(false);
    }, 2000);
}, []);

Второй параметр передается в массиве зависимостей, который будет запущен снова, только если будут изменены зависимые свойства.useEffectисполнение. В приведенном выше примере мы передаем пустой массив для представления этогоuseEffectбудет выполняться только один раз.

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

const fetch = () => {
    setLoading(true);
    setTimeout(() => {
      setCount(1);
      setLoading(false);
    }, 2000);
}

React.useEffect(() => {
    fetch()
}, [fetch]);

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

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

const fetch = React.useCallback(() => {
    setLoading(true);
    setTimeout(() => {
      setCount(1);
      setLoading(false);
    }, 2000);
}, [])

React.useEffect(() => {
    fetch()
}, [fetch]);

Готово, мы сделали именно то, что могли бы сделать с компонентом класса с парой компонентов Hooks + function.

Суммировать

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

  • useState: передать нужное нам начальное состояние и вернутьпостоянныйСостояние и функции, меняющие состояние
  • useEffect: первый параметр принимает обратный вызов, который будет выполняться каждый раз при обновлении компонента, а обратный вызов может возвращать функцию, которая будет выполняться перед уничтожением каждого компонента. еслиuseEffectЕсть свойства, которые зависят от извне, и есть надежда, что свойства-зависимости не изменятся и не будут выполняться повторноuseEffectЗатем передайте массив как второй параметр, зависящий
  • useRef: если вам нужно место для хранения измененных данных
  • useCallback: если вам нужен обратный вызов, который не будет воссоздан с обновлениями компонентов

Кроме того, я также инкапсулировал несколько часто используемых Hooks API, если вам интересно, вы можете прочитать это.код, код в репозитории будет постоянно обновляться.

наконец

Из этой статьи мы узнали, как использовать хуки. Если у вас есть какие-либо вопросы, пожалуйста, не стесняйтесь общаться со мной в области комментариев.

Все мои циклы статей будут в моемGithubОн обновляется первым, и желающие могут обратить на него внимание. В этом году я в основном перепишу следующие три столбца.

  • Переучи JS
  • Реагировать продвинуто
  • Переопределить компонент

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