Привет, Хабр! Представляю вашему вниманию перевод статьи «React Best Practices & Tips Every React Developer Should Know Pt.1» автора Alex Devero.

image

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

Содержание:

  1. Сохраняйте ваши компоненты небольшими
  2. Избегайте нагромождения компонентов
  3. Сократите использование stateful-компонентов
  4. Используйте функциональные компоненты с хуками и memo вместо компонентов на классах
  5. Не используйте props в исходном state.

Эпилог: Лучшие практики React и советы, которые каждый разработчик должен знать Часть 1

1. Сохраняйте ваши компоненты небольшими


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

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

Цель состоит в том, чтобы создать кусочки кода, которые вы пишете один раз, а затем использовать их снова и снова, когда вам нужно. С этой точки зрения нет смысла объединять все ваши данные в один массивный компонент, один файл. И, даже если вам на самом деле наплевать на многократно используемый код, подумайте об этом. Насколько просты в поддержке будут компоненты с сотнями строк кода?

Такой компонент будет трудно поддерживать, отлаживать и обновлять. Это также означает, что любая работа с этим компонентом займет гораздо больше времени. Другими словами, пострадает ваша общая производительность. И рано или поздно это сведет тебя с ума. Или это сведет с ума ваших товарищей по команде и коллег, и они начнут сводить с ума вас.

Что бы вы ни выбрали, вы скоро потеряете рассудок и, возможно, наживете себе врагов. Это того не стоит. Следите за тем, чтобы ваши компоненты были небольшими. Сохраните дружеские отношения, здравомыслие, время и продуктивность. Упростите отладку, обновление и сопровождение кода. Рассмотрим один пример.

Было

///
// file: index.jsx
import React from 'react'
const books = [
  {
    category: 'Business',
    price: '$20.00',
    name: 'Private Empires',
    author: 'Steve Coll'
  },
  {
    category: 'Philosophy',
    price: '$25.00',
    name: 'The Daily Stoic',
    author: 'Ryan Holiday'
  },
  {
    category: 'Sport',
    price: '$15.95',
    name: 'Moneyball',
    author: 'Michael Lewis'
  },
  {
    category: 'Biography',
    price: '$21.00',
    name: 'Titan',
    author: 'Ron Chernow'
  },
  {
    category: 'Business',
    price: '$29.99',
    name: 'The Hard Thing About Hard Things',
    author: 'Ben Horowitz'
  },
  {
    category: 'Fiction',
    price: '$4.81',
    name: 'Limitless: A Novel',
    author: 'Alan Glynn'
  }
]
class Bookshelf extends React.Component {
  render() {
    const tableRows = []
    this.props.books.forEach((book) => {
      tableRows.push(
        <tr>
          <td>{book.name}</td>
          <td>{book.author}</td>
          <td>{book.price}</td>
          <td>{book.category}</td>
        </tr>
      )
    })
    return (
      <div>
        <form>
          <input type="text" placeholder="Search..." />
          <button>Search</button>
        </form>
        <table>
          <thead>
            <tr>
              <th>Name</th>
              <th>Author</th>
              <th>Price</th>
              <th>Category</th>
            </tr>
          </thead>
          <tbody>{tableRows}</tbody>
        </table>
      </div>
    )
  }
}
// Render Bookshelf component
ReactDOM.render(<Bookshelf books={booksData} />, document.getElementById('container'))

Стало

///
// file: books-data.js
const books = [
  {
    category: 'Business',
    price: '$20.00',
    name: 'Private Empires',
    author: 'Steve Coll'
  },
  {
    category: 'Philosophy',
    price: '$25.00',
    name: 'The Daily Stoic',
    author: 'Ryan Holiday'
  },
  {
    category: 'Sport',
    price: '$15.95',
    name: 'Moneyball',
    author: 'Michael Lewis'
  },
  {
    category: 'Biography',
    price: '$21.00',
    name: 'Titan',
    author: 'Ron Chernow'
  },
  {
    category: 'Business',
    price: '$29.99',
    name: 'The Hard Thing About Hard Things',
    author: 'Ben Horowitz'
  },
  {
    category: 'Fiction',
    price: '$4.81',
    name: 'Limitless: A Novel',
    author: 'Alan Glynn'
  }
]
export default booksData
///
// file: components/books-table.jsx
import React from 'react'
class BooksTable extends React.Component {
  render() {
    const tableRows = []
    this.props.books.forEach((book) => {
      tableRows.push(
        <tr>
          <td>{book.name}</td>
          <td>{book.author}</td>
          <td>{book.price}</td>
          <td>{book.category}</td>
        </tr>
      )
    })
    return (
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Author</th>
            <th>Price</th>
            <th>Category</th>
          </tr>
        </thead>
        <tbody>{tableRows}</tbody>
      </table>
    )
  }
}
export default BooksTable
///
// file: components/search-bar.jsx
import React from 'react'
class SearchBar extends React.Component {
  render() {
    return (
      <form>
        <input type="text" placeholder="Search..." />
        <button>Search</button>
      </form>
    )
  }
}
export default SearchBar
///
// file: components/bookshelf.jsx
import React from 'react'
// Import components
import BooksTable from './components/books-table'
import SearchBar from './components/search-bar'
class Bookshelf extends React.Component {
  render() {
    return (
      <div>
        <SearchBar />
        <BooksTable books={this.props.books} />
      </div>
    )
  }
}
export default Bookshelf
///
// file: index.jsx
import React from 'react'
// Import components
import Bookshelf from './components/bookshelf
// Import data
import booksData from './books-data'
// Render Bookshelf component
ReactDOM.render(<Bookshelf books={booksData} />, document.getElementById('container'))

2. Избегайте нагромождения компонентов


Каждое правило должно применяться с осторожностью. Это также относится и к этим лучшим практикам React, особенно предыдущей. Когда дело доходит до компонентов, очень легко переусердствовать и написать даже мельчайшие фрагменты кода в виде компонентов. Не делай этого. Нет смысла делать так, чтобы каждый параграф, span или div был компонентом.
Думайте, прежде чем начать делить каждый компонент на мелкие части. Вы можете думать о компоненте как о смеси из «HTML», которая делает только одно, будучи независима, и пользователь воспримет ее как единое целое. Имеет ли смысл сделать этот кусок кода компонентом? Если нет, объедините этот код. Иначе, разделите его.

Давайте рассмотрим некоторые примеры, чтобы проиллюстрировать это определение компонента. Одним из примеров является модальный диалог. Этот компонент может состоять из множества мелких элементов, таких как div'ы, заголовки, абзацы текста, кнопки и т.д. Теоретически, все эти элементы можно выделить в небольшие компоненты.

На практике это бессмысленно. Да, некоторые из этих элементов могут существовать независимо друг от друга. Однако действительно ли полезно создавать компонент, состоящий только из одного абзаца или одного заголовка? Что будет дальше? Компонент для label, input или даже span? Такой подход не является устойчивым.

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

Затем вы объединяете атомы и создаете молекулы. Примерами молекул могут быть модальный диалог, форма, всплывающее окно, выпадающее меню, навигация и т.д. Далее, вы можете комбинировать одну молекулу с другой или с атомом и создавать организм. Примером организма может быть заголовок, список товаров или корзина для покупок. Шаблоны, страницы и утилиты теперь не важны.

Как совместить атомарное проектирование с этими двумя примерами лучшего опыта в области компонентов React? Давайте не будем усложнять. Компонентом может быть что угодно большее, чем атом, т.е. молекула, организм или даже шаблон или страница, если их взять до предела. В этом смысле label, заголовок, параграф не являются компонентами, потому что это атомы.
Однако модальные диалоги, формы, всплывающие окна, выпадающие списки и т.д. являются компонентами, поскольку все они относятся либо к молекулам, либо к категории организма. Все еще есть некоторые сомнительные элементы, такие как кнопка. Да, с точки зрения атомной конструкции это атом. Однако она может существовать независимо, во многих вариациях и все еще работать.

В подобных случаях я предлагаю не задумываться над лучшими практиками React'а, и просто руководствоваться своим внутренним чутьем. В конце концов, именно вы будете работать с кодом. Важно то, что вам удобно. Так что не надо просто слепо следовать какому-то списку передовых практик. А если ты работаешь в команде? Поделитесь своими мыслями об этом со своими коллегами.

3. Сократить использование stateful-компонентов


Это одна из лучших практик React, которая применялась в течение определенного времени. Однако эта практика стала более популярной с появлением React 16.8.0 и React hooks. До этого, когда вы хотели использовать состояние или любой метод жизненного цикла, вам также приходилось использовать stateful-компонент. Другого выхода не было.

Хуки изменили это. После того, как они были официально представлены, разработчики React больше не ограничивались stateful-компонентами, так как им нужно было использовать состояние. Благодаря хукам разработчики React теперь могут писать функциональные компоненты (stateless), используя при этом state и даже методы жизненного цикла по своему желанию.

Почему это важно? Компоненты без состояния или функциональные компоненты, как правило, лучше, чем stateful-компоненты, когда речь заходит о производительности. Причина в том, что нет ни состояния, ни методов жизненного цикла. Другими словами, меньше кода для выполнения, а также для транспайлинга. Конечно, эта разница может быть едва ощутимой, почти невидимой, если вы работаете над каким-то очень маленьким проектом.

Однако эти небольшие различия могут складываться по мере роста вашего проекта. Также подумайте о том, сколько строк кода требуется компоненту с контролем состояния по сравнению с функциональными. Функциональные возможности также короче и часто проще для чтения. Давайте посмотрим на компонент кнопки, определяемый как компонент с контролем состояния и функциями. Какой из них вам больше нравится?


// Button defined as a stateful component
class Button extends React.Component {
  handleClick = () => {
    // Do something
  }
  render() {
    return(
      <button type="button" onClick={this.handleClick}>Click me</button>
    )
  }
}
// Button defined as a functional component
const Button = () => {
  const handleClick = () => {
    // Do something
  }
  return(
    <button type="button" onClick={handleClick}>Click me</button>
  )
}

4. Используйте функциональные компоненты с хуками и memo вместо компонентов на классах


Как мы уже обсуждали, вам больше не нужно использовать компоненты, учитывающие состояние, только для того, чтобы использовать состояние. Более того, некоторые разработчики React также считают, что в будущем React начнет отходить от классов. Правда ли это, сейчас не важно. Важно то, что один функциональный компонент теперь может использовать состояние благодаря хукам.
И, во-вторых, использование функциональных компонентов имеет свои преимущества. TLDR? Нет класса, наследования и constructor. Нет этого ключевого слова. Передовая практика строгого React. Высокое соотношение сигнал/шум. Раздутые компоненты и плохие структуры данных легче обнаружить. Код легче понять и проверить. И, опять же, производительность лучше.

И еще кое-что. Многие разработчики React выступали против функциональных компонентов. Одна из проблем заключается в том, что вы, как разработчик React, не можете контролировать процесс рендеринга при использовании функционального компонента. Когда что-то меняется, React возвращает функциональный компонент, независимо от того, был ли сам компонент изменен.
В прошлом решение заключалось в использовании чистого компонента. Чистый компонент обеспечивает возможность сравнения состояния и props. Значит, React может «проверять», изменилось ли содержание компонента, props или самого компонента. Если да, то он вернёт его. В противном случае он пропустит повторный рендеринг и будет повторно использовать последний отрисованный результат. Меньше рендеринга равнозначно лучшей производительности.
С выпуском React 16.6.0 это больше не проблема, и аргумент против функциональных компонентов больше недействителен. Что изменило игру, так это memo. Memo принесла неглубокое сравнение с функциональным компонентом, возможность «проверить», изменилось ли содержание компонента, props или самого компонента.

Опять же, основываясь на этом сравнении, React либо вернет компонент назад, либо повторно использует результат последнего рендеринга. Короче говоря, memo позволяет создавать «чистые» функциональные компоненты. Больше нет причин использовать statefull-компоненты, или чистые компоненты. По крайней мере, если вам не нужно справляться со сложным состоянием.

В этом случае вам следует рассмотреть возможность использования чего-то более масштабируемого и управляемого, например, MobX, Redux или Flux, вместо состояния компонентов. Другим возможным вариантом могло бы стать использование контекста. В любом случае, благодаря хукам и memo, функциональные компоненты, безусловно, являются одними из лучших практик React, о которых стоит задуматься.

5. Не используйте props в исходном state


Это одна из лучших практик React, о которой я хотел бы знать, когда начинал изучение. Тогда я не знал, что это была очень плохая идея — использовать props в исходном состоянии. Почему это плохая идея? Проблема в том, что конструктор вызывается только один раз, во время создания компонента.

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

Это может не быть проблемой, если вы хотите, чтобы состояние получало значения от props только один раз, во время первоначального рендеринга, и вы бы управляли состоянием внутри компонента. В противном случае, вы можете решить эту проблему с помощью componentDidUpdate. Как сказано в названии, этот метод жизненного цикла позволяет вам обновлять компонент при изменении чего-либо, например, props.

Если вы решили использовать этот метод, запомните одну вещь. Он будет задействован не при первоначальном рендеринге, а только на следующем. Поэтому обязательно инициализируйте состояние компонента необходимыми значениями, возможно, полученными из props. Затем используйте componentDidUpdate для обновления этих значений и компонента по мере необходимости.

Эпилог: React Лучшие практики и советы, которые каждый разработчик должен знать Часть 1


Поздравляю! Вы только что закончили первую часть этой мини-серии статей, посвященную передовым методам React'а. Сегодня вы узнали о пяти практиках, которые можно использовать, чтобы сделать код React более коротким, простым, лучшим, быстрым и удобным для чтения и обслуживания. Теперь дело за вами, внедрить ту практику, с которой вы согласны, и начать ее использовать.

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

Если вам понравилась эта статья, тогда, пожалуйста, подпишитесь.

Первоначально опубликовано в блоге Alex Devero Blog.

Комментарии (5)


  1. friday
    31.08.2019 23:45
    +1

    А не лучше ли вместо forEach использовать map?


    const tableRows = this.props.books.map((book) => (
            <tr>
              <td>{book.name}</td>
              <td>{book.author}</td>
              <td>{book.price}</td>
              <td>{book.category}</td>
            </tr>
         ))

    Хабр режет теги, но думаю, мысль понятна.


    1. Carduelis
      01.09.2019 12:44
      +1

      Да это статья вообще противопоказана начинающем. Спорные тезисы, убеждения во вкусовщине, откровенно слабые и вредные примеры кода.


      Если уж выбирать статью на перевод, есть куда более достойные кандидаты.


  1. Carduelis
    01.09.2019 12:47
    +2

    Какой из них вам больше нравится?

    Конечно же вариант с использованием классов. Там хотя бы не пересоздается при каждом рендере функция для onClick.


    Не используйте props в исходном state

    И ни слова здесь про getDerivedStateFromProps. Говорится про componentDidUpdate, как будто componentDidMount вовсе не существует.


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


    1. v1vendi
      01.09.2019 20:48

      В целом Вы конечно правы, но стоит признать, что для достаточно стабильных компонентов, которые не перерендериваются десятки раз, создание новой функции во время рендера не стоит вообще ничего. А "лидеры мнений" типа того же Дэна Абрамова, вообще не имеют ничего против создания новых функций даже в большом количестве при рендеринге.
      Запрет на их использование — это весьма популярное заблуждение, не имеющее под собой достаточного количественного обоснования.
      Хотя да, в идеальном мире оно сэкономит какому-то приложению единицы килобайт оперативки и доли процента скорости повторного рендеринга


  1. faiwer
    01.09.2019 13:26
    +2

    Сократить использование stateful-компонентов

    Хуки изменили это.

    Благодаря хукам разработчики React теперь могут писать функциональные компоненты (stateless), используя при этом state и даже методы жизненного цикла по своему желанию.

    О_о? Что я только что прочитал?


    • statefull = with state
    • stateless = without state
    • Hooks это state внутри функциональных компонент

    Компонент с использованием любого хука автоматически становится stateful. Даже если это какой-нибудь useCallback. Рекомендация использовать stateless компоненты вообще абсолютно никак не связана с тем как этот компонент реализован (используя какую-нибудь стрелочную функцию или таки класс). Эта рекомендация просто говорит о том, что компоненты без своего состояния проще устроены, их проще тестировать, они не подкинут вам пару багов на дебаг которых уйдёт пара часов.


    stateless !== функциональный. state[less|full] — это про наличие состояния. Хуки это состояние в пределах функциональных компонент. Они же это методы жизненного цикла приложения (useEffect). Разница между функциональными компонентами с хуками и классами со своим state и методами жизненного цикла в том, как оно организовано в кодовой базе, а не идиоматическая.


    P.S. простоты ради условно можно считать что useCallback, useMemo, useEffect не обязательно делают ваш компонент statefull, т.к. несмотря на то, что они таки используют свой state в недрах react, это не совсем тот state который так нелюбим многими


    P.S.S. я бы наверное вообще ничего не понял бы в п4, если бы не знал о чём речь, рекомендую переписать текст про мемоизацию, думаю людям в ней не сведущим будет сложно понять хоть что-то :)