preact + parket で軽量開発

# Parket
<img src='https://raw.githubusercontent.com/ForsakenHarmony/parket/master/.github/logo.png' alt='parket' width='50%'>

https://www.npmjs.com/package/parket

`inspired by mobx-state-tree` な状態管理ライブラリです。preact と併用することで、hyperappに迫る軽量化を実現しています（Small (~1.5KB)）。initial commit が [2018/01/24](https://github.com/ForsakenHarmony/parket/commit/49698350d83221d3e64a9cc73a70a748adc07b2b) と、出来たてホヤホヤですね。


# usage

リポジトリを眺めていれば特に難しいことは無いと思います。parket に view は含まれておらず、現状では preact と react のバインディングが含まれています。以下リポジトリの usage 引用。

```usage.js
import model from 'parket';
// model returns a "constructor" function
const Person = model('Person', { // name is used internally for serialisation
  initial: () => ({
    firstname: null,
    lastname: null,
    nested: null,
  }),
  actions: state => ({
    setFirstName (first) {
      state.firstname = first; // no set state, no returns to merge, it's reactive™
    },
    setLastName (last) {
      state.lastname = last;
    },
    setNested (nested) {
      state.nested = nested;
    },
  }),
  views: state => ({
    fullname: () => `${state.firstname} ${state.lastname}`, // views are computed properties
  }),
});
```
`views` は Vue.js や Mobx の `computed` と等価のものですね。（是非 computed に改名して欲しい）


# mittで動いている

https://www.npmjs.com/package/mitt

軽量 EventEmitter の mitt が Dependencies。[値の変化をsubscribeするAPI](https://github.com/ForsakenHarmony/parket/blob/master/src/index.js#L139-L148)がいくつか用意されている様です。console の内容を見ると、イベント名 や payload が乗っかってるのが分かります。

```
＃counterA.onAction(console.log)
{name: "increment", path: "/counterA", args: Array(1)}

# counterA.onPatch(console.log)
{path: "/counterA/count", op: "replace", value: 1}

# counterA.onSnapshot(console.log)
{name: "counterA", count: 1, label: "counterA + counterB", calcVal: 0, __p_model: "Counter"}
```

これらは `subscribe` の wrapper で、戻り値を呼ぶことで unsubscribe 出来ます。

# サービス層に乗せてみる

![Untitled.gif](https://qiita-image-store.s3.amazonaws.com/0/59071/b241c0e6-a3d5-12e0-7b9d-32a02a02eb3c.gif)


「状態管理にサービス層がないとやっていけない」と最近思っている筆者は、さっそくasync/await に乗せてみました。SingleStoreを作って、サービス起動して、View を render する[いつもの構成](https://qiita.com/Takepepe/items/af873a027fc67926d3ed)です。

```app.js
import 'babel-polyfill'
import { render } from 'preact'
import model from 'parket'
import Counter from './model'
import runService from './service'
import View from './view'

const store = model('Store', {
  initial: () => ({
    counterA: Counter({
      name: 'counterA',
      label: 'counterA + counterB',
      count: 0
    }),
    counterB: Counter({
      name: 'counterB',
      label: 'counterA x counterB',
      count: 1
    })
  })
})()

runService(store)

render(<View store={store} />, document.getElementById('app'))

```
```model.js
import model from 'parket'

export default model('Counter', {
  initial: () => ({
    name: '',
    count: 0,
    label: '',
    calcVal: 0
  }),
  actions: state => ({
    increment () {
      state.count++
    },
    decrement () {
      state.count--
    },
    setCalcVal (calcVal) {
      state.calcVal = calcVal
    }
  })
})

```
```service.js
export function change () {
  return Promise.race(
    [].slice.call(arguments).map(model => {
      return new Promise(resolve => {
        const unsubscribe = model.onPatch(event => {
          unsubscribe()
          resolve(event)
        })
      })
    })
  )
}

async function calculate ({ counterA, counterB }) {
  while (true) {
    counterA.setCalcVal(counterA.count + counterB.count)
    counterB.setCalcVal(counterA.count * counterB.count)
    await change(counterA, counterB)
  }
}

export default function runService (store) {
  const { counterA, counterB } = store
  calculate({ counterA, counterB })
}


```
```view.js
import { Component } from 'preact'
import { connect, observe, Provider } from 'parket/preact'

function MyComponent ({ name, count, label, calcVal, increment, decrement }) {
  return (
    <div>
      <h1>{name}:{count}</h1>
      <p>{label} = {calcVal}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  )
}

@observe
class Panel extends Component {
  render ({ counter }) {
    return (
      <MyComponent
        name={counter.name}
        count={counter.count}
        label={counter.label}
        calcVal={counter.calcVal}
        increment={counter.increment}
        decrement={counter.decrement}
      />
    )
  }
}

@connect
class Container extends Component {
  render ({ store }) {
    return <div>{this.props.children}</div>
  }
}

const root = ({ store }) => (
  <Provider store={store}>
    <Container>
      <Panel counter={store.counterA} />
      <Panel counter={store.counterB} />
    </Container>
  </Provider>
)

export default root


```
難なくこなしてくれました。モデルが純粋で、魅力的 :sparkles: 

sample: https://github.com/takefumi-yoshii/preact-parket-sandbox

# Single source of truth（自説）

上記サンプルの `app.js` では Store を一つにしています。というのも、`service.js` を以下の様に変更すれば、ReduxAction と同じ様に`onPatchStore()`関数でグローバルイベントとしてキャッチすることができるためです。 Redux ユーザーにとっては、ピンとくる利点ではないでしょうか。

```service.js
function onPatchStore (event) {
  // console.log(event) # do something
}

export default function runService (store) {
  const { counterA, counterB } = store
  calculate({ counterA, counterB })
  store.onPatch(onPatchStore) // New
}
```

# LGTM :innocent: 

MobXらしい簡単さと、イベント駆動が融合していて、v0.2.0ですがとても期待の高まるライブラリです。