Сегодняшний паттерн — один из самых популярных современных паттернов реагирования. Вариации этого видны повсюду, но концепция и строительные блоки остаются прежними. Идея состоит в том, чтобы создать простую организованную структуру, состоящую из трех частей: контекста, поставщика и использования. Это создает структурированный подход, упорядочивающий значения контекста и повышающий удобство повторного использования благодаря сходству, созданному между этими частями. Ниже приведен простой пример используемой структуры, которая подходит практически для всех случаев, когда можно использовать этот шаблон, за исключением нескольких исключений.

const value = {
  state: {
    // Here goes values, that are to be considered immutable
    // Immer.js can be used to ensure immutability is obeyed
  },
  actions: {
    // Here goes functions, that manipulate the above values
    // or return values based on the context internals
  },
};

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

import { useState } from 'react';
function TodoProvider({children}) {
  // Start with an empty list
  const [todos, setTodos] = useState([]);

  // When adding a todo, simply append to list:
  const add = useCallback((title) => setTodos(
    (old) => [...old, {title, isDone: false}]
  ), []);

  // Remove a todo by offset in list
  const remove = useCallback((offset) => setTodos(
    (old) => [
      ...old.slice(0, offset,
      ...old.slice(offset + 1),
    ]
  ), []);

  // Mark a todo by offset and isDone flag
  const mark = useCallback((offset, isDone) => setTodos(
    (old) => [
      ...old.slice(0, offset,
      {
        ...old[offset],
        isDone,
      },
      ...old.slice(offset + 1),
    ]
  ), []);

  const value = {
    state: { todos},
    actions: { add, remove, mark },
  };

  return (
    <TodoContext.Provider value={value}>
      {children}
    </TodoContext.Provider>
  )
}

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

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

Вы можете задать вопрос: нужен ли вообще Context? Вы были бы правы, если бы сказали нет. Фактическая переменная, созданная в файле контекста, является внутренней реализацией деталей для ToDoProvider. Вы можете заменить это любой другой подобной технологией, такой как Redux. И, как мы обсуждали ранее, повторная визуализация может быть обработана с помощью мемоизации, при этом используемые компоненты повторно визуализируются только на основе значения Context только при изменении используемых свойств. В целом, это то, что Redux делает за многие годы оптимизации, но часто не обязательно требуется в приложениях меньшего масштаба, поэтому возможность использовать этот шаблон без реализации Redux и подобных контейнеров состояний — это здорово. Потому что он становится отличным строительным блоком, когда приложения масштабируются и требуют более надежного решения.