2017年7月20日 星期四

Connecting our First Component with Redux and React

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
      • 處理邏輯且實際跟Redux連結
    • Presentational Component
      • 接收props且render UI
  • 接下來的問題是,若有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)











沒有留言:

張貼留言