メインコンテンツにスキップ

チュートリアル: connect API の使用

ヒント

現在、React-Redux hooks API をデフォルトとして使用することをお勧めします。ただし、connect API も問題なく動作します。

このチュートリアルでは、Redux ロジックをタイプ別にフォルダに分けるなど、現在では推奨されていない古いプラクティスも示しています。このチュートリアルは、完全を期すために現状維持していますが、現在のベストプラクティスについては、Redux ドキュメントの「Redux Essentials」チュートリアルと、Redux スタイルガイドを参照することをお勧めします。

現在、hooks API を紹介する新しいチュートリアルを作成中です。それまでの間は、Redux Fundamentals, Part 5: UI and Reactで hooks のチュートリアルを読むことをお勧めします。

React Redux の実践的な使い方を見るために、Todo リストアプリを作成するステップバイステップの例を示します。

Todo リストの例

ジャンプ先

React UI コンポーネント

React UI コンポーネントは次のように実装しました

  • TodoApp はアプリのエントリーコンポーネントです。ヘッダー、AddTodoTodoListVisibilityFilters コンポーネントをレンダリングします。
  • AddTodo は、ユーザーが Todo アイテムを入力し、「Todo を追加」ボタンをクリックするとリストに追加できるコンポーネントです
    • onChange で状態を設定する制御された入力を使用します。
    • ユーザーが「Todo を追加」ボタンをクリックすると、Todo をストアに追加するためのアクション (React Redux を使用して提供します) をディスパッチします。
  • TodoList は Todo のリストをレンダリングするコンポーネントです
    • VisibilityFilters のいずれかが選択されると、フィルタリングされた Todo のリストをレンダリングします。
  • Todo は単一の Todo アイテムをレンダリングするコンポーネントです
    • Todo の内容をレンダリングし、Todo が完了したことを取り消し線で示します。
    • onClick で Todo の完了ステータスを切り替えるアクションをディスパッチします。
  • VisibilityFilters は、すべて完了未完了 のシンプルなフィルターセットをレンダリングします。それぞれのフィルターをクリックすると、Todo がフィルタリングされます
    • 親から、ユーザーが現在選択しているフィルターを示す activeFilter プロパティを受け入れます。アクティブなフィルターは下線付きでレンダリングされます。
    • 選択したフィルターを更新するために setFilter アクションをディスパッチします。
  • constants にはアプリの定数データが保持されます。
  • 最後に、index はアプリを DOM にレンダリングします。

Redux ストア

アプリケーションの Redux 部分は、Redux ドキュメントで推奨されているパターンを使用して設定されています

  • ストア
    • todos: 正規化された Todo のリデューサー。すべての Todo の byIds マップと、すべての ID のリストを含む allIds を含みます。
    • visibilityFilters: 単純な文字列 allcompleted、または incomplete
  • アクションクリエーター
    • addTodo は Todo を追加するアクションを作成します。単一の文字列変数 content を取り、自己インクリメントされた idcontent を含む payloadADD_TODO アクションを返します
    • toggleTodo は Todo を切り替えるアクションを作成します。単一の数値変数 id を取り、id のみを含む payloadTOGGLE_TODO アクションを返します
    • setFilter はアプリのアクティブなフィルターを設定するアクションを作成します。単一の文字列変数 filter を取り、filter 自体を含む payloadSET_FILTER アクションを返します
  • リデューサー
    • todos リデューサー
      • ADD_TODO アクションを受け取ると、allIds フィールドに id を追加し、byIds フィールド内に Todo を設定します
      • TOGGLE_TODO アクションを受け取ると、Todo の completed フィールドを切り替えます
    • visibilityFilters リデューサーは、SET_FILTER アクションペイロードから受け取った新しいフィルターにストアのスライスを設定します
  • アクションタイプ
    • actionTypes.js ファイルを使用して、再利用されるアクションタイプの定数を保持します
  • セレクター
    • getTodoListtodos ストアから allIds リストを返します
    • getTodoByIdid で指定されたストア内の Todo を検索します
    • getTodos は少し複雑です。allIds からすべての id を取得し、byIds で各 Todo を検索し、Todo の最終的な配列を返します
    • getTodosByVisibilityFilter は可視性フィルターに従って Todo をフィルタリングします

UI コンポーネントと、上記の接続されていない Redux ストアのソースコードについては、この CodeSandbox をチェックしてください。


次に、React Redux を使用してこのストアをアプリに接続する方法を示します。

ストアの提供

まず、アプリで store を利用できるようにする必要があります。これを行うには、React Redux が提供する <Provider /> API でアプリをラップします。

// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import TodoApp from './TodoApp'

import { Provider } from 'react-redux'
import store from './redux/store'

// As of React 18
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<Provider store={store}>
<TodoApp />
</Provider>,
)

<TodoApp /> が、prop として渡された store を持つ <Provider /> でラップされていることに注意してください。

コンポーネントの接続

React Redux は、Redux ストアから値を読み取る (およびストアが更新されたときに値を再読み取りする) ための connect 関数を提供します。

connect 関数は、両方ともオプションである 2 つの引数を受け取ります

  • mapStateToProps: ストアの状態が変更されるたびに呼び出されます。ストアの状態全体を受け取り、このコンポーネントが必要とするデータのオブジェクトを返す必要があります。

  • mapDispatchToProps: このパラメーターは、関数またはオブジェクトのいずれかになります。

    • 関数である場合、コンポーネントの作成時に一度呼び出されます。dispatch を引数として受け取り、アクションをディスパッチするために dispatch を使用する関数のオブジェクトを返す必要があります。
    • アクションクリエーターのオブジェクトである場合、各アクションクリエーターは、呼び出されると自動的にアクションをディスパッチする prop 関数に変換されます。: この「オブジェクトショートハンド」形式を使用することをお勧めします。

通常、connect は次のように呼び出します

const mapStateToProps = (state, ownProps) => ({
// ... computed data from state and optionally ownProps
})

const mapDispatchToProps = {
// ... normally is an object full of action creators
}

// `connect` returns a new function that accepts the component to wrap:
const connectToStore = connect(mapStateToProps, mapDispatchToProps)
// and that function returns the connected, wrapper component:
const ConnectedComponent = connectToStore(Component)

// We normally do both in one step, like this:
connect(mapStateToProps, mapDispatchToProps)(Component)

まず、<AddTodo /> から作業を始めましょう。新しい Todo を追加するために store への変更をトリガーする必要があります。したがって、ストアにアクションを dispatch できる必要があります。これがその方法です。

addTodo アクションクリエーターは次のようになります

// redux/actions.js
import { ADD_TODO } from './actionTypes'

let nextTodoId = 0
export const addTodo = (content) => ({
type: ADD_TODO,
payload: {
id: ++nextTodoId,
content,
},
})

// ... other actions

connect に渡すことで、コンポーネントはそれを prop として受け取り、呼び出されると自動的にアクションをディスパッチします。

// components/AddTodo.js

// ... other imports
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'

class AddTodo extends React.Component {
// ... component implementation
}

export default connect(null, { addTodo })(AddTodo)

<AddTodo /><Connect(AddTodo) /> という親コンポーネントでラップされていることに注意してください。一方、<AddTodo /> は 1 つの prop (addTodo アクション) を取得します。

addTodo アクションをディスパッチして入力をリセットするために、handleAddTodo 関数も実装する必要があります

// components/AddTodo.js

import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../redux/actions'

class AddTodo extends React.Component {
// ...

handleAddTodo = () => {
// dispatches actions to add todo
this.props.addTodo(this.state.input)

// sets state back to empty string
this.setState({ input: '' })
}

render() {
return (
<div>
<input
onChange={(e) => this.updateInput(e.target.value)}
value={this.state.input}
/>
<button className="add-todo" onClick={this.handleAddTodo}>
Add Todo
</button>
</div>
)
}
}

export default connect(null, { addTodo })(AddTodo)

これで、<AddTodo /> がストアに接続されました。Todo を追加すると、ストアを変更するアクションがディスパッチされます。他のコンポーネントがまだ接続されていないため、アプリには表示されません。Redux DevTools Extension が接続されている場合は、アクションがディスパッチされていることがわかります

ストアもそれに応じて変更されていることがわかります

<TodoList /> コンポーネントは Todo のリストをレンダリングする役割を担っています。したがって、ストアからデータを読み取る必要があります。ストアから必要なデータの一部を記述する関数である mapStateToProps パラメーターを指定して connect を呼び出すことで、有効にします。

<Todo /> コンポーネントは、Todo アイテムを prop として受け取ります。これは todosbyIds フィールドからの情報です。ただし、どの Todo をどの順序でレンダリングする必要があるかを示すストアの allIds フィールドからの情報も必要です。mapStateToProps 関数は次のようになる可能性があります

// components/TodoList.js

// ...other imports
import { connect } from "react-redux";

const TodoList = // ... UI component implementation

const mapStateToProps = state => {
const { byIds, allIds } = state.todos || {};
const todos =
allIds && allIds.length
? allIds.map(id => (byIds ? { ...byIds[id], id } : null))
: null;
return { todos };
};

export default connect(mapStateToProps)(TodoList);

幸いなことに、これを正確に行うセレクターがあります。セレクターをインポートしてここで使用するだけです。

// redux/selectors.js

export const getTodosState = (store) => store.todos

export const getTodoList = (store) =>
getTodosState(store) ? getTodosState(store).allIds : []

export const getTodoById = (store, id) =>
getTodosState(store) ? { ...getTodosState(store).byIds[id], id } : {}

export const getTodos = (store) =>
getTodoList(store).map((id) => getTodoById(store, id))
// components/TodoList.js

// ...other imports
import { connect } from "react-redux";
import { getTodos } from "../redux/selectors";

const TodoList = // ... UI component implementation

export default connect(state => ({ todos: getTodos(state) }))(TodoList);

データの複雑なルックアップや計算は、セレクター関数にカプセル化することをお勧めします。さらに、Reselect を使用して、不要な処理をスキップできる「メモ化」されたセレクターを記述することで、パフォーマンスを最適化できます。(なぜセレクター関数を使用するのか、またどのように使用するのかについての詳細は、派生データの計算に関するReduxドキュメントと、ブログ記事Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performanceを参照してください。)

これで<TodoList />がストアに接続されました。TODOリストを受け取り、それらをマッピングして、各TODOを<Todo />コンポーネントに渡す必要があります。<Todo />はそれらを画面にレンダリングします。TODOを追加してみてください。TODOリストに表示されるはずです!

さらに多くのコンポーネントを接続します。その前に、少し立ち止まって、まずconnectについて詳しく学びましょう。

connectの一般的な呼び出し方

作業しているコンポーネントの種類に応じて、connectの呼び出し方にはさまざまな方法があり、最も一般的なものを以下にまとめます。

ストアをサブスクライブしないストアをサブスクライブする
アクションクリエーターを注入しないconnect()(Component)connect(mapStateToProps)(Component)
アクションクリエーターを注入するconnect(null, mapDispatchToProps)(Component)connect(mapStateToProps, mapDispatchToProps)(Component)

ストアをサブスクライブせず、アクションクリエーターを注入しない

引数を何も指定せずにconnectを呼び出すと、コンポーネントは次のようになります。

  • ストアが変更されても再レンダリングされません
  • アクションを手動でディスパッチするために使用できるprops.dispatchを受け取ります
// ... Component
export default connect()(Component) // Component will receive `dispatch` (just like our <TodoList />!)

ストアをサブスクライブし、アクションクリエーターを注入しない

mapStateToPropsのみでconnectを呼び出すと、コンポーネントは次のようになります。

  • mapStateToPropsがストアから抽出した値にサブスクライブし、それらの値が変更された場合にのみ再レンダリングします
  • アクションを手動でディスパッチするために使用できるprops.dispatchを受け取ります
// ... Component
const mapStateToProps = (state) => state.partOfState
export default connect(mapStateToProps)(Component)

ストアをサブスクライブせず、アクションクリエーターを注入する

mapDispatchToPropsのみでconnectを呼び出すと、コンポーネントは次のようになります。

  • ストアが変更されても再レンダリングされません
  • mapDispatchToPropsで注入した各アクションクリエーターをpropsとして受け取り、呼び出されると自動的にアクションをディスパッチします
import { addTodo } from './actionCreators'
// ... Component
export default connect(null, { addTodo })(Component)

ストアをサブスクライブし、アクションクリエーターを注入する

mapStateToPropsmapDispatchToPropsの両方でconnectを呼び出すと、コンポーネントは次のようになります。

  • mapStateToPropsがストアから抽出した値にサブスクライブし、それらの値が変更された場合にのみ再レンダリングします
  • mapDispatchToPropsで注入したすべてのアクションクリエーターをpropsとして受け取り、呼び出されると自動的にアクションをディスパッチします。
import * as actionCreators from './actionCreators'
// ... Component
const mapStateToProps = (state) => state.partOfState
export default connect(mapStateToProps, actionCreators)(Component)

これら4つのケースが、connectの最も基本的な使用法を網羅しています。connectの詳細については、より詳細に説明しているAPIセクションを引き続きお読みください。


それでは、残りの<TodoApp />を接続しましょう。

TODOの切り替えの相互作用はどのように実装すればよいでしょうか?熱心な読者ならすでにお答えが出ているかもしれません。環境をセットアップし、ここまでの手順に従ってきた場合は、ここで一度手を止めて、自分で機能を実装してみるのも良いでしょう。<Todo />を同様の方法で接続してtoggleTodoをディスパッチしても驚くことではないでしょう。

// components/Todo.js

// ... other imports
import { connect } from "react-redux";
import { toggleTodo } from "../redux/actions";

const Todo = // ... component implementation

export default connect(
null,
{ toggleTodo }
)(Todo);

これでTODOの完了を切り替えることができます。もうすぐです!

最後に、VisibilityFilters機能を実装しましょう。

<VisibilityFilters />コンポーネントは、現在アクティブなフィルターをストアから読み取り、ストアにアクションをディスパッチできる必要があります。したがって、mapStateToPropsmapDispatchToPropsの両方を渡す必要があります。ここのmapStateToPropsは、visibilityFilter状態の単純なアクセサーにすることができます。また、mapDispatchToPropsには、setFilterアクションクリエーターが含まれます。

// components/VisibilityFilters.js

// ... other imports
import { connect } from "react-redux";
import { setFilter } from "../redux/actions";

const VisibilityFilters = // ... component implementation

const mapStateToProps = state => {
return { activeFilter: state.visibilityFilter };
};
export default connect(
mapStateToProps,
{ setFilter }
)(VisibilityFilters);

一方、アクティブなフィルターに従ってTODOをフィルタリングするために、<TodoList />コンポーネントも更新する必要があります。以前、<TodoList />connect関数呼び出しに渡したmapStateToPropsは、単にTODOリスト全体を選択するセレクターでした。TODOをステータスでフィルタリングするのに役立つ別のセレクターを記述しましょう。

// redux/selectors.js

// ... other selectors
export const getTodosByVisibilityFilter = (store, visibilityFilter) => {
const allTodos = getTodos(store)
switch (visibilityFilter) {
case VISIBILITY_FILTERS.COMPLETED:
return allTodos.filter((todo) => todo.completed)
case VISIBILITY_FILTERS.INCOMPLETE:
return allTodos.filter((todo) => !todo.completed)
case VISIBILITY_FILTERS.ALL:
default:
return allTodos
}
}

そして、セレクターの助けを借りてストアに接続します

// components/TodoList.js

// ...

const mapStateToProps = (state) => {
const { visibilityFilter } = state
const todos = getTodosByVisibilityFilter(state, visibilityFilter)
return { todos }
}

export default connect(mapStateToProps)(TodoList)

これで、React Reduxを使用したTODOアプリの非常に簡単な例が完成しました。すべてのコンポーネントが接続されました!素晴らしいでしょう?🎉🎊

さらにヘルプが必要な場合