Update docs

This commit is contained in:
Aaron Smith 2023-09-20 18:42:29 +01:00
parent 97096f8aff
commit 85b1127551
15 changed files with 200 additions and 298 deletions

View file

@ -53,17 +53,9 @@ export default defineConfig({
]
},
{
text: "Control Flow WIP",
text: "Advanced Reactivity",
items: [
{ text: "switch", link: "/tut/control-flow/switch.md" },
{ text: "indexes", link: "/tut/control-flow/indexes.md" },
{ text: "values", link: "/tut/control-flow/values.md" },
]
},
{
text: "Advanced Reactivity WIP",
items: [
{ text: "reactive-scopes", link: "/tut/reactive-scoping.md"}
{ text: "Reactive Scopes", link: "/tut/advanced/reactive-scoping.md"}
]
}
],

View file

@ -2,9 +2,33 @@
<br/>
## show()
Shows one of two components depending on an input source.
- **Type**
```lua
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 reactive scope. If it changes from a
truthy to falsey value, the reactive 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 reactive scope, and destroyed
when the input source switches back to truthy.
## switch()
Changes object based on a source and a mapping table.
Shows one of a set of components depending on an input source and a mapping table.
- **Type**
@ -14,12 +38,14 @@ Changes object based on a source and a mapping table.
- **Details**
The mapped function is ran in a new reactive scope that is destroyed when
the source changes and maps to a different function.
Returns a source holding an instance of the currently shown component.
::: warning
Mapped functions cannot yield.
:::
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 reactive
scope. If the input source changes, the reactive scope the component was
created in will be destroyed, and a new component created under a new
reactive scope. If no component is found for an input value, the switch will
output `nil`.
- **Example**
@ -51,24 +77,26 @@ Maps each index in a table source to an object.
- **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 reactive
scope to produce a new instance.
- For any removed index, the reactive 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. The first argument is a source containing the index's value
and the second argument is just the index.
source table.
Anytime a new index is added, the transform function will be called again
for that new index.
1. First argument is a *source containing the index's value*.
2. Second argument is the *index itself*.
Anytime an existing index value changes, the transform function is not rerun,
instead the source value for that index will update, causing anything
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.
Returns a state containing an array of all objects returned by the
transform.
::: warning
`transform()` cannot yield.
:::
- **Example**
The intended purpose of this function is to map each index in a table to
@ -85,14 +113,12 @@ Maps each index in a table source to an object.
local displays = indexes(items, function(item, i)
return ItemDisplay {
Name = function()
return item().name
return i .. ": " .. item().name
end,
Image = function()
return "rbxassetid://" .. item().icon
end,
LayoutOrder = i
}
end)
```
@ -111,28 +137,31 @@ Maps each value in a table source to an object.
- **Details**
The transform function is called only ever *once* for each value in the
source table. The first argument is the index's value and
the second argument is a source containing the index.
Returns a source holding an array of instances currently shown.
Anytime a new value is added, the transform function will be called again
for that new value.
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 reactive
scope to produce a new instance.
- For any removed value, the reactive 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.
Returns a state containing an array of all objects returned by the
transform.
::: warning
`transform()` cannot yield.
:::
::: warning
Having primitive values in the source table can cause unexpected behavior,
as duplicate primitives can result in multiple index sources being bound
to the same UI element.
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**
@ -150,11 +179,11 @@ Maps each value in a table source to an object.
local displays = values(items, function(item, i)
return ItemDisplay {
Name = item.Name
Name = function()
return i() .. ": " .. item.Name
end
Image = "rbxassetid://" .. item.icon,
LayoutOrder = i
}
end)
```
@ -181,6 +210,9 @@ Maps each value in a table source to an object.
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.
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 easily be put through a spring.
--------------------------------------------------------------------------------

View file

@ -24,20 +24,6 @@ Runs a callback anytime a reactive scope is re-ran.
end)
```
```lua
local data = source(1)
derive(function()
local label = create "TextLabel" { Text = data() }
cleanup(function()
label:Destroy()
end)
return label
end)
```
## untrack()
Runs a given function where any sources read will not track its reactive scope.
@ -70,4 +56,14 @@ Runs a given function where any sources read will not track its reactive scope.
print(sum()) -- 2
```
## read()
Utility used to read a value that is either a primitive or a source.
- **Type**
```lua
function read<T>(value: T | () -> T): T
```
--------------------------------------------------------------------------------

View file

@ -6,6 +6,9 @@ Strict mode is library-wide and can get set by doing:
vide.strict = true
```
It is automatically enabled when Vide is first required and not running in O2
optimization level.
Strict mode is designed to help the development process by adding safety checks
and identifying improper usage.
@ -15,8 +18,9 @@ Currently, strict mode will:
2. Run effects twice when a source updates.
3. Throw an error if yields occur where they are not allowed.
4. Checks for `indexes()` and `values()` returning primitive values.
5. Checks for duplicate nested properties at same depth.
6. Better error reporting and stack traces + creation traces of property bindings.
5. Checks for `values()` input having duplicate values.
6. Checks for duplicate nested properties at same depth.
7. Better error reporting and stack traces + creation traces of property bindings.
By rerunning sources and effects, any side-effects are made more apparent.
This also helps ensure that cleanups are being handled correctly.
@ -29,4 +33,5 @@ recording and better emitting stack traces where errors occur, particularly
when binding properties to sources.
It is recommend to develop UI with strict mode and to disable it when pushing to
production.
production. In Roblox, production code compiles at O2 by default, so you don't
need to worry about disabling strict mode unless you have manually enabled it.

View file

View file

@ -0,0 +1 @@
# show()

View file

@ -0,0 +1 @@
# switch()

View file

@ -0,0 +1 @@
# indexes()

View file

@ -0,0 +1 @@
# values()

View file

@ -1,66 +0,0 @@
# Control Flow
Vide has specific functions for dealing with sources that store a table value.
Often, you will have a table of values that will be displayed in a similar
manner. Rather than manually looping over each value to generate a corresponding
UI element, Vide provides functions `indexes()` and `values()` to do this for
you.
`indexes()` maps each *index* in a table to a UI element.
```lua
local names = source { "a", "b", "c" }
local elements = indexes(names, function(name, i)
return create "TextLabel" {
Text = function()
return "Name: " .. name()
end,
LayoutOrder = i
}
end)
```
What happens here is the given callback is only ever ran *once* for each index
in the table. The callback receives two arguments, a *source* containing the
index's value and then the index itself.
Anytime the value at a corresponding index changes, the source for that index
value is updated, causing the UI element depending on it to update too.
`values()` behaves similarly, except it maps each *value* in a table to a UI
element.
```lua
type Item = {
Name: string,
Icon: number
}
local items = source({} :: Array<Item>)
local elements = values(items, function(item, i)
return create "ImageLabel" {
Image = "rbxassetid://" .. item.Icon,
LayoutOrder = i
}
end)
```
The callback is again only ever ran *once* for each value in the table. The
callback receives two arguments, a value in the table and then a *source*
containing the value's corresponding index.
Any time a value in a table changes index, the source for that value is updated,
causing the UI element position to change.
In certain cases `values()` can cause less recalculation and rerenders than
`indexes()` like when items are re-arranged and shifted within a table.
It is important that each value in a table is unique when using `values()`,
and for this reason always using `indexes()` if a table contains primitive
values.
Both `indexes()` and `values()` return an array of all mapped UI elements.

View file

@ -1,66 +0,0 @@
# Control Flow
Vide has specific functions for dealing with sources that store a table value.
Often, you will have a table of values that will be displayed in a similar
manner. Rather than manually looping over each value to generate a corresponding
UI element, Vide provides functions `indexes()` and `values()` to do this for
you.
`indexes()` maps each *index* in a table to a UI element.
```lua
local names = source { "a", "b", "c" }
local elements = indexes(names, function(name, i)
return create "TextLabel" {
Text = function()
return "Name: " .. name()
end,
LayoutOrder = i
}
end)
```
What happens here is the given callback is only ever ran *once* for each index
in the table. The callback receives two arguments, a *source* containing the
index's value and then the index itself.
Anytime the value at a corresponding index changes, the source for that index
value is updated, causing the UI element depending on it to update too.
`values()` behaves similarly, except it maps each *value* in a table to a UI
element.
```lua
type Item = {
Name: string,
Icon: number
}
local items = source({} :: Array<Item>)
local elements = values(items, function(item, i)
return create "ImageLabel" {
Image = "rbxassetid://" .. item.Icon,
LayoutOrder = i
}
end)
```
The callback is again only ever ran *once* for each value in the table. The
callback receives two arguments, a value in the table and then a *source*
containing the value's corresponding index.
Any time a value in a table changes index, the source for that value is updated,
causing the UI element position to change.
In certain cases `values()` can cause less recalculation and rerenders than
`indexes()` like when items are re-arranged and shifted within a table.
It is important that each value in a table is unique when using `values()`,
and for this reason always using `indexes()` if a table contains primitive
values.
Both `indexes()` and `values()` return an array of all mapped UI elements.

View file

@ -1,66 +0,0 @@
# Control Flow
Vide has specific functions for dealing with sources that store a table value.
Often, you will have a table of values that will be displayed in a similar
manner. Rather than manually looping over each value to generate a corresponding
UI element, Vide provides functions `indexes()` and `values()` to do this for
you.
`indexes()` maps each *index* in a table to a UI element.
```lua
local names = source { "a", "b", "c" }
local elements = indexes(names, function(name, i)
return create "TextLabel" {
Text = function()
return "Name: " .. name()
end,
LayoutOrder = i
}
end)
```
What happens here is the given callback is only ever ran *once* for each index
in the table. The callback receives two arguments, a *source* containing the
index's value and then the index itself.
Anytime the value at a corresponding index changes, the source for that index
value is updated, causing the UI element depending on it to update too.
`values()` behaves similarly, except it maps each *value* in a table to a UI
element.
```lua
type Item = {
Name: string,
Icon: number
}
local items = source({} :: Array<Item>)
local elements = values(items, function(item, i)
return create "ImageLabel" {
Image = "rbxassetid://" .. item.Icon,
LayoutOrder = i
}
end)
```
The callback is again only ever ran *once* for each value in the table. The
callback receives two arguments, a value in the table and then a *source*
containing the value's corresponding index.
Any time a value in a table changes index, the source for that value is updated,
causing the UI element position to change.
In certain cases `values()` can cause less recalculation and rerenders than
`indexes()` like when items are re-arranged and shifted within a table.
It is important that each value in a table is unique when using `values()`,
and for this reason always using `indexes()` if a table contains primitive
values.
Both `indexes()` and `values()` return an array of all mapped UI elements.

View file

@ -5,5 +5,27 @@ be set with `vide.strict = true` once when you first require Vide. Strict mode
will add extra safety checks and emit better error traces, particularly when
errors occur in property bindings.
Strict mode will run derived sources and effects twice each time they update.
This is to help identify improper cleanup of side-effects and ensure that pure
computations are actually pure.
```lua
local source = vide.source
local effect = vide.effect
vide.strict = true
local count = source(0)
local ran = 0
effect(function()
ran += 1
end)
print(ran) -- 2
count(1)
print(ran) -- 4
```
A full list of what strict mode will do can be found
[here](../../api/strict-mode).

View file

@ -1,56 +1,105 @@
# Control Flow
Eventually you will need a way to dynamically create and destroy UI elements
resulting from state changes. Vide provides functions to help you do this,
resulting from source updates. Vide provides functions to help you do this,
known as *control flow* functions.
These functions return a new source, which holds the instances to be displayed.
These functions return new sources, which hold the instances to be displayed.
These sources can be assigned as children, meaning the displayed children
will update when the input source updates.
One of these functions is `switch()`, used to conditionally show one of a set of
components.
Control flow functions are special, because they run their components in a new
reactive scope, which can be destroyed independently of the reactive scope that
called the control flow function itself. This means that parts of your app can
be independently created then destroyed and cleaned.
## show()
The most basic control flow function is `show()`, which is used to conditionally
show a component.
```lua
local source = vide.source
local show = vide.show
local function JoinMenu()
local joined = source(false)
local function JoinButton()
return Button {
Activated = function() joined(true) end
}
end
return create "Frame" {
show(function() return not joined() end, JoinButton)
}
end
```
This will make a button to join if you have not joined already.
You can also pass a third argument, a fallback to show if the condition is falsey.
```lua
local function JoinMenu()
local joined = source(false)
local function JoinButton()
return Button {
Activated = function() joined(true) end
}
end
local function LeaveButton()
return Button {
Activated = function() joined(false) end
}
end
return create "Frame" {
show(joined, LeaveButton, JoinButton)
}
end
```
## switch()
Similar to `show()`, `switch()`, also condtionally displays one instance at a
time. It is more flexible since it can show one of many components, based on a
table used to map a source value to a component.
```lua
local vide = require(vide)
local source = vide.source
local switch = vide.switch
local function ToggleButton(p: {
Text: string,
Toggle: (boolean) -> boolean
})
return create "TextButton" {
Size = UDim2.fromOffset(300, 300),
Text = p.Text,
Activated = function()
p.Toggle(not p.Toggle())
end
local function JoinMenu()
local joined = source(false)
local function JoinButton()
return Button {
Activated = function() joined(true) end
}
end
local loggedIn = source(false)
local function LoginMenu()
return Frame {
switch(loggedIn) {
[true] = function()
return ToggleButton { Text = "Log out", Toggle = loggedIn }
end,
[false] = function()
return ToggleButton { Text = "Log in", Toggle = loggedIn }
end
local function LeaveButton()
return Button {
Activated = function() joined(false) end
}
end
return create "Frame" {
switch(joined) {
[true] = LeaveButton,
[false] = JoinButton
}
}
end
mount(function() return create "ScreenGui" { LoginMenu {} } end, game.StarterGui)
```
Above is an example of using a switch to create a login menu. Each time
`loggedIn` toggles, the current button will be destroyed, and a new button
created, which the text to represent the current action, to log in or log out.
Above is an example of using a switch to create a join menu. Each time
`joined` toggles, the current button will be destroyed, and a new button
created, which the text to represent the current action, to join or leave.
The callbacks given to control flow functions are ran in a new reactive-scope,
so any cleanups registered will be ran when the input is changed and a new