Skip to main content

Що робить "selector pattern" у контексті?

Проблема, яку він вирішує

Звичайний контекст працює так:

javascript
const AppContext = createContext({ user, theme, cart }); const value = { user, theme, cart }; // об'єкт змінюється за посиланням <AppContext.Provider value={value}> <Child /> </AppContext.Provider>

Якщо всередині Child ти читаєш тільки theme:

javascript
const { theme } = useContext(AppContext);

то при зміні user або cart theme залишається тим самим, але компонент все одно перерендериться, тому що value змінився за посиланням.

Це фундаментальна поведінка React Context. Навіть якщо змінилася одна частина, ререндеряться всі споживачі.


Selector pattern: як він це вирішує

Ідея: компонент підписується тільки на частину контексту, а не на весь об'єкт value.

Тобто:

"Стеж тільки за theme, і не реагуй на зміни user або cart."


Приклад з бібліотекою use-context-selector

Ця бібліотека реалізує selector pattern для Context API.

1. Створюємо контекст

javascript
import { createContext } from "use-context-selector"; export const AppContext = createContext({ user: { name: "Tim" }, theme: "light", cart: [], });

2. Обгортаємо Provider

javascript
import { AppContext } from "./AppContext"; export function AppProvider({ children }) { const [user, setUser] = useState({ name: "Tim" }); const [theme, setTheme] = useState("light"); const [cart, setCart] = useState([]); const value = { user, theme, cart, setUser, setTheme, setCart }; return <AppContext.Provider value={value}>{children}</AppContext.Provider>; }

3. Використовуємо селектори у споживачах

javascript
import { useContextSelector } from "use-context-selector"; import { AppContext } from "./AppContext"; function ThemeSwitcher() { const theme = useContextSelector(AppContext, ctx => ctx.theme); const setTheme = useContextSelector(AppContext, ctx => ctx.setTheme); console.log("ThemeSwitcher render"); return ( <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}> {theme} </button> ); } function UserName() { const user = useContextSelector(AppContext, ctx => ctx.user); console.log("UserName render"); return <p>Hello, {user.name}</p>; }

Тепер:

  • При зміні theme -> перерендериться тільки ThemeSwitcher;
  • При зміні user -> тільки UserName;
  • Інші споживачі залишаться недоторканими.

Чому це працює

  • Кожен споживач підписується на конкретне підзначення з контексту.
  • use-context-selector під капотом створює "міні-підписку" тільки на вибраний фрагмент value.
  • При оновленні контексту React порівнює старе і нове вибране значення через Object.is - якщо вони рівні, компонент не ререндериться.

Без сторонніх бібліотек (базова реалізація)

Можна реалізувати селекторний підхід вручну - розділивши контексти:

javascript
export const UserContext = createContext(); export const ThemeContext = createContext(); export const CartContext = createContext();

і споживати рівно той, що потрібен.

Це найпростіший аналог "selector pattern", тільки не на рівні значення, а на рівні "контексту як сутності".


Підсумок

ПідхідПоведінка
useContextКомпонент підписаний на все value, ререндериться при будь-якій зміні
use-context-selectorКомпонент підписаний на вибране поле, ререндериться тільки при його зміні
"Багато контекстів"У кожного своя зона відповідальності - теж варіант selector pattern

Selector pattern допомагає:

зменшити ререндери при частих оновленнях контексту підвищити продуктивність великих дерев компонентів писати гнучкі, реактивні контексти без важких сторів (Redux, Zustand)

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.