5 вещей в React, которые могут вас удивить
Библиотека React довольно проста, и в нее относительно легко войти, тем более сейчас существует большое количество материалов, которые помогают в изучении. Но у каждого инструмента есть свои уловки или проблемы, которые обычно не рассматриваются в этих руководствах. Вы узнаете их, когда кто-то просматривает ваш код, или, что еще хуже, когда вы сталкиваетесь с проблемой и отчаянно пытаетесь найти решение. Надеюсь, в этой статье я смогу раскрыть некоторые из этих вещей о React и, возможно, даже удивить вас!
1. Установка состояния с использованием предыдущего состояния непредсказуема
Управление состоянием - это основа React. useState, вероятно, является наиболее распространенным вариантом управления состоянием, однако в нем может быть некоторая неосведомленность о его реальном поведении.
Давайте посмотрим на следующий компонент:
import React, { useState } from "react"; import "./styles.css"; export default function App() { const [counter, setCounter] = useState(0); return ( <div className="App"> <h1>Counter: {counter}</h1> <button onClick={() => { setCounter(counter + 1); setCounter(counter + 1); }} > + </button> </div> ); }
Каким будет значение состояния счетчика после того, как пользователь нажмет кнопку?
A. 2
B. 1
Уверены в своем ответе?
Попробуйте сами:
Да, ответ - B!
Причина в том, что во время обновления состояния мы использовали предыдущее значение: setCounter(count + 1)
. По сути, функция установки состояния функционального компонента замыкает предыдущее значение. Это означает, что когда она в конечном итоге будет выполнена (функция установки состояния является асинхронной), она может содержать значение состояния, которое больше не актуально. Вдобавок к этому последовательное выполнение setState может заставить алгоритмы планирования React обрабатывать несколько очень быстрых обновлений состояния с использованием одного и того же обработчика событий.
Та же проблема может возникнуть при установке состояния внутри асинхронной функции:
onClick = { () => { setTimout(() => setCounter(counter + 1), 1000); } };
Но не беспокойтесь, React предлагает простое решение этой проблемы - «функциональные обновления».
setCounter((prevCounter) => prevCounter + 1);
Вместо того, чтобы передавать значение напрямую в setCounter, мы передаем функцию. Эта функция получает в качестве параметра предыдущее состояние.
Таким образом, мы можем быть уверены, что React предоставляет нам правильное значение предыдущего состояния, и избегать сценариев, которые могут вызвать неожиданное поведение.
Всякий раз, когда ваше обновление состояния полагается на предыдущее состояние, обязательно используйте функциональные обновления!
2. Вы можете использовать useRef для хранения статической переменной
Мы привыкли использовать механизм ref в React как средство доступа к узлу DOM элемента, возможно потому что он нам нужен для вычисления его размера, установки статуса фокуса или в основном для того, что React не может сделать естественным образом. Но ссылки также можно использовать для другой цели - чего мы можем достичь легко с компонентами класса, но не можем с функциональными - сохранить статическую переменную, которая не будет воссоздаваться при каждом рендеринге.
Зачем нам это нужно? Что ж, это действительно зависит от ситуации. Например, предположим, что мы используем некоторую библиотеку javascript, которая не была написана для React.
В этом примере у нас есть класс Dog. У собаки две функции: одна - присвоить ей имя, а вторая - лаять.
Если мы попытаемся создать экземпляр этого класса, используя простую переменную, произойдет то, что в случае (по какой-либо причине) компонент будет повторно отрисован, будет создан новый экземпляр. Так что любые изменения, которые мы сделали до сих пор, исчезнут.
Но, используя ref, мы сможем поддерживать наш экземпляр в живых, пока мы не решим переопределить его, установив новое значение для ref.current.
someRef.current = newValue;
3. React можно заставить повторно смонтировать компонент
Запись в DOM - одно из наиболее затратных действий, которые может выполнить React. Вот почему мы обычно не хотим перемонтировать компоненты, если в этом нет крайней необходимости. Увы, но иногда приходится, по разным причинам. Как мы вообще можем сказать реакту размонтировать или немедленно смонтировать компонент? С помощью простого трюка - предоставив ключ нашему компоненту и изменив его значение.
Ключевое свойство - это специальное свойство React, о котором вы, скорее всего, узнали, когда впервые пытались визуализировать массив компонентов.
Ключ - это то, что помогает React отслеживать элемент, даже если мы изменили его положение в структуре нашего компонента или повторно отрендерили родительский (в противном случае каждый рендер приведет к повторному монтированию всего массива компонентов, что плохо в плане производительности).
Используя этот механизм, мы можем обмануть React, чтобы он подумал, что компонент не такой же, как его предыдущий «я», и заставим его повторно смонтировать.
Глянь пример:
Кстати, зачем нам это вообще нужно? Ну, например, это быстрый способ создать кнопку «перезагрузить» для вашего приложения. Другой вариант использования - это сочетание обработки DOM с React, где мы хотим, чтобы состояние React отражало обновления, которые были внесены в DOM «извне».
Но этого все же следует избегать, если только это не последнее средство.
4. Контекст работает не так, как вы ожидаете.
Контекстный API - отличное встроенное решение для обмена состоянием между компонентами и предотвращения «prop drilling» - передачи пропсов на несколько уровней вниз - от одного компонента к другому, иногда просто ради передачи их туда, где это действительно необходимо.
При использовании для повторяющихся или сложных обновлений это не так эффективно. Цитата Себастьяна Маркбиджа, инженера Facebook:
«Мое личное резюме состоит в том, что новый контекст готов к использованию для маловероятных обновлений с низкой частотой (например, локаль / тема). Также хорошо использовать его так же, как использовался старый контекст. Т.е. для статических значений, а затем распространять обновления через подписки. Он не готов к использованию в качестве замены для распространения всех состояний, подобных Flux ".
Даже команде React-Redux пришлось вернуть части библиотеки, переписанной с контекстным API в версии 6, просто из-за значительного снижения производительности по сравнению с предыдущей версией (теперь React-Redux использует контекст только для передачи ссылки на хранилище) .
Другой серьезной проблемой является неспособность компонентов подписаться только на часть контекста (когда значение контекста является объектом, а не примитивом).
Давайте посмотрим на следующий пример:
В этом примере в контексте хранится объект, содержащий две записи: имя и возраст. Затем у нас есть два компонента, подписанных на этот контекст. Один использует только возраст, а другой - имя. Но - при обновлении только одной из записей, либо имени, либо возраста, компоненты <Name> и <Age> повторно отображаются. Вы можете получить доступ к этой демонстрации здесь и посмотреть, как она работает.
Все компоненты, подписанные на контекст, содержащие значение непримитивного типа, повторно визуализируются, что явно неэффективно и в некоторых случаях может вызвать проблемы с производительностью.
Есть несколько собственных решений этой проблемы, но большинство из них громоздкие. Вы также можете рассмотреть возможность использования сторонней библиотеки, такой как use-context-selector, которая значительно упрощает решение этой проблемы.
5. В React есть целый API для работы с дочерними объектами.
Передача дочерних элементов компоненту - очень распространенный шаблон, независимо от того, используете ли вы композицию или просто пишете повторно используемый компонент-оболочку.
Но знаете ли вы, что вместо простого рендеринга дочерних элементов React также предоставляет вам API, позволяющий делать с ним все, что угодно?
Прежде всего, дочерние элементы могут быть строкой, объектом или массивом любого из этих типов. Это затрудняет итерацию таких дочерних элементов, поэтому мы можем вызвать toArray
для преобразования дочерних элементов в массив:
React.Children.toArray(children) // Если вы хотите использовать map/forEach: React.Children.map(children, fn) React.Children.forEach(children, fn)
Мы также можем вызвать count, чтобы узнать, сколько детей есть, избегая тех же проблем:
React.Children.count(children)
Если вам нужно принудительно использовать один дочерний элемент в вашем компоненте (я недавно заметил, что это делает formik), вы можете просто включить в свой компонент следующую строку, и React выполнит проверку и обработку ошибок за вас:
React.Children.only(children)
Вы можете использовать следующую песочницу, чтобы поэкспериментировать с этим:
Вот и все! Я очень надеюсь, что некоторые из них будут вам полезны. Пожалуйста, не стесняйтесь делиться в комментариях любыми другими уловками React, которые вам известны!
Источник: https://medium.com/geekculture/react-5-things-that-might-surprise-you-ddefd9fbac0f