Update docs

This commit is contained in:
Aaron Smith 2023-09-28 16:39:25 +01:00
parent 9b6be14441
commit 46937c6979
16 changed files with 157 additions and 116 deletions

View file

@ -60,7 +60,7 @@ This code will produce a graph that looks like so:
}
}}%%
flowchart
graph
subgraph root
forename & surname --> name
name --> effect
@ -145,7 +145,7 @@ This code produces a graph like so:
}
}}%%
flowchart LR
graph LR
subgraph root
counters --> indexes
@ -208,7 +208,7 @@ The above code produces a graph like so:
}
}}%%
flowchart LR
graph LR
subgraph root
direction LR
show

View file

@ -5,6 +5,8 @@ Vide.
Vide is heavily inspired by [Solid](https://www.solidjs.com/).
This tutorial assumes familiarity with Luau and Roblox GUI.
## Why Vide?
Creating UI is a slow and tedious process. The purpose of Vide is to make UI

View file

@ -6,7 +6,7 @@ is used to register a cleanup callback for the next time the reactive scope
it is called in re-runs.
```lua
locla mount = vide.mount
local mount = vide.mount
local source = vide.source
local cleanup = vide.cleanup
@ -60,11 +60,11 @@ The reactive graph for the above example:
}
}}%%
flowchart
graph
subgraph root
subgraph mount
direction LR
cleanup([cleanup]) ~~~ count
count --> bind[text binding]
count --> bind["effect (text binding)"]
end
```

View file

@ -11,7 +11,7 @@ will update when the input source updates.
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.
be independently created then destroyed.
## show()
@ -78,21 +78,24 @@ The reactive graph for the above example:
}
}}%%
flowchart
graph
subgraph root ["mount() scope"]
subgraph root["mount() scope"]
direction LR
joined --> show -.- subroot
subgraph subroot ["show() scope"]
subgraph subroot["show() scope"]
direction LR
Button
end
end
```
The dotted line indicates that the new reactive scope isn't actually connected
to the `show` on the graph, it is only managed internally through code.
`show()` will implicitly create an effect depending on `joined`, which can be
seen as `show` on the graph. This effect manages, and can create or destroy
a separate reactive scope seen as `show() scope` on the graph. The dotted line
indicates that it isn't actually connected, only indirectly managed through
code.
## switch()
@ -135,11 +138,11 @@ The switch can map any value to any component.
```lua
type ActiveMenu = "none" | "inventory" | "shop" | "settings"
local menu = source "none"
local menu = source "inventory"
switch(menu) {
inventory = InventoryMenu,
shop = ShopMenu.
shop = ShopMenu,
settings = SettingsMenu
}
```
@ -159,25 +162,25 @@ The reactive graph for the above example:
}
}}%%
flowchart
graph
subgraph root ["mount() scope"]
subgraph root["mount() scope"]
direction LR
joined --> show -.- subroot
menu --> switch -.- subroot
subgraph subroot ["switch() scope"]
subgraph subroot["switch() scope"]
direction LR
Button
Menu
end
end
```
## indexes()
Often, you will have a table of values that will be displayed in a similar
Often, you will have a table of values with each value displayed in a similar
manner. Rather than manually looping over each value to generate a corresponding
UI element, `indexes()` allows you to create an instance for each table index,
to display the value at that index.
UI element, `indexes()` allows you to create elements for each table index, to
display the value at that index.
```lua
local todoList = source {
@ -204,13 +207,22 @@ end
TodoList { list = todoList }
```
For each unique index in the passed table, the transform function will be called
with 1. a source containing the value of the index, 2. the index itself.
For each index in the given source table, the given function will be called
with:
1. a source containing the value of 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.
An element is only destroyed if the value of an index is set to `nil`.
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 reactive scope destroyed to clean up that
element.
`indexes()` is said to *map* each table index to a new UI element that can
update to display the current value at that index.
The reactive graph for the above example:
@ -227,7 +239,7 @@ The reactive graph for the above example:
}
}}%%
flowchart
graph
subgraph root ["mount() scope"]
direction LR
@ -245,5 +257,20 @@ subgraph root ["mount() scope"]
end
```
One thing to note regarding table sources, is that when you edit a table in a
source, you must set that table again to actually update the source.
```lua
local src = source { 1, 2 }
local data = src()
table.insert(data, 3) -- no effects will run
src(data) -- effects will run
```
Together, these control flow functions cover the majority of cases where you
need to dynamically create and destroy parts of your UI.
If you need to do something that these control flow functions cannot, you can
always use `mount()` within an effect to dynamically create and destroy
components on your own terms. Just remember to use `cleanup()` to unmount when
the effect reruns.

View file

@ -77,7 +77,7 @@ be parented.
```lua
type Children = {
-- allows us to also optionally pass a source that returns an array of children instead
-- also can optionally pass a source that returns an array of children too
Children = Array<Instance> | () -> Array<Instance>
}

View file

@ -1,8 +1,7 @@
# Actions
Actions in Vide are special callbacks that you can pass along with properties,
which will be called when those properties are being processed with the instance
being assigned to, allowing you to run custom code.
to run some code on an instance receiving them.
```lua
local action = vide.action
@ -20,24 +19,22 @@ create "TextLabel" {
-- will print "test"
```
Actions can be wrapped with functions to re-use specific behaviors. Below is
an example of an action used to listen for property changes:
Actions can be wrapped with functions for reuse. Below is an example of an
action used to listen for property changes:
```lua
local action = vide.action
local cleanup = vide.cleanup
local function changed(property: string, callback: (new) -> ())
local function changed(prop: string, callback: (new) -> ())
return action(function(instance)
local con = instance:GetPropertyChangedSignal(property):Connect(function()
local connection = instance:GetPropertyChangedSignal(prop):Connect(function()
callback(instance[property])
end)
-- remember to clean up the connection when the reactive scope the action
-- is ran in is destroyed, so the instance can be garbage collected
cleanup(function()
con:Disconnect()
end)
cleanup(connection)
end)
end

View file

@ -5,9 +5,13 @@ 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 is automatically enabled when Vide is required in O0 or O1
optimization (default studio level). You can `vide.strict = false` if you do not
want this.
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.
This is to help ensure that derived source computations are pure, and that any
cleanups made in derived sources or effects are done correctly.
```lua
local source = vide.source

View file

@ -45,19 +45,10 @@ Assign a value to a string key to set a property, and assign a value to a
number key to set a child. Events can be connected to by assigning a function
to a string key.
You can also use a shorthand to create datatypes instead of explicitly typing
out the class name and constructor. The table will be unpacked into the `.new()`
constructor of the property's type.
```lua
create "Frame" {
AnchorPoint = { 0.5, 1 },
UDim2 = { 0.5, 0, 0.5, 0 }
}
```
::: warning
When creating an instance with no properties, it is important to not forget to
actually call the constructor: `create "Frame" {}` and not `create "Frame"`.
To be clear, `create "Frame"` returns a *function* which is a constructor for
that class, not an instance of that class. This would result in you attempting
to parent a function instead of an instance which is not the correct behavior.
to parent a function instead of an instance which is not correct.
:::

View file

@ -1,9 +1,11 @@
# Components
Components are custom-made reusable pieces of UI made from other pieces of UI.
A component is a function that creates and returns a piece of UI.
By using components you can make your application more modular and better
organized.
This is a way to separate your app into small chunks that you can reuse and put
together.
::: code-group
```lua [Button.luau]
local create = vide.create
@ -15,11 +17,14 @@ local function Button(props: {
})
return create "TextButton" {
BackgroundColor3 = Color3.fromRGB(50, 50, 50),
TextColor3 = Color3.fromRGB(255, 255, 255),
Size = UDim2.fromOffset(200, 150),
Position = props.Position,
Text = props.Text,
Activated = props.Activated
Activated = props.Activated,
create "UICorner" {}
}
end
@ -36,10 +41,17 @@ local function App()
return create "ScreenGui" {
Button {
Position = UDim2.fromOffset(200, 200),
Text = "click me!",
Text = "back",
Activated = function()
print "clicked"
print "go to previous page"
end
},
Button {
Position = UDim2.fromOffset(400, 200),
Text = "next",
Activated = function()
print "go to next page"
end
}
}
@ -48,8 +60,9 @@ end
mount(App, game.StarterGui)
```
Above is a simple example of a button component with a set color and size,
being reused across files.
:::
Above is a simple example of a button component being used across files.
A single parameter `props` is used to pass properties to the component.
@ -57,8 +70,8 @@ Components allow you to *encapsulate* behavior. You can only modify the
component in ways that you allow in the component, through the `props` parameter.
To create a new button all you must do is call the `Button` function, passing in
values through props. This saves having to create and set every property each
time. Also, when updating the button component in future, any changes to the
button file will be seen anywhere the button is used in your app.
values. This saves having to create and set every property each time. Also, when
updating the button component in future, any changes to the button file will be
seen anywhere the button is used in your app.
This can be extended to much more complicated UI.

View file

@ -1,10 +1,9 @@
# Sources
*Sources* in Vide are special objects that store a single value. They are the
core of reactivity in Vide. Each source represents a source of data, and they
can be composed and derived to create new sources of data.
Sources are special objects that store a single value. They are the core of
Vide's reactivity. They are called sources because they act as sources of data.
A source in Vide can be created using `source()`.
A source can be created using `source()`.
```lua
local source = vide.source

View file

@ -21,12 +21,9 @@ count(1)
-- "count: 1" printed
```
The callback given to `effect()` is ran in a *reactive scope*. Any source read
from inside a reactive scope will be tracked, so that if any of those sources
update, the effect will be reran too.
The callback is first ran immediately inside the `effect()` call to initially
track sources used.
The callback given to `effect()` is initially ran immediately in a
*reactive scope*. Any source read from inside a reactive scope will be tracked,
so that if any of those sources update, the effect will be reran too.
Effects also work with derived sources, it doesn't matter how deeply nested
inside a function a source is.
@ -50,23 +47,5 @@ count(2)
-- "doubled count: 4" printed
```
The reactive graph for the above example:
```mermaid
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#1B1B1F",
"primaryTextColor": "#fff",
"primaryBorderColor": "#1B1B1F",
"lineColor": "#79B8FF",
"tertiaryColor": "#161618",
"tertiaryBorderColor": "#fff"
}
}}%%
flowchart LR
count --> effect
```
If a source is updated with the same value it already had, it will not rerun
effects depending on it.

View file

@ -1,7 +1,7 @@
# Root Reactive Scopes
Any reactive scopes created, such as one from `effect()`, must be done so within
a "root" reactive scope. This is the main purpose of `mount()`, which you use
Any reactive scopes created, such as by `effect()`, must be done so within a
"root" reactive scope. This is the main purpose of `mount()`, which you use
once at the top level to create your app as shown in the first introduction.
This is so that when the app is unmounted, it can clean up any reactive scopes
@ -25,7 +25,18 @@ vide.mount(App) -- works!
App() -- will error since effect() was not called within a reactive scope
```
The reactive graph for the above example:
Mounting returns a function that when called will destroy any reactive scopes
created during the `mount()` call.
```lua
local unmount = mount(App)
unmount()
```
Vide's reactivity can be represented graphically, as a *reactive graph*.
The reactive graph for the above example looks like so:
```mermaid
%%{init: {
@ -40,10 +51,18 @@ The reactive graph for the above example:
}
}}%%
flowchart
graph
subgraph root
subgraph root["mount"]
direction LR
count --> effect
end
```
When the `mount` scope is destroyed, the `effect` scope will also be destroyed
since it was created within it.
You don't need to worry about ensuring all your effects are created within a
root scope, since you should be creating all your UI and corresponding effects
within a top-level `mount()` call that puts all your UI together. So it is safe
to assume that any effect you create will be created under this top level scope.

View file

@ -5,6 +5,8 @@ A stateful component is a component that stores and displays some data.
Stateful components in Vide are created using sources and effects - sources to
store the data, and effects to display the data.
## Internal State
```lua
local create = vide.create
local source = vide.source
@ -30,12 +32,14 @@ end
Above is an example of a counter component, that when clicked, will increment
its internal count, and automatically update its text to reflect that count.
Making a property update based on a source is also called *property
Making a property update based on a source is also referred to as *property
binding*.
Each instance of `Counter()` will maintain its own independent count, since the
count source is created inside the scope of the component.
## External State
External sources can also be passed into components for them to use.
```lua
@ -65,5 +69,5 @@ count(1) -- the Counter component will update to display this count
```
Sources can be created internally or passed in from externally, there are no
restrictions on how they are used as long as the effect is created within a
reactive scope so that it can be tracked.
restrictions on how they are used as long as the effect using it is created
within a reactive scope so that it can be cleaned up later.

View file

@ -20,6 +20,7 @@ local function Counter()
count(count() + 1)
end
}
end
```
This example is equivalent to the example seen on the previous page.
@ -32,15 +33,16 @@ Just like effects, the function is ran immediately in a reactive scope to set
the property initially and determine what sources are being depended on.
This allows you as the programmer to not need to manually update UI as the state
of your program changes. You just define how the data maps to UI, and Vide's
reactive system will automatically update any properties depending on sources
that are updated.
of your program changes. You just define how the data sources map to UI, and
Vide's reactive system will automatically update any properties depending on
those sources that were updated.
## Children Binding
Children can also be set in a similar manner. Sources bound to properties can
return an instance or an array of instances. Vide will automatically unparent
removed instances and parent new instances.
Children can also be set in a similar manner. A source passed as a child (passed
with a number key instead of string key) can return an instance or an array of
instances. Vide will automatically unparent removed instances and parent new
instances when that source's stored instances change.
```lua
local items = source {

View file

@ -60,17 +60,15 @@ effect(function()
end)
effect(function()
text() -- does not print
text() -- does not print, returns cached value
end)
```
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,
improper usage could accidently create infinite loops in the reactive graph.
Always favour deriving when you need one source to update based on another.
`derive()` must also be used within a root reactive scope, just like `effect()`.
If the recalculated value is the same as the old value, the derived source will
not rerun the effects using it.
The reactive graph for the above example:
```mermaid
@ -86,10 +84,15 @@ The reactive graph for the above example:
}
}}%%
flowchart
graph
subgraph root
direction LR
count --> text --> effect1 & effect2
end
```
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,
improper usage could accidently create infinite loops in the reactive graph.
Always favour deriving when you need one source to update based on another.