Деструктуризация в React. Очевидно, но важно

e7a94115511915eb156dd1ab83e97846.png

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

Казалось бы, что можно рассказать о том, о чем все и так знают? Но практика и чтение статей на Хабре, показали, что есть некоторые нюансы использования деструктуризации в React, о которых не все из нас знают или просто не задумываются, хотя они и являются очевидными.

Как часто Вам приходилось сталкиваться с подобным кодом?

export default function ParentComponent ({ post }) {
  const { images, title } = post

  return (
    
  )
}

Думаю, что довольно часто. Все просто, удобно и отлично работает.

Теперь предположим, что данных для ParentComponent может и не быть, но, по каким либо причинам, нам все-таки нужно, чтобы у них было значение по умолчанию:

export default function ParentComponent ({ post }) {
  const { images = [], title = 'New Post' } = post

  return (
    
  )
}

Снова, все просто, удобно и отлично работает. Но что может пойти не так?

Давайте предположим, что мы решили оптимизировать ререндер нашего ChildComponent, т.е. мемоизируем его, с помощью React.memo:

const ChildComponent = React.memo(({ images, title }) => {
  return (
    <>
      
      
    
  )
})

И вот, мы столкнулись с проблемой, так как в случае, когда в нашем post нет массива images т.е. мы устанавливаем в него значение по умолчанию, а именно пустой массив, то для ChildComponent это будет является новым пропсом на каждый ререндер родителя или источника этой переменной. Это равнозначно с объявлением новой переменной с пустым массивом в компоненте, которая будет создаваться на каждый его рендер, собственно, это так и есть:

const images = []

Хорошо, что у нас один уровень вложенности, мы легко найдем проблемное место и сможем быстро исправить это недоразумение. А что, если уровень вложенности у нас больше, и есть ненавистный props drilling?

export default function ParentComponent ({ post }) {
  const { images = [], title = 'New Post' } = post

  return (
    
  )
}

const ChildComponent = ({ images, title }) => {
  return (
    <>
      
      
    
  )
}

const ImagesList = React.memo(({ images }) => {
  return (
    <>
      {images.map((image) => (
        
      ))}
    
  )
})

Да, ImagesList будет ререндериться каждый раз, когда будет происходит ререндер ParentComponent. Пример, конечно, простой, но ведь мемоизированный ImagesList может применяться по всему проекту десятки раз, иметь больше уровней вложенности, триггеров ререндера источника тоже может быть много и найти все места, где добавленное значение по умолчанию ломает нашу оптимизацию — может оказаться хоть и простой, но затратной по времени задачей.

Кстати, добавление значения по умолчанию в деструктуризации props компонента сразу в аргументах, очевидно, приводит к той же самой проблеме, но встречается на практике еще чаще:

const ChildComponent = ({ images = [], title }) => {
  return (
    <>
      
      
    
  )
}

Тем временем, значение по умолчанию примитивами, как в примере выше:

const { title = 'New Post' } = post

Не приведет к подобной проблеме, т.к. сверять memo будет именно примитивные данные, а не ссылки на массивы или объекты (вспоминаем базу).

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

© Habrahabr.ru