connect()関数はReactコンポーネントをRedux storeに接続するために使います。
connect()関数(の戻り値)はstoreから必要なデータを抽出して接続済みコンポーネントに渡します。また、connect()関数(の戻り値)はactionをstoreにdispatchすることができる関数も接続済みコンポーネントに渡します。
connect()関数(の戻り値)は、渡されたコンポーネントクラスに変更を加えません。代わりに、渡されたコンポーネントクラスをラップしたstoreに接続した新しいコンポーネントクラスを返します。
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)
mapStateToPropsとmapDispatchToPropsは、それぞれ、Redux storeのstateとdispatchを扱います。stateはmapStateToProps関数の第1引数として提供されます。dispatchはmapDispatchToProps関数の第1引数として提供されます。
mapStateToPropsとmapDispatchToPropsの戻り値をそれぞれstatePropsとdispatchPropsとします。それらはmergePropsが定義されていれば、第1引数と第2引数になります。ちなみに第3引数はownPropsです。それらを統合したmergePropsの結果はmergedPropsと言われます。それは接続済みコンポーネントに渡されます。
connect()の引数connectは以下の4つの引数を受け取ります。それらはすべてオプションです。
mapStateToProps?: FunctionmapDispatchToProps?: Function | ObjectmergeProps?: Functionoptions?: ObjectmapStateToProps?: (state, ownProps?) => ObjectmapStateToProps関数がconnectに渡されると新しく生成されるラップしているコンポーネントはRedux storeの更新をsubscribeします。これはstoreが更新される度、mapStateToPropsが実行されることを意味します。mapStateToProps関数は素のobjectを返す必要があります。これはラップされたコンポーネントのpropsとマージされます。storeの更新をsubscribeしたくない場合は、mapStateToPropsの所にnullかundefinedを渡します。
state: ObjectownProps?: ObjectmapStateToProps関数は最大2つの引数を受け取ります。関数の定義に設定された引数の数によって実行されるタイミングが異なります。つまり、定義時にownPropsを受け取るようにしているかしていないかです。
詳しくはこちらを見てください。
statemapStateToProps関数定義の引数の数が1つの場合、mapStateToProps関数はstoreのstateが変更される時に実行されます。その時、store stateのみ引数として受け取ります。
const mapStateToProps = (state) => ({ todos: state.todos })
ownPropsmapStateToProps関数定義の引数の数が2つの場合、storeのstateが変更されるもしくは(浅い(shallow)比較に基づいた)新しいpropsを受け取った時に実行されます。それの第1引数はstoreのstateです。第2引数はラップしている(外側の)コンポーネントが受け取るpropsです。第2引数のことをownPropsと言います。
const mapStateToProps = (state, ownProps) => ({
todo: state.todos[ownProps.id],
})
mapStateToPropsの戻り値は、(Factory Functionの場合を除いて)objectである必要があります。そのobjectはstatePropsと言われます。statePropsは接続済みコンポーネントのpropsにマージ(merge)されます。statePropsはmergePropsの第1引数として渡されます。
mapStateToPropsの戻り値によって接続済みコンポーネントが再レンダリングされるかが決まります。(詳しくはこちら)
mapStateToPropsを使いこなしたい場合は、こちらを見てください。
mapStateToPropsとmapDispatchToPropsをfactory function(objectでなく関数を返す関数)として定義している場合、その戻り値の関数がmapStateToPropsやmapDispatchToPropsとして扱われます。詳しくはFactory Functionsやパフォーマンス最適化に関するガイドを見てください。
mapDispatchToProps?: Object | (dispatch, ownProps?) => ObjectmapDispatchToPropsはconnect()の第2引数です。これはobject、関数、渡されないかのいずれかです。
以下のようにconnect()の第2引数(mapDispatchToProps)を指定しない場合、コンポーネントはデフォルトでdispatchを受け取ります。
// `mapDispatchToProps`を渡さない。
connect()(MyComponent)
connect(mapStateToProps)(MyComponent)
connect(mapStateToProps, null, mergeProps, options)(MyComponent)
mapDispatchToPropsが関数の場合、それは最大で2つの引数を持つ関数として定義することができます。
dispatch: FunctionownProps?: ObjectdispatchmapDispatchToPropsの引数が1つと定義されている場合、storeのdispatchが渡されます。
const mapDispatchToProps = (dispatch) => {
return {
// dispatching plain actions
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' }),
reset: () => dispatch({ type: 'RESET' }),
}
}
ownPropsmapDispatchToPropsの引数が1つと定義されている場合、第1引数にはdispatchが渡されます。第2引数にはラップしているコンポーネントに渡されたpropsが渡されます。新しいpropsが接続済みコンポーネントに渡される度、mapDispatchToProps関数が実行されます。
第2引数はownPropsと言われます。
// binds on component re-rendering
<button onClick={() => this.props.toggleTodo(this.props.todoId)} />
// binds on `props` change
const mapDispatchToProps = (dispatch, ownProps) => ({
toggleTodo: () => dispatch(toggleTodo(ownProps.todoId)),
})
mapDispatchToPropsで定義されている引数の数によってownPropsが渡されるか決まります。詳しくはこれを見てください。
mapDispatchToProps関数はobjectを返す必要があります。そのobjectの各フィールドは関数でなければなりません。その関数が実行されるとactionがstoreにdispatchされます。mapDispatchToProps関数の戻り値はdispatchPropsと言われます。接続済みコンポーネントのpropsにマージされます。dispatchPropsはmergePropsの第2引数になります。
const createMyAction = () => ({ type: 'MY_ACTION' })
const mapDispatchToProps = (dispatch, ownProps) => {
const boundActions = bindActionCreators({ createMyAction }, dispatch)
return {
dispatchPlainObject: () => dispatch({ type: 'MY_ACTION' }),
dispatchActionCreatedByActionCreator: () => dispatch(createMyAction()),
...boundActions,
// こうやってdispatchを渡すこともできます。
dispatch,
}
}
mapDispatchToPropsを使いこなしたい場合は、こちらを見てください。
mapStateToPropsとmapDispatchToPropsをfactory function(objectでなく関数を返す関数)として定義している場合、その戻り値の関数がmapStateToPropsやmapDispatchToPropsとして扱われます。詳しくはFactory Functionsやパフォーマンス最適化に関するガイドを見てください。
以下のように、mapDispatchToPropsはaction creatorを値に持つobjectにすることもできます。
import { addTodo, deleteTodo, toggleTodo } from './actionCreators'
const mapDispatchToProps = {
addTodo,
deleteTodo,
toggleTodo,
}
export default connect(null, mapDispatchToProps)(TodoApp)
このケースではReact-ReduxはbindActionCreatorsを使って各action creatorをstoreのdispatchにbindします。mapDispatchToPropsの戻り値はdispatchPropsと言われます。接続済みコンポーネントのpropsにマージされるかmergePropsの第2引数になります。
// internally, React-Redux calls bindActionCreators
// to bind the action creators to the dispatch of your store
bindActionCreators(mapDispatchToProps, dispatch)
mapDispatchToPropsのObject Shorthand Formを使ったやり方について詳しく知りたい場合はこちらを見てください。
mergeProps?: (stateProps, dispatchProps, ownProps) => ObjectmergePropsはラップされたコンポーネントに最終的に渡るpropsを決定します。mergePropsがconnect関数に渡されてない場合、ラップされたコンポーネントにはデフォルトで{ ...ownProps, ...stateProps, ...dispatchProps } が渡されます。
mergePropsは最大で3つの引数を渡すことができます。それらの引数は、それぞれ、mapStateToProps()とmapDispatchToProps()の戻り値とラップしているコンポーネントのpropsです。
statePropsdispatchPropsownPropsmergePropsが返す素のobjectにある各値はラップされたコンポーネントのpropsになります。この関数でpropsに応じてstateの一部らから更に値を抽出したり、action creatorに特定のpropsの値をbindしたりすることができます。
mergePropsの戻り値はmergedPropsと言われます。これの各値はラップされたコンポーネントのpropsになります。
options?: Object{
context?: Object,
pure?: boolean,
areStatesEqual?: Function,
areOwnPropsEqual?: Function,
areStatePropsEqual?: Function,
areMergedPropsEqual?: Function,
forwardRef?: boolean,
}
context: Object注意: この変数は6.0以上でサポートされています。
React-Redux 6.0からReact-Redux内で使用できるカスタムcontextインスタンスを設定できるようになりました。contextインスタンスを<Provider />と接続済みコンポーネントに渡す必要があります。contextインスタンスを接続済みコンポーネントに渡す方法は、このオプションを使う方法とレンダリング時に接続済みコンポーネントのprops経由で渡す方法があります。
// const MyContext = React.createContext();
connect(mapStateToProps, mapDispatchToProps, null, { context: MyContext })(
MyComponent
)
pure: booleantrueラップされたコンポーネントはpure component(propsのみに依存するコンポーネントのことでPureComponentではない。)とみなされ、propsとRedux Storeのstate以外の入力やstateには依存しないとみなされます。
options.pureがtrueの場合、connectは不要なmapStateToProps、mapDispatchToProps、mergeProps、renderの呼び出しを予防するために複数の等価チェックを行います。その等価チェックはareStatesEqual、areOwnPropsEqual、areStatePropsEqual、areMergedPropsEqualです。これらの関数はデフォルトの物で99%の場合で問題ありませんが、パフォーマンスや別の理由でこれらを置き換えたいかもしれません。
以下でそれらの例を示します。
areStatesEqual: (next: Object, prev: Object) => booleanstrictEqual: (next, prev) => prev === nextpureが有効な場合、現在のstore stateと1つ前のそれを比較します。
例 1
const areStatesEqual = (next, prev) =>
prev.entities.todos === next.entities.todos
mapStateToPropsの処理コストが高い、かつ、それがstateのごく一部のみに依存している場合はareStatesEqualを上書きすると良いと思います。上の例では指定したstateの一部以外のstateの変化は無視されます。
例 2
store stateを変更するimpure reducersが存在している場合、areStatesEqual は常にfalseを返すと良いと思います。
const areStatesEqual = () => false
おそらく、これはmapStateToProps関数によって他の等価チェックに影響を及ぼすと思います。
areOwnPropsEqual: (next: Object, prev: Object) => booleanshallowEqual: (objA, objB) => boolean
( 各objectの各フィールドの値が等しい場合、trueを返します。 )pureが有効な場合、現在のpropsと1つ前のpropsを比較します。
現在のpropsを通過させたい場合、areOwnPropsEqualを上書きすると良いと思います。propsを通過させるには、mapStateToProps、mapDispatchToProps、mergePropsを実装する必要があります。(recomposeのmapPropsのような別の方法の方が簡単かもしれません。)
areStatePropsEqual: (next: Object, prev: Object) => booleanfunctionshallowEqualpureが有効な場合、現在のmapStateToProps戻り値と1つ前のそれを比較します。
areMergedPropsEqual: (next: Object, prev: Object) => booleanshallowEqualpureが有効な場合、現在のmergeProps戻り値と1つ前のそれを比較します。
mapStateToPropsが関連するpropsが変更された場合のみ新しいobjectを返すメモ化されたselectorを使っているなら、areStatePropsEqualを上書きしてstrictEqualにした方が良いと思います。mapStateToPropsが実行される度、各propsの余計な等価性チェックを避けることができるので少しだけパフォーマンスが改善します。
selectorが複雑なpropsを生成する場合、areMergedPropsEqualを上書きしてdeepEqualな実装にすると良いと思います。(たぶん、deep equalは再レンダリングよりも速いと思います。)
forwardRef: boolean注意: このパラメータはバージョン6.0からサポートされています。
{forwardRef : true}がconnectに渡されると、refをラップしている接続済みコンポーネントに渡すとref.currentは内側のラップされたコンポーネントを参照します。
connect()の戻り値connect()はコンポーネントを受け取って、注入される追加のpropsを持つそれをラップしているコンポーネントを返すラッパー関数です。
import { login, logout } from './actionCreators'
const mapState = (state) => state.user
const mapDispatch = { login, logout }
// 1回目: コンポーネントをラップするために使うHOCを返します。
const connectUser = connect(mapState, mapDispatch)
// 2回目: mergedPropsを持つラップしているコンポーネントを返します。
// HOCを使うと異なるコンポーネントに同じ動作を追加することができます。
const ConnectedUserLogin = connectUser(Login)
const ConnectedUserProfile = connectUser(Profile)
通常、ラッパー関数は変数に格納されることなく、すぐに実行されます。
import { login, logout } from './actionCreators'
const mapState = (state) => state.user
const mapDispatch = { login, logout }
// connect関数を実行して、すぐにラッパー関数を実行して最終的なラッパーコンポーネントを生成します。
export default connect(mapState, mapDispatch)(Login)
connectはとても柔軟なので、いろいろな使い方ができます。その使用例を見ていきましょう。
dispatchを注入してstoreをsubscribeしない。export default connect()(TodoApp)
import * as actionCreators from './actionCreators'
export default connect(null, actionCreators)(TodoApp)
dispatchとglobal stateの各フィールドを注入する。以下のようにしないでください。
TodoAppはstateが変更される度、再レンダリングされるのでパフォーマンスの最適化が犠牲になります。 だから、個々のコンポーネントが必要なstaetの部分のみをsubscribeするようにした方が良いです。
// これはダメ!
export default connect((state) => state)(TodoApp)
dispatchとtodosを注入します。function mapStateToProps(state) {
return { todos: state.todos }
}
export default connect(mapStateToProps)(TodoApp)
todosとすべてのaction creatorを注入します。import * as actionCreators from './actionCreators'
function mapStateToProps(state) {
return { todos: state.todos }
}
export default connect(mapStateToProps, actionCreators)(TodoApp)
- todosとすべてのaction creator(addTodo, completeTodo, …)をactionsとして注入します。
import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(actionCreators, dispatch) }
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
todosと特定のaction creator(addTodo)を注入します。import { addTodo } from './actionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addTodo }, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
todosと特定のaction creator(addTodoとdeleteTodo)を注入します。import { addTodo, deleteTodo } from './actionCreators'
function mapStateToProps(state) {
return { todos: state.todos }
}
const mapDispatchToProps = {
addTodo,
deleteTodo,
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
todosとtodoActionCreatorsとtodoActionsとして、counterActionCreatorsをcounterActionsとして注入します。import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return {
todoActions: bindActionCreators(todoActionCreators, dispatch),
counterActions: bindActionCreators(counterActionCreators, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
todosと、todoActionCreatorsとcounterActionCreatorsを統合してactionsとして注入します。import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(
{ ...todoActionCreators, ...counterActionCreators },
dispatch
),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
todasと、すべてのtodoActionCreatorsとcounterActionCreatorsの各action creatorをpropsとして注入します。import * as todoActionCreators from './todoActionCreators'
import * as counterActionCreators from './counterActionCreators'
import { bindActionCreators } from 'redux'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{ ...todoActionCreators, ...counterActionCreators },
dispatch
)
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
todosをtodosとして注入します。import * as actionCreators from './actionCreators'
function mapStateToProps(state, ownProps) {
return { todos: state.todos[ownProps.userId] }
}
export default connect(mapStateToProps)(TodoApp)
todosをtodosとして注入します。そして、props.userIdをactionに注入します。import * as actionCreators from './actionCreators'
function mapStateToProps(state) {
return { todos: state.todos }
}
function mergeProps(stateProps, dispatchProps, ownProps) {
return Object.assign({}, ownProps, {
todos: stateProps.todos[ownProps.userId],
addTodo: (text) => dispatchProps.addTodo(ownProps.userId, text),
})
}
export default connect(mapStateToProps, actionCreators, mergeProps)(TodoApp)
mapStateToPropsとmapDispatchToProps関数定義の引数の数によって、引数にownPropsを受け取るか決まります。
mapStateToPropsとmapDispatchToProps関数定義関数定義で引数の数が1つ(func.lengthが1)の場合、ownPropsはmapStateToPropsとmapDispatchToProps関数に渡されません。例えば、以下のように定義された関数は第2引数にownPropsを受け取りません。ownPropsがundefinedの場合、デフォルトの値が使われます。
function mapStateToProps(state) {
console.log(state) // state
console.log(arguments[1]) // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
console.log(state) // state
console.log(ownProps) // {}
}
// console.log(mapStateToProps.length) => 1
関数が0個または2以上の個数の引数を受け取るように定義されている場合、ownPropsが渡されます。
const mapStateToProps = (state, ownProps) => {
console.log(state) // state
console.log(ownProps) // ownProps
}
function mapStateToProps() {
console.log(arguments[0]) // state
console.log(arguments[1]) // ownProps
}
const mapStateToProps = (...args) => {
console.log(args[0]) // state
console.log(args[1]) // ownProps
}
mapStateToPropsもしくはmapDispatchToProps関数が関数を返す場合、コンポーネントがインスタンス化する際に一度だけ実行されます。それらの戻り値は、それ以後、それぞれmapStateToPropsとmapDispatchToProps関数として取り扱われます。
factory functionsはメモ化されたselectorによく使われます。これによって、クロージャー内にコンポーネントインスタンスごとにselectorを作成することが可能になります。
const makeUniqueSelectorInstance = () =>
createSelector([selectItems, selectItemId], (items, itemId) => items[itemId])
const makeMapState = (state) => {
const selectItemForThisComponent = makeUniqueSelectorInstance()
return function realMapState(state, ownProps) {
const item = selectItemForThisComponent(state, ownProps.itemId)
return { item }
}
}
export default connect(makeMapState)(SomeComponent)
Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)
Copyright (c) 2021 38elements
The MIT License (MIT)
Copyright (c) 2015-present Dan Abramov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.