react:redux和react-redux

工作流程

  1. 用戶(通過View)發出Action,發出方式就用到了dispatch方法。
  2. Store自動調用Reducer,並且傳入兩個參數:當前State和收到的Action,Reducer會返回新的State
  3. State一旦有變化,Store就會調用監聽函數,來更新View。

可以看到,在整個流程中數據都是單向流動的,這種方式保證了流程的清晰。

名詞解釋

createStore 創建 store 對象,包含 getState, dispatch, subscribe, replaceReducer
reducer reducer 是一個計劃函數,接收舊的 state 和 action,生成新的 state
action action 是一個對象,必須包含 type 字段
dispatch dispatch( action ) 觸發 action,生成新的 state
subscribe 實現訂閱功能,每次觸發 dispatch 的時候,會執行訂閱函數
combineReducers 多 reducer 合併成一個 reducer
replaceReducer 替換 reducer 函數
middleware 擴展 dispatch 函數!

redux 和 react 沒有關係,redux 可以用在任何框架中。
connect 不屬於 redux,它其實屬於 react-redux

UI組件和容器組件

React-Redux 將所有組件分成兩大類:UI 組件(presentational component)和容器組件(container component)。

UI組件:

  • 只負責 UI 的呈現,不帶有任何業務邏輯
  • 沒有狀態(即不使用this.state這個變量)
  • 所有數據都由參數(this.props)提供
  • 不使用任何 Redux 的 API
const Title = value => <h1>{value}</h1>;

容器組件:

  • 負責管理數據和業務邏輯,不負責 UI 的呈現
  • 帶有內部狀態
  • 使用 Redux 的 API

如果一個組件既有 UI 又有業務邏輯,那怎麼辦?
將它拆分成下面的結構:外面是一個容器組件,裏面包了一個UI 組件。前者負責與外部的通信,將數據傳給後者,由後者渲染出視圖。(其實就是我們最常見的class組件)

connect()

作用:將 UI 組件生成容器組件
下面 TodoList 是 UI 組件,VisibleTodoList 就是生成的容器組件。

import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);

但是,因爲沒有定義業務邏輯,上面這個容器組件毫無意義,只是 UI 組件的一個單純的包裝層。爲了定義業務邏輯,需要給出下面兩方面的信息。

  1. 輸入邏輯:外部的數據(即state對象)如何轉換爲 UI 組件的參數
  2. 輸出邏輯:用戶發出的動作如何變爲 Action 對象,從 UI 組件傳出去。

因此,connect方法的完整 API 如下:

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

前者 mapStateToProps 負責輸入邏輯,即將state映射到 UI 組件的參數(props)
後者 mapDispatchToProps 負責輸出邏輯,即將用戶對 UI 組件的操作映射成 Action。

mapStateToProps()

第一個參數總是state對象,還可以使用第二個參數,代表容器組件的props對象。
mapStateToProps 執行後應該返回一個對象,裏面的每一個鍵值對就是一個映射。

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

後面的 getVisibleTodos 函數是可以根據 state 計算出 todos 的值。

mapDispatchToProps()

建立 UI 組件的參數到store.dispatch方法的映射。也就是說,它定義了哪些用戶的操作應該當作 Action,傳給 Store。它可以是一個函數,也可以是一個對象。
如果mapDispatchToProps是一個函數,會得到dispatch和ownProps(容器組件的props對象)兩個參數。

const mapDispatchToProps = (
  dispatch,
  ownProps
) => {
  return {
    onClick: () => {
      dispatch({
        type: 'SET_VISIBILITY_FILTER',
        filter: ownProps.filter
      });
    }
  };
}

返回一個對象,該對象的每個鍵值對都是一個映射,定義了 UI 組件的參數怎樣發出 Action。它的每個鍵名也是對應 UI 組件的同名參數,鍵值應該是一個函數,會被當作 Action creator ,返回的 Action 會由 Redux 自動發出。

<Provider> 組件

connect方法生成容器組件以後,需要讓容器組件拿到state對象,才能生成 UI 組件的參數。

一種解決方法是將state對象作爲參數,傳入容器組件。但是,這樣做比較麻煩,尤其是容器組件可能在很深的層級,一級級將state傳下去就很麻煩。

React-Redux 提供Provider組件,可以讓容器組件拿到state。

import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

這樣一來,App的所有子組件就默認都可以拿到state了。
它的原理是React組件的context屬性:

class Provider extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return this.props.children;
  }
}

Provider.childContextTypes = {
  store: React.PropTypes.object
}

舉例:

class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object
}

計時器完整代碼例子

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'

// React component
class Counter extends Component {
  render() {
    const { value, onIncreaseClick } = this.props
    return (
      <div>
        <span>{value}</span>
        <button onClick={onIncreaseClick}>Increase</button>
      </div>
    )
  }
}

Counter.propTypes = {
  value: PropTypes.number.isRequired,
  onIncreaseClick: PropTypes.func.isRequired
}

// Action
const increaseAction = { type: 'increase' }

// Reducer
function counter(state = { count: 0 }, action) {
  const count = state.count
  switch (action.type) {
    case 'increase':
      return { count: count + 1 }
    default:
      return state
  }
}

// Store
const store = createStore(counter)

// Map Redux state to component props
function mapStateToProps(state) {
  return {
    value: state.count
  }
}

// Map Redux actions to component props
function mapDispatchToProps(dispatch) {
  return {
    onIncreaseClick: () => dispatch(increaseAction)
  }
}

// Connected Component
const App = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

React-Router 路由庫

使用React-Router的項目,與其他項目沒有不同之處,也是使用Provider在Router外面包一層,畢竟Provider的唯一功能就是傳入store對象。

const Root = ({ store }) => (
  <Provider store={store}>
    <Router>
      <Route path="/" component={App} />
    </Router>
  </Provider>
);
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章