mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
Update docs
This commit is contained in:
parent
94add5d452
commit
a3cc2dfbda
30 changed files with 850 additions and 770 deletions
|
|
@ -34,7 +34,7 @@ export default withMermaid({
|
||||||
items: [
|
items: [
|
||||||
{ text: "Reactivity: Core", link: "/api/reactivity-core" },
|
{ text: "Reactivity: Core", link: "/api/reactivity-core" },
|
||||||
{ text: "Reactivity: Utility", link: "/api/reactivity-utility" },
|
{ text: "Reactivity: Utility", link: "/api/reactivity-utility" },
|
||||||
{ text: "Reactivity: Control Flow", link: "/api/reactivity-flow" },
|
{ text: "Reactivity: Dynamic Scoping", link: "/api/reactivity-dynamic" },
|
||||||
{ text: "Element Creation", link: "/api/creation" },
|
{ text: "Element Creation", link: "/api/creation" },
|
||||||
{ text: "Animation", link: "/api/animation" },
|
{ text: "Animation", link: "/api/animation" },
|
||||||
{ text: "Strict Mode", link: "/api/strict-mode" },
|
{ text: "Strict Mode", link: "/api/strict-mode" },
|
||||||
|
|
@ -52,20 +52,25 @@ export default withMermaid({
|
||||||
{ text: "Sources", link: "/tut/crash-course/4-source" },
|
{ text: "Sources", link: "/tut/crash-course/4-source" },
|
||||||
{ text: "Effects", link: "/tut/crash-course/5-effect" },
|
{ text: "Effects", link: "/tut/crash-course/5-effect" },
|
||||||
{ text: "Scopes", link: "/tut/crash-course/6-scope" },
|
{ text: "Scopes", link: "/tut/crash-course/6-scope" },
|
||||||
{ text: "Stateful Components", link: "/tut/crash-course/7-stateful-component" },
|
{ text: "Reactive Components", link: "/tut/crash-course/7-reactive-component" },
|
||||||
{ text: "Implicit Effects", link: "/tut/crash-course/8-implicit-effect" },
|
{ text: "Implicit Effects", link: "/tut/crash-course/8-implicit-effect" },
|
||||||
{ text: "Derived Sources", link: "/tut/crash-course/9-derived-source" },
|
{ text: "Derived Sources", link: "/tut/crash-course/9-derived-source" },
|
||||||
{ text: "Cleanup", link: "/tut/crash-course/10-cleanup" },
|
{ text: "Cleanup", link: "/tut/crash-course/10-cleanup" },
|
||||||
{ text: "Control Flow", link: "/tut/crash-course/11-control-flow" },
|
{ text: "Dynamic Scoping", link: "/tut/crash-course/11-dynamic-scope" },
|
||||||
{ text: "Actions", link: "/tut/crash-course/12-actions" },
|
{ text: "Actions", link: "/tut/crash-course/12-actions" },
|
||||||
{ text: "Strict Mode", link: "/tut/crash-course/13-strict-mode" },
|
{ text: "Strict Mode", link: "/tut/crash-course/13-strict-mode" },
|
||||||
{ text: "Concepts Summary", link: "/tut/crash-course/14-concepts" }
|
{ text: "Concepts Summary", link: "/tut/crash-course/14-concepts" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Advanced Reactivity",
|
text: "Dynamic Scoping",
|
||||||
|
items: [
|
||||||
|
{ text: "Custom Scopes", link: "/tut/dynamic-scoping/custom"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Design Patterns",
|
||||||
items: [
|
items: [
|
||||||
{ text: "Nested Scopes", link: "/tut/advanced/nested-scoping.md"}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -17,3 +17,9 @@
|
||||||
margin-top: auto !important;
|
margin-top: auto !important;
|
||||||
margin-bottom: auto !important;
|
margin-bottom: auto !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.VPBadge a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// .vitepress/theme/index.js
|
// .vitepress/theme/index.js
|
||||||
import DefaultTheme from 'vitepress/theme'
|
import DefaultTheme from 'vitepress/theme'
|
||||||
import './vars.css'
|
import './vars.css'
|
||||||
import './home.css'
|
import './index.css'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: DefaultTheme,
|
extends: DefaultTheme,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,11 @@
|
||||||
--vp-c-divider: #dfe2e6;
|
--vp-c-divider: #dfe2e6;
|
||||||
--vp-c-gutter: #dfe2e6;
|
--vp-c-gutter: #dfe2e6;
|
||||||
--vp-plugin-tabs-tab-bg: var(--vp-c-bg);
|
--vp-plugin-tabs-tab-bg: var(--vp-c-bg);
|
||||||
|
|
||||||
|
--vp-badge-info-bg: #122d26;
|
||||||
|
--vp-badge-info-text: #6bdbbd;
|
||||||
|
--vp-badge-tip-bg: #132741;
|
||||||
|
--vp-badge-tip-text: #70abfa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|
@ -44,3 +49,4 @@
|
||||||
--vp-c-divider: #1d273c;
|
--vp-c-divider: #1d273c;
|
||||||
--vp-c-gutter: #181d27;
|
--vp-c-gutter: #181d27;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Animation API
|
# Animation
|
||||||
|
|
||||||
## spring()
|
## spring() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
|
||||||
|
|
||||||
Returns a new source with a value always moving torwards the input source value.
|
Returns a new source with a value always moving torwards the input source value.
|
||||||
|
|
||||||
|
|
@ -18,8 +18,7 @@ Returns a new source with a value always moving torwards the input source value.
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
An effect is created to update the new source every frame based on the input
|
Creates a reactive scope internally to detect source updates.
|
||||||
source value.
|
|
||||||
|
|
||||||
The movement is physically simulated according to a
|
The movement is physically simulated according to a
|
||||||
[spring](https://en.wikipedia.org/wiki/Simple_harmonic_motion).
|
[spring](https://en.wikipedia.org/wiki/Simple_harmonic_motion).
|
||||||
|
|
@ -39,3 +38,7 @@ Returns a new source with a value always moving torwards the input source value.
|
||||||
You can change when the solver runs by calling `vide.step(dt)`, which will
|
You can change when the solver runs by calling `vide.step(dt)`, which will
|
||||||
advance the simulation time by `dt` seconds and automatically stop the
|
advance the simulation time by `dt` seconds and automatically stop the
|
||||||
solver running in heartbeat.
|
solver running in heartbeat.
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
Large periods or damping ratios can break the spring.
|
||||||
|
:::
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,4 @@
|
||||||
# Element Creation API
|
# Element Creation
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## mount()
|
|
||||||
|
|
||||||
Runs a function in a new stable scope and optionally applies its result to a
|
|
||||||
target instance.
|
|
||||||
|
|
||||||
- **Type**
|
|
||||||
|
|
||||||
```luau
|
|
||||||
function mount<T>(component: () -> T, target: Instance?): () -> ()
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Details**
|
|
||||||
|
|
||||||
The result of the function is applied to a target in the same way
|
|
||||||
properties are using `create()`.
|
|
||||||
|
|
||||||
The function is ran in a new stable scope, just like
|
|
||||||
[root()](reactivity-core.md#root).
|
|
||||||
|
|
||||||
Returns a function that when called will destroy the stable scope.
|
|
||||||
|
|
||||||
- **Example**
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local function App()
|
|
||||||
return create "ScreenGui" {
|
|
||||||
create "TextLabel" { Text = "Vide" }
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
mount(App, game.StarterGui)
|
|
||||||
```
|
|
||||||
|
|
||||||
## create()
|
## create()
|
||||||
|
|
||||||
|
|
@ -45,7 +10,7 @@ Creates a new UI element, applying any given properties.
|
||||||
function create(class: string): (Properties) -> Instance
|
function create(class: string): (Properties) -> Instance
|
||||||
function create(instance: Instance): (Properties) -> Instance
|
function create(instance: Instance): (Properties) -> Instance
|
||||||
|
|
||||||
type Properties = Map<string|number, any>
|
type Properties = Map<string|number, unknown>
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
@ -77,42 +42,22 @@ Creates a new UI element, applying any given properties.
|
||||||
Basic element creation.
|
Basic element creation.
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local frame = create "Frame" {
|
local frame = create "TextButton" {
|
||||||
Name = "NewFrame",
|
Name = "Button",
|
||||||
Position = UDim2.fromScale(1, 0)
|
Size = UDim2.fromOffset(200, 160),
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
A component using property nesting.
|
Activated = function()
|
||||||
|
print "clicked"
|
||||||
|
end,
|
||||||
|
|
||||||
```luau
|
create "UICorner" {}
|
||||||
type Layout = {
|
|
||||||
Layout = {
|
|
||||||
Position: UDim2?,
|
|
||||||
Size: UDim2?,
|
|
||||||
AnchorPoint: Vector2?
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
type Children = {
|
|
||||||
Children = Array<Instance>
|
|
||||||
}
|
|
||||||
|
|
||||||
function Background(props: Layout & Children & {
|
|
||||||
Color: Color3
|
|
||||||
})
|
|
||||||
return create "Frame" {
|
|
||||||
BackgroundColor3 = props.Color,
|
|
||||||
props.Layout,
|
|
||||||
props.Children
|
|
||||||
}
|
|
||||||
end
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## action()
|
## action()
|
||||||
|
|
||||||
Creates a callback that can be passed to `create()` to invoke custom actions on
|
Creates a special object that can be passed to `create()` to invoke custom
|
||||||
instances.
|
actions on instances.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
|
|
@ -122,27 +67,27 @@ instances.
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
When passed to `create()`, the given callback is called with the instance
|
When passed to `create()`, the function is called with the instance being
|
||||||
being created as the only argument. Actions take precedence over property
|
created as the only argument. Actions take precedence over property and
|
||||||
and child assignments.
|
child assignments.
|
||||||
|
|
||||||
A priority can be optionally specified to ensure certain actions run after
|
A priority can be optionally specified to ensure certain actions run after
|
||||||
other actions. Higher priority numbers are ran after lower priority numbers.
|
other actions. Lower priority values are ran first.
|
||||||
|
|
||||||
- **Example**
|
- **Example**
|
||||||
|
|
||||||
An action to listen to changed properties:
|
An action to listen to changed properties:
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local function changed(property: string, callback: (new) -> ())
|
local function changed(property: string, fn: (new) -> ())
|
||||||
return action(function(instance)
|
return action(function(instance)
|
||||||
local con - instance:GetPropertyChangedSignal(property):Connect(function()
|
local cn = instance:GetPropertyChangedSignal(property):Connect(function()
|
||||||
callback(instance[property])
|
fn(instance[property])
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- disconnect on reactive scope destruction to allow gc of instance
|
-- disconnect on scope destruction to allow gc of instance
|
||||||
cleanup(function()
|
cleanup(function()
|
||||||
con:Disconnect()
|
cn:Disconnect()
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
@ -150,7 +95,7 @@ instances.
|
||||||
local output = source ""
|
local output = source ""
|
||||||
|
|
||||||
create "TextBox" {
|
create "TextBox" {
|
||||||
-- will update the `output` source anytime the text property is changed
|
-- will update the output source anytime the text property is changed
|
||||||
changed("Text", output)
|
changed("Text", output)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
@ -162,15 +107,46 @@ A wrapper for `action()` to listen for property changes.
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function changed(property: string, callback: (...unknown) -> ()): Action
|
function changed(property: string, fn: (unknown) -> ()): Action
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
Will run the given callback any time the property is changed, as well as
|
Will run the given function immediately and whenever the property updates.
|
||||||
when the action is initially run.
|
|
||||||
|
|
||||||
The changed connection is disconnected when the scope the action is ran in
|
The function is called with the updated property value.
|
||||||
is destroyed.
|
|
||||||
|
|
||||||
Runs with an action priority of 1.
|
Runs with an action priority of 1.
|
||||||
|
|
||||||
|
## mount() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
|
||||||
|
|
||||||
|
Runs a function in a new stable scope and optionally applies its result to a
|
||||||
|
target instance.
|
||||||
|
|
||||||
|
- **Type**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function mount<T>(component: () -> T, target: Instance?): () -> ()
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Details**
|
||||||
|
|
||||||
|
This is a utility for `root()` when parenting a component to an existing
|
||||||
|
instance.
|
||||||
|
|
||||||
|
The result of the function is applied to a target in the same way
|
||||||
|
properties are using `create()`.
|
||||||
|
|
||||||
|
Returns a function that when called will destroy the stable scope.
|
||||||
|
|
||||||
|
- **Example**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local function App()
|
||||||
|
return create "ScreenGui" {
|
||||||
|
create "TextLabel" { Text = "Vide" }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local destroy = mount(App, game.StarterGui)
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,40 @@
|
||||||
# Reactivity API: Core
|
# Reactivity: Core
|
||||||
|
|
||||||
<br/>
|
## Scopes
|
||||||
|
|
||||||
|
Vide code can run in one of two scopes: <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge> or <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>.
|
||||||
|
|
||||||
|
- Reactive scopes rerun if a source read within updates.
|
||||||
|
- Stable scopes never rerun.
|
||||||
|
- Reactive scopes cannot be created directly within another reactive scope.
|
||||||
|
- When a scope is destroyed, all scopes created within are also destroyed.
|
||||||
|
|
||||||
|
Different functions in Vide's API will run code in different scopes.
|
||||||
|
|
||||||
:::warning
|
:::warning
|
||||||
Yielding is not allowed in any stable or reactive scope. Strict mode will check
|
Yielding is not allowed in any stable or reactive scope. Strict mode will check
|
||||||
for this.
|
for this.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
## root()
|
## root() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
|
||||||
|
|
||||||
Creates and runs a function in a new stable scope.
|
Runs a function in a new stable scope.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function root<T...>(fn: (() -> ()) -> T...): (() -> (), T...)
|
function root<T...>(fn: (Destructor) -> T...): (Destructor, T...)
|
||||||
|
|
||||||
|
type Destructor = () -> ()
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
Returns a function to destroy the root scope. Also passes this function as
|
Returns a destructor and any values returned by the callback.
|
||||||
the first argument into its callback.
|
|
||||||
|
|
||||||
All values returned by the callback are also returned following the destructor.
|
|
||||||
|
|
||||||
## source()
|
## source()
|
||||||
|
|
||||||
Creates a new source with the given value.
|
Creates a new source.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
|
|
@ -40,71 +48,64 @@ Creates a new source with the given value.
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
Calling the returned source with no argument will return its stored value,
|
Call the returned source with no argument to read its value.
|
||||||
calling with an argument will set a new value.
|
Call the returned source with an argument to set its value.
|
||||||
|
|
||||||
- **Example**
|
- **Example**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local count = source(0)
|
local count = source(0)
|
||||||
|
print(count())-- 0
|
||||||
count() -- 0
|
count(count() + 1)
|
||||||
|
print(count()) -- 1
|
||||||
count(count() + 1) -- 1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## effect()
|
## effect() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
|
||||||
|
|
||||||
Runs a side-effect in a new reactive scope on source update.
|
Runs a function in a new reactive scope.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function effect(callback: () -> ())
|
function effect(fn: () -> ())
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
Any time a source referenced in the callback is updated, the callback will
|
The function is ran once immediately.
|
||||||
be reran.
|
|
||||||
|
|
||||||
The callback is ran once immediately.
|
|
||||||
|
|
||||||
- **Example**
|
- **Example**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local num = source(1)
|
local count = source(1)
|
||||||
|
|
||||||
effect(function()
|
effect(function()
|
||||||
print(num())
|
print(count())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- prints 1
|
-- prints 1
|
||||||
|
|
||||||
num(num() + 1)
|
count(2)
|
||||||
|
|
||||||
-- prints 2
|
-- prints 2
|
||||||
```
|
```
|
||||||
|
|
||||||
## derive()
|
## derive() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
|
||||||
|
|
||||||
Derives a new source in a new reactive scope from existing sources.
|
Runs a function in a new reactive scope to compute a value for new source.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function derive<T>(source: () -> T): () -> T
|
function derive<T>(fn: () -> T): () -> T
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
The derived source will have its value recalculated when any source source
|
Anytime the reactive scope reruns, the output source value is set to what is
|
||||||
it derives from is updated.
|
returned.
|
||||||
|
|
||||||
Anytime its value is recalculated it is also cached, subsequent calls will
|
The function is ran once immediately.
|
||||||
retun this cached value until it recalculates again.
|
|
||||||
|
|
||||||
The callback is ran once immediately.
|
|
||||||
|
|
||||||
- **Example**
|
- **Example**
|
||||||
|
|
||||||
|
|
@ -112,11 +113,43 @@ Derives a new source in a new reactive scope from existing sources.
|
||||||
local count = source(0)
|
local count = source(0)
|
||||||
local text = derive(function() return `count: {count()}` end)
|
local text = derive(function() return `count: {count()}` end)
|
||||||
|
|
||||||
text() -- "count: 0"
|
print(text()) -- "count: 0"
|
||||||
|
|
||||||
count(1)
|
count(1)
|
||||||
|
|
||||||
text() -- "count: 1"
|
print(text()) -- "count: 1"
|
||||||
```
|
```
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
A `derive()` should be used instead of a pure function when you expect it to
|
||||||
|
be read multiple times between updates, because `derive()` will cache the
|
||||||
|
result to prevent recomputing it on every read.
|
||||||
|
|
||||||
|
::: code-group
|
||||||
|
|
||||||
|
```luau [Pure Function]
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
|
local text = function()
|
||||||
|
print "ran"
|
||||||
|
return `count: {count()}`
|
||||||
|
end
|
||||||
|
|
||||||
|
count(1)
|
||||||
|
print(text()) -- prints "ran" followed by "count: 1"
|
||||||
|
print(text()) -- prints "ran" followed by "count: 1"
|
||||||
|
```
|
||||||
|
|
||||||
|
```luau [Derived Source]
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
|
local text = derive(function() -- [!code highlight]
|
||||||
|
print "ran"
|
||||||
|
return `count: {count()}`
|
||||||
|
end) -- [!code highlight]
|
||||||
|
|
||||||
|
count(1) -- prints "ran"
|
||||||
|
print(text()) -- prints "count: 1"
|
||||||
|
print(text()) -- prints "count: 1"
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
|
||||||
212
docs/api/reactivity-dynamic.md
Normal file
212
docs/api/reactivity-dynamic.md
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
# Reactivity: Dynamic Scoping
|
||||||
|
|
||||||
|
Dynamic scoping is the act of creating and destroying new scopes in response to
|
||||||
|
source updates. Vide provides functions for some common use-cases to do this.
|
||||||
|
|
||||||
|
## show() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
|
||||||
|
|
||||||
|
Shows a component if the source is truthy. Optionally shows a fallback component
|
||||||
|
if the source is falsey.
|
||||||
|
|
||||||
|
- **Type**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function show<T>(source: () -> unknown, component: () -> T): () -> T?
|
||||||
|
function show<T, U>(source: () -> unknown, component: () -> T, fallback: () -> U): () -> T | U
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Details**
|
||||||
|
|
||||||
|
Creates a reactive scope internally to detect source updates.
|
||||||
|
|
||||||
|
The component is run in a stable scope when truthy, otherwise the stable
|
||||||
|
scope is destroyed.
|
||||||
|
|
||||||
|
Returns a source holding an instance of the currently shown component or
|
||||||
|
`nil` if no component is currently shown.
|
||||||
|
|
||||||
|
## switch() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
|
||||||
|
|
||||||
|
Shows one of a set of components depending on a source and a mapping table.
|
||||||
|
|
||||||
|
- **Type**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function switch<K, V>(source: () -> K): (map: Map<K, () -> V>) () -> V?
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Details**
|
||||||
|
|
||||||
|
Creates a reactive scope internally to detect source updates.
|
||||||
|
|
||||||
|
When the source updates, its value is inputted into a map to get a component
|
||||||
|
constructor. This component is then run in a stable scope. The previous
|
||||||
|
stable scope is destroyed.
|
||||||
|
|
||||||
|
Returns a source holding an instance of the currently shown component or
|
||||||
|
`nil` if no component is currently shown.
|
||||||
|
|
||||||
|
- **Example**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local logged = source(false)
|
||||||
|
|
||||||
|
local button = switch(logged) {
|
||||||
|
[true] = function()
|
||||||
|
return Button { Text = "Log out", Toggle = logged }
|
||||||
|
end,
|
||||||
|
|
||||||
|
[false] = function()
|
||||||
|
return Button { Text = "Log in", Toggle = logged }
|
||||||
|
end
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## indexes() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
|
||||||
|
|
||||||
|
Shows a component for each index in a table.
|
||||||
|
|
||||||
|
- **Type**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function indexes<KI, VI, VO>(
|
||||||
|
source: () -> Map<KI, VI>,
|
||||||
|
transform: (value: () -> VI, index: KI) -> VO
|
||||||
|
): Array<VO>
|
||||||
|
|
||||||
|
- **Details**
|
||||||
|
|
||||||
|
Creates a reactive scope internally to detect source updates.
|
||||||
|
|
||||||
|
When the source table updates, a component is generated for each index in
|
||||||
|
the table.
|
||||||
|
|
||||||
|
- For any added index, the `transform` function is run in a new stable
|
||||||
|
scope to produce an instance that is cached.
|
||||||
|
- For any removed index, the stable scope for that index is destroyed.
|
||||||
|
|
||||||
|
The `transform` function is called with:
|
||||||
|
|
||||||
|
1. A *source containing the index's value*.
|
||||||
|
2. The *index itself*.
|
||||||
|
|
||||||
|
Anytime an existing index's value changes, the `transform` function is not
|
||||||
|
rerun, instead, that index's corresponding source is updated with the new
|
||||||
|
value.
|
||||||
|
|
||||||
|
Returns a source holding an array of instances currently shown.
|
||||||
|
|
||||||
|
- **Example**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
type Item = {
|
||||||
|
name: string,
|
||||||
|
icon: number
|
||||||
|
}
|
||||||
|
|
||||||
|
local items = source {} :: () -> Array<Item>
|
||||||
|
|
||||||
|
local displays = indexes(items, function(item, i)
|
||||||
|
return ItemDisplay {
|
||||||
|
Name = function()
|
||||||
|
return i .. ": " .. item().name
|
||||||
|
end,
|
||||||
|
|
||||||
|
Image = function()
|
||||||
|
return "rbxassetid://" .. item().icon
|
||||||
|
end,
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
## values() <Badge type="tip" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">REACTIVE</a></Badge>
|
||||||
|
|
||||||
|
Shows a component for each value in a table.
|
||||||
|
|
||||||
|
- **Type**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
function values<KI, VI, VO>(
|
||||||
|
source: () -> Map<KI, VI>,
|
||||||
|
transform: (value: VI, index: () -> KI) -> VO
|
||||||
|
): Array<VO>
|
||||||
|
|
||||||
|
- **Details**
|
||||||
|
|
||||||
|
Operates with the same idea as `indexes()`, but applied to values instead of
|
||||||
|
indexes.
|
||||||
|
|
||||||
|
Creates a reactive scope internally to detect source updates.
|
||||||
|
|
||||||
|
When the source table updates, a component is generated for each value in
|
||||||
|
the table.
|
||||||
|
|
||||||
|
- For any added value, the `transform` function is run in a new stable scope
|
||||||
|
to produce an instance that is cached.
|
||||||
|
- For any removed value, the stable scope for that value is destroyed.
|
||||||
|
|
||||||
|
The `transform` function is called with:
|
||||||
|
|
||||||
|
1. The *value itself*.
|
||||||
|
2. A *source containing the value's index*.
|
||||||
|
|
||||||
|
Anytime an existing value's index changes, the `transform` function is not
|
||||||
|
rerun, instead, that value's corresponding source is updated with the new
|
||||||
|
index.
|
||||||
|
|
||||||
|
Returns a source holding an array of instances currently shown.
|
||||||
|
|
||||||
|
::: warning
|
||||||
|
Having the same values appear multiple times in the input source table can
|
||||||
|
cause unexpected behavior. Strict mode has checks for this.
|
||||||
|
:::
|
||||||
|
|
||||||
|
- **Example**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
type Item = {
|
||||||
|
name: string,
|
||||||
|
icon: number
|
||||||
|
}
|
||||||
|
|
||||||
|
local items = source {} :: () -> Array<Item>
|
||||||
|
|
||||||
|
local displays = values(items, function(item, i)
|
||||||
|
return ItemDisplay {
|
||||||
|
Name = function()
|
||||||
|
return i() .. ": " .. item.Name
|
||||||
|
end
|
||||||
|
|
||||||
|
Image = "rbxassetid://" .. item.icon,
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Extra**
|
||||||
|
|
||||||
|
When should you use `indexes()` and `values()`?
|
||||||
|
|
||||||
|
`values()` should be used when you have a fixed set of objects where the
|
||||||
|
same objects can be re-arranged in the source table. It maps a value to a
|
||||||
|
UI element.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
- List of all players.
|
||||||
|
- Inventory of items.
|
||||||
|
- Chat message history.
|
||||||
|
- Toast notifications.
|
||||||
|
|
||||||
|
`indexes()` should be used in other cases, especially when your source table
|
||||||
|
has primitive values. It maps an index to a UI element.
|
||||||
|
|
||||||
|
e.g.
|
||||||
|
- List of character or weapon stats.
|
||||||
|
|
||||||
|
In most cases, both functions will produce the same observed result.
|
||||||
|
The main difference is performance, picking the right function to use can
|
||||||
|
result in less property updates and less re-renders. One case to note is
|
||||||
|
that `values()` works nicely when animating re-ordering of instances, since
|
||||||
|
the source index can be used to animate a change in position for the UI
|
||||||
|
element.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
@ -1,218 +0,0 @@
|
||||||
# Reactivity API: Control Flow
|
|
||||||
|
|
||||||
<br/>
|
|
||||||
|
|
||||||
## show()
|
|
||||||
|
|
||||||
Shows one of two components depending on an input source.
|
|
||||||
|
|
||||||
- **Type**
|
|
||||||
|
|
||||||
```luau
|
|
||||||
function show<T>(source: () -> unknown, component: () -> T): () -> T?
|
|
||||||
function show<T, U>(source: () -> unknown, component: () -> T, fallback: () -> U): () -> T | U
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Details**
|
|
||||||
|
|
||||||
Returns a source holding an instance of the currently shown component.
|
|
||||||
|
|
||||||
When the input source changes from a falsey to a truthy value, the
|
|
||||||
component will be reran under a new stable scope. If it changes from a
|
|
||||||
truthy to falsey value, the stable scope the component was created in will
|
|
||||||
be destroyed, and the returned source will output `nil`, or a fallback
|
|
||||||
component if given.
|
|
||||||
|
|
||||||
The fallback component is also ran under a new stable scope, and destroyed
|
|
||||||
when the input source switches back to truthy.
|
|
||||||
|
|
||||||
## switch()
|
|
||||||
|
|
||||||
Shows one of a set of components depending on an input source and a mapping table.
|
|
||||||
|
|
||||||
- **Type**
|
|
||||||
|
|
||||||
```luau
|
|
||||||
function switch<K, V>(source: () -> K): (map: Map<K, () -> V>) -> V?
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Details**
|
|
||||||
|
|
||||||
Returns a source holding an instance of the currently shown component.
|
|
||||||
|
|
||||||
When the input source changes, the new value will be used to lookup a given
|
|
||||||
mapping table to get a component, which will be ran under a new stable
|
|
||||||
scope. If the input source changes, the stable scope the component was
|
|
||||||
created in will be destroyed, and a new component created under a new
|
|
||||||
stable scope. If no component is found for an input value, the switch will
|
|
||||||
output `nil`.
|
|
||||||
|
|
||||||
- **Example**
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local logged = source(false)
|
|
||||||
|
|
||||||
local button = switch(logged) {
|
|
||||||
[true] = function()
|
|
||||||
return Button { Text = "Log out", Toggle = logged }
|
|
||||||
end,
|
|
||||||
|
|
||||||
[false] = function()
|
|
||||||
return Button { Text = "Log in", Toggle = logged }
|
|
||||||
end
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## indexes()
|
|
||||||
|
|
||||||
Maps each index in a table source to an object.
|
|
||||||
|
|
||||||
- **Type**
|
|
||||||
|
|
||||||
```luau
|
|
||||||
function indexes<KI, VI, VO>(
|
|
||||||
source: () -> Map<KI, VI>,
|
|
||||||
transform: (value: () -> VI, index: KI) -> VO
|
|
||||||
): Array<VO>
|
|
||||||
|
|
||||||
- **Details**
|
|
||||||
|
|
||||||
Returns a source holding an array of instances currently shown.
|
|
||||||
|
|
||||||
When the input source changes, each *index* in the new table is compared with
|
|
||||||
the last input table.
|
|
||||||
|
|
||||||
- For any new index, the `transform` function is ran under a new stable
|
|
||||||
scope to produce a new instance.
|
|
||||||
- For any removed index, the stable scope for that index is destroyed.
|
|
||||||
- Unchanged indexes are untouched.
|
|
||||||
|
|
||||||
The transform function is called only ever *once* for each index in the
|
|
||||||
source table.
|
|
||||||
|
|
||||||
1. First argument is a *source containing the index's value*.
|
|
||||||
2. Second argument is the *index itself*.
|
|
||||||
|
|
||||||
Anytime an existing index's value changes, the transform function is not
|
|
||||||
rerun, instead the source value for that index will update, causing anything
|
|
||||||
depending on it to update too.
|
|
||||||
|
|
||||||
- **Example**
|
|
||||||
|
|
||||||
The intended purpose of this function is to map each index in a table to
|
|
||||||
a UI element.
|
|
||||||
|
|
||||||
```luau
|
|
||||||
type Item = {
|
|
||||||
name: string,
|
|
||||||
icon: number
|
|
||||||
}
|
|
||||||
|
|
||||||
local items = source {} :: () -> Array<Item>
|
|
||||||
|
|
||||||
local displays = indexes(items, function(item, i)
|
|
||||||
return ItemDisplay {
|
|
||||||
Name = function()
|
|
||||||
return i .. ": " .. item().name
|
|
||||||
end,
|
|
||||||
|
|
||||||
Image = function()
|
|
||||||
return "rbxassetid://" .. item().icon
|
|
||||||
end,
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
## values()
|
|
||||||
|
|
||||||
Maps each value in a table source to an object.
|
|
||||||
|
|
||||||
- **Type**
|
|
||||||
|
|
||||||
```luau
|
|
||||||
function values<KI, VI, VO>(
|
|
||||||
source: () -> Map<KI, VI>,
|
|
||||||
transform: (value: VI, index: () -> KI) -> VO
|
|
||||||
): Array<VO>
|
|
||||||
|
|
||||||
- **Details**
|
|
||||||
|
|
||||||
Returns a source holding an array of instances currently shown.
|
|
||||||
|
|
||||||
When the input source changes, each *value* in the new table is compared with
|
|
||||||
the last input table. Similar to `indexes()` but for values instead of indexes.
|
|
||||||
|
|
||||||
- For any new value, the `transform` function is ran under a new stable
|
|
||||||
scope to produce a new instance.
|
|
||||||
- For any removed value, the stable scope for that value is destroyed.
|
|
||||||
- Unchanged values are untouched.
|
|
||||||
|
|
||||||
The transform function is only ever called *once* for each value in the
|
|
||||||
source table.
|
|
||||||
|
|
||||||
1. First argument is the *value itself*.
|
|
||||||
2. Second argument is a *source containing the value's index*.
|
|
||||||
|
|
||||||
Anytime an existing value's index changes, the transform function is not
|
|
||||||
rerun, instead the source index for that value will update, causing anything
|
|
||||||
depending on it to update too.
|
|
||||||
|
|
||||||
::: warning
|
|
||||||
Having primitive values in the input source table can cause unexpected
|
|
||||||
behavior, as duplicate values can result in multiple tranforms being ran for
|
|
||||||
a single value, meaning there can be multiple source indexes bound to the
|
|
||||||
same UI element. Strict mode has checks for this.
|
|
||||||
:::
|
|
||||||
|
|
||||||
- **Example**
|
|
||||||
|
|
||||||
The intended purpose of this function is to map each value in a table to
|
|
||||||
a UI element.
|
|
||||||
|
|
||||||
```luau
|
|
||||||
type Item = {
|
|
||||||
name: string,
|
|
||||||
icon: number
|
|
||||||
}
|
|
||||||
|
|
||||||
local items = source {} :: () -> Array<Item>
|
|
||||||
|
|
||||||
local displays = values(items, function(item, i)
|
|
||||||
return ItemDisplay {
|
|
||||||
Name = function()
|
|
||||||
return i() .. ": " .. item.Name
|
|
||||||
end
|
|
||||||
|
|
||||||
Image = "rbxassetid://" .. item.icon,
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Extra**
|
|
||||||
|
|
||||||
When should you use `indexes()` and `values()`?
|
|
||||||
|
|
||||||
`values()` should be used when you have a fixed set of objects where the
|
|
||||||
same objects can be re-arranged in the source table. It maps a value to a
|
|
||||||
UI element.
|
|
||||||
|
|
||||||
e.g.
|
|
||||||
- List of all players.
|
|
||||||
- Inventory of items.
|
|
||||||
- Chat message history.
|
|
||||||
- Toast notifications.
|
|
||||||
|
|
||||||
`indexes()` should be used in other cases, especially when your source table
|
|
||||||
has primitive values. It maps an index to a UI element.
|
|
||||||
|
|
||||||
e.g.
|
|
||||||
- List of character or weapon stats.
|
|
||||||
|
|
||||||
In most cases, both functions will appear to have the same behavior.
|
|
||||||
The main difference is performance, picking the right function to use can
|
|
||||||
result in less property updates and less re-renders. One case to note is
|
|
||||||
that `values()` works nicely when animating re-ordering of instances, since
|
|
||||||
the value is not destroyed when indexes are changed, and the source index
|
|
||||||
can be used to animate a change in position for the UI element.
|
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
@ -1,16 +1,15 @@
|
||||||
# Reactivity API: Utility
|
# Reactivity: Utility
|
||||||
|
|
||||||
## cleanup()
|
## cleanup()
|
||||||
|
|
||||||
Runs a callback anytime a scope is reran or destroyed.
|
Queues a callback to run when a scope is reran or destroyed.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
function cleanup(callback: () -> ())
|
function cleanup(v: Function | Disconnectable | Destroyable)
|
||||||
function cleanup(obj: Destroyable)
|
|
||||||
function cleanup(obj: Disconnectable)
|
|
||||||
|
|
||||||
|
type Function = () -> ()
|
||||||
type Destroyable = { destroy: () -> () }
|
type Destroyable = { destroy: () -> () }
|
||||||
type Disconnectable = { disconnect: () -> () }
|
type Disconnectable = { disconnect: () -> () }
|
||||||
```
|
```
|
||||||
|
|
@ -18,20 +17,27 @@ Runs a callback anytime a scope is reran or destroyed.
|
||||||
- **Example**
|
- **Example**
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local data = source(1)
|
local count = source(0)
|
||||||
|
|
||||||
|
local destroy = root(function()
|
||||||
effect(function()
|
effect(function()
|
||||||
local label = create "TextLabel" { Text = data() }
|
count()
|
||||||
|
|
||||||
cleanup(function()
|
cleanup(function()
|
||||||
label:Destroy()
|
print "cleaned"
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- nothing printed yet
|
||||||
|
count(1) -- prints "cleaned"
|
||||||
|
count(2) -- prints "cleaned"
|
||||||
|
destroy() -- prints "cleaned"
|
||||||
```
|
```
|
||||||
|
|
||||||
## untrack()
|
## untrack() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
|
||||||
|
|
||||||
Runs a given function in a new stable scope.
|
Runs a function in a new stable scope.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
|
|
@ -55,16 +61,15 @@ Runs a given function in a new stable scope.
|
||||||
end)
|
end)
|
||||||
|
|
||||||
print(sum()) -- 0
|
print(sum()) -- 0
|
||||||
b(1)
|
b(1) -- untracked so reactive scope created by derive() does not rerun
|
||||||
print(sum()) -- 0
|
print(sum()) -- 0
|
||||||
a(1)
|
a(1) -- reactive scope created by derive() reruns
|
||||||
print(sum()) -- 2
|
print(sum()) -- 2
|
||||||
```
|
```
|
||||||
|
|
||||||
## read()
|
## read()
|
||||||
|
|
||||||
Utility used to read a value that is either a primitive or a source. Sources
|
Utility used to read a value that is either a primitive or a source.
|
||||||
read can still be tracked inside a reactive scope.
|
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
|
|
@ -74,8 +79,8 @@ read can still be tracked inside a reactive scope.
|
||||||
|
|
||||||
## batch()
|
## batch()
|
||||||
|
|
||||||
Runs a given function where any source updates made within the function do not
|
Runs a function where any source updates made within the function do not
|
||||||
trigger effects until after the function finishes running.
|
trigger effects until after the function ends.
|
||||||
|
|
||||||
- **Type**
|
- **Type**
|
||||||
|
|
||||||
|
|
@ -86,11 +91,29 @@ trigger effects until after the function finishes running.
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
Improves performance when an effect depends on multiple sources, and those
|
Improves performance when an effect depends on multiple sources, and those
|
||||||
sources need to be updated. Updating those sources inside a batch call will
|
sources need to be updated.
|
||||||
only cause the effect to run once after the batch call ends instead of after
|
|
||||||
each time a source is updated.
|
|
||||||
|
|
||||||
## context()
|
- **Example**
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local a = source(0)
|
||||||
|
local b = source(0)
|
||||||
|
|
||||||
|
effect(function()
|
||||||
|
print(a() + b())
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- prints "0"
|
||||||
|
|
||||||
|
batch(function()
|
||||||
|
a(1) -- no print
|
||||||
|
b(2) -- no print
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- prints "3"
|
||||||
|
```
|
||||||
|
|
||||||
|
## context() <Badge type="info" text="STABLE"><a href="/vide/api/reactivity-core#Scopes">STABLE</a></Badge>
|
||||||
|
|
||||||
Creates a new context.
|
Creates a new context.
|
||||||
|
|
||||||
|
|
@ -101,15 +124,17 @@ Creates a new context.
|
||||||
|
|
||||||
type Context<T> =
|
type Context<T> =
|
||||||
() -> T -- get
|
() -> T -- get
|
||||||
& (T, () -> ()) -> () -- set
|
& <U>(T, () -> U) -> U -- set
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Details**
|
- **Details**
|
||||||
|
|
||||||
Calling `context()` returns a new context function.
|
Calling `context()` returns a new context function.
|
||||||
Call this function with no arguments to get the context value.
|
Call this function with no arguments to get the context value.
|
||||||
Call this function with a value and a callback to set a new context with the
|
Call this function with a value and a function to create a new context with
|
||||||
given value.
|
the given value.
|
||||||
|
|
||||||
|
The new context is run under a stable scope.
|
||||||
|
|
||||||
- **Example**
|
- **Example**
|
||||||
|
|
||||||
|
|
@ -131,4 +156,3 @@ Creates a new context.
|
||||||
end)
|
end)
|
||||||
```
|
```
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
|
|
@ -14,25 +14,23 @@ and identifying improper usage.
|
||||||
|
|
||||||
Currently, strict mode will:
|
Currently, strict mode will:
|
||||||
|
|
||||||
1. Run derived sources twice a source updates.
|
1. Run reactive scopes twice when a source updates.
|
||||||
2. Run effects twice when a source updates.
|
2. Throw an error if yields occur where they are not allowed.
|
||||||
3. Throw an error if yields occur where they are not allowed.
|
3. Checks for `indexes()` and `values()` outputting primitive values.
|
||||||
4. Checks for `indexes()` and `values()` returning primitive values.
|
4. Checks for `values()` input having duplicate values.
|
||||||
5. Checks for `values()` input having duplicate values.
|
5. Checks for duplicate nested properties at same depth.
|
||||||
6. Checks for duplicate nested properties at same depth.
|
6. Better error reporting and stack traces + creation traces of property bindings.
|
||||||
7. Better error reporting and stack traces + creation traces of property bindings.
|
|
||||||
|
|
||||||
By rerunning derived sources and effects twice each time they update, it helps
|
By rerunning reactive scopes twice each time they update, it helps ensure that
|
||||||
ensure that derived source computations are pure, and that any
|
computations are pure, and that any cleanup is done correctly.
|
||||||
cleanups made in derived sources or effects are done correctly.
|
|
||||||
|
|
||||||
Accidental yielding within reactive scopes can break Vide's reactive graph,
|
Accidental yielding within reactive scopes can break Vide's reactive graph,
|
||||||
which strict mode will catch.
|
which strict mode will catch.
|
||||||
|
|
||||||
As well as additional safety checks, Vide will dedicate extra resources to
|
As well as additional safety checks, Vide will dedicate extra resources to
|
||||||
recording and better emitting stack traces where errors occur, particularly
|
recording and better emitting stack traces where errors occur, particularly
|
||||||
when binding properties to sources.
|
when implicit effects are created for instance property updating.
|
||||||
|
|
||||||
It is recommended to develop UI with strict mode and to disable it when pushing to
|
It is recommended to develop UI with strict mode and to disable it when pushing to
|
||||||
production. In Roblox, production code compiles at O2 by default, so you don't
|
production. In Roblox, production code compiles at O2 by default, so you do not
|
||||||
need to worry about disabling strict mode unless you have manually enabled it.
|
need to worry about disabling strict mode unless you have manually enabled it.
|
||||||
|
|
|
||||||
|
|
@ -1,192 +0,0 @@
|
||||||
# Nested Scopes
|
|
||||||
|
|
||||||
Nesting scopes gives you finer control over the reactive graph, but needs more work to do. The built-in control flow functions try to cover the
|
|
||||||
most common cases, but they do not cover all of them.
|
|
||||||
|
|
||||||
This tutorial will demonstrate how to implement a `show()` control flow function
|
|
||||||
using just sources and effects.
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local mount = vide.mount
|
|
||||||
local source = vide.source
|
|
||||||
local show = vide.show
|
|
||||||
|
|
||||||
local function Counter()
|
|
||||||
local count = source(0)
|
|
||||||
|
|
||||||
return create "TextButton" {
|
|
||||||
Text = count,
|
|
||||||
Activated = function() count(count() + 1) end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
root(function()
|
|
||||||
local toggled = source(true)
|
|
||||||
|
|
||||||
show(toggled, Button)
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
%%{init: {
|
|
||||||
"theme": "base",
|
|
||||||
"themeVariables": {
|
|
||||||
"primaryColor": "#1B1B1F",
|
|
||||||
"primaryTextColor": "#fff",
|
|
||||||
"primaryBorderColor": "#1B1B1F",
|
|
||||||
"lineColor": "#79B8FF",
|
|
||||||
"tertiaryColor": "#161618",
|
|
||||||
"tertiaryBorderColor": "#1C1C1F"
|
|
||||||
}
|
|
||||||
}}%%
|
|
||||||
|
|
||||||
graph
|
|
||||||
|
|
||||||
subgraph mount
|
|
||||||
direction LR
|
|
||||||
toggle --> show
|
|
||||||
|
|
||||||
subgraph show[show effect]
|
|
||||||
text[Text effect]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Above is the reactive graph for `show()`. It creates a new effect depending on
|
|
||||||
`toggle` where anytime `toggle` is truthy, it will create a new `Counter`. The
|
|
||||||
`show` effect calls `Counter`, which creates a new reactive scope to update its
|
|
||||||
text whenever `count` changes. As per the rules of reactive scopes, a reactive
|
|
||||||
scope rerunning will destroy any scopes created within it. So the text
|
|
||||||
effect's reactive scope is destroyed whenever the show effect is rerun.
|
|
||||||
|
|
||||||
The same can be achieved without the use of `show()`:
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local mount = vide.mount
|
|
||||||
local source = vide.source
|
|
||||||
local effect = vide.effect
|
|
||||||
local cleanup = vide.cleanup
|
|
||||||
|
|
||||||
local function Counter()
|
|
||||||
local count = source(0)
|
|
||||||
|
|
||||||
return create "TextButton" {
|
|
||||||
Text = count,
|
|
||||||
Activated = function() count(count() + 1) end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
mount(function()
|
|
||||||
local toggled = source(true)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
if toggled() then
|
|
||||||
local destroy = root(function()
|
|
||||||
Counter()
|
|
||||||
end)
|
|
||||||
cleanup(destroy)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
%%{init: {
|
|
||||||
"theme": "base",
|
|
||||||
"themeVariables": {
|
|
||||||
"primaryColor": "#1B1B1F",
|
|
||||||
"primaryTextColor": "#fff",
|
|
||||||
"primaryBorderColor": "#1B1B1F",
|
|
||||||
"lineColor": "#79B8FF",
|
|
||||||
"tertiaryColor": "#161618",
|
|
||||||
"tertiaryBorderColor": "#1C1C1F"
|
|
||||||
}
|
|
||||||
}}%%
|
|
||||||
|
|
||||||
graph
|
|
||||||
|
|
||||||
subgraph mount
|
|
||||||
direction LR
|
|
||||||
toggle --> effect
|
|
||||||
|
|
||||||
subgraph effect
|
|
||||||
subgraph mount2[inner mount]
|
|
||||||
text[Text effect]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This is another way to achieve the same. Here we use `root()` within the effect
|
|
||||||
to manually create and destroy a new stable scope whenever the effect reruns.
|
|
||||||
|
|
||||||
The reason for creating a stable scope is to prevent the effect from tracking
|
|
||||||
any sources that may be read inside the `Counter()` call. Otherwise, the effect
|
|
||||||
may be rerun needlessly and recreate the counter.
|
|
||||||
|
|
||||||
Alternatively, instead of using `root()`:
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local mount = vide.mount
|
|
||||||
local source = vide.source
|
|
||||||
local effect = vide.effect
|
|
||||||
local untrack = vide.untrack
|
|
||||||
|
|
||||||
local function Counter()
|
|
||||||
local count = source(0)
|
|
||||||
|
|
||||||
return create "TextButton" {
|
|
||||||
Text = count,
|
|
||||||
Activated = function() count(count() + 1) end
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
mount(function()
|
|
||||||
local toggled = source(true)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
if toggled() then
|
|
||||||
untrack(Button)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
```
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
%%{init: {
|
|
||||||
"theme": "base",
|
|
||||||
"themeVariables": {
|
|
||||||
"primaryColor": "#1B1B1F",
|
|
||||||
"primaryTextColor": "#fff",
|
|
||||||
"primaryBorderColor": "#1B1B1F",
|
|
||||||
"lineColor": "#79B8FF",
|
|
||||||
"tertiaryColor": "#161618",
|
|
||||||
"tertiaryBorderColor": "#1C1C1F"
|
|
||||||
}
|
|
||||||
}}%%
|
|
||||||
|
|
||||||
graph
|
|
||||||
|
|
||||||
subgraph mount
|
|
||||||
direction LR
|
|
||||||
toggle --> effect
|
|
||||||
|
|
||||||
subgraph effect
|
|
||||||
text[Text effect]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
Without the use of `untrack()`, an error would occur, since Vide does not allow
|
|
||||||
the creation of reactive scopes inside reactive scopes. `untrack()` creates a
|
|
||||||
stable scope inside the reactive scope, and we can create another reactive scope
|
|
||||||
inside that stable scope. The
|
|
||||||
reason for this, is because if the `Counter` component reads from a source
|
|
||||||
internally, that can cause the reactive scope calling `Counter()` to track that
|
|
||||||
source, causing unintentional reruns. As a guard against this, you are forced to
|
|
||||||
use `untrack()` to create nested reactive scopes.
|
|
||||||
|
|
||||||
The final result is the same as using the `show()` component. An effect is
|
|
||||||
created which creates the counter, which creates its own reactive scope. The
|
|
||||||
effect rerunning causes the counter's internal reactive scope to be destroyed,
|
|
||||||
making sure everything is cleaned up.
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
# show()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
# switch()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
# indexes()
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
# values()
|
|
||||||
|
|
@ -6,10 +6,16 @@ Vide is heavily inspired by [Solid](https://www.solidjs.com/).
|
||||||
|
|
||||||
## Why Vide?
|
## Why Vide?
|
||||||
|
|
||||||
Vide provides a reactive and declarative API to simplify managing UI.
|
Vide's reactive and declarative API aims to let you program UI as simply as
|
||||||
|
possible, with a strong focus on how data flows through your application.
|
||||||
|
|
||||||
Some of the main focuses behind Vide's design choices:
|
Some of Vide's main design choices:
|
||||||
|
|
||||||
- Minimal syntax
|
- Syntax minimal.
|
||||||
- Complete typechecking
|
- Data oriented.
|
||||||
- Independence from instances
|
- Typechecking compatible.
|
||||||
|
- Instance independent.
|
||||||
|
|
||||||
|
Vide's reactivity operates with the concept
|
||||||
|
of scopes which carries a learning curve, though is what makes Vide's minimal
|
||||||
|
syntax possible. The crash course will introduce these concepts gradually.
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ destroyed, or when a stable scope is destroyed.
|
||||||
local root = vide.root
|
local root = vide.root
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
local effect = vide.effect
|
local effect = vide.effect
|
||||||
|
local cleanup = vide.cleanup
|
||||||
|
|
||||||
local count = source(0)
|
local count = source(0)
|
||||||
|
|
||||||
|
|
||||||
local destroy = root(function()
|
local destroy = root(function()
|
||||||
effect(function()
|
effect(function()
|
||||||
local x = count()
|
local x = count()
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
# Control Flow
|
|
||||||
|
|
||||||
Eventually you may need a way to dynamically create and destroy UI elements
|
|
||||||
resulting from source updates. Vide provides functions to help you do this,
|
|
||||||
known as *control flow* functions.
|
|
||||||
|
|
||||||
These functions return new sources, which hold the instances to be displayed.
|
|
||||||
The new sources can be used in `create()` to update the children of a container
|
|
||||||
instance.
|
|
||||||
|
|
||||||
## indexes()
|
|
||||||
|
|
||||||
`indexes()` *maps* each table index to a new UI element that can
|
|
||||||
update to display the current value at that index. Each table index is given a
|
|
||||||
single corresponding UI element.
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local list = source {
|
|
||||||
"finish the crash course",
|
|
||||||
"star Vide's GitHub"
|
|
||||||
}
|
|
||||||
|
|
||||||
local function TodoList(props: { list: () -> Array<string> })
|
|
||||||
return create "Frame" {
|
|
||||||
create "UIListLayout" {},
|
|
||||||
|
|
||||||
indexes(list, function(todo, i)
|
|
||||||
return create "TextLabel" {
|
|
||||||
Text = function()
|
|
||||||
return i .. ": " .. todo()
|
|
||||||
end,
|
|
||||||
|
|
||||||
LayoutOrder = i
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
TodoList { list = list }
|
|
||||||
```
|
|
||||||
|
|
||||||
For each index in the given source table, the given function to `indexes()` will
|
|
||||||
be run in a new stable scope with:
|
|
||||||
|
|
||||||
1. a source containing the value at the index
|
|
||||||
2. the index itself
|
|
||||||
|
|
||||||
When the value at an index is changed, the function is not reran. Instead, the
|
|
||||||
given source for that index is updated.
|
|
||||||
|
|
||||||
Any time the input source table is updated, the given function will be ran for
|
|
||||||
any newly added indexes, while any removed indexes (indexes now with a `nil`
|
|
||||||
value), will have its corresponding stable scope destroyed.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The reactive graph for the above example:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
%%{init: {
|
|
||||||
"theme": "base",
|
|
||||||
"themeVariables": {
|
|
||||||
"primaryColor": "#1B1B1F",
|
|
||||||
"primaryTextColor": "#fff",
|
|
||||||
"primaryBorderColor": "#1B1B1F",
|
|
||||||
"lineColor": "#79B8FF",
|
|
||||||
"tertiaryColor": "#161618",
|
|
||||||
"tertiaryBorderColor": "#1C1C1F"
|
|
||||||
}
|
|
||||||
}}%%
|
|
||||||
|
|
||||||
graph
|
|
||||||
|
|
||||||
subgraph root ["root scope"]
|
|
||||||
direction LR
|
|
||||||
todoList --> indexes -.- subroot1 & subroot2
|
|
||||||
|
|
||||||
subgraph subroot1 ["indexes scope 1"]
|
|
||||||
direction LR
|
|
||||||
value1[todo] --> prop1["prop binding"]
|
|
||||||
end
|
|
||||||
|
|
||||||
subgraph subroot2 ["indexes scope 2"]
|
|
||||||
direction LR
|
|
||||||
value2[todo] --> prop2[prop binding]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
When you edit a table in a source, you must set that table again to actually
|
|
||||||
update the source.
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local src = source { 1, 2 }
|
|
||||||
local data = src()
|
|
||||||
table.insert(data, 3) -- no effects will run
|
|
||||||
src(data) -- effects will run
|
|
||||||
```
|
|
||||||
158
docs/tut/crash-course/11-dynamic-scope.md
Normal file
158
docs/tut/crash-course/11-dynamic-scope.md
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
# Dynamic Scoping
|
||||||
|
|
||||||
|
Eventually you may need a way to dynamically create and destroy UI elements
|
||||||
|
resulting from source updates. Vide provides functions to help you do this,
|
||||||
|
known as *dynamic scope* functions.
|
||||||
|
|
||||||
|
These functions create and destroy components for you in response to source
|
||||||
|
updates. They return a source containing the created component. This source can
|
||||||
|
be parented as a child which will update the shown children whenever the source
|
||||||
|
updates.
|
||||||
|
|
||||||
|
The simplest example is using `show()`.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local source = vide.source
|
||||||
|
local create = vide.create
|
||||||
|
local show = vide.show
|
||||||
|
local root = vide.root
|
||||||
|
|
||||||
|
function Button(props: { Text: string, Activated: () -> () })
|
||||||
|
return create "TextButton" {
|
||||||
|
Text = props.Text,
|
||||||
|
Activated = props.Activated
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function Menu()
|
||||||
|
return create "TextLabel" {
|
||||||
|
Text = "This is a menu"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function App()
|
||||||
|
local toggled = source(false)
|
||||||
|
|
||||||
|
return create "ScreenGui" {
|
||||||
|
Button {
|
||||||
|
Text = "Toggle Menu",
|
||||||
|
Activated = function()
|
||||||
|
toggled(not toggled())
|
||||||
|
end
|
||||||
|
},
|
||||||
|
|
||||||
|
show(toggled, Menu) -- [!code highlight]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
App().Parent = game.StarterGui
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a complete example of rendering UI which has a single button that
|
||||||
|
toggles the opening of a menu.
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Another common function is `indexes()`. This function creates a component for
|
||||||
|
each index in a table.
|
||||||
|
|
||||||
|
Each component created is done so in a new and independent stable scope. The
|
||||||
|
indexes of the table are checked each source update to prevent redunant
|
||||||
|
destruction and recreation of UI elements.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local source = vide.source
|
||||||
|
local create = vide.create
|
||||||
|
local indexes = vide.indexes
|
||||||
|
local root = vide.root
|
||||||
|
|
||||||
|
local function Todo(props: {
|
||||||
|
Text: () -> string,
|
||||||
|
Position: number,
|
||||||
|
Activated: () -> ()
|
||||||
|
})
|
||||||
|
return create "TextButton" {
|
||||||
|
Text = function() return props.Position .. ": " .. props.Text() end,
|
||||||
|
LayoutOrder = props.Position,
|
||||||
|
Activated = Activated
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local function TodoList(props: { List: () -> Array<string> })
|
||||||
|
return create "Frame" {
|
||||||
|
create "UIListLayout" {},
|
||||||
|
|
||||||
|
indexes(props.List, function(text, i) -- [!code highlight]
|
||||||
|
return Todo {
|
||||||
|
Text = text,
|
||||||
|
Position = i,
|
||||||
|
Activated = function() -- remove the todo when clicked
|
||||||
|
local list = props.List()
|
||||||
|
table.remove(list, i)
|
||||||
|
props.List(list)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
function App()
|
||||||
|
local list = source {
|
||||||
|
"finish the crash course",
|
||||||
|
"star Vide's GitHub"
|
||||||
|
}
|
||||||
|
|
||||||
|
return create "ScreenGui" {
|
||||||
|
TodoList { List = list },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
App().Parent = game.StarterGui
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
The reactive graph for the above example:
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {
|
||||||
|
"theme": "base",
|
||||||
|
"themeVariables": {
|
||||||
|
"primaryColor": "#111720",
|
||||||
|
"primaryTextColor": "#fff",
|
||||||
|
"primaryBorderColor": "#111720",
|
||||||
|
"lineColor": "#79B8FF",
|
||||||
|
"tertiaryColor": "#0d131b",
|
||||||
|
"tertiaryBorderColor": "#0d131b"
|
||||||
|
}
|
||||||
|
}}%%
|
||||||
|
|
||||||
|
graph
|
||||||
|
|
||||||
|
subgraph root ["root"]
|
||||||
|
direction LR
|
||||||
|
todoList --> indexes -.- subroot1 & subroot2
|
||||||
|
|
||||||
|
subgraph subroot1 ["indexes scope 1"]
|
||||||
|
direction LR
|
||||||
|
value1[todo] --> prop1["prop binding"]
|
||||||
|
end
|
||||||
|
|
||||||
|
subgraph subroot2 ["indexes scope 2"]
|
||||||
|
direction LR
|
||||||
|
value2[todo] --> prop2[prop binding]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
When you edit a table in a source, you must set that table again to actually
|
||||||
|
update the source.
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local src = source { 1, 2 }
|
||||||
|
local data = src()
|
||||||
|
table.insert(data, 3) -- no effects will run
|
||||||
|
src(data) -- effects will run
|
||||||
|
```
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
# Actions
|
# Actions
|
||||||
|
|
||||||
Actions in Vide are special callbacks that you can pass along with properties,
|
Actions are special callbacks that you can pass along with properties,
|
||||||
to run some code on an instance receiving them.
|
to run some code on an instance receiving them.
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local action = vide.action
|
local action = vide.action
|
||||||
```
|
|
||||||
|
|
||||||
```luau
|
|
||||||
create "TextLabel" {
|
create "TextLabel" {
|
||||||
Text = "test",
|
Text = "test",
|
||||||
|
|
||||||
|
|
@ -24,6 +22,7 @@ action used to listen for property changes:
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local action = vide.action
|
local action = vide.action
|
||||||
|
local source = vide.source
|
||||||
local effect = vide.effect
|
local effect = vide.effect
|
||||||
local cleanup = vide.cleanup
|
local cleanup = vide.cleanup
|
||||||
|
|
||||||
|
|
@ -49,7 +48,7 @@ effect(function()
|
||||||
print(output())
|
print(output())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
instance.Text = "foo" -- "foo" will be printed from the effect
|
instance.Text = "foo" -- "foo" will be printed by the effect
|
||||||
```
|
```
|
||||||
|
|
||||||
The source `output` will be updated with the new property value any time it is
|
The source `output` will be updated with the new property value any time it is
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ local count = source(0)
|
||||||
|
|
||||||
local ran = 0
|
local ran = 0
|
||||||
effect(function()
|
effect(function()
|
||||||
|
count()
|
||||||
ran += 1
|
ran += 1
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ Created by:
|
||||||
|
|
||||||
- `root()`
|
- `root()`
|
||||||
- `untrack()`
|
- `untrack()`
|
||||||
- `switch()`
|
- `show()`
|
||||||
- `indexes()`
|
- `indexes()`
|
||||||
|
|
||||||
Stable scopes do not track sources and never rerun.
|
Stable scopes do not track sources and never rerun.
|
||||||
|
|
@ -47,23 +47,14 @@ Created by:
|
||||||
Reactive scopes do track sources and will rerun when those sources update.
|
Reactive scopes do track sources and will rerun when those sources update.
|
||||||
|
|
||||||
Reactive scopes cannot be created within a reactive scope, but stable scopes
|
Reactive scopes cannot be created within a reactive scope, but stable scopes
|
||||||
can.
|
can be created within a reactive scope.
|
||||||
|
|
||||||
## Scope Owners
|
## Scope Cleanup
|
||||||
|
|
||||||
A scope created within another scope is *owned* by the other scope, with the
|
When a scope is rerun or destroyed, all scopes created within it are
|
||||||
exception of the scope created by `root()`.
|
automatically destroyed.
|
||||||
|
|
||||||
When a scope is rerun or destroyed, all scopes owned by it are automatically
|
Any functions queued by `cleanup()` are also ran.
|
||||||
destroyed.
|
|
||||||
|
|
||||||
`root()` creates a stable scope with no owner, instead it is destroyed manually.
|
|
||||||
|
|
||||||
## Cleanup
|
|
||||||
|
|
||||||
Arbitrary code to run whenever a stable or reactive scope is rerun or destroyed.
|
|
||||||
|
|
||||||
Queue a function to run using `cleanup()`.
|
|
||||||
|
|
||||||
## Reactive Graph
|
## Reactive Graph
|
||||||
|
|
||||||
|
|
@ -93,12 +84,12 @@ end)
|
||||||
%%{init: {
|
%%{init: {
|
||||||
"theme": "base",
|
"theme": "base",
|
||||||
"themeVariables": {
|
"themeVariables": {
|
||||||
"primaryColor": "#1B1B1F",
|
"primaryColor": "#111720",
|
||||||
"primaryTextColor": "#fff",
|
"primaryTextColor": "#fff",
|
||||||
"primaryBorderColor": "#1B1B1F",
|
"primaryBorderColor": "#111720",
|
||||||
"lineColor": "#79B8FF",
|
"lineColor": "#79B8FF",
|
||||||
"tertiaryColor": "#161618",
|
"tertiaryColor": "#0d131b",
|
||||||
"tertiaryBorderColor": "#1C1C1F"
|
"tertiaryBorderColor": "#202530"
|
||||||
}
|
}
|
||||||
}}%%
|
}}%%
|
||||||
|
|
||||||
|
|
@ -118,6 +109,5 @@ Notes:
|
||||||
- An update to `count` will cause `text` to rerun, which
|
- An update to `count` will cause `text` to rerun, which
|
||||||
then causes `effect` to rerun.
|
then causes `effect` to rerun.
|
||||||
- When the root scope is destroyed, `text` and
|
- When the root scope is destroyed, `text` and
|
||||||
`effect` will be destroyed alongside it, since they are
|
`effect` will be destroyed alongside it, since they were created within it.
|
||||||
owned by it. `count` will be untouched and future updates
|
`count` will be untouched and future updates to `count` will have no effect.
|
||||||
to `count` will have no effect.
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Effects
|
# Effects
|
||||||
|
|
||||||
Effects are functions that are ran in response to source updates. They are
|
Effects are functions that are ran in response to source updates.
|
||||||
A source and effect is analogous to a signal and connection.
|
A source and effect is analogous to a signal and connection.
|
||||||
|
|
||||||
Effects are created using `effect()`.
|
Effects are created using `effect()`.
|
||||||
|
|
@ -23,7 +23,10 @@ count(1)
|
||||||
Any source read inside an effect is tracked and will rerun the effect when
|
Any source read inside an effect is tracked and will rerun the effect when
|
||||||
that source is updated.
|
that source is updated.
|
||||||
|
|
||||||
Derived sources are also tracked, it doesn't matter how deeply nested
|
The effect runs its callback once immediately to initially figure out what
|
||||||
|
sources are being read.
|
||||||
|
|
||||||
|
Derived sources are also tracked, it does not matter how deeply nested
|
||||||
inside a function a source is.
|
inside a function a source is.
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
|
|
@ -47,3 +50,22 @@ count(2)
|
||||||
|
|
||||||
If a source is updated with the same value it already had, it will not rerun
|
If a source is updated with the same value it already had, it will not rerun
|
||||||
effects depending on it.
|
effects depending on it.
|
||||||
|
|
||||||
|
You can also read from a source within an effect without the effect tracking it.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local source = vide.source
|
||||||
|
local effect = vide.effect
|
||||||
|
local untrack = vide.untrack
|
||||||
|
|
||||||
|
local a = source(0)
|
||||||
|
local b = source(0)
|
||||||
|
|
||||||
|
effect(function()
|
||||||
|
print(`a: {a()} b: {untrack(b)}`)
|
||||||
|
end)
|
||||||
|
|
||||||
|
a(1) -- prints "a: 1 b: 0"
|
||||||
|
b(1) -- prints nothing
|
||||||
|
a(2) -- prints "a: 2 b: 1"
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -7,29 +7,31 @@ But the disconnecting of many signals and connections is tedious and verbose.
|
||||||
Vide instead operates on the concept of scopes which provides a much cleaner
|
Vide instead operates on the concept of scopes which provides a much cleaner
|
||||||
API, given that you follow a few rules.
|
API, given that you follow a few rules.
|
||||||
|
|
||||||
Scopes come in two flavors; stable and reactive.
|
Thre are two types of scopes: stable and reactive.
|
||||||
|
|
||||||
- All scopes must be created within another scope with the exception of `root()`
|
- A scope must be created within another scope.
|
||||||
- Stable scopes never rerun
|
- Stable scopes never rerun.
|
||||||
- Reactive scopes can rerun
|
- Reactive scopes can rerun.
|
||||||
- A reactive scope cannot be created within another reactive scope
|
- A reactive scope cannot be created within another reactive scope, only within
|
||||||
|
a stable scope.
|
||||||
|
|
||||||
|
An exception to the first rule is `root()`, which creates the initial scope that
|
||||||
|
you destroy manually with a destructor function it returns.
|
||||||
|
|
||||||
`effect()` creates a reactive scope.
|
|
||||||
`root()` creates a stable scope.
|
`root()` creates a stable scope.
|
||||||
|
`effect()` creates a reactive scope.
|
||||||
|
|
||||||
Whenever a scope is destroyed, any scope created within that scope is also
|
Whenever a scope is destroyed, any scope created within that scope is also
|
||||||
destroyed, and so on. This is why all scopes must be created within another
|
destroyed, and so on.
|
||||||
scope, except `root()` which is used to create the initial scope that you can
|
|
||||||
manually destroy.
|
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local root = vide.root
|
local root = vide.root
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
local effect = vide.effect
|
local effect = vide.effect
|
||||||
|
|
||||||
local function setup()
|
local count = source(0)
|
||||||
local count = source(0)
|
|
||||||
|
|
||||||
|
local function setup()
|
||||||
effect(function()
|
effect(function()
|
||||||
print(count())
|
print(count())
|
||||||
end)
|
end)
|
||||||
|
|
@ -37,32 +39,16 @@ local function setup()
|
||||||
return count
|
return count
|
||||||
end
|
end
|
||||||
|
|
||||||
setup() -- will error since effect() tries to create a reactive scope outside of a stable scope
|
setup() -- error, effect() tried to create a reactive scope with no stable scope
|
||||||
|
|
||||||
local count = root(setup) -- ok since effect() was called within a stable scope
|
local destroy = root(setup) -- ok since effect() was called in a stable scope
|
||||||
count(1) -- prints "1"
|
|
||||||
```
|
|
||||||
|
|
||||||
The scope created by `root()` can be destroyed.
|
|
||||||
|
|
||||||
```luau
|
|
||||||
local function setup()
|
|
||||||
local count = source(0)
|
|
||||||
|
|
||||||
effect(function()
|
|
||||||
print(count())
|
|
||||||
end)
|
|
||||||
|
|
||||||
return count
|
|
||||||
end
|
|
||||||
|
|
||||||
local destroy, count = root(setup)
|
|
||||||
|
|
||||||
count(1) -- prints "1"
|
count(1) -- prints "1"
|
||||||
|
count(2) -- prints "2"
|
||||||
|
|
||||||
destroy()
|
destroy()
|
||||||
|
|
||||||
count(2) -- effect is destroyed; no longer prints
|
count(3) -- reactive scope created by effect() is destroyed, it does not rerun
|
||||||
```
|
```
|
||||||
|
|
||||||
Vide's reactivity can be represented graphically, as a *reactive graph*.
|
Vide's reactivity can be represented graphically, as a *reactive graph*.
|
||||||
|
|
@ -73,12 +59,12 @@ The reactive graph for the above example looks like so:
|
||||||
%%{init: {
|
%%{init: {
|
||||||
"theme": "base",
|
"theme": "base",
|
||||||
"themeVariables": {
|
"themeVariables": {
|
||||||
"primaryColor": "#1B1B1F",
|
"primaryColor": "#111720",
|
||||||
"primaryTextColor": "#fff",
|
"primaryTextColor": "#fff",
|
||||||
"primaryBorderColor": "#1B1B1F",
|
"primaryBorderColor": "#111720",
|
||||||
"lineColor": "#79B8FF",
|
"lineColor": "#79B8FF",
|
||||||
"tertiaryColor": "#161618",
|
"tertiaryColor": "#0d131b",
|
||||||
"tertiaryBorderColor": "#161618"
|
"tertiaryBorderColor": "#0d131b"
|
||||||
}
|
}
|
||||||
}}%%
|
}}%%
|
||||||
|
|
||||||
|
|
@ -90,7 +76,7 @@ subgraph root
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
When the stable `root()` is destroyed, the reactive `effect()`
|
When the stable `root()` scope is destroyed, the reactive `effect()`
|
||||||
scope will also be destroyed since it was created within it.
|
scope will also be destroyed since it was created within it.
|
||||||
|
|
||||||
This is important because you may have an effect that updates the property of a
|
This is important because you may have an effect that updates the property of a
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
# Stateful Components
|
# Reactive Components
|
||||||
|
|
||||||
Stateful components in Vide are created using sources and effects - sources to
|
Reactive components in Vide are created using sources and effects - sources to
|
||||||
store the data, and effects to display the data.
|
store the data, and effects to display the data.
|
||||||
|
|
||||||
## Internal State
|
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
|
|
@ -33,8 +31,6 @@ its internal count, and automatically update its text to reflect that count.
|
||||||
Each instance of `Counter()` will maintain its own independent count, since the
|
Each instance of `Counter()` will maintain its own independent count, since the
|
||||||
count source is created inside the component.
|
count source is created inside the component.
|
||||||
|
|
||||||
## External State
|
|
||||||
|
|
||||||
External sources can also be passed into components for them to use.
|
External sources can also be passed into components for them to use.
|
||||||
|
|
||||||
```luau
|
```luau
|
||||||
|
|
@ -3,7 +3,9 @@
|
||||||
Explicitly creating effects to update properties is tedious. You can
|
Explicitly creating effects to update properties is tedious. You can
|
||||||
*implicitly* create an effect to update properties instead.
|
*implicitly* create an effect to update properties instead.
|
||||||
|
|
||||||
```luau
|
::: code-group
|
||||||
|
|
||||||
|
```luau [Implicit Effect]
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
local source = vide.source
|
local source = vide.source
|
||||||
|
|
||||||
|
|
@ -22,6 +24,30 @@ local function Counter()
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```luau [Explicit Effect]
|
||||||
|
local create = vide.create
|
||||||
|
local source = vide.source
|
||||||
|
local effect = vide.effect
|
||||||
|
|
||||||
|
local function Counter()
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
|
local instance = create "TextButton" {
|
||||||
|
Activated = function()
|
||||||
|
count(count() + 1)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
effect(function()
|
||||||
|
instance.Text = "count: " .. count()
|
||||||
|
end)
|
||||||
|
|
||||||
|
return instance
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
This example is equivalent to the example seen on the previous page.
|
This example is equivalent to the example seen on the previous page.
|
||||||
|
|
||||||
Instead of explicitly creating an effect, assigning a (non-event) property a
|
Instead of explicitly creating an effect, assigning a (non-event) property a
|
||||||
|
|
@ -46,12 +72,12 @@ local function List(props: { children: () -> { Instance } })
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
local list = List { children = items } -- creates a list with a single text label "A"
|
local list = List { children = items } -- creates a list with text label "A"
|
||||||
|
|
||||||
items {
|
items {
|
||||||
create "TextLabel" { Text = "B" },
|
create "TextLabel" { Text = "B" },
|
||||||
create "TextLabel" { Text = "C" }
|
create "TextLabel" { Text = "C" }
|
||||||
}
|
}
|
||||||
|
|
||||||
-- this will automatically unparent the text label "A", and parent the labels "B" and "C"
|
-- this will automatically unparent text label "A", and parent labels "B" and "C"
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,8 @@ effect(function() text() end)
|
||||||
count(1) -- prints "ran" x1
|
count(1) -- prints "ran" x1
|
||||||
```
|
```
|
||||||
|
|
||||||
`derive()` must also be called within a stable scope, just like `effect()`.
|
Because `derive()` creates a reactive scope, it must be called within a stable
|
||||||
|
scope, just like `effect()`.
|
||||||
|
|
||||||
If the recalculated value is the same as the old value, the derived source will
|
If the recalculated value is the same as the old value, the derived source will
|
||||||
not rerun the effects using it.
|
not rerun the effects using it.
|
||||||
|
|
@ -68,12 +69,12 @@ The reactive graph for the above example:
|
||||||
%%{init: {
|
%%{init: {
|
||||||
"theme": "base",
|
"theme": "base",
|
||||||
"themeVariables": {
|
"themeVariables": {
|
||||||
"primaryColor": "#1B1B1F",
|
"primaryColor": "#111720",
|
||||||
"primaryTextColor": "#fff",
|
"primaryTextColor": "#fff",
|
||||||
"primaryBorderColor": "#1B1B1F",
|
"primaryBorderColor": "#111720",
|
||||||
"lineColor": "#79B8FF",
|
"lineColor": "#79B8FF",
|
||||||
"tertiaryColor": "#161618",
|
"tertiaryColor": "#0d131b",
|
||||||
"tertiaryBorderColor": "#161618"
|
"tertiaryBorderColor": "#0d131b"
|
||||||
}
|
}
|
||||||
}}%%
|
}}%%
|
||||||
|
|
||||||
|
|
@ -86,7 +87,7 @@ end
|
||||||
```
|
```
|
||||||
|
|
||||||
Deriving a source in this manner is similar to creating an effect to update
|
Deriving a source in this manner is similar to creating an effect to update
|
||||||
another source. You should never manually do this using an effect however.
|
another source. You should avoid doing this using an effect however.
|
||||||
Improper usage could accidently create infinite loops in the reactive graph.
|
Improper usage could accidently create infinite loops in the reactive graph.
|
||||||
Always favour deriving when you need one source to update based on another
|
Always favour deriving when you need one source to update based on another
|
||||||
source.
|
source.
|
||||||
|
|
|
||||||
144
docs/tut/dynamic-scoping/custom.md
Normal file
144
docs/tut/dynamic-scoping/custom.md
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Dynamic Scoping
|
||||||
|
|
||||||
|
Dynamic scoping is the act of creating and destroying new scopes in response to
|
||||||
|
source updates. This is needed for conditionally rendering parts of your UI,
|
||||||
|
such as opening and closing menus.
|
||||||
|
|
||||||
|
While Vide provides functions for common ways to do this, this section will
|
||||||
|
show how you can implement them yourself so you are not limited by only what is
|
||||||
|
provided.
|
||||||
|
|
||||||
|
## Recreating [`show()`](/api/reactivity-dynamic#show-reactive)
|
||||||
|
|
||||||
|
The most basic one, `show()`, can be
|
||||||
|
implemented yourself like so:
|
||||||
|
|
||||||
|
```luau
|
||||||
|
local function show(toggle: () -> unknown, component: () -> Instance)
|
||||||
|
return derive(function()
|
||||||
|
return if toggle() then untrack(component) else nil
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
The main thing to note here is the use of `untrack()`. This function runs its
|
||||||
|
callback in a new stable scope. Without this, if the component were to create
|
||||||
|
a reactive scope, an error would occur since a reactive scope cannot be created
|
||||||
|
within a reactive scope.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {
|
||||||
|
"theme": "base",
|
||||||
|
"themeVariables": {
|
||||||
|
"primaryColor": "#111720",
|
||||||
|
"primaryTextColor": "#fff",
|
||||||
|
"primaryBorderColor": "#444455",
|
||||||
|
"lineColor": "#79B8FF",
|
||||||
|
"tertiaryColor": "#0d131b",
|
||||||
|
"tertiaryBorderColor": "#444455"
|
||||||
|
}
|
||||||
|
}}%%
|
||||||
|
|
||||||
|
graph
|
||||||
|
|
||||||
|
subgraph derive ["derive (reactive)"]
|
||||||
|
|
||||||
|
subgraph untrack ["untrack (stable)"]
|
||||||
|
subgraph effect ["effect (reactive)"]
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
You can see from the above graph how the effect would not be created directly
|
||||||
|
inside the derive, there is a stable scope between them. This requirement exists
|
||||||
|
as a guard against unintentional rerendering of UI.
|
||||||
|
|
||||||
|
## Recreating [`switch()`](/api/reactivity-dynamic#switch-reactive)
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local function switch(key)
|
||||||
|
return function(map)
|
||||||
|
return derive(function()
|
||||||
|
local component = map[key()]
|
||||||
|
return if component then untrack(component) else nil
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recreating [`indexes()`](/api/reactivity-dynamic#indexes-reactive)
|
||||||
|
|
||||||
|
This is a more complicated function because it manages multiple scopes at the
|
||||||
|
same time, unlike the previous functions. Because some scopes may persist
|
||||||
|
between reruns, we cannot use `untrack()` anymore which automatically destroys
|
||||||
|
on rerun; we must use `root()` where the lifetime of each scope is managed
|
||||||
|
manually and independently.
|
||||||
|
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local function indexes<I, VI, VO>(
|
||||||
|
input: () -> Map<I, VI>,
|
||||||
|
transform: (value: () -> VI, index: I) -> VO
|
||||||
|
)
|
||||||
|
local index_caches = {} :: Map<I, {
|
||||||
|
previous_input: VI,
|
||||||
|
output: VO,
|
||||||
|
source: (VI) -> VI,
|
||||||
|
destroy: () -> ()
|
||||||
|
}?>
|
||||||
|
|
||||||
|
return derive(function()
|
||||||
|
local new_input = input()
|
||||||
|
|
||||||
|
-- destroy scopes of removed indexes
|
||||||
|
for i, cache in index_caches do
|
||||||
|
if new_input[i] == nil then
|
||||||
|
assert(cache).destroy()
|
||||||
|
index_caches[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- create scopes or update sources of added or changed index values
|
||||||
|
for i, v in new_input do
|
||||||
|
local cache = index_caches[i]
|
||||||
|
|
||||||
|
if cache == nil then -- no scope created for this index, create one
|
||||||
|
local src = source(v)
|
||||||
|
|
||||||
|
local destroy, result = root(function()
|
||||||
|
return transform(src, i)
|
||||||
|
end)
|
||||||
|
|
||||||
|
index_caches[i] = {
|
||||||
|
destroy = destroy,
|
||||||
|
source = src,
|
||||||
|
output = result,
|
||||||
|
previous_input = v
|
||||||
|
}
|
||||||
|
elseif cache.previous_input ~= v then -- scope exists, update source
|
||||||
|
cache.previous_input = v
|
||||||
|
cache.source(v)
|
||||||
|
else -- scope exists and value has not changed; do nothing
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- return the cached output values as an array
|
||||||
|
local array = table.create(#index_caches)
|
||||||
|
|
||||||
|
for _, cache in index_caches do
|
||||||
|
table.insert(array, assert(cache).output)
|
||||||
|
end
|
||||||
|
|
||||||
|
return array
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Though the above functions are already provided to you by Vide, this serves as
|
||||||
|
an example for how you may create your own dynamic scope functions.
|
||||||
|
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue