Обертка для контекста

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

// dependencies.js
export default {
  data: {},
  get(key) {
    return this.data[key];
  },
  register(key, value) {
    this.data[key] = value;
  }
}

Затем, если мы вернемся к нашему примеру, самый верхний компонент App может выглядеть так:

import dependencies from './dependencies';
dependencies.register('title', 'React in patterns');

class App extends React.Component {
  getChildContext() {
    return dependencies;
  }
  render() {
    return <Header />;
  }
}

App.childContextTypes = {
  data: PropTypes.object,
  get: PropTypes.func,
  register: PropTypes.func
};

И наш компонент Title получает его данные через контекст:

// Title.jsx
export default class Title extends React.Component {
  render() {
    return <h1>{ this.context.get('title') }</h1>
  }
}
Title.contextTypes = {
  data: PropTypes.object,
  get: PropTypes.func,
  register: PropTypes.func
};

В идеале мы не хотим указывать contextTypes каждый раз, когда нам нужен доступ к контексту. Эта деталь может быть обернута компонентом высокого порядка(HOC). И даже более того, мы можем написать вспомогательную функцию, которая более лучше описана, то есть, вместо того, чтобы напрямую обращаться к контексту this.context.get ('title'), мы просим компонент высокого порядка получить то, что нам нужно, и передать его в качестве свойств нашему компоненту. К примеру:

// Title.jsx
import wire from './wire';

function Title(props) {
  return <h1>{ props.title }</h1>;
}

export default wire(Title, ['title'], function resolve(title) {
  return { title };
});

Функция Wire принимает сначала компонент React, затем массив со всеми необходимыми зависимостями (которые уже зарегистрированы), а затем функция, которую я хотел бы назвать mapper. Она получает то, что хранится в контексте как необработанные данные, и возвращает объект, который является фактическим реквизитом для нашего компонента Title. В этом примере мы просто передаем то, что получим - переменная строки заголовка. Однако в реальном приложении это может быть коллекция хранилищ данных, настройка или что-то еще. Итак, хорошо, что мы передаем именно то, что нам нужно, и не загрязняем компоненты данными, которые им не нужны.

Вот как выглядит функция wire:

var dependencies = {};

export function register(key, dependency) {
  dependencies[key] = dependency;
}

export function fetch(key) {
  if (key in dependencies) return dependencies[key];
  throw new Error(`"${ key } is not registered as dependency.`);
}

export function wire(Component, deps, mapper) {
  return class Injector extends React.Component {
    constructor(props) {
      super(props);
      this._resolvedDependencies = mapper(...deps.map(fetch));
    }
    render() {
      return (
        <Component
          {...this.state}
          {...this.props}
          {...this._resolvedDependencies}
        />
      );
    }
  };
}

Мы будем хранить зависимости в глобальной переменной зависимостей (она глобальна для нашего модуля, а не на уровне приложения)

Затем мы экспортируем две функции register и fetch для записи и чтения.

Это немного похоже на реализацию setter и getter против простого объекта JavaScript.

Затем у нас есть функция wire, которая принимает наш компонент React и возвращает компонент высокого порядка.

В конструкторе этого компонента мы резолвим зависимости, а затем при рендеринге исходного компонента мы передаем их как реквизиты.(props).

Мы следуем той же схеме, где мы описываем, что нам нужно (аргумент deps), и извлекаем необходимые реквизиты с помощью функции mapper.

Имея помощник di.jsx, мы снова можем регистрировать наши зависимости в точке входа нашего приложения (app.jsx) и вводить их везде (Title.jsx), которые нам нужны.

// app.jsx
import Header from './Header.jsx';
import { register } from './di.jsx';

register('my-awesome-title', 'React in patterns');

class App extends React.Component {
  render() {
    return <Header />;
  }
}
// Header.jsx
import Title from './Title.jsx';

export default function Header() {
  return (
    <header>
      <Title />
    </header>
  );
}
// Title.jsx
import { wire } from './di.jsx';

var Title = function(props) {
  return <h1>{ props.title }</h1>;
};

export default wire(Title, ['my-awesome-title'], title => ({ title }));

Если мы посмотрим на файл Title.jsx, мы увидим, что актуальный компонент и wiring могут находиться в разных файлах. Таким образом, компонент и функция mapper становятся легко тестируемыми.

results matching ""

    No results matching ""