Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/jamiebuilds/unstated-next
200 bytes to never think about React state management libraries ever again
https://github.com/jamiebuilds/unstated-next
library react redux state-management
Last synced: 6 days ago
JSON representation
200 bytes to never think about React state management libraries ever again
- Host: GitHub
- URL: https://github.com/jamiebuilds/unstated-next
- Owner: jamiebuilds
- License: mit
- Created: 2019-05-02T00:44:35.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2022-02-10T13:23:27.000Z (almost 3 years ago)
- Last Synced: 2024-11-20T18:49:50.045Z (22 days ago)
- Topics: library, react, redux, state-management
- Language: TypeScript
- Homepage:
- Size: 867 KB
- Stars: 4,178
- Watchers: 32
- Forks: 145
- Open Issues: 53
-
Metadata Files:
- Readme: README-ru-ru.md
- License: LICENSE
Awesome Lists containing this project
- awesome-react-state-management - unstated-next - 200 bytes to never think about React state management libraries ever again (List)
- awesome-star-libs - jamiebuilds / unstated-next
README
English |
中文 |
Русский |
ภาษาไทย |
Tiếng Việt
(Please contribute translations!)# Unstated Next
> 200 байт, чтобы навсегда забыть о библиотеках для управления состоянием React-компонентов
- **React-хуки**: _это все, что нужно для управления состоянием._
- **~200 байт**, _min+gz._
- **Знакомый API**: _просто пользуйтесь React, как обычно._
- **Минимальный API**: _хватит пяти минут, чтобы разобраться._
- **Написан на TypeScript**, _чтобы обеспечить автоматический вывод типов в коде ваших компонентов React._Главный вопрос: чем он лучше, чем Redux? Ну...
- **Он меньше.** _Он в 40 раз меньше._
- **Он быстрее.** _Изолируйте проблемы производительности на уровне компонентов._
- **Он проще в изучении.** _Вам в любом случае нужно уметь пользоваться React-хуками и контекстом, они классные._
- **Он проще в интеграции.** _Подключайте по одному компоненту за раз, не ломая совместимости с другими React-библиотеками._
- **Он проще в тестировании.** _Тестировать отдельно редьюсеры — напрасная трата времени, тестируйте сами React-компоненты._
- **Он проще с точки зрения типизации.** _Написан так, чтобы максимально задействовать выведение типов._
- **Он минималистичный.** _Это просто React._Вам решать.
### [См. также: миграция с Unstated →](#миграция-с-unstated)
## Установка
```sh
npm install --save unstated-next
```## Пример
```js
import React, { useState } from "react"
import { createContainer } from "unstated-next"
import { render } from "react-dom"function useCounter(initialState = 0) {
let [count, setCount] = useState(initialState)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment }
}let Counter = createContainer(useCounter)
function CounterDisplay() {
let counter = Counter.useContainer()
return (
-
{counter.count}
+
)
}function App() {
return (
)
}render(, document.getElementById("root"))
```## API
### `createContainer(useHook)`
```js
import { createContainer } from "unstated-next"function useCustomHook() {
let [value, setInput] = useState()
let onChange = e => setValue(e.currentTarget.value)
return { value, onChange }
}let Container = createContainer(useCustomHook)
// Container === { Provider, useContainer }
```### ``
```js
function ParentComponent() {
return (
)
}
```### ``
```js
function useCustomHook(initialState = "") {
let [value, setValue] = useState(initialState)
// ...
}function ParentComponent() {
return (
)
}
```### `Container.useContainer()`
```js
function ChildComponent() {
let input = Container.useContainer()
return
}
```### `useContainer(Container)`
```js
import { useContainer } from "unstated-next"function ChildComponent() {
let input = useContainer(Container)
return
}
```## Руководство
Если вы пока не знакомы с React-хуками, рекомендую прервать чтение и ознакомиться с
[прекрасной документацией на сайте React](https://reactjs.org/docs/hooks-intro.html).Итак, с помощью хуков вы можете написать что-нибудь вроде такого компонента:
```js
function CounterDisplay() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return (
-
You clicked {count} times
+
)
}
```Если логику компонента требуется использовать в нескольких местах, ее можно вынести
в отдельный кастомный хук:```js
function useCounter() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment }
}function CounterDisplay() {
let counter = useCounter()
return (
-
You clicked {counter.count} times
+
)
}
```Но что делать, когда вам требуется общее состояние, а не только логика?
Здесь пригодится контекст:```js
function useCounter() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment }
}let Counter = createContext(null)
function CounterDisplay() {
let counter = useContext(Counter)
return (
-
You clicked {counter.count} times
+
)
}function App() {
let counter = useCounter()
return (
)
}
```Это замечательно и прекрасно; чем больше людей будет писать в таком стиле, тем лучше.
Однако стоит внести еще чуть больше структуры и ясности, чтобы API предельно четко выражал ваши намерения.
Для этого мы добавили функцию `createContainer()`, чтобы можно было рассматривать ваши кастомные хуки как "контейнеры", чтобы наш четкий и ясный API просто невозможно было использовать неправильно.
```js
import { createContainer } from "unstated-next"function useCounter() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment }
}let Counter = createContainer(useCounter)
function CounterDisplay() {
let counter = Counter.useContainer()
return (
-
You clicked {counter.count} times
+
)
}function App() {
return (
)
}
```Сравните текст компонента до и после наших изменений:
```diff
- import { createContext, useContext } from "react"
+ import { createContainer } from "unstated-next"function useCounter() {
...
}- let Counter = createContext(null)
+ let Counter = createContainer(useCounter)function CounterDisplay() {
- let counter = useContext(Counter)
+ let counter = Counter.useContainer()
return (
...
)
}function App() {
- let counter = useCounter()
return (
-
+
)
}
```Если вы пишете на TypeScript (а если нет — настоятельно рекомендую ознакомиться с ним), вы ко всему прочему получаете более качественный вывод типов. Если ваш кастомный хук строго типизирован, вывод всех остальных типов сработает автоматически.
## Советы
### Совет #1: Объединение контейнеров
Поскольку мы имеем дело с кастомными хуками, мы можем объединять контейнеры внутри других хуков.
```js
function useCounter() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment, setCount }
}let Counter = createContainer(useCounter)
function useResettableCounter() {
let counter = Counter.useContainer()
let reset = () => counter.setCount(0)
return { ...counter, reset }
}
```### Совет #2: Используйте маленькие контейнеры
Контейнеры лучше всего делать маленькими и четко сфокусированными на конкретной задаче. Если вам нужна дополнительная бизнес-логика в контейнерах — выносите новые операции в отдельные хуки, а состояние пусть хранится в контейнерах.
```js
function useCount() {
return useState(0)
}let Count = createContainer(useCount)
function useCounter() {
let [count, setCount] = Count.useContainer()
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
let reset = () => setCount(0)
return { count, decrement, increment, reset }
}
```### Совет #3: Оптимизация компонентов
Не существует никакой отдельной "оптимизации" для `unstated-next`, достаточно обычных приемов оптимизации React-компонентов.
#### 1) Оптимизация тяжелых поддеревьев с помощью разбиения компонентов на части.
**До:**
```js
function CounterDisplay() {
let counter = Counter.useContainer()
return (
-
You clicked {counter.count} times
+
СУПЕР НАВОРОЧЕННОЕ ПОДДЕРЕВО КОМПОНЕНТОВ
)
}
```**После:**
```js
function ExpensiveComponent() {
return (
СУПЕР НАВОРОЧЕННОЕ ПОДДЕРЕВО КОМПОНЕНТОВ
)
}function CounterDisplay() {
let counter = Counter.useContainer()
return (
-
You clicked {counter.count} times
+
)
}
```#### 2) Оптимизация тяжелых операций с помощью хука useMemo()
**До:**
```js
function CounterDisplay(props) {
let counter = Counter.useContainer()// Вычислять выражение каждый раз, когда обновляется `counter` — слишком медленно
let expensiveValue = expensiveComputation(props.input)return (
-
You clicked {counter.count} times
+
)
}
```**После:**
```js
function CounterDisplay(props) {
let counter = Counter.useContainer()// Пересчитываем значение только тогда, когда входные данные изменились
let expensiveValue = useMemo(() => {
return expensiveComputation(props.input)
}, [props.input])return (
-
You clicked {counter.count} times
+
)
}
```#### 3) Снижаем количество повторных рендеров с помощью React.memo() and useCallback()
**До:**
```js
function useCounter() {
let [count, setCount] = useState(0)
let decrement = () => setCount(count - 1)
let increment = () => setCount(count + 1)
return { count, decrement, increment }
}let Counter = createContainer(useCounter)
function CounterDisplay(props) {
let counter = Counter.useContainer()
return (
-
You clicked {counter.count} times
+
)
}
```**После:**
```js
function useCounter() {
let [count, setCount] = useState(0)
let decrement = useCallback(() => setCount(count - 1), [count])
let increment = useCallback(() => setCount(count + 1), [count])
return { count, decrement, increment }
}let Counter = createContainer(useCounter)
let CounterDisplayInner = React.memo(props => {
return (
-
You clicked {props.count} times
+
)
})function CounterDisplay(props) {
let counter = Counter.useContainer()
return
}
```## Отношение к Unstated
Я рассматриваю данную библиотеку как духовного преемника [Unstated](https://github.com/jamiebuilds/unstated). Я сделал Unstated, поскольку был убежден, что React и сам превосходно справлялся с управлением состоянием, и ему не хватало только простого механизма для разделения общего состояния и логики. Поэтому я создал Unstated как "минимальное" решение для данной проблемы.
С появлением хуков React стал гораздо лучше в плане выделения общего состояния и логики. Настолько лучше, что, с моей точки зрения, Unstated стал излишней абстракцией.
**ТЕМ НЕ МЕНЕЕ**, я считаю, что многие разработчики слабо представляют, как разделять логику и общее состояние приложения с помощью React-хуков. Это может быть связано просто с недостаточным качеством документации и инерцией сообщества, но я полагаю, что четкий API как раз способен исправить этот недостаток.
Unstated Next и есть этот самый API. Вместо того, чтобы быть "Минимальным API для разделения общего состояния и логики в React", теперь он "Минимальный API для понимания, как разделять общее состояние и логику в React".
Я всегда был на стороне React, и я хочу, чтобы React процветал. Я бы предпочел, чтобы сообщество отказалось от использования библиотек для управления состоянием наподобие Redux, и начало наконец в полную силу использовать встроенные в React инструменты.
Если вместо того, чтобы использовать Unstated, вы будете просто использовать React — я буду это только приветствовать. Пишите об этом в своих блогах! Выступайте об этом на конференциях! Делитесь своими знаниями с сообществом.
## Миграция с `unstated`
Я нарочно публикую эту библиотеку как отдельный пакет, потому что весь API полностью новый. Поэтому вы можете параллельно установить оба пакета и мигрировать постепенно.
Поделитесь своими впечатлениями о переходе на `unstated-next`, потому что в течение нескольких следующих месяцев я планирую на базе этой информации сделать две вещи:
- Убедиться, что `unstated-next` удовлетворяет все нужды пользователей `unstated`.
- Удостовериться, что для `unstated` есть четкий и ясный процесс миграции на `unstated-next`.Возможно, я добавлю какие-то API в старую или новую библиотеку, чтобы упростить жизнь разработчикам. Что касается `unstated-next`, я обещаю, что добавленные API будут минимальными, насколько это возможно, и я приложу все усилия, чтобы библиотека осталась маленькой.
В будущем, я, вероятно, перенесу код `unstated-next` обратно в `unstated` в качестве новой мажорной версии. `unstated-next` будет по-прежнему доступен, чтобы можно было параллельно пользоваться `unstated@2` и `unstated-next` в одном проекте. Затем, когда вы закончите миграцию, вы сможете обновиться до версии `unstated@3` и удалить `unstated-next` (разумеется, обновив все импорты... поиска и замены должно быть достаточно).
Несмотря на кардинальную смену API, я надеюсь, что смогу обеспечить вам максимально простую миграцию, насколько это вообще возможно. Я оптимизирую процесс с точки зрения использования самых последних API React-хуков, а не с точки зрения сохранения кода, написанного с использованием `Unstated.Container`-ов. Буду рад любым замечаниям о том, что можно было бы сделать лучше.