{"id":13433223,"url":"https://github.com/jamiebuilds/unstated-next","last_synced_at":"2025-05-14T23:07:24.767Z","repository":{"id":38451803,"uuid":"184500850","full_name":"jamiebuilds/unstated-next","owner":"jamiebuilds","description":"200 bytes to never think about React state management libraries ever again","archived":false,"fork":false,"pushed_at":"2022-02-10T13:23:27.000Z","size":888,"stargazers_count":4188,"open_issues_count":53,"forks_count":143,"subscribers_count":31,"default_branch":"master","last_synced_at":"2025-04-13T19:50:05.222Z","etag":null,"topics":["library","react","redux","state-management"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamiebuilds.png","metadata":{"files":{"readme":"README-ru-ru.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-05-02T00:44:35.000Z","updated_at":"2025-04-06T12:37:33.000Z","dependencies_parsed_at":"2022-07-21T04:48:56.849Z","dependency_job_id":null,"html_url":"https://github.com/jamiebuilds/unstated-next","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated-next","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated-next/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated-next/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamiebuilds%2Funstated-next/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamiebuilds","download_url":"https://codeload.github.com/jamiebuilds/unstated-next/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254243362,"owners_count":22038046,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["library","react","redux","state-management"],"created_at":"2024-07-31T02:01:22.702Z","updated_at":"2025-05-14T23:07:19.759Z","avatar_url":"https://github.com/jamiebuilds.png","language":"TypeScript","readme":"﻿\u003cp align=\"right\"\u003e\n  \u003cstrong\u003e\n    \u003ca href=\"README.md\"\u003eEnglish\u003c/a\u003e |\n    \u003ca href=\"README-zh-cn.md\"\u003e中文\u003c/a\u003e |\n    \u003ca href=\"README-ru-ru.md\"\u003eРусский\u003c/a\u003e |\n    \u003ca href=\"README-th-th.md\"\u003eภาษาไทย\u003c/a\u003e |\n    \u003ca href=\"README-vi-vn.md\"\u003eTiếng Việt\u003c/a\u003e\n  \u003c/strong\u003e\n  \u003cbr/\u003e\n  \u003csup\u003e\u003cem\u003e(Please contribute translations!)\u003c/em\u003e\u003c/sup\u003e\n\u003c/p\u003e\n\n# Unstated Next\n\n\u003e 200 байт, чтобы навсегда забыть о библиотеках для управления состоянием React-компонентов\n\n- **React-хуки**: _это все, что нужно для управления состоянием._\n- **~200 байт**, _min+gz._\n- **Знакомый API**: _просто пользуйтесь React, как обычно._\n- **Минимальный API**: _хватит пяти минут, чтобы разобраться._\n- **Написан на TypeScript**, _чтобы обеспечить автоматический вывод типов в коде ваших компонентов React._\n\nГлавный вопрос: чем он лучше, чем Redux? Ну...\n\n- **Он меньше.** _Он в 40 раз меньше._\n- **Он быстрее.** _Изолируйте проблемы производительности на уровне компонентов._\n- **Он проще в изучении.** _Вам в любом случае нужно уметь пользоваться React-хуками и контекстом, они классные._\n- **Он проще в интеграции.** _Подключайте по одному компоненту за раз, не ломая совместимости с другими React-библиотеками._\n- **Он проще в тестировании.** _Тестировать отдельно редьюсеры — напрасная трата времени, тестируйте сами React-компоненты._\n- **Он проще с точки зрения типизации.** _Написан так, чтобы максимально задействовать выведение типов._\n- **Он минималистичный.** _Это просто React._\n\nВам решать.\n\n### [См. также: миграция с Unstated \u0026rarr;](#миграция-с-unstated)\n\n## Установка\n\n```sh\nnpm install --save unstated-next\n```\n\n## Пример\n\n```js\nimport React, { useState } from \"react\"\nimport { createContainer } from \"unstated-next\"\nimport { render } from \"react-dom\"\n\nfunction useCounter(initialState = 0) {\n  let [count, setCount] = useState(initialState)\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  return { count, decrement, increment }\n}\n\nlet Counter = createContainer(useCounter)\n\nfunction CounterDisplay() {\n  let counter = Counter.useContainer()\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cspan\u003e{counter.count}\u003c/span\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction App() {\n  return (\n    \u003cCounter.Provider\u003e\n      \u003cCounterDisplay /\u003e\n      \u003cCounter.Provider initialState={2}\u003e\n        \u003cdiv\u003e\n          \u003cdiv\u003e\n            \u003cCounterDisplay /\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/Counter.Provider\u003e\n    \u003c/Counter.Provider\u003e\n  )\n}\n\nrender(\u003cApp /\u003e, document.getElementById(\"root\"))\n```\n\n## API\n\n### `createContainer(useHook)`\n\n```js\nimport { createContainer } from \"unstated-next\"\n\nfunction useCustomHook() {\n  let [value, setInput] = useState()\n  let onChange = e =\u003e setValue(e.currentTarget.value)\n  return { value, onChange }\n}\n\nlet Container = createContainer(useCustomHook)\n// Container === { Provider, useContainer }\n```\n\n### `\u003cContainer.Provider\u003e`\n\n```js\nfunction ParentComponent() {\n  return (\n    \u003cContainer.Provider\u003e\n      \u003cChildComponent /\u003e\n    \u003c/Container.Provider\u003e\n  )\n}\n```\n\n### `\u003cContainer.Provider initialState\u003e`\n\n```js\nfunction useCustomHook(initialState = \"\") {\n  let [value, setValue] = useState(initialState)\n  // ...\n}\n\nfunction ParentComponent() {\n  return (\n    \u003cContainer.Provider initialState={\"value\"}\u003e\n      \u003cChildComponent /\u003e\n    \u003c/Container.Provider\u003e\n  )\n}\n```\n\n### `Container.useContainer()`\n\n```js\nfunction ChildComponent() {\n  let input = Container.useContainer()\n  return \u003cinput value={input.value} onChange={input.onChange} /\u003e\n}\n```\n\n### `useContainer(Container)`\n\n```js\nimport { useContainer } from \"unstated-next\"\n\nfunction ChildComponent() {\n  let input = useContainer(Container)\n  return \u003cinput value={input.value} onChange={input.onChange} /\u003e\n}\n```\n\n## Руководство\n\nЕсли вы пока не знакомы с React-хуками, рекомендую прервать чтение и ознакомиться с\n[прекрасной документацией на сайте React](https://reactjs.org/docs/hooks-intro.html).\n\nИтак, с помощью хуков вы можете написать что-нибудь вроде такого компонента:\n\n```js\nfunction CounterDisplay() {\n  let [count, setCount] = useState(0)\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {count} times\u003c/p\u003e\n      \u003cbutton onClick={increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nЕсли логику компонента требуется использовать в нескольких местах, ее можно вынести\nв отдельный кастомный хук:\n\n```js\nfunction useCounter() {\n  let [count, setCount] = useState(0)\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  return { count, decrement, increment }\n}\n\nfunction CounterDisplay() {\n  let counter = useCounter()\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\nНо что делать, когда вам требуется общее состояние, а не только логика?\nЗдесь пригодится контекст:\n\n```js\nfunction useCounter() {\n  let [count, setCount] = useState(0)\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  return { count, decrement, increment }\n}\n\nlet Counter = createContext(null)\n\nfunction CounterDisplay() {\n  let counter = useContext(Counter)\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction App() {\n  let counter = useCounter()\n  return (\n    \u003cCounter.Provider value={counter}\u003e\n      \u003cCounterDisplay /\u003e\n      \u003cCounterDisplay /\u003e\n    \u003c/Counter.Provider\u003e\n  )\n}\n```\n\nЭто замечательно и прекрасно; чем больше людей будет писать в таком стиле, тем лучше.\n\nОднако стоит внести еще чуть больше структуры и ясности, чтобы API предельно четко выражал ваши намерения.\n\nДля этого мы добавили функцию `createContainer()`, чтобы можно было рассматривать ваши кастомные хуки как \"контейнеры\", чтобы наш четкий и ясный API просто невозможно было использовать неправильно.\n\n```js\nimport { createContainer } from \"unstated-next\"\n\nfunction useCounter() {\n  let [count, setCount] = useState(0)\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  return { count, decrement, increment }\n}\n\nlet Counter = createContainer(useCounter)\n\nfunction CounterDisplay() {\n  let counter = Counter.useContainer()\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction App() {\n  return (\n    \u003cCounter.Provider\u003e\n      \u003cCounterDisplay /\u003e\n      \u003cCounterDisplay /\u003e\n    \u003c/Counter.Provider\u003e\n  )\n}\n```\n\nСравните текст компонента до и после наших изменений:\n\n```diff\n- import { createContext, useContext } from \"react\"\n+ import { createContainer } from \"unstated-next\"\n\n  function useCounter() {\n    ...\n  }\n\n- let Counter = createContext(null)\n+ let Counter = createContainer(useCounter)\n\n  function CounterDisplay() {\n-   let counter = useContext(Counter)\n+   let counter = Counter.useContainer()\n    return (\n      \u003cdiv\u003e\n        ...\n      \u003c/div\u003e\n    )\n  }\n\n  function App() {\n-   let counter = useCounter()\n    return (\n-     \u003cCounter.Provider value={counter}\u003e\n+     \u003cCounter.Provider\u003e\n        \u003cCounterDisplay /\u003e\n        \u003cCounterDisplay /\u003e\n      \u003c/Counter.Provider\u003e\n    )\n  }\n```\n\nЕсли вы пишете на TypeScript (а если нет — настоятельно рекомендую ознакомиться с ним), вы ко всему прочему получаете более качественный вывод типов. Если ваш кастомный хук строго типизирован, вывод всех остальных типов сработает автоматически.\n\n## Советы\n\n### Совет #1: Объединение контейнеров\n\nПоскольку мы имеем дело с кастомными хуками, мы можем объединять контейнеры внутри других хуков.\n\n```js\nfunction useCounter() {\n  let [count, setCount] = useState(0)\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  return { count, decrement, increment, setCount }\n}\n\nlet Counter = createContainer(useCounter)\n\nfunction useResettableCounter() {\n  let counter = Counter.useContainer()\n  let reset = () =\u003e counter.setCount(0)\n  return { ...counter, reset }\n}\n```\n\n### Совет #2: Используйте маленькие контейнеры\n\nКонтейнеры лучше всего делать маленькими и четко сфокусированными на конкретной задаче. Если вам нужна дополнительная бизнес-логика в контейнерах — выносите новые операции в отдельные хуки, а состояние пусть хранится в контейнерах.\n\n```js\nfunction useCount() {\n  return useState(0)\n}\n\nlet Count = createContainer(useCount)\n\nfunction useCounter() {\n  let [count, setCount] = Count.useContainer()\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  let reset = () =\u003e setCount(0)\n  return { count, decrement, increment, reset }\n}\n```\n\n### Совет #3: Оптимизация компонентов\n\nНе существует никакой отдельной \"оптимизации\" для `unstated-next`, достаточно обычных приемов оптимизации React-компонентов.\n\n#### 1) Оптимизация тяжелых поддеревьев с помощью разбиения компонентов на части.\n\n**До:**\n\n```js\nfunction CounterDisplay() {\n  let counter = Counter.useContainer()\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n      \u003cdiv\u003e\n        \u003cdiv\u003e\n          \u003cdiv\u003e\n            \u003cdiv\u003eСУПЕР НАВОРОЧЕННОЕ ПОДДЕРЕВО КОМПОНЕНТОВ\u003c/div\u003e\n          \u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n**После:**\n\n```js\nfunction ExpensiveComponent() {\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003e\n        \u003cdiv\u003e\n          \u003cdiv\u003eСУПЕР НАВОРОЧЕННОЕ ПОДДЕРЕВО КОМПОНЕНТОВ\u003c/div\u003e\n        \u003c/div\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n\nfunction CounterDisplay() {\n  let counter = Counter.useContainer()\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n      \u003cExpensiveComponent /\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n#### 2) Оптимизация тяжелых операций с помощью хука useMemo()\n\n**До:**\n\n```js\nfunction CounterDisplay(props) {\n  let counter = Counter.useContainer()\n\n  // Вычислять выражение каждый раз, когда обновляется `counter` — слишком медленно\n  let expensiveValue = expensiveComputation(props.input)\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n**После:**\n\n```js\nfunction CounterDisplay(props) {\n  let counter = Counter.useContainer()\n\n  // Пересчитываем значение только тогда, когда входные данные изменились\n  let expensiveValue = useMemo(() =\u003e {\n    return expensiveComputation(props.input)\n  }, [props.input])\n\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n#### 3) Снижаем количество повторных рендеров с помощью React.memo() and useCallback()\n\n**До:**\n\n```js\nfunction useCounter() {\n  let [count, setCount] = useState(0)\n  let decrement = () =\u003e setCount(count - 1)\n  let increment = () =\u003e setCount(count + 1)\n  return { count, decrement, increment }\n}\n\nlet Counter = createContainer(useCounter)\n\nfunction CounterDisplay(props) {\n  let counter = Counter.useContainer()\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={counter.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {counter.count} times\u003c/p\u003e\n      \u003cbutton onClick={counter.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n**После:**\n\n```js\nfunction useCounter() {\n  let [count, setCount] = useState(0)\n  let decrement = useCallback(() =\u003e setCount(count - 1), [count])\n  let increment = useCallback(() =\u003e setCount(count + 1), [count])\n  return { count, decrement, increment }\n}\n\nlet Counter = createContainer(useCounter)\n\nlet CounterDisplayInner = React.memo(props =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003cbutton onClick={props.decrement}\u003e-\u003c/button\u003e\n      \u003cp\u003eYou clicked {props.count} times\u003c/p\u003e\n      \u003cbutton onClick={props.increment}\u003e+\u003c/button\u003e\n    \u003c/div\u003e\n  )\n})\n\nfunction CounterDisplay(props) {\n  let counter = Counter.useContainer()\n  return \u003cCounterDisplayInner {...counter} /\u003e\n}\n```\n\n## Отношение к Unstated\n\nЯ рассматриваю данную библиотеку как духовного преемника [Unstated](https://github.com/jamiebuilds/unstated). Я сделал Unstated, поскольку был убежден, что React и сам превосходно справлялся с управлением состоянием, и ему не хватало только простого механизма для разделения общего состояния и логики. Поэтому я создал Unstated как \"минимальное\" решение для данной проблемы.\n\nС появлением хуков React стал гораздо лучше в плане выделения общего состояния и логики. Настолько лучше, что, с моей точки зрения, Unstated стал излишней абстракцией.\n\n**ТЕМ НЕ МЕНЕЕ**, я считаю, что многие разработчики слабо представляют, как разделять логику и общее состояние приложения с помощью React-хуков. Это может быть связано просто с недостаточным качеством документации и инерцией сообщества, но я полагаю, что четкий API как раз способен исправить этот недостаток.\n\nUnstated Next и есть этот самый API. Вместо того, чтобы быть \"Минимальным API для разделения общего состояния и логики в React\", теперь он \"Минимальный API для понимания, как разделять общее состояние и логику в React\".\n\nЯ всегда был на стороне React, и я хочу, чтобы React процветал. Я бы предпочел, чтобы сообщество отказалось от использования библиотек для управления состоянием наподобие Redux, и начало наконец в полную силу использовать встроенные в React инструменты.\n\nЕсли вместо того, чтобы использовать Unstated, вы будете просто использовать React — я буду это только приветствовать. Пишите об этом в своих блогах! Выступайте об этом на конференциях! Делитесь своими знаниями с сообществом.\n\n## Миграция с `unstated`\n\nЯ нарочно публикую эту библиотеку как отдельный пакет, потому что весь API полностью новый. Поэтому вы можете параллельно установить оба пакета и мигрировать постепенно.\n\nПоделитесь своими впечатлениями о переходе на `unstated-next`, потому что в течение нескольких следующих месяцев я планирую на базе этой информации сделать две вещи:\n\n- Убедиться, что `unstated-next` удовлетворяет все нужды пользователей `unstated`.\n- Удостовериться, что для `unstated` есть четкий и ясный процесс миграции на `unstated-next`.\n\nВозможно, я добавлю какие-то API в старую или новую библиотеку, чтобы упростить жизнь разработчикам. Что касается `unstated-next`, я обещаю, что добавленные API будут минимальными, насколько это возможно, и я приложу все усилия, чтобы библиотека осталась маленькой.\n\nВ будущем, я, вероятно, перенесу код `unstated-next` обратно в `unstated` в качестве новой мажорной версии. `unstated-next` будет по-прежнему доступен, чтобы можно было параллельно пользоваться `unstated@2` и `unstated-next` в одном проекте. Затем, когда вы закончите миграцию, вы сможете обновиться до версии `unstated@3` и удалить `unstated-next` (разумеется, обновив все импорты... поиска и замены должно быть достаточно).\n\nНесмотря на кардинальную смену API, я надеюсь, что смогу обеспечить вам максимально простую миграцию, насколько это вообще возможно. Я оптимизирую процесс с точки зрения использования самых последних API React-хуков, а не с точки зрения сохранения кода, написанного с использованием `Unstated.Container`-ов. Буду рад любым замечаниям о том, что можно было бы сделать лучше.\n","funding_links":[],"categories":["TypeScript","目录","List"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamiebuilds%2Funstated-next","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamiebuilds%2Funstated-next","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamiebuilds%2Funstated-next/lists"}