6
5

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.

Redux: redux-sagaで非同期処理を試す ー 公式のカウンター作例を使って

Last updated at Posted at 2019-12-11

redux-sagaは、非同期処理が同期的に書け、複数の処理を同時並行で実行できるライブラリです。GitHubのredux-saga/examples/には、簡単なカウンターの作例が3つ収められています。その中でもっともシンプルなredux-saga/examples/counter-vanillaは、index.htmlひとつにまとめられていて、ビルドが要りません。この作例(サンプル001)のコードを段階に分けてご説明しましょう。さらにReactも加えて、コンポーネントで組み立てた作例は「React + Redux: redux-sagaで非同期処理を試す」で解説しています。ご興味のある方はご覧ください。

サンプル001■Redux: Counter example with redux-saga

See the Pen Redux: Counter example with redux-saga by Fumio Nonaka (@FumioNonaka) on CodePen.

Reduxでカウンターをつくる

JavaScriptコードを書きはじめる前に、CDNからのReduxとredux-sagaのライブラリの読み込みです。公式作例はunpkgを用いています。けれど、redux-sagaのバージョンが1.0.0-beta.1と少し古いようです。そこで、新しいライブラリ(1.0.5)が手に入るcdnjsに差し替えましょう。

<!-- <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
<script src="https://unpkg.com/redux-saga@1.0.0-beta.1/dist/redux-saga.min.js"></script> -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.4/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-saga/1.0.5/redux-saga.umd.min.js"></script>

まず、redux-sagaは使わずに、Reduxのアプリケーションをつくります。数値を含むカウンターのテキストと、ボタンは[+][-][Increment if odd]の4つです。

<div>
	<p>
	Clicked: <span id="value">0</span> times
	<button id="increment">+</button>
	<button id="decrement">-</button>
	<button id="incrementIfOdd">Increment if odd</button>
	</p>
</div>

Reduxアプリケーションでは、処理の開始をActionで知らせます。Actionを受け取って、どのActionかtypeで判別して結果を返すのが、つぎのReducerです。今回stateは数値で、その加算あるいは減算が行われます。

function counter(state, action) {
	if (typeof state === 'undefined') {
		return 0
	}
	switch (action.type) {
		case 'INCREMENT':
			return state + 1
		case 'DECREMENT':
			return state - 1
		default:
			return state
	}
}

Reduxはアプリケーションにひとつだけ備わるStoreが状態を一手に管理します。そのStoreをつくるのがcreateStore()です(「React + Redux入門 02: フィールドに入力したテキストを項目リストに加える」02「Storeをつくる」参照)。引数にはReducerを渡します。

ボタンクリック(clickイベント)のリスナー関数から呼び出しているのが、Actionを送り出すdispatch()です。引数のActionオブジェクトは、少なくとも識別のためのプロパティtypeをもたなければなりません。これが、前掲Reducerによる判別に使われるのです。

subscribe()は、Actionが送られるたびに、引数のリスナー関数を呼び出します。Storeがもつstateを参照するのがgetState()です。

var store = Redux.createStore(
	counter
)
var valueEl = document.getElementById('value')
function render() {
	valueEl.innerHTML = store.getState().toString()
}
render()
store.subscribe(render)
document.getElementById('increment')
.addEventListener('click', function () {
	store.dispatch({ type: 'INCREMENT' })
})
document.getElementById('decrement')
.addEventListener('click', function () {
	store.dispatch({ type: 'DECREMENT' })
})
document.getElementById('incrementIfOdd')
.addEventListener('click', function () {
	if (store.getState() % 2 !== 0) {
	store.dispatch({ type: 'INCREMENT' })
	}
})

このコードにより、ボタンごとに決められたActionが送り出され、カウンタの数値となるstateの値が変わります。すると、ページのカウンタの数がその値で書き替えるという仕組みです。なお、[Increment if odd]のボタンは、カウンタが奇数のときのみカウントアップします。

redux-sagaで非同期処理を行う

つぎに、redux-sagaで非同期の処理を行いましょう。SagaはReducerに替わって、Storeに送られるあらかじめ決められたActionを監視します。コードは以下のとおりてす。Sagaのアプリケーションへの組み込みと起動は、このあとご説明します。

非同期の処理を扱うため、ジェネレーター関数のfunction*宣言を用いることにご注目ください(「ジェネレーター関数」参照)。rootSaga()が、あとでミドルウェアから実行される関数です。yieldキーワードに定めたtakeEvery()は、第1引数(INCREMENT_ASYNC)のActionを監視し、送り出されるたびに第2引数(incrementAsync())の関数を呼び出します。

関数incrementAsync()は、delay()で引数のミリ秒数実行を止めます。実行再開後、Storeに引数オブジェクトのActionを送り出すのがput()です。つまり、1秒待ってカウントアップされることになります。

const effects = ReduxSaga.effects
function* incrementAsync() {
	yield effects.delay(1000)
	yield effects.put({type: 'INCREMENT'})
}
function* counterSaga() {
	yield effects.takeEvery('INCREMENT_ASYNC', incrementAsync)
}

それでは、SagaをStoreにミドルウェアとして組み込みます。Sagaミドルウェアをつくるのが、createSagaMiddleware()です。ミドルウェアは、applyMiddleware()でStoreに適用します。そのうえで、run()によりSagaを実行してください。前掲関数render()を定義するコードの前に加えるのが、つぎのステートメントです。

const createSagaMiddleware = ReduxSaga.default
const sagaMiddleware = createSagaMiddleware()
var store = Redux.createStore(
	counter,
	Redux.applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(counterSaga)

こうして、前掲サンプル001の作例が書き上がりました。

コードの構文を改める

サンプル001にはECMAScript 2015のconstと古いvar宣言が混じっています。コード全体をECMAScript 2015の構文に改めましょう1。まず、宣言varは定数constに書き替えます(値を上書きしませんので、letは使いません)。

// var store = Redux.createStore(
const store = Redux.createStore(
	counter,
	Redux.applyMiddleware(sagaMiddleware)
)

// var valueEl = document.getElementById('value')
const valueEl = document.getElementById('value')

つぎに、無名関数にはアロー関数式=>を用いましょう。

document.getElementById('increment')
// .addEventListener('click', function () {
.addEventListener('click', () =>
	store.dispatch({ type: 'INCREMENT' })
)
document.getElementById('decrement')
// .addEventListener('click', function () {
.addEventListener('click', () =>
	store.dispatch({ type: 'DECREMENT' })
)
document.getElementById('incrementIfOdd')
// .addEventListener('click', function () {
.addEventListener('click', () => {
	if (store.getState() % 2 !== 0) {
		store.dispatch({ type: 'INCREMENT' })
	}
})
document.getElementById('incrementAsync')
// .addEventListener('click', function () {
.addEventListener('click', () =>
	store.dispatch({ type: 'INCREMENT_ASYNC' })
)

これでECMAScript 2015の構文にできました。あとひとつ、プロパティのinnerHTMLtextContentに差し替えます。セキュリティやパフォーマンスの点から、後者で足りる場合にはこちらを使った方がよいからです。MDNのリファレンスから、引用しておきましょう。

「element.innerHTML」「セキュリティの考慮事項

プレーンテキストを挿入するときにはinnerHTMLを使用せず、代わりにNode.textContentを使用することをお勧めします。これは渡されたコンテンツを HTML として解釈するのではなく、生テキストとして挿入します。

「Node.textContent」「innerHTMLとの違い

textContentはパフォーマンスを向上させる場合があります。テキストが HTML として解析されないためです。さらに、textContentを使用することで XSS 攻撃を防ぐことができます。

function render() {
	// valueEl.innerHTML = store.getState().toString()
	valueEl.textContent = store.getState().toString()
}

構文を改めたサンプル002もCodePenに公開しました。

サンプル002■ Redux + ES6: Counter example with redux-saga

See the Pen Redux + ES6: Counter example with redux-saga by Fumio Nonaka (@FumioNonaka) on CodePen.

  1. GitHubのREADME_ja.mdで「counter-vanilla」の説明を見ると、「ES2015を使っていない素のJavaScriptとUMDビルドを使用したデモ」と書かれています。けれど、英語の「counter-vanilla」はECMAScript 2015には触れていません。おそらく、日本語版の説明が古いのでしょう。

6
5
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
6
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?