4.3 KiB
Dynamic Scopes
Dynamic scopes are scopes that are created and destroyed in response to source updates. This is needed for conditionally rendering parts of your UI, such as opening and closing menus.
While Vide provides functions for common ways to do this, this section will show how you can implement them yourself so you are not limited by only what is provided.
Recreating show()
The most basic one, show(), can be
implemented yourself like so:
local function show(toggle: () -> unknown, component: () -> Instance)
return derive(function()
return if toggle() then untrack(component) else nil
end)
end
The main thing to note here is the use of untrack(). This function runs its
callback in a new stable scope. Without this, if the component were to create
a reactive scope, an error would occur since a reactive scope cannot be created
within a reactive scope.
%%{init: {
"theme": "base",
"themeVariables": {
"primaryColor": "#111720",
"primaryTextColor": "#fff",
"primaryBorderColor": "#444455",
"lineColor": "#79B8FF",
"tertiaryColor": "#0d131b",
"tertiaryBorderColor": "#444455"
}
}}%%
graph
subgraph derive ["derive (reactive)"]
subgraph untrack ["untrack (stable)"]
subgraph effect ["effect (reactive)"]
end
end
end
You can see from the above graph how the effect would not be created directly inside the derive, there is a stable scope between them. This requirement exists as a guard against unintentional rerendering of UI.
Recreating switch()
local function switch(key)
return function(map)
return derive(function()
local component = map[key()]
return if component then untrack(component) else nil
end)
end
end
Recreating indexes()
This is a more complicated function because it manages multiple scopes at the
same time, unlike the previous functions. Because some scopes may persist
between reruns, we cannot use untrack() anymore which automatically destroys
on rerun; we must use root() where the lifetime of each scope is managed
manually and independently.
local function indexes<I, VI, VO>(
input: () -> Map<I, VI>,
transform: (value: () -> VI, index: I) -> VO
)
local index_caches = {} :: Map<I, {
previous_input: VI,
output: VO,
source: (VI) -> VI,
destroy: () -> ()
}?>
-- destroy all scopes if the parent scope is destroyed
cleanup(function()
for _, cache in index_caches do
assert(cache).destroy()
end
end)
return derive(function()
local new_input = input()
-- destroy scopes of removed indexes
for i, cache in index_caches do
if new_input[i] == nil then
assert(cache).destroy()
index_caches[i] = nil
end
end
-- create scopes or update sources of added or changed index values
for i, v in new_input do
local cache = index_caches[i]
if cache == nil then -- no scope created for this index, create one
local src = source(v)
local destroy, result = root(function()
return transform(src, i)
end)
index_caches[i] = {
destroy = destroy,
source = src,
output = result,
previous_input = v
}
elseif cache.previous_input ~= v then -- scope exists, update source
cache.previous_input = v
cache.source(v)
else -- scope exists and value has not changed; do nothing
end
end
-- return the cached output values as an array
local array = table.create(#index_caches)
for _, cache in index_caches do
table.insert(array, assert(cache).output)
end
return array
end)
end
Though the above functions are already provided to you by Vide, this serves as an example for how you may create your own dynamic scope functions.