├── .gitignore
├── .prettierignore
├── .prettierrc
├── LICENSE
├── README-ru-ru.md
├── README-th-th.md
├── README-vi-vn.md
├── README-zh-cn.md
├── README.md
├── example
├── index.html
└── index.tsx
├── package-lock.json
├── package.json
├── src
└── unstated-next.tsx
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *.log
3 | example/dist
4 | dist
5 | .rts2_cache_*
6 | .cache
7 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .rts2_cache_*
2 | node_modules
3 | .*ignore
4 | dist
5 | LICENSE
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "useTabs": true,
3 | "semi": false,
4 | "trailingComma": "all",
5 | "overrides": [
6 | {
7 | "files": "*.md",
8 | "options": {
9 | "useTabs": false
10 | }
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c)
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README-ru-ru.md:
--------------------------------------------------------------------------------
1 |
80 |
81 |
82 | )
83 | }
84 |
85 | render(, document.getElementById("root"))
86 | ```
87 |
88 | ## API
89 |
90 | ### `createContainer(useHook)`
91 |
92 | ```js
93 | import { createContainer } from "unstated-next"
94 |
95 | function useCustomHook() {
96 | let [value, setInput] = useState()
97 | let onChange = e => setValue(e.currentTarget.value)
98 | return { value, onChange }
99 | }
100 |
101 | let Container = createContainer(useCustomHook)
102 | // Container === { Provider, useContainer }
103 | ```
104 |
105 | ### ``
106 |
107 | ```js
108 | function ParentComponent() {
109 | return (
110 |
111 |
112 |
113 | )
114 | }
115 | ```
116 |
117 | ### ``
118 |
119 | ```js
120 | function useCustomHook(initialState = "") {
121 | let [value, setValue] = useState(initialState)
122 | // ...
123 | }
124 |
125 | function ParentComponent() {
126 | return (
127 |
128 |
129 |
130 | )
131 | }
132 | ```
133 |
134 | ### `Container.useContainer()`
135 |
136 | ```js
137 | function ChildComponent() {
138 | let input = Container.useContainer()
139 | return
140 | }
141 | ```
142 |
143 | ### `useContainer(Container)`
144 |
145 | ```js
146 | import { useContainer } from "unstated-next"
147 |
148 | function ChildComponent() {
149 | let input = useContainer(Container)
150 | return
151 | }
152 | ```
153 |
154 | ## Руководство
155 |
156 | Если вы пока не знакомы с React-хуками, рекомендую прервать чтение и ознакомиться с
157 | [прекрасной документацией на сайте React](https://reactjs.org/docs/hooks-intro.html).
158 |
159 | Итак, с помощью хуков вы можете написать что-нибудь вроде такого компонента:
160 |
161 | ```js
162 | function CounterDisplay() {
163 | let [count, setCount] = useState(0)
164 | let decrement = () => setCount(count - 1)
165 | let increment = () => setCount(count + 1)
166 | return (
167 |
168 |
169 |
You clicked {count} times
170 |
171 |
172 | )
173 | }
174 | ```
175 |
176 | Если логику компонента требуется использовать в нескольких местах, ее можно вынести
177 | в отдельный кастомный хук:
178 |
179 | ```js
180 | function useCounter() {
181 | let [count, setCount] = useState(0)
182 | let decrement = () => setCount(count - 1)
183 | let increment = () => setCount(count + 1)
184 | return { count, decrement, increment }
185 | }
186 |
187 | function CounterDisplay() {
188 | let counter = useCounter()
189 | return (
190 |
191 |
192 |
You clicked {counter.count} times
193 |
194 |
195 | )
196 | }
197 | ```
198 |
199 | Но что делать, когда вам требуется общее состояние, а не только логика?
200 | Здесь пригодится контекст:
201 |
202 | ```js
203 | function useCounter() {
204 | let [count, setCount] = useState(0)
205 | let decrement = () => setCount(count - 1)
206 | let increment = () => setCount(count + 1)
207 | return { count, decrement, increment }
208 | }
209 |
210 | let Counter = createContext(null)
211 |
212 | function CounterDisplay() {
213 | let counter = useContext(Counter)
214 | return (
215 |
216 |
217 |
You clicked {counter.count} times
218 |
219 |
220 | )
221 | }
222 |
223 | function App() {
224 | let counter = useCounter()
225 | return (
226 |
227 |
228 |
229 |
230 | )
231 | }
232 | ```
233 |
234 | Это замечательно и прекрасно; чем больше людей будет писать в таком стиле, тем лучше.
235 |
236 | Однако стоит внести еще чуть больше структуры и ясности, чтобы API предельно четко выражал ваши намерения.
237 |
238 | Для этого мы добавили функцию `createContainer()`, чтобы можно было рассматривать ваши кастомные хуки как "контейнеры", чтобы наш четкий и ясный API просто невозможно было использовать неправильно.
239 |
240 | ```js
241 | import { createContainer } from "unstated-next"
242 |
243 | function useCounter() {
244 | let [count, setCount] = useState(0)
245 | let decrement = () => setCount(count - 1)
246 | let increment = () => setCount(count + 1)
247 | return { count, decrement, increment }
248 | }
249 |
250 | let Counter = createContainer(useCounter)
251 |
252 | function CounterDisplay() {
253 | let counter = Counter.useContainer()
254 | return (
255 |
256 |
257 |
You clicked {counter.count} times
258 |
259 |
260 | )
261 | }
262 |
263 | function App() {
264 | return (
265 |
266 |
267 |
268 |
269 | )
270 | }
271 | ```
272 |
273 | Сравните текст компонента до и после наших изменений:
274 |
275 | ```diff
276 | - import { createContext, useContext } from "react"
277 | + import { createContainer } from "unstated-next"
278 |
279 | function useCounter() {
280 | ...
281 | }
282 |
283 | - let Counter = createContext(null)
284 | + let Counter = createContainer(useCounter)
285 |
286 | function CounterDisplay() {
287 | - let counter = useContext(Counter)
288 | + let counter = Counter.useContainer()
289 | return (
290 |
291 | ...
292 |
293 | )
294 | }
295 |
296 | function App() {
297 | - let counter = useCounter()
298 | return (
299 | -
300 | +
301 |
302 |
303 |
304 | )
305 | }
306 | ```
307 |
308 | Если вы пишете на TypeScript (а если нет — настоятельно рекомендую ознакомиться с ним), вы ко всему прочему получаете более качественный вывод типов. Если ваш кастомный хук строго типизирован, вывод всех остальных типов сработает автоматически.
309 |
310 | ## Советы
311 |
312 | ### Совет #1: Объединение контейнеров
313 |
314 | Поскольку мы имеем дело с кастомными хуками, мы можем объединять контейнеры внутри других хуков.
315 |
316 | ```js
317 | function useCounter() {
318 | let [count, setCount] = useState(0)
319 | let decrement = () => setCount(count - 1)
320 | let increment = () => setCount(count + 1)
321 | return { count, decrement, increment, setCount }
322 | }
323 |
324 | let Counter = createContainer(useCounter)
325 |
326 | function useResettableCounter() {
327 | let counter = Counter.useContainer()
328 | let reset = () => counter.setCount(0)
329 | return { ...counter, reset }
330 | }
331 | ```
332 |
333 | ### Совет #2: Используйте маленькие контейнеры
334 |
335 | Контейнеры лучше всего делать маленькими и четко сфокусированными на конкретной задаче. Если вам нужна дополнительная бизнес-логика в контейнерах — выносите новые операции в отдельные хуки, а состояние пусть хранится в контейнерах.
336 |
337 | ```js
338 | function useCount() {
339 | return useState(0)
340 | }
341 |
342 | let Count = createContainer(useCount)
343 |
344 | function useCounter() {
345 | let [count, setCount] = Count.useContainer()
346 | let decrement = () => setCount(count - 1)
347 | let increment = () => setCount(count + 1)
348 | let reset = () => setCount(0)
349 | return { count, decrement, increment, reset }
350 | }
351 | ```
352 |
353 | ### Совет #3: Оптимизация компонентов
354 |
355 | Не существует никакой отдельной "оптимизации" для `unstated-next`, достаточно обычных приемов оптимизации React-компонентов.
356 |
357 | #### 1) Оптимизация тяжелых поддеревьев с помощью разбиения компонентов на части.
358 |
359 | **До:**
360 |
361 | ```js
362 | function CounterDisplay() {
363 | let counter = Counter.useContainer()
364 | return (
365 |
496 | )
497 | })
498 |
499 | function CounterDisplay(props) {
500 | let counter = Counter.useContainer()
501 | return
502 | }
503 | ```
504 |
505 | ## Отношение к Unstated
506 |
507 | Я рассматриваю данную библиотеку как духовного преемника [Unstated](https://github.com/jamiebuilds/unstated). Я сделал Unstated, поскольку был убежден, что React и сам превосходно справлялся с управлением состоянием, и ему не хватало только простого механизма для разделения общего состояния и логики. Поэтому я создал Unstated как "минимальное" решение для данной проблемы.
508 |
509 | С появлением хуков React стал гораздо лучше в плане выделения общего состояния и логики. Настолько лучше, что, с моей точки зрения, Unstated стал излишней абстракцией.
510 |
511 | **ТЕМ НЕ МЕНЕЕ**, я считаю, что многие разработчики слабо представляют, как разделять логику и общее состояние приложения с помощью React-хуков. Это может быть связано просто с недостаточным качеством документации и инерцией сообщества, но я полагаю, что четкий API как раз способен исправить этот недостаток.
512 |
513 | Unstated Next и есть этот самый API. Вместо того, чтобы быть "Минимальным API для разделения общего состояния и логики в React", теперь он "Минимальный API для понимания, как разделять общее состояние и логику в React".
514 |
515 | Я всегда был на стороне React, и я хочу, чтобы React процветал. Я бы предпочел, чтобы сообщество отказалось от использования библиотек для управления состоянием наподобие Redux, и начало наконец в полную силу использовать встроенные в React инструменты.
516 |
517 | Если вместо того, чтобы использовать Unstated, вы будете просто использовать React — я буду это только приветствовать. Пишите об этом в своих блогах! Выступайте об этом на конференциях! Делитесь своими знаниями с сообществом.
518 |
519 | ## Миграция с `unstated`
520 |
521 | Я нарочно публикую эту библиотеку как отдельный пакет, потому что весь API полностью новый. Поэтому вы можете параллельно установить оба пакета и мигрировать постепенно.
522 |
523 | Поделитесь своими впечатлениями о переходе на `unstated-next`, потому что в течение нескольких следующих месяцев я планирую на базе этой информации сделать две вещи:
524 |
525 | - Убедиться, что `unstated-next` удовлетворяет все нужды пользователей `unstated`.
526 | - Удостовериться, что для `unstated` есть четкий и ясный процесс миграции на `unstated-next`.
527 |
528 | Возможно, я добавлю какие-то API в старую или новую библиотеку, чтобы упростить жизнь разработчикам. Что касается `unstated-next`, я обещаю, что добавленные API будут минимальными, насколько это возможно, и я приложу все усилия, чтобы библиотека осталась маленькой.
529 |
530 | В будущем, я, вероятно, перенесу код `unstated-next` обратно в `unstated` в качестве новой мажорной версии. `unstated-next` будет по-прежнему доступен, чтобы можно было параллельно пользоваться `unstated@2` и `unstated-next` в одном проекте. Затем, когда вы закончите миграцию, вы сможете обновиться до версии `unstated@3` и удалить `unstated-next` (разумеется, обновив все импорты... поиска и замены должно быть достаточно).
531 |
532 | Несмотря на кардинальную смену API, я надеюсь, что смогу обеспечить вам максимально простую миграцию, насколько это вообще возможно. Я оптимизирую процесс с точки зрения использования самых последних API React-хуков, а не с точки зрения сохранения кода, написанного с использованием `Unstated.Container`-ов. Буду рад любым замечаниям о том, что можно было бы сделать лучше.
533 |
--------------------------------------------------------------------------------
/README-th-th.md:
--------------------------------------------------------------------------------
1 |
12 |
13 | # Unstated Next
14 |
15 | > Chỉ với 200 bytes, không cần lo nghĩ về quản lý React state
16 |
17 | - **React Hooks** _dùng React Hooks để quản lý tất cả các state._
18 | - **~200 bytes** _min+gz._
19 | - **API quen thuộc** _viết code như React thuần._
20 | - **API đơn giản** _chỉ tốn 5 phút để học._
21 | - **Written in TypeScript** _việc đặt kiểu cho React code sẽ rất dễ dàng._
22 |
23 | Tuy nhiên, câu hỏi quan trọng là: Nó có tốt hơn Redux? Để xem nào...
24 |
25 | - **It's smaller.** _Unstated-next nhỏ gọn hơn đến 40 lần._
26 | - **It's faster.** _Component hóa vấn đề về tốc độ._
27 | - **It's easier to learn.** _Nếu bạn đã biết về React Hooks & Context, chỉ cần sử dụng thôi, nó rất tuyệt._
28 | - **It's easier to integrate.** _Tương thích với từng Component một, và có thể kết hợp dễ dàng với mọi thư viện React khác._
29 | - **It's easier to test.** _Kiểm thử reducers rất phí thời gian, nó sẽ đơn giản hơn khi bạn chỉ cần test React components._
30 | - **It's easier to typecheck.** _Thiết kế ra để bạn định nghĩa được hầu hết các types._
31 | - **It's minimal.** _Nó chỉ là React._
32 |
33 | Vậy quyết định là ở bạn.
34 |
35 | ### [Xem các nâng cấp từ Unstated →](#migration-from-unstated)
36 |
37 | ## Cài đặt
38 |
39 | ```sh
40 | npm install --save unstated-next
41 | ```
42 |
43 | ## Ví dụ
44 |
45 | ```js
46 | import React, { useState } from "react"
47 | import { createContainer } from "unstated-next"
48 | import { render } from "react-dom"
49 |
50 | function useCounter(initialState = 0) {
51 | let [count, setCount] = useState(initialState)
52 | let decrement = () => setCount(count - 1)
53 | let increment = () => setCount(count + 1)
54 | return { count, decrement, increment }
55 | }
56 |
57 | let Counter = createContainer(useCounter)
58 |
59 | function CounterDisplay() {
60 | let counter = Counter.useContainer()
61 | return (
62 |
80 |
81 |
82 | )
83 | }
84 |
85 | render(, document.getElementById("root"))
86 | ```
87 |
88 | ## API
89 |
90 | ### `createContainer(useHook)`
91 |
92 | ```js
93 | import { createContainer } from "unstated-next"
94 |
95 | function useCustomHook() {
96 | let [value, setValue] = useState()
97 | let onChange = e => setValue(e.currentTarget.value)
98 | return { value, onChange }
99 | }
100 |
101 | let Container = createContainer(useCustomHook)
102 | // Container === { Provider, useContainer }
103 | ```
104 |
105 | ### ``
106 |
107 | ```js
108 | function ParentComponent() {
109 | return (
110 |
111 |
112 |
113 | )
114 | }
115 | ```
116 |
117 | ### ``
118 |
119 | ```js
120 | function useCustomHook(initialState = "") {
121 | let [value, setValue] = useState(initialState)
122 | // ...
123 | }
124 |
125 | function ParentComponent() {
126 | return (
127 |
128 |
129 |
130 | )
131 | }
132 | ```
133 |
134 | ### `Container.useContainer()`
135 |
136 | ```js
137 | function ChildComponent() {
138 | let input = Container.useContainer()
139 | return
140 | }
141 | ```
142 |
143 | ### `useContainer(Container)`
144 |
145 | ```js
146 | import { useContainer } from "unstated-next"
147 |
148 | function ChildComponent() {
149 | let input = useContainer(Container)
150 | return
151 | }
152 | ```
153 |
154 | ## Hướng dẫn
155 |
156 | Nếu bạn chưa bao giờ sử dụng React Hooks trước đây, Tôi khuyên bạn nên dừng lại và đọc qua [tài liệu tuyệt vời trên trang React](https://reactjs.org/docs/hooks-intro.html).
157 |
158 | Vậy với hooks bạn có thể tạo một component như thế này:
159 |
160 | ```js
161 | function CounterDisplay() {
162 | let [count, setCount] = useState(0)
163 | let decrement = () => setCount(count - 1)
164 | let increment = () => setCount(count + 1)
165 | return (
166 |
167 |
168 |
You clicked {count} times
169 |
170 |
171 | )
172 | }
173 | ```
174 |
175 | Sau đó nếu bạn muốn chia sẻ phần logic đằng sau component, bạn có thể tách nó ra thành một custom hook:
176 |
177 | ```js
178 | function useCounter() {
179 | let [count, setCount] = useState(0)
180 | let decrement = () => setCount(count - 1)
181 | let increment = () => setCount(count + 1)
182 | return { count, decrement, increment }
183 | }
184 |
185 | function CounterDisplay() {
186 | let counter = useCounter()
187 | return (
188 |
189 |
190 |
You clicked {counter.count} times
191 |
192 |
193 | )
194 | }
195 | ```
196 |
197 | Nhưng trong trường hợp bạn muốn chia sẻ cả trạng thái lẫn logic, bạn sẽ làm gì?
198 |
199 | Đây sẽ là lúc context giúp được bạn:
200 |
201 | ```js
202 | function useCounter() {
203 | let [count, setCount] = useState(0)
204 | let decrement = () => setCount(count - 1)
205 | let increment = () => setCount(count + 1)
206 | return { count, decrement, increment }
207 | }
208 |
209 | let Counter = createContext(null)
210 |
211 | function CounterDisplay() {
212 | let counter = useContext(Counter)
213 | return (
214 |
215 |
216 |
You clicked {counter.count} times
217 |
218 |
219 | )
220 | }
221 |
222 | function App() {
223 | let counter = useCounter()
224 | return (
225 |
226 |
227 |
228 |
229 | )
230 | }
231 | ```
232 |
233 | Điều này thật tuyệt, thật hoàn hảo, và mọi người nên viết code theo cách này.
234 |
235 | Nhưng đôi khi chúng ta đều cần một cấu trúc rõ ràng và được thiết kế bài bản để mang lại một sự nhất quán.
236 |
237 | Xin giới thiệu hàm `createContainer()`, bạn có thể coi như custom hooks của bạn là một "containers" và có một API rõ ràng, có thể ngăn chặn bạn khỏi việc sử dụng nó sai mục đích.
238 |
239 | ```js
240 | import { createContainer } from "unstated-next"
241 |
242 | function useCounter() {
243 | let [count, setCount] = useState(0)
244 | let decrement = () => setCount(count - 1)
245 | let increment = () => setCount(count + 1)
246 | return { count, decrement, increment }
247 | }
248 |
249 | let Counter = createContainer(useCounter)
250 |
251 | function CounterDisplay() {
252 | let counter = Counter.useContainer()
253 | return (
254 |
255 |
256 |
You clicked {counter.count} times
257 |
258 |
259 | )
260 | }
261 |
262 | function App() {
263 | return (
264 |
265 |
266 |
267 |
268 | )
269 | }
270 | ```
271 |
272 | Đây là khác biệt của sự thay đổi:
273 |
274 | ```diff
275 | - import { createContext, useContext } from "react"
276 | + import { createContainer } from "unstated-next"
277 |
278 | function useCounter() {
279 | ...
280 | }
281 |
282 | - let Counter = createContext(null)
283 | + let Counter = createContainer(useCounter)
284 |
285 | function CounterDisplay() {
286 | - let counter = useContext(Counter)
287 | + let counter = Counter.useContainer()
288 | return (
289 |
290 | ...
291 |
292 | )
293 | }
294 |
295 | function App() {
296 | - let counter = useCounter()
297 | return (
298 | -
299 | +
300 |
301 |
302 |
303 | )
304 | }
305 | ```
306 |
307 | Nếu bạn đang sử dụng TypeScript (cái mà tôi khuyến khích bạn học thêm về nó nếu bạn chưa dùng), nó còn có lợi ích trong việc làm các gợi ý có sẵn của TypeScript's hoạt đông tốt hơn. Miễn là custom hook của bạn đã được đặt kiểu, thì mọi thứ sẽ hoạt động.
308 |
309 | ## Lời khuyên
310 |
311 | ### Tip #1: Tạo các Containers
312 |
313 | Vì chúng ta chỉ đang làm việc với custom React hooks, chúng ta có thể tạo các containers bên trong những hooks khác.
314 |
315 | ```js
316 | function useCounter() {
317 | let [count, setCount] = useState(0)
318 | let decrement = () => setCount(count - 1)
319 | let increment = () => setCount(count + 1)
320 | return { count, decrement, increment, setCount }
321 | }
322 |
323 | let Counter = createContainer(useCounter)
324 |
325 | function useResettableCounter() {
326 | let counter = Counter.useContainer()
327 | let reset = () => counter.setCount(0)
328 | return { ...counter, reset }
329 | }
330 | ```
331 |
332 | ### Tip #2: Giữ cho những Containers thật nhỏ
333 |
334 | Nó sẽ hữu ích nếu như bạn giữ được các containers của bạn thật nhỏ và tập trung vào một mục đích nhất định. Điều này có thể sẽ quan trọng nếu bạn muốn tách phần logic khỏi containers: Chỉ cần chuyển logic sang phần hooks của nó và giữ state lại ở containers.
335 |
336 | ```js
337 | function useCount() {
338 | return useState(0)
339 | }
340 |
341 | let Count = createContainer(useCount)
342 |
343 | function useCounter() {
344 | let [count, setCount] = Count.useContainer()
345 | let decrement = () => setCount(count - 1)
346 | let increment = () => setCount(count + 1)
347 | let reset = () => setCount(0)
348 | return { count, decrement, increment, reset }
349 | }
350 | ```
351 |
352 | ### Tip #3: Tối ưu hóa components
353 |
354 | Không có "tối ưu" `unstated-next` cần làm ở đây, tất cả việc tối ưu bạn có thể phải làm đó là tối ưu React code của bạn.
355 |
356 | #### 1) Tối ưu các nhánh component nặng bằng việc chia ra thành một component riêng
357 |
358 | **Trước:**
359 |
360 | ```js
361 | function CounterDisplay() {
362 | let counter = Counter.useContainer()
363 | return (
364 |
530 | ), [count])
531 | }
532 | ```
533 |
534 | ## Sự tương quan với Unstated
535 |
536 | Tôi coi thư viện này là sự kế thừa tinh thần cho [Unstated](https://github.com/jamiebuilds/unstated). Tôi đã tạo ra Unstated vì tôi tin rằng React đã thực sự tuyệt vời trong việc quản state và điều duy nhất còn thiếu là chia sẻ state, logic một cách dễ dàng. Vì vậy, tôi đã tạo ra Unstated, một giải pháp "tối giản" để chia sẻ React state và logic.
537 |
538 | Tuy nhiên, với Hook, React đã trở nên tốt hơn nhiều trong việc chia sẻ state và logic. Đến mức tôi nghĩ Unstated đã trở thành một abstraction không cần thiết.
539 |
540 | **Tuy nhiên**, Tôi nghĩ rằng nhiều nhà phát triển đã vật lộn để xem cách chia sẻ state và logic với React Hook trong một "trạng thái của ứng dụng". Đó có thể chỉ là vấn đề về tài liệu và động lực từ cộng đồng, nhưng tôi nghĩ rằng sẽ có một API có thể giúp thu hẹp khoảng cách về tinh thần đó.
541 |
542 | API đó chính là Unstated Next. Thay vì là "API tối thiểu để chia sẻ state và logic trong React", giờ đây nó là "API tối thiểu để hiểu state và logic được chia sẻ trong React".
543 |
544 | Tôi đã luôn đứng về phía React. Tôi muốn React giành chiến thắng. Tôi muốn thấy cộng đồng từ bỏ các thư viện quản lý state như Redux và tìm ra những cách tốt hơn để sử dụng chuỗi công cụ tích hợp có sẵn của React.
545 |
546 | Nếu thay vì sử dụng Unstated, bạn chỉ muốn sử dụng React, tôi rất khuyến khích điều đó. Viết bài đăng trên blog về nó! Hãy kể về nó! Truyền bá kiến thức của bạn trong cộng đồng.
547 |
548 | ## Cách cập nhật từ `unstated`
549 |
550 | Tôi đã cố tình xuất bản thư viện này dưới dạng tên gói riêng vì API đã được viết lại hoàn toàn. Bằng cách này, bạn có thể cài cả hai và migrate dần dần.
551 |
552 | Vui lòng cung cấp cho tôi thông tin phản hồi về quá trình migrate đó, vì trong vài tháng tới tôi hy vọng sẽ nhận được nhiều thông tin và thực hiện hai điều:
553 |
554 | - Đảm bảo rằng `unstated-next` đáp ứng tất cả các nhu cầu của người dùng` unstated`.
555 | - Đảm bảo rằng `unstated` có một quy trình migrate sạch sẽ đối với` unstated-next`.
556 |
557 | Tôi có thể chọn thêm API vào một trong hai thư viện để giúp các nhà phát triển dễ dàng hơn. Đối với `unstated-next` Tôi hứa rằng các API được thêm vào sẽ tối thiểu nhất có thể và tôi sẽ cố gắng giữ thư viện thật nhỏ.
558 |
559 | Trong tương lai, tôi có thể sẽ hợp nhất `unstated-next` trở lại thành `unstated` như một phiên bản chính mới. `unstated-next` vẫn sẽ tồn tại để bạn có thể cài đặt cả `unstated@2` và `unstated-next`. Sau đó, khi bạn hoàn thành việc migrate, bạn có thể cập nhật thành `unstated@3` và xóa `unstated-next` (chắc chắn bạn đã cập nhật tất cả các imports... bằng cách tìm kiếm và thay thế).
560 |
561 | Mặc dù đây là một thay đổi API mới, tôi hy vọng rằng tôi có thể thực hiện việc migrate này dễ dàng nhất có thể đối với bạn. Tôi đang tối ưu hóa cho bạn để sử dụng API React Hook mới nhất chứ không phải để giữ code được viết bằng `Unstated.Container`. Hãy cho ý kiến của bạn về cách nó có thể được làm tốt hơn.
562 |
--------------------------------------------------------------------------------
/README-zh-cn.md:
--------------------------------------------------------------------------------
1 |
12 |
13 | # Unstated Next
14 |
15 | > 200 bytes to never think about React state management libraries ever again
16 |
17 | - **React Hooks** _use them for all your state management._
18 | - **~200 bytes** _min+gz._
19 | - **Familiar API** _just use React as intended._
20 | - **Minimal API** _it takes 5 minutes to learn._
21 | - **Written in TypeScript** _and will make it easier for you to type your React code._
22 |
23 | But, the most important question: Is this better than Redux? Well...
24 |
25 | - **It's smaller.** _It's 40x smaller._
26 | - **It's faster.** _Componentize the problem of performance._
27 | - **It's easier to learn.** _You already will have to know React Hooks & Context, just use them, they rock._
28 | - **It's easier to integrate.** _Integrate one component at a time, and easily integrate with every React library._
29 | - **It's easier to test.** _Testing reducers is a waste of your time, make it easier to test your React components._
30 | - **It's easier to typecheck.** _Designed to make most of your types inferable._
31 | - **It's minimal.** _It's just React._
32 |
33 | So you decide.
34 |
35 | ### [See Migration From Unstated docs →](#migration-from-unstated)
36 |
37 | ## Install
38 |
39 | ```sh
40 | npm install --save unstated-next
41 | ```
42 |
43 | ## Example
44 |
45 | ```js
46 | import React, { useState } from "react"
47 | import { createContainer } from "unstated-next"
48 | import { render } from "react-dom"
49 |
50 | function useCounter(initialState = 0) {
51 | let [count, setCount] = useState(initialState)
52 | let decrement = () => setCount(count - 1)
53 | let increment = () => setCount(count + 1)
54 | return { count, decrement, increment }
55 | }
56 |
57 | let Counter = createContainer(useCounter)
58 |
59 | function CounterDisplay() {
60 | let counter = Counter.useContainer()
61 | return (
62 |
172 | )
173 | }
174 | ```
175 |
176 | Then if you want to share the logic behind the component, you could pull it out
177 | into a custom hook:
178 |
179 | ```js
180 | function useCounter() {
181 | let [count, setCount] = useState(0)
182 | let decrement = () => setCount(count - 1)
183 | let increment = () => setCount(count + 1)
184 | return { count, decrement, increment }
185 | }
186 |
187 | function CounterDisplay() {
188 | let counter = useCounter()
189 | return (
190 |
191 |
192 |
You clicked {counter.count} times
193 |
194 |
195 | )
196 | }
197 | ```
198 |
199 | But what if you want to share the state in addition to the logic, what do you do?
200 |
201 | This is where context comes into play:
202 |
203 | ```js
204 | function useCounter() {
205 | let [count, setCount] = useState(0)
206 | let decrement = () => setCount(count - 1)
207 | let increment = () => setCount(count + 1)
208 | return { count, decrement, increment }
209 | }
210 |
211 | let Counter = createContext(null)
212 |
213 | function CounterDisplay() {
214 | let counter = useContext(Counter)
215 | return (
216 |
217 |
218 |
You clicked {counter.count} times
219 |
220 |
221 | )
222 | }
223 |
224 | function App() {
225 | let counter = useCounter()
226 | return (
227 |
228 |
229 |
230 |
231 | )
232 | }
233 | ```
234 |
235 | This is great, it's perfect, more people should write code like this.
236 |
237 | But sometimes we all need a little bit more structure and intentional API design in order to get it consistently right.
238 |
239 | By introducing the `createContainer()` function, you can think about your custom hooks as "containers" and have an API that's clear and prevents you from using it wrong.
240 |
241 | ```js
242 | import { createContainer } from "unstated-next"
243 |
244 | function useCounter() {
245 | let [count, setCount] = useState(0)
246 | let decrement = () => setCount(count - 1)
247 | let increment = () => setCount(count + 1)
248 | return { count, decrement, increment }
249 | }
250 |
251 | let Counter = createContainer(useCounter)
252 |
253 | function CounterDisplay() {
254 | let counter = Counter.useContainer()
255 | return (
256 |
294 | )
295 | }
296 |
297 | function App() {
298 | - let counter = useCounter()
299 | return (
300 | -
301 | +
302 |
303 |
304 |
305 | )
306 | }
307 | ```
308 |
309 | If you're using TypeScript (which I encourage you to learn more about if you are not), this also has the benefit of making TypeScript's built-in inference work better. As long as your custom hook is typed, then everything else will just work.
310 |
311 | ## Tips
312 |
313 | ### Tip #1: Composing Containers
314 |
315 | Because we're just working with custom React hooks, we can compose containers inside of other hooks.
316 |
317 | ```js
318 | function useCounter() {
319 | let [count, setCount] = useState(0)
320 | let decrement = () => setCount(count - 1)
321 | let increment = () => setCount(count + 1)
322 | return { count, decrement, increment, setCount }
323 | }
324 |
325 | let Counter = createContainer(useCounter)
326 |
327 | function useResettableCounter() {
328 | let counter = Counter.useContainer()
329 | let reset = () => counter.setCount(0)
330 | return { ...counter, reset }
331 | }
332 | ```
333 |
334 | ### Tip #2: Keeping Containers Small
335 |
336 | This can be useful for keeping your containers small and focused. Which can be important if you want to code split the logic in your containers: Just move them to their own hooks and keep just the state in containers.
337 |
338 | ```js
339 | function useCount() {
340 | return useState(0)
341 | }
342 |
343 | let Count = createContainer(useCount)
344 |
345 | function useCounter() {
346 | let [count, setCount] = Count.useContainer()
347 | let decrement = () => setCount(count - 1)
348 | let increment = () => setCount(count + 1)
349 | let reset = () => setCount(0)
350 | return { count, decrement, increment, reset }
351 | }
352 | ```
353 |
354 | ### Tip #3: Optimizing components
355 |
356 | There's no "optimizing" `unstated-next` to be done, all of the optimizations you might do would be standard React optimizations.
357 |
358 | #### 1) Optimizing expensive sub-trees by splitting the component apart
359 |
360 | **Before:**
361 |
362 | ```js
363 | function CounterDisplay() {
364 | let counter = Counter.useContainer()
365 | return (
366 |
532 | ), [count])
533 | }
534 | ```
535 |
536 | ## Relation to Unstated
537 |
538 | I consider this library the spiritual successor to [Unstated](https://github.com/jamiebuilds/unstated). I created Unstated because I believed that React was really great at state management already and the only missing piece was sharing state and logic easily. So I created Unstated to be the "minimal" solution to sharing React state and logic.
539 |
540 | However, with Hooks, React has become much better at sharing state and logic. To the point that I think Unstated has become an unnecessary abstraction.
541 |
542 | **HOWEVER**, I think many developers have struggled seeing how to share state and logic with React Hooks for "application state". That may just be an issue of documentation and community momentum, but I think that an API could help bridge that mental gap.
543 |
544 | That API is what Unstated Next is. Instead of being the "Minimal API for sharing state and logic in React", it is now the "Minimal API for understanding shared state and logic in React".
545 |
546 | I've always been on the side of React. I want React to win. I would like to see the community abandon state management libraries like Redux, and find better ways of making use of React's built-in toolchain.
547 |
548 | If instead of using Unstated, you just want to use React itself, I would highly encourage that. Write blog posts about it! Give talks about it! Spread your knowledge in the community.
549 |
550 | ## Migration from `unstated`
551 |
552 | I've intentionally published this as a separate package name because it is a complete reset on the API. This way you can have both installed and migrate incrementally.
553 |
554 | Please provide me with feedback on that migration process, because over the next few months I hope to take that feedback and do two things:
555 |
556 | - Make sure `unstated-next` fulfills all the needs of `unstated` users.
557 | - Make sure `unstated` has a clean migration process towards `unstated-next`.
558 |
559 | I may choose to add APIs to either library to make life easier for developers. For `unstated-next` I promise that the added APIs will be as minimal as possible and I'll try to keep the library small.
560 |
561 | In the future, I will likely merge `unstated-next` back into `unstated` as a new major version. `unstated-next` will still exist so that you can have both `unstated@2` and `unstated-next` installed. Then when you are done with the migration, you can update to `unstated@3` and remove `unstated-next` (being sure to update all your imports as you do... should be just a find-and-replace).
562 |
563 | Even though this is a major new API change, I hope that I can make this migration as easy as possible on you. I'm optimizing for you to get to using the latest React Hooks APIs and not for preserving code written with `Unstated.Container`'s. Feel free to provide feedback on how that could be done better.
564 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react"
2 | import { createContainer } from "../src/unstated-next"
3 | import { render } from "react-dom"
4 |
5 | function useCounter(initialState = 0) {
6 | let [count, setCount] = useState(initialState)
7 | let decrement = () => setCount(count - 1)
8 | let increment = () => setCount(count + 1)
9 | return { count, decrement, increment }
10 | }
11 |
12 | let Counter = createContainer(useCounter)
13 |
14 | function CounterDisplay() {
15 | let counter = Counter.useContainer()
16 | return (
17 |