[Углубленное реагирование] От Mixin до HOC и Hook

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

Управляемое чтение

Фронтенд разработка идет очень быстро, страницы и компоненты становятся все сложнее и сложнее, как лучше реализовать状态逻辑复用Это всегда было важной частью приложения, которая напрямую связана с качеством приложения и простотой обслуживания.

В этой статье описываетсяReactИспользуются три реализации状态逻辑复用технологий и анализирует принципы их реализации, методы использования, практические приложения и способы их использования.

Эта статья немного длиннее, и ниже приведена карта ума этой статьи, вы можете прочитать ее с самого начала или выбрать интересующую вас часть для чтения:

image

Шаблоны дизайна Mixin

image

Mixin(Смешивание) — это способ расширения функции сбора, которая по существу копирует свойства одного объекта в другой объект, но вы можете копировать任意多объект任意个метод к новому объекту, который继承Не реализована. Его внешний вид в основном предназначен для решения проблемы кодового мультиплексирования.

Многие библиотеки с открытым исходным кодом предоставляютMixinреализация, напримерUnderscoreиз_.extendметод,JQueryизextendметод.

использовать_.extendМетод повторного использования кода:

var LogMixin = {
  actionLog: function() {
    console.log('action...');
  },
  requestLog: function() {
    console.log('request...');
  },
};
function User() {  /*..*/  }
function Goods() {  /*..*/ }
_.extend(User.prototype, LogMixin);
_.extend(Goods.prototype, LogMixin);
var user = new User();
var good = new Goods();
user.actionLog();
good.requestLog();

Мы можем попробовать вручную написать простойMixinметод:

function setMixin(target, mixin) {
  if (arguments[2]) {
    for (var i = 2, len = arguments.length; i < len; i++) {
      target.prototype[arguments[i]] = mixin.prototype[arguments[i]];
    }
  }
  else {
    for (var methodName in mixin.prototype) {
      if (!Object.hasOwnProperty(target.prototype, methodName)) {
        target.prototype[methodName] = mixin.prototype[methodName];
      }
    }
  }
}
setMixin(User,LogMixin,'actionLog');
setMixin(Goods,LogMixin,'requestLog');

ты можешь использовать этоsetMixinМетод расширяет любой метод любого объекта на целевой объект.

Применение миксинов в React

Reactтакже обеспечиваетMixinРеализация, если существует аналогичная функциональность, которая совершенно другой, мы можем ввести мультиплексирование кода, конечно, только используется только.createClassсоздаватьReactкомпонент можно использовать только потому, что вReactкомпонентes6Это устарело в письменной форме.

Например, в следующем примере многие компоненты или страницы должны записывать поведение пользователя, показатели производительности и т. д. Если ввести логику записи логов в каждый компонент, будет генерироваться много повторяющегося кода.MixinМы можем решить эту проблему:

var LogMixin = {
  log: function() {
    console.log('log');
  },
  componentDidMount: function() {
    console.log('in');
  },
  componentWillUnmount: function() {
    console.log('out');
  }
};

var User = React.createClass({
  mixins: [LogMixin],
  render: function() {
    return (<div>...</div>)
  }
});

var Goods = React.createClass({
  mixins: [LogMixin],
  render: function() {
    return (<div>...</div>)
  }
});

Вред Миксина

ReactОфициальная документация находится наMixins Considered Harmfulупоминается в статьеMixinприносит вред:

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

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

орнамент

image

декоратор (decorator) может динамически добавлять обязанности к объектам во время выполнения программы без изменения самих объектов. Декораторы — это более легкий и гибкий подход, чем наследование.

Компоненты высшего порядка (HOC)

image

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

Компоненты высшего порядка (HOC)ДаReactРасширенные методы в , чтобы повторно использовать логику компонента. Но компоненты более высокого порядка сами по себе неReact API.它只是一种模式,这种模式是由ReactЕго собственная композиционная природа неизбежно возникает.

function visible(WrappedComponent) {
  return class extends Component {
    render() {
      const { visible, ...props } = this.props;
      if (visible === false) return null;
      return <WrappedComponent {...props} />;
    }
  }
}

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

Далее мы подробно рассмотрим следующие аспекты:HOC.

image

Как реализуется HOC

Риелтером

Функция возвращает компонент, который мы определяем сами, а затем вrenderвозвращает компонент для переноса, чтобы мы могли проксировать все входящиеprops, и решить, как визуализировать. Фактически, компонент более высокого порядка, сгенерированный таким образом, является родительским компонентом исходного компонента. Вышеприведенная функцияvisibleтолько одинHOCКак реализован прокси свойства.

function proxyHOC(WrappedComponent) {
  return class extends Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
}

Улучшенные предметы по сравнению с нативными компонентами:

  • Действует на все входящиеprops
  • Жизненный цикл активного компонента
  • операционные компонентыstaticметод
  • Получатьrefs

обратное наследование

Возвращает компонент, который наследует исходный компонент, вrenderвызвать исходный компонентrender. Поскольку исходный компонент наследуется, к исходному компоненту можно получить доступ через этот生命周期、props、state、renderи т. д., он может манипулировать большим количеством свойств, чем прокси-сервер свойств.

function inheritHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      return super.render();
    }
  }
}

Усовершенствованные элементы по сравнению с родными компонентами:

  • Действует на все входящиеprops
  • Жизненный цикл активного компонента
  • операционные компонентыstaticметод
  • Получатьrefs
  • оперативныйstate
  • Может отображать угон

Что может ХОК?

Комбинированный рендеринг

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

Реализация атрибутивным агентом

function stylHOC(WrappedComponent) {
  return class extends Component {
    render() {
      return (<div>
        <div className="title">{this.props.title}</div>
        <WrappedComponent {...this.props} />
      </div>);
    }
  }
}

Реализовано через обратное наследование

function styleHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      return <div>
        <div className="title">{this.props.title}</div>
        {super.render()}
      </div>
    }
  }
}

условный рендеринг

Определяет, визуализируется ли исходный компонент в соответствии с определенным свойством

Реализовано через прокси свойства

function visibleHOC(WrappedComponent) {
  return class extends Component {
    render() {
      if (this.props.visible === false) return null;
      return <WrappedComponent {...props} />;
    }
  }
}

Реализовано через обратное наследование

function visibleHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      if (this.props.visible === false) {
        return null
      } else {
        return super.render()
      }
    }
  }
}

Реквизит для действий

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

Реализовано через прокси свойства

function proxyHOC(WrappedComponent) {
  return class extends Component {
    render() {
      const newProps = {
        ...this.props,
        user: 'ConardLi'
      }
      return <WrappedComponent {...newProps} />;
    }
  }
}

получить реф.

Исходная компонента может быть получена в компоненте более высокого порядкаref,пройти черезrefПолучите мощность компонента, например следующий код, когда инициализация программы завершена, вызывается метод журнала исходного компонента. (Я не умею пользоваться рефами, пожалуйста👇Refs & DOM)

Реализовано через прокси свойства

function refHOC(WrappedComponent) {
  return class extends Component {
    componentDidMount() {
      this.wapperRef.log()
    }
    render() {
      return <WrappedComponent {...this.props} ref={ref => { this.wapperRef = ref }} />;
    }
  }
}

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

государственное управление

Извлеките состояние исходного компонента вHOCУправляемый в следующем коде, мы будемInputизvalueизвлечь вHOCУправляйте им в элементе управления, делая его контролируемым компонентом, не влияя на его использование.onChangeметод для выполнения некоторых других операций. На основе этого подхода мы можем реализовать простой双向绑定, пожалуйста, посмотридвусторонняя привязка.

Реализовано через прокси свойства

function proxyHoc(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = { value: '' };
    }

    onChange = (event) => {
      const { onChange } = this.props;
      this.setState({
        value: event.target.value,
      }, () => {
        if(typeof onChange ==='function'){
          onChange(event);
        }
      })
    }

    render() {
      const newProps = {
        value: this.state.value,
        onChange: this.onChange,
      }
      return <WrappedComponent {...this.props} {...newProps} />;
    }
  }
}

class HOC extends Component {
  render() {
    return <input {...this.props}></input>
  }
}

export default proxyHoc(HOC);

рабочее состояние

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

Реализовано через обратное наследование

function debugHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      console.log('props', this.props);
      console.log('state', this.state);
      return (
        <div className="debuging">
          {super.render()}
        </div>
      )
    }
  }
}

надHOCсуществуетrenderгенерал-лейтенантpropsа такжеstateРаспечатайте его, его можно использовать как этап отладки, конечно же вы можете написать в нем больше отладочного кода. Представьте, что вы просто добавляете компонент, который хотите отлаживать.@debugВы можете отлаживать компонент без написания большого количества избыточного кода каждый раз, когда отлаживаете его. (Если вы еще не знаете, как использовать HOC, пожалуйста 👇Как использовать ХОК)

рендеринг угона

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

На самом деле вышеизложенноеКомбинированный рендеринга такжеусловный рендерингобе渲染劫持Своего рода путем обратного наследования можно не только достичь двух вышеуказанных пунктов, но и напрямую增强по оригинальным компонентамrenderгенерируется функциейReact元素.

Реализовано через обратное наследование

function hijackHOC(WrappedComponent) {
  return class extends WrappedComponent {
    render() {
      const tree = super.render();
      let newProps = {};
      if (tree && tree.type === 'input') {
        newProps = { value: '渲染被劫持了' };
      }
      const props = Object.assign({}, tree.props, newProps);
      const newTree = React.cloneElement(tree, props, tree.props.children);
      return newTree;
    }
  }
}

Обратите внимание на инструкции выше, которые я использовал增强вместо更改.renderВнутри функция фактически вызываетReact.creatElementпроизведеноReact元素:

image
Хотя мы можем получить его, мы не можем напрямую изменить свойства в нем, мы передаемgetOwnPropertyDescriptorsфункция для печати элементов конфигурации:

image

Можно обнаружить, что всеwritableсвойства настроены какfalse, т. е. все свойства неизменны. (Если у вас есть какие-либо вопросы об этих элементах конфигурации, пожалуйста, 👇defineProperty)

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

React.cloneElement()клонировать и вернуть новыйReact元素,использоватьelementв качестве отправной точки. Результирующий элемент будет иметь неглубокое слияние реквизитов исходного элемента с новыми реквизитами. Новые дети заменяют существующих детей. Ключ и ссылка исходного элемента будут сохранены.

React.cloneElement()почти эквивалентно:

<element.type {...element.props} {...props}>{children}</element.type>

Как использовать ХОК

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

class myComponent extends Component {
  render() {
    return (<span>原组件</span>)
  }
}
export default inheritHOC(myComponent);

compose

В практических приложениях компонент может использоваться несколькимиHOCвсе улучшения, которые мы используем,HOCРасширенные компоненты, одолжить один装饰模式Это может быть легче понять с диаграммой, чтобы проиллюстрировать:

image

Допустим, теперь у нас естьlogger,visible,styleбольше, чем одинHOC, теперь для одновременного повышенияInputКомпоненты:

logger(visible(style(Input)))

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

const compose = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
compose(logger,visible,style)(Input);

composeФункция возвращает функцию, которая объединяет все функции,compose(f, g, h)а также(...args) => f(g(h(...args)))это то же самое.

Многие сторонние библиотеки предоставляют аналогичныеcomposeфункция, такая какlodash.flowRight,Reduxкоторый предоставилcombineReducersфункция и т. д.

Decorators

Мы также можем использоватьES7предоставил намDecoratorsДавайте сделаем наше письмо более элегантным:

@logger
@visible
@style
class Input extends Component {
  // ...
}

DecoratorsдаES7Предложение для , которое еще не стандартизировано, но в настоящее времяBabelТранскодер уже поддерживается, нам нужно настроить его заранееbabel-plugin-transform-decorators-legacy:

"plugins": ["transform-decorators-legacy"]

Также можно комбинировать вышеперечисленноеcomposeИспользование функции:

const hoc = compose(logger, visible, style);
@hoc
class Input extends Component {
  // ...
}

Практическое применение HOC

Вот некоторые из реальных тестов, которые у меня есть в производствеHOCИз-за длины статьи код был сильно упрощен, если у вас есть какие-либо вопросы, пожалуйста, укажите в области комментариев:

управление журналом

На самом деле это одно из самых распространенных приложений, несколько компонентов имеют схожую логику, нам нужно повторно использовать повторяющуюся логику. в официальной документацииCommentListВ примере также решается проблема повторного использования кода.Он написан очень подробно.Если интересно,можете 👇Используйте компоненты высшего порядка (HOC) для решения сквозных проблем.

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

function logHoc(WrappedComponent) {
  return class extends Component {
    componentWillMount() {
      this.start = Date.now();
    }
    componentDidMount() {
      this.end = Date.now();
      console.log(`${WrappedComponent.dispalyName} 渲染时间:${this.end - this.start} ms`);
      console.log(`${user}进入${WrappedComponent.dispalyName}`);
    }
    componentWillUnmount() {
      console.log(`${user}退出${WrappedComponent.dispalyName}`);
    }
    render() {
      return <WrappedComponent {...this.props} />
    }
  }
}

в наличии, контроль доступа

function auth(WrappedComponent) {
  return class extends Component {
    render() {
      const { visible, auth, display = null, ...props } = this.props;
      if (visible === false || (auth && authList.indexOf(auth) === -1)) {
        return display
      }
      return <WrappedComponent {...props} />;
    }
  }
}

authListЭто список всех разрешений, которые мы запрашиваем у бэкэнда при входе в программу.Когда разрешения, требуемые компонентом, отсутствуют в списке или установленыvisibleдаfalse, мы отображаем его как входящий стиль компонента, илиnull. Мы можем применить любой компонент, который требует проверки разрешенияHOC:

  @auth
  class Input extends Component {  ...  }
  @auth
  class Button extends Component {  ...  }

  <Button auth="user/addUser">添加用户</Button>
  <Input auth="user/search" visible={false} >添加用户</Input>

двусторонняя привязка

существуетvue中,绑定一个变量后可实现双向数据绑定,即表单中的值改变后绑定的变量也会自动改变。 а такжеReactВ , такая обработка не выполняется, по умолчанию элементы формы非受控组件. После привязки состояния к элементу формы его часто нужно прописывать вручнуюonChangeспособ переписать его как受控组件, эти повторяющиеся операции очень болезненны в случае очень большого количества элементов формы.

Мы можем реализовать простую двустороннюю привязку с помощью компонентов высокого порядка.Код немного длиннее, что можно понять в сочетании со следующей картой ума.

image

Сначала мы настраиваемFormКомпонент, который используется для переноса всех компонентов формы, которые необходимо обернуть, черезcontexПредоставьте два свойства дочерним компонентам:

  • model:токFormВсе данные, управляемые формойnameа такжеvalueсостав, например{name:'ConardLi',pwd:'123'}.modelОн может быть импортирован извне, а также может управляться сам по себе.
  • changeModel:Изменятьmodelодин изnameценность .

class Form extends Component {
  static childContextTypes = {
    model: PropTypes.object,
    changeModel: PropTypes.func
  }
  constructor(props, context) {
    super(props, context);
    this.state = {
      model: props.model || {}
    };
  }
  componentWillReceiveProps(nextProps) {
    if (nextProps.model) {
      this.setState({
        model: nextProps.model
      })
    }
  }
  changeModel = (name, value) => {
    this.setState({
      model: { ...this.state.model, [name]: value }
    })
  }
  getChildContext() {
    return {
      changeModel: this.changeModel,
      model: this.props.model || this.state.model
    };
  }
  onSubmit = () => {
    console.log(this.state.model);
  }
  render() {
    return <div>
      {this.props.children}
      <button onClick={this.onSubmit}>提交</button>
    </div>
  }
}

Следующие определения для двусторонней привязкиHOC, который проксирует формуonChangeсвойства иvalueАтрибуты:

  • происходитьonChangeВызов верхнего уровня при возникновении событияFormизchangeModelспособ изменитьcontextсерединаmodel.
  • при рендерингеvalueИзменено сcontextзначение взято из .
function proxyHoc(WrappedComponent) {
  return class extends Component {
    static contextTypes = {
      model: PropTypes.object,
      changeModel: PropTypes.func
    }

    onChange = (event) => {
      const { changeModel } = this.context;
      const { onChange } = this.props;
      const { v_model } = this.props;
      changeModel(v_model, event.target.value);
      if(typeof onChange === 'function'){onChange(event);}
    }

    render() {
      const { model } = this.context;
      const { v_model } = this.props;
      return <WrappedComponent
        {...this.props}
        value={model[v_model]}
        onChange={this.onChange}
      />;
    }
  }
}
@proxyHoc
class Input extends Component {
  render() {
    return <input {...this.props}></input>
  }
}

Приведенный выше код является лишь краткой частью, за исключениемinput, мы также можемHOCиспользуется дляselectи другие компоненты формы, вы даже можете добавить вышеуказанныеHOCсовместимый сspan、tableОжидание компонента дисплея, это может значительно упростить код, и давайте сэкономим много государственных управлений, используйте следующее:

export default class extends Component {
  render() {
    return (
      <Form >
        <Input v_model="name"></Input>
        <Input v_model="pwd"></Input>
      </Form>
    )
  }
}

проверка формы

Основываясь на приведенном выше примере с двусторонней привязкой, давайте возьмем еще один валидатор формы. Валидатор формы может содержать функции проверки и подсказки. При сбое проверки отображается сообщение об ошибке:

function validateHoc(WrappedComponent) {
  return class extends Component {
    constructor(props) {
      super(props);
      this.state = { error: '' }
    }
    onChange = (event) => {
      const { validator } = this.props;
      if (validator && typeof validator.func === 'function') {
        if (!validator.func(event.target.value)) {
          this.setState({ error: validator.msg })
        } else {
          this.setState({ error: '' })
        }
      }
    }
    render() {
      return <div>
        <WrappedComponent onChange={this.onChange}  {...this.props} />
        <div>{this.state.error || ''}</div>
      </div>
    }
  }
}
const validatorName = {
  func: (val) => val && !isNaN(val),
  msg: '请输入数字'
}
const validatorPwd = {
  func: (val) => val && val.length > 6,
  msg: '密码必须大于6位'
}
<HOCInput validator={validatorName} v_model="name"></HOCInput>
<HOCInput validator={validatorPwd} v_model="pwd"></HOCInput>

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

Редукс подключить

image

в редуксеconnect, на самом делеHOC, ниже приведена упрощенная версияconnectвыполнить:

export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {
  class Connect extends Component {
    static contextTypes = {
      store: PropTypes.object
    }

    constructor () {
      super()
      this.state = {
        allProps: {}
      }
    }

    componentWillMount () {
      const { store } = this.context
      this._updateProps()
      store.subscribe(() => this._updateProps())
    }

    _updateProps () {
      const { store } = this.context
      let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props): {} 
      let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props) : {} 
      this.setState({
        allProps: {
          ...stateProps,
          ...dispatchProps,
          ...this.props
        }
      })
    }

    render () {
      return <WrappedComponent {...this.state.allProps} />
    }
  }
  return Connect
}

Код очень понятный,connectФункция на самом деле делает одну вещь,mapStateToPropsа такжеmapDispatchToPropsОни деконструируются и передаются в исходный компонент, чтобы мы могли использовать их непосредственно в исходном компоненте.propsПолучатьstateтак же какdispatchфункция.

Меры предосторожности при использовании HOC

Предостережение — копирование статического свойства

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

function proxyHOC(WrappedComponent) {
  class HOCComponent extends Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
  HOCComponent.staticMethod = WrappedComponent.staticMethod;
  // ... 
  return HOCComponent;
}

Если оригинальный компонент имеет много статических свойств, этот процесс очень болезненлен, но вы должны понимать, что необходимо повысить статические свойства всех компонентов, заключается в том, что мы можем использоватьhoist-non-react-staticsчтобы помочь нам решить эту проблему, он может автоматически копировать все не-ReactСтатический метод используется следующим образом:

import hoistNonReactStatic from 'hoist-non-react-statics';
function proxyHOC(WrappedComponent) {
  class HOCComponent extends Component {
    render() {
      return <WrappedComponent {...this.props} />;
    }
  }
  hoistNonReactStatic(HOCComponent,WrappedComponent);
  return HOCComponent;
}

Предостережение: проходные рефери

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

Компоненты более высокого порядка не похожи на Passthroughpropsчто будетrefsПрозрачная передача, мы можем использовать функцию обратного вызова для завершенияrefДоставка:

function hoc(WrappedComponent) {
  return class extends Component {
    getWrappedRef = () => this.wrappedRef;
    render() {
      return <WrappedComponent ref={ref => { this.wrappedRef = ref }} {...this.props} />;
    }
  }
}
@hoc
class Input extends Component {
  render() { return <input></input> }
}
class App extends Component {
  render() {
    return (
      <Input ref={ref => { this.inpitRef = ref.getWrappedRef() }} ></Input>
    );
  }
}

React 16.3версия обеспечиваетforwardRef APIчтобы помочь намrefsпроход, так что мы попадаем на компонент более высокого порядкаrefэто оригинальный компонентref, без необходимости передавать его вручную, если вашReactверсия больше, чем16.3, вы можете использовать следующие методы:

function hoc(WrappedComponent) {
  class HOC extends Component {
    render() {
      const { forwardedRef, ...props } = this.props;
      return <WrappedComponent ref={forwardedRef} {...props} />;
    }
  }
  return React.forwardRef((props, ref) => {
    return <HOC forwardedRef={ref} {...props} />;
  });
}

Предостережение — не создавайте компоненты более высокого порядка внутри метода рендеринга.

React DiffПринципы алгоритма таковы:

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

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

Соглашение - не изменять исходный компонент

Официальная документация объясняет компоненты более высокого порядка:

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

Давайте снова посмотрим на определение чистой функции:

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

Если мы изменим исходный компонент в компоненте более высокого порядка, например, в следующем коде:

InputComponent.prototype.componentWillReceiveProps = function(nextProps) { ... }

Это нарушает наше соглашение о компонентах более высокого порядка, а также меняет первоначальное намерение использовать компоненты более высокого порядка: мы используем компоненты более высокого порядка для增强вместо改变оригинальные компоненты.

Конвенция - прозрачно передавать нерелевантный реквизит

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

function visible(WrappedComponent) {
  return class extends Component {
    render() {
      const { visible, ...props } = this.props;
      if (visible === false) return null;
      return <WrappedComponent {...props} />;
    }
  }
}

мы используем толькоvisibleсвойства для управления отображением компонента можно скрыть, поставить другиеpropsПередайте это.

соглашение-displayName

В использованииReact Developer ToolsПри отладке, если мы используемHOC, интерфейс отладки может стать очень трудным для чтения, как в следующем коде:

@visible
class Show extends Component {
  render() {
    return <h1>我是一个标签</h1>
  }
}
@visible
class Title extends Component {
  render() {
    return <h1>我是一个标题</h1>
  }
}

image

Для облегчения отладки мы можем вручнуюHOCуказатьdisplayName, Официальная рекомендацияHOCName(WrappedComponentName):

static displayName = `Visible(${WrappedComponent.displayName})`

image

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

Мотивация использования HOC

Пересмотрите вышеупомянутоеMixinРиски, связанные с:

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

image

а такжеHOCПоявление может решить эти проблемы:

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

Недостатки HOC

  • HOCЕго необходимо обернуть или вложить в исходный компонент, если он используется в больших количествах.HOC, создаст большое количество вложений, что очень усложнит отладку.
  • HOCможет быть угнанprops, также может вызвать конфликты, если соглашение не соблюдается.

Hooks

image

HooksдаReact v16.7.0-alphaДобавлены новые функции в . это позволяет вамclassиспользовать кромеstateи другиеReactхарактеристика.

использоватьHooks, вы можете включитьstateЛогика абстрагируется от компонента, что упрощает ее тестирование. в то же время,HooksМожет помочь вам повторно использовать эту логику, не переписывая структуру компонента. Таким образом, его также можно использовать в качестве реализации状态逻辑复用строить планы.

Прочтите главы нижеМотивация использования хуковВы можете обнаружить, что он может решить обаMixinа такжеHOCвызванные проблемы.

Официальные крючки

State Hook

Мы хотим использоватьclassРеализация компонентов计数器функцию, мы могли бы написать:

export default class Count extends 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>
    )
  }
}

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

export default function HookTest() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => { setCount(count + 1); setNumber(number + 1); }}>
        Click me
        </button>
    </div>
  );
}

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

Effect Hook

Эффект-хук позволяет выполнять некоторые операции с побочными эффектами в функциональных компонентах.

параметр

useEffectМетод принимает два параметра:

  • 1. Функция обратного вызова: один раз в первом компонентеrenderКаждый раз после негоupdateпосле бега,Reactгарантировано вDOMОбратный вызов не будет выполняться, пока обновление не будет завершено.
  • 2. Зависимость от состояния (массив): если настроена зависимость от состояния, функция обратного вызова будет вызываться только при обнаружении настроенного изменения состояния.
  useEffect(() => {
    // 只要组件render后就会执行
  });
  useEffect(() => {
    // 只有count改变时才会执行
  },[count]);

возвращаемое значение обратного вызова

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

export default function HookTest() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log('执行...', count);
    return () => {
      console.log('清理...', count);
    }
  }, [count]);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => { setCount(count + 1); setNumber(number + 1); }}>
        Click me
        </button>
    </div>
  );
}

Выполните приведенный выше код и нажмите кнопку несколько раз, вы получите следующий результат:

image

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

 页面渲染...1
 执行... 1
 页面渲染...2
 清理... 1
 执行... 2
 页面渲染...3
 清理... 2
 执行... 3
 页面渲染...4
 清理... 3
 执行... 4

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

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

    var flag = 1;
    var clean;
    function effect(flag) {
      return function () {
        console.log(flag);
      }
    }
    clean = effect(flag);
    flag = 2;
    clean();
    clean = effect(flag);
    flag = 3;
    clean();
    clean = effect(flag);

    // 执行结果

    effect... 1
    clean... 1
    effect... 2
    clean... 2
    effect... 3

фиктивный компонентDidMount

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

function useDidMount(callback) {
  useEffect(callback, []);
}

Официальный не рекомендует этот способ написания, потому что это может привести к некоторым ошибкам.

фиктивный компонентWillUnmount

function useUnMount(callback) {
  useEffect(() => callback, []);
}

В отличие от componentDidMount или componentDidUpdate, эффекты, используемые в useEffect, не блокируют отображение страницы браузером. Это сделает ваше приложение более плавным.

ref Hook

использоватьuseRef Hook, вы можете легко получитьdomизref.

export default function Input() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    inputEl.current.focus();
  };
  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </div>
  );
}

УведомлениеuseRef()можно использовать не только для полученияrefиспользовать, использоватьuseRefпроизведеноrefизcurrentСвойства изменяемы, что означает, что вы можете использовать их для хранения произвольного значения.

фиктивный компонентDidUpdate

componentDidUpdateэквивалентно удалению первого вызоваuseEffect, мы можем использоватьuseRefСоздайте флаг, чтобы записать, является ли это первым выполнением:

function useDidUpdate(callback, prop) {
  const init = useRef(true);
  useEffect(() => {
    if (init.current) {
      init.current = false;
    } else {
      return callback();
    }
  }, prop);
}

Меры предосторожности при использовании хуков

Сфера использования

  • только вReactФункциональный компонент или пользовательскийHookиспользуется вHook.

Hookпредлагается в основном для решенияclassРяд проблем с компонентами, поэтому мы можемclassКомпоненты используют его.

декларативные ограничения

  • Не вызывайте хуки в циклах, условных выражениях или вложенных функциях.

HookРеализуется через массив, каждый разuseStateизменит индекс,ReactПорядок вызова необходимо использовать для корректного обновления соответствующего состояния, еслиuseStateЗаключенный в циклы или условные операторы, он может вызвать путаницу в вызывающей последовательности, что приведет к непредвиденным ошибкам.

мы можем установитьeslintПлагины, которые помогут нам избежать этих проблем.

// 安装
npm install eslint-plugin-react-hooks --save-dev
// 配置
{
  "plugins": [
    // ...
    "react-hooks"
  ],
  "rules": {
    // ...
    "react-hooks/rules-of-hooks": "error"
  }
}

Пользовательский крючок

как описано вышеHOCа такжеmixinТочно так же мы можем настроитьHookИзвлеките аналогичную логику состояния в компонентах.

настроитьHookОчень просто, нам нужно только определить функцию и поставить соответствующее требуемое состояние иeffectупакованы, и в то же время,HookОни также могут ссылаться друг на друга. использоватьuseимя нестандартное в началеHook, что делает его удобнымeslintпроверка.

Ниже мы рассмотрим несколько конкретныхHookУпаковка:

управление журналом

Мы можем использовать жизненный цикл, инкапсулированный вышеHook.

const useLogger = (componentName, ...params) => {
  useDidMount(() => {
    console.log(`${componentName}初始化`, ...params);
  });
  useUnMount(() => {
    console.log(`${componentName}卸载`, ...params);
  })
  useDidUpdate(() => {
    console.log(`${componentName}更新`, ...params);
  });
};

function Page1(props){
  useLogger('Page1',props);
  return (<div>...</div>)
}

Изменить название

Изменить страницы в соответствии с разными именами страницtitle:

function useTitle(title) {
  useEffect(
    () => {
      document.title = title;
      return () => (document.title = "主页");
    },
    [title]
  );
}
function Page1(props){
  useTitle('Page1');
  return (<div>...</div>)
}

двусторонняя привязка

мы сформируемonChangeЛогика извлекается и упаковывается вHook, чтобы можно было повторно использовать все компоненты формы, для которых требуется двусторонняя привязка:

function useBind(init) {
  let [value, setValue] = useState(init);
  let onChange = useCallback(function(event) {
    setValue(event.currentTarget.value);
  }, []);
  return {
    value,
    onChange
  };
}
function Page1(props){
  let value = useBind('');
  return <input {...value} />;
}

Конечно вы можетеHOCтаким образом, комбинируйтеcontextа такжеformЧтобы инкапсулировать более общую двустороннюю привязку, вы можете реализовать ее вручную, если вам это интересно.

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

Уменьшите риск повторного использования логики состояния

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

использовать без соблюденияHOCТакже могут быть некоторые конфликты, такие какpropsПереопределить и т. д., используйтеHookЭтих проблем можно избежать.

Избегайте гнездования в аду

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

Сделайте компоненты более понятными

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

Используйте функции вместо классов

Вместо функции напишитеclassВозможно, вам потребуется освоить больше знаний, и тем больше моментов, на которые вам нужно обратить внимание, таких какthisУказывать, связывать события и т. д. Кроме того, компьютер понимает функцию лучше, чемclassБыстрее.Hooksтак что вы можетеclassesиспользовать большеReactНовые особенности.

рациональный выбор

Фактически,Hookсуществуетreact 16.8.0только что официально выпущенHookСтабильная версия не использовалась автором в производственной среде, в настоящее время автор больше всего использует ее в производственной среде.HOC.

ReactЧиновники неclassesотReactпредназначен для удаления вclassкомпоненты иHookОн может существовать одновременно, и чиновник также рекомендует избегать любого «масштабного рефакторинга», ведь это очень новая версия, если она вам нравится, вы можете использовать ее в новом некритическом коде.Hook.

резюме

mixinбыл заброшен,HOCВ расцвете сил,HookFront-end круг именно такой, и скорость технической итерации очень высока, но когда мы изучаем эти знания, мы должны понимать, зачем нам нужно учиться, полезно ли учиться, и стоит ли нам это использовать или нет. Не забывайте о первоначальном намерении, Фанг Де всегда.

Если в тексте есть ошибки, исправьте их в комментариях, спасибо за прочтение.

Рекомендуемое чтение

Рекомендуемое внимание

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

Рекомендую обратить внимание на мой публичный аккаунт WeChat [code secret garden], будем общаться и расти вместе.

Подписавшись на официальную учетную запись, ответьте на [Добавить группу], чтобы включить вас в высококачественную группу внешнего интерфейса.