Простые советы по написанию чистого кода React-компонентов

от автора

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


Избегайте использования оператора spread при передаче свойств

Начнём с анти-паттерна, с приёма работы, которым лучше не пользоваться в тех случаях, когда для этого нет конкретных, обоснованных причин. Речь идёт о том, что следует избегать использования оператора spread ({...props}) при передаче свойств от родительских компонентов дочерним.

Передача свойств таким образом может, на самом деле, ускорить разработку. Но этот приём значительно усложняет обнаружение ошибок в коде. Разработчик начинает меньше доверять своим компонентам, это усложняет их рефакторинг, и, как результат, ошибки появляются в них раньше, чем в случаях, когда оператор spread для передачи свойств не используется.

Объявляйте функции, использующие несколько параметров, применяя объекты с параметрами

Если функция принимает несколько параметров — хорошо будет поместить их все в объект. Вот как это может выглядеть:

export const sampleFunction = ({ param1, param2, param3 }) => {     console.log({ param1, param2, param3 }); } 

Если подходить к созданию сигнатур своих функций именно так — можно получить несколько заметных преимуществ перед обычным способом объявления функций:

  1. Вам больше не придётся беспокоиться о порядке, в котором функции передаются аргументы. Я несколько раз сталкивался с проблемой, когда передача аргументов функции в неправильном порядке приводила к возникновению ошибки.
  2. При работе с редакторами, в которых используется IntelliSense (в наши дни это — практически все редакторы), в нашем распоряжении окажется приятная возможность автодополнения при вводе аргументов функции.

При работе с обработчиками событий используйте функции, возвращающие функции-обработчики

Если вы знакомы с функциональным программированием — то знайте, что то, о чём я хочу сейчас рассказать, напоминает каррирование. Суть в том, что тут применяется заблаговременная установка некоторых параметров функции.

Взгляните на этот пример:

export default function SampleComponent({ onValueChange }) {     const handleChange = (key) => {         return (e) => onValueChange(key, e.target.value)     }      return (         <form>             <input onChange={handleChange('name')} />             <input onChange={handleChange('email')} />             <input onChange={handleChange('phone')} />         </form>     ) } 

Как видите, такое оформление функции-обработчика событий помогает поддерживать дерево компонентов в чистоте.

Используйте объекты, хранящие пары ключ-значение вместо условного оператора

Если нужно выводить различные элементы, основываясь на некоей логике, я посоветовал бы пользоваться объектами, хранящими данные в формате ключ-значение (коллекциями записей), а не выражениями if/else.

Вот пример, в котором используется if/else:

const Student = ({ name }) => <p>Student name: {name}</p> const Teacher = ({ name }) => <p>Teacher name: {name}</p> const Guardian = ({ name }) => <p>Guardian name: {name}</p>  export default function SampleComponent({ user }) {     let Component = Student;     if (user.type === 'teacher') {         Component = Teacher     } else if (user.type === 'guardian') {         Component = Guardian     }      return (         <div>             <Component name={user.name} />         </div>     ) } 

А вот — пример использования объекта, хранящего соответствующие значения:

import React from 'react'  const Student = ({ name }) => <p>Student name: {name}</p> const Teacher = ({ name }) => <p>Teacher name: {name}</p> const Guardian = ({ name }) => <p>Guardian name: {name}</p>  const COMPONENT_MAP = {     student: Student,     teacher: Teacher,     guardian: Guardian }  export default function SampleComponent({ user }) {     const Component = COMPONENT_MAP[user.type]      return (         <div>             <Component name={user.name} />         </div>     ) } 

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

Оформляйте в виде хуков компоненты, рассчитанные на многократное использование в проекте 

Я считаю весьма полезным паттерн, о котором хочу рассказать в этом разделе.

Возможно, вы сталкивались или столкнётесь с ситуацией, когда окажется, что какие-то компоненты постоянно используются в разных частях приложения. Если им для работы нужно хранить что-то в состоянии приложения, это значит, что их можно обернуть в хуки, предоставляющие им это состояние. Удачными примерами подобных компонентов можно назвать всплывающие окна, тост-уведомления, простые модальные окна. Например — вот компонент, в котором, с использованием хука, реализовано простое модальное окно для подтверждения выполнения некоей операции:

import ConfirmationDialog from 'components/global/ConfirmationDialog';  export default function useConfirmationDialog({     headerText,     bodyText,     confirmationButtonText,     onConfirmClick, }) {      const [isOpen, setIsOpen] = useState(false);      const onOpen = () => {         setIsOpen(true);     };         const Dialog = useCallback(         () => (             <ConfirmationDialog                 headerText={headerText}                 bodyText={bodyText}                 isOpen={isOpen}                 onConfirmClick={onConfirmClick}                 onCancelClick={() => setIsOpen(false)}                 confirmationButtonText={confirmationButtonText}             />         ),         [isOpen]     );      return {         Dialog,         onOpen,     };  } 

Пользоваться этим хуком можно так:

import React from "react"; import { useConfirmationDialog } from './useConfirmationDialog'  function Client() {   const { Dialog, onOpen } = useConfirmationDialog({     headerText: "Delete this record?",     bodyText:       "Are you sure you want to delete this record? This cannot be undone.",     confirmationButtonText: "Delete",     onConfirmClick: handleDeleteConfirm,   });    function handleDeleteConfirm() {     //TODO: удалить   }    const handleDeleteClick = () => {     onOpen();   };    return (     <div>       <Dialog />       <button onClick={handleDeleteClick} />     </div>   ); }  export default Client; 

Такой подход к абстрагированию компонентов избавляет программиста от необходимости написания больших объёмов шаблонного кода для управления состоянием приложения. Если вы хотите узнать о нескольких полезных хуках React — взгляните на этот мой материал.

Разделяйте код компонентов на части

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

▍Использование обёрток

Если вам сложно найти способ разбиения большого компонента на части — взгляните на функционал, предоставляемый каждым из его элементов. Некоторые элементы, например, существуют ради решения узкоспециализированных задач, например, для поддержки механизма drag-and-drop.

Вот пример компонента, реализующего drag-and-drop с использованием react-beautiful-dnd:

import React from 'react' import { DragDropContext, Droppable } from 'react-beautiful-dnd';    export default function DraggableSample() {     function handleDragStart(result) {         console.log({ result });     }      function handleDragUpdate({ destination })          console.log({ destination });     }      const handleDragEnd = ({ source, destination }) => {         console.log({ source, destination });     };      return (         <div>             <DragDropContext                 onDragEnd={handleDragEnd}                 onDragStart={handleDragStart}                 onDragUpdate={handleDragUpdate}             >                 <Droppable droppableId="droppable" direction="horizontal">                     {(provided) => (                         <div {...provided.droppableProps} ref={provided.innerRef}>                             {columns.map((column, index) => {                                 return (                                     <ColumnComponent                                         key={index}                                         column={column}                                     />                                 );                             })}                         </div>                     )}                 </Droppable>             </DragDropContext>         </div>     ) } 

А теперь взгляните на тот же компонент после того, как мы переместили drag-and-drop-логику в компонент-обёртку:

import React from 'react'  export default function DraggableSample() {     return (         <div>             <DragWrapper>                 {columns.map((column, index) => {                     return (                         <ColumnComponent                             key={index}                             column={column}                         />                     );                 })}             </DragWrapper>         </div>     ) } 

Вот — код обёртки:

import React from 'react' import { DragDropContext, Droppable } from 'react-beautiful-dnd';  export default function DragWrapper({children}) {     function handleDragStart(result) {         console.log({ result });     }      function handleDragUpdate({ destination }) {         console.log({ destination });     }      const handleDragEnd = ({ source, destination }) => {         console.log({ source, destination });     };      return (         <DragDropContext             onDragEnd={handleDragEnd}             onDragStart={handleDragStart}             onDragUpdate={handleDragUpdate}         >             <Droppable droppableId="droppable" direction="horizontal">                 {(provided) => (                     <div {...provided.droppableProps} ref={provided.innerRef}>                         {children}                     </div>                 )}             </Droppable>         </DragDropContext>     ) } 

В результате код компонента можно очень быстро прочесть и в общих чертах разобраться с тем, какую именно задачу решает этот компонент. А весь функционал, имеющий отношение к механизму drag-and-drop, размещён в компоненте-обёртке. С кодом, реализующим этот механизм, теперь тоже гораздо проще и удобнее работать.

▍Разделение обязанностей

Тут я хочу рассказать о моём любимом методе разделения больших компонентов на части.

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

Паттерн «хук» появился именно благодаря идее разделения обязанностей. Можно и даже нужно оборачивать в хуки всю логику, ответственную за взаимодействие с некими API или с глобальным состоянием.

Например — давайте посмотрим на следующий компонент:

import React from 'react' import { someAPICall } from './API' import ItemDisplay from './ItemDisplay'  export default function SampleComponent() {     const [data, setData] = useState([])      useEffect(() => {         someAPICall().then((result) => {             setData(result)         })     }, [])      function handleDelete() {         console.log('Delete!');     }      function handleAdd() {         console.log('Add!');     }      const handleEdit = () => {         console.log('Edit!');     };      return (         <div>             <div>                 {data.map(item => <ItemDisplay item={item} />)}             </div>             <div>                 <button onClick={handleDelete} />                 <button onClick={handleAdd} />                 <button onClick={handleEdit} />             </div>         </div>     ) } 

Ниже показано то, что получилось после его рефакторинга, в ходе которого его код разделён на части с применением пользовательских хуков. А именно — вот сам компонент:

import React from 'react' import ItemDisplay from './ItemDisplay'  export default function SampleComponent() {     const { data, handleDelete, handleEdit, handleAdd } = useCustomHook()      return (         <div>             <div>                 {data.map(item => <ItemDisplay item={item} />)}             </div>             <div>                 <button onClick={handleDelete} />                 <button onClick={handleAdd} />                 <button onClick={handleEdit} />             </div>         </div>     ) } 

Вот — код хука:

import { someAPICall } from './API'  export const useCustomHook = () => {     const [data, setData] = useState([])      useEffect(() => {         someAPICall().then((result) => {             setData(result)         })     }, [])      function handleDelete() {         console.log('Delete!');     }      function handleAdd() {         console.log('Add!');     }      const handleEdit = () => {         console.log('Edit!');     };      return { handleEdit, handleAdd, handleDelete, data } } 

▍Хранение кода каждого компонента в отдельном файле

Часто программисты пишут код компонентов примерно так:

import React from 'react'  export default function SampleComponent({ data }) {      export const ItemDisplay = ({ name, date }) => (         <div>             <h3><font color="#3AC1EF">▍{name}</font></h3>             <p>{date}</p>         </div>     )      return (         <div>             <div>                 {data.map(item => <ItemDisplay item={item} />)}             </div>         </div>     ) } 

И, хотя тут нет ничего ужасно неправильного, нельзя рекомендовать писать код именно так. У перемещения ItemDisplay в отдельный файл нет никаких минусов. При этом плюсом такого подхода можно назвать тот факт, что, после переноса кода компонента в отдельный файл, компоненты проекта оказываются слабее связаны друг с другом. Их, кроме того, легче будет расширять.

Итоги

Написание чистого кода, в основном, сводится к тому, чтобы внимательно и вдумчиво относиться к своему делу и находить время на изучение удачных и неудачных паттернов. Хорошие решения стоит включать в свои проекты, а анти-паттерны лучше обходить стороной. Собственно говоря, применение удачных паттернов помогает писать более чистый код React-компонентов. Паттерны, которыми я тут поделился, очень помогают мне в работе. Надеюсь, они пригодятся и вам.

Что вы посоветовали бы тем, кто хочет сделать код своих React-компонентов чище?

ссылка на оригинал статьи https://habr.com/ru/company/ruvds/blog/553068/