Skip to content

3. Signals (Reactivity) and Dynamic Render

NotElementImport edited this page Nov 10, 2024 · 6 revisions

Base

There are mechanisms in the framework that allow you to achieve application reactivity.
First, we need to understand how reactivity works:

The Signal is responsible for the reactivity. Signal returns a structure with the value property. Which, when changed, sends an event for changes in the Signal object. That allows to work with small details in the framework in a rather optimized way.

Practice:

import { comp } from "horizon-core/component";
import { useSignal } from "horizon-core/state";

export default comp((_, { text, $ }) => {
    // Creating Signal
    const counter = useSignal(0)

    // Creating button with click event and text like `Counter: ${counter.value}`
    $('button', { '@click': () => counter.value++ }, () => {
        text('Counter: ')
        text(counter)
    })
})

As Raw (Custom data for DOM)

To display your information in the DOM element. You can use the asRaw method in the configuration

import { comp } from "horizon-core/component";
import { useSignal } from "horizon-core/state";

export default comp((_, { text, $ }) => {
    const name = useSignal('jonh doe', {
        asRaw: value => `Hello ${value}!`
    })

    text(name)
})

And the output will be

<span>Hello jonh doe!</span>

Or another example:

import { comp } from "horizon-core/component";
import { useSignal } from "horizon-core/state";

export default comp((_, { text, $ }) => {
    const blockConfig = useSignal({}, {
        asRaw: value => `display: ${value.display}; width: ${display.width}em;`
    })

    blockConfig.display = 'flex'
    blockConfig.width   = 10

    $('div', { style: blockConfig }, () => {
        // ...Block content...
    })
})

And the output will be

<div style="display: flex; width: 10em;">
    <!--...Block content...-->
</div>

And all of the examples have reactivity

Observe Mutation Signal

To keep track of Signal changes, you need to use the global method watch()

import { comp } from "horizon-core/component";
import { useSignal, watch } from "horizon-core/state";

export default comp((_, { text, $ }) => {
    const counter = useSignal(0)

    const unwatch = watch(counter, value => {
        console.log(`Counter: ${value}`)
    })

    counter.value++
    // Counter: 1

    unwatch()

    counter.value++
})

Work with object/arrays

import { comp } from "horizon-core/component";
import { useSignal, watch } from "horizon-core/state";

export default comp((_, { text, $ }) => {
    const data = useSignal({
        first: 'test 1',
        second: {
            value: 'test 2'
        }
    })

    watch(data, value => {
        console.log(`Without deep: ${value}`)
    })

    watch(data, value => {
        console.log(`With deep: ${value}`)
    }, { deep: true })

    data.value.first = 'New value test'
    // Without deep: { "first": "New value test", "second": { value: "test 2" } }
    // With deep: { "first": "New value test", "second": { value: "test 2" } }

    data.value.second.value = 'Deep value test'
    // With deep: { "first": "New value test", "second": { value: "Deep value test" } }
})

Local property observe:

import { comp } from "horizon-core/component";
import { useSignal, watch } from "horizon-core/state";

export default comp((_, { text, $ }) => {
    const person = useSignal({
        lastName: 'Doe',
        firstName: 'Jonh'
    })

    watch(person.value.firstName, value => {
        console.log(`New name: ${value}`)
    })

    person.value.firstName = 'Mike'
    // New name: Mike
})

Dynamic Render

To render data dynamically, you need to use a bunch of useSignal() and dyn() in the layout. dyn() is needed to render a list or other complex that requires redrawing part of the component

import { comp } from "horizon-core/component";
import { useSignal } from "horizon-core/state";

export default comp((_, { text, dyn, $ }) => {
    const articles = useSignal([
        { title: '1. Test', tags: ['new'] },
        { title: '2. Test', tags: ['old', 'sale'] },
        { title: '3. Test', tags: ['old'] }
    ])

    dyn([ articles ], () => {
        for(const article of articles) {
            $('div', {}, () => {
                text(article.title)

                dyn([ article.tags ], () => {
                    for(const tag of article.tags) {
                        text(tag)
                    }
                })
            })
        }
    })
})

dyn() works like watch() with deep: false, so if you change data locally, a full re-rendering will not occur. This allows you to achieve full optimization without extra complex movements on the part of the programmer, many things the engine does itself dynamically. dyn() is needed only for redrawing of lists or other complex things. Where the value can be overwritten

Clone this wiki locally