Here is how you would implement a simple counter app without mutating anything except for the DOM.
const h1 = document.querySelector("h1");
const [decrement, reset, increment] = document.querySelectorAll("button");
const render = count => {
h1.innerHTML = count; // output
decrement.onclick = event => render(count - 1); // -+
reset.onclick = event => render(0); // |-- transition functions
increment.onclick = event => render(count + 1); // -+
};
render(0);
// ^
// |
// +-- initial state
<h1></h1>
<button>-</button>
<button>Reset</button>
<button>+</button>
This is an example of a Moore machine. A Moore machine is a finite-state machine. It consists of three things.
- The initial state of the machine. In our case, the initial state is
0
.
- A transition function, which given the current state and some input, produces a new state.
- An output function, which given the current state, produces some output.
In our case, we combined the transition function and the output function into a single render
function. This is possible because both the transition function and the output function require the current state.
When the render function is provided the current state, it produces some output as well as a transition function which when provided some input, produces a new state and updates the state machine.
In our case, we divided our transition function into multiple transition functions which share the current state.
We can also use event delegation to improve performance. In the example below we only register one click event listener on the entire document. When the user clicks anywhere in the document, we check whether the target element has an onClick
method. If it does, we apply it to the event.
Next, in the render
function instead of registering separate onclick
listeners for each button, we store them as regular methods named onClick
(different case). This is more performant because we're not registering multiple event listeners, which would slow down the app.
const h1 = document.querySelector("h1");
const [decrement, reset, increment] = document.querySelectorAll("button");
const render = count => {
h1.innerHTML = count;
decrement.onClick = event => render(count - 1);
reset.onClick = event => render(0);
increment.onClick = event => render(count + 1);
};
render(0);
document.addEventListener("click", event => {
if (typeof event.target.onClick === "function") {
event.target.onClick(event);
}
});
<h1></h1>
<button>-</button>
<button>Reset</button>
<button>+</button>