diff --git a/docs/tut/crash-course/1-introduction.md b/docs/tut/crash-course/1-introduction.md index 9436414..aa334f8 100644 --- a/docs/tut/crash-course/1-introduction.md +++ b/docs/tut/crash-course/1-introduction.md @@ -27,21 +27,21 @@ Some of the main focuses behind Vide's design choices: ## Structure Of A Vide App -The entry point for all Vide apps is the `root()` function. This function -sets up Vide's reactivity system and allows proper disposal of your app. It -takes and calls a function that should create your entire app, then returns the -result. +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 create "ScreenGui" { create "TextLabel" { Text = "hi" } } end -root(App).Parent = game.StarterGui +mount(App, game.StarterGui) ``` diff --git a/docs/tut/crash-course/2-creation.md b/docs/tut/crash-course/2-creation.md index c0ca3c4..deb1683 100644 --- a/docs/tut/crash-course/2-creation.md +++ b/docs/tut/crash-course/2-creation.md @@ -1,62 +1,54 @@ # Creating UI -Instances are created using [`create()`](../../api/creation.md#create). - -```lua -local vide = require(vide) -local create = vide.create -``` +Instances are created using `create()`. `create()` returns a constructor for a class which then takes a table of properties to assign when creating a new instance for that class. Luau allows us to omit parentheses `()` when calling functions with string or -table literals for brevity. +table literals which Vide takes advantage of for brevity. ```lua -local frame = create "Frame" { - Name = "Background", - Position = UDim2.fromScale(0.5, 0.5) -} -``` +local vide = require(vide) +local mount = vide.mount +local create = vide.create -String keys are treated as properties and integer keys are treated as child -instances. +local function App() + return create "ScreenGui" { + create "Frame" { + AnchorPoint = Vector2.new(0.5, 0.5), + Position = UDim2.fromScale(0.5, 0.5), + Size = UDim2.fromScale(0.4, 0.7), -```lua -create "ScreenGui" { - Parent = game.StarterGui, + create "TextLabel" { + Text = "hi" + }, - create "Frame" { - AnchorPoint = Vector2.new(0.5, 0.5), - Position = UDim2.fromScale(0.5, 0.5), - Size = UDim2.fromScale(0.4, 0.7), + create "TextLabel" { + Text = "bye" + }, - create "TextLabel" { - Text = "hi" - }, + create "TextButton" { + Text = "click me", - create"TextLabel" { - Text = "bye" + Activated = function() + print "clicked!" + end + } } } -} +end + +mount(App, game.StarterGui) ``` -To connect to an event, just assign the event property a function. +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. -All event arguments are passed into the function. - -```lua -create "TextButton" { - Activated = function() - print "clicked!" - end -} -``` - -You can also use a form of aggregate initialization to create datatypes instead -of explicitly typing out the class name and constructor. +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" { @@ -64,7 +56,3 @@ create "Frame" { UDim2 = { 0.5, 0, 0.5, 0 } } ``` - -When a property is assigned a table, Vide will inspect the type of the property -being assigned to, and call that type's default `new()` constructor with the -unpacked values from the assigned table. diff --git a/docs/tut/crash-course/3-components.md b/docs/tut/crash-course/3-components.md index 42f3fb1..712ab9a 100644 --- a/docs/tut/crash-course/3-components.md +++ b/docs/tut/crash-course/3-components.md @@ -29,6 +29,7 @@ return Button ```lua [App.luau] local vide = require(vide) +local mount = vide.mount local create = vide.create local Button = require(Button) @@ -46,7 +47,7 @@ local function App() } end -root(App).Parent = game.StarterGui +mount(App, game.StarterGui) ``` Above is a simple example of a button component with a set color and size, diff --git a/docs/tut/crash-course/4-source.md b/docs/tut/crash-course/4-source.md index c1213dc..cf7ceb2 100644 --- a/docs/tut/crash-course/4-source.md +++ b/docs/tut/crash-course/4-source.md @@ -4,35 +4,18 @@ core of reactivity in Vide, as updates to a source can automatically update properties or other sources depending on that source. -A source in Vide can be created using -[`source()`](../../api/reactivity-core.md#source). +A source in Vide can be created using `source()`. ```lua -local source = vide.source -local count = source(0) -``` - -The value passed to `source()` is the initial value of the source. - -The value of a source can be set by calling it with an argument, and can be read -by calling it with no arguments. - -```lua -count(count() + 1) -- increment source by 1 -``` - -Below is an example of a stateful counter component. - -```lua [Counter.luau] local vide = require(vide) local source = vide.source -local function Counter(props: { Position: UDim2 }) +local function Counter() local count = source(0) return create "TextButton" { - Position = props.Position, - Size = UDim2.new(200, 50), + Position = UDim2.fromOffset(300, 300), + Size = UDim2.fromOffset(200, 50), Text = count, @@ -41,18 +24,28 @@ local function Counter(props: { Position: UDim2 }) end } end + +mount(function() return create "ScreenGui" { Counter {} } end, game.StarterGui) ``` -Each call of `Counter {}` will create a new counter element, each with their own -independent count. +The value passed to `source()` is the initial value of the source. -Vide detects when you assign a function to a property. This is known -as *binding* and doing so will cause the property to *automatically* update -whenever a source in that function is updated, by rerunning the function and -assigning its return value. You can only bind non-event -properties, otherwise the function is connected as the event callback. +The value of a source can be set by calling it with an argument, and can be read +by calling it with no arguments. -This allows you as the programmer to not need to manually update GUI as the state +```lua +count(count() + 1) -- increment count by 1 +``` + +Each call of `Counter {}` will create a new counter, each maintaining their +own count. + +When you assign a function to a non-event property, Vide will immediately run it +and check what sources were read from. When updating those sources again after, +this function will be re-ran and its return value applied to the property. +This is known as *binding* properties. + +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 surgically update any properties depending on sources that -are changed. +reactive system will automatically update any properties depending on sources +that are updated. diff --git a/docs/tut/crash-course/5-effect.md b/docs/tut/crash-course/5-effect.md index e69de29..0dc77bf 100644 --- a/docs/tut/crash-course/5-effect.md +++ b/docs/tut/crash-course/5-effect.md @@ -0,0 +1,42 @@ +# Effect + +An effect is a function that is run anytime a source updates. They are called +effects because they can produce side-effects when reacting to source changes. + +Effects are created using `effect()`. + +```lua +local vide = require(vide) +local source = vide.source +local effect = vide.effect + +local function Counter() + local count = source(0) + + effect(function() + print("count has updated to: " .. count()) + end) + + return create "TextButton" { + Position = UDim2.fromOffset(300, 300), + Size = UDim2.fromOffset(200, 50), + + Text = count, + + Activated = function() + count(count() + 1) + end + } +end + +mount(function() return create "ScreenGui" { Counter {} } end, game.StarterGui) +``` + +This will print to the terminal anytime the count is changed. + +`effect()` creates an explicit side-effect. There are other side-effects in the +above code sample. The setting of `Text = count` creates another side-effect; +the updating of the Text property anytime the count is changed. + +All observable changes to the user are considered to be side-effects of the +reactive system. diff --git a/docs/tut/crash-course/6-derived-source.md b/docs/tut/crash-course/6-derived-source.md index 673d078..01fe1c2 100644 --- a/docs/tut/crash-course/6-derived-source.md +++ b/docs/tut/crash-course/6-derived-source.md @@ -8,95 +8,69 @@ used inside a function is updated, the whole function can be re-ran to recompute its value. ```lua -local count = source(0) +local vide = require(vide) +local source = vide.source -local function text() - return "count: " .. count() +local function Counter() + local count = source(0) + + local function doubled() + return count() * 2 + end + + return create "TextButton" { + Position = UDim2.fromOffset(300, 300), + Size = UDim2.fromOffset(200, 50), + + Text = doubled, + + Activated = function() + count(count() + 1) + end + } end -create "TextLabel" { - Text = text -} +mount(function() return create "ScreenGui" { Counter {} } end, game.StarterGui) ``` +Now the counter will increment in 2s each time it is clicked. + Sometimes when using expensive computations to derive state, you only want to -recalculate it once when a source state has changed - -If you wrap a source with a regular function, its value will be recomputed -every time you call that function. -[`derive()`](../../api/reactivity-core.md#derive) accepts a functions whose -return value will be cached, so that subsequent calls of this derived source -will return the same cached value until one of its input sources have changed. +recalculate it once when a source state has changed. Although not needed in +most cases, you can use `derive()` to create a new source that will cache its +value, only recomputing when an input source has changed. ```lua +local vide = require(vide) +local source = vide.source local derive = vide.derive -``` -```lua -local count = source(0) +local function Counter() + local count = source(0) -local factorial = derive(function() - local n = 1 - for i = 2, count() do - n *= i - end - return n -end) + local factorial = derive(function() + local n = 1 + for i = 2, count() do + n *= i + end + return n + end) + + return create "TextButton" { + Position = UDim2.fromOffset(300, 300), + Size = UDim2.fromOffset(200, 50), + + Text = function() + return factorial() + factorial() + factorial() + end, + + Activated = function() + count(count() + 1) + end + } +end ``` This can improve performance in cases where a source is read from multiple times -between recalculations, like in the example below: - -```lua -create "TextLabel" { - Text = function() - return "factorial squared: " .. factorial() * factorial() - end -} - -count(3) -- displays "factorial squared: 36" -count(4) -- displays "factorial squared: 576" -``` - -Vide knows what sources are being depended on by immediately running the -callback when deriving or binding sources. If a source is in a function but is -never referenced the first time it runs, Vide will not know to rerun the -function if that source changes. - -An example to effect out for is when using sources within branches: - -```lua -local condition = source(true) -local count1 = source(0) -local count2 = source(0) - -local text = function() - if condition() then - return "text: " .. count1() - else - return "text: " .. count2() - end -end -``` - -In the above case, only `count1` will be referenced, meaning `text` will not be -aware of `count2` even if the condition is later set to false. - -All sources to be tracked must be referenced the first time the function runs. - -```lua -local condition = source(true) -local count1 = source(0) -local count2 = source(0) - -local text = function() - local c1 = count1() - local c2 = count2() - - if condition() then - return "text: " .. c1 - else - return "text: " .. c2 - end -end -``` +between recalculations. In the above example, the factorial is only ever +calculated once each time the count changes. diff --git a/docs/tut/crash-course/7-cleanup.md b/docs/tut/crash-course/7-cleanup.md new file mode 100644 index 0000000..3e5525d --- /dev/null +++ b/docs/tut/crash-course/7-cleanup.md @@ -0,0 +1,40 @@ +# Cleanup + +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. + +```lua +local vide = require(vide) +local source = vide.source +local cleanup = vide.cleanup + +local function Timer() + local count = source(0) + + local con = game:GetService("RunService").Heartbeat:Connect(function(dt) + count(count() + dt) + end) + + cleanup(function() + con:Disconnect() + end) + + return create "TextButton" { + Position = UDim2.fromOffset(300, 300), + Size = UDim2.fromOffset(200, 50), + + Text = function() + return "seconds: " .. count() + end, + } +end + +mount(function() return create "ScreenGui" { Timer {} } end, game.StarterGui) +``` + +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. diff --git a/docs/tut/crash-course/8-cleanup.md b/docs/tut/crash-course/8-cleanup.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/tut/crash-course/7-control-flow.md b/docs/tut/crash-course/8-control-flow.md similarity index 99% rename from docs/tut/crash-course/7-control-flow.md rename to docs/tut/crash-course/8-control-flow.md index 04c6dee..70864e7 100644 --- a/docs/tut/crash-course/7-control-flow.md +++ b/docs/tut/crash-course/8-control-flow.md @@ -1,4 +1,4 @@ -# Table Source +# Control Flow Vide has specific functions for dealing with sources that store a table value. diff --git a/src/mount.luau b/src/mount.luau new file mode 100644 index 0000000..4de1985 --- /dev/null +++ b/src/mount.luau @@ -0,0 +1,16 @@ +if not game then script = require "test/relative-string" end + +local root = require(script.Parent.root) +local apply = require(script.Parent.apply) + +local function mount(app: () -> T, target: Instance?): () -> () + local _, destroy = root(function() + local result = app() + if target then apply(target, { result }) end + return nil + end) + + return destroy +end + +return mount