Reduxミドルウェアとは?
- store.dispatchが呼び出れてから実際にstateを更新されるまでの間に、処理を挟み込むためのもの
Reduxミドルウェアの例
Loggerミドルウェア
- 以下みたいな感じで実装できるだろう。
const logger = (state) => (next) => (action) => {
console.group(action.type)
console.log('The action: ', action)
const result = next(action) // 次の処理
console.log('The new state :', store.getState())
console.groupEnd()
return result
}
ReduxThunkミドルウェア
ライブラリとして提供される。中身の骨格は以下のような感じである。
const thunk = (store) => (next) => (action) => {
if (typeof action === 'function') {
// これを満たすactionは"handleDeleteTodo(todo)の返り値のcallback関数"とか
return action(store.dispatch)
}
return next(action)
}
要は、dispatchに渡されるactionが、(オブジェクトではなく)callback関数の時に発動される。このcallback関数は、dispatchを引数に取り、行いたい処理を行うcallback関数
であることを前提としている。
// Action Creator
function handleInitialData() {
return (dispatch) => { // callback関数を返している
return Promise.all([
API.fetchTodos(),
API.fetchGoals()
]).then(([todos, goals]) => {
dispatch(receiveDataAction(todos, goals)) // 新たにactionを初期化してdispatchしている
})
}
}
class App extends React.Component {
componentDidMount () {
const { dispatch } = this.props
dispatch(handleInitialData()) // callback関数が渡されている -> Redux Thunksミドルウェアで処理される
}
render(){...}
}
Redux Thunkミドルウェアは主に非同期処理を行いたい時に使用できる。これを使用することで、APIリクエストを行うdata fetchingのコードをComponentの中ではなく、ActionCreatorの中に含めることができる。
ミドルウェアの中で新たにactionがdispatchされるというところが面白い。
ミドルウェアの使用方法
const store = Redux.createStore(
Redux.combineReducers({todos, goals}),
Redux.applyMiddleware(ReduxThunk.default, checker, logger)
)
おまけ1(Optismistic UI)
itemの削除時はUIで一旦消してしまう。API通信でDBの更新に失敗した場合は、そのitemを復活させる。
class Todos extends React.Component {
addItem = (e) => {
e.preventDefault()
const name = this.input.value
// 新しくTodoが作られる時、そのidはサーバーサイドで作られるので、
// UIをoptimistic updateすることはしない
return API.saveTodo(this.input.value)
.then((todo) => {
this.props.store.dispatch(addTodoAction(todo))
this.input.value = ''
})
.catch(() => {
alert('An error occurred when adding Todo. Try again!')
})
}
removeItem = (todo) => {
this.props.store.dispatch(removeTodoAction(todo.id))
return API.deleteTodo(todo.id)
.catch(() => {
// Optimistic UI
// これだとstore.todosの末尾に追加されるのでtodosの並びが変わるが、、、
this.props.store.dispatch(addTodoAction(todo))
alert('An error occurred when removing Todo. Try again!')
})
}
render() {...}
}
おまけ2(inputタグのSingle Source of Truth)
inputタグについて、「ユーザの入力内容をstateに保持し、随時更新すべきである」「inputの中のvalueは随時このstateを反映すべきである」(single source of truthを実現するため)というのが面白かったのでメモっておく。
class Todos extends React.Component {
addItem = (e) => {
e.preventDefault()
this.props.dispatch(handleAddTodo(
this.input.value,
() => this.input.value = ''
))
}
render() {
return (
<div>
<h1>Todos</h1>
<input
type='text'
placeholder='Add Todo'
// inputのvalueが更新されるたびに、input DOM(要はrefを含んでいるDOM)を引数にこのcallback関数が実行される
// つまり、ユーザが入力した内容は随時state(input)に保持される!
ref={(input) => this.input = input}
/>
<button onClick={this.addItem}>Add Todo</button>
</div>
)
}
}