Я слышал, что вы до сих пор не понимаете React Hook?

JavaScript
Я слышал, что вы до сих пор не понимаете React Hook?

Хуки появились в React 16.8 впервые. Это позволяет вам использовать состояние и другие функции React без написания классов.

Из этого предложения на официальном сайте мы можем ясно понять, чтоHookДобавлены функциональные компонентыstateИспользование , в прошлом, функциональные компоненты не могли иметь своего состояния, только черезpropsтак же какcontextсделать свой собственныйUI, а в бизнес-логике некоторые сценарии должны использоватьstate, то мы можем определить функциональные компоненты только какclassкомпоненты. а теперь черезHook, мы можем легко поддерживать наше состояние в функциональных компонентах, не меняя их наclassкомпоненты.

React HooksПроблема, которую необходимо решить, — это совместное использование состояния.Под общим состоянием здесь понимается только повторное использование логики состояния, а не совместное использование данных. мы знаем, что вReact HooksРаньше для решения проблемы повторного использования логики состояния мы обычно использовалиhigher-order componentsа такжеrender-props, то если есть оба решения, то почемуReactРазработчики также должны представитьReact Hook? дляhigher-order componentsа такжеrender-props,React HookГде преимущества?

Пример хука реакции

Давайте сначала посмотримReactофициально даноReact Hookизdemo

import { useState } from 'React';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Посмотрим еще разReact HookЕсли да, то как добиться

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

Видно, что вReact Hookсередина,class ExampleКомпонент становится функциональным компонентом, но функциональный компонент имеет свое собственное состояние и также может обновлять свое состояние. Все это благодаряuseStateэтоHook,useStateвернет пару значений: текущее состояние и функцию, позволяющую обновить его, которую можно вызвать в обработчике событий или в другом месте. это что-то вродеclassкомпонентthis.setState, но новую не поставитstateи старыйstateобъединить

ReactРешения для повторного использования логики состояния

Hookэто еще одно решение для повторного использования логики состояния,ReactРазработчики постоянно предлагали и улучшали схему повторного использования логики состояния, начиная сMixinк компонентам более высокого порядка, чтобыRender Propsдо сих порHook, давайте кратко рассмотрим предыдущее решение

Mixinмодель

существуетReactНа начальном этапе было предложеноMixinШаблон для повторного использования логики между компонентами. существуетJavascript, мы можем поставитьMixinНаследование рассматривается как способ расширения функциональности коллекции.Каждый новый объект, который мы определяем, имеет прототип, от которого он может наследовать дополнительные свойства.Прототипы могут быть унаследованы от других объектов, но что более важно, возможность определять свойства для любого количества объектов.Мы может воспользоваться этим фактом для облегчения функционального повторного использования.

ReactсерединаmixinВ основном используется в двух совершенно не связанных компонентах, есть набор в принципе схожих функций, мы можем извлечь его черезmixinспособ ввода, чтобы добиться повторного использования кода. Например, в разных компонентах компоненты нужно обновлять время от времени, мы можем создатьsetInterval()для достижения этой функции, и когда компонент будет уничтожен, нам нужно выгрузить эту функцию. Таким образом, можно создать простойmixin, предоставляя простойsetInterval()функция, которая автоматически очищается при уничтожении компонента.

var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.forEach(clearInterval);
  }
};

var createReactClass = require('create-React-class');

var TickTock = createReactClass({
  mixins: [SetIntervalMixin], // 使用 mixin
  getInitialState: function() {
    return {seconds: 0};
  },
  componentDidMount: function() {
    this.setInterval(this.tick, 1000); // 调用 mixin 上的方法
  },
  tick: function() {
    this.setState({seconds: this.state.seconds + 1});
  },
  render: function() {
    return (
      <p>
        React has been running for {this.state.seconds} seconds.
      </p>
    );
  }
});

ReactDOM.render(
  <TickTock />,
  document.getElementById('example')
);

mixinНедостатки

  1. разныеmixinМожет быть взаимозависимым, и связь слишком сильная, что приводит к высоким затратам на техническое обслуживание в более поздний период.
  2. mixinИмена в списке могут конфликтовать и не могут использовать одно и то же имя.mixin
  3. mixinДаже если они начинаются с простого, со временем они могут усложняться лавинообразно, поскольку бизнес-сценарии умножаются.

Конкретные недостатки смотрите по этой ссылкеМиксины - это бич

потому чтоmixinиз этих недостатков существуют, вReactустарело вmixinрежим повторного использования кода,ReactВместо этого полностью рекомендуется использовать компоненты более высокого порядка.mixinрежим, покаES6сама не содержитmixinслужба поддержки. Поэтому, когда выReactиспользуется вES6 classне будет поддерживатьmixins.

компоненты более высокого порядка

компоненты более высокого порядка(HOC)даReactУсовершенствованный метод повторного использования логики компонентов в .HOCне самReact APIчастьReactШаблоны проектирования, сформированные путем объединения характеристик

Расширенные компоненты неReactкоторый предоставилAPI, ноReactТехнику использования компонентов более высокого порядка можно рассматривать как шаблон декоратора (Decorator Pattern)существуетReactреализация. Шаблон декоратора: для динамического назначения обязанностей объектам и расширения функциональности декораторы предоставляют более гибкую альтернативу наследованию.

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

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

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

function logProps(WrappedComponent) {
  return class extends React.Component {
    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);
    }
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
}

Render Propss

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

Компоненты с Render Props принимают функцию, которая возвращает элемент React, и вызывают ее вместо реализации собственной логики рендеринга.

Ниже мы предоставляемpropиз<Mouse>компоненты, которые могут динамически решать, что нужно визуализировать, чтобы<Mouse>Логика компонента и повторное использование состояния без изменения структуры его рендеринга.

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>移动鼠标!</h1>
        <Mouse render={mouse => (
          
        )}/>
      </div>
    );
  }
}

Однако обычно мы говоримRender PropsЭто потому, что шаблон называетсяRender Props, а не потому, что его нужно использоватьrenderправильноpropНазови это. Мы также можем выразить это

<Mouse>
  {mouse => (
    <Cat mouse={mouse} />
  )}
</Mouse>

Мотивация React Hook

React HookЭто еще одно совершенно новое решение, предложенное официальным сайтом.React HookПрежде давайте посмотримReact Hookмотивация

  1. Повторное использование логики состояния между компонентами сложно
  2. Сложные компоненты становятся трудными для понимания
  3. Трудно понятьclass

Вот мое понимание этих трех мотивов:

Повторное использование логики состояния между компонентами сложно, прежде чем мы передадим компонент более высокого порядка (Higher-Order Components) и свойства рендеринга (Render Propss) для решения сложной проблемы повторного использования логики состояния. Многие библиотеки используют эти шаблоны для повторного использования логики состояния, такой как наша обычно используемаяredux,React Router. Компоненты более высокого порядка и свойства рендеринга являются вложенными и совместно используемыми компонентами слой за слоем посредством комбинирования, что значительно увеличит иерархическую взаимосвязь нашего кода, что приведет к чрезмерно преувеличенной вложенности. отReactизdevtoolМы можем ясно видеть уровень вложенности, вызванный использованием этих двух режимов.

Сложные компоненты становятся трудными для понимания, В постоянно меняющихся бизнес-требованиях компоненты будут постепенно заполняться логикой состояния и побочными эффектами, и каждый жизненный цикл часто содержит некоторую неуместную логику. Мы обычно пишем код по единому принципу функций, функция обычно обрабатывает только одну вещь, но в функции-хуке жизненного цикла она обычно делает много вещей одновременно. Например, когда нам нужноcomponentDidMountинициировано вajaxВ этом жизненном цикле прописывается запрос на получение данных, а иногда и привязка события, и даже иногда нужноcomponentWillReceivePropsДанные следуютcomponentDidMountТакая же обработка.

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

непонятный класс, лично чувствую, используяclassКомпоненты все еще в порядке, если вы понимаетеclassизthisУказав на проблему привязки, на самом деле начать работу несложно. Поймите, что это неReactсвоеобразное поведение; это на самом деле связано сКак работают функции JavaScriptСвязанный. Так что просто поймиJSКак работает функция, на самом делеthisПривязка ни о чем. просто иногда для гарантииthisДело верное, мы обычно пишем много кода для привязкиthisЕсли вы забудете привязать, будет разныеbug. связыватьthisметод:

1.this.handleClick = this.handleClick.bind(this);
2.<button onClick={(e) => this.handleClick(e)}>
   Click me
 </button>

Поэтому для решения вышеуказанных проблемReact Hookбыл воспитан

использование хука состояния

Давайте вернемся к коду и посмотрим, как определить его в функциональном компоненте.state

import React, { useState } from 'React';
const [count, setCount] = useState(0);
  1. useStateчто ты сделал

    Мы видим, что в этой функции мы передаемuseStateопределяет «переменную состояния», которая связана сclassвнутри this.stateобеспечивает точно такую ​​же функциональность. Эквивалентно следующему коду

    class Example extends React.Component {
      constructor(props) {
        super(props);
        this.state = {
          count: 0
        };
      }
    
  2. useStateпараметр

    В коде мы передаем0так какuseStateпараметр, значение этого параметра будет рассматриваться какcountПервоначальный значение. Конечно, этот параметр не ограничивается передачей чисел и строк, вы можете передать объект как начальный.state. еслиstateНужно хранить значения нескольких переменных, а затем вызывать несколько разuseStateТолько что

  3. useStateвозвращаемое значение

    Возвращаемое значение: текущееstateи обновитьstateфункция, аналогичнаяclassв this.state.count а также this.setStateАналогично, с той лишь разницей, что вам нужно получить их парами. Видеть[count, setCount]Легко понять, что это метод записи деконструированного массива ES6. Эквивалентно следующему коду

    let _useState = useState(0);// 返回一个有两个元素的数组
    let count = _useState[0];// 数组里的第一个值
    let setCount = _useState[1];// 数组里的第二个值
    

Чтение значения состояния

Просто используйте переменные

предыдущее написание

<p>You clicked {this.state.count} times</p>

написать сейчас

<p>You clicked {count} times</p>

обновить состояние

пройти черезsetCountобновление функции

предыдущее написание

<button onClick={() => this.setState({ count: this.state.count + 1 })}>
    Click me
 </button>

написать сейчас

  <button onClick={() => setCount(count + 1)}>
    Click me
  </button>

здесьsetCountПолученный параметр представляет собой измененное новое значение состояния.

Объявить несколько переменных состояния

Мы можем использовать несколько раз в компонентеstate Hookобъявить несколькоstateПеременная

function ExampleWithManyStates() {
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
  // ...
}

Реакция предполагает, что когда вы звонили несколько разuseState, вы можете гарантировать, что порядок, в котором они называются, одинаково каждый раз, когда вы визуете

ЗачемReactПонятно, что порядок вызова при каждом их отображении неизменен.Hookкритический вопрос

Правила хука

HookСуть в томJavaScriptФункция, но когда вы используете его, вам нужно следовать двум правилам. а такжеReactНеобходимо соблюдать эти два правила, иначе будут исключенияbug

  1. Использовать только на верхнем уровнеHook

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

  1. только вReactвызов функцииHook

Не вызывайте хуки в обычных функциях JavaScript.

Причина этих двух правил в том, что мы можем использовать несколькоState HookилиEffect Hook,Reactполагаться наHookпорядок звонков, чтобы узнать, какойstateчто соответствуетuseState

function Form() {
  const [name1, setName1] = useState('Arzh1');
  const [name2, setName2] = useState('Arzh2');
  const [name3, setName3] = useState('Arzh3');
  // ...
}
// ------------
// 首次渲染
// ------------
useState('Arzh1')       // 1. 使用 'Arzh1' 初始化变量名为 name1 的 state
useState('Arzh2')       // 2. 使用 'Arzh2' 初始化变量名为 name2 的 state
useEffect('Arzh3')     	// 3. 使用 'Arzh3' 初始化变量名为 name3 的 state

// -------------
// 二次渲染
// -------------
useState('Arzh1')        // 1. 读取变量名为 name1 的 state(参数被忽略)
useState('Arzh2')        // 2. 读取变量名为 name2 的 state(参数被忽略)
useEffect('Arzh3')       // 3. 读取变量名为 name3 的 state(参数被忽略)

если мы нарушимReactправила, использующие условный рендеринг

if (name !== '') {
    const [name2, setName2] = useState('Arzh2');
}

Допустим, в первый раз(name !== '')дляtrue, выполнить этоHook, второй рендер(name !== '')дляfalse, не делайте этогоHook,ТакHookПорядок вызовов изменится, в результате чегоbug

useState('Arzh1')        // 1. 读取变量名为 name1 的 state
//useState('Arzh2')        // 2. Hook被忽略
useEffect('Arzh3')       // 3. 读取变量名为 name2(之前为name3) 的 state

Reactне знаю второйuseState изHookчто надо вернуть.Reactподумал бы, что в компоненте второйHookВызов подобен последнему рендерингу, соответствующемуarzh2 изuseState, но не так. так вот почемуReactОбязательныйHookИспользование должно следовать этим двум правилам, и мы можем использоватьeslint-plugin-React-Hooksдля обеспечения соблюдения ограничений

Использование хука эффекта

Мы добавляем в приведенный выше кодEffect Hookиспользование, добавление побочных эффектов к функциональным компонентам, изменение заголовка веб-страницы

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

Если вы знакомы с функциями жизненного цикла класса React, вы можете поместитьuseEffectКрюк какcomponentDidMount,componentDidUpdate а также componentWillUnmountСочетание этих трех функций.

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

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

  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }

  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }

Это то, что мы упоминали вышеReact HookОдин из вторых проблемных источников мотивации, необходимость вызывать один и тот же код как при первом рендере, так и при последующих рендерах.

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

явные побочные эффекты

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

  componentDidMount() {
    this.pollingNewStatus()
  }

  componentWillUnmount() {
    this.unPollingNewStatus()
  }

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

  useEffect(() => {
    pollingNewStatus()
    //告诉React在每次渲染之前都先执行cleanup()
    return function cleanup() {
      unPollingNewStatus()
    };
  });

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

Оптимизация производительности эффектов

useEffectФактически, он выполняется при каждом обновлении, что в некоторых случаях может вызвать проблемы с производительностью. тогда мы можем пропуститьEffectВыполнить оптимизацию производительности. существуетclassкомпонент, мы можем передатьcomponentDidUpdateдобавить пару вprevProps или prevStateСравнительно-логическое решение

componentDidUpdate(prevProps, prevState) {
  if (prevState.count !== this.state.count) {
    document.title = `You clicked ${this.state.count} times`;
  }
}

существуетEffect, мы можем добавитьEffectВторой параметр , если нет изменений, пропустить обновление

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

Другие крючки

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

Справочная статья

  1. Освоение React Hooks за 30 минут
  2. Практика использования React-хуков
  3. От Mixin к HOC к крючку