Skip to content

henryhale/reactivity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

16 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Reactivity

Reactivity

After completing my basic implementation of render functions, I went further to tap into Reactive Programmming using JavaScript.

 import { reactive } from "../lib/index.js"

 // App state
 const state = reactive({ count: 0 })

 // track `count` 
 watchEffect(() => console.log(`Count: ${state.count}`))

 // increment
 ++state.count
    // > Count: 1
 ++state.count
    // > Count: 2

Live Demo

Temperature Converter -> source code -> demo link

Target Audience

This repository is not for beginners; it's targeted at intermediate or professional developers and programmers who want to take their JavaScript skills to the next level.

Some of the basics (like loops, conditionals, and closures) are not discussed at all. If you find you need to brush up on some of those topics, refer to the MDN Web Docs

NB: Intermediate understanding of JavaScript and some of the latest ECMAScript Standards like classes, modules, shortcircuiting, and more.

Before You Start

Modern JavaScript Frameworks have different implementations on gaining reactivity; common usecases include DOM updates and App state.

I recommend that you check out how some modern frameworks implement reactivity; - Vue, - Solid, -Svelte

Warning: No application should be built on top of this Reactivity System as it is intended to be for learning or study purposes only.

The Architecture Behind

My understanding of Reactive JavaScript is based on the idea that when the variable changes, all computations that depend on it get re-evaluated.

Example: Spreadsheets like Microsoft Excel use cell referencing such that if cell A3 is the sum of cells A1 and A2, then whenever cell A1 or A2 changes, the value of cell A3 gets re-evaluated.

Basic Implementation

  • Inorder to gain reactivity, wrap the initial value (primitive type) into an object with a getter and a setter.
  • In the getter, every time the value is accessed, we capture the active caller (a subscriber/dependant) to this value, and store it (as a subscriber to this value).
  • In the setter, whenever the value is being changed, make the change, and then notify all subscribers/dependants that the value is changed, so re-evaluate (update).
  • Incase the initial value was an object, wrap the object in a Proxy with set and get traps which work like getters and setters as described before.

Features

The naming of the building blocks is based on Vue's Reactivity.

  • watchEffect

    A function that opens a subcription to reactive values and so saves the action to be notified when updates occur

    Takes an action as an argument

    Interface

     watchEffect(action: function)

    We can watch all reactive value like ref, computed and properties of reactive objects

    How does watchEffect work, check it out here

  • ref

    A function that creates a reactive object from a primitive value either string, number or null

    Takes any primitive type and return a object with the value property

    Interface

     const variable_name = ref(value: string | number | null | undefined)

    Example

     import { ref } from "../lib/index.js";
    
     // first name
     const firstName = ref("Brad")
    
     // read value 
     console.log(`My first name is ${firstName.value}.`)
    
     // watch firstName for changes
     watchEffect(() => {
        console.log(`Your first name has changed to ${firstName.value}`)
     })
    
     // change value
     firstName.value = "Henry"
    
     // change again
     firstName.value = "John"

    How does ref work, check it out here

  • computed

    A function that stores a value that re-calculates when it's dependencies get updated.

    This is used in conjuction with other reactive values like ref, reactive

    Takes a function as an argument and returns an object with only a getter for a value property

    Interface

     const variable_name = computed(action: function)

    Example

     import { computed, ref, watchEffect } from "../lib/index.js";
    
     // cells A1 and A2
     const A1 = ref(1), 
           A2 = ref(2)
    
     // cell A3 - computed sum  
     const A3 = computed(() => A1.value + A2.value)
    
     // watch cell A3 for changes if either cell A1 or A2 changes
     watchEffect(() => {
        // read A3
        console.log(`A3 is ${A3.value}`)
     })
    
     // change cell A1 
     ++A1.value
        // logs > A3 is 4
    
     // change cell A2
     A2.value = 5
        // logs > A3 is 7 

    How does computed works, check it out here

  • reactive

    A function that creates a reactive object from only objects not arrays.

    Takes an object as argument and returns a proxy

    Access of properties does not behave like ref or computed, they are accessed directly and still reactive.

    Interface

     const variable_name = reactive(obj?: any)

    Example

     import { reactive } from "../lib/index.js";
     
     //app state
     const state = reactive({ count: 0 })
    
     // read a property
     console.log(`Count: ${state.count}`)
     // logs > Count: 0
    
     // change `count`
     ++state.count
    
     // read a property
     console.log(`Count: ${state.count}`)
     // logs > Count: 1

    Note: Destructing objects causes a disconnection from reactivity since the properties are referenced in objects and so destruction assignment to a variable de-references the property from the reactive object

    How does reactive work, check it out here

Building Blocks

Source code contains comments that take you through what happens behind the scenes

To build it from scratch as you test each of the building blocks, check out the source code.

Examples

I have developed some basic examples to illustrate how reactivity can be useful.

 import { ref, computed, watchEffect } from "../lib/index.js"

 // count 
 const count = ref(0)

 // double 
 const double = computed(() => 2 * count)

 // watch `count` and `double` for changes 
 watchEffect(() => {
   console.log(`Count: ${count.value}, Double: ${double.value}`)
 })

 // trigger changes
 ++count.value
   // logs > Count: 1, Double: 2
 ++count.value
   // logs > Count: 2, Double: 4
 ++count.value
   // logs > Count: 3, Double: 6

More Examples

References

Thoughts

In case of any issues or inquiries about this, a pull request is welcome.

Thanks.