Skip to content

Simple Self-Made JS Reactivity with JSX (No REACT). An experiment project to understand reactivity in depth

Notifications You must be signed in to change notification settings

gadingnst/selfmade-reactivity

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Simple Self-Made JS Reactivity with JSX (No REACT)

Live Demo

Live WIKI

What is my purpose?

In the Front-end worlds, "Reactivity" is something that everybody uses, but very few people understand. It’s no one's fault, really, as several people have different definitions of reactivity in programming.

"Reactivity, among JavaScript frameworks, is the phenomenon in which changes in the application state are automatically reflected in the DOM."


Because that, I learn about "Reactivity" from scratch. Precisely, I loved Reactivity concept in Vue, because its reactivity is lightweight and simpler than other frameworks, But I also loved JSX in React, because it's the only templating that's very close and can be integrated in the Javascript/Typescript ecosystem.

So, i tried to make them work together by myself.

Explanation

First, i make reactivity class named Reactive.js in src/lib folder. Besides that, i uses dom-chef for standalone JSX, (NO REACT INCLUDED).

/*
  using JSX standalone with dom-chef,
*/
import { h } from 'dom-chef'

/*
  set JSX in globalThis for global uses.
  so we don't need to import it over and over again
*/
globalThis.h = h

...

In the Reactive.js file, we found constructor method. It used to create initial state and determine what elements to mount for the component that we are creating.

...

class Reactive {
  /*
    Object constructor for initial reactive object
    @param {Generic or any} state : several reactive data model, like data in VueJS or state in ReactJS
    @param {HTMLElement|JSX.Element} app : element that you want to mount
  */
  constructor(state = {}, app = document.createElement('div')) {
    this.app = app
    this.state = state
    this.subscriber = () => {}
  }

  ...
}

For example, assume that we had the following data:

const state = {
  price: 1000,
  qty: 1
}

And we want to mount the component in div element with id root:

const app = document.getElementById('root')

So, we can call the instance like this:

const reactive = new Reactive(state, app)

Below the constructor, we found the depend() and notify() method.

  • depend(): Function that set the effect functions into the subscriber property.
  • notify(): Function that runs the effect functions stored in the subscriber property.
  ...

  /*
    Depend method, set the target reactivity subscriber 
    @param {Function => HTMLElement|JSX.Element} effect : callback function that return HTMLElement or JSX.Element
  */
  depend(effect) {
    if (typeof effect === 'function') {
      this.subscriber = effect
    } else {
      throw new Error('Effect must be function.')
    }
  }

  /*
    Run the target function that stored in subscriber property. 
  */
  notify() {
    const { app, state } = this
    app.innerHTML = ''
    app.appendChild(this.subscriber(state))
  }

  ...

What's subscriber? What's effect?

Well so, the subscriber is like: Determine what function i should run if the state has changed? In this case, we will set the subscriber with effect function that returns JSX and HTML Element

For the notify() method. Just like name, this method will notify the subscriber to runs its functions when there the state has changed. In this case, we will clear HTML element in the component and append it again while the state changes has triggered.


Last, we found the watch() method, this method is what we will always use to define a stateful component. The callback named effect functions will be passed to the depend(). It's called setter and getter which are used to track state changes through the Object.defineProperty(). setter will call the depend() method with effect functions passed to it before the state assigning new value, and the getter will call the notify() method to notify the subscriber to runs the effect functions before returning the state value.

  ...

  /*
    Observe the state changes here. It uses reactivity concept like VueJS, with Object.defineProperty()
    @param {Function => HTMLElement|JSX.Element} effect : callback function that return HTMLElement or JSX.Element
  */
  watch(effect) {
    const { app, state } = this
    this.depend(effect)
    this.notify()
    Object.entries(state).forEach(([key, value]) => {
      Object.defineProperty(state, key, {
        get: () => {
          this.depend(effect)
          return value
        },
        set: newValue => {
          value = newValue
          this.notify()
        }
      })
    })
    return app
  }
}

Continue the example above, assume that we want to assign state price and qty, then calculate the total through the buttons click like this.

reactive.watch((state) => {
  const total = state.price * state.qty
  return (
    <>
      <p>{state.price} x {state.qty} = {total}</p>
      <button onClick={() => state.qty++} class="my-btn">Add Qty by 1</button>
      <button onClick={() => state.price += 1000} class="my-btn">Add Price by 1000</button>
    </>
  )
}) 

We're done, maybe you can learn more by trying it for yourself! Sorry for my bad english explanation.

$ yarn start # start development server

$ yarn build # production build

© 2020 — Sutan Gading Fadhillah Nasution

About

Simple Self-Made JS Reactivity with JSX (No REACT). An experiment project to understand reactivity in depth

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published