What is Expressive?

Overview

Expressive MVC is a state-management library, built around Javascript classes. It is designed to be simple, scalable, and typesafe and provides a borderline declarative way to manage any application.

In front-end applications like React, it's an alternative to reducer-style managers such as Redux (opens in a new tab). There, it comes out of the box with support for Hooks, Context, Suspense. However, they can be used anywhere, not just React.

Expressive MVC works mainly through Model, an (almost) empty class extended by you. A custom model contains any number of properties, methods, and instructions, representing state and behavior.

MVC Pattern

As the name suggests, Expressive MVC is based on the Model-View-Controller (opens in a new tab) style of development.

In this library, the pattern is implemented as follows:

  • Model - any class (extending Model) which defines some state and behavior.
  • View - any subscriber (e.g. components) which consumes from one or more models.
  • Controller - any instance of a Model, managing one or more subscribers.

The key advantages of this architecture:

  • Declarative - state defined is state managed, state accessed is state subscribed to.
    You do not need to write code to subscribe to state, nor to update it.
  • Typesafe - models are classes, and can be strongly typed as such.
    You can use typescript and/or flow to ensure a model is being used correctly.
  • Scalable - controllers can manage just one component or multiple, using context.
    Models can also be composed, allowing you to build complex state from separated concerns.
  • Instanced - models, just like components, are reusable.
    A model can exist in multiple places, but a controller will only affect components accessing it.

Models at a glance

Models give you a concise way to capture state. They are self-contained, so can be easily reused and composed. This makes them ideal for managing any app, from single-use to the entire UX.

import Model from "@expressive/mvc";
 
class Control extends Model {
  foo = 1;
  bar = 2;
  baz = 3;
}

Instances of a model can be created using a build-in static method new(). These will then control anything from simple side-effects to an entire component tree, implicitly.

For a simple example, the instance method get() is used to subscribe to one of more values in a controller.

const control = Control.new();
 
control.get(current => {
  console.log(`Foo is now ${current.foo}.`);
});
 
// > Foo is now 2.

This callback has a special ability to update whenever a property accessed from current is updated. Updates happen just by assigning to properties. Subscribers are notified and refresh automatically.

control.foo = 3;
 
// > Foo is now 3.

Subscribers are smart though. If an updated property is not accessed, it will not cause a refresh.

control.bar = 4;
 
// Nothing happens.

Models in React

In React, making a controller is also simple.

@expressive/react exports the same Model as @expressive/mvc does. However, is augmented with static methods use() and get(), making models turn-key with React components.

Extend Model like before, and add some properties and methods. No custom syntax (ala MobX (opens in a new tab)) necessary, and it slots into any existing project. While Typescript is recommended, it is not even necessary.

import Model from "@expressive/react";
 
class Control extends Model {
  current = 1
 
  increment = () => this.current += 1;
  decrement = () => this.current -= 1;
}

In a React app, all models retroactively get the static property use(). The method itself is a hook, so will memoize and refresh a component automatically. It is lazy too, so only properties you access will trigger a refresh.

function Counter(){
  const { current, increment, decrement } = Control.use();
 
  return (
    <div>
      <button onClick={decrement}>{"-"}</button>
      <pre>{current}</pre>
      <button onClick={increment}>{"+"}</button>
    </div>
  )
}

Why use classes?

Especially in React, classes have a bad rap. It's understandable though. Before hooks, class components were clunky, hard to use and all we had.

Hooks however, have their own problems which are less visible at first. The largest is that they are not portable or self contained. While custom hooks exist, they're hardly what you would call reusable, let alone extensible.

By combining the benefit of classes (not class components) and the benefit of hooks, we get write our apps in a way that is both ergonomic and scalable.