React позволяет вам указать функцию как дочернюю, которая children
является обычной опорой, поэтому она эквивалентна обратному вызову рендеринга.
Давайте посмотрим, как все это выглядит.
render() {
return (
<div>
<LoadContent>
{
({ loading }) => <span>{loading}</span>
}
</LoadContent>
</div>
)
}
Здесь мы используем компонент LoadContent
, передаем функцию и затем возвращаем некоторый контент.
Это работает только потому, что наш LoadContent
понимает, что делать с children
, когда это функция.
Наш LoadContent
компонент будет выглядеть примерно так. Это фальшивый пример, но он демонстрирует точку зрения.
class LoadContent extends Component {
state = {
loading: true,
};
componentDidMount() {
setTimeout(() => {
this.setState({
loading: false,
});
}, 500);
}
render() {
return (
<div>
{this.props.children({
...this.props,
...this.state,
})}
</div>
);
}
}
Компонент LoadContent
может управлять всем состоянием, не передавая его логику другим компонентам.
Это позволяет выполнять очень мощную декларативную работу. Теперь вы можете объявить свои зависимости данных и использовать их в своем приложении. Динамические возвращаемые значения упрощаются, потому что вы просто работаете с функцией javascript.
Вашим внешним компонентам не нужно управлять состоянием. Кроме того, другие ваши компоненты затем получают эти данные в качестве свойств, что помогает повторно использовать компонент в файлах, а также упрощает тестирование.
Давайте посмотрим на другой пример. Скажем, мы хотели загрузить некоторые данные
class LoadContent extends Component {
state = {
loading: true,
error: false,
data: [],
};
componentDidMount() {
fetch(this.props.url)
// we should check status code here and throw for errors so our catch will work.
.then(res => res.json())
.then((data) => this.setState({ data, loading: false }))
.catch((err) => this.setState({ loading: false, error: true }))
}
render() {
return (
<div>
{this.props.children({
...this.props,
...this.state,
})}
</div>
);
}
}
Теперь у нас есть общий LoadContent
компонент. Мы можем передать url
, который будет вызван и преобразован в JSON. После возврата вызова ajax мы можем установить соответствующие значения error / loading / data.
Мы бы использовали компонент так
<LoadContent url="https://yourendpoint.com">
{
({ loading, error, data }) => {
if (loading) return <span>Loading...</span>
if (error) return <span>Error loading</span>
return (
<div>
{
data.map((item) => <div>{item}</div>)
}
</div>
)
}
}
</LoadContent>
У нас есть динамический возврат, когда инициируется loading
, если происходит ошибка, мы возвращаем ошибку. Наконец, если мы не загружаемся или не получаем ошибки, мы визуализируем наш фактический контент.
Этот тип компонентов применим в тех случаях, когда вам нужно каждый раз перезагружать определенные данные. Мы не кэшируем какой-либо контент, и им нельзя делиться. Однако с этой абстракцией вы можете легко перейти к сокращению или другому механизму кеширования.
Рендеринг реквизита
Дети как функция технически являются реквизитом для рендеринга. Однако свойства рендеринга позволяют определять сложный компонент и раскрывать динамические возможности.
Давайте посмотрим на пример.
render() {
return (
<div>
<ComplexList
data={["1", "2", "3", "4"]}
renderHeader={({ loading }) => <span>{loading}</span>}
renderListItem={(item) => <div>{item}</div>}
>
<div>Some data</div>
</ComplexList>
</div>
);
}
У нас есть ComplexList
, который принимает некоторые данные. Он имеет 2 обратных вызова рендеринга, один для рендеринга содержимого заголовка, а другой для рендеринга элементов списка.
Обратные вызовы рендеринга позволяют нам иметь стилизованный список и рендерить компоненты в определенном месте.
renderHeader
, являющийся функцией, позволяет ComplexList
определять логику местоположения для размещения заголовка. Кроме того, если есть дополнительная логика, такая как липкие заголовки, внешнему компоненту не нужно ничего знать о логике, необходимой для этого.
renderListItem
- это функция, которая вызывается несколько раз с каждым конкретным фрагментом данных. Опять же, с этим типом расположения, если требуется дополнительная логика для визуализации строк, которые можно абстрагировать. Включая обертывание строк дополнительными div
тегами, стилями и т. Д. Вот сколько таких компонентов, как FlatList
в React Native и react-virtualized
, работает.
Реализация будет выглядеть примерно так
class ComplexList extends Component {
render() {
return (
<div>
<div className="header">
{this.props.renderHeader(this.props)}
</div>
<div className="footer">
{
this.props.data.map((item) => this.props.renderListItem(item))
}
</div>
<div className="footer">
{this.props.children}
</div>
</div>
);
}
}
У нас есть сложный компонент с внешним API, который упрощен до нескольких обратных вызовов рендеринга. Однако мы все еще можем использовать наш children
как обычно.
Собираем все вместе
С дочерними элементами как функциями мы по-прежнему можем составлять наши компоненты, как любой другой компонент React. Мы можем взять наш компонент данных LoadContent
и подключить его к нашему ComplexList
компоненту.
render() {
<LoadContent url="https://yourendpoint.com">
{
({ loading, error, data }) => {
if (loading) return <span>Loading...</span>
if (error) return <span>Error loading</span>
return (
<ComplexList
data={data}
renderHeader={() => <span>{loading ? "Loading..." : "Header Content" }</span>}
renderListItem={(item) => <div>{item}</div>}
>
<div>We have {data.length} items</div>
</ComplexList>
)
}
}
</LoadContent>
}
Последствия для производительности
Имейте в виду, что при работе с дочерними элементами как с функцией или рендерингом обратных вызовов вы не сможете использовать shouldComponentUpdate
, если вам нужно.
Причина в том, что вы объявляете новую функцию в каждом цикле рендеринга. Так что, если вы когда-нибудь проводили поверхностное сравнение реквизита, они всегда будут разными.
При этом высокопроизводительные библиотеки, такие как react-motion
, используют функцию как дочернюю парадигму.
В целом это способ создавать сложные компоненты с помощью упрощенных, но динамических API.
Первоначально опубликовано на сайте codedaily.io 19 июня 2017 г.