Modularity

In the basics of the architecture described a simple Counter module. In this example that Counter module will be used to create pair of counters. Each counter will be changeable independently, and the reset button will reset both counters back to 0.

The CounterPair module starts with importing the Counter module and the reflex library:

import {html, forward, start} from "reflex"
import type {DOM, Address} from "reflex"
import * as Counter from "./Counter"

While Counter.Model, Counter.init, etc. are used no knowledge is assumed in regards how they work. This is very subtle but important detail that enables true modularity.

Model

Counter module already defines a complete representation of a counter via Counter.Model type. There for modeling a pair of counters is pretty straight forward:

export class Model {
  top: Counter.Model;
  bottom: Counter.Model;
  constructor(top:Counter.Model, bottom:Counter.Bottom) {
    this.top = top
    this.bottom = bottom
  }
}

Model just needs to hold representation of top counter and a bottom counter. As of init function it just needs to init both of them:

export const init = (top:number=0, bottom:number=0) =>
  new Model(Counter.init(top), Counter.init(bottom))

The Counter module provides an init function to create a Counter.Model. Only thing necessary is to delegate to Counter.init for top and bottom counter and then package results into Model.

Update

Next thing we need is to define what kinds of messages can be received. In requirements we mentioned ability to reset counters, so message for that & all the messages produced by the top and bottom counters:

export type Message =
  | { type: "Reset" }
  | { type: "Top", top: Counter.Message }
  | { type: "Bottom", bottom: Counter.Message }

First Reset message is pretty normal. The of Top and Bottom cases are a bit different. They are just saying we will get counter messages from the top and bottom counters. Important thing is there is no need to know anything about Counter.Message, all we care about is weather it came from a top or a bottom counter.

Note: Due to an issue #1890 in flow, it is recommended to use distinct property names when defining types with properties of external type. Which is why top: Counter.Message and bottom: Counter.Message are used here.

export const update = (model:Model, message:Message):Model => {
    switch (message.type) {
      case "Reset":
        return init(0, 0)
      case "Top":
        const top = Counter.update(model.top, message.top)
        return new Model(top, model.bottom)
      case "Bottom":
        const bottom = Counter.update(model.bottom, message.bottom)
        return new Model(model.top, bottom)
    }
  }

When Reset message is received, module is just reinitialized. When Top message is received, there is only one thing that can be done. The Counter.update function is the only function exposed by Counter that takes an argument of Counter.Message type (whatever that may be). So we just delegate to Counter.update with a top counter, which gives us a new Counter.Model which is placed in place of former top counter model. Exact same thing happens for the bottom counter.

What this means is that when someone adds a ton of new features to Counter module in next three years, CounterPair module will continue working exactly the same. All the details are encapsulated inside the Counter module.

View

Finally, a view function. Nothing new here, maybe just the fact top and bottom counters are passed "forwarding addresses" that tag messages before passing them on to address so that update can figure out which counter does wrapped message belongs to.

export const view = (model:Model, address:Address<Message>):DOM =>
  html.section({
    className: "counter-pair"
  }, [
    html.div({
      className: "top-counter"
    }, [
      Counter.view(model.top, forward(address, tagTop))
    ]),
    html.div({
      className: "bottom-counter"
    }, [
      Counter.view(model.bottom, forward(address, tagBottom))
    ]),
    html.button({
      onClick: forward(address, createReset)
    }, ["Reset"])
  ])

const createReset = _ => ({ type: "Reset" })
const tagTop = message => ({ type: "Top", top: message })
const tagTop = message => ({ type: "Bottom", bottom: message })

results matching ""

    No results matching ""