Note: This post refers to this branch, and is a part of an ongoing series.
Version 1.2 looks a little different, yet again. As the README states in the repo:
This branch is the version of the application where the developer has started more work into finding a solution where directly modifying the todo values links those changes to the DOM.
For this version of the project I've chosen a very rough implementation of the Observer pattern, as I think that of the ways that frameworks and updates function currently that this is probably the easiest to understand; there's not a lot of magic that goes on, like you would have to make the jump to understand with things like Proxies (which I believe Vue uses, though I could be wrong) or the magic that comes from the Svelte compiler. You create a store, and in one particular place that we subscribe to it we set the render function.
This is also nice because it's a relatively simple pattern to follow and extend, meaning that if we should want to create other "components" there really isn't a lot we would need to add. It's also nice because decoupling the store (or "state") from the rendering process means that we could just subscribe to the todos state in other locations to make some additional functionality, such as adding in a "yet to be done" section in an entirely different part of the page (like the navbar).
So let's take a look at the world's roughest Observable implementation.
//observableStore.class.js
/**
* @template T
* @description A very rough observable-store class. In real-life, you would probably use a library like RxJS
*/
export default class ObservableStore {
/**
* @param {T} initial The initial value for the store. Defaults to null.
*/
constructor(initial = null) {
this._store = initial;
this._fns = [];
}
/**
*
* @param {T | (cur: T) => T} v the new value that will be updated to the store. A rough typecheck is done.
* Could also be function that will be passed the current value of the store and will return the new value.
*/
update(v) {
// Rough type check.
if (typeof v !== 'function' && typeof v !== typeof this._store) {
throw TypeError('Typeof updated data is not the same as the initial data.');
}
// update the store
this._store = typeof v === 'function' ? v(this._store) : v;
// run the subscribed functions for each. In a real implementation it might be worth it to check if making this async would cut times on observables with many subscribers.
this._fns.forEach(fn => fn(this._store));
}
/**
*
* @param {(data: T) => void} fn A function to run everytime the store is updated.
*/
subscribe(fn) {
fn(this._store);
const index = this._fns.length;
this._fns.push(fn);
return () => {
this._fns.splice(index, 1);
};
}
/**
* @description This function is only included as it allows for some quick moment-in-time debugging and has some
* use cases
* @returns {T}
*/
get() {
return this._store;
}
}
This effectively creates a class that takes an initial value for the store, and whenever someone updates the value using the ObservableStore.prototype.update
function, any listener functions are run. Note that these listeners could be functions that tie into anything, anywhere. There's nothing stopping you from subscribing to the store with a function that simply counts how many things there are. Absolutely nothing!
Most of the changes here are for the fact that the Store + Subscription now deal with most of the rendering. The rest is reworking the add/remove/toggle functions to deal with the todos store.
All of the code I think is fairly well documented, but please give drop a comment if you think I could expand upon something.
The next branch will be "the big rewrite" -- it will be the move to TypeScript and creating a very rough React-like Component. The WIP/version-2.0 branch on the repo is there and visible if you want to check it out. It will be removed once I finalize the items and iron out some bugs. I'll be honest, I haven't been working on it for awhile now due to my day job 😅.
Top comments (0)