Skip to content

Instantly share code, notes, and snippets.

@Oikio
Created November 15, 2017 19:42
Show Gist options
  • Save Oikio/67c691872747e6ed1cb2b360991a8454 to your computer and use it in GitHub Desktop.
Save Oikio/67c691872747e6ed1cb2b360991a8454 to your computer and use it in GitHub Desktop.
Thoughts about reactive architecture
import * as m from 'mithril'
import * as stream from 'mithril/stream'
interface State {
error: boolean
counter: number
}
// state
const counter = stream(0)
const error = stream(false)
const state: stream.Stream<State> = stream
.merge([counter, error])
.map(([counter, error]: [number, boolean]) => {
return {
counter,
error
}
})
const getState = () => state()
// action
const updateCounter = (n: number) => counter(n)
const setError = (exists: boolean) => error(exists)
// .. usecase, can have Promise or callback for progress update to m.redraw
const updateCounterUseCase = (n: number) => {
if (n >= -10 && n <= 10) {
setError(false)
updateCounter(n)
} else {
setError(true)
}
}
const appRoot = document.createElement('div')
appRoot.id = 'app'
document.body.appendChild(appRoot)
const Home: m.Component<
{},
{
getState: () => State
updateCounter: (n: number) => void
}
> = {
oninit: vnode => {
vnode.state = {
getState,
updateCounter: updateCounterUseCase
}
},
view: vnode => {
console.log('redraw')
const state = vnode.state.getState()
const { updateCounter } = vnode.state
return m('', [
m('h1', 'Home route. ' + state.counter),
m('div', state.error ? 'You are out of bounds, stop it!' : 'You are OK!'),
m(
'button',
{
onclick: (e: MouseEvent) => updateCounter(state.counter - 1),
onmousemove: (e: MouseEvent) => {
e.redraw = false
}
},
'Decrement'
),
m(
'button',
{ onclick: (e: MouseEvent) => updateCounter(state.counter + 1) },
'Increment'
)
])
}
}
m.route(appRoot, 'home', {
home: Home
})
import * as m from 'mithril'
import * as stream from 'mithril/stream'
import { __, converge, findIndex, identity, update } from 'ramda'
// Core idea is to use onion architecture with usecases as glue
//
// view state \
// ==— uscases
// actions services gateways ... /
//
// views use usecases for intent and take streams from state (or get state for redux liek state)
// usecases act as glue and know just about contracts (they can be passthrough for simple cases, like actions)
// actions is the only place to update state
// component state can be in components and other parts of application can talk to each other if it makes sense
// for testing use DI dividing module into two files (componentFolder -> [component.ts, index.ts])
// style as you please
// todo: take a look at react-redux, to think about react implementation
type Diff<T extends string, U extends string> = ({ [P in T]: P } &
{ [P in U]: never } & { [x: string]: never })[T]
type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] }
interface Todo {
id: number
text: string
}
interface State {
todos: Todo[]
}
// state
const todos = stream<Todo[]>([])
const state: stream.Stream<State> = stream.merge([todos]).map(([todos]) => {
return {
todos
}
})
state.map(console.log)
// actions
const addTodo = (todo: Todo) => {
todos([...todos(), todo])
}
const updateTodo = (id: number, todo: Omit<Todo, 'id'>) => {
todos(
converge(update(__, { ...todo, id }), [
findIndex((t: Todo) => t.id === id),
identity
])(todos())
)
}
// .. usecase, can have Promise or callback for progress update to m.redraw
const addTodoUseCase = addTodo
const updateTodoUseCase = updateTodo
// component
const Todos: m.Component<
{},
{
todos: stream.Stream<Todo[]>
addTodo: (todo: Todo) => void
updateTodo: (id: number, todo: Omit<Todo, 'id'>) => void
}
> = {
oninit: vnode => {
vnode.state = {
todos: todos,
addTodo: addTodoUseCase,
updateTodo: updateTodoUseCase
}
},
view: ({ state }) => {
// console.log(todos())
return m('', [
m('h1', 'Todos:'),
m(
'ul',
state.todos().map(todo =>
m(
'li',
m('input', {
value: todo.text,
oninput: (e: Event & { target: HTMLInputElement }) => {
state.updateTodo(todo.id, {
text: e.target.value
})
}
})
)
)
),
m(
'button',
{
onclick: () => state.addTodo({ id: +new Date(), text: '' })
},
'add todo'
)
])
}
}
// app
const appRoot = document.createElement('div')
appRoot.id = 'app'
document.body.appendChild(appRoot)
m.route(appRoot, 'home', {
home: Todos
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment