mirror of
https://github.com/centau/vide.git
synced 2026-08-20 14:41:37 +00:00
Refactor
This commit is contained in:
parent
67e87110c7
commit
f2de9b0e63
25 changed files with 315 additions and 364 deletions
|
|
@ -1,7 +1,6 @@
|
|||
# Nested Reactive Scopes
|
||||
# Nested Scopes
|
||||
|
||||
Nesting reactive 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
|
||||
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
|
||||
|
|
@ -21,7 +20,7 @@ local function Counter()
|
|||
}
|
||||
end
|
||||
|
||||
mount(function()
|
||||
root(function()
|
||||
local toggled = source(true)
|
||||
|
||||
show(toggled, Button)
|
||||
|
|
@ -57,7 +56,7 @@ 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 reactive scope created within it. So the text
|
||||
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()`:
|
||||
|
|
@ -82,7 +81,10 @@ mount(function()
|
|||
|
||||
effect(function()
|
||||
if toggled() then
|
||||
local destroy = mount(Button)
|
||||
local destroy = root(function(destroy)
|
||||
Counter()
|
||||
return destroy
|
||||
end)
|
||||
cleanup(destroy)
|
||||
end
|
||||
end)
|
||||
|
|
@ -116,11 +118,14 @@ subgraph mount
|
|||
end
|
||||
```
|
||||
|
||||
This is another way to achieve the same. Here we use `mount()` within the effect
|
||||
to manually create and destroy a new reactive scope whenever the effect reruns.
|
||||
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.
|
||||
|
||||
Alternatively, instead of using `mount()`, a new reactive scope can be created
|
||||
directly within the effect:
|
||||
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()`:
|
||||
|
||||
```lua
|
||||
local mount = vide.mount
|
||||
|
|
@ -174,7 +179,9 @@ end
|
|||
```
|
||||
|
||||
Without the use of `untrack()`, an error would occur, since Vide does not allow
|
||||
the creation of reactive scopes inside reactive scopes that are tracking. The
|
||||
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
|
||||
|
|
@ -184,5 +191,3 @@ 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.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,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 queue a cleanup callback for the next time a reactive scope is rerun
|
||||
or destroyed.
|
||||
or destroyed, or when a stable scope is destroyed.
|
||||
|
||||
```lua
|
||||
local mount = vide.mount
|
||||
|
|
@ -31,14 +31,18 @@ local function Timer()
|
|||
}
|
||||
end
|
||||
|
||||
local unmount = mount(Timer)
|
||||
local instance, destroy = root(function(destroy)
|
||||
local instance = Timer()
|
||||
return instance, destroy
|
||||
end)
|
||||
|
||||
unmount() -- all queued cleanups are ran, heartbeat connection disconnected
|
||||
wait(5)
|
||||
|
||||
destroy() -- all queued cleanups are ran, heartbeat connection disconnected
|
||||
```
|
||||
|
||||
In the above example, this allows us to disconnect the heartbeat connection
|
||||
when the reactive scope responsible for creating the timer component is
|
||||
destroyed, such as when it is unmounted.
|
||||
when the scope responsible for creating the timer component is destroyed.
|
||||
|
||||
::: tip
|
||||
Roblox instances do not need to be explicitly destroyed for their
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ known as *control flow* functions.
|
|||
|
||||
These functions return new sources, which hold the instances to be displayed.
|
||||
|
||||
Control flow functions run their components in a new reactive scope, which can
|
||||
be destroyed independently of the reactive scope that called the control flow
|
||||
Control flow functions run their components in a new stable scope, which can
|
||||
be destroyed independently of the stable scope that called the control flow
|
||||
function. This means parts of your app can be independently created and
|
||||
destroyed.
|
||||
|
||||
|
|
@ -93,9 +93,9 @@ subgraph root["root scope"]
|
|||
end
|
||||
```
|
||||
|
||||
A `switch()` call creates a new effect and a new scope as seen in the above
|
||||
graph. Whenever `menu` updates, it causes the `switch` effect to run, which
|
||||
will destroy and recreate the switch scope with the new component.
|
||||
A `switch()` call creates a new effect and a new stable scope as seen in the
|
||||
above graph. Whenever `menu` updates, it causes the `switch` effect to run,
|
||||
which will destroy and recreate the switch scope with the new component.
|
||||
|
||||
This will also destroy the internal effect that the button uses to highlight
|
||||
itself when it is hovered, each time the switch is rerun.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ want this.
|
|||
|
||||
Strict mode will run derived sources and effects twice each time they update.
|
||||
This is to help ensure that derived source computations are pure, and that any
|
||||
cleanups made in derived sources or effects are done correctly.
|
||||
cleanups made in derived sources or effects are done properly.
|
||||
|
||||
```lua
|
||||
local source = vide.source
|
||||
|
|
|
|||
|
|
@ -22,52 +22,52 @@ Anything that happens in response to a source update.
|
|||
|
||||
Created with `effect()`.
|
||||
|
||||
## Reactive Scope
|
||||
## Stable Scope
|
||||
|
||||
A scope created by certain functions such as:
|
||||
One of the two types of Vide scopes.
|
||||
|
||||
Created by:
|
||||
|
||||
- `root()`
|
||||
- `untrack()`
|
||||
- `switch()`
|
||||
- `indexes()`
|
||||
|
||||
Stable scopes do not track sources and never rerun.
|
||||
|
||||
New stable or reactive scopes can be created within a stable scope.
|
||||
|
||||
## Reactive Scope
|
||||
|
||||
Created by:
|
||||
|
||||
- `effect()`
|
||||
- `derive()`
|
||||
|
||||
Reactive scopes can:
|
||||
Reactive scopes do track sources and will rerun when those sources update.
|
||||
|
||||
- track sources that are read from within.
|
||||
- rerun when a tracked source updates.
|
||||
- track new reactive scopes created from within.
|
||||
New reactive scopes cannot be created within a reactive scope, but stable scopes
|
||||
can.
|
||||
|
||||
## Scope Owners
|
||||
|
||||
A reactive scope created within another reactive scope is *owned* by the other
|
||||
reactive scope, with the exception of the reactive scope created by `root()`.
|
||||
A scope created within another scope is *owned* by the other scope, with the
|
||||
exception of the scope created by `root()`.
|
||||
|
||||
When a reactive scope is rerun or destroyed, all reactive scopes owned by it are
|
||||
automatically destroyed.
|
||||
When a scope is rerun or destroyed, all scopes owned by it are automatically
|
||||
destroyed.
|
||||
|
||||
`root()`, which `mount()` uses internally, creates a reactive scope with no
|
||||
owner, since it must be destroyed manually using a destructor
|
||||
returned.
|
||||
`root()` creates a stable scope with no owner, instead it is destroyed manually.
|
||||
|
||||
## Cleanup
|
||||
|
||||
Arbitrary code to run whenever a reactive scope is rerun or destroyed.
|
||||
Arbitrary code to run whenever a stable or reactive scope is rerun or destroyed.
|
||||
|
||||
Queue a function to run using `cleanup()`.
|
||||
|
||||
## Tracking
|
||||
|
||||
Sources read from within a reactive scope will be tracked. This can be disabled
|
||||
using `untrack()`, which will make reactive scopes temporarily ignore sources
|
||||
read.
|
||||
|
||||
The reactive scope created by `root()` is non-tracking by default.
|
||||
|
||||
As a guard against misusage, a reactive scope cannot be created within a
|
||||
reactive scope, unless it is made non-tracking using `untrack()`.
|
||||
|
||||
## Reactive Graph
|
||||
|
||||
The combination of reactive scopes can viewed graphically, called a
|
||||
The combination of stable and 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.
|
||||
|
||||
|
|
@ -114,10 +114,10 @@ count --> text
|
|||
Notes:
|
||||
|
||||
- Since `count` is a source, not an effect, it can exist
|
||||
outside of a root reactive scope.
|
||||
outside of scopes.
|
||||
- An update to `count` will cause `text` to rerun, which
|
||||
then causes `effect` to rerun.
|
||||
- When the root reactive scope is destroyed, `text` and
|
||||
- When the root 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.
|
||||
|
|
|
|||
|
|
@ -39,10 +39,3 @@ return create "ScreenGui" {
|
|||
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.
|
||||
|
||||
::: 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.
|
||||
:::
|
||||
|
|
|
|||
|
|
@ -58,20 +58,8 @@ local function App()
|
|||
}
|
||||
}
|
||||
end
|
||||
|
||||
App().Parent = game.StarterGui
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -1,43 +1,65 @@
|
|||
# Root Reactive Scopes
|
||||
# Scopes
|
||||
|
||||
Vide operates on the concept of scopes. Vide scopes come in two flavors:
|
||||
stable and reactive.
|
||||
|
||||
The three main rules for scopes are:
|
||||
|
||||
- Stable scopes never rerun.
|
||||
- Reactive scopes will rerun on source updates.
|
||||
- A reactive scope cannot be created within another reactive scope.
|
||||
|
||||
Reactive scopes cannot be created on their own - they must be created within
|
||||
another reactive scope so that it can be tracked and later destroyed when it is
|
||||
a stable scope so that it can be tracked and later destroyed when it is
|
||||
no longer needed.
|
||||
|
||||
This is the purpose of `mount()`, which creates an initial "root", or
|
||||
"top-level" reactive scope, which all other reactive scopes, such as
|
||||
ones created by `effect()`, can stem from.
|
||||
This is the purpose of `root()`, which creates an initial stable scope, which
|
||||
all other reactive scopes, such as ones created by `effect()`, can stem from.
|
||||
|
||||
When this root reactive scope is destroyed, it will ensure all other reactive
|
||||
scopes created within it are also destroyed, ensuring everything is cleaned up
|
||||
properly.
|
||||
When this root reactive scope is destroyed, it will destroy any effects created
|
||||
within it, ensuring everything is cleaned up properly.
|
||||
|
||||
```lua
|
||||
local source = vide.source
|
||||
local effect = vide.effect
|
||||
|
||||
local function App()
|
||||
local function setup()
|
||||
local count = source(0)
|
||||
|
||||
effect(function()
|
||||
print(count())
|
||||
end)
|
||||
|
||||
return count
|
||||
end
|
||||
|
||||
setup() -- will error since effect() was not called within a stable scope
|
||||
|
||||
App() -- will error since effect() was not called within a reactive scope
|
||||
|
||||
vide.mount(App) -- works!
|
||||
|
||||
local count = vide.root(setup) -- runs
|
||||
count(1) -- prints "1"
|
||||
```
|
||||
|
||||
Mounting returns a function that when called will destroy its reactive scope,
|
||||
along with any other reactive scopes created inside it.
|
||||
The scope created by `root()` can be destroyed by calling the function it passes
|
||||
into the given function.
|
||||
|
||||
```lua
|
||||
local unmount = mount(App)
|
||||
local function setup(destroy)
|
||||
local count = source(0)
|
||||
|
||||
unmount()
|
||||
effect(function()
|
||||
print(count())
|
||||
end)
|
||||
|
||||
return count, destroy
|
||||
end
|
||||
|
||||
local count, destroy = root(setup)
|
||||
|
||||
count(1) -- prints "1"
|
||||
|
||||
destroy()
|
||||
|
||||
count(2) -- effect is destroyed; no longer prints
|
||||
```
|
||||
|
||||
Vide's reactivity can be represented graphically, as a *reactive graph*.
|
||||
|
|
@ -65,7 +87,7 @@ subgraph root
|
|||
end
|
||||
```
|
||||
|
||||
When the root reactive scope created by `mount()` is destroyed, the `effect`
|
||||
When the root reactive scope created by `root()` 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
|
||||
|
|
@ -75,6 +97,6 @@ instance to be garbage collected.
|
|||
|
||||
You don't need to worry about ensuring all your effects are created within a
|
||||
root reactive 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
|
||||
effects within a top-level `root()` 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.
|
||||
|
|
@ -27,8 +27,6 @@ local function Counter()
|
|||
|
||||
return instance
|
||||
end
|
||||
|
||||
mount(Counter, game.StarterGui)
|
||||
```
|
||||
|
||||
Above is an example of a counter component, that when clicked, will increment
|
||||
|
|
@ -37,9 +35,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
|
||||
count source is created inside the component.
|
||||
|
||||
We use `mount()` to create the counter within a reactive scope, which also takes
|
||||
a second argument to parent the counter to another instance.
|
||||
|
||||
## External State
|
||||
|
||||
External sources can also be passed into components for them to use.
|
||||
|
|
@ -72,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.
|
||||
within a stable scope.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Property Binding
|
||||
# Implicit Effects
|
||||
|
||||
Explicitly creating effects to update properties can be tedious. Vide provides a
|
||||
way to *implicitly* create an effect to update properties.
|
||||
|
|
@ -31,12 +31,7 @@ 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 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 data sources map to UI, and Vide's
|
||||
reactive system will automatically update any properties depending on those
|
||||
sources.
|
||||
|
||||
## Children Binding
|
||||
## Children
|
||||
|
||||
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
|
||||
|
|
@ -36,7 +36,7 @@ source(1) -- prints "ran" x2
|
|||
```
|
||||
|
||||
To avoid this, you can use `derive()` to derive a new source instead. This will
|
||||
run a callback in a new reactive scope only when a dependent source has updated.
|
||||
run a function in a new reactive scope only when a dependent source has updated.
|
||||
Reading this derived source multiple times will just return a cached result from
|
||||
when it last updated.
|
||||
|
||||
|
|
@ -58,7 +58,7 @@ effect(function() text() end)
|
|||
source(1) -- prints "ran" x1
|
||||
```
|
||||
|
||||
`derive()` must also be called within a reactive scope, just like `effect()`.
|
||||
`derive()` must also be called within a stable 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