Що робить "selector pattern" у контексті?
Проблема, яку він вирішує
Звичайний контекст працює так:
const AppContext = createContext({ user, theme, cart });
const value = { user, theme, cart }; // об'єкт змінюється за посиланням
<AppContext.Provider value={value}>
<Child />
</AppContext.Provider>Якщо всередині Child ти читаєш тільки theme:
const { theme } = useContext(AppContext);то при зміні user або cart theme залишається тим самим,
але компонент все одно перерендериться, тому що value змінився за посиланням.
Це фундаментальна поведінка React Context. Навіть якщо змінилася одна частина, ререндеряться всі споживачі.
Selector pattern: як він це вирішує
Ідея: компонент підписується тільки на частину контексту,
а не на весь об'єкт value.
Тобто:
"Стеж тільки за
theme, і не реагуй на зміниuserабоcart."
Приклад з бібліотекою use-context-selector
Ця бібліотека реалізує selector pattern для Context API.
1. Створюємо контекст
import { createContext } from "use-context-selector";
export const AppContext = createContext({
user: { name: "Tim" },
theme: "light",
cart: [],
});2. Обгортаємо Provider
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. Використовуємо селектори у споживачах
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- якщо вони рівні, компонент не ререндериться.
Без сторонніх бібліотек (базова реалізація)
Можна реалізувати селекторний підхід вручну - розділивши контексти:
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)
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.