-
Notifications
You must be signed in to change notification settings - Fork 23
FRP Best Practice
I’ll guide you to some good practice of Functional Programming through the Counter example.
> It’s really helpful even you don’t use xreact, the FP idea is common and applicable to anywhere even for redux project.
union-type is a awesome library to define union type/case class.
most flux-like library will define Intents using the keyword `type`
inc: () => ({type: 'inc'})but union type fit perfectly to define Intent
Intent.js
import Type from 'union-type'
export default Type({
Inc: []
Dec: []
})it’s like case class in scala, you get a lot of benefit by using union-type Intent
import Intent from 'intent'
const counterable = x(intent$ => {
return {
sink$: intent$.map(Intent.case({
Inc: () => state => ({count: state.count + 1}),
Dec: () => state => ({count: state.count - 1}),
_: () => state => state
})),
actions: {
inc: Intent.Inc,
dec: Intent.Dec,
}
}
})like scala, union type can also contain values
import Type from 'union-type'
export default Type({
Inc: [Number]
Dec: [Number]
})if you define Intent constructors, you will be able to destruct them via case
import Intent from 'intent'
const counterable = x(intent$ => {
return {
sink$: intent$.map(Intent.case({
Inc: (value) => state => ({count: state.count + value}),
Dec: (value) => state => ({count: state.count - value}),
_: () => state => state
})),
actions: {
inc: Intent.Inc,
dec: Intent.Dec,
}
}
})lens is composable, immutable, functional way to view, update your state
you can use lens implemented by ramda, or update in lodash
import {lens, over, inc, dec, identity} from 'ramda'
const counterable = x(intent$ => {
let lensCount = lens(prop('count'))
return {
sink$: intent$.map(Intent.case({
Inc: () => over(lensCount, inc)
Dec: () => over(lensCount, dec),
_: () => identity
}))
}
})when the value is async, e.g. promise
it could be response from rest request, or other async IO
import when from 'when'
import {just, from, lens, over, set, inc, dec, identity, compose} from 'ramda'
const counterable = x(intent$ => {
let lensCount = lens(prop('count'))
return {
sink$: intent$.map(Intent.case({
Inc: () => over(lensCount, inc)
Dec: () => over(lensCount, dec),
_: () => identity
}))
data$: just(0)
.flatMap(compose(from, when)) // <-- when is a async value
.map(set(lensCount))
}
})x wrappers are composable, just like functions
import Type from 'union-type'
export default Type({
Inc: [Number],
Dec: [Number],
Double: [],
Half: []
})create a new wrapper with some kind of behaviors
const doublable = x(intent$ => {
let lensCount = lens(prop('count'))
return {
sink$: intent$.map(Intent.case({
Double: () => over(lensCount, x=>x*2)
Half: () => over(lensCount, x=>X/2),
_: () => identity,
}))
actions: {
double: Intent.Double,
half: Intent.Half,
}
}
})compose doublable and increasable
const Counter = doublable(increasable(CounterView))CounterView then get both abilities of double/half and inc/dec
const CounterView = props => (
<div>
<button onClick={props.actions.half}>/2</button>
<button onClick={props.actions.dec}>-</button>
<span>{props.count}</span>
<button onClick={props.actions.inc}>+</button>
<button onClick={props.actions.double}>*2</button>
</div>
)now our FRP counter example will become something like this