Пропуск ефекту в React

pic

Якщо ви використовуєте useEffect з рефами, щоб обійти правила масиву залежностей в React, з новим компілятором React, який наближається, ви могли б натрапити на цю знайому помилку:

Значення рефів (властивість current) не можна доступати під час рендеру.

Або, можливо, ви вимкнули правила хуків для цього випадку, і натрапили на таке повідомлення:

Компілятор React пропустив оптимізацію цього компонента, оскільки одне або кілька правил React ESLint були вимкнені. Компілятор React працює тільки тоді, коли ваші компоненти дотримуються всіх правил React, вимкнення їх може призвести до несподіваної або неправильній поведінки react-compiler/react-compiler

💡Ідея: Що, якщо ми могли б виконувати ефект за потребою та одночасно передавати його залежності як аргумент для деструктуризації?

Представляємо usePureEffect

Хук usePureEffect є альтернативою useEffect з чистим зворотним викликом та точною реактивністю, яка поважає правила React без необхідності використання обхідних шляхів, таких як рефи, що оновлюються під час рендеру.

Термін «чистий» у usePureEffect підкреслює відповідність принципам функціонального програмування. На відміну від традиційного useEffect, usePureEffect передає об'єкт залежностей функції як аргумент. Це дозволяє функції ефекту оголошуватися поза межами рендеру компонента без додаткової мемоізації.

Ось реалізація:

import { useEffect, useRef, useState } from "react"  

type Destructor = () => void  
type PureEffect = (dependencies: T) => void | Destructor  
type Trigger = (objA: T, objB: T) => boolean  
type Shape = Record  

function bothAreFunctions(a: unknown, b: unknown): boolean {  
 return typeof a === "function" && typeof b === "function"  
}  

/**  
 * Порівнює два об'єкти залежностей на рівність.  
 *  
 * Порівняння є поверхневим, що означає, що вкладені об'єкти не порівнюються.  
 *  
 * Винятки:  
 * - функції завжди вважаються рівними  
 * - `NaN` вважається рівним `NaN`  
 *  
 * @param objA - Перший об'єкт для порівняння.  
 * @param objB - Другий об'єкт для порівняння.  
 * @returns `true`, якщо об'єкти рівні, `false` в іншому випадку.  
 */  
export function equal(objA: T, objB: T): boolean {  
 if (Object.is(objA, objB)) {  
 return true  
 }  

 if (objA === null || objB === null) {  
 throw new TypeError("Несподіване значення null")  
 }  

 if (typeof objA !== "object" || typeof objB !== "object") {  
 throw new TypeError("Очікуються об'єкти")  
 }  

 const keysA = Object.keys(objA) as Array  
 const keysB = Object.keys(objB) as Array  

 if (keysA.length !== keysB.length) {  
 return false  
 }  

 for (let index = 0; index < keysA.length; index++) {  
 const valueA = objA[keysA[index]]  
 const valueB = objB[keysA[index]]  

 if (!(keysA[index] in objB)) {  
 return false  
 }  

 if (bothAreFunctions(valueA, valueB)) {  
 continue  
 }  

 if (!Object.is(valueA, valueB)) {  
 return false  
 }  
 }  

 return true  
}  

/**  
 * usePureEffect — це кастомний хук, який виконує чистий ефект  
 * на основі наданих залежностей.  
 *  
 * Якщо наданий третій аргумент, ефект буде виконуватися  
 * тільки коли залежності зміняться. Залежності порівнюються поверхнево,  
 * значення функцій завжди ігноруються.  
 *  
 * @param effect - Функція ефекту, яку потрібно виконати при зміні залежностей.  
 * @param dependencies - Об'єкт, що містить залежності для ефекту.  
 * @param when - Опційна функція тригера, яка визначає, коли запускати ефект.

*/  
export function usePureEffect(  
 effect: PureEffect,  
 dependencies: T,  
 when?: Trigger  
): void {  
 const [state, setState] = useState({ dependencies, effect })  

 if (  
 (!when || when(state.dependencies, dependencies))  
 && !equal(state.dependencies, dependencies)  
 ) {  
 setState({ dependencies, effect })  
 }  

 useEffect(() => state.effect(state.dependencies), [state])  
}

І найкраща частина полягає в тому, що React Compiler може оптимізувати цей хук без проблем.

Як працює usePureEffect

  1. Передбачуване відстеження залежностей: Функція equal гарантує точні порівняння, ігноруючи функції.

  2. Користувацькі тригери з аргументом when: Опційна функція when дозволяє точно контролювати, коли виконувати ефект, без використання ненадійних обхідних шляхів — ефект виконується лише тоді, коли умова when також виконана.
    3.
    Впровадження залежностей як аргумент: Об’єкт передається безпосередньо у функцію ефекту, що робить її чистою.

Цей підхід усуває необхідність мемоізації зворотних викликів (callbacks) або відстеження контексту JavaScript у масиві залежностей.

Практичний випадок використання

Припустимо, вам потрібно виконувати ефект щоразу, коли змінюються певні властивості, але ви хочете ігнорувати зміни інших:

import { usePureEffect } from '@/hooks/usePureEffect';  

interface AppProps {  
 propA: string;  
 propB: number;  
}  

// Зворотний виклик ефекту чистий та стабільний за посиланням:  
function callback(dependencies: AppProps) {  
 console.log("Ефект запущено з:", dependencies);  
}  

// Точний контроль над тим, чи має виконуватись ефект:  
function whenPropAChanges(prev: AppProps, next: AppProps) {  
 return prev.propA !== next.propA;  
}  

const App: React.FC = ({ propA, propB }) => {  
 usePureEffect(callback, { propA, propB }, whenPropAChanges);  

 return 
...
;   };      export default App; ```  > Ефект виконується лише тоді, коли змінюється propA, навіть якщо оновлюється propB.  _Спробуйте usePureEffect у вашому наступному проекті. Це чистий, передбачуваний і комбінований спосіб керування побічними ефектами. Дайте знати, як це працює для вас!_  Насолоджуйтесь!



Перекладено з: [Skipping the effect in React](https://martinadamko.medium.com/skipping-the-effect-in-react-4c9c5038d64c)

Leave a Reply

Your email address will not be published. Required fields are marked *