Почему использование только механизма состояний React - плохая идея

Вступление

Теперь, когда у нас есть hooks и Context API, может показаться привлекательным отказаться от библиотеки управления состоянием при запуске нового проекта в React - должны ли мы поддаться искушению? Давайте выясним это, посмотрев на простой пример корзины покупок, начиная с нижней части дерева - представления списка товаров.

Внизу

Компонент потребителя контекста будет выглядеть примерно так, конечно, это довольно простой пример, но он хорошо иллюстрирует основные моменты, представляющие интерес. Он просто отображает список товаров, которые можно добавить или удалить из корзины.

const SimpleItemList = () => {
    // Using the hook to gain access to the context
    const context = useContext(GlobalContext);
    
    return (
        <div>
        {context.items.map((item, index) => {
            return (
                <Item
                    key={index}
                    item={item}
                    onAdd={() => {
                        context.addToCart(item);
                    }}
                    onRemove={() => {
                        context.removeFromCart(item);
                    }}
                />
            );
        })}
        </div>
    );
};

Сначала это кажется хорошим, поскольку для этого не требуется передавать какие-либо реквизиты, а просто напрямую подключаться ко всему, что нам нужно, из контекста.

Однако у этого есть несколько недостатков:

  • повторное использование компонента вредит, потому что оно напрямую связано с контекстом
  • с первого взгляда понять, какие переменные нужны этому компоненту, гораздо сложнее, так как здесь нет реквизита.

Мы могли бы передать контекст как опору для компонента, чтобы сделать его более пригодным для повторного использования.

const context = useContext(props.context);

Если бы мы использовали Typescript, мы могли бы указать, что свойства компонента расширяют какой-то базовый интерфейс, описывающий то, что мы ожидаем от контекста, но я не могу рекомендовать такие действия, поскольку это просто похоже на взлом, вариант существует, если обстоятельства достаточно плохи.

Наверху

Мы определяем состояние нашего приложения вместе с функциями, необходимыми для работы с ним, и используем Context API, чтобы предоставить его другим нашим компонентам.

Чтобы использовать Context API, нам нужно обернуть наше приложение компонентом поставщика контекста, чтобы дочерние элементы, которым он нужен, могли получить доступ к нашему контексту.

function SimpleStateApp() {
    const addToCart = item => {
        setState(prevState => {
            const copy = { ...prevState };
            if (copy.cart[item.id]) {
                copy.cart[item.id].count++;
            } else {
                copy.cart[item.id] = { ...item, count: 1 };
            }
            return {
                ...copy
            };
        });
    };
const removeFromCart = item => {
        setState(prevState => {
            const copy = { ...prevState };
            if (copy.cart[item.id].count === 1) {
                delete copy.cart[item.id];
            } else {
                copy.cart[item.id].count--;
            }
            return {
                ...copy
            };
        });
    };
const [state, setState] = useState({
        addToCart: addToCart,
        removeFromCart: removeFromCart,
        items: [...ITEMS_ARRAY],
        cart: {}
    });
return (
        <GlobalContext.Provider value={state}>
            <div className="App">
                <header className="App-header">
                    <div>This is the Simple barn</div>
                </header>
                <SimpleItemList />
                <SimpleCart />
            </div>
        </GlobalContext.Provider>
    );
}

Работает как часы! Хотя он раскрывает несколько вещей:

  • Это все еще локальное состояние отдельного компонента, тесно связанного с ним.
  • Увеличение сложности бизнес-логики увеличит сложность корневого компонента.

Заключение

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

Вы можете проверить несколько способов управления состоянием для одного и того же приложения в этом репо.