4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

LitElement + Redux でカウンター

Last updated at Posted at 2018-11-16

前々回lit-html前回LitElementについて勉強してきて、それらを使ったWebアプリのサンプルReduxを使ってたのでコネクタとなるpwa-helpersとともに勉強してみます。

Reduxとは

redux

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

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

redux-store

仕組みとしては、

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

うーん、全然頭に入ってきません。。。:scream::scream::scream: でしたが、ようやくこちらの記事を読み、

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

と理解しました。

LitElement + Reduxでカウンター

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

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

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)

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

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

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

counter-element

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

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

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)

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

以上。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?