Галоўная / Артыкулы

Пераскладчуванне стану React: дзе насправды павінны знаходзіцца вашы даны

Менша колькасць багоў у React завяліква з меньшага выкарыстоўвання useState: перанесіце стан у URL, DOM або вырахаваныя значэнні, і выберыце, хто будзе адпавядаць за кожную частку, прычым не вяршыце да адпаведнага механізма карыстоўвання станам.

Выдавец: REACTAPP.TOP17 хв чытання3931 слоў

Крыніцы: Stop Adding React State: It’s Creating Bugs You Don’t Need

Працавак адміністрацыі ў растучай компаніи з програмнага аб’ёктаванняяслуг выдзеляе значную частку ранку на працу з базай дадзеных кліентаў, якая мае дзесяць тысяч аблікаванняў.

Пасля рэтельнага фільтраўвання ён выделяе корпаратыўных кліентаў, якія знаходзяцца ў Еврапе і чыёў аблікаванні не было паўтарнае за апошнія дзевяноста дзён.

Рэзультатам стае тачны список з трыдцяці аблікаванняў, якімы трэба негайнае адзначэнне.

Ён берае URL прымусова з браузера і надае яго через Slack своему рэгіональнаму дыректару з прымечаннем:

"Ціха прыоритэтныя аблікаванні на сённяшні ранак."

Дыректар ачынае гэтыя лінк.

Старонка атрымліваецца.

Але замест фільтраванага списка тут паказваецца стандартны відворы: усе дзесяць тысяч аблікаванняў, без фільтраўвання і без сортавання.

Все гэтыя рэтельныя фільтрацыі зніклі, таму што інжынер, які стварыў сторанку, зберагаў кожны фільтр, поле сортавання і значэнне пагінацыі ў стане локальнага компонента за дапамогою useState.

Гэты сценарый ілюструеў модель, якая постаўляецца стало ў кодавых базах React.

Мы часта вярнаемся да стану проста таму, што React так лёгка яго стварае.

Патрэбна значэнне фільтра?

useState.

Патрэбна ведаць, якая карточка ў актыўным стане?

useState.

Патрэбны лічыльнік?

useState.

Патрэбна зберагаць полье формы?

useState.

Патрэбна ведаць разныя фазы вызову API?

Это ўсё тры дадатковыя вызовы useState.

Чэрз некалькі часоў адны компонент начынае кераваць дзесяткамі значэнняў, якія спачатку нават не патрабаваліся ў стане React.

Сам стан не ўжо ёсць вораг у гэтым случае. Кожны не просты React-дзял прымушаны ўжо яго выкарыстоўваць.

Асалодзіцае працэю ўстановка стану там, дзе яму не месца.

Кожная дапаможная зменна стану — это ўсё больш элемента, які ваш дзял павінен синхронізаваць з усім іншым.

Якщо гэта не караць, гэта можа спрычыніць шмат проблем, з-поміж яых:

  • Дадатковая праця, якая патрэбна для падтрымкі суманства дубліруючых значэнняў
  • Даныя, якія застаюцца старымі ў кэшы
  • Значэнняя, якія паказваюцца на экране і якія больш не адпавядаюць рэальнасці
  • Апдэйты, якія выкананы, калі на самай працэ нічога значныя не зменілася
  • Проблемы з часам, калі два апдэйты зустрочыцца неперакладальна
  • Эфекты, якія становяцца заплутанымі, калі павінны быць адпаведнымі
  • Багі, якія трэба набагато дольш часу, каб знайсці ўсё іхнё джэрела
  • Старонка, якая забывае ўсё як толькі яе перазавантажваюць
  • Экраны, даець кан якім няма можлівасці з’яўіцца пахадам або сэваваць на пазнейшы час
  • Комбінаціі флагоў, якія ствараюць стан інтэрфейсу, які ніколі не павінен існаваць
  • Стварэнне высокакалітэтных праектаў на React на самай справе не абумоўкае володзення складной логікай стану.

    Часта гэта абумоўкае здатнасць разгледзець, калі вовсе не трэба ствараць такі стан.

    1. URL — гэта найлепшы менеджар стану вашага прыемніка

    Уявіце типовы экран прыемніка з великай колькасцю дадзеных.

    Яму, верагодна, патрэбна ўстаўка:

    • Пошук
    • Фільтры
    • Сортаванне
    • Стронічнаванне
    • Актыўныя вкладкі
    • Выбраныя катэгорыі
    • Дзеянні дат

    Зазвычай адбываецца так, што весь гэты матерыял кладзіцца ў стан компонента.

    function ProductList() {
      const [activeCategory, setActiveCategory] = useState('all');
      const [currentPage, setCurrentPage] = useState(1);
      const [searchQuery, setSearchQuery] = useState('');
    
      // Fetch and filter data...
    }
    

    На першы погляд, гэта здаецца абсалютна нормальным.

    Потым выкарыстоўвач натыкаецца на кнопку апавярнення.

    Кожная значэнне вяртаецца да свайго стандартнага стану.

    Або яны дзеляюцца гэтым супакой з колегай.

    Этот калега прабывае на абсолютна іншым адзору.

    Або яны нажываюць кнопку «Назад» у браузеры.

    Фільтрацыя, якую яны настаўілі, можа не вернуцца, таму што стан React не мае вбудованага звязку з історыяй навігацыі браузера.

    useState сама па сабе тут не ў чыні.

    Справжняя проблема заключаецца у тым, што URL вялікі час вжываецца як механізм для прадставлення стану навігацыі, і яго цэлкам ігнаруецца.

    У зменшыню да таго, каб напісаць:

    const [currentPage, setCurrentPage] = useState(1);
    const [searchQuery, setSearchQuery] = useState('');
    const [category, setCategory] = useState('all');
    

    вы моглі бы закодаваць тую ж самую інфармацыю безпосередзя ў URL.

    Што-та такое:

    /products?category=enterprise&page=3&query=react
    

    Выкарыстоўваючы бібліятэкі для маршрутацыі, такія як Next.js, React Router або TanStack Router, вы можете чытаць гэтыя параметры працоўна з URL:

    import { useSearchParams } from 'next/navigation';
    
    function ProductList() {
      const searchParams = useSearchParams();  const category = searchParams.get('category') || 'all';
      const page = Number(searchParams.get('page')) || 1;
      const query = searchParams.get('query') || '';  // Fetch data using the URL state...
    }
    

    Гэта невялікае змена драматычна зменяе цикл жыцця таго стану.

    Апдэтуваць сторанку?

    Фільтры застаюцца незменнымі.

    Падзеліцца наслядам?

    Кожны, хто яго ачыне, побачыць абсолютна тую ж відобразу.

    Зберагчы як закладку?

    Пазней ён зноў запрацюе у там самым стане.

    Нажмаць «Назад»?

    Браузер правільна пераходзіць да паканальных станоў.

    Это значны архітектурны прынт.

    Стан URL дае вам больш, чым працэздатнась

    Выжыванне пасля апдэту сторанкі — толькі частка прынтамоў.

    Большы прынт — гэта тое, што URL стае портатыбным кадрам текущаг стану вашай аплікацыі, які можна падзеліцца з кемнебудзь.

    Вярнімся да панелі керування падтрымкай кляўэнтаў як прыклад:

    /support/tickets?status=open&priority=high&team=billing
    

    Адні манеджары могу мгновенна апрацаваць гэты самы насляд другім манеджарам.

    Панель керування, спрямованая на прыемку замоў, можа выглядаць так:

    /accounts?region=europe&plan=enterprise&renewal=90
    

    Інструмент аналітыкі можа викорыстоўваць ўсё такое:

    /analytics?range=30d&metric=revenue&groupBy=region
    

    Ніхто з эых не ўтвараецца як довільныя строкі запиту.

    Кожна з іх – глыбокія лінкі, які вядуць да конкрэтнага, можна восстанавіць стану прыкладнення.

    Самэўсёлі гэта тое, для чаго ствараліся URL-адресы.

    Калі стан URL мае сэнс

    Параметры URL чырпаюцься як ідеальны варыянт для такіх рэчаў, як:

    • Тое, што корыстнік увёў у поле пошуку
    • Актыўныя выбіраны фільтры
    • Колонкі та напрамак сортавання дадзэнняў
    • Якая стораніца рэзультатаў адображаецца
    • Актуальна выбраная вікнонка
    • Выбраны період часу
    • Які зявлены варыянт зараз адкрыты
    • Наладжэнні для створанага атласу
    • Індывідуальная налааджэння на панелі керавання
    • Будзь-які стан інтэрфейсу, які корыстнік можа захацець перадаць іншаму

    Аднак не все паслужыць для таго.

    Паролі, адзначаецца, не павінны быць ў URL.

    Не павінна існаваць тымчасовая стана анімацыі модалага вікна.

    Узагальнена, большая, глыбокая структура об’екта таксама не павінна існаваць.

    Ключовы вопыт просты:

    Задайце сабе пытанне: чы можа корыстнік разумнаўця, што гэты відобразы можна будзе падазначыць, распаўсюдзіць чы вярнуць — якщо так, то URL, верагодна, ўпрымк для такога стану.

    2. Перастаньце зберагаць значэнні, якія можна проста вылічыць

    Адзин з простых спосабоў скорачыць стан React — перестаць зберагаць значэнні, якія на самай працоўцы вылічваюцца з іншых значэнняў.

    Возьмім гэты прыклад:

    const [items, setItems] = useState([]);
    const [itemCount, setItemCount] = useState(0);
    

    А ў іншых частках компонента:

    setItems(newItems);
    setItemCount(newItems.length);
    

    Тепер у вас ёсць два аднальныя зменны стану, якія описваюць тую ж асновную рэч.

    Якщо items апдэйтуецца, але itemCount не паўтарыць гэта, ваш інтерфейс пакажае некоректную інформацыю.

    Вы створылі проблему с синхронізацыёю, якой ніколі не было трэба.

    Чыстейшая версія выглядае так:

    const [items, setItems] = useState([]);
    
    const itemCount = items.length;
    

    Теперь ёсць едынага адказвальнага джерага.

    Колькасць вылічваецца кожны раз, калі компонент атрыбутуецца.

    Тая ж логіка застосоўваецца пад час фільтравання колекцыяў.

    Уместо таго, каб пісаць:

    const [items, setItems] = useState([]);
    const [activeItems, setActiveItems] = useState([]);
    

    зазвычай можна проста напісаць:

    const activeItems = items.filter(item => item.active);
    

    Альбо:

    const totalPrice = items.reduce(
      (total, item) => total + item.price,
      0
    );
    

    Альбо:

    const hasErrors = fields.some(field => field.error);
    

    Альбо:

    const completedCount = todos.filter(todo => todo.completed).length;
    

    Пытанне, якое варта задаць, не ў тым:

    "Магчымае тэхнічна зберагчы гэта ў стаце?"

    Звычайна, магчымае.

    Тое, што насправдзе трэба запытацца, — гэта:

    "Чы мае гэтыя значэння свой самастоятны цыкл жыцця?"

    Якщо ні, то яму, верагодна, не месца ў стаце.

    3. Не прыяжоце кожны поле формы да стану

    Іншая звычка, якую варта пазбітць, — це спрыяжэнне кожнага поля формы з яго сабе становай зменныю.

    Малая форма, створаная такім спосабам, можа выглядаць так:

    function ContactForm() {
      const [firstName, setFirstName] = useState('');
      const [lastName, setLastName] = useState('');
      const [email, setEmail] = useState('');
      const [message, setMessage] = useState('');
    
      const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();    submitData({
          firstName,
          lastName,
          email,
          message
        });
      };  return (
        <form onSubmit={handleSubmit}>
          <input
            value={firstName}
            onChange={e => setFirstName(e.target.value)}
          />      <input
            value={lastName}
            onChange={e => setLastName(e.target.value)}
          />      <input
            value={email}
            onChange={e => setEmail(e.target.value)}
          />      <textarea
            value={message}
            onChange={e => setMessage(e.target.value)}
          />      <button type="submit">Send</button>
        </form>
      );
    }
    

    Тут нічога тэхнічна не ўскладненага.

    Управляемыя элементы вводу становяцься неабходнымі тады, калі ваша прыкладка дасправды павінна реагаваць на тое, што вводзіт корыстувач. Є калькі ситуацый, дзе гэта мае значэнне:

    • Перакананне ў правільнасці поля пад час його заполнення
    • Прадстаўленне падказак у стылі автодаполнення
    • Мгновэнае пераформатаванне тексту
    • Підлічэнне застаўшыхся симвалоў
    • Адкрыць чы сховаць іншыя поля на адной засадзе вводу
    • Оцэнка ступеня надзеі пароля
    • Прыяжэнне прыменшэння адной зменны з значэннем іншай

    Але часта ваша прыкладка не мае прычынай карбавацца на тое, што вводзіт корыстувач, пакуль той не нажме Submit.

    У такіх случаях немае прытэчкі, каб React стежыў за кожным символам пад час яго вводу.

    Браузер уже выкананае гэту роботу.

    Дазвольце браузеру займіцца гэтым

    Натыўныя формы HTML керуюць значэнням вводу вядомы час.

    Вы можете скорастацца на гэтае вбудованае прадзейсво за дапамогою FormData:

    function ContactForm() {
      const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
    
        const formData = new FormData(e.currentTarget);    const payload = {
          firstName: formData.get('firstName') as string,
          lastName: formData.get('lastName') as string,
          email: formData.get('email') as string,
          message: formData.get('message') as string,
        };    submitData(payload);
      };  return (
        <form onSubmit={handleSubmit}>
          <input name="firstName" required />
          <input name="lastName" required />
          <input name="email" type="email" required />
          <textarea name="message" required />      <button type="submit">Send</button>
        </form>
      );
    }
    

    За дапамогою гэтага падходу React больш не патрабуе спецыяльнага слота для стану кожнай полькі.

    Браузер зберагае тое, што увёў корыстнік.

    React проста чытае значэння пасля таго, як форма адправлена.

    Гэта становіцца особліва цянючым для большых форм.

    Уявіце сабе процес адкалікавання новых спэцялістаў у компаніі з 30 полькамі.

    Чы хацелі б вы справжнья 30 окремых зменных стану толькі для зберагача таго, што хтось увёў?

    Працоўна.

    Контролюваныя і неконтролюваныя элементы вводу можаць сусістаць

    Нічыга з гэтага не значыць, што контролюваныя компоненты — глытка.

    Яны не такі.

    Існуе багато ситуацыяў, калі контроль над вхіднымі данымі є абсалютна правильным.

    Суть проста — трэба паслабіць уявленне, што React павінен за замовчанням кераваць значэнням кожнага поля.

    Якщо ваша аплікацыя не патрэбуюць даных да моменту адправкі, часта ўсё, што трэба, — це дазволіць стандартнаму форму зберагчы іх.

    Роблена такая размежованасць можа значна спростіць вялікія формы.

    4. Чаму булевы флагі не працуюць

    Іншая часта проблема постае ў рэальных кодавых базах.

    Разглянем типовы вызов API.

    Паўзловы падход — стежыць за цім за дапамогою трохоў разных булевых значэнняў:

    const [isLoading, setIsLoading] = useState(false);
    const [isSuccess, setIsSuccess] = useState(false);
    const [isError, setIsError] = useState(false);
    

    Спачатку ўсе тры значэнняя ў стане false:

    loading = false
    success = false
    error = false
    

    Калі пачынаецца запит:

    loading = true
    

    Калі ён завершаецца успешна:

    loading = false
    success = true
    

    На першы погляд гэта здаецца разумным.

    Але заўважкім, што ветка для обробкі нештачакоў не можа скасаваць значэнне isLoading.

    У такім случае можа выйсці:

    loading = true
    error = true
    

    Альбо недбалы механізм перапрыткі можа заставіць вас у такой ситуацыі:

    success = true
    error = true
    

    У такі момент ваш компонент знаходзіцца ў стане, які логічна не мае сэнсу.

    Чы хто ён должен адобразіць паведамленне пра успех?

    Чы хто ён должен адобразіць стан нештачку?

    Чы хто ён все ж такі должен паказваць індыкатор загрузкі?

    З тэхнічной точка зору, усе трое булевых значэнняя могу адгукавацься на „так“ адночасна.

    Самэ гэта неодназначнасць і є багам.

    Адносіцеся да яго як да машыны з канечнай колькасцю станоў

    Болей чысты падход:

    type RequestStatus =
      | 'idle'
      | 'loading'
      | 'success'
      | 'error';
    
    const [status, setStatus] =
      useState<RequestStatus>('idle');
    

    Тепер кожны дзейсны стан є чыста выражаным і взаімна адэксклюзывным.

    Запрос можа быць толькі адным з:

    idle
    loading
    success
    error
    

    Няма можлівасці випадкова паказваць станы „загрузкі“ і „успеху“ адночасна.

    Рэндарынг таксама становіцца набагато простэйшым для разумення:

    if (status === 'loading') {
      return <Spinner />;
    }
    
    if (status === 'error') {
      return <ErrorMessage />;
    }if (status === 'success') {
      return <SuccessBanner />;
    }return <DefaultForm />;
    

    Это не проста додатковая функцыя TypeScript.

    Это адпраўляе шырэйшую правілу дизайна:

    Калі два элементы стану ніколі не можуць быць правдзівымі незалежна ад другога, іх не трэба моделюваць як два адзельныя зменныкі.

    5. Хацеўна было бы ухіліцца ад викорыстання Effects для сінхронізаціі стану

    Это ўжо другі прыклад, калі дадатковыя зменныкі стану ствараюць непатрэбны, можна было бы ухіліцца ад яго код.

    Пагляньце на такую структуру:

    const [firstName, setFirstName] = useState('');
    const [lastName, setLastName] = useState('');
    const [fullName, setFullName] = useState('');
    

    У поўнай зусімнасці з:

    useEffect(() => {
      setFullName(`${firstName} ${lastName}`);
    }, [firstName, lastName]);
    

    Тое, што вы тут створылі, — это механізм сінхронізаціі для значэння, якому з самага пачатку не была потребна сопны стан.

    Простэйшы спосаб прыладзення:

    const fullName = `${firstName} ${lastName}`;
    

    Змена можа здавацца незначной, але яе архітектурны эфект ўсё ж значны.

    У первасной версіі трэба:

    • Тры адзельныя зменныкі стану
  • Эфект для ўтримання іх у суракце
  • Няявныя зв’язкі сінхронізаціі межа цінямі
  • Дадзены цикл адрасавання, які запускаецца за дапамою эфекта
  • Масіва залежнасцяў, якая павінна заставацца точной
  • У перасмотрэнай версіі патрэбны толькі:

    • Две рэальныя ціні
    • Одна ціні, якая вылучаецца з іх
    • Жадных эфектаў
    • Жадной логікі сінхронізаціі

    Хорашыя прывычкі пад час перагляду компанентаў React — гэта запытанне:

    "Чаму гэтая ціні павінна існаваць за межамы расчыткаў адрасавання?"

    Якша адказ будзе "чырабаваць у суракце з чым-небудзь іншым," гэта зазвычай ўскладненне, што гэтая ціні вовсе не павінна быць стаўам.

    6. Тры запытанні, якія трэба задаць перад аддачай стаўу

    Перш чым запісваць гэты рядок:

    const [value, setValue] = useState(...);
    

    Спачатку перагляньце тры пераканання.

    Запытанне 1: Чы можна вылучыць гэта пад час адрасавання?

    Якщо даны, неабяжлівыя для стварэння гэтага значэння, вжо існуюць, няма прычыны зберагаць дублікат.

    Замест:

    const [itemCount, setItemCount] = useState(0);
    

    запісаць:

    const itemCount = items.length;
    

    Замест:

    const [hasErrors, setHasErrors] = useState(false);
    

    запісаць:

    const hasErrors = fields.some(field => field.error);
    

    Утримвацца ад зберагачвання стану для таго, што можа быць вырахавана на месцы.

    Запит 2: Чы гэта должна застацца пасля перзагрузкі сторанкі?

    Якщо так, разважыце, чы ўсё-такі URL яўляецца правым месцам для гэтых дадзенняў.

    Тыповыя варыянты:

    • Тэрміны пошуку
    • Стан пагінацыі
    • Актыўныя фільтры
    • Парадакт сортавання
    • Выбраныя вкладкі
    • Дзеянні дат

    Задайце сабе пытанне: якшо лінк на гэтую сторанку будзе распаўнуты, чы абліковальнік должен прабачыцца на абоўсцю той жа відворы?

    Якшо так, то URL зазвычай яўляецца лепшым месцам для гэтых дадзенняў.

    Запит 3: Чы можа браузер зберагчваць гэта до моменту адправкі формы?

    Якщо значэнне палаты мае значэнне толькі час адправкі, можа патрабавацца толькі неактуальны вхід.

    Замест:

    const [email, setEmail] = useState('');
    

    вам можа патрабавацца проста:

    <input name="email" type="email" />
    

    і прачытаць яго пазней за дапамою:

    const formData = new FormData(e.currentTarget);
    

    Дазвольце браузеру караць тое, што ён вже можа караць.

    7. Чы ўсё справды належыць у стане React

    Нічга з гэтаго не ўскладнюе падзею абыцяжна зняць стан компонента.

    Прабава абыцяжна яго зняць будзе перекантролюваннем.

    Багато значэнняў справядліва падходзяць для зберагчання ў стане.

    Напрыклад:

    const [isOpen, setIsOpen] = useState(false);
    

    Тымчасавы выпадаючы список або модальна вікно зовсім не павінны адбівацца ў URL.

    Інтэрактыўны стан

    const [activeStep, setActiveStep] = useState(1);
    

    Калі значэнне керуе інтэракцыяй, якая застаёцца выключна ўнутрь адного компонента, яго зберагчанне ў локальным стане часта яўляецца правым рашэннем.

    Значэнні, які змянююцца незалежна

    Якщо ў чаго-небудзь сапраўды є свой жыцёвы цикл, і няма можнасці вырачыць яго на адной з ужо імеючыхся дадзэнняў, тады стан ёсць правільным месца для яго.

    Оптымістычны UI

    Інодзе патрэбна адразу паказаць ўсё на экране, прычаму сервер яшчэ не паўнастаілі за тым, што гэта адбылося.

    Гэта таксама ўзгоднае выкарыстоўванне стану.

    Мета не ў тым, каб следаваць гэтам правілу:

    "Вжывайце менш useState, незалежна ад усьога."

    Мета – гэтае правілу:

    "Бярозцеся, каб кожны элемент стану мёў правильнага власніка."

    Інодзе правільным власнікам ёст Контэйнер React.

    Інодзе – URL.

    Інодзе – DOM.

    Інодзе – сервер.

    Інодзе – спецыяльны слой для запрашэння дадзэнняў або керавання станам.

    Самэ гэтае рашыненне праўа ўласнасці і ёсьць тое, што насправдзе формуе вашу архітектуру.

    8. Сфокусавацца на тым, хто ўладае станам, а не толькі на тым, сколькі ў вас яго ёсць

    Добра пабудаваныя прыклады застосоў на React не визначаюцца колькісцю маленькіх зменных стану.

    Насправдзе важлівае — надаць кожной часткі стану чыстага, правильнага власніка.

    Возьмім дашборд, які стежыць за:

    Search query
    Filters
    Pagination
    Sort order
    Form values
    Server data
    Loading status
    Selected row
    Modal visibility
    Derived totals
    

    Існуе спакуса проста падмесціць усе гэта ў useState без дзвяго раздуму.

    Але гэтыя значэнні не выканаюць адно і тое ж заведамства, таму яны не павінны знаходзіцца ў адном месцы.

    Болей корыстны модэль мышлення — раздзеліць іх так:

    URL

    • Пошук
    • Фільтры
    • Стронаванне
    • Парадакт сортавання
    • Вікны для аднарасовага распадзелу

    DOM

    • Значэнні формы, якія маюць значэнне толькі пасля ўсунення формы

    Сервер / шар дадзэнняў

    • Даныя кліентаў
    • Адпаведзі з API
    • Даныя сервера, якія зберагліся у кэшы

    Вырахаваныя значэнні

    • Колькасці
    • Аб’ёмы
    • Фільтраваныя спісы
    • Падсумкі пераканальнай перапыткі

    Стан React

    • Візыбнасць модалага вікна
    • Тымчасовыя выборы
    • Стан анімацыі
    • Короткачасныя взаімадзеі

    Калі вы распрадзялеце стан такім чынам, компоненту застаёцца значна меньш да сінхронізацыі.

    Самэўсёлкаванне ў такі спосаб являе ся справжнім прыбуткам.

    9. Падражнены стан — прычына багоў завтра

    Найбольш рызыкаваны стан у прыёмніце не павінен быць неабходна найболей складным станам.

    Часта цэў стан, які існуе ў больш чым аднам месцы адночасова.

    Уявіце сабе, што у вас є:

    const [products, setProducts] = useState([]);
    const [filteredProducts, setFilteredProducts] = useState([]);
    

    Тепер вы абсалютная відпавальність за тое, каб filteredProducts завжды падзялася з тым, што ў products.

    Якшо дадаць ўсё больш аднаго фільтра, то:

    const [filteredProducts, setFilteredProducts] = useState([]);
    const [searchResults, setSearchResults] = useState([]);
    

    Тепер існуе ўсё больш адна зв’язка, якую трэба ручна падтрымваць у сувярзанасці.

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

    Інтэрфейс тыхо выходзіць за межы сувярзанасці з рэальнасцю.

    У падсумку корыстнік адмаўляецца пра ўсё такое:

    "У підрахунку напісана 42 продукты, але на экране паказваецца толькі 39."

    Такая несувярзанасць зазвычай не ўскладненне самай логікі фільтрацыі.

    Это симптам з’яўлення ў прыкладнай програме больш чым аднага джерела правды для тых сабе інформацый.

    Якшо ж вы будете выведаць значэнне:

    const filteredProducts = products.filter(...);
    

    Вы усунулі цэлы клас багоў прычаму, што яны не моглі выйсці на паверх.

    Самэўтама, зменшэнне колькасці стану не стосуецца толькі выкарыстоўвання ресурсаў пад час атракцыі.

    На самай справе хадзіць пра змену колькасці элементаў, якія могу стаць несінхроннымі.

    10. Косць стану выходзіць за межы простаеў атракцыі

    Калі людзі гавораюць пра стан, размова зазвычай сфокусаваная на выкарыстоўванні ресурсаў: «Чы гэта спрычыніць новую атракцыю?»

    Это правядзячая прычына для абавязак, але гэта не самая дорога частка ў всім процесе.

    Праўыя косты — это сложнасць.

    Кожная дадатковая зменная стану — это ўсё, што ваша команда павінна памяцать.

    Потым неабходна дадаць вызов методу задання значэння.

    setSomething(...)
    

    Потым, верагодна, useEffect, каб адразу зрэагаваць на гэта.

    useEffect(...)
    

    Потым — масіва залежнасцяў, якая перыядзіць значэнняя, якія ён стежыць.

    [firstValue, secondValue]
    

    Потым трэба якась логіка скасавання, каб вярнуць стан у яго пачатковую значэння.

    setSomething(initialValue)
    

    Потым ёсць крайній случай, калі гэты стан павінен застацца супарадны зь змянай навігацыі.

    Потым прыходзіць адказ пра баг, таму што стан не быў очышаны пасля якой-небудзь змены.

    Паштоўку за паштою кодавая база ператвараецца на сэть логікі сінхронізацыі.

    Гэта значна сложней для разумеў, чым компонент, у якога даныя чыста надходзяць з калькі добра апісаных выхадных джэрел.

    Настоямыя навыкі ў React — гэта не керуванне большым колікствам станаў

    Гэта разумеў, дзе кожны элемент стана павінен знаходзіцца.

    Менш апытны разработчык, які розглядае інтерфейс, можа запытацца:

    "Какі стан патрэбен тут?"

    Кращыя архітэктурныя інстынкты прыводзяць да іншага запитання:

    «Калькі з эйха значэнняяў насправды патрабуеюць быць у стане?»

    А найболей агульная версія запытання ў такой:

    «Калькі ёсць законны власнік кожнага элемента стану?»

    Якшы вы будете думаць так, гэта можа змяніць ваш падход да стварэння застосоў на React.

    URL можа карыстацца для стану навігацыі.

    Браўзер можа карыстацца для стану формы.

    Сервер можа карыстацца для дадзэнняяў, якія належаць яму.

    Праўяжаныя значэнняяў могу застацца праўяжанымі, замест таго каб іх дублюваць.

    Union-тыпы у TypeScript могу моделіраваць станы, якія ўзаемна выключальныя.

    А сам React можа сфокусавацца на тымчасовым, інтэрактывным стане, які насправды належы слою UI.

    Такое раздзелэнне задач стварае набліжна чыстейшую архітектуру.

    Падсумак

    Няма большэй колькасці станоў — гэта сама па сабе не ёсць досягненням.

    Большая колькасць зменных стану не робіць прыемнік болей спроможным.

    Зазвычай гэта проста означае большую колькасць работ з сінхронізацыей, большую колькасць залежнасцяў, якія трэба стежыць, і большую колькасць можлівасцей таго, што даныя стануць несупараднімі.

    Перш чым вжываць ўтолькі што аднойчы useState, разгляньце следуючыя пытанні:

    1. Можна лі цэю значэннем расчысляць пад час атрыбутавання? Якщо так, то вырашыце яго замест таго, каб хаваць яго окрема.
    2. Чы трэба, каб яно засталося пасля аднаўлення сторанкі, ці каб яго можна было раздаваць праз лінк? Якщо так, то пакладзіце яго ў параметры пошуку URL.
    3. Чы трэба стежыць за гэтым полам формы пасля кожнага натискання клавішы? Якщо не, то неконтрольваны вхід у поўнасці разам з FormData можа быць дастатнім.
  • Чы розныя булевыя флагі на самай працо описваюць адны асновны жыцёвы цикл? Аб’еднайце іх у адзін значэнне статусу або машыну станаў.
  • Чы гэты стан ствараецца толькі для таго, каб ён быў сінхронізаваны з іншым станам? Як так, то вы, верагатна, зберагаеце тое, што можна было б проста вывести.
  • Чы гэтае значэнне дзейсна мае савой жыцёвы цикл? Як ні, то яго, верагатна, вовсе не трэба лічыць станам.
  • Мэтай ніколі не было мінімізаваць чыстую колькасць вызоў useState.

    Мэтай є пераканацца, што якомога меньшыя значэнні адпаведная за визначэнне правды вашай прыемлі.

    Адтуды, што кожны непатрэбны стан — это ўсё, што ваша прыемлі павінна сінхронізаваць.

    А кожны крок сінхронізацыі, якога вы не пішлі, — это баг, які вам ніколі не трэба будзе лагчыць.