Галоўная / Артыкулы / Спісак перагляду коду на React: стан, які выраховываецца, функцыі Reducer, эфекты і Memo

Спісак перагляду коду на React: стан, які выраховываецца, функцыі Reducer, эфекты і Memo

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

1906 слоў

Разработчыкі, які маюць калькі гадзінакоў дазвялів у React, зазвычай ведаюць, як функціонуюць пропы, як работаюць хукі і чаму стэйт нельга зменяць безпосередна. Аднак пад час перагляду коду ў ўсіх іх запросах пра злучэння продовжваюць выяўляцца тыя ж проблемы: компаненты, якія работаюць, але ўразламныя, важкія для тэставання, занадта складныя або важкія для розшырэння. Чыганьне за межы гэтага этапу не столькі спрыяе вивучэнню рэдкісных API, сколькі паглыбленню розумэння таго, дзе знаходзіцца стэйт, для чаго патрэбны аўтаработы і калі оптымізацыя дае рэзультат. У гэтым чек-лісте аналізуюцца семь распашчаючыхся проблем, на прыкладзе списку задач у TypeScript, і паказваецца патэрн дзеяння для кожнага случая.

1. Стэйт, які трэба вырахаваць

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

Як гэта выглядае

Спісак задач, які трэба адрабатаваць і які вжо адрабатаны, частае выкарыстоўваець два масівы ў стане:

// ❌ BAD: Storing derived state in two separate places
const [todos, setTodos] = useState<Todo[]>([]);
const [completedTodos, setCompletedTodos] = useState<Todo[]>([]);

Тады кожныя змены трэба прымаць два разы. Якщо корыстувач перайменавае задачу, код апцэнтывае todos, але забывае про completedTodos, інтэрфейс паказвае стары заглавле ў аднам списку, а новы — у іншым.

Што пісаць заместа гэтага

Зберагаць адны масіў у стане і вырахоўваць частку задач, якія вжо адрабатаны, пад час атрыбутавання:

// ✅ GOOD: Single source of truth with derived state
const [todos, setTodos] = useState<Todo[]>([]);
// Derived value recalculated on every render (inexpensive computation)
const completedTodos = todos.filter((todo) => todo.completed);

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

2. Ручныя запыты да дадзэнняў у useEffect

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

Як гэта выглядае

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

// ❌ BAD: Boilerplate-heavy, error-prone, no caching or deduplication
useEffect(() => {
  let isMounted = true;
  fetch('/api/todos')
    .then((res) => res.json())
    .then((data) => {
      if (isMounted) setTodos(data);
    });
  return () => { isMounted = false; };
}, []);

У гэтам падходзе все ж є недагоднасці. Няма керавання адзінакоў, няма стану заваносу, няма усунення дублікацый, калі два компоненты запрашваюць тыя ж данні, няма кешавання межа экранамі і няма можлівасці перапрыбутку. Флаг isMounted таксама толькі запобегае пазнейшым апдэйтам; ён не анулюе запит, як гэта робіць AbortController.

Што напісаць замест

Ўзайце бібліятэку, створаную для керавання станам сервера, такую як TanStack Query, SWR або RTK Query. Адэкватны калект TanStack Query выказвае ключ кашы і функцыю запрашоўкі, а пасля прымеяе данні, стан загрузкі і стан памилак:

// ✅ GOOD: Declarative, automatically cached, auto-retried
const { data: todos, isLoading, error } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,
});

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

3. Купка калектаў useState, дзе можа пасадыцца редукер

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

Як гэта выглядае

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

Што напісаць замест

useReducer пераносіць пераходы статусу з компанента ў адну функцыю. Редукер нижэй выкарыстоўвае дыскурмініраную юнію для своіх дзеянняў, таму TypeScript знае тип паданых дадзеных для кожнага случая. Дадаванне задачі дапамогаючы прыкладу адбываецца праз дадаванне новага об’екта з генераваным ідэнтыфікаторам; змяна статусу выкарыстоўвае метод map, каб змяніць статус падходзячага элемента. Заўважыце, што у рэчы змяны статусу є памылка: ...todo.completed трэба заменіць на !todo.completed, тое ўзаемна адрыгванне, інакш код не скомпілюецца.

type Action =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: string };

function todoReducer(state: Todo[], action: Action): Todo[] {
  switch (action.type) {
    case 'ADD_TODO':
      return [...state, { id: crypto.randomUUID(), text: action.payload, completed: false }];
    case 'TOGGLE_TODO':
      return state.map((todo) =>
        todo.id === action.payload ? { ...todo, completed: ...todo.completed } : todo
      );
    default:
      return state;
  }
}

Редукеры маюць прынадныя эфекты двума спосабамі:

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

4. Няма тэстаў, бо «тэставанне — грандыя проект»

Багато команд ставяцца да тэставання як да чагось цалкам або нічога: альбо полная наладка «з канца ў канец», якая здаецца неперадоленай, альбо ўсё цэла нічога. Кращы падход — пачынаецца з тых тэстаў, якія лёгкае напісаць і якія найбольш корыстныя трэба заставіць.

Прыоритэтызавайце ў такой спрабе:

  • Адзінольная бізнес-логіка. Памагальныя прыемкі, форматары, верыфікаторы і інструменты, якія знаходзяцца за межамі дрэва компанентаў.
  • Редактары і спецыяльныя хукі. Чыстая логіка пераходу стану.
  • Критычныя траекторіі викорыстоўвання паўтаральніка. Ключовыя взаімадзеянні, працаваныя з аблікам React Testing Library (@testing-library/react).
  • Тэст редуктара — гэта хорашы першы крок. Ён стварае пачатковы стан, надсылае дзеянне безпосередна через функцію і пераканаецца ў рэзультате, без якога-лібо атрыбутавання:

    // Example: Testing a reducer takes 2 minutes and provides huge peace of mind
    test('should toggle todo status', () => {
      const initialState = [{ id: '1', text: 'Write Article', completed: false }];
      const newState = todoReducer(initialState, { type: 'TOGGLE_TODO', payload: '1' });
    
      expect(newState[0].completed).toBe(true);
    });
    

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

    5. Іспытанне useEffect для сінхронізаціі React з самым сабой

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

    Пастаянна з’яўляюцца два хыбных уявленні:

    • «Стан должен адаптавацца ў эфекте, калі змянюецца проп.» Зазвычай не трэба. Вырахавайце значэнне пад час атрыбутаўвання, але якщо стан дзецінскага элемента павінен запачатковацца занова, задайце яму key, які зменшаецца разам з адпаведным пропам, ўжо каб React перыяктуваў яго.
    • «Паведзенне пад час кліка патрабуе эфекта.» Логіка, якая запускаецца пад час дзейства корыстніка, павінна знаходзіцца ў працоўніку адбування змагання, такім как onClick, а не ў эфекте, які стежыць за змянай стану, вызванай клікам.

    Канонічны прынцып: эфекты ўжоўсьці служаюць спосабам сінхронізацыі з системамі за межамі React, такімі як API прыгледачаў, падпісанні на абнаўленняя, аналітыка чыста трэціх сторон. Данні, якія вже належаць React, можна трансфармаваць без іх вжытку. Для болейшага розгледу з прыкладамі «до» і «пасля» адзірніце stop syncing state with useEffect.

    6. Автаматычна мемаізацыя всаго

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

    Чаму абсалютная мемаізацыя пагубна

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

    Больш рашучы падход

    • Спачатку аналізуйце. Профіляр у React DevTools паказвае, якія компаненты на самай працоўнай часто рэндаруюцца або занадта медлена.
    • Спачатку вылагодзіце структуру, прытымкуючы мемаў. Перанесенне статаусу ў сам компанент, які яго выкарыстоўвае, чы перадача дорогіх паддрэсаў як children часта запобегаюць непатрэбным рэндарам без якой-лібо мемаізацыі.
  • Зберагаеце даныя там, дзе гэта важнае. Застосавайте React.memo да вялікіх спісаў і складных дыяграм, а таксама выкарыстоўвайте useCallback і useMemo, каб стабілізаваць ўсе іх параметры.
  • 7. Аднос да CSS і доступнасці як да чужай задачы

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

    Сферы, якія варта опанаваць:

    • Сучасны макетаванне. Добра разбірайцеся у Flexbox і CSS Grid, каб абсалютная пазіцыяванне больш не была альтернатываў для звычных макетаў.
    • Стылізаванне ў межах компаненту. Зберагаеце стылі разам з компанентамі за дапамой CSS Modules, Tailwind CSS або CSS-in-JS, у замест на вялікія глобальныя шаблоны, дзе імены класаў сталачуцца.
  • Доступнасць і прыемнае викорыстоўванне. Ўжывайце сэмантычныя элементы, такія як справжні <button> заместо <div onClick={...}>. Абавяжыцеся тым, каб навігацыя за дапамою клавіатуры з асабліванням Tab, Enter і Space працавала ў всіх месцах. Адзначайце видныя стылі :focus-visible і чысткія станы для інтэракцый, напрыклад, курсор на элементах, якія можна нажыць.
  • Спіс пераканальнай перагляду

    Пры пераглядзе запрошэнняя пра з’едынэнне ў React шукайце такія замены:

    • Значэнні, якія вырахоўваюцца, храняцца ў дадатковых хукі useState: вырахоўвайце іх пад час атрыбутаўвання.
    • Ручныя вызовы API ўнутры useEffect: выкарыстоўвайце TanStack Query, SWR або RTK Query.
    • Многія вызовы useState для складных масэў і об’ектаў: пераносіце транзыцыі ў useReducer і тэставайце редюсер.
    • Няма тэстаў, таму што настройка здаецца дорогай: пачніце з чыстых функцый, редактараў і спецыяльных хуків.
    • useEffect выкорыстоўваецца для сінхронізаціі стану: вычысціце значэнне або обработаць яго ў хэндлеры змян.
    • useCallback і React.memo ў всіх месцах: аналізуйце перад мемаўваннем.
    • Элементы div, якія можна клікаць, і відсутніе стыляў фокусу: выкорыстоўвайце семантычны HTML і пераканаюцеся ў доступнасці за дапамогою клавіатуры.

    Заключанне

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