diff --git a/docs/api/strict-mode.md b/docs/api/strict-mode.md index 35de4d8..958672e 100644 --- a/docs/api/strict-mode.md +++ b/docs/api/strict-mode.md @@ -22,8 +22,9 @@ Currently, strict mode will: 6. Checks for duplicate nested properties at same depth. 7. Better error reporting and stack traces + creation traces of property bindings. -By rerunning sources and effects, any side-effects are made more apparent. -This also helps ensure that cleanups are being handled correctly. +By rerunning derived sources and effects twice each time they update,it helps +ensure that derived source computations are pure, and that any +cleanups made in derived sources or effects are done correctly. Accidental yielding within reactive scopes can break Vide's reactive graph, which strict mode can catch. diff --git a/docs/tut/advanced/reactive-scoping.md b/docs/tut/advanced/reactive-scoping.md index c9ed084..20872cf 100644 --- a/docs/tut/advanced/reactive-scoping.md +++ b/docs/tut/advanced/reactive-scoping.md @@ -60,7 +60,7 @@ This code will produce a graph that looks like so: } }}%% -flowchart +graph subgraph root forename & surname --> name name --> effect @@ -145,7 +145,7 @@ This code produces a graph like so: } }}%% -flowchart LR +graph LR subgraph root counters --> indexes @@ -208,7 +208,7 @@ The above code produces a graph like so: } }}%% -flowchart LR +graph LR subgraph root direction LR show diff --git a/docs/tut/crash-course/1-introduction.md b/docs/tut/crash-course/1-introduction.md index ccf739a..604f7ae 100644 --- a/docs/tut/crash-course/1-introduction.md +++ b/docs/tut/crash-course/1-introduction.md @@ -5,6 +5,8 @@ Vide. Vide is heavily inspired by [Solid](https://www.solidjs.com/). +This tutorial assumes familiarity with Luau and Roblox GUI. + ## Why Vide? Creating UI is a slow and tedious process. The purpose of Vide is to make UI diff --git a/docs/tut/crash-course/10-cleanup.md b/docs/tut/crash-course/10-cleanup.md index df4614d..4f62233 100644 --- a/docs/tut/crash-course/10-cleanup.md +++ b/docs/tut/crash-course/10-cleanup.md @@ -6,7 +6,7 @@ is used to register a cleanup callback for the next time the reactive scope it is called in re-runs. ```lua -locla mount = vide.mount +local mount = vide.mount local source = vide.source local cleanup = vide.cleanup @@ -60,11 +60,11 @@ The reactive graph for the above example: } }}%% -flowchart +graph -subgraph root +subgraph mount direction LR cleanup([cleanup]) ~~~ count - count --> bind[text binding] + count --> bind["effect (text binding)"] end ``` diff --git a/docs/tut/crash-course/11-control-flow.md b/docs/tut/crash-course/11-control-flow.md index dcb0bbf..e5430b8 100644 --- a/docs/tut/crash-course/11-control-flow.md +++ b/docs/tut/crash-course/11-control-flow.md @@ -11,7 +11,7 @@ will update when the input source updates. Control flow functions are special, because they run their components in a new reactive scope, which can be destroyed independently of the reactive scope that called the control flow function itself. This means that parts of your app can -be independently created then destroyed and cleaned. +be independently created then destroyed. ## show() @@ -78,21 +78,24 @@ The reactive graph for the above example: } }}%% -flowchart +graph -subgraph root ["mount() scope"] +subgraph root["mount() scope"] direction LR joined --> show -.- subroot - subgraph subroot ["show() scope"] + subgraph subroot["show() scope"] direction LR Button end end ``` -The dotted line indicates that the new reactive scope isn't actually connected -to the `show` on the graph, it is only managed internally through code. +`show()` will implicitly create an effect depending on `joined`, which can be +seen as `show` on the graph. This effect manages, and can create or destroy +a separate reactive scope seen as `show() scope` on the graph. The dotted line +indicates that it isn't actually connected, only indirectly managed through +code. ## switch() @@ -135,11 +138,11 @@ The switch can map any value to any component. ```lua type ActiveMenu = "none" | "inventory" | "shop" | "settings" -local menu = source "none" +local menu = source "inventory" switch(menu) { inventory = InventoryMenu, - shop = ShopMenu. + shop = ShopMenu, settings = SettingsMenu } ``` @@ -159,25 +162,25 @@ The reactive graph for the above example: } }}%% -flowchart +graph -subgraph root ["mount() scope"] +subgraph root["mount() scope"] direction LR - joined --> show -.- subroot + menu --> switch -.- subroot - subgraph subroot ["switch() scope"] + subgraph subroot["switch() scope"] direction LR - Button + Menu end end ``` ## indexes() -Often, you will have a table of values that will be displayed in a similar +Often, you will have a table of values with each value displayed in a similar manner. Rather than manually looping over each value to generate a corresponding -UI element, `indexes()` allows you to create an instance for each table index, -to display the value at that index. +UI element, `indexes()` allows you to create elements for each table index, to +display the value at that index. ```lua local todoList = source { @@ -204,13 +207,22 @@ end TodoList { list = todoList } ``` -For each unique index in the passed table, the transform function will be called -with 1. a source containing the value of the index, 2. the index itself. +For each index in the given source table, the given function will be called +with: + +1. a source containing the value of the index +2. the index itself When the value at an index is changed, the function is not reran. Instead, the given source for that index is updated. -An element is only destroyed if the value of an index is set to `nil`. +Any time the input source table is updated, the given function will be ran for +any newly added indexes, while any removed indexes (indexes now with a `nil` +value), will have its corresponding reactive scope destroyed to clean up that +element. + +`indexes()` is said to *map* each table index to a new UI element that can +update to display the current value at that index. The reactive graph for the above example: @@ -227,7 +239,7 @@ The reactive graph for the above example: } }}%% -flowchart +graph subgraph root ["mount() scope"] direction LR @@ -245,5 +257,20 @@ subgraph root ["mount() scope"] end ``` +One thing to note regarding table sources, is that when you edit a table in a +source, you must set that table again to actually update the source. + +```lua +local src = source { 1, 2 } +local data = src() +table.insert(data, 3) -- no effects will run +src(data) -- effects will run +``` + Together, these control flow functions cover the majority of cases where you need to dynamically create and destroy parts of your UI. + +If you need to do something that these control flow functions cannot, you can +always use `mount()` within an effect to dynamically create and destroy +components on your own terms. Just remember to use `cleanup()` to unmount when +the effect reruns. diff --git a/docs/tut/crash-course/12-property-nesting.md b/docs/tut/crash-course/12-property-nesting.md index ea2ae43..d8c44ce 100644 --- a/docs/tut/crash-course/12-property-nesting.md +++ b/docs/tut/crash-course/12-property-nesting.md @@ -77,7 +77,7 @@ be parented. ```lua type Children = { - -- allows us to also optionally pass a source that returns an array of children instead + -- also can optionally pass a source that returns an array of children too Children = Array | () -> Array } diff --git a/docs/tut/crash-course/13-actions.md b/docs/tut/crash-course/13-actions.md index 79ca7c7..06b431d 100644 --- a/docs/tut/crash-course/13-actions.md +++ b/docs/tut/crash-course/13-actions.md @@ -1,8 +1,7 @@ # Actions Actions in Vide are special callbacks that you can pass along with properties, -which will be called when those properties are being processed with the instance -being assigned to, allowing you to run custom code. +to run some code on an instance receiving them. ```lua local action = vide.action @@ -20,24 +19,22 @@ create "TextLabel" { -- will print "test" ``` -Actions can be wrapped with functions to re-use specific behaviors. Below is -an example of an action used to listen for property changes: +Actions can be wrapped with functions for reuse. Below is an example of an +action used to listen for property changes: ```lua local action = vide.action local cleanup = vide.cleanup -local function changed(property: string, callback: (new) -> ()) +local function changed(prop: string, callback: (new) -> ()) return action(function(instance) - local con = instance:GetPropertyChangedSignal(property):Connect(function() + local connection = instance:GetPropertyChangedSignal(prop):Connect(function() callback(instance[property]) end) -- remember to clean up the connection when the reactive scope the action -- is ran in is destroyed, so the instance can be garbage collected - cleanup(function() - con:Disconnect() - end) + cleanup(connection) end) end diff --git a/docs/tut/crash-course/14-strict-mode.md b/docs/tut/crash-course/14-strict-mode.md index 3cb09e6..ca8465a 100644 --- a/docs/tut/crash-course/14-strict-mode.md +++ b/docs/tut/crash-course/14-strict-mode.md @@ -5,9 +5,13 @@ be set with `vide.strict = true` once when you first require Vide. Strict mode will add extra safety checks and emit better error traces, particularly when errors occur in property bindings. +Strict mode is automatically enabled when Vide is required in O0 or O1 +optimization (default studio level). You can `vide.strict = false` if you do not +want this. + Strict mode will run derived sources and effects twice each time they update. -This is to help identify improper cleanup of side-effects and ensure that pure -computations are actually pure. +This is to help ensure that derived source computations are pure, and that any +cleanups made in derived sources or effects are done correctly. ```lua local source = vide.source diff --git a/docs/tut/crash-course/2-creation.md b/docs/tut/crash-course/2-creation.md index 68e3007..e66f987 100644 --- a/docs/tut/crash-course/2-creation.md +++ b/docs/tut/crash-course/2-creation.md @@ -45,19 +45,10 @@ 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. -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" { - AnchorPoint = { 0.5, 1 }, - UDim2 = { 0.5, 0, 0.5, 0 } -} -``` - +::: 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. This would result in you attempting -to parent a function instead of an instance which is not the correct behavior. +to parent a function instead of an instance which is not correct. +::: diff --git a/docs/tut/crash-course/3-components.md b/docs/tut/crash-course/3-components.md index 3050cf8..a9f38d9 100644 --- a/docs/tut/crash-course/3-components.md +++ b/docs/tut/crash-course/3-components.md @@ -1,9 +1,11 @@ # Components -Components are custom-made reusable pieces of UI made from other pieces of UI. +A component is a function that creates and returns a piece of UI. -By using components you can make your application more modular and better -organized. +This is a way to separate your app into small chunks that you can reuse and put +together. + +::: code-group ```lua [Button.luau] local create = vide.create @@ -15,11 +17,14 @@ local function Button(props: { }) return create "TextButton" { BackgroundColor3 = Color3.fromRGB(50, 50, 50), + TextColor3 = Color3.fromRGB(255, 255, 255), Size = UDim2.fromOffset(200, 150), Position = props.Position, Text = props.Text, - Activated = props.Activated + Activated = props.Activated, + + create "UICorner" {} } end @@ -36,10 +41,17 @@ local function App() return create "ScreenGui" { Button { Position = UDim2.fromOffset(200, 200), - Text = "click me!", - + Text = "back", Activated = function() - print "clicked" + print "go to previous page" + end + }, + + Button { + Position = UDim2.fromOffset(400, 200), + Text = "next", + Activated = function() + print "go to next page" end } } @@ -48,8 +60,9 @@ end mount(App, game.StarterGui) ``` -Above is a simple example of a button component with a set color and size, -being reused across files. +::: + +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. @@ -57,8 +70,8 @@ Components allow you to *encapsulate* behavior. 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 through props. 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. +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. diff --git a/docs/tut/crash-course/4-source.md b/docs/tut/crash-course/4-source.md index f8f9e86..f5a848a 100644 --- a/docs/tut/crash-course/4-source.md +++ b/docs/tut/crash-course/4-source.md @@ -1,10 +1,9 @@ # Sources -*Sources* in Vide are special objects that store a single value. They are the -core of reactivity in Vide. Each source represents a source of data, and they -can be composed and derived to create new sources of data. +Sources are special objects that store a single value. They are the core of +Vide's reactivity. They are called sources because they act as sources of data. -A source in Vide can be created using `source()`. +A source can be created using `source()`. ```lua local source = vide.source diff --git a/docs/tut/crash-course/5-effect.md b/docs/tut/crash-course/5-effect.md index 22cd199..9f0dd6b 100644 --- a/docs/tut/crash-course/5-effect.md +++ b/docs/tut/crash-course/5-effect.md @@ -21,12 +21,9 @@ count(1) -- "count: 1" printed ``` -The callback given to `effect()` is ran in a *reactive scope*. Any source read -from inside a reactive scope will be tracked, so that if any of those sources -update, the effect will be reran too. - -The callback is first ran immediately inside the `effect()` call to initially -track sources used. +The callback given to `effect()` is initially ran immediately in a +*reactive scope*. Any source read from inside a reactive scope will be tracked, +so that if any of those sources update, the effect will be reran too. Effects also work with derived sources, it doesn't matter how deeply nested inside a function a source is. @@ -50,23 +47,5 @@ count(2) -- "doubled count: 4" printed ``` -The reactive graph for the above example: - -```mermaid -%%{init: { - "theme": "base", - "themeVariables": { - "primaryColor": "#1B1B1F", - "primaryTextColor": "#fff", - "primaryBorderColor": "#1B1B1F", - "lineColor": "#79B8FF", - "tertiaryColor": "#161618", - "tertiaryBorderColor": "#fff" - } -}}%% - -flowchart LR - -count --> effect - -``` +If a source is updated with the same value it already had, it will not rerun +effects depending on it. diff --git a/docs/tut/crash-course/6-root.md b/docs/tut/crash-course/6-root.md index ff45184..2b2d7ad 100644 --- a/docs/tut/crash-course/6-root.md +++ b/docs/tut/crash-course/6-root.md @@ -1,7 +1,7 @@ # Root Reactive Scopes -Any reactive scopes created, such as one from `effect()`, must be done so within -a "root" reactive scope. This is the main purpose of `mount()`, which you use +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. This is so that when the app is unmounted, it can clean up any reactive scopes @@ -25,7 +25,18 @@ vide.mount(App) -- works! App() -- will error since effect() was not called within a reactive scope ``` -The reactive graph for the above example: +Mounting returns a function that when called will destroy any reactive scopes +created during the `mount()` call. + +```lua +local unmount = mount(App) + +unmount() +``` + +Vide's reactivity can be represented graphically, as a *reactive graph*. + +The reactive graph for the above example looks like so: ```mermaid %%{init: { @@ -40,10 +51,18 @@ The reactive graph for the above example: } }}%% -flowchart +graph -subgraph root +subgraph root["mount"] direction LR count --> effect end ``` + +When the `mount` scope is destroyed, the `effect` scope will also be destroyed +since it was created within it. + +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. diff --git a/docs/tut/crash-course/7-stateful-component.md b/docs/tut/crash-course/7-stateful-component.md index 246e050..8de8209 100644 --- a/docs/tut/crash-course/7-stateful-component.md +++ b/docs/tut/crash-course/7-stateful-component.md @@ -5,6 +5,8 @@ A stateful component is a component that stores and displays some data. Stateful components in Vide are created using sources and effects - sources to store the data, and effects to display the data. +## Internal State + ```lua local create = vide.create local source = vide.source @@ -30,12 +32,14 @@ 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 called *property +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. +## External State + External sources can also be passed into components for them to use. ```lua @@ -65,5 +69,5 @@ 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 is created within a -reactive scope so that it can be tracked. +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. diff --git a/docs/tut/crash-course/8-property-binding.md b/docs/tut/crash-course/8-property-binding.md index c8985c9..8304565 100644 --- a/docs/tut/crash-course/8-property-binding.md +++ b/docs/tut/crash-course/8-property-binding.md @@ -20,6 +20,7 @@ local function Counter() count(count() + 1) end } +end ``` This example is equivalent to the example seen on the previous page. @@ -32,15 +33,16 @@ 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. 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 automatically update any properties depending on sources -that are updated. +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. ## Children Binding -Children can also be set in a similar manner. Sources bound to properties can -return an instance or an array of instances. Vide will automatically unparent -removed instances and parent new instances. +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 +instances. Vide will automatically unparent removed instances and parent new +instances when that source's stored instances change. ```lua local items = source { diff --git a/docs/tut/crash-course/9-derived-source.md b/docs/tut/crash-course/9-derived-source.md index 52574eb..17e3b77 100644 --- a/docs/tut/crash-course/9-derived-source.md +++ b/docs/tut/crash-course/9-derived-source.md @@ -60,17 +60,15 @@ effect(function() end) effect(function() - text() -- does not print + text() -- does not print, returns cached value end) ``` -Deriving a source in this manner is similar to creating an effect to update -another source. You should never manually do this using an effect however, -improper usage could accidently create infinite loops in the reactive graph. -Always favour deriving when you need one source to update based on another. - `derive()` must also be used within a root 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. + The reactive graph for the above example: ```mermaid @@ -86,10 +84,15 @@ The reactive graph for the above example: } }}%% -flowchart +graph subgraph root direction LR count --> text --> effect1 & effect2 end ``` + +Deriving a source in this manner is similar to creating an effect to update +another source. You should never manually do this using an effect however, +improper usage could accidently create infinite loops in the reactive graph. +Always favour deriving when you need one source to update based on another.