Как я упростил компоненты высшего порядка.

Наследство мертво. МВК мертв. Компоненты для всего - это путь. Большие, преимущественно боковые деревья объектов — это будущее, и на то есть веские причины. Функциональная композиция уступает понятному синтаксису класса. Итак, при всем при этом, как нам поддерживать композицию, которая так необходима для привязки поведения к точке нашего дерева просмотра?

Существует несколько различных способов обработки ввода данных в представление.

  1. Представьте базовый класс, который понимает получение данных из вашего API. Подклассируйте его и реализуйте необходимые методы, которые определяют что извлекать, не зная, как и как обновлять. Базовый класс неизменно либо использует состояние, либо некоторую внутреннюю переменную (данные являются общими) и forceUpdate.
  2. Создайте отдельный компонент-оболочку, который отслеживает свое собственное состояние выборки, управляет обновлениями и передает данные, а также функции манипулирования в качестве свойств своего дочернего элемента.
  3. Реализуйте поведение в определенных компонентах «дозорных», которые представляют собой смесь данных и представления, передавая эти данные вниз, а также используя их напрямую.

Каждый из них частично неверен по ряду различных причин:

  1. Базовый класс безопасен только в том случае, если он чрезвычайно устойчив и хорошо протестирован для своего контракта. Это много накладных расходов (действительно требуется больше, чем обычно, тестов). Короче говоря, базовый класс, который часто используется в вашем приложении, обычно является экземпляром запаха кода. И тот, который достаточно сложен для обработки асинхронной выборки данных, ЯВЛЯЕТСЯ запахом кода.
  2. Эта сложность часто не может использоваться повторно, и, что более важно, она должна быть относительно хорошо абстрагирована, чтобы ее можно было повторно использовать любым способом. Это требует, чтобы вы написали два компонента для одного вывода.
  3. Эти сторожевые компоненты, несомненно, будут находиться не в том месте, заставляя вас писать много повторяющегося кода и слишком часто передавать реквизиты вниз.

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

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

Компоненты более высокого порядка позволяют нам повторно использовать дополнительные функции. В моем текущем проекте я использую graphql и redux. Оба они предоставляют компоненты более высокого порядка для доступа к хранилищу.

import React from 'react';
import {graphql} from 'apollo-react';
import { connect } from 'react-redux';
import {gql} from 'graphql-tag';

class Component extends React.Component {
	render() {
		return <div></div>;
	}
}
const reduxAwareComponent = connect(state => ({}))(Component);

const apolloAwareComponent = graphql(gql`query{toDos{id}}`)(reduxAwareComponent);

export default apolloAwareComponent;

Этот шаблон становится нормой для реакции, что имеет смысл. В конце концов, он хорошо работает, и когда вы его используете, эти компоненты более высокого порядка практически прозрачны. Единственная проблема в том, что они НЕ прозрачны в редакторе. Когда я потребляю Component, импортируя его, я фактически получаю apolloAwareComponent. Мой редактор понимает, что я получаю. Но реальность такова, что мне, как потребителю, все равно, как мой компонент получает данные.

Когда я использую этот код, меня совершенно не волнует, как Component получает доступ к graphql или redux, или даже к тому, что он делает. Я забочусь о том, чтобы это дало мне возможность разместить на моей странице. Более того, этот код обладает определенной хрупкостью. Допустим, изначально я ничего не использовал Redux в этом конкретном компоненте:

import React from 'react';
import {graphql} from 'apollo-react';
import {gql} from 'graphql-tag';

class Component extends React.Component {
	render() {
		return <div></div>;
	}
}

const apolloAwareComponent = graphql(gql`query{toDos{id}}`)(Component);

export default apolloAwareComponent;

Что ж, это интересно. Видите ли, graphql напрямую обертывает Component. Теперь, чтобы добавить Redux, мне нужно сделать три вещи:

  1. Импорт подключения
  2. Оберните соединение вокруг компонента
  3. Заставьте graphql обернуть результат этого.

Один дополнительный шаг, умноженный на сотни компонентов в приложении, складывается. (шаг 3 требуется дополнительно, потому что мне обычно нужно хранилище избыточности внутри оболочки graphql). К счастью, есть лучший способ с декораторами. На самом деле, вам даже не нужно делать ничего существенного, чтобы использовать их. Начнем с .babelrc для этого примера:

{
  "presets": [
    "latest"
  ],
  "plugins": [
    "transform-decorators-legacy",
    "transform-class-properties"
  ]
}

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

После этого я могу начать использовать декорированные компоненты. Начнем только с обработчика graphql:

import React from 'react';
import {graphql} from 'apollo-react';
import {gql} from 'graphql-tag';

@graphql(gql`query{toDos{id}}`)
export default class Component extends React.Component {
	render() {
		return <div></div>;
	}
}

Здесь я хочу отметить несколько важных моментов:

  • Оператор экспорта перемещен в класс, который является звездой файла, а не оболочкой. Это означает, что такие редакторы, как Webstorm, понимают, что вы используете Component, а не graphql.
  • Мне не нужно где-либо хранить обернутую версию или делать что-либо для ее создания. Сейчас все это обрабатывается транспиляцией, а среда — в будущем.
  • Этот код короче, лаконичнее и ясно говорит о том, что он делает.
  • Это поддерживает композиционный характер javascript, позволяя использовать четко определенные классы, такие как контракты.

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

import React from 'react';
import {graphql} from 'apollo-react';
import {gql} from 'graphql-tag';
import { connect } from 'react-redux';

@graphql(gql`query{toDos{id}}`)
@connect(state => ({}))
export default class Component extends React.Component {
	render() {
		return <div></div>;
	}
}

Разве это не было немного чище? Это также никоим образом не изменило то, что потребители компонента знают или заботятся. Вы можете установить proptypes для Component, и webstorm автоматически заполнит их для вас при использовании Component. Этого бы не произошло, если бы вы имели дело с обернутой версией. Прелесть этого в том, что декораторы предоставляют контракт. Контракт этого символа @ прост. Внешний смысл украшаемого предмета принципиально от этого не меняется. Есть ряд других применений декораторов, например автоматическое связывание:

export default function Handler(target, name, descriptor) {
   let bound = Symbol('bound' + name);
   const value = descriptor.value || descriptor.initializer();
   descriptor.get = function get() {
      if (!this[bound]) {
         this[bound] = value.bind(this);
      }
      return this[bound];
   }
   delete descriptor.value;
   return descriptor;
}

Это используется очень просто:

import Handler from 'handler.js';
import React from 'react';
export default Component extends React.Component {
   @Handler
   onClick(event) {
      console.log(this, event);
   }
   render() {
      return <div onClick={this.onClick} />
   }
}

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

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

Эта статья является первой в серии, которая, как я надеюсь, будет посвящена практическому применению малоиспользуемых функций ES6/ES7. Все примеры кода можно найти в этом списке:https://gist.github.com/lassombra/616f5e1024093f539387f1fbc2fb709c

Статьи в серии на данный момент: