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: "Property Nesting", link: "/tut/crash-course/12-property-nesting" },
|
||||
{ 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
|
||||
|
||||
This is a brief tutorial designed to give you a quick run through the usage of
|
||||
Vide.
|
||||
This is a tutorial that introduces the concepts and usage of Vide.
|
||||
|
||||
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?
|
||||
|
||||
Creating UI is a slow and tedious process. The purpose of Vide is to make UI
|
||||
declarative and concise, making it faster to create and more importantly easier
|
||||
to maintain. Vide achieves this using a reactive style of programming which
|
||||
allows you to focus on the flow of data through your application without
|
||||
worrying about manually updating UI instances.
|
||||
Creating UI is complicated, slow, and tedious.
|
||||
|
||||
Vide tries to simplify and speed up this process by providing a declarative and
|
||||
reactive of style programming, which lets you focus more on designing the UI
|
||||
itself and not having to manually update or reparent UI instances.
|
||||
|
||||
Some of the main focuses behind Vide's design choices:
|
||||
|
||||
- Concise syntax.
|
||||
- Being completely typecheckable.
|
||||
- Independence from instance lifetimes.
|
||||
- Real reactivity.
|
||||
- Minimal syntax.
|
||||
- Complete typechecking
|
||||
- Independence from instances.
|
||||
|
||||
## Structure Of A Vide App
|
||||
|
||||
The entry point for all Vide apps is the `mount()` function. This function
|
||||
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)
|
||||
```
|
||||
As with most declarative libraries, there is an initial learning curve to
|
||||
understand the concepts and usage. This tutorial tries to comprehensively
|
||||
cover these concepts and usage, more so than you need just to use it.
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
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
|
||||
is used to register a cleanup callback for the next time the reactive scope
|
||||
it is called in re-runs.
|
||||
is used to queue a cleanup callback for the next time a reactive scope re-runs.
|
||||
|
||||
```lua
|
||||
local mount = vide.mount
|
||||
|
|
@ -33,18 +32,33 @@ end
|
|||
|
||||
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
|
||||
when the timer component is destroyed, whether that is from unmounting the app
|
||||
or if it is dynamically created by a control-flow function, which will be
|
||||
covered next.
|
||||
when the reactive scope responsible for creating the timer component is
|
||||
destroyed, such as when it is unmounted.
|
||||
|
||||
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
|
||||
that any registered cleanups created by your app components can be ran when
|
||||
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:
|
||||
|
||||
```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.
|
||||
|
||||
```lua
|
||||
local mount = vide.mount
|
||||
local create = vide.create
|
||||
|
||||
local function App()
|
||||
return create "ScreenGui" {
|
||||
create "Frame" {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
# 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.
|
||||
|
||||
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.
|
||||
|
||||
::: 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.
|
||||
|
||||
Components allow you to *encapsulate* behavior. You can only modify the
|
||||
component in ways that you allow in the component, through the `props` parameter.
|
||||
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. 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.
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
scope, it can automatically rerun the scope that reads it when the source is
|
||||
updated in the future.
|
||||
with *effects*. Similar to a signal and connection, a source and effect allows
|
||||
you to do things like automatically updating UI when a source is updated.
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
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.
|
||||
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
|
||||
within them.
|
||||
|
||||
|
|
@ -53,16 +53,22 @@ The reactive graph for the above example looks like so:
|
|||
|
||||
graph
|
||||
|
||||
subgraph root["mount"]
|
||||
subgraph root
|
||||
direction LR
|
||||
count --> effect
|
||||
end
|
||||
```
|
||||
|
||||
When the `mount` scope is destroyed, the `effect` scope will also be destroyed
|
||||
since it was created within it.
|
||||
When the root reactive scope created by `mount()` is destroyed, the `effect`
|
||||
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
|
||||
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.
|
||||
Vide will prevent you from accidently doing otherwise anyways.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# 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
|
||||
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
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
Instead of explicitly creating an effect, assigning a (non-event) property
|
||||
a function will implicitly create a side-effect to update that property anytime
|
||||
a dependent source is updated.
|
||||
Instead of explicitly creating an effect, assigning a (non-event) property a
|
||||
function will implicitly create an effect to update that property anytime a
|
||||
source used within is updated.
|
||||
|
||||
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
|
||||
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.
|
||||
of your program changes. You just define how data sources map to UI, and Vide's
|
||||
reactive system will automatically update any properties depending on those
|
||||
sources.
|
||||
|
||||
## Children Binding
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,10 @@ local text = function()
|
|||
return "count: " .. tostring(count())
|
||||
end
|
||||
|
||||
effect(function()
|
||||
text() -- prints "ran"
|
||||
end)
|
||||
effect(function() text() end)
|
||||
effect(function() text() end)
|
||||
|
||||
effect(function()
|
||||
text() -- prints "ran" again
|
||||
end)
|
||||
source(1) -- prints "ran" x2
|
||||
```
|
||||
|
||||
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())
|
||||
end)
|
||||
|
||||
effect(function()
|
||||
text() -- prints "ran"
|
||||
end)
|
||||
effect(function() text() end)
|
||||
effect(function() text() end)
|
||||
|
||||
effect(function()
|
||||
text() -- does not print, returns cached value
|
||||
end)
|
||||
source(1) -- prints "ran" x1
|
||||
```
|
||||
|
||||
`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
|
||||
not rerun the effects using it.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue