Skip to content

Commit

Permalink
Try improve crash course
Browse files Browse the repository at this point in the history
  • Loading branch information
centau committed Nov 21, 2023
1 parent c288cb9 commit 338c66e
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 102 deletions.
3 changes: 2 additions & 1 deletion docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
]
},
{
Expand Down
46 changes: 13 additions & 33 deletions docs/tut/crash-course/1-introduction.md
Original file line number Diff line number Diff line change
@@ -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.

Some of the main focuses behind Vide's design choices:

- Concise syntax.
- Being completely typecheckable.
- Independence from instance lifetimes.
- Real reactivity.

## Structure Of A Vide App
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.

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*.
Some of the main focuses behind Vide's design choices:

```lua
local function App()
return {
PlayerStats(),
Inventory(),
Settings()
}
end
- Minimal syntax.
- Complete typechecking
- Independence from instances.

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.
26 changes: 20 additions & 6 deletions docs/tut/crash-course/10-cleanup.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
132 changes: 132 additions & 0 deletions docs/tut/crash-course/15-concepts.md
Original file line number Diff line number Diff line change
@@ -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.
39 changes: 17 additions & 22 deletions docs/tut/crash-course/2-creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,36 +9,31 @@ 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),
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.fromScale(0.4, 0.7),
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),

create "TextLabel" {
Text = "hi"
},
create "TextLabel" {
Text = "hi"
},

create "TextLabel" {
Text = "bye"
},
create "TextLabel" {
Text = "bye"
},

create "TextButton" {
Text = "click me",
create "TextButton" {
Text = "click me",

Activated = function()
print "clicked!"
end
}
Activated = function()
print "clicked!"
end
}
}
end

mount(App, game.StarterGui)
}
```

Assign a value to a string key to set a property, and assign a value to a
Expand Down
14 changes: 10 additions & 4 deletions docs/tut/crash-course/3-components.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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.
8 changes: 2 additions & 6 deletions docs/tut/crash-course/4-source.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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.
16 changes: 11 additions & 5 deletions docs/tut/crash-course/6-root.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Loading

0 comments on commit 338c66e

Please sign in to comment.