mirror of
https://github.com/littensy/charm.git
synced 2026-10-20 17:51:37 +00:00
docs: adjust ordering by importance
This commit is contained in:
parent
fe7064acee
commit
787084ba59
1 changed files with 156 additions and 156 deletions
310
README.md
310
README.md
|
|
@ -44,17 +44,17 @@ Charm is a state management library that brings the [fine-grained reactivity](ht
|
|||
- [`computed(getter)`](#computedgetter)
|
||||
- [`effect(callback)`](#effectcallback)
|
||||
- [Nested effects](#nested-effects)
|
||||
- [`reactive(initialValue)`](#reactiveinitialvalue)
|
||||
- [`untracked(callback)`](#untrackedcallback)
|
||||
- [`peek(callback)`](#peekcallback)
|
||||
- [`batch(callback)`](#batchcallback)
|
||||
- [`effectScope(callback)`](#effectscopecallback)
|
||||
- [`listen(getter, callback)`](#listengetter-callback)
|
||||
- [Getter functions](#getter-functions)
|
||||
- [`subscribe(getter, callback)`](#subscribegetter-callback)
|
||||
- [`observe(getter, callback)`](#observegetter-callback)
|
||||
- [`subscribe(getter, callback)`](#subscribegetter-callback)
|
||||
- [`untracked(callback)`](#untrackedcallback)
|
||||
- [`peek(callback)`](#peekcallback)
|
||||
- [`batch(callback)`](#batchcallback)
|
||||
- [`mapped(getter, mapper)`](#mappedgetter-mapper)
|
||||
- [`onCleanup(callback, failSilently?)`](#oncleanupcallback-failsilently)
|
||||
- [`reactive(initialValue)`](#reactiveinitialvalue)
|
||||
- [`atom(initialValue, equals?)`](#atominitialvalue-equals)
|
||||
- [`recursive()`](#recursive)
|
||||
- [`trigger(callback)`](#triggercallback)
|
||||
|
|
@ -290,41 +290,129 @@ Computed signals are updated lazily, meaning the getter will only re-execute onc
|
|||
|
||||
---
|
||||
|
||||
### `reactive(initialValue)`
|
||||
### `effectScope(callback)`
|
||||
|
||||
By default, Charm's signals are _shallowly reactive_, meaning only the value itself is reactive, and table properties are not tracked. Signal values must also be **immutable** in order to track changes in tables.
|
||||
|
||||
You can opt-in to deep reactivity with **reactive proxies**. The `reactive()` function takes a mutable table and wraps it in a reactive proxy. Reading properties through the proxy will perform dependency tracking, and table values will also be wrapped in a reactive proxy.
|
||||
Scopes allow you to dispose multiple effects at once. The `effectScope` function creates a scope that tracks inner effects, so effects created inside the callback clean up when the scope disposes.
|
||||
|
||||
```luau
|
||||
local users, setUsers = reactive({
|
||||
{ name = "John", surname = "Doe" },
|
||||
})
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
local dispose = effectScope(function()
|
||||
effect(function()
|
||||
print(`{users[1].name} {users[1].surname}`)
|
||||
end) -- Output: John Doe
|
||||
print(`Count 1 is {getCounter()}`)
|
||||
end)
|
||||
effect(function()
|
||||
print(`Count 2 is {getCounter()}`)
|
||||
end)
|
||||
end)
|
||||
|
||||
setUsers(function(state)
|
||||
state[1].name = "Jane"
|
||||
state[1].surname = "Smith"
|
||||
table.insert(state, { name = "Steve", surname = "Doe" })
|
||||
end) -- Output: Jane Smith
|
||||
|
||||
-- You can also mutate the reactive proxy directly:
|
||||
users[1].name = "John" -- John Smith
|
||||
setCounter(1) -- Count 1 is 1, Count 2 is 1
|
||||
dispose()
|
||||
setCounter(2) -- No output; effects got disposed
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Because reactive proxies use metatables for reading and writing, functions like `table.insert` will not work on the proxy. Table functions should only be called on the raw table value.
|
||||
Similar to `effect()`, the callback can return a cleanup function that runs when the effect scope is disposed.
|
||||
|
||||
If you need to access the raw table through the reactive proxy, use the `toRaw()` function:
|
||||
---
|
||||
|
||||
### `listen(getter, callback)`
|
||||
|
||||
The `listen` function creates an effect that only subscribes to the signals accessed by `getter`. Signals accessed by the callback will not be subscribed to, avoiding accidental subscriptions when you want to run side effects.
|
||||
|
||||
The callback also receives the previous value, or `nil` when running for the first time.
|
||||
|
||||
```luau
|
||||
local raw = {}
|
||||
local proxy = reactive(raw)
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
print(toRaw(proxy) == raw) -- true
|
||||
-- Output: Count is 0 (was nil)
|
||||
listen(getCounter, function(count, prevCount)
|
||||
print(`Count is {count} (was {prevCount})`)
|
||||
end)
|
||||
|
||||
setCounter(1) -- Count is 1 (was 0)
|
||||
```
|
||||
|
||||
### Getter functions
|
||||
|
||||
Reaction APIs like `listen()`, `subscribe()`, and `observe()` aren't only restricted to signals. You can listen to "getter" functions that call one or more signals, and they will automatically be memoized:
|
||||
|
||||
```luau
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
local function floorCounter()
|
||||
return math.floor(getCounter())
|
||||
end
|
||||
|
||||
-- Output: Floor of count is 0 (was nil)
|
||||
listen(floorCounter, function(count, prevCount)
|
||||
print(`Floor of count is {count} (was {prevCount})`)
|
||||
end)
|
||||
|
||||
setCounter(0.5) -- Doesn't print anything, floor is still 0
|
||||
setCounter(1) -- Floor of count is 1 (was 0)
|
||||
```
|
||||
|
||||
Note that nested effects can be created inside `listen`, and they will clean up automatically when the listener re-runs or gets disposed.
|
||||
|
||||
---
|
||||
|
||||
### `observe(getter, callback)`
|
||||
|
||||
[Observers](https://sleitnick.github.io/RbxObservers/docs/observer-pattern) allow you to track the lifetime of a given state. The `observe` function executes the callback for every unique key added to a table, and disposes the callback when that key is removed.
|
||||
|
||||
```luau
|
||||
local getItems, setItems = signal({ a = 0, b = 0 })
|
||||
|
||||
-- Added a, Added b
|
||||
observe(getItems, function(value, key)
|
||||
print(`Added {key}`)
|
||||
return function()
|
||||
print(`Removed {key}`)
|
||||
end
|
||||
end)
|
||||
|
||||
setItems({ a = 0, c = 0 }) -- Removed b, Added c
|
||||
```
|
||||
|
||||
The callback runs in an effect scope, so effects created in the callback will be disposed when the key is removed:
|
||||
|
||||
```luau
|
||||
local getItems, setItems = signal({ a = 0, b = 0 })
|
||||
|
||||
local dispose = observe(getItems, function(value, key)
|
||||
local getValue = computed(function(prevValue)
|
||||
return getItems()[key] or prevValue
|
||||
end)
|
||||
|
||||
effect(function()
|
||||
local value = getValue()
|
||||
print(`{key} = {value}`)
|
||||
return function()
|
||||
print(`Cleanup {key} = {value}`)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
setItems({ a = 1, b = 0 }) -- Cleanup a = 0, a = 1
|
||||
setItems({ a = 1 }) -- Cleanup b = 0
|
||||
dispose() -- Cleanup a = 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `subscribe(getter, callback)`
|
||||
|
||||
The `subscribe` function is identical to `listen()`, but the effect callback skips the initial run. The callback only runs when the value returned by the getter function changes.
|
||||
|
||||
```luau
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
-- Does not output anything initially
|
||||
subscribe(getCounter, function(count, prevCount)
|
||||
print(`Count is {count} (was {prevCount})`)
|
||||
end)
|
||||
|
||||
setCounter(1) -- Count is 1 (was 0)
|
||||
```
|
||||
|
||||
---
|
||||
|
|
@ -404,133 +492,6 @@ end)
|
|||
|
||||
---
|
||||
|
||||
### `effectScope(callback)`
|
||||
|
||||
Scopes allow you to dispose multiple effects at once. The `effectScope` function creates an effect with no dependencies, and effects created inside the callback clean up when the effect scope disposes.
|
||||
|
||||
```luau
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
local dispose = effectScope(function()
|
||||
effect(function()
|
||||
print(`Count 1 is {getCounter()}`)
|
||||
end)
|
||||
effect(function()
|
||||
print(`Count 2 is {getCounter()}`)
|
||||
end)
|
||||
end)
|
||||
|
||||
setCounter(1) -- Count 1 is 1, Count 2 is 1
|
||||
dispose()
|
||||
setCounter(2) -- No output; effects got disposed
|
||||
```
|
||||
|
||||
Similar to `effect()`, the callback can return a cleanup function that runs when the effect scope is disposed.
|
||||
|
||||
---
|
||||
|
||||
### `listen(getter, callback)`
|
||||
|
||||
The `listen` function creates an effect that only subscribes to the signals accessed by `getter`. Signals accessed by the callback will not be subscribed to, avoiding accidental subscriptions when you want to run side effects.
|
||||
|
||||
The callback also receives the previous value, or `nil` when running for the first time.
|
||||
|
||||
```luau
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
-- Output: Count is 0 (was nil)
|
||||
listen(getCounter, function(count, prevCount)
|
||||
print(`Count is {count} (was {prevCount})`)
|
||||
end)
|
||||
|
||||
setCounter(1) -- Count is 1 (was 0)
|
||||
```
|
||||
|
||||
### Getter functions
|
||||
|
||||
Reaction APIs like `listen()`, `subscribe()`, and `observe()` aren't only restricted to signals. You can listen to "getter" functions that call one or more signals, and they will automatically be memoized:
|
||||
|
||||
```luau
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
local function floorCounter()
|
||||
return math.floor(getCounter())
|
||||
end
|
||||
|
||||
-- Output: Floor of count is 0 (was nil)
|
||||
listen(floorCounter, function(count, prevCount)
|
||||
print(`Floor of count is {count} (was {prevCount})`)
|
||||
end)
|
||||
|
||||
setCounter(0.5) -- Doesn't print anything, floor is still 0
|
||||
setCounter(1) -- Floor of count is 1 (was 0)
|
||||
```
|
||||
|
||||
Note that nested effects can be created inside `listen`, and they will clean up automatically when the listener re-runs or gets disposed.
|
||||
|
||||
---
|
||||
|
||||
### `subscribe(getter, callback)`
|
||||
|
||||
The `subscribe` function is identical to `listen()`, but the effect callback skips the initial run. The callback only runs when the value returned by the getter function changes.
|
||||
|
||||
```luau
|
||||
local getCounter, setCounter = signal(0)
|
||||
|
||||
-- Does not output anything initially
|
||||
subscribe(getCounter, function(count, prevCount)
|
||||
print(`Count is {count} (was {prevCount})`)
|
||||
end)
|
||||
|
||||
setCounter(1) -- Count is 1 (was 0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `observe(getter, callback)`
|
||||
|
||||
[Observers](https://sleitnick.github.io/RbxObservers/docs/observer-pattern) allow you to track the lifetime of a given state. The `observe` function executes the callback for every unique key added to a table, and disposes the callback when that key is removed.
|
||||
|
||||
```luau
|
||||
local getItems, setItems = signal({ a = 0, b = 0 })
|
||||
|
||||
-- Added a, Added b
|
||||
observe(getItems, function(value, key)
|
||||
print(`Added {key}`)
|
||||
return function()
|
||||
print(`Removed {key}`)
|
||||
end
|
||||
end)
|
||||
|
||||
setItems({ a = 0, c = 0 }) -- Removed b, Added c
|
||||
```
|
||||
|
||||
The callback runs in an effect scope, so effects created in the callback will be disposed when the key is removed:
|
||||
|
||||
```luau
|
||||
local getItems, setItems = signal({ a = 0, b = 0 })
|
||||
|
||||
local dispose = observe(getItems, function(value, key)
|
||||
local getValue = computed(function(prevValue)
|
||||
return getItems()[key] or prevValue
|
||||
end)
|
||||
|
||||
effect(function()
|
||||
local value = getValue()
|
||||
print(`{key} = {value}`)
|
||||
return function()
|
||||
print(`Cleanup {key} = {value}`)
|
||||
end
|
||||
end)
|
||||
end)
|
||||
|
||||
setItems({ a = 1, b = 0 }) -- Cleanup a = 0, a = 1
|
||||
setItems({ a = 1 }) -- Cleanup b = 0
|
||||
dispose() -- Cleanup a = 1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `mapped(getter, mapper)`
|
||||
|
||||
The `mapped` function iterates over every key in a table and uses the mapper to assign them to a new key and value. The result is returned as a read-only signal containing the new keys and values. When a key's value changes, or a new key is added to the table, the mapper is called for that key and its current value.
|
||||
|
|
@ -579,6 +540,45 @@ dispose() -- Cleaned up
|
|||
|
||||
---
|
||||
|
||||
### `reactive(initialValue)`
|
||||
|
||||
By default, Charm's signals are _shallowly reactive_, meaning only the value itself is reactive, and table properties are not tracked. Signal values must also be **immutable** in order to track changes in tables.
|
||||
|
||||
You can opt-in to deep reactivity with **reactive proxies**. The `reactive()` function takes a mutable table and wraps it in a reactive proxy. Reading properties through the proxy will perform dependency tracking, and table values will also be wrapped in a reactive proxy.
|
||||
|
||||
```luau
|
||||
local users, setUsers = reactive({
|
||||
{ name = "John", surname = "Doe" },
|
||||
})
|
||||
|
||||
effect(function()
|
||||
print(`{users[1].name} {users[1].surname}`)
|
||||
end) -- Output: John Doe
|
||||
|
||||
setUsers(function(state)
|
||||
state[1].name = "Jane"
|
||||
state[1].surname = "Smith"
|
||||
table.insert(state, { name = "Steve", surname = "Doe" })
|
||||
end) -- Output: Jane Smith
|
||||
|
||||
-- You can also mutate the reactive proxy directly:
|
||||
users[1].name = "John" -- John Smith
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> Because reactive proxies use metatables for reading and writing, functions like `table.insert` will not work on the proxy. Table functions should only be called on the raw table value.
|
||||
|
||||
If you need to access the raw table through the reactive proxy, use the `toRaw()` function:
|
||||
|
||||
```luau
|
||||
local raw = {}
|
||||
local proxy = reactive(raw)
|
||||
|
||||
print(toRaw(proxy) == raw) -- true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `atom(initialValue, equals?)`
|
||||
|
||||
The `atom` function creates a new reactive signal and returns a single function that acts as both a getter and setter.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue