DEV Community

Mike Skoe
Mike Skoe

Posted on

Making Vanilly App

It is great to be able to make app using Reaction, View, Angler. But it is cooler better if you can do even a small app with pure JS.

Today I will show you the way I did my vanilla app.

...

I call it "property-oriented pub-sub pattern". But before I will explain the idea, let's look at some essentials.

Every app has three fundamental parts: State, Actions and Presentation layer.
State is an object with all changeable information. Usually, every app has one global state and optionally little states for small parts of an app.
Actions are functions, that change state. Usually triggered on some button press or on any other update.
Presentation layer or "view", is a way to beautifully present the state to users.

React+Redux cycle looks like this:

  • trigger update of a state by pressing button (for example).
  • basing on the new state we make new VDOM (JS-object as a representation of a future DOM).
  • new VDOM compares with old one.
  • basing on parts that was really changed, React updates only needed DOM elements.

propertyorientedpubsubpattern has much simpler flow. Action change a state and list all changed properties. Basing on changed state keys subscribed functions are triggered.

To make everything clear, let's first learn how to use it, then we will make it.

For example, we have a state "Character".

const initialState = {
   name: 'Jake',
   age: 32,
   about: 'hi, im jake, yo',
}
Enter fullscreen mode Exit fullscreen mode

We put it into some 'init' function, that gives us 'pub' and 'sub', to deal with.

const {pub, sub} = init(initialState);
Enter fullscreen mode Exit fullscreen mode

Then we make several components.

const c = document.createElement.bind(document); // alias for createElement
const q = document.querySelector.bind(document); // alias for querySelector
const Name = c('div');
const Age = c('div');
const MakeOlder = c('button');
MakeOlder.innerText = 'Make a person older!';
const App = c('div');
App.append(Name, Age, MakeOlder);
Enter fullscreen mode Exit fullscreen mode

then use those pub-sub stuffs.

sub(
   ['age'],
   age => Age.innerText = `age: ${age}`,
);
sub(
   ['name'],
   name => Name.innerText = `name: ${name}`,
);
const OLDER = pub(
   ['age'],
   state => ({...state, age: state.age + 1}),
);

MakeOlder.onclick = OLDER;
Enter fullscreen mode Exit fullscreen mode

Now we have a little counter, masked as an account app.
As you can see, we explicitly list all properties public and subscribe to. When certain properties are updated, certain functions are triggered. Easy-Peasy.

So let's build this 'init' peace of function.

const init = state => { // 1.
   const callbacks = {}; // 2.
   return {
      sub: (keys, fn) => { // 3.
         fn.args = keys; // 4.
         keys.forEach(key => {
            callback[key] = callback[key] || []; // 5.
            callbacks[key].push(fn);
         };
         fn(...fn.args.map(arg => state[arg])); // 6.
      },
      pub: (keys, update) => () => {
         initialState = update(initialState); // 7.
         keys.forEach(key => { // 8.
            (callback[key] || []).forEach(cb => { // 9.
               cb(...(cb.args || []).map(arg => state[arg]) // 10.
            });
         });
      }
   }
}
Enter fullscreen mode Exit fullscreen mode

Now let me explain everything line by line.

  1. state is kept in the closure.
  2. 'callbacks' is an object, that holds all subscribed functions. Key is state property string, value - function, that depends on that key.
  3. 'sub' takes keys of state, as you already know, and a function to trigger.
  4. when function is subscribed on several keys, the function is listed in different arrays on several keys of 'callbacks' object. To save all keys associated with a function, we put them in 'args' property, cuz' functions are objects in JS.
  5. if callbacks objects already has that key = we do nothing, otherwise we create empty array.
  6. immediately call with arguments taken as keys from the state.
  7. append the function to callbacks object.
  8. we update the state by reassigning 'initialState' from closure.
  9. for safety, if the are no array on that key, we use empty array(to do nothing).
  10. same as at line (6.), trigger needed functions.

This code has list of defects, because i whated to make it as easy as possible, but it is already usable tool to make small apps!

If you what to play with this, here is codesandbox example:

Everything is great, but:

  • some functions are can be called twice
  • creating and updating elements via 'document' object directly is not cool
  • we have no way to unsubscribe functions
  • nature cataclysms are still happening

In the next episode we will fix some of those problems, so until next time!

lib: repo;
app: repo
gh-pages version

Top comments (0)