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 г.