LitElement + Redux でカウンター

[前々回](https://qiita.com/howking/items/644b75c021a718c23d9a)に[lit-html](https://lit-html.polymer-project.org/)、[前回](https://qiita.com/howking/items/6f481263f84d607fe525)は[LitElement](https://lit-element.polymer-project.org/)について勉強してきて、それらを使ったWebアプリの[サンプル](https://pwa-starter-kit.polymer-project.org/sample-apps/)が[Redux](https://redux.js.org/)を使ってたのでコネクタとなる[pwa-helpers](https://github.com/Polymer/pwa-helpers/)とともに勉強してみます。

## Reduxとは

![redux](https://qiita-image-store.s3.amazonaws.com/0/90678/e16808e7-017e-9624-1a9a-50096b2960dd.png)

今さらですが、流行りの状態(state)管理ライブラリ。

そもそもなぜ使う必要があるのかというと、例えばドキュメントツリー関係なく複数の表示更新をしたい時に、イベントメッセージによる親子間のやりとりでは対処しずらいので、`state`という全体で共通の状態保存先をつくってやりとりしましょう、と。

![redux-store](https://css-tricks.com/wp-content/uploads/2016/03/redux-article-3-03.svg)

仕組みとしては、

- `state`を参照、更新するには`store`経由に限り、`store`は全体で一つしかつくらないこと
- `state`を参照するには`store.getSate()`を使う。
- `state`を更新するには`store.dispatch()`を使い、`action`形式のオブジェクトを渡す
- `action`形式とは最低限何をしたいかの識別文字列として`type`プロパティを持たせる
- `action`の`type`プロパティに応じて`state`を更新する`reducer`という純関数的処理をつくる

うーん、全然頭に入ってきません。。。:scream::scream::scream: でしたが、ようやく[こちら](http://okakacacao.wpblog.jp/technology/what-is-redux)の記事を読み、

- `store`とは申請受付窓口である
    * **窓口の一本化**が図れる
- `action`とは申請書(様式)である
    * `store.dispatch({type: 'TEST', data: 1})` = 窓口に提出(申請書名,申請内容)
    * action一覧が**どんな申請ができるかの一覧表**となって全体像の把握がしやすい
- `reducer`とは実際の申請処理である
    * 同じ申請に対しては**忖度されない**同じ処理をしてくれるのでテストしやすくなる、はず

と理解しました。

## LitElement + Reduxでカウンター

pwa-helpersの`connect`を使うとstateの更新時に`stateChanged(state)`が呼ばれるので、そこでLitElementのプロパティにセットします。

> ※ 非同期処理ライブラリの`redux-thunk`もいれてみてます

```js:my-element.ts
import { LitElement, customElement, property, html } from '@polymer/lit-element'
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { connect } from 'pwa-helpers'

// actions
const COUNT_UP = 'count-up'
const countUp = { type: COUNT_UP }
const countUpLate = dispatch => setTimeout(() => dispatch(countUp), 500) // thunk

// reducer
const initState = { counter: 0 }
const reducer = (state = initState, action) => {
  switch (action.type) {
    case COUNT_UP:
      return { ...state, counter: ++state.counter }
    default:
      return state
  }
}

// store
const store = createStore(reducer, applyMiddleware(thunk))

@customElement('my-element') class extends connect(store)(LitElement) {

  @property() counter

  stateChanged(state) {
    this.counter = state.counter
  }

  render() {
    return html`
      <h2>counter: ${this.counter}</h2>
      <button @click=${() => store.dispatch(countUp)}>+</button>
      <button @click=${() => store.dispatch(countUpLate)}>...+</button>
      `
  }

}
```
[:globe_with_meridians: デモサイト (stackblitz)](https://stackblitz.com/edit/lit-element-redux?file=my-element.ts)

うーん、仕組みはわかってきたものの、なにか遠回りしてる感が。。。

### コンポーネント間で値のやりとり

表示とボタンを別エレメントにしてみます。

![counter-element](https://qiita-image-store.s3.amazonaws.com/0/90678/2224ba38-af7a-7aa4-b5de-722b236e850b.png)

```html:index.html
<counter-view></counter-view>
<counter-button></counter-button>
```

actionやreducerをつくるのは`redux-actions`で楽できそうなので、

```js:my-element.ts
import { LitElement, customElement, property, html } from '@polymer/lit-element'
import { createStore } from 'redux'
import { createAction, handleAction } from 'redux-actions'
import { connect } from 'pwa-helpers'

const countUp = createAction('COUNT_UP') // action
const countUpper = handleAction(countUp, state => ({ ...state, counter: ++state.counter }), { counter: 0 }) // reducer
const store = createStore(countUpper) // store

@customElement('counter-view') class extends connect(store)(LitElement) {

  @property() counter

  stateChanged({ counter }) {
    this.counter = counter
  }

  render() { return html`<h2>counter: ${this.counter}</h2>` }

}

@customElement('counter-button') class extends connect(store)(LitElement) {

   render() { return html`<button @click=${() => store.dispatch(countUp())}>+</button>` }

}
```

[:globe_with_meridians: デモサイト (stackblitz)](https://stackblitz.com/edit/lit-element-redux-xttsdz?file=my-element.ts)

すっきりしつつ、ちゃんと受け渡しできてますね。思ってたより使えそうな気がしてきました。:sunny::sunny::sunny:

以上。