mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
Try improve crash course
This commit is contained in:
parent
c288cb92c4
commit
338c66ed57
11 changed files with 223 additions and 102 deletions
|
|
@ -51,7 +51,8 @@ export default withMermaid({
|
||||||
{ text: "Control Flow", link: "/tut/crash-course/11-control-flow" },
|
{ text: "Control Flow", link: "/tut/crash-course/11-control-flow" },
|
||||||
{ text: "Property Nesting", link: "/tut/crash-course/12-property-nesting" },
|
{ text: "Property Nesting", link: "/tut/crash-course/12-property-nesting" },
|
||||||
{ text: "Actions", link: "/tut/crash-course/13-actions" },
|
{ text: "Actions", link: "/tut/crash-course/13-actions" },
|
||||||
{ text: "Strict Mode", link: "/tut/crash-course/14-strict-mode" }
|
{ text: "Strict Mode", link: "/tut/crash-course/14-strict-mode" },
|
||||||
|
{ text: "Concepts Summary", link: "/tut/crash-course/15-concepts" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,25 @@
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
This is a brief tutorial designed to give you a quick run through the usage of
|
This is a tutorial that introduces the concepts and usage of Vide.
|
||||||
Vide.
|
|
||||||
|
|
||||||
Vide is heavily inspired by [Solid](https://www.solidjs.com/).
|
Vide is heavily inspired by [Solid](https://www.solidjs.com/).
|
||||||
|
|
||||||
This tutorial assumes familiarity with Luau and Roblox GUI.
|
This tutorial assumes familiarity with Luau and Roblox UI.
|
||||||
|
|
||||||
## Why Vide?
|
## Why Vide?
|
||||||
|
|
||||||
Creating UI is a slow and tedious process. The purpose of Vide is to make UI
|
Creating UI is complicated, slow, and tedious.
|
||||||
declarative and concise, making it faster to create and more importantly easier
|
|
||||||
to maintain. Vide achieves this using a reactive style of programming which
|
Vide tries to simplify and speed up this process by providing a declarative and
|
||||||
allows you to focus on the flow of data through your application without
|
reactive of style programming, which lets you focus more on designing the UI
|
||||||
worrying about manually updating UI instances.
|
itself and not having to manually update or reparent UI instances.
|
||||||
|
|
||||||
Some of the main focuses behind Vide's design choices:
|
Some of the main focuses behind Vide's design choices:
|
||||||
|
|
||||||
- Concise syntax.
|
- Minimal syntax.
|
||||||
- Being completely typecheckable.
|
- Complete typechecking
|
||||||
- Independence from instance lifetimes.
|
- Independence from instances.
|
||||||
- Real reactivity.
|
|
||||||
|
|
||||||
## Structure Of A Vide App
|
As with most declarative libraries, there is an initial learning curve to
|
||||||
|
understand the concepts and usage. This tutorial tries to comprehensively
|
||||||
The entry point for all Vide apps is the `mount()` function. This function
|
cover these concepts and usage, more so than you need just to use it.
|
||||||
sets up Vide's reactivity system. It takes and calls a function that should
|
|
||||||
create your entire app, and will apply its result to a target.
|
|
||||||
|
|
||||||
In Vide, your app should be composed of functions, each function creates a
|
|
||||||
specific part of your app, and can be reused if needed. These functions are
|
|
||||||
called *components*.
|
|
||||||
|
|
||||||
```lua
|
|
||||||
local function App()
|
|
||||||
return {
|
|
||||||
PlayerStats(),
|
|
||||||
Inventory(),
|
|
||||||
Settings()
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
mount(App, game.StarterGui)
|
|
||||||
```
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
Sometimes you may need to do some cleanup when destroying a component or after
|
Sometimes you may need to do some cleanup when destroying a component or after
|
||||||
a side-effect from a source update. Vide provides a function `cleanup()` which
|
a side-effect from a source update. Vide provides a function `cleanup()` which
|
||||||
is used to register a cleanup callback for the next time the reactive scope
|
is used to queue a cleanup callback for the next time a reactive scope re-runs.
|
||||||
it is called in re-runs.
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local mount = vide.mount
|
local mount = vide.mount
|
||||||
|
|
@ -33,18 +32,33 @@ end
|
||||||
|
|
||||||
local unmount = mount(Timer)
|
local unmount = mount(Timer)
|
||||||
|
|
||||||
unmount() -- all registered cleanups are ran, heartbeat connection stopped
|
unmount() -- all queued cleanups are ran, heartbeat connection stopped
|
||||||
```
|
```
|
||||||
|
|
||||||
In the above example, this allows us to disconnect the heartbeat connection
|
In the above example, this allows us to disconnect the heartbeat connection
|
||||||
when the timer component is destroyed, whether that is from unmounting the app
|
when the reactive scope responsible for creating the timer component is
|
||||||
or if it is dynamically created by a control-flow function, which will be
|
destroyed, such as when it is unmounted.
|
||||||
covered next.
|
|
||||||
|
Vide does not see "components", it only sees reactive scopes and how they are
|
||||||
|
linked together. Components are just a user pattern that creates UI instances
|
||||||
|
alongside effects. In other words, instances are just a side-effect of the
|
||||||
|
reactive graph. When a reactive scope is created, you create a corresponding
|
||||||
|
instance to display that data, when that reactive scope is destroyed, any
|
||||||
|
cleanups queued will be ran and take care of anything that needs to be, such
|
||||||
|
as disconnecting connections.
|
||||||
|
|
||||||
This is another reason why `mount()` is used at the top level of your app, so
|
This is another reason why `mount()` is used at the top level of your app, so
|
||||||
that any registered cleanups created by your app components can be ran when
|
that any registered cleanups created by your app components can be ran when
|
||||||
they are destroyed.
|
they are destroyed.
|
||||||
|
|
||||||
|
Side note: Roblox instances do not need to be explicitly destroyed for their
|
||||||
|
memory to be freed, they only need to be parented to `nil`. So there is no
|
||||||
|
need to use `cleanup()` to destroy instances. However, be wary of connecting
|
||||||
|
a function that references an instance to an event from the same instance,
|
||||||
|
this causes the instance to reference itself and never be freed. In such a case
|
||||||
|
you would need to use `cleanup()` to disconnect this connection or to explicitly
|
||||||
|
destroy the instance.
|
||||||
|
|
||||||
The reactive graph for the above example:
|
The reactive graph for the above example:
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|
|
||||||
132
docs/tut/crash-course/15-concepts.md
Normal file
132
docs/tut/crash-course/15-concepts.md
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
# Concepts Summary
|
||||||
|
|
||||||
|
A summary of all the concepts covered during the crash course.
|
||||||
|
|
||||||
|
## Source
|
||||||
|
|
||||||
|
A source of data.
|
||||||
|
|
||||||
|
Stores a single value that can be updated by the user.
|
||||||
|
|
||||||
|
## Effect
|
||||||
|
|
||||||
|
Anything that happens in reponse to a source update.
|
||||||
|
|
||||||
|
Vide has built-in functions to create effects such as
|
||||||
|
|
||||||
|
- `effect()` - runs arbitrary user code on source update
|
||||||
|
- `derive()` - updates a derived source on source update
|
||||||
|
|
||||||
|
## Reactive Scope
|
||||||
|
|
||||||
|
A scope created by certain Vide functions where source updates can be tracked,
|
||||||
|
and cleanups queued.
|
||||||
|
|
||||||
|
When a source used inside a reactive scope is updated, the reactive scope will
|
||||||
|
rerun.
|
||||||
|
|
||||||
|
Reactive scopes are created by functions such as
|
||||||
|
|
||||||
|
- `root()`
|
||||||
|
- `effect()`
|
||||||
|
- `derive()`
|
||||||
|
|
||||||
|
## Owner
|
||||||
|
|
||||||
|
A reactive scope created within an outer reactive scope, is *owned* by the outer
|
||||||
|
reactive scope.
|
||||||
|
|
||||||
|
When a reactive scope is re-ran or destroyed, all reactive scopes owned by it
|
||||||
|
are also destroyed.
|
||||||
|
|
||||||
|
Vide does not let you create reactive scopes without owners.
|
||||||
|
|
||||||
|
## Root Reactive Scope
|
||||||
|
|
||||||
|
A top-level reactive scope. These scopes are an exception to the owner rule.
|
||||||
|
|
||||||
|
Created by `root()`, which `mount()` uses internally.
|
||||||
|
|
||||||
|
A root reactive scope can be created on its own. It allows other reactive scopes
|
||||||
|
to be created with an owner.
|
||||||
|
|
||||||
|
Root reactive scopes must be destroyed manually by the user, a function to do
|
||||||
|
this is given by `root()`.
|
||||||
|
|
||||||
|
A root reactive scope can be created within another reactive scope and it will
|
||||||
|
not automatically be owned by that scope.
|
||||||
|
|
||||||
|
## Cleanup
|
||||||
|
|
||||||
|
Cleans up the result from an effect.
|
||||||
|
|
||||||
|
Unneeded in most cases, a cleanup is arbitrary code that can be ran before
|
||||||
|
a reactive scope is rerun or destroyed, so that the result from the previous
|
||||||
|
run can be cleaned up. A cleanup can be queued by using `cleanup()` within
|
||||||
|
a reactive scope.
|
||||||
|
|
||||||
|
## Tracking
|
||||||
|
|
||||||
|
Reactive scopes are tracking by default, meaning sources read from within scope
|
||||||
|
will be tracked.
|
||||||
|
|
||||||
|
A reactive scope can be made temporarily non-tracking within `untrack()`, so
|
||||||
|
that any source used will be ignored. The only function that creates a
|
||||||
|
nontracking reactive scope by default is `root()`.
|
||||||
|
|
||||||
|
## Reactive Graph
|
||||||
|
|
||||||
|
The combination of reactive scopes can viewed graphically, called a
|
||||||
|
*reactive graph*. This can be a more intuitive way to think of the
|
||||||
|
relationships between effects and the sources they depend on.
|
||||||
|
|
||||||
|
### Code
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local count = source(0)
|
||||||
|
|
||||||
|
root(function()
|
||||||
|
local text = derive(function()
|
||||||
|
return "count: " .. text()
|
||||||
|
end)
|
||||||
|
|
||||||
|
effect(function()
|
||||||
|
print(text())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Graph resulting from code
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
%%{init: {
|
||||||
|
"theme": "base",
|
||||||
|
"themeVariables": {
|
||||||
|
"primaryColor": "#1B1B1F",
|
||||||
|
"primaryTextColor": "#fff",
|
||||||
|
"primaryBorderColor": "#1B1B1F",
|
||||||
|
"lineColor": "#79B8FF",
|
||||||
|
"tertiaryColor": "#161618",
|
||||||
|
"tertiaryBorderColor": "#1C1C1F"
|
||||||
|
}
|
||||||
|
}}%%
|
||||||
|
|
||||||
|
graph LR
|
||||||
|
|
||||||
|
subgraph root
|
||||||
|
text --> effect
|
||||||
|
end
|
||||||
|
|
||||||
|
count --> text
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- Since `count` is a source, not an effect, it can exist
|
||||||
|
outside of a root reactive scope.
|
||||||
|
- An update to `count` will cause `text` to rerun, which
|
||||||
|
then causes `effect` to rerun.
|
||||||
|
- When the root reactive scope is destroyed, `text` and
|
||||||
|
`effect` will be destroyed alongside it, since they are
|
||||||
|
owned by it. `count` will be untouched and future updates
|
||||||
|
to `count` will have no effect.
|
||||||
|
|
@ -9,10 +9,8 @@ Luau allows us to omit parentheses `()` when calling functions with string or
|
||||||
table literals which Vide takes advantage of for brevity.
|
table literals which Vide takes advantage of for brevity.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local mount = vide.mount
|
|
||||||
local create = vide.create
|
local create = vide.create
|
||||||
|
|
||||||
local function App()
|
|
||||||
return create "ScreenGui" {
|
return create "ScreenGui" {
|
||||||
create "Frame" {
|
create "Frame" {
|
||||||
AnchorPoint = Vector2.new(0.5, 0.5),
|
AnchorPoint = Vector2.new(0.5, 0.5),
|
||||||
|
|
@ -36,9 +34,6 @@ local function App()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
|
||||||
|
|
||||||
mount(App, game.StarterGui)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Assign a value to a string key to set a property, and assign a value to a
|
Assign a value to a string key to set a property, and assign a value to a
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
# Components
|
# Components
|
||||||
|
|
||||||
|
Vide encourages separating different parts of your UI into functions called
|
||||||
|
*components*.
|
||||||
|
|
||||||
A component is a function that creates and returns a piece of UI.
|
A component is a function that creates and returns a piece of UI.
|
||||||
|
|
||||||
This is a way to separate your app into small chunks that you can reuse and put
|
This is a way to separate your UI into small chunks that you can reuse and put
|
||||||
together.
|
together.
|
||||||
|
|
||||||
::: code-group
|
::: code-group
|
||||||
|
|
@ -66,12 +69,15 @@ 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.
|
A single parameter `props` is used to pass properties to the component.
|
||||||
|
|
||||||
Components allow you to *encapsulate* behavior. You can only modify the
|
You can only modify the component in ways that you allow in the component,
|
||||||
component in ways that you allow in the component, through the `props` parameter.
|
through the `props` parameter.
|
||||||
|
|
||||||
To create a new button all you must do is call the `Button` function, passing in
|
To create a new button all you must do is call the `Button` function, passing in
|
||||||
values. This saves having to create and set every property each time. Also, when
|
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
|
updating the button component in future, any changes to the button file will be
|
||||||
seen anywhere the button is used in your app.
|
seen anywhere the button is used in your app.
|
||||||
|
|
||||||
This can be extended to much more complicated UI.
|
The `mount()` function is used to set up Vide's reactivity system when creating
|
||||||
|
your UI. It only needs to be called once at the top-level with the function that
|
||||||
|
puts together your entire app. It also parents the returned instance to another
|
||||||
|
a target instance for you.
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,6 @@ count(count() + 1) -- increment count by 1
|
||||||
Sources can be *derived* by wrapping them in functions. A wrapped source
|
Sources can be *derived* by wrapping them in functions. A wrapped source
|
||||||
effectively becomes a new source.
|
effectively becomes a new source.
|
||||||
|
|
||||||
Derived sources should be pure functions. This is where the same output is
|
|
||||||
always produced for the same input no matter how many times it is reran.
|
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
local count = source(0)
|
local count = source(0)
|
||||||
|
|
||||||
|
|
@ -40,6 +37,5 @@ print(text()) -- "count: 1"
|
||||||
|
|
||||||
Sources on their own aren't very special, the above can be achieved with plain
|
Sources on their own aren't very special, the above can be achieved with plain
|
||||||
variables. The real use for sources become apparent when used in combination
|
variables. The real use for sources become apparent when used in combination
|
||||||
with Vide's *reactive scopes*. When a source is read from within a reactive
|
with *effects*. Similar to a signal and connection, a source and effect allows
|
||||||
scope, it can automatically rerun the scope that reads it when the source is
|
you to do things like automatically updating UI when a source is updated.
|
||||||
updated in the future.
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
Any reactive scopes created, such as by `effect()`, must be done so within a
|
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
|
"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.
|
once at the top level to create your UI.
|
||||||
|
|
||||||
This is so that when the app is unmounted, it can clean up any reactive scopes
|
This is so that if you want to destroy your UI, it can stop any reactive scopes
|
||||||
created within it, since reactive scopes track any reactive scopes created
|
created within it, since reactive scopes track any reactive scopes created
|
||||||
within them.
|
within them.
|
||||||
|
|
||||||
|
|
@ -53,16 +53,22 @@ The reactive graph for the above example looks like so:
|
||||||
|
|
||||||
graph
|
graph
|
||||||
|
|
||||||
subgraph root["mount"]
|
subgraph root
|
||||||
direction LR
|
direction LR
|
||||||
count --> effect
|
count --> effect
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
When the `mount` scope is destroyed, the `effect` scope will also be destroyed
|
When the root reactive scope created by `mount()` is destroyed, the `effect`
|
||||||
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
|
||||||
|
UI instance, meaning the effect is referencing and holding that instance in
|
||||||
|
memory. The effect being destroyed will remove this reference, allowing the
|
||||||
|
instance to be garbage collected.
|
||||||
|
|
||||||
You don't need to worry about ensuring all your effects are created within a
|
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
|
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
|
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.
|
to assume that any effect you create will be created under this top level scope.
|
||||||
|
Vide will prevent you from accidently doing otherwise anyways.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Stateful Components
|
# Stateful Components
|
||||||
|
|
||||||
A stateful component is a component that stores and displays some data.
|
A stateful component is a component that can update in reponse to data.
|
||||||
|
|
||||||
Stateful components in Vide are created using sources and effects - sources to
|
Stateful 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.
|
||||||
|
|
@ -32,9 +32,6 @@ end
|
||||||
Above is an example of a counter component, that when clicked, will increment
|
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.
|
its internal count, and automatically update its text to reflect that count.
|
||||||
|
|
||||||
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
|
Each instance of `Counter()` will maintain its own independent count, since the
|
||||||
count source is created inside the scope of the component.
|
count source is created inside the scope of the component.
|
||||||
|
|
||||||
|
|
@ -70,4 +67,4 @@ count(1) -- the Counter component will update to display this count
|
||||||
|
|
||||||
Sources can be created internally or passed in from externally, there are no
|
Sources can be created internally or passed in from externally, there are no
|
||||||
restrictions on how they are used as long as the effect using it is created
|
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.
|
within a reactive scope.
|
||||||
|
|
|
||||||
|
|
@ -25,17 +25,17 @@ 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
|
Instead of explicitly creating an effect, assigning a (non-event) property a
|
||||||
a function will implicitly create a side-effect to update that property anytime
|
function will implicitly create an effect to update that property anytime a
|
||||||
a dependent source is updated.
|
source used within is updated.
|
||||||
|
|
||||||
Just like effects, the function is ran immediately in a reactive scope to set
|
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.
|
the property initially and determine what sources are being used.
|
||||||
|
|
||||||
This allows you as the programmer to not need to manually update UI as the state
|
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 sources map to UI, and
|
of your program changes. You just define how data sources map to UI, and Vide's
|
||||||
Vide's reactive system will automatically update any properties depending on
|
reactive system will automatically update any properties depending on those
|
||||||
those sources that were updated.
|
sources.
|
||||||
|
|
||||||
## Children Binding
|
## Children Binding
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,13 +29,10 @@ local text = function()
|
||||||
return "count: " .. tostring(count())
|
return "count: " .. tostring(count())
|
||||||
end
|
end
|
||||||
|
|
||||||
effect(function()
|
effect(function() text() end)
|
||||||
text() -- prints "ran"
|
effect(function() text() end)
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function()
|
source(1) -- prints "ran" x2
|
||||||
text() -- prints "ran" again
|
|
||||||
end)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To avoid this, you can use `derive()` to derive a new source instead. This will
|
To avoid this, you can use `derive()` to derive a new source instead. This will
|
||||||
|
|
@ -55,16 +52,13 @@ local text = derive(function()
|
||||||
return "count: " .. tostring(count())
|
return "count: " .. tostring(count())
|
||||||
end)
|
end)
|
||||||
|
|
||||||
effect(function()
|
effect(function() text() end)
|
||||||
text() -- prints "ran"
|
effect(function() text() end)
|
||||||
end)
|
|
||||||
|
|
||||||
effect(function()
|
source(1) -- prints "ran" x1
|
||||||
text() -- does not print, returns cached value
|
|
||||||
end)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`derive()` must also be used within a root reactive scope, just like `effect()`.
|
`derive()` must also be called within a reactive 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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue