Ответы на распространённые вопросы о шаблоне render prop

https://blog.kentcdodds.com/answers-to-common-questions-about-render-props-a9f84bb12d5d
  • Перевод
Кент С. Доддс, автор материала, перевод которого мы публикуем сегодня, говорит, что недавно группа программистов, с которыми он должен был провести тренинг по React, заинтересовалась шаблоном «render props», который ещё называют шаблоном «функция как потомок» (в разных публикациях его упоминают как «children as a function» или «function as child»). Документация по React определяет «render prop» как простую технику передачи кода между компонентами React с использованием свойства, значением которого является функция. Компонент, использующий render prop, принимает функцию, которая возвращает элемент React. Этот компонент вызывает данную функцию вместо реализации собственной логики рендеринга. Кент рекомендует тем, кто не знаком с шаблоном render prop, почитать этот материал и посмотреть это видео, а потом уже читать дальше.

image

Кент говорит, что, готовясь к занятиям, он экспериментировал и решил спросить своих читателей в Твиттере о том, какие вопросы у них есть по поводу render props, о том, подробности о каких ещё шаблонах, связанных с этим, им хотелось бы узнать, и о том, какие примеры помогли бы им с render props разобраться. В ответ на свой твит Кент получил немало откликов. В результате он решил остановиться на трёх наиболее типичных вопросах, касающихся шаблона render prop и ответить на них, проиллюстрировав ответы примерами.

Вопрос №1: Как использование шаблона render prop влияет на производительность?


Пожалуй, этот тот самый вопрос, который чаще всего возникает, когда я рассказываю о шаблоне render prop. Кроме того, в комментариях к вышеупомянутому твиту несколько раз спрашивали именно о производительности. Мой ответ на этот вопрос очень прост. Тут я ссылаюсь на материал Райана Флоренса, касающийся использования функций и производительности в React. Если процитировать основные выводы этого материала, то можно сказать следующее:

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

Если вы относитесь к противникам преждевременной оптимизации, значит вам не нужно доказательств того, что встроенные функции ухудшают производительность. Вам, для того, чтобы задуматься об их оптимизации, нужны доказательства обратного.

Мне хотелось бы дополнить эти выводы. Если вас действительно беспокоит встраивание render prop-функций и возможное негативное влияние этого на производительность — просто не используйте встроенные функции.

class MyComp extends React.Component {
  renderDownshift = downshift => (
    <div>{/* Элементы пользовательского интерфейса */}</div>
  )
  render() {
    return (
      <Downshift>
        {this.renderDownshift}
      </Downshift>
    )
  }
}

Вопрос №2: Как, при использовании render props, избежать появления чрезмерно усложнённого кода рендеринга, который тяжело читать?


Один из моих читателей, Марк Эриксон, задался вопросом о том, как, используя шаблон render props, не помещать всю логику рендеринга во вложенные функции внутри метода render(). Он хотел знать, можно ли, в угоду повышения читаемости кода, разбить эту логику на небольшие функции, но при этом не отказываться от render props.

Марк задаёт правильные вопросы. Почти все примеры использования шаблона render prop, которые мне доводилось видеть, сводятся к размещению всей логики рендеринга в одной функции. Обычно в подобных случаях можно видеть команду возврата и одно гигантское выражение. Меня эти огромные функции рендеринга раздражали, но я стал относиться к ним гораздо лучше, когда понял, что единственная причина, по которой они мне не нравились, заключается в том, что я думал, что они были сложными, хотя на самом деле это не так.

В любом случае, так как то, что мы называем «render props» — это всего лишь функции, которые вызываются с аргументами, с ними можно делать всё, что душе угодно. В результате я подготовил для Марка пару примеров.


Примеры для Марка

Вот сокращённый вариант кода этих примеров.

function JustARegularFunctionComponent(props) {
  // тут можете делать что хотите
  return <div>{/* ваш код */}</div>
}
function App() {
  return (
    <div>
      <div>
        With a totally different component.
        Thanks React composibility!
      </div>
      <RenderPropComp
        render={arg => (
          <JustARegularFunctionComponent {...arg} />
        )}
      />
      <hr />
      <div>
        Inline! You don't have to make it an
        implicit return arrow function 
      </div>
      <RenderPropComp
        render={arg => { // <-- notice the curly brace!
          // тут можете делать что хотите
          return <div>{/* ваш код */}</div>
        }}
      />
    </div>
  )
}

Вопрос №3: Как организовать доступ к аргументам render prop-функций в методах обработки событий жизненного цикла компонента?


Ещё один достаточно распространённый вопрос заключается в том, как получить доступ к аргументам render prop-функции в методах обработки жизненного цикла компонента. Сложность тут заключается в том, что эти функции вызываются в контексте render компонента, поэтому неясно, как работать с данными, передаваемыми им, скажем, в componentDidMount.

На самом деле, ответ на этот вопрос скрыт в ответе на вопрос Марка, который мы разбирали выше. Обратите внимание на то, что благодаря возможностям React по композиции мы можем создать отдельный компонент и просто перенаправить аргументы в свойства нашего компонента. Например, это можно сделать так.

class RegularClassComponent extends React.Component {
  componentDidUpdate() {
    // вот то, что вам нужно
    console.log(this.props.whatever)
  }
  render() {
    return <div>{/* код пользовательского интерфейса */}</div>
  }
}
function App() {
  return (
    <RenderPropComp
      render={arg => <RegularClassComponent {...arg} />}
    />
  )
}

Мой друг Донавон опечалится, если я не расскажу о его любимом шаблоне «внедрение компонента» (component injection). С использованием этого шаблона добиться нашей цели можно ещё проще, да и код будет выглядеть чище:

class RegularClassComponent extends React.Component {
  componentDidUpdate() {
    // вот то, что вам нужно
    console.log(this.props.whatever)
  }
  render() {
    return <div>{/* код пользовательского интерфейса */}</div>
  }
}
function App() {
  return (
    <CompInjectionComp component={RegularClassComponent} />
  )
}

Над деталями реализации вы можете поработать самостоятельно. Кроме того, можете взглянуть на новую библиотеку, которую создал Донавон после нашего разговора о render props.

Итоги


Я считаю, что шаблон render prop — это просто замечательная технология, и я ожидаю пополнения списка awesome-react-render-props, над которым работает Джаред Палмер. В этом шаблоне мне особенно нравится то, что он может включать в себя логику компонента и при этом не вредить возможностям настройки системы и не усложнять разметку. Думаю, в том, что касается render props, нас ждёт ещё много интересного.

Уважаемые читатели! Пользуетесь ли вы шаблоном render props в своих React-проектах?

RUVDS.com

766,00

RUVDS – хостинг VDS/VPS серверов

Поделиться публикацией
Комментарии 9
    +1
    Уважаемые читатели! Пользуетесь ли вы шаблоном render props в своих React-проектах?

    Пользовался еще до того, как это стало мейнстримом. Допустим, у нас есть компонент для рендеринга списков:


    <MyList items={items} renderItem={item => <span>{item.name}</span>}>

    задавать шаблон функции для рендеринга элемента списка казалось настолько естественным, что я даже не думал называть это "шаблоном render props"

      0
      А в чем плюс, по сравнению с таким подходом?

      <List>
       {(data) => {
          return <Item data={data} />
        }}
       </List>
      
        0
        1. Нужно обязательно писать парный закрывающий тег, лишняя писанина
        2. Труднее читать. Render prop можно назвать подходящим по смыслу именем, например <List renderItem={data => <Item />}>, а children так и останутся анонимными.
        3. Легче допустить ошибку. Вы не поверите, но вот это — валидный JSX, линтеры молчат:

        <List>
         {(data) => <Item data={data} />}
         {(data) => <Item data={data} />}
        </List>

        Изначальный смысл специального свойства children был в том, чтобы JSX-разметка выглядела как html. Функция как контент смотрится странновато. Почему в React ввели именно такой синтаксис для нового Context API — непонятно.

          0
          Вот именно затем, чтобы JSX-разметка выглядела как html. Чтобы не писать один тэг внутри атрибута другого тэга, а тот — внутри атрибута третьего.
            0

            Вот это похоже на разметку, здесь children имеют смысл


            <List>
              <Item />
              {props.something}
              <Item />
            </List>

            А вот здесь какая-то дичь:


            <List>
              {(data) => <div>
                  <Item />
                 {data.something}
                 <Item />
                </div>
              }}
            </List>

            Здесь фигурных скобочек и других значков больше, чем собственно чего-то похожего на html.

              0

              А вот здесь — еще большая дичь:


              <List render={(data) => <div>
                    <Item />
                   {data.something}
                   <Item />
                  </div>
                }} />
                0

                Ну я же уже показал, чем именно использование свойства render лучше. Из двух дичей я выбираю меньшую.

                  0
                  Может и лучше, но на разметку это похоже еще меньше.
                    +1

                    На самом деле, если у нас вместо примитивного html идут сложные компоненты, может лучше вообще отказаться от JSX и перейти на plain JS


                    const Layout = React.createElement.bind(React, LayoutComponent);
                    // ...
                    render() {
                       return Layout({
                         left: () => <div></div>,
                         right: () => <div></div>
                       });
                    }

                    Простой JS вписывается здесь намного лучше, чем JSX, где надо все свойства оборачивать в скобки {}. Предполагаю, что в 2018 году мы еще увидим тренд на подобный JSX-less React.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое