React.js: упаковка одного компонента в другой

Многие языки шаблонов имеют инструкции "слотов" или "выход", которые позволяют выполнять некоторую инверсию элемента управления для обертывания одного шаблона внутри другого.

Angular имеет "переключить" вариант.

Rails имеет инструкцию вывода. Если React.js имеет оператор yield, он будет выглядеть так:

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          <yield/>
        after
      </div>
    );
  }
});

var Main = React.createClass({
  render: function() {
    return (
      <Wrapper><h1>content</h1></Wrapper>
    );
  }
});

Требуемый вывод:

<div class="wrapper">
  before
    <h1>content</h1>
  after
</div>

Увы, React.js не имеет <yield/>. Как определить компонент Wrapper для достижения того же выхода?

Ответ 2

Простейшее решение: оболочка среды выполнения

const Wrapper = ({children}) => (
  <div>
    <div>header</div>
    <div>{children}</div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = () => (
  <Wrapper>
    <App/>
  </Wrapper>
);

Это решение является самым идиоматичным, но Wrapper всегда нужно отображать, когда он получает новый children prop каждый раз. Не беспокойтесь, это, безусловно, штраф на 90% ваш usecase).

Лучшее решение: компоненты более высокого порядка (HOC).

const wrapHOC = (WrappedComponent) => (props) => (
  <div>
    <div>header</div>
    <div><WrappedComponent {...props}/></div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = wrapHOC(App);

Для более высоких характеристик, используя Компонент /HOC более высокого порядка (теперь официально добавлен в React documentation) лучше, чем обертки времени выполнения, потому что HOC обычно может коротко замыкать рендеринг с shouldComponentUpdate.

Здесь я сохранил пример простым, но, очевидно, если вы хотите использовать это увеличение производительности, вы должны использовать PureComponent вместо функционального компонента без состояния (который неожиданно не оптимизируется с помощью shouldComponentUpdate)

const wrapHOC = (WrappedComponent) => (props) => {
  class Wrapper extends React.PureComponent {
    render() {
      return (
        <div>
          <div>header</div>
          <div><WrappedComponent {...this.props}/></div>
          <div>footer</div>
        </div>
      );
    }  
  }
  return Wrapper;
}

Теперь HOC сможет коротко отстроить рендеринг на один шаг вперед, если символ name не изменится.

Другие шаблоны оболочки:

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

<State initial={0}>
  {(val, set) => (
    <div onClick={() => set(val + 1)}>  
      clicked {val} times
    </div>
  )}
</State>

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

<Promise promise={getSomeAsyncData()}>
  {{
    loading: () => <div>...</div>,
    success: (data) => <div>{data.something}</div>,
    error: (e) => <div>{e.message}</div>,
  }}
</Promise >

Аргументы HOC могут быть любыми

Таким же образом вы можете предоставить дополнительные параметры своему HOC. Посмотрите пример в connect react-redux, который принимает функцию mapStateToProps и параметры.

Вы можете даже комбинировать функцию детей и HOC вместе: здесь пример использования найден в блог с открытым исходным кодом Guillermo Rauch: HOC/Использование

export default withViews(({ views }) => (
  <div>
    This blog post has been viewed {views} times
    .....
  </div>
)

Заключение

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

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

Redux сначала использовал оболочку времени выполнения <Connect> и позже переключился на HOC connect(options)(Comp) по соображениям производительности. Это прекрасная иллюстрация того, что я хотел бы выделить в этом ответе.

Ответ 3

В дополнение к ответу Бена, я также нашел применение при отправке дочерних компонентов, выполнив что-то вроде этого:

var ListView = React.createClass({
    render: function() {
        var items = this.props.data.map(function(item) {
            return this.props.delegate({data:item});
        }.bind(this));
        return <ul>{items}</ul>;
    }
});

var ItemDelegate = React.createClass({
    render: function() {
        return <li>{this.props.data}</li>
    }
});

var Wrapper = React.createClass({    
    render: function() {
        return <ListView delegate={ItemDelegate} data={someListOfData} />
    }
});