ccstate 4.0.0 â 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -29
- package/core/index.cjs +577 -788
- package/core/index.cjs.map +1 -0
- package/core/index.d.cts +7 -3
- package/core/index.d.cts.map +1 -0
- package/core/index.d.ts +7 -3
- package/core/index.d.ts.map +1 -0
- package/core/index.js +577 -788
- package/core/index.js.map +1 -0
- package/debug/index.cjs +584 -796
- package/debug/index.cjs.map +1 -0
- package/debug/index.d.cts +7 -3
- package/debug/index.d.cts.map +1 -0
- package/debug/index.d.ts +7 -3
- package/debug/index.d.ts.map +1 -0
- package/debug/index.js +584 -796
- package/debug/index.js.map +1 -0
- package/index.cjs +592 -801
- package/index.cjs.map +1 -0
- package/index.d.cts +7 -3
- package/index.d.cts.map +1 -0
- package/index.d.ts +7 -3
- package/index.d.ts.map +1 -0
- package/index.js +592 -801
- package/index.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,17 +9,16 @@
|
|
|
9
9
|
[](https://github.com/e7h4n/ccstate/actions/workflows/ci.yaml)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
11
|
|
|
12
|
-
CCState is a
|
|
12
|
+
CCState is a modern signals-based state management library that elegantly implements async computed and read-write capability isolation based on signal features, making it suitable for medium to large web applications.
|
|
13
13
|
|
|
14
|
-
The name of CCState comes from three basic
|
|
14
|
+
The name of CCState comes from three basic types: `Computed`, `Command`, and `State`.
|
|
15
15
|
|
|
16
16
|
## Quick Features
|
|
17
17
|
|
|
18
|
-
-
|
|
18
|
+
- âïļ Intuitive async computation: using async/await or try/catch to process async flow as regular JavaScript without any additional concept
|
|
19
|
+
- ðŊ Simple & Intuitive: Crystal-clear API design with just three types and two operations
|
|
19
20
|
- â
Rock-solid Reliability: Comprehensive test coverage reaching 100% branch coverage
|
|
20
|
-
- ðŠķ Ultra-lightweight: Zero dependencies, only 500 lines of core code
|
|
21
21
|
- ðĄ Framework Agnostic: Seamlessly works with [React](docs/react.md), [Vue](docs/vue.md), [Solid.js](docs/solid.md), [Vanilla](docs/vanilla.md), or any UI framework
|
|
22
|
-
- ð Blazing Fast: Optimized performance from day one, 2x-7x faster than Jotai across scenarios
|
|
23
22
|
|
|
24
23
|
## Getting Started
|
|
25
24
|
|
|
@@ -36,16 +35,18 @@ pnpm add ccstate
|
|
|
36
35
|
yarn add ccstate
|
|
37
36
|
```
|
|
38
37
|
|
|
39
|
-
### Create
|
|
38
|
+
### Create Signals
|
|
40
39
|
|
|
41
40
|
Use `state` to store a simple value unit, and use `computed` to create a derived computation logic:
|
|
42
41
|
|
|
43
42
|
```ts
|
|
44
|
-
//
|
|
43
|
+
// signals.js
|
|
45
44
|
import { state, computed } from 'ccstate';
|
|
46
45
|
|
|
46
|
+
// a simple value unit which supports read/write
|
|
47
47
|
export const userId$ = state('');
|
|
48
48
|
|
|
49
|
+
// intuitive async computation logic
|
|
49
50
|
export const user$ = computed(async (get) => {
|
|
50
51
|
const userId = get(userId$);
|
|
51
52
|
if (!userId) return null;
|
|
@@ -55,14 +56,14 @@ export const user$ = computed(async (get) => {
|
|
|
55
56
|
});
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
### Use
|
|
59
|
+
### Use signals in React
|
|
59
60
|
|
|
60
|
-
Use `useGet` and `useSet` hooks in React to get/set
|
|
61
|
+
Use `useGet` and `useSet` hooks in React to get/set signals, and use `useResolved` to get Promise value.
|
|
61
62
|
|
|
62
63
|
```jsx
|
|
63
|
-
// App.
|
|
64
|
+
// App.jsx
|
|
64
65
|
import { useGet, useSet, useResolved } from 'ccstate-react';
|
|
65
|
-
import { userId$, user$ } from './
|
|
66
|
+
import { userId$, user$ } from './signals';
|
|
66
67
|
|
|
67
68
|
export default function App() {
|
|
68
69
|
const userId = useGet(userId$);
|
|
@@ -84,18 +85,9 @@ export default function App() {
|
|
|
84
85
|
</div>
|
|
85
86
|
);
|
|
86
87
|
}
|
|
87
|
-
|
|
88
|
-
// main.jsx
|
|
89
|
-
import { createRoot } from 'react-dom/client';
|
|
90
|
-
import App from './App';
|
|
91
|
-
|
|
92
|
-
const rootElement = document.getElementById('root');
|
|
93
|
-
const root = createRoot(rootElement);
|
|
94
|
-
|
|
95
|
-
root.render(<App />);
|
|
96
88
|
```
|
|
97
89
|
|
|
98
|
-
That's it! [Click here to see the full example](https://
|
|
90
|
+
That's it! [Click here to see the full example](https://stackblitz.com/edit/vitejs-vite-rert8c7a?file=src%2FApp.tsx).
|
|
99
91
|
|
|
100
92
|
Through these examples, you should have understood the basic usage of CCState. Next, you can read to learn about CCState's core APIs.
|
|
101
93
|
|
|
@@ -121,13 +113,14 @@ const user$ = state<({
|
|
|
121
113
|
name: 'e7h4n',
|
|
122
114
|
avatar: 'https://avatars.githubusercontent.com/u/813596',
|
|
123
115
|
} | undefined>(undefined);
|
|
116
|
+
|
|
124
117
|
store.set({
|
|
125
118
|
name: 'yc-kanyun',
|
|
126
119
|
avatar: 'https://avatars.githubusercontent.com/u/168416598'
|
|
127
120
|
});
|
|
128
121
|
```
|
|
129
122
|
|
|
130
|
-
These examples should be very easy to understand. You might notice a detail in the examples: all variables returned by `state` have a `$` suffix. This is a naming convention used to distinguish an CCState
|
|
123
|
+
These examples should be very easy to understand. You might notice a detail in the examples: all variables returned by `state` have a `$` suffix. This is a naming convention used to distinguish an CCState signal type from other regular types. CCState signal types must be accessed through the store's get/set methods, and since it's common to convert an CCState signal type to a regular type using get, the `$` suffix helps avoid naming conflicts.
|
|
131
124
|
|
|
132
125
|
### Store
|
|
133
126
|
|
|
@@ -143,7 +136,7 @@ const otherStore = createStore(); // another new Map()
|
|
|
143
136
|
otherStore.get(count$); // anotherMap[$count] ?? $count.init, returns 0
|
|
144
137
|
```
|
|
145
138
|
|
|
146
|
-
This should be easy to understand. If `Store` only needed to support `State` types, a simple Map would be sufficient. However, CCState needs to support two additional
|
|
139
|
+
This should be easy to understand. If `Store` only needed to support `State` types, a simple Map would be sufficient. However, CCState needs to support two additional signal types. Next, let's introduce `Computed`, CCState's reactive computation unit.
|
|
147
140
|
|
|
148
141
|
### Computed
|
|
149
142
|
|
|
@@ -168,13 +161,13 @@ Does this example seem less intuitive than `State`? Here's a mental model that m
|
|
|
168
161
|
- `computed(fn)` returns an object `{read: fn}`, which is assigned to `user$`
|
|
169
162
|
- When `store.get(user$)` encounters an object which has a read function, it calls that function: `user$.read(store.get)`
|
|
170
163
|
|
|
171
|
-
This way, `Computed` receives a get accessor that can access other
|
|
164
|
+
This way, `Computed` receives a get accessor that can access other signal in the store. This get accessor is similar to `store.get` and can be used to read both `State` and `Computed`. The reason CCState specifically passes a get method to `Computed`, rather than allowing direct access to the store within `Computed`, is to shield the logic within `Computed` from other store methods like `store.set`. The key characteristic of `Computed` is that it can only read states from the store but cannot modify them. In other words, `Computed` is side-effect free.
|
|
172
165
|
|
|
173
166
|
In most cases, side-effect free computation logic is extremely useful. They can be executed any number of times and have few requirements regarding execution timing. `Computed` is one of the most powerful features in CCState, and you should try to write your logic as `Computed` whenever possible, unless you need to perform set operations on the `Store`.
|
|
174
167
|
|
|
175
168
|
### Command
|
|
176
169
|
|
|
177
|
-
`Command` is CCState's logic unit for organizing side effects. It has both `set` and `get` accessors from the store, allowing it to not only read other
|
|
170
|
+
`Command` is CCState's logic unit for organizing side effects. It has both `set` and `get` accessors from the store, allowing it to not only read other signal types but also modify `State` or call other `Command`.
|
|
178
171
|
|
|
179
172
|
```typescript
|
|
180
173
|
import { command, createStore } from 'ccstate';
|
|
@@ -332,9 +325,9 @@ While Jotai is a great state management solution that has benefited the Motiff p
|
|
|
332
325
|
- Should reduce reactive capabilities, especially the `onMount` capability - the framework shouldn't provide this ability
|
|
333
326
|
- Some implicit magic operations, especially Promise wrapping, make the application execution process less transparent
|
|
334
327
|
|
|
335
|
-
To address these issues, I got an idea: "What concepts in Jotai are essential? And which concepts create mental overhead for developers?". Rather than just discussing it theoretically, I decided to try implementing it myself. So I created CCState to express my thoughts on state management. Before detailing the differences from Jotai, we need to understand CCState's
|
|
328
|
+
To address these issues, I got an idea: "What concepts in Jotai are essential? And which concepts create mental overhead for developers?". Rather than just discussing it theoretically, I decided to try implementing it myself. So I created CCState to express my thoughts on state management. Before detailing the differences from Jotai, we need to understand CCState's signal types and subscription system.
|
|
336
329
|
|
|
337
|
-
### More semantic
|
|
330
|
+
### More semantic atom types
|
|
338
331
|
|
|
339
332
|
Like Jotai, CCState is also an Atom State solution. However, unlike Jotai, CCState doesn't expose Raw Atom, instead dividing Atoms into three types:
|
|
340
333
|
|
|
@@ -355,7 +348,7 @@ export const userIdChange$ = command(({ get, set }) => {
|
|
|
355
348
|
});
|
|
356
349
|
|
|
357
350
|
// ...
|
|
358
|
-
import { userId$, userIdChange$ } from './
|
|
351
|
+
import { userId$, userIdChange$ } from './signals';
|
|
359
352
|
|
|
360
353
|
function setupPage() {
|
|
361
354
|
const store = createStore();
|
|
@@ -436,7 +429,7 @@ export function App() {
|
|
|
436
429
|
When designing CCState, we wanted the trigger points for value changes to be completely detached from React's Mount/Unmount lifecycle and completely decoupled from React's rendering behavior.
|
|
437
430
|
|
|
438
431
|
```jsx
|
|
439
|
-
//
|
|
432
|
+
// signals.js
|
|
440
433
|
export const userId$ = state(0)
|
|
441
434
|
export const init$ = command(({set}) => {
|
|
442
435
|
const userId = // ... parse userId from location search
|