Redux Tutorial: createStore
- 我們來看看一個基本的reducer,假設這個reducer第一次被呼叫,那state會是怎麼樣的? 由於default state是一個物件,所以它會是undefined。
function reducer (state, action) {
switch (action.type) {
case 'SUBMIT_USER' :
return Object.assign({}, state, {
name: action.user
})
case 'CHANGE_LOADING' :
return Object.assign({}, state, {
isLoading: action.loading
})
default :
return state
}
}
- 因此我們需要給reducer一個initial state,當第一次呼叫是undefined的時候,state就會設置成initial state。
const initialState = {
name: '',
isLoading: false
}
function reducer (state = initialState, action) {
switch (action.type) {
case 'SUBMIT_USER' :
return Object.assign({}, state, {
name: action.user
})
case 'CHANGE_LOADING' :
return Object.assign({}, state, {
isLoading: action.loading
})
default :
return state
}
}
- 我們的application最後會有好幾個reducer,所以application的state會是一個物件,他結合了每一個reducer的initial state。
- 現在我們知道用設置reducer的initial state的方式可以初始化產生single state tree,但實際上要怎麼做呢? Redux提供一個createStore( ) method,可以傳一個reducer或是一個包含多個reducer的物件給它,它會以undefined state為參數去呼叫每個reducer,以此來初始化各個state並且把state return回來。
import { createStore } from 'redux'
import myReducer from '../reducer'
const store = createStore(myReducer)
- 我們如何將single state tree整到React呢? 下面是一般初始化React component的作法,假設用React Router:
ReactDOM.render(
{routes},
document.getElementById('app')
)
- 我們可以使用"react-redux" module提供的Provider,改去render這個Provider,並且把store帶進去property。
import { Provider } from 'react-redux'
import myReducer from '../reducer'
.
.
.
const store = createStore(myReducer)
ReactDOM.render(
<Provider store={store}>
{routes}
</Provider>,
document.getElementById('app')
)
Connect: dispatch, mapStateToProps, mapDispatchToProps, bindActionCreators
- 之前我們有提過需要dispatch用以呼叫Reducer並且可以把Action帶進去。使用react-redux,dispatch會當成prop傳入component,為達此目的我們必須讓component跟Redux連結起來,你可以使用react-redux的connect( ) method來做到。
- 下面是一個範例,不是將component export出去,而是將connect function的呼叫export出去,它會return一個function,接著會將component帶到那個function中。這樣就可以產生所謂的Container Component。範例中有一個dispatch function被當成prop傳入component,當你點擊div,Reducer就會被呼叫,state也會跟著改變。
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { setUser } from '../actions'
const MyComponent = React.createClass({
propTypes: {
dispatch: PropTypes.func.isRequired,
},
handleClick () {
this.props.dispatch(setUser('Tyler'))
},
render () {
return (
<div onClick={this.handleClick}> Hello </div>
)
}
})
export default connect()(MyComponent)
- 將你的application分成Container及Presentational Components是不錯的主意
- Container Component
- Presentational Component
- 接下來的問題是,若有component需要state tree的部分資訊怎麼辦? 假設一個state tree長的如下:
{
authedId: 'tyler',
name: 'Tyler McGinnis'
isAuthed: true,
isFetching: false,
bio: 'Posuere ad repellendus odit sagittis? Non velit do mollitia dignissim, quam nihil cupidatat laboriosam'
}
- 接著假設有一個basic component需要render prop: name及bio
const MyComponent = React.createClass({
propTypes: {
name: PropTypes.string.isRequired,
bio: PropTypes.string.isRequired,
},
render () {
return (
<div>
<h1>{this.props.name}</h1>
<p>{this.props.bio}</p>
</div>
)
}
})
- 我們可以這樣做,定義一個mapStateToProps( ) function會回傳從帶入的state中得到的資訊而產生的物件,並將mapStateToProps當成connect( )的第一個參數傳入,這樣就可以告訴Redux那些prop我要傳到component中。
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
const MyComponent = React.createClass({
propTypes: {
name: PropTypes.string.isRequired,
bio: PropTypes.string.isRequired,
},
render () {
return (
<div>
<h1>{this.props.name}</h1>
<p>{this.props.bio}</p>
</div>
)
}
})
function mapStateToProps (state) {
return {
name: state.name,
bio: state.bio
}
}
export default connect(mapStateToProps)(MyComponent)
- 接下來討論幾個方便但非必須的method。
- 假設我們有多個action想要去dispatch,長的可能如下,這段程式碼是work的,但有多個 this.props.dispatch(actionCreator())有點麻煩,而為了包裝setUser()的dispatch還得特地定義handleClear(),為什麼不直接把Action跟dispatch bind在一起就好呢!
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { setUser, addPost, fanoutPost } from '../actions'
const MyComponent = React.createClass({
propTypes: {
dispatch: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
},
handleClear (user) {
this.props.dispatch(setUser(user))
},
submit (post) {
this.props.dispatch(addPost(post))
this.props.dispatch(fanoutPost(post))
},
render () {
return <Post name={this.props.name} handleSubmit={this.submit} handleClear={this.handleClear} />
}
})
function mapStateToProps (state) {
return {
name: state.name
}
}
export default connect(mapStateToProps)(MyComponent)
- connect( ) function的第二個參數可以允許我們將Action跟dispatch bind在一起。我們定義mapDispatchToProps ( ) function,他應該要回傳一個物件,物件裡面的method會被當成prop傳入component。bindActionCreators( )會把所帶入的Action Creators一個個被dispatch包裝起來,並將包裝後的結果當成prop放在物件中並return回去。
import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import * as actions from from '../actions'
const MyComponent = React.createClass({
propTypes: {
name: PropTypes.string.isRequired,
setUser: PropTypes.func.isRequired,
addPost: PropTypes.func.isRequired,
fanoutPost: PropTypes.func.isRequired,
},
submit (post) {
addPost(post)
fanoutPost(post)
},
render () {
return <Post name={this.props.name} handleSubmit={this.submit} handleClear={this.props.setUser} />
}
})
function mapStateToProps (state) {
return {
name: state.name
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators(actions, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)