Home > Enterprise >  Implementing reactivity in vanilla JS
Implementing reactivity in vanilla JS

Time:07-26

I use vanilla JS on webpages, and I'm trying to understand whether my design pattern is correctly implementing the principle of reactivity.

(Note: I'm not referring to the library React -- though I'm happy for answers to draw on features or strategies of such libraries).

My basic understanding is that you have some data which acts as your single source of truth, you listen for changes, and when that data changes, your page|app|component re-renders to reflect that.

Here's a simplified version of what I do, with questions after.

Let's say I have my single source of truth:

let data = {}
data.someContent = 'Hello World'
data.color = 'red'

and my app's markup in a template string for dynamic rendering:

function template(data) {
  return `
  <div id="app" style="color:${data.color}">${data.someContent}</div>
`
}
// assume there are also plain HTML inputs on the page, outside of what gets re-rendered.   

a function that renders based on the data:

function render(data) {
  document.getElementById('app').innerHtml = template(data)
} 

then, for the equivalent of reactivity from client-side updates:

document.addEventListener('input', (e) => {
  data[e.target.id] = e.target.value // update data to reflect input
  render(data) // re-render based on new data
})

and from server-side updates:

function fetchDataAndReRender() {
  data.propToUpdate = // fetch data from server
  render(data) // again, re-render
return
}

So, we've got the single source of truth and re-rendering based on data updates.

  • Is there another pillar to this, beyond the trio of data, listeners, and rendering?
  • I understand that libraries usually listen directly to changes on the data object, e.g. via Proxies. It seems like the only advantage to that is avoiding manually calling render(). Is that correct?

CodePudding user response:

The main thing I'm missing here is a way to optimize re-renders.

Interacting with the DOM is expensive, so apart from using a Proxy around your data/state to automatically call render, you would ideally want to avoid just replacing all the HTML for your app if only one element or, say, one attribute, has changed:

  • Consider updating an <input>. If you are not able to detect that only its value has changed and update that, and instead you replace the whole HTML that includes this input, the cursor will move to the end after each re-render (so probably after every character I type).

  • Consider a drag & drop feature. If you are not able to detect that the user is dragging something across the screen and only update the CSS transform property on it, and instead you replace the whole HTML that includes the element being dragged, the performance is going to be horrible. Also, you'll be interrupting the drag events, so this won't work at all.

Another possible optimization would be batching updates, so if you update your data 3 times in a row, you could potentially only update the HTML once with the final result. You already mentioned you debounce continuous input events, which would be one way of doing it.

Answers to your comments:

(1) I assumed you wanted a general purpose solution, that's where being able to detect changes and only update the parts of the DOM that need to be updated is a must.

If your only need to (re)render small static chunks of HTML, then you can probably go without this. The main two reasons being:

  • As you said, there's no interactivity, so you'll not experience the issues I exemplified.

  • As you updates are small, you are unlikely to see any performance issues by replacing blocks of HTML instead of trying to update the existing elements in the DOM.

(2) In any case, the main problem you would have to face if trying to update only what has change would not be finding the data that has changed (regardless of whether you use a Proxy or not), but to map that to the elements that need to be updated and actions that would perform those updates.

(3) You can also make compromises on the app side, say, that the data must be immutable (like with React).

CodePudding user response:

This is not an answer to your question. It is a comment too long to fit in the comment space.

What "thinking reactive" means, at least in my opinion

"thinking reactive" means, as far as I can tell, that you model your problem in terms of "streams of events" and the "side effects" such events have, for instance what happens on your screen when some of these events occur.

In an front end app, the first event is "somebody opens the app" and the side effect is that "you see something on your screen", usually the browser.

Then, starting from that event, you switch to another stream of event, or probably the combination of many streams of events.

This combination can be the combination of:

  • "the user clicks buttonA"
  • "the user scrolls down the page"

Each of these streams of events has its own side effects:

  • "the user clicks buttonA" may lead you to another page
  • "the user scrolls down the page" may trigger a call to a remote service for the next page of data

The summary is that, in reactive style, your write the book of "what happens when" and then you unfold this book.

In more precise terms, you can declare how an entire application will behave as a single stream of events and the related side effects.

Once your declaration is complete (and correct) you simply subscribe to the stream and let the events, and their side effect, flow.

Is it worth programming this way down to the extreme? Probably not, as all extreme positions.

Is it convenient some times? Definitely yes.

  • Related