Галоўная / Артыкулы / З адынктуўных скрыптаў DOM да UI як функцыі стану: асновная модэль React

З адынктуўных скрыптаў DOM да UI як функцыі стану: асновная модэль React

Чаму React заменяе ручныя апдэты DOM на декларатыўныя компаненты, і як JSX, props, state, перарэндарыўванне і односмерны поток дадзэнняяў упаўнаважваюцься ў адной канцэптуальной модэлі.

4868 слоў

Кнапку «Пасулька», яка адаптаваець лічылку, зменшвае айконку і практыкуе анімазыю, лёгка ўтворыць за дапамою звычных вызоваў DOM. Але калі такіх кнапак 50 у рэальным стріме, пры чым кожная з іх таксама стежыць за коментарамі, падзеленнямі і зберажаным станом, тады рукапісны код DOM пачынае не выдараць. React створаны, каб усунуць такія проблемы: вы описваеце, як павінен выглядаць экран на адповедныя даны, а React сам выбірае, якія змены DOM неабходны.

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

Проблема, якую рашыў React

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

// Traditional DOM manipulation
const button = document.getElementById("like-button");
const countEl = document.getElementById("like-count");
const icon = document.getElementById("like-icon");
let liked = false;
let count = 42;

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

button.addEventListener("click", function() {
  if (!liked) {
    liked = true;
    count++;
    countEl.textContent = count;
    button.classList.add("liked");
    button.classList.remove("unliked");
    icon.src = "/icons/heart-filled.svg";
    button.style.color = "#e0245e";
    // trigger animation
    button.classList.add("animate-pop");
    setTimeout(() => button.classList.remove("animate-pop"), 300);
  } else {
    liked = false;
    count--;
    countEl.textContent = count;
    button.classList.remove("liked");
    button.classList.add("unliked");
    icon.src = "/icons/heart-empty.svg";
    button.style.color = "#6c757d";
  }
});

Это было прыемна для аднаго кантакта. А тепер уявіце стрэлку з 50 пастак, кожная з якіх мае своия элементы для паспадзення, каментавання, аддачы у сябе і зберагання, пры чым усе гэта можа змяніцца пад вплывам клікаў корыстувальніка або пад час прыходу новых дадзеных з сервера. У такім разе скрыпт ператвараецца на сэть пошуку элементаў, прыемнікаў і зменных, якія трэба ў ручны спосаб зберагаць у суворай адпаведнасці. Калі прыложэнне расте, з’яўляюцца тры можлівыя проблемы:

  • Сінхронізацыя. DOM паказвае аднае, а вашыя зменныя — іншае. Вы апдэюўаете элемент лічыльніка, але забываеце пры этым зменную, або наадворс, і тады трэба дыягноставаць два разныя источнікі правды.
  • Можлівасць паўтарнага выкарыстання. Функцыя обработкі прычынена конкрэтным ID элементаў і адной-ўсёй структуры HTML, таму перыявленне кнапкі на іншую сторонку значыць яе перапісванне.
  • Складнасць. Кожная новая функцыя змушае вас разумець усё інше, чым яна можа зачапіцца. Незначныя змены пашкоджают далёкія часткі сторонкі.

React, які Facebook адкрыў пад працэсам коду 2013 года, даў адказ на всі тры прыбліжненняя за дапамою адной ідеі: перастаць выдаляць каманды DOM і замест таго апісваць UI, якія павінны існаваць для заданага стану. React пароўнае гэтыя апісанні з тым, што ўісвечваецца на экране, і прыменяе разлік. Вы апісваеце; React апдэюўае.

JSX: маркап, яны перакладваюцца ў JavaScript

Перша нечыганая дзеянне ў коде React — это маркап, який выглядае як HTML, усередзіньне функцыі:

function Greeting() {
  return (
    <div className="greeting">
      <h1>Hello, Priya!</h1>
      <p>Welcome back.</p>
    </div>
  );
}

Этот маркап — це JSX, расширэнне сынтаксісу, якое дазволяе пісаць дрэвы элементаў у файлах JavaScript. Ён не ёсць ні HTML, ні чым-небудзь, што розумее браузер. На этапе компіляцыі (Babel, кампайлер TypeScript або інструмент для збірання коду, які ўжоўае іх) ён перапісываецца у звычныя вызовы функцый прытаманна коду запускаецца. Вы пішаце:

// What you write (JSX):
return (
  <h1 className="title">Hello!</h1>
);

а кампайлер стварае ўсё тое ж самае, але у іншым формате:

// What the compiler transforms it into:
return React.createElement("h1", { className: "title" }, "Hello!");

React.createElement проста ўтварае звычайны об’ект, які описвае элемент: його тип, атрыбуты і дзеця. React чытае гэтыя об’екты, каб вырашыць, што саме павінен мястіць рэальны DOM. (У новышых версіях JSX для перетварэнняў викорыстоўваецца іншы функцыйны элемент з react/jsx-runtime, але рэзультат — той самы тип об’екта-опісу.) Ніхто не пішаць гэтыя вызывы вручную; JSX існуе таму, што вялікія структуры маркапаў лёгчэ за чытанне.

У чым JSX разлічаецца ад HTML

Сынтаксіс близкі да HTML, але не ідэнтычны. Найбольш пастойныя разлікі:

// HTML                              JSX
// ─────────────────────────────     ──────────────────────────────────
// class="title"                    className="title"  ← JS reserved word
// for="email"                      htmlFor="email"    ← JS reserved word
// <input> (self-closing optional)  <input />          ← must self-close
// onclick="handler()"              onClick={handler}  ← camelCase, no quotes
// style="color: red"               style={{ color: "red" }}  ← JS object

class і for ў JavaScript являюцься зарезерваванымі словамі, таму JSX выкарыстоўвае className і htmlFor. Кожны элемент павінен быць закрыты. Адпаведальнікі за здарэння пішуцца у формате camelCase і прыймаюць функцію, а не строку. Внутрашні стылі ў JSX являюцься об’ектамі. Чырвонейшае розумеенне перадчуткаў, якія стояць за гэтым синтаксам, можна знайсці у чаму JSX не ўзьме на сябе ролю HTML.

Уключэнне выразаў JavaScript

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

function UserCard({ user }) {
  const isOnline = user.lastSeen < Date.now() - 5 * 60 * 1000;

а пасля калькуюцься выразы, якія формуюць маркап: скрашаная біяграфія, умовны назва класу, умовны пазначкі і сформатаваная дата:

  return (
    <div className="card">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.bio.length > 100 ? user.bio.slice(0, 100) + "..." : user.bio}</p>
      <span className={isOnline ? "badge-green" : "badge-grey"}>
        {isOnline ? "Online" : "Offline"}
      </span>
      <p>Joined: {new Date(user.joinedAt).toLocaleDateString()}</p>
    </div>
  );
}

Правілы простыя: кранцы выкорыстоўваюцца для абрамлення выразаў, а не запаведзей. Можна вжываць тэрнарны выраз, але не блок if, і метод .map(), але не цыкл for, прычаму ў самай структуре маркапаў.

Компаненты: функцыі, якія вяртаюць UI

Компанент — это перадаўальны, самастойны элемент UI, напісаны як функцыя JavaScript, якая вяртае JSX. Гэта і ўсё паўнай прыметыкі:

// A component is just a function that returns JSX
function Button() {
  return (
    <button className="btn">
      Click me
    </button>
  );
}

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

function App() {
  return (
    <div>
      <Button />
      <Button />
      <Button />
    </div>
  );
}

Гэта атрыбутуе тры ідэнтычныя кнопкі на адной-едзінай дыферэнцыяцыі.

Складанне малых компанентаў у большыя

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

function Avatar({ src, alt }) {
  return <img className="avatar" src={src} alt={alt} />;
}

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

function UserName({ name, handle }) {
  return (
    <div>
      <strong>{name}</strong>
      <span>@{handle}</span>
    </div>
  );
}function UserInfo({ user }) {
  return (
    <div className="user-info">
      <Avatar src={user.avatar} alt={user.name} />
      <UserName name={user.name} handle={user.handle} />
    </div>
  );
}function PostCard({ post, author }) {
  return (
    <article className="post-card">
      <UserInfo user={author} />
      <p>{post.content}</p>
      <span>{post.likes} likes</span>
    </article>
  );
}

Кожны слой мае адну задачу. Avatar атрыбутуе картынку, UserInfo размешчае аватара падле імені, PostCard структуруе цэлую пастку. Гэта та прычына, за якой React заохочвае: дзеліць інтэрфейс на самыя маленькія, здаровыя часткі, надаць кожнай чыстую адпаведальнасць і складаць іх разам. нарадчык па стварэнню відновімых компанентаў React дэтальней распаводзіцься пра дыяграматыку компанентаў.

Чаму імены компанентаў пішучыся з великага літеры

React разлічае натыўныя элементы і компаненты па першай літере. <button> становіцца DOM-элементам button; <Button> змушвае React шукати ў межах дыапазону зменную пад назвай Button і вызваць яе. Компанент, названный малайшой літерай, тыхо спрацьоввае як невядомы HTML-тэг, што часта стае прычыной плутання типу „мой компанент нічога не атрыбутуе“.

Props: налашчэнне компанента зза межаў

Кнопка, якая завжды пішае „Нажміце мяне“, не ўсё така корыстная. Props (скрацанне ад properties) — це спосаб, яким родны компанент перадае даныя дзецінскаму компаненту, тады адна вялічына можа паслужыць для багато ситуацый.

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

// Parent passes data as props (looks like HTML attributes)
function App() {
  return (
    <div>
      <Button label="Submit" colour="blue" />
      <Button label="Cancel" colour="grey" />
      <Button label="Delete" colour="red" />
    </div>
  );
}

і дзеціны элементы выкарыстоўваюць тое, што ў іх патрэбна:

// Child receives them as an object
function Button({ label, colour }) {
  return (
    <button className={`btn btn-${colour}`}>
      {label}
    </button>
  );
}

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

Праблемы можу несці будзь-якія значэння

Строкі — толькі пачатак. Чысла, булевыя значэння, масівы, об’екты і функцыі — усе гэта ўзятыя праблемы. Напрыклад, карточка продукту можа мець об’ект дадзеных, функцыю-выклік і флаг:

function ProductCard({ product, onAddToCart, featured }) {
  return (
    <div className={`card ${featured ? "card-featured" : ""}`}>
      <img src={product.image} alt={product.name} />
      <h3>{product.name}</h3>
      <p>₹{product.price.toLocaleString()}</p>
      <p>{product.rating} ★ ({product.reviewCount} reviews)</p>
      <button onClick={onAddToCart}>
        Add to Cart
      </button>
    </div>
  );
}

У месцы выкліку гэтыя значэння перадаюцца за дапамою складакоў:

// Usage:
<ProductCard
  product={{ name: "Headphones", price: 2499, rating: 4.3, reviewCount: 128 }}
  onAddToCart={() => addToCart(product.id)}
  featured={true}
/>

Заўважкі: внутрашня функцыя-адаптавальнік onAddToCart аб’являеся ў відношэні до product.id, але product тут ёсць лишэнь об’ект, прынятый як атрыбут, а не зменная ў межах тэрыторіі. У рэальнам коде трэба было бы викорыстоўваць зменную, заданую ў родным компоненте, напрыклад onAddToCart={() => addToCart(item.id)}. Перадача функцый як атрыбутаў ёсць стандартным спосабам для дзецячых компонентаў аб’являць падзеі, што знова будзе практыкувана далей.

Атрыбуты ўстановленыя

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

// WRONG — never modify props
function Button({ count }) {
  count = count + 1; // ← this is a mistake
  return <button>{count}</button>;
}

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

// CORRECT — props are read-only, use state for data that changes
function Button({ initialCount }) {
  const [count, setCount] = useState(initialCount);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}

Тут initialCount толькі запускае стан; пасля першага атрыбутавання компонент сам керуе своім лічыльнікам.

Стан: саўстанні памяці компонента

Праблемы прыходзяць з занаветку. Стан — это данні, якія належаць компоненту і якія ён можа змяцца з часам. Калі стан зменяецца, React зноў атрыбутуе компонент новай значэннем, і вам ніколі не трэба самостоятельна прымацца да DOM.

Стан ствараецца за дапамою хука useState, імпортаванага з React:

import { useState } from "react";

Лічыльнік паказвае базовую структуру:

function Counter() {
  const [count, setCount] = useState(0);
  //     ↑ current value   ↑ function to update it    ↑ initial value  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

useState(0) вяртае пару: текущую значэнне (count, якое пачаткуецца з 0) і функцыю для яе змены (setCount). Выклік функцыі змены з новым значэннем паведамляе React аб стоўкі ўявлення і перыскаладзе компонент.

Перабудова кнопкі «Лайка» з викорыстаннем стата

Імператывная кнопка «Лайка» з самага пачатку становіцца значна меньшай. Пачнім з імпорту:

import { useState } from "react";

а пасля апісам кнопку для будзь-яй комбінацыі liked і count:

function LikeButton({ initialLikes }) {
  const [liked, setLiked] = useState(false);
  const [count, setCount] = useState(initialLikes);  function handleClick() {
    if (liked) {
      setLiked(false);
      setCount(c => c - 1);
    } else {
      setLiked(true);
      setCount(c => c + 1);
    }
  }  return (
    <button
      onClick={handleClick}
      className={liked ? "btn-liked" : "btn-default"}
    >
      {liked ? "♥" : "♡"} {count}
    </button>
  );
}

Няма пошуку элементаў, няма вызоў classList і няма прызначэнняе textContent. Функцыя-обрабнік апдэйтуе два значэнні стата, а JSX паказвае, як выглядае кнопка для гэтых значэнняў. React прыменяе змены да DOM, каб ён паспаўляўся.

Зверніце ўвагу на форму для апдэйту setCount(c => c - 1). Аддача функцыі заместа значэння дазволяе отрымаць найсвежы стан, што ўбезпечвае большую надзею, калі калькюляцыі апдэйту запланаваны ў однам заходзе.

Кожны экземпляр зберагае свой стан

Стан належыць конкрэтнам экземпляру компонента, а не самой дыферэнціяванню компонента. Адобразіце тры такія кнопкі, і кожна з яных будзе маць свой liked і count; кліканне на одну не паўтарыцца для іншых:

function Feed() {
  return (
    <div>
      <LikeButton initialLikes={24} />   {/* has its own state */}
      <LikeButton initialLikes={7} />    {/* has its own state */}
      <LikeButton initialLikes={156} />  {/* has its own state */}
    </div>
  );
}

Што належыць у стане

Вжывайце стан для значэнняў, якія:

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

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

Як функціонуе перзначанне відображэння

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

Пад час першага атрыбутавання лічылка стварае свой маркап, а React формуе вузлы DOM:

Initial render:
  count = 0
  Component runs → returns <p>Count: 0</p> <button>Increment</button>
  React creates DOM nodes

Калі корыстнік нажмае, функцыя-зменшывач запускае новае атрыбутаванне, і React поручвае два описы:

User clicks Increment:
  setCount(1) called
  React re-renders the component
  count = 1
  Component runs again → returns <p>Count: 1</p> <button>Increment</button>
  React compares: <p>Count: 0</p> vs <p>Count: 1</p>
  React updates only the text node inside <p>
  Button is unchanged — React leaves it alone

У рэальным DOM-структуре зменяецца толькі текст унутрэй абзаца. Кнопка застаецца некінульной. React не перзбудоввае сторанку; ён вырахоўвае мінімальны набор зменаў і прымае толькі іх, таму частыя перзрэндары зазвычай не створаюць проблем.

Што вызывае перзрэндар компонента

Компонент перзрэндаруецца знову, калі зменяецца яго састанак:

1. The component's own state changes (setCount, setUser, etc.)
         ↓
   Component re-renders

Ён таксама перзрэндаруецца ў калькі іншых ситуацыях:

2. The component's props change (parent passes different values)
         ↓
   Component re-renders3. A context the component uses changes
         ↓
   Component re-renders4. The parent re-renders
         ↓
   All children re-render (unless memoized)

У практыцы „змяніліся props“ і „перзрэндарыўся роднык“ — гэта адно і тое ж здарэнне, якое бачыцца з двух сторон: дзецінскі компонент отрымае новыя props толькі тады, калі яго роднык перзрэндаруецца знову. Практычны наследкі — за замовчаннем перзрэндар родныка распрастраняецца на всіх яго дзецінскіх компонентаў, незалежна ад таго, чыя props змініліся чы ні, якщо толькі яны не ўвімкнутыя у режым мемаізацыі.

Такі каскадныя прытворанні рэдкая час з’яўляюцца проблемай. Прытворанне — гэта проста вызов функцыі, якая стварае об’екты; дорогі ў часе ёсць аднос з рэальным DOM, які React стараецца мінімізаваць. Большасць рэальных проблем з выдатнасцю вынікае з таго, як структуруюцца компоненты і стан, а не з простага колькіснага показніка прытворанняў.

Вяртуальны DOM і супрацоўка змян

React падтрымвае лёгкую JavaScript-рэпрэзентацыю інтерфейсу, яую часта называюць вяртуальным DOM. Кожна прытворанне стварае новую дрэва об’ектаў элементаў, і React порівнюе яе з пярэднім дрэвам у процесе, які называецца супрацоўка змян. Рэзультатам гэтага порівняння є спіс рэальных операцый над DOM, якія трэба выкарыстоўваць.

Вы ніколи не працюеце безпосередня з гэтым шаром; це ўскладнення рэалізацыі. Гэта мае значэнне таму, што пораўнянне звычайных об’ектаў у памяці є дашчавым, тады як рэальныя операцыі DOM є столькі ж слабейшымі. Перадача кожнай змены через такое пораўнянне дазволяе React агрупаваць і мінімізаваць дашчовую роботу.

Декларатыўны протыва імператыўнага UI

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

Імператыўны: пераказ стэпаў

У імператыўнай версіі спачатку апошчацца контейнер:

// Imperative: manual DOM manipulation
const list = document.getElementById("list");
list.innerHTML = "";  // clear it

потым ручна ствараецца, налаштовуецца і дадаецца кожны элемент:

items.forEach(item => {
  const li = document.createElement("li");
  li.textContent = item.name;
  li.className = item.active ? "active" : "";
  li.addEventListener("click", () => handleClick(item.id));
  list.appendChild(li);
});

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

Декларатыўны: апісанне рэзультата

Декларатыўны варіянт паказвае, як должны выглядаць элементы для будзь-якага масіву items:

// Declarative: describe the desired output
function ItemList({ items, onItemClick }) {
  return (
    <ul>
      {items.map(item => (
        <li
          key={item.id}
          className={item.active ? "active" : ""}
          onClick={() => onItemClick(item.id)}
        >
          {item.name}
        </li>
      ))}
    </ul>
  );
}

Няма такога падходу, калі спачатку «очышваецца список, а потым ён перзначаецца». Вы проста описваеце бажаны стан, а React сам вырашае, як дасягнуць гэтаго стану з нынешняй структуры DOM. Атрыбут key паведамляе React, які элемент ёсць калі-небудзь між перрадараў, тады ён можа пераместіць, апдэйтаваць чы скасаваць толькі правыя элементы, а не ствараць іх усе занова.

Чаму декларатыўны стыль падходзіць для інтерфейсаў

  • Прыведамасць. З тымі ж атрыбутамі і станом компонент выдае той самы рэзультат. Інтерфейс можна спрыяжаць з чыстай функцыяю дадзеных: UI = f(state, props).
  • Менша колькасць інформацыі, якую трэба памятаць. Не трэба стежыць за тым, як выглядае DOM зараз чы якія змены ёму трэба. Дастатнька толькі описація нынешняго стану.
  • Менша колькасць багоў. Большая частка багоў, які выступаюць у форме ручных змян структуры DOM, такіх як апдэйт адного элемента без апдэйту суседньага чы ўзгоджэння слухачаў з данымі, проста не можа выйсці, калі весь відобразлівы фрагмент прадукту генеруецца на адной засадзе стану.
  • Дрэва компонентаў і однонаправлены поток даных

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

    App
    ├── Navbar
    │   ├── Logo
    │   ├── NavLinks
    │   └── UserMenu
    │       ├── Avatar
    │       └── DropdownMenu
    ├── Dashboard
    │   ├── Sidebar
    │   │   ├── SidebarLink (×5)
    │   │   └── UserStats
    │   └── MainContent
    │       ├── StatsRow
    │       │   ├── StatCard (×4)
    │       └── RecentActivity
    │           ├── ActivityItem (×10)
    └── Footer
    

    Даныя трапляюцца зверху вярху

    Даныя пераходзяць з абяцаючага компонента на дзецячы чераз атрыбуты props. Компонент не можа з’ясаваць стан свайго брата-компонента чы абяцаючага компонента. Саме гэты однонаправлены поток даных дапамагае заставіць вялікія аплікацыі застаўацца зрозумелымі:

    App (has user, notifications, theme)
      │
      ├── Navbar (receives: user, notifications)
      │     │
      │     └── UserMenu (receives: user)
      │           │
      │           └── Avatar (receives: user.avatar, user.name)
      │
      └── Dashboard (receives: user, theme)
            │
            └── MainContent (receives: theme)
    

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

    Прыбыткі супакоўваюцься роўнаў

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

    // Parent owns the state and passes down both the value and the updater
    function App() {
      const [searchQuery, setSearchQuery] = useState("");
    

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

      return (
        <div>
          <SearchBar
            query={searchQuery}
            onChange={setSearchQuery}   {/* passes the setter as a prop */}
          />
          <Results query={searchQuery} />
        </div>
      );
    }// Child receives the updater and calls it on user input
    function SearchBar({ query, onChange }) {
      return (
        <input
          value={query}
          onChange={e => onChange(e.target.value)}  {/* calls parent's setter */}
          placeholder="Search..."
        />
      );
    }
    

    Запит існуе толькі ў App. SearchBar нічога не зберагачыць; ён фіксуе кожны натиск клавішы через атрыбут onChange. Results отрымае апошню версію запиту як атрыбут і перыядруеўае элемент. Даныя прабіваюць вакол у вазе атрыбутаў, а запускі эвэнтаў — у вазе вызоваў функцый. (Адгукі, якія знаходзяцца ўнутры апускаючых тагоў, прызначаны толькі для чытання; у рэальным JSX коментары пішуцца ўнутры элементаў, а ўнутры тагоў вы можаце использоваць звычайныя /* */ коментары або ўзагалі іх не пісаць.)

    Памылкі, якія спакошваюць апдэйты у React

    Мутацыя стану на месцы

    Є спакуса безпасабліва зменіць об’ект стану і перадаць яго знову:

    // WRONG — mutating state directly
    const [user, setUser] = useState({ name: "Priya", age: 28 });
    

    Першы прыклад birthday зменяе об’ект і перадае функцыю-зменнік тую ж самую рэферэнцю, таму інтэрфейс не апдэйтуецца. У другім прыкладзе выкарыстоўваецца оператор распашчання, які стварае новы об’ект, і React спрыймае гэта як змяну:

    function birthday() {
      user.age = 29;        // ← directly modifying the object
      setUser(user);        // ← same object reference — React sees no change
    }                       //   UI does NOT update// CORRECT — create a new object
    function birthday() {
      setUser({ ...user, age: user.age + 1 }); // ← new object, React detects change
    }
    

    React выявляе, чыя змінілася стан, паўпараўнуючы рэферэнсы за дапамогай Object.is, а не пераглядаючы ўжоўтварэнныя данні. Якщо зменіць об’ект чы супакет на месцы і вернуць той самы рэферэнс, React будзе судзіць, што нічога не зменілася, і можа прыпусціць процес атрыбутавання.

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

    // WRONG — mutating the array
    const [items, setItems] = useState([1, 2, 3]);
    items.push(4);       // ← modifies in place
    setItems(items);     // ← same reference — no re-render
    

    Аднак копіюванне ў новы супакет працуе:

    // CORRECT — create a new array
    setItems([...items, 4]);  // ← spread creates a new array
    

    Памешанне пропаў і стану

    Просты тэст дапамагае з’ясавіць, што ў чым. Якшо значэнне прадаўаеся з верхнім компонентам, яго можна лячыць пропам і ён є толькі для чытання. Якшо компонент сам стварае і зміняе гэта значэнне, яго лячыць станам. Якшо значэнне, якое ніколі не зміняецца, пакладаецца ў useState, гэта сігнал памешання:

    // Wrong: using state for something that should be a prop
    function UserCard() {
      const [userName] = useState("Priya"); // ← why is this state? It never changes here
      return <p>{userName}</p>;
    }
    

    Якшо верхній компонент керуе данымі, іх трэба прыймаць як проп.

    // Correct: static data the parent controls comes as a prop
    function UserCard({ name }) {
      return <p>{name}</p>;
    }
    

    Зберагчыце значэнні, якія можна вылічыць

    Не кожна зміна патрабуе свой стан. Зберагчы лік ужо праз самы спіс, мы дублюема інфармацыю:

    // Wrong: storing derived data in state
    const [items, setItems] = useState([...]);
    const [itemCount, setItemCount] = useState(0); // ← why is this state?
    

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

    // Every time items changes, you have to remember to also update itemCount
    // And if you forget once, they're out of sync// Correct: derive it during render
    const [items, setItems] = useState([...]);
    const itemCount = items.length; // ← just a variable — always in sync
    

    Компаненты, якія робяць усё

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

    // Hard to maintain — does everything
    function UserDashboard() {
      // manages user state
      // fetches orders
      // handles filters
      // manages pagination
      // controls modal open/close
      // renders sidebar
      // renders order table
      // renders filter controls
      // renders pagination
      // renders modal
      return ( /* 200 lines of JSX */ );
    }
    

    Раздзелэнне яе па адпаведальнасцях дае кожнай часткі імя і заведамасць:

    // Better — clear single responsibilities
    function UserDashboard() {
      return (
        <DashboardLayout>
          <OrderFilters />
          <OrderTable />
          <OrderPagination />
          <OrderDetailModal />
        </DashboardLayout>
      );
    }
    

    Проектаванне прыстрыю з выкарыстаннем компанентаў

    Накрэсліце межы перад напісаннем коду

    Пачатакніце з дыяграмы дизайна і пазначыце её элементы. Кожны элемент, які павтараецца, ўважаецца складнай часткай. Кожны элемент, які выканае адну чыстую функцыю, таксама ўважаецца складнай часткай. Элементы, якія зменяюцца разам, належаць адно да другога; элементы, якія зменяюцца незалежна, трэба раздзеліць. Для сторонкі супакавання продуктов дыяграма дизайна:

    Looking at the design:
      [ Filter Bar                          ]
      [ Product Card ][ Product Card ][ Product Card ]
      [ Product Card ][ Product Card ][ Product Card ]
      [ Pagination                          ]
    

    разбіваецца на такую структуру:

    Components:
      ProductPage
      ├── FilterBar
      │   ├── FilterGroup (×3)
      │   └── SortDropdown
      ├── ProductGrid
      │   └── ProductCard (×N)
      └── Pagination
    

    Разместіце стан у найнижэшым спакойным абяцеўцы

    Для кожнага элемента стану знайдзіце найнижэю складную частку ў структуре, якой ён патрэбен, і заставіце стан там:

    If only ProductCard needs the "expanded" state → put it in ProductCard
    If FilterBar and ProductGrid both need filters → put filters in ProductPage
    If Pagination and ProductGrid both need currentPage → put it in ProductPage
    

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

    Спачатку створыце статычную версію, а потым дадзіце стан

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

    // Step 1: static — no state, hardcoded data
    function ProductCard() {
      return (
        <div className="card">
          <img src="/headphones.jpg" alt="Headphones" />
          <h3>Wireless Headphones</h3>
          <p>₹2,499</p>
          <button>Add to Cart</button>
        </div>
      );
    }
    

    Другі крок заменяе жорсткая заданы контэнт на проп product, а трэцій крок дадае невялікі фрагмент стану для паведамлення пра "Дадзена", якое сама сабе скасоўвае пасля двух секунд:

    // Step 2: accept props — still no state
    function ProductCard({ product }) {
      return (
        <div className="card">
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p>₹{product.price.toLocaleString()}</p>
          <button>Add to Cart</button>
        </div>
      );
    }// Step 3: add state for interactivity
    function ProductCard({ product, onAddToCart }) {
      const [added, setAdded] = useState(false);  function handleAdd() {
        setAdded(true);
        onAddToCart(product.id);
        setTimeout(() => setAdded(false), 2000);
      }  return (
        <div className="card">
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p>₹{product.price.toLocaleString()}</p>
          <button onClick={handleAdd} disabled={added}>
            {added ? "Added ✓" : "Add to Cart"}
          </button>
        </div>
      );
    }
    

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

    Ключовыя выводы

    Усё модэлі, стиснутыя. Чаму існуе React:

    Why React:
      Plain JS DOM manipulation is hard to scale and maintain
      React: describe the UI, let React handle DOM updates
    

    і основы кожнай з вышэй практыкуемых концэпцый, ад JSX да частых памылак:

    JSX:
      HTML-like syntax in JavaScript — compiled to React.createElement()
      {} embeds any JavaScript expression
      Use className (not class), onClick (not onclick)Components:
      Functions that return JSX
      Capital letter names (Button, not button)
      Reusable, composable building blocksProps:
      Data passed from parent to child (like function arguments)
      Read-only — a component never modifies its own props
      Can be strings, numbers, objects, arrays, functionsState:
      const [value, setValue] = useState(initialValue)
      Data owned by a component that changes over time
      Calling the setter triggers a re-render
      Each component instance has its own stateRe-rendering:
      Happens when state changes, props change, or parent re-renders
      React diffs the virtual DOM and updates only what changed
      Not expensive — surgical DOM updatesDeclarative:
      Describe what the UI should look like
      React figures out what changed and how to update the DOM
      UI = f(state, props) — predictable, testableData flow:
      Props flow down (parent → child)
      Events flow up (child calls parent's function)
      One-way data flow keeps the application predictableCommon mistakes:
      Mutating state directly (use spread / new objects)
      Storing derived values in state (compute them during render)
      Overloading one component (split by responsibility)
    
    • Компаненты — это функцыяў, пропсы — іх аргументы, а стэйт — іх памяць. UI завжды ўособлівае текущы стэйт.
    • Перзрэндароўванне є простым за прыродой; робота з DOM, яку эканоміць React, — гэта дорогі ўчастак. Ёшчы до таго, як перажывацца па колькасці рэндароўвання, правільна структуруйце стэйт.
    • Заўжды давайце сэтарам новыя об’екты і масівы; React паўставляе рэферэнсы, а не ўмест наявнасці.
    • Зберагаюце стэйт у тым компаненте, які яго патрэбуе, выводзіце все іншае пад час рэндароўвання, і дазвольце з’явамся падацца праз калбэк пропсы.

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

    Спаднія матэрыялы

  • Redux Toolkit ад First Slice да Async Data: Рабочыя ментальныя моделі — Дазвольце вы дазнаецеся, як складаюцца між сабой хранэнне дадзеных, фрагменты, редукеры на адварце Immer, селектары і createAsyncThunk у Redux Toolkit, на прыкладзе лічыльніка, кошыка пакупак і спісу продуктав.