Использование children
const Wrapper = ({children}) => (
<div>
<div>header</div>
<div>{children}</div>
<div>footer</div>
</div>
);
const App = ({name}) => <div>Hello {name}</div>;
const WrappedApp = ({name}) => (
<Wrapper>
<App name={name}/>
</Wrapper>
);
render(<WrappedApp name="toto"/>,node);
Это также известно как transclusion
в Angular.
children
- это специальная опора в React, которая будет содержать то, что находится внутри тегов вашего компонента (здесь <App name={name}/>
находится внутри Wrapper
, поэтому это children
Обратите внимание, что вам не обязательно использовать children
, который является уникальным для компонента, и вы также можете использовать обычные реквизиты, если хотите, или смешивать реквизиты и дочерние элементы:
const AppLayout = ({header,footer,children}) => (
<div className="app">
<div className="header">{header}</div>
<div className="body">{children}</div>
<div className="footer">{footer}</div>
</div>
);
const appElement = (
<AppLayout
header={<div>header</div>}
footer={<div>footer</div>}
>
<div>body</div>
</AppLayout>
);
render(appElement,node);
Это просто и хорошо для многих сценариев использования, и я бы рекомендовал это для большинства потребительских приложений.
рендеринг реквизита
Компоненту можно передавать функции рендеринга, этот шаблон обычно называется render prop
, и свойство children
часто используется для обеспечения этого обратного вызова.
Этот шаблон на самом деле не предназначен для макета. Компонент-оболочка обычно используется для хранения и управления некоторым состоянием, а также для внедрения его в функции рендеринга.
Пример счетчика:
const Counter = () => (
<State initial={0}>
{(val, set) => (
<div onClick={() => set(val + 1)}>
clicked {val} times
</div>
)}
</State>
);
Вы можете получить еще больше фантазии и даже предоставить объект
<Promise promise={somePromise}>
{{
loading: () => <div>...</div>,
success: (data) => <div>{data.something}</div>,
error: (e) => <div>{e.message}</div>,
}}
</Promise>
Обратите внимание, что вам не обязательно использовать children
, это вопрос вкуса / API.
<Promise
promise={somePromise}
renderLoading={() => <div>...</div>}
renderSuccess={(data) => <div>{data.something}</div>}
renderError={(e) => <div>{e.message}</div>}
/>
На сегодняшний день многие библиотеки используют свойства рендеринга (контекст React, React-motion, Apollo ...), потому что люди склонны находить этот API более простым, чем HOC. react-powerplug - это набор простых компонентов render-prop. react-accept помогает создавать композицию.
Компоненты высшего порядка (HOC).
const wrapHOC = (WrappedComponent) => {
class Wrapper extends React.PureComponent {
render() {
return (
<div>
<div>header</div>
<div><WrappedComponent {...this.props}/></div>
<div>footer</div>
</div>
);
}
}
return Wrapper;
}
const App = ({name}) => <div>Hello {name}</div>;
const WrappedApp = wrapHOC(App);
render(<WrappedApp name="toto"/>,node);
Компонент высшего порядка / HOC обычно представляет собой функцию, которая принимает компонент и возвращает новый компонент.
Использование компонента более высокого порядка может быть более эффективным, чем использование children
или render props
, потому что оболочка может иметь возможность сокращать рендеринг на один шаг вперед с помощью shouldComponentUpdate
.
Здесь мы используем PureComponent
. При повторном рендеринге приложения, если свойство WrappedApp
name не меняется с течением времени, оболочка имеет возможность сказать: «Мне не нужно визуализировать, потому что свойства (фактически, имя) такие же, как и раньше». В решении на основе children
выше, даже если оболочка PureComponent
, это не так, потому что дочерний элемент воссоздается каждый раз, когда родительский рендеринг, а это означает, что оболочка, вероятно, всегда будет повторно визуализироваться, даже если обернутый компонент чистый. Существует плагин babel, который может помочь смягчить это и обеспечить постоянный children
элемент с течением времени.
Заключение
Компоненты более высокого порядка могут повысить производительность. Это не так сложно, но поначалу определенно выглядит недружелюбно.
Не переносите всю свою кодовую базу в HOC после прочтения этого. Просто помните, что на критических путях вашего приложения вы можете использовать HOC вместо оболочек времени выполнения по соображениям производительности, особенно если одна и та же оболочка используется много раз, стоит подумать о том, чтобы сделать ее HOC.
Redux сначала использовал оболочку времени выполнения <Connect>
, а позже переключился на HOC connect(options)(Comp)
из соображений производительности (по умолчанию оболочка чистая и использует shouldComponentUpdate
). Это прекрасная иллюстрация того, что я хотел выделить в этом ответе.
Обратите внимание: если у компонента есть API render-prop, обычно легко создать HOC поверх него, поэтому, если вы являетесь автором библиотеки, вы должны сначала написать API-интерфейс render prop и, в конечном итоге, предложить версию HOC. Это то, что Apollo делает с <Query>
компонентом render-prop и graphql
HOC его использует.
Лично я использую оба, но в случае сомнений предпочитаю HOC, потому что:
- Составлять их (
compose(hoc1,hoc2)(Comp)
) более идиоматично, чем рендерить реквизиты.
- Это может дать мне лучшую производительность
- Я знаком с этим стилем программирования
Я без колебаний использую / создаю HOC-версии моих любимых инструментов:
- Композиция
Context.Consumer
от React
- Unstated's
Subscribe
- с использованием
graphql
HOC Apollo вместо Query
render prop
На мой взгляд, иногда рендеринг реквизита делает код более читаемым, иногда - менее ... Я стараюсь использовать наиболее прагматичное решение в соответствии с имеющимися у меня ограничениями. Иногда удобочитаемость важнее производительности, иногда нет. Выбирайте с умом и не следуйте обязательной тенденции 2018 года преобразовывать все в рендер-реквизиты.
person
Sebastien Lorber
schedule
22.07.2015