React-Redux 中文文檔

介紹

快速開始

React-ReduxRedux的官方React綁定庫。它可以使你的React組件從Redux store中讀取數據,而且向store分發actions以更新數據javascript

安裝

在你的React app中使用React-Redux:html

npm install --save react-redux

或者java

yarn add react-redux

Providerconnect

React-Redux 提供<Provider/>組件,可以使你的整個app訪問到Redux store中的數據:react

import React from "react";
import ReactDOM from "react-dom";

import { Provider } from "react-redux";
import store from "./store";

import App from "./App";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
);

React-Redux提供一個connect方法可以讓你把組件和store鏈接起來。git

一般你能夠如下面這種方式調用connect方法:github

import { connect } from "react-redux";
import { increment, decrement, reset } from "./actionCreators";

// const Counter = ...

const mapStateToProps = (state /*, ownProps*/) => {
  return {
    counter: state.counter
  };
};

const mapDispatchToProps = { increment, decrement, reset };

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

基礎教程

爲了進一步瞭解如何實際使用React-Redux,咱們將一步一步建立一個todo listappweb

一個Todo List實例

跳轉到:npm

React UI 組件redux

咱們所用到的React UI組件以下:api

  • TodoApp:咱們應用的入口組件,它renderAddTodo,TodoListVisibilityFilters組件
  • AddTodo:容許用戶在點擊Add Todo按鈕後,向todo list中加入一個新的待辦項:

    • 使用一個受控input監聽onChange事件以設置state
    • 當用戶單擊Add Todo按鈕後,該組件dispatch一個action,向store中添加一個新的待辦項。(這個action是咱們由React-Redux提供的)
  • TodoList:渲染出待辦項列表的組件:

    • 當一個VisibilityFilter被選擇後,可以渲染出所匹配的待辦項列表
  • Todo:僅負責渲染單個todo待辦項:

    • 渲染出待辦項的內容,經過橫貫線表示該項已被完成
    • 觸發onClick事件後,dispatch一個能切換完成狀態的action
  • VisibilityFilters:渲染一個filters集合:_all_,_complete_ 以及 _incomplete_。單擊每一項可以篩選匹配的todos:

    • 從父組件接收一個activeFilter屬性以表示當前用戶選擇的過濾條件。選中的filter會顯示出下劃線。
    • 可以dispatch名爲setFilteraction以更新已選過濾條件
  • constants:保存咱們的app全部須要的常量數據
  • 最後,index將app渲染到DOM中

the Redux Store

應用的Redux部分遵循Redux官方文檔建議模式進行搭建:

  • Store:

    • todos:標準化的todos的reducer。包含了byIds的待辦項map對象結構,和一個包含了全部待辦項id的allIds數組
    • visibilityFilters:簡單的字符串all, completed, or incomplete.
  • Action Creators:

    • addTodo:建立增添待辦項的action。接收一個string變量content,返回ADD_TODO類型的action,以及一個payload對象(包含了自增的idcontent屬性)
    • toggleTodo:建立一個切換待辦項的action。只接收一個number類型的變量id,返回TOGGLE_TODO類型action以及僅含id屬性的payload對象。
    • setFilter:建立設置app當前過濾條件的action。接收一個string類型變量filter返回一個SET_FILTER類型action一集一個包含filter自身的payload對象。
  • Reducers

    • todos reducer:

      - 在接收到`ADD_TODO` action時,將`id`追加到`allIds`數組,而且更新`byIds`
      - 在接收到`TOGGLE_TODO` action時,切換`completed`狀態
    • VisibilityFilters reducer:在接收到SET_FILTERaction 時負責更新VISIBILITY_FILTERS狀態
  • Action Types

    • 使用一個actionTypes.js文件來保存全部的action types常量,以便複用
  • Selectores

    • getTodoList:從todos store中返回allIds列表
    • getTodoById:經過id查詢store中的todo項
    • getTodos:有點複雜。它接收allIds數組中的全部id,找到每個對應的byIds中的todo,返回最終的todos數組
    • getTodosByVisibilityFilter:根據篩選條件過濾todos

你能夠查看上面這些UI組件的和還沒有鏈接的Redux Store源碼

下面咱們將展現如何使用React-Reduxstore鏈接到咱們的app中

建立Store

首先咱們須要讓store成爲咱們app中可訪問的對象。爲此,咱們將用React-Redux提供給咱們的<Provider/>組件包裹咱們的根組件

// 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";

const rootElement = document.getElementById("root");
ReactDOM.render(
  <Provider store={store}>
    <TodoApp />
  </Provider>,
  rootElement
);

要注意到store是以一個prop<Provider/>傳遞到被包裹的<TodoApp/>中的

鏈接組件

React-Redux提供一個connect方法使你能夠從Redux store中讀取數據(以及當store更新後,從新讀取數據)

connect方法接收兩個參數,都是可選參數:

  • mapStateToProps:每當store state發生變化時,就被調用。接收整個store state,而且返回一個該組件所須要的數據對象
  • mapDispatchToProps:這個參數能夠是一個函數對象

    • 若是是一個函數,一旦該組件被建立,就會被調用。接收dispatch做爲一個參數,而且返回一個可以使用dispatch來分發actions的若干函數組成的對象
    • 若是是一個action creators構成的對象,每個action creator將會轉化爲一個prop function並會在調用時自動分發actions。注意: 咱們建議使用這種形式。

一般,你能夠這樣去connect

const mapStateToProps = (state, ownProps) => ({
  // ... 從state中處理的一些數據,以及可選的ownProps
});

const mapDispatchToProps = {
  // ... 一般是action creators構成的對象
};

// `connect`返回一個新的函數,能夠接收一個待包裝的組件
const connectToStore = connect(
  mapStateToProps,
  mapDispatchToProps
);
// 上面的函數可以返回一個已經包裝、鏈接過的組件
const ConnectedComponent = connectToStore(Component);

// 咱們一般寫成一條語句以下:
connect(
  mapStateToProps,
  mapDispatchToProps
)(Component);

下面讓咱們開始編寫<AddTodo/>。它要可以觸發store的變化從而增長新的todos。所以,他要可以向store dispatch actions。下面就是具體流程。

咱們的addTodo action建立函數以下所示:

// 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接收到它。而且一旦咱們調用,它就可以自動的分發actions

// 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/>如今擁有了一個propaddTodo action

咱們也須要實現handleAddTodo方法以便分發addTodo action 而且重置input

// components/AddTodo.js

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

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

  handleAddTodo = () => {
    // 分發action以增長todo項
    this.props.addTodo(this.state.input);

    // 將state的input置爲空字符串
    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/>已經鏈接到了store。當咱們增長一個新的todo時,該組件就可以分發action從而改變store。咱們如今還不能看到這個效果,由於別的組件還沒有鏈接到store。若是你安裝了Redux DevTools谷歌瀏覽器擴展程序,那麼你能夠看到action已經被分發了:

你也可以看到store相應地發生了變化。

<TodoList/>組件負責渲染todos列表。所以,他須要從store中讀取數據。咱們經過調用connect方法,並向其中傳入mapStateToProps參數從而提供給組件所須要的部分來自store數據。

咱們的<Todo/>組件接收一個todo項做爲prop。咱們從todosbtIds對象獲取到所需信息。然而,咱們也須要store中的allIds字段的信息,以便指明哪些todos以哪一種順序渲染。咱們的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);

幸虧咱們有一個selector專門作這個事情,咱們只須要簡單地導入selector而且使用它。

// 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);

咱們建議將全部複雜的查找和計算數據的方法封裝到selector中。此外,你從此能夠經過使用Reselect編寫「memoized」 selectors來跳過沒必要要的工做從而優化性能。(查閱Redux中文文檔 | 計算衍生數據以及博客:Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance以得到更多的關於爲什麼以及如何使用selector方法的信息)

既然咱們的<TodoList/>也已經鏈接到了store。它應該可以接收到todos列表了,遍歷他們,而後把每個傳遞給<Todo/>組件。<Todo/>組件會將它們渲染到瀏覽器中,如今嘗試增長一個todo待辦項。它應該能當即出如今咱們的todo列表中!

https://i.imgur.com/N68xvrG.png

咱們接下來會connect更多的組件。在開始以前,讓咱們先暫停一下,首先學習更多關於connect的知識。

常見的調用connect的方式

有不一樣的方式來調用connect,這取決於你如今編寫的組件類型。現對經常使用的方式進行總結:

  不訂閱Store 訂閱Store
不注入Action Creators connect()(Component) connect(mapStateToProps)(Component)
注入Action Creators connect(null, mapDispatchToProps)(Component) connect(mapStateToProps, mapDispatchToProps)(Component)

不訂閱store而且不注入action建立函數

若是你調用connect方法而且不傳入任何參數,那麼你的組件將會:

  • store改變時不可以從新渲染
  • 接收一個props.dispatch方法以便你手動分發actions
// ... Component
export default connect()(Component); // 組件將接收 `dispatch` (正如 <TodoList />!)

訂閱store但不注入action建立函數

若是你調用connect方法而且只傳入了mapStateToProps方法,你的組件將會:

  • 訂閱mapStateToProps從store中提取的部分值,當這些值改變時會從新渲染
  • 接收一個props.dispatch以便你手動分發actions
// ... Component
const mapStateToProps = state => state.partOfState;
export default connect(mapStateToProps)(Component);

不訂閱store但注入action建立函數

若是你調用connect方法並只傳入mapDispatchToProps參數,你的組件將會:

  • store改變時不從新渲染
  • props形式接收每一個你經過mapDispatchToProps注入的action建立函數,可以在你調用後自動分發actions
import { addTodo } from "./actionCreators";
// ... Component
export default connect(
  null,
  { addTodo }
)(Component);

訂閱store而且注入action建立函數

若是你在connect方法中傳入了mapStateToPropsmapDispatchToProps,你的組件將會:

  • 訂閱mapStateToProps從store中提取的部分值,當這些值改變時會從新渲染
  • props形式接收每一個你經過mapDispatchToProps注入的action建立函數,可以在你調用後自動分發actions
import * as actionCreators from "./actionCreators";
// ... Component
const mapStateToProps = state => state.partOfState;
export default connect(
  mapStateToProps,
  actionCreators
)(Component);

上面這四個例子基本覆蓋了全部connect的用法。若是想了解更多關於connnect的信息,能夠繼續閱讀[API 部分]()內容


如今咱們把<TodoApp/>餘下的部分鏈接到store

咱們該如何實現切換todos的操做呢?一個聰明的讀者也許已經有答案了。若是你截止目前的全部步驟都是緊跟指導完成的,如今是一個拋開指南、本身實現該功能的絕佳時機。不出所料,咱們以相似的方法鏈接<Todo/>以分發toggleTodo

// components/Todo.js

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

const Todo = // ... 實現組件

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

如今咱們的todo可以切換爲complete狀態了。立刻就行了!

https://i.imgur.com/4UBXYtj.png

終於,讓咱們開始實現VisibilityFilters功能。

<VisiblityFilterse/>組件須要從store中讀取當前選中的過濾條件,而且分發actions。所以,咱們須要把mapStateToProps以及mapDispatchToProps都傳遞給connect方法。mapStateToProps可以做爲visiblityFilter狀態的一個簡單的訪問器。mapDispatchToProps會包括setFilteraction建立函數。

// components/VisibilityFilters.js

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

const VisibilityFilters = // ... 組件實現

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

同時,咱們也要更新咱們的<TodoList/>組件來根據篩選條件過濾todos。先前咱們傳遞給<TodoList/> connect 方法的mapStateToProps正如一個簡單的選擇了全部列表中的todos的selector。如今咱們來寫一個selector以經過todos的狀態來進行篩選。

// 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;
  }
};

而後藉助selector鏈接到store

// components/TodoList.js

// ...

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

export default connect(mapStateToProps)(TodoList);

如今咱們已經用React-Redux完成了一個很簡單的todo app案例。咱們全部的組件都已經鏈接到了store。是否是很棒呢?🎉🎊

https://i.imgur.com/ONqer2R.png

連接

獲取更多幫助

使用 React-Redux

Connect:使用mapStateToProps抽取數據

做爲傳遞給connect的第一個參數,mapStateToProps用來從store中選擇被鏈接的組件所須要的部分數據。常以mapState縮寫來表示。

  • 每當store state改變時就被調用
  • 接收整個store state,而且返回一個組件須要的數據對象

聲明mapStateToProps

mapStateToProps應該聲明爲一個方法:

function mapStateToProps(state, ownProps?)

他接收的第一個參數是state,可選的第二個參數時ownProps,而後返回一個被鏈接組件所須要的數據的純對象。

這個方法應做爲第一個參數傳遞給connect,而且會在每次Redux store state改變時被調用。若是你不但願訂閱store,那麼請傳遞null或者undefined替代mapStateToProps做爲connect的第一個參數。

不管mapStateToProps是使用function關鍵字聲明的(function mapState(state) { } ) 仍是以一個箭頭函數(const mapState = (state) => { } ) 的形式定義的——它都可以以一樣的方式生效。

參數

  1. state
  2. ownProps(可選)

state

mapStateToProps的第一個參數是整個Redux store state對象(與store.getState()方法返回的值相同)。所以第一個參數一般命名爲state(固然你也能夠選擇別的名字,可是叫store就不推薦了——由於它是state值而不是store實例)

mapStateToProps方法至少要傳遞state參數。

// TodoList.js 

function mapStateToProps(state) {
  const { todos } = state;
  return { todoList: todos.allIds };
};
    
export default connect(mapStateToProps)(TodoList);

ownProps(可選)

若是你的組件須要用自身的props數據以從store中檢索出數據,你能夠傳入第二個參數,ownProps。這個參數將包含全部傳遞給由connect生成的包裝組件的props

// Todo.js

function mapStateToProps(state, ownProps) {
  const { visibilityFilter } = state;
  const { id } = ownProps;
  const todo = getTodoById(state, id);

  // 組件額外接收:
  return { todo, visibilityFilter };
};

// 以後,在你的應用裏,渲染一個以下父組件:
<ConnectedTodo id={123} />
// 你的組件接收 props.id, props.todo, 以及 props.visibilityFilter

你不須要把ownProps中的值再添加入mapStateToProps返回的對象中,connect會自動的把這些不一樣源的prop合併爲一個最終的prop集。

返回值

你的mapStateToProps方法應該返回一個包含了組件用到的數據的純對象:

  • 每個對象中的字段都將做爲你組件的一個prop
  • 字段中的值用來判斷你的組件是否須要從新渲染

例如:

function mapStateToProps(state) {
  return {
    a : 42,
    todos : state.todos,
    filter : state.visibilityFilter
  }
}

// 組件會接收: props.a, props.todos,以及 props.filter
注意:在一些高級場景中,你可能須要更多地對於渲染性能的控制, mapStateToProps也能夠返回一個方法。在這種狀況下,那個所返回的方法會作爲一個特定組件實例的最終的 mapStateToProps。這樣一來,你就能夠對每一個實例進行 memoization。參考[高級用法]()部分以獲取更多信息。也能夠看 PR #279以及上面增長的測試。但 大部分應用根本不須要這樣作

使用指南

mapStateToProps改造從store中取出的數據

mapStateToProps方法可以,且應該,作更多的事情,而不只僅是返回一個state.someSlice他們有責任去改造組建所須要的store中的數據。好比說,返回一個特定prop名稱的值,從state樹中不一樣部分取出數據片斷併合併爲一個總體,以及以不一樣的方式轉化store。

使用Selector方法去抽取和轉化數據

咱們強烈建議使用selector方法去封裝抽取state樹中的特定位置的值。Memoized selector方法也在提升應用性能上起到了關鍵做用。(參考本頁如下部分:[高級用法:性能]()以獲取更多關於爲什麼以及如何使用selectors的細節)

mapStateToProps方法應該足夠快

一旦store改變了,全部被鏈接組件中的全部mapStateToProps方法都會運行。所以,你的mapStateToProps方法必定要足夠快。這也意味着緩慢的mapStateToProps方法會成爲你應用的一個潛在瓶頸。

做爲「重塑數據」想法的一部分,mapStateToProps方法常常須要以各類方式來轉化數據(好比過濾數組,遍歷IDs數組映射到對應的對象,或者從Immutable.js對象中抽取純js值)。這些轉化的開銷一般很高昂,不只表如今運行轉化操做的開銷上,也表如今判斷最終是否要更新組件上。若是的確須要考慮性能問題了,那麼要確保你的這些轉化只發生在所輸入的值發生變化的時候。

mapStateToProps方法應該純淨且同步

正如Redux Reducer,一個mapStateToProps方法應該是100%純淨的而且是同步的。他應該僅接受state(以及ownProps)做爲參數,而後以props形式返回組件所須要的數據。他應該觸發任何異步操做,如AJAX請求數據,方法也不能聲明爲async形式。

mapStateToProps和性能

返回值決定你的組件是否須要更新

React-Redux 內部實現了shouldComponentUpdate方法以便在組件用到的數據發生變化後可以精確地從新渲染。默認地,React-Redux使用「===」對mapStateToProps返回的對象的每個字段逐一對比,以判斷內容是否發生了改變。但凡是有一個字段改變了,你的組件就會被從新渲染以便接收更新過的props值。注意到,返回一個相同引用的突變對象(mutated object)是一個常見錯誤,由於這會致使你的組件不能如期從新渲染。

總結一下傳入mapStateToProps參數來抽取store中的數據的connect方法包裝過的組件行爲:

<span/> state=>stateProps (state,ownProps)=>stateProps
mapStateToProps運行條件: store state 發生改變 store state發生改變

任何ownProps字段改變
組件從新渲染條件 任何stateProps字段改變 任何stateProps字段改變

任何ownProps字段改變

僅在須要時返回新的對象引用

React-Redux進行淺比較來檢查mapStateToProps的結果是否改變了。返回一個新對象或數組引用十分容易操做,但會形成你的組件在數據沒變的狀況下也從新渲染。

不少常見的操做都會返回新對象或數組引用:

  • 建立新的數組:使用someArray.map()或者someArray.filter()
  • 合併數組:array.concat
  • 截取數組:array.slice
  • 複製值:Object.assgin
  • 使用擴展運算符:{...oldState,...newData}

把這些操做放在memeoized selector functions中確保它們只在輸入值變化後運行。這樣也可以確保若是輸入值沒有改變,mapStateToProps仍然返回與以前的相同值,而後connect就可以跳太重渲染過程。

僅在數據改變時運行開銷大的操做

轉化數據常常開銷很大(而且一般會建立一個新的對象引用)。爲了使你的mapStateToProps方法足夠快,你應該相關數據改變時從新運行這些複雜的轉化。

有下面幾種形式來達到這樣的目的:

  • 一些轉化能夠在action建立函數或者reducer中運算,而後能夠把轉化過的數據儲存在store中
  • 轉換也能夠在組件的render()生命週期中完成
  • 若是轉化必需要在mapStateToProps方法中完成,那麼咱們建議使用memoized selector functions以確保轉化僅發生在輸入的數據變化時。

考慮Immutable.js性能

Immutable.js 的做者Lee Byron在Twitter中明確建議了若是開始考慮性能了要避免使用toJS

Perf tip for #immutablejs: avoid .toJS() .toObject() and .toArray() all slow full-copy operations which render structural sharing useless.

還有一些別的關於Immutable.js的性能提高建議——參見下方的連接列表。

行爲及總結

mapStateToProps在store state相同的狀況下不會運行

connect生成的包裝組件會訂閱Redux store。每當action被分發後,它就調用store.getState()並檢查是否lastState===currentState。若是兩個狀態值引用徹底相同,那麼mapStateToProps就不會運行,由於組件假設了你餘下的store state也沒有發生改變。

Redux的combineReducers功能函數會嘗試對其優化。若是全部reducer都沒有返回新值,那麼combineReducers會返回舊state對象而不是新的。這意味着,一個reducer中的突變不會使根state對象更新,固然UI也不會從新渲染。

聲明參數的數量影響行爲

當僅有(state)時,每當根store state對象不一樣了,函數就會運行。

當有(state,ownProps)兩個參數時,每當store state不一樣、每當包裝props變化時,函數都會運行。

這意味着你不該該增長ownProps參數,除非你實在是須要它,不然你的mapStateToProps函數會比它實際須要運行次數運行更屢次。

關於這個行爲有一些極端案例。arguments的數量決定了mapStateToProps是否接收ownProps參數

若是先前定義函數的時候包含了一個命令參數,mapStateToProps就不會接收ownProps

function mapStateToProps(state) {
  console.log(state);        // state
  console.log(arguments[1]); // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
  console.log(state);    // state
  console.log(ownProps); // undefined
}

若是以前定義的函數包含了0個或2個命令參數,他就須要接收ownProps參數:

function mapStateToProps(state, ownProps) {
  console.log(state);    // state
  console.log(ownProps); // ownProps
}

function mapStateToProps() {
  console.log(arguments[0]); // state
  console.log(arguments[1]); // ownProps
}

function mapStateToProps(...args) {
  console.log(args[0]); // state
  console.log(args[1]); // ownProps
}

連接和參考

教程

性能

Q&A

Connect: 使用mapDispatchToProps分發actions

做爲第二個傳入connect的參數,mapDispatchToProps能夠實現向store中分發acions。

dispatch是Redux store實例的一個方法,你會經過store.dispatch來分發一個action。這也是惟一觸發一個state變化的途徑

使用React-Redux後,你的組件就再也不須要直接和store打交道了——connect會爲你完成這件任務,React-Redux提供了兩種能夠分發actions的方式:

  • 默認地,一個已鏈接組件能夠接收props.dispatch而後本身分發actions
  • connect可以接收一個mapDispatchToProps做爲第二個參數,這將讓你可以建立dispatch調用方法,而後把這些方法做爲props傳遞給你的組件。

分發(Dispatching)的途徑

默認:做爲一個prop的dispatch

若是你不爲connect()指明第二個參數,你的組件會默認接收dispatch。好比:

connect()(MyComponent);
// 與下面語句等價
connect(
  null,
  null
)(MyComponent);

// 或者
connect(mapStateToProps /** 沒有第二個參數 */)(MyComponent);

一旦你以這種方式鏈接了你的組件,你的組件就會接收props.dispatch。你能夠用它來向store中分發actions。

function Counter({ count, dispatch }) {
  return (
    <div>
      <button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
      <span>{count}</span>
      <button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
      <button onClick={() => dispatch({ type: "RESET" })}>reset</button>
    </div>
  );
}

提供一個mapDispatchToProps參數

提供一個mapDispatchToProps參數可以讓你指明你的組件所實際須要分發的那些actions。它容許你提供action分發函數做爲props,這樣一來,你不用再進行props.dispatch(() => increment())調用,你能夠直接props.increment()。你這麼作是出於下面幾個緣由:

更加聲明式的

首先,把分發邏輯封裝到函數中使得整個實現更加聲明式。分發一個action而後讓Redux store處理數據流,表現出了你如何實現這一行爲而不只僅是隻關心它作了什麼。

單擊按鈕後分發一個action也許是個不錯的例子。把一個button直接鏈接從概念上來說有點說不通,button也沒有dispatch引用。

// button須要有意識地 "dispatch"
<button onClick={() => dispatch({ type: "SOMETHING" })} />

// button看起來不像在 "dispatch",
<button onClick={doSomething} />

一旦你把全部的action creators使用函數封裝起來以後,你的組件就不須要再dispatch了。所以,若是你定義了mapDispatchToProps被鏈接組件就再也不接收到dispatch

把action分發邏輯向子(未鏈接)組件傳遞

此外,你如今也可以向下傳遞你的action分發函數給子組件(可能還沒有鏈接)。這樣就可以使更多的組件分發actions,且它們對Redux也是無感知的。

// 把toggleTodo 傳遞給子組件
// 讓Todo 能分發 toggleTodo action
const TodoList = ({ todos, toggleTodo }) => (
  <div>
    {todos.map(todo => (
      <Todo todo={todo} onClick={toggleTodo} />
    ))}
  </div>
);

這就是React-Redux的connect所作的工做——封裝與Redux Store對話的邏輯,而且你再也不須要操心。你也應該在你的實現中充分利用這一點。

兩種mapDispatchToProps的形式

mapDispatchToProps參數有兩種形式:函數形式自定義化程度更高,對象形式更簡單。

  • 函數形式:更高自由度、可以訪問dispatch和可選擇的ownProps
  • 對象形式:更聲明式,更易於使用
注意:咱們建議使用對象形式的 mapDispatchToProps,除非你須要以某種自定義形式進行分發操做

mapDispatchToProps定義爲一個函數

mapDispatchToProps定義爲一個函數使你更靈活地定義你的組件可以接收到的函數、以及這些函數如何分發actions。你對dispatchownProps都具備訪問權限。你能夠藉此機會編寫你的鏈接組件的自定義函數。

參數

  1. dispatch
  2. ownProps(可選)

dispatch

mapDispatchToProps函數調用時以dispatch做爲第一個參數。一般狀況下,你會利用這個參數來返回一個內部調用了dispatch()的新函數,而後內部傳遞一個純的action對象或者action建立函數的返回值。

const mapDispatchToProps = dispatch => {
  return {
    // 分發純action對象
    increment: () => dispatch({ type: "INCREMENT" }),
    decrement: () => dispatch({ type: "DECREMENT" }),
    reset: () => dispatch({ type: "RESET" })
  };
};

你也可能須要把一些參數轉發給你的action建立函數

const mapDispatchToProps = dispatch => {
  return {
    // 直接轉發參數
    onClick: event => dispatch(trackClick(event)),

    // 間接轉發參數
    onReceiveImpressions: (...impressions) =>
      dispatch(trackImpressions(impressions))
  };
};

ownProps(可選)

你的mapDispatchToProps函數是能夠接收兩個參數的,第一個是dispatch,傳遞給鏈接組件的props即爲mapDispatchToProps的第二個參數,而後在組件接收到新的props後會從新調用。

這意味着,你應該在組件props改變階段從新把新的props綁定到action分發函數中去,而不是在組件從新渲染階段進行。

// 在組件re-rendering階段綁定
<button onClick={() => this.props.toggleTodo(this.props.todoId)} />;

// 在 props 改變階段綁定
const mapDispatchToProps = (dispatch, ownProps) => {
  toggleTodo: () => dispatch(toggleTodo(ownProps.todoId));
};

返回值

你的mapDispatchToProps函數應該的返回一個純對象。

  • 每個對象的字段都會做爲你的組件的一個獨立prop,而且字段的值一般是一個調用後能分發action的函數。
  • 若是你在dispatch()中使用了action建立函數(區別於純對象形式的action),一般約定字段名與action建立函數的名稱相同
const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
const reset = () => ({ type: "RESET" });

const mapDispatchToProps = dispatch => {
  return {
    // 分發由action creators建立的actions
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement()),
    reset: () => dispatch(reset())
  };
};

mapDispatchToProps的函數返回值會合併到你的組件props中去。你就可以直接調用它們來分發action。

function Counter({ count, increment, decrement, reset }) {
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{count}</span>
      <button onClick={increment}>+</button>
      <button onClick={reset}>reset</button>
    </div>
  );
}

(Counter案例的完整代碼參見:CodeSandbox)

使用bindActionCreators定義mapDispatchToProps函數

手動封裝這些函數實在是繁瑣,因此Redux提供了一個函數簡化這個操做。

bindActionCreators將值爲 action creators的對象,轉化爲同鍵名的新對象,但將每一個action creators封裝到一個dispatch調用中,以即可以直接調用它們。參閱 Redux | bindActionCreators

bindActionCreators接收兩個參數:

  1. 一個函數(action creator)或一個對象(每一個屬性爲一個action creator)
  2. dispatch

bindActionCreators生成的包裝函數會自動轉發它們全部的參數,因此你不須要在手動操做了。

import { bindActionCreators } from "redux";

const increment = () => ({ type: "INCREMENT" });
const decrement = () => ({ type: "DECREMENT" });
const reset = () => ({ type: "RESET" });

// 綁定一個action creator
// 返回 (...args) => dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch);

// 綁定一個action creators構成的object
const boundActionCreators = bindActionCreators({ increment, decrement, reset }, dispatch);
// 返回值:
// {
//   increment: (...args) => dispatch(increment(...args)),
//   decrement: (...args) => dispatch(decrement(...args)),
//   reset: (...args) => dispatch(reset(...args)),
// }

mapDispatchToProps中使用bindActionCreators函數:

import { bindActionCreators } from "redux";
// ...

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ increment, decrement, reset }, dispatch);
}

// 組件能接收到 props.increment, props.decrement, props.reset
connect(
  null,
  mapDispatchToProps
)(Counter);

手動注入dispatch

若是提供了mapDispatchToProps,組件將再也不接收到默認的dispatch。但你能夠經過在mapDispatchToPropsreturn中添加dispatch把它從新注入你的組件。多數狀況下,你不須要這麼作。

import { bindActionCreators } from "redux";
// ...

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    ...bindActionCreators({ increment, decrement, reset }, dispatch)
  };
}

mapDispatchToProps定義爲一個對象

你已經注意到了,在React組件中分發Redux actions的過程都十分相似:定義action建立函數,把它包裝在形如(…args) => dispatch(actionCreator(…args))的另外一個函數,而後把那個包裝函數做爲props 傳遞給你的組件。

由於這一流程實在是太通用了,connect支持了一個「對象簡寫」形式的mapDispatchToProps參數:若是你傳遞了一個由action creators構成的對象,而不是函數,connect會在內部自動爲你調用bindActionCreators

咱們建議適中使用這種「對象簡寫」形式的mapDispatchToProps,除非你有特殊理由須要自定義dispatching行爲

注意到:

  • 每一個mapDispatchToProps對象的字段都被假設爲一個action建立函數
  • 你的組件再也不接收dispatch做爲一個prop
// React-Redux 自動爲你作:
dispatch => bindActionCreators(mapDispatchToProps, dispatch);

所以,咱們的mapDispatchToProps能夠簡寫爲:

const mapDispatchToProps = {
  increment,
  decrement,
  reset
};

既然變量名取決於你,你可能想把它命名爲actionCreators或者甚至直接在調用connect時使用一個行內對象:

import {increment, decrement, reset} from "./counterActions";

const actionCreators = {
  increment,
  decrement,
  reset
}

export default connect(mapState, actionCreators)(Counter);

// 或者
export default connect(
  mapState,
  { increment, decrement, reset }
)(Counter);

常見問題

爲何組件再也不接收dispatch?

也就是說會報錯:

TypeError: this.props.dispatch is not a function

在你試圖調用this.props.dispatch時這個錯誤就很常見了,由於實際上dispatch並無注入到你的組件。

dispatch僅在下面這些狀況下注入組件:

  1. 你沒有提供mapDispatchToProps

默認的mapDispatchToProps只是簡單的dispatch => ({ dispatch })。若是你不提供mapDispatchToPropsdispatch會以上面的形式自動提供給你。

換言之,也就是你這麼作了:

//組件接收 `dispatch`
connect(mapStateToProps /** 沒有第二參數*/)(Component);
  1. 你自定義的mapDispatchToProps明確地返回了dispatch

你也許想把dispatch帶回你的組件,經過形以下面的定義方法:

const mapDispatchToProps = dispatch => {
  return {
    increment: () => dispatch(increment()),
    decrement: () => dispatch(decrement()),
    reset: () => dispatch(reset()),
    dispatch
  };
};

或者,使用bindActionCreators

import { bindActionCreators } from "redux";

function mapDispatchToProps(dispatch) {
  return {
    dispatch,
    ...bindActionCreators({ increment, decrement, reset }, dispatch)
  };
}

本錯誤可參考:Redux’s GitHub issue #255.

有關是否須要對組件提供dispatch的討論(Dan Abramov對#255的回覆)。您能夠閱讀它們以進一步瞭解目前這麼作的目的。

我能不能不使用mapStateToProps而僅使用mapDispatchToProps?

固然。你能夠經過給第一個參數傳入nullundefined來跳過它。你的組件就不會訂閱store但仍然可以接收到mapDispatchToProps定義的dispatch props

connect(
  null,
  mapDispatchToProps
)(MyComponent);

我能夠調用store.dispatch嗎?

不管是直接import的仍是從context拿到的store,這都不是一種與store交互的良好模式(參見Redux FAQ entry on store setup以獲取更多詳情)。讓React-Redux的connect來獲取對store的訪問權,而且使用dispatch分發actions。

連接和參考

教程

相關文檔

Q&A

API 參考

API

Provider

使Redux store可用於connect()調用下面組件層次結構中。一般,若是沒有<Provider>包裝父級或祖先組件,則沒法使用connect()

若是你 實在 是須要,你能夠手動地將store做爲一個prop傳遞給被鏈接組件,可是咱們僅建議在單元測試中對store僞造(stub),或者在非徹底React代碼庫中這麼作。一般,你就使用<Provider>吧。

props

  • storeRedux Store):你應用裏的惟一Redux store
  • children(ReactElement):根組件

實例

Vanilla React

ReactDOM.render(
  <Provider store={store}>
    <MyRootComponent />
  </Provider>,
  rootEl
)

React Router

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <Route path="foo" component={Foo}/>
        <Route path="bar" component={Bar}/>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
)

connect

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

將一個React組件鏈接到Redux Store。connectadvancedConnect的對外提供的一個較爲經常使用的API,可以用於大多數場景。

它不會改變傳遞給它的組件類,而是返回了一個新的、被鏈接的組件供你使用。

參數

  • [mapStateToProps(state, [ownProps]): stateProps] (Function):

若是指定過了這個參數,那麼新組件就會向Redux store訂閱更新。這意味着任什麼時候候store一旦更新,mapStateToProps就會被調用。mapStateToProps的結果必須是一個純對象,以後該對象會合併到組件的props

若是你的mapStateToProps函數被聲明爲接收兩個參數,那麼第一個參數就是store state,第二個參數就是傳遞給鏈接組件的propsmapStateToProps函數也會在鏈接組件的props經過淺比較發現改變時從新運行。(第二個參數約定命名爲ownProps)。

注意:在一些高級場景中,你可能須要更多地對於渲染性能的控制, mapStateToProps也能夠返回一個方法。在這種狀況下,那個所返回的方法會作爲一個特定組件實例的最終的 mapStateToProps。這樣一來,你就能夠對每一個實例進行 memoization。參考[高級用法]()部分以獲取更多信息。也能夠看 PR #279以及上面增長的測試。但 大部分應用根本不須要這樣作

mapStateToPros函數的第一個參數是整個Redux Store state,而後該函數返回一個能夠傳遞給鏈接組件的props對象,這個常被稱爲 selector 。使用reselect來高效地構建selectors以及計算衍生數據

  • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):

若是傳遞的是一個對象,其內部的每個函數都被假定爲一個Redux action 建立函數。這個對象會做爲props傳遞給鏈接組件,且其內部每一個字段名都與action creators相同,但被一個能調用dispatch方法的函數包裝,這樣一來這些分發函數就能夠直接調用。

若是傳遞的是一個函數,其第一個參數爲dispatch。你想如何使用dispatch去綁定action建立函數均可以。(貼士:你可使用Redux提供的bindActionCreators()方法)

若是你的mapDispatchToProps方法被聲明爲接收兩個變量,那麼其第一個參數是dispatch,第二個參數就是傳遞給鏈接組件的propsmapDispatchToProps函數也會在鏈接組件的props經過淺比較發現改變時從新運行。(第二個參數約定命名爲ownProps)。

若是你不提供mapDispatchToProps函數(或action cretors對象)默認的這一行爲實現僅是經過向你的組件props注入dispatch

注意:在一些高級場景中,你可能須要更多地對於渲染性能的控制, mapDispatchToProps也能夠返回一個方法。在這種狀況下,那個所返回的方法會作爲一個特定組件實例的最終的 mapDispatchToProps。這樣一來,你就能夠對每一個實例進行 memoization。能夠看 PR #279以及上面增長的測試。但 大部分應用根本不須要這樣作
  • [mergeProps(stateProps,dispatchProps,ownProps):props]:(Function):

若是指明瞭這個參數,它內部要接收mapStateToProps()mapDispatchToProps()以及父props三個參數。這個函數參數所返回的純對象將會做爲props傳遞給被包裝組件。你可使用這個函數來根據props選擇一部分state,或者把action建立函數綁定在一個特定的props變量上。若是你省略這個參數,則會默認使用Object.assign({}, ownProps, stateProps, dispatchProps)方法。

  • [options](Object):這個參數能夠進一步定製connector的行爲。除了能夠傳遞給connectAdvanced()的選項(見下),connect()還接受如下額外選項:

    • [pure] (Boolean):若爲true,在相關的state/props分別進行了淺比較後發現沒有改變的狀況下,connect()會避免從新渲染以及對mapStateToPropsmapDispatchToPropsmergeProps的調用。假設咱們的被包裝組件是一個不依賴任何inputstate的「純」組件,僅依賴於props和被選中的Redux store state除外。默認值:true
    • [areStatesEqual](Function):當puretrue,比較下一個store state和其先前值。默認值:嚴格相等(===)
    • [areOwnPropsEqual](Function):當puretrue,比較下一個`props
`和其先前值。默認值:`淺比較`
- [`areStatePropsEqual`](Function):當`pure`爲`true`,比較`mapStateToProps
`的結果和其先前值。默認值:`淺比較`
- [`areMergedPropsEqual`](Function):當`pure`爲`true`,比較`mergeProps
`的結果和其先前值。默認值:`淺比較`
- [`storeKey`] (String):讀取`store`的地方的`context`的`key`。可能你只有在不建議的具備多個`store`的位置纔會須要這個。默認值:`store`

mapStateToPropsmapDispatchToProps的參數數量決定了他們是否接收ownProps

注意:在位於前面的 mapStateToPropsmapDispatchToProps函數定義時只有一個強制參數(函數長度爲1)的狀況下, ownProps不會傳遞給這兩個函數。好比,把函數定義爲如下形式時不會接受 ownProps做爲其第二個參數。
function mapStateToProps(state) {
  console.log(state); // state
  console.log(arguments[1]); // undefined
}
const mapStateToProps = (state, ownProps = {}) => {
  console.log(state); // state
  console.log(ownProps); // {}
}

函數若是沒有強制參數或者有兩個參數則會接收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
}

option.puretrue時優化connect

option.puretrue時,connect會進行一系列的等性判斷來避免沒必要要的對mapStateToPropsmapDispatchToPropsmergeProps的調用,以及最終的render。這些判斷包括areStatesEqual, areOwnPropsEqual, areStatePropsEqual, and areMergedPropsEqual。儘管大多數狀況下默認判斷均可以適用,可是你也許處於性能或其餘緣由須要重寫這些判斷。下面是幾個例子:

  • 在你的mapStateToProps函數計算開銷大、而且老是關心一小部分的state時,你也許但願重寫areStatesEqual。舉個例子:areStatesEqual: (next, prev) => prev.entities.todos === next.entities.todos。這樣一來,即可以有效地忽略無關的state變化,而只關心你那一小部分state
  • 若是你有使你store state突變的不純淨reducers,那麼你可能想重寫areStatesEqual以使它老是返回falseareStatesEqual: () => false)。(這樣可能會影響你的其餘等性判斷,取決於你的mapStateToProps函數)
  • 你可能想經過重寫areOwnPropsEqual把未來的props列入白名單。你還要實現mapStateToPropsmapDispatchToPropsmergePropsprops列入白名單。(實現方式還能夠更簡單,好比使用重構的mapProps
  • 若是你的mapStateToProps使用了一種只在一個相關prop變化時才返回一個新對象的memoized selector,你可能想重寫areStatePropsEqual使其使用strictEqual。這可能僅是一個很小的性能提高,但卻能夠省去每一次mapStateToProps調用時對每個props進行等性判斷。
  • 若是你的selectors製造了一個複雜的props,你可能想去重寫areMergedPropsEqual來實現deepEqual深比較。好比:嵌套對象,新數組等。(深比較會比直接re-rendering要更快)

返回值

一個高階React組件類,它可以把stateaction creators傳遞給由所提供的參數生成的組件中去。這一高階組件由connectAdvanced生成,這裏會把它的詳情羅列出來。

實例

僅注入dispatch而不監聽store

export default connect()(TodoApp)

注入全部action建立函數(addTodocompleteTodo,...)而不訂閱store

import * as actionCreators from './actionCreators'

export default connect(null, actionCreators)(TodoApp)

注入dispatch以及全局state的全部字段

不要這麼作!這將會扼殺全部的性能優化,由於 TodoApp會在每一次state變化後從新渲染。最好把更多細粒度的 connect()用在你的每一個展現層裏的組件中去監聽相關的一小部分state
export default connect(state => state)(TodoApp)

注入dispatchtodos

function mapStateToProps(state) {
  return { todos: state.todos }
}

export default connect(mapStateToProps)(TodoApp)

注入todos和全部的action建立函數

import * as actionCreators from './actionCreators'

function mapStateToProps(state) {
  return { todos: state.todos }
}

export default connect(mapStateToProps, actionCreators)(TodoApp)

注入todos和全部的action建立函數(addTodocompleteTodo,...)的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建立函數(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 建立函數(addTodo and 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(Object.assign({}, todoActionCreators, counterActionCreators), dispatch)
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)

注入 todos 並把全部的 todoActionCreators 和 counterActionCreators 做爲 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(
    Object.assign({}, todoActionCreators, counterActionCreators),
    dispatch
  )
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoApp)

根據組件的 props 注入特定用戶的 todos

import * as actionCreators from './actionCreators'

function mapStateToProps(state, ownProps) {
  return { todos: state.todos[ownProps.userId] }
}

export default connect(mapStateToProps)(TodoApp)

根據組件的 props 注入特定用戶的 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)

工廠函數

工廠函數能夠用來進行性能優化

import {addTodo} from './actionCreators'

function mapStateToPropsFactory(initialState,initialProps){
    const getSomeProperty=createSelector(...);
    const anotherProperty=200+initialState[initialProps.another];
    return function(state){
        return {
            anotherProperty,
            someProperty:getSomeProperty(state),
            todos:state.todos
        }
    }
}

function mapDispatchToPropsFactory(initialState,initialProps){
    function goToSomeLink(){
        initialProps.history.push('some/link');
    }
    return function(dispatch){
        return {
            addTodo
        }
    }
}

export default connect(mapStateToPropsFactory,mapDispatchToPropsFactory)(TodoApp)

connectAdvanced

connectAdvanced(selectorFactory,[connectOptions])

它是一個將 React 組件鏈接到 Redux store 的函數。這個函數是 connect() 的基礎,可是對於如何把state, props, 和 dispatch 組合到最後的 props 中,則不那麼自覺得是。它不對默認值或結果的記錄作任何假設,而是將這些責任留給調用者。

它不修改傳遞給它的組件類;相反,它返回一個新的、已鏈接的組件類,供您使用。

參數

  • selectorFactory(dispatch, factoryOptions): selector(state, ownProps): props (Function):初始化選擇器函數 (在每一個實例的構造函數中)。該選擇器函數是在 connector 組件須要從新計算一個新的 props 時調用,做爲 store 的 state 改變或者接收到一個新的 props 的結果。selector 的結果應該是一個普通對象,做爲被包裹的組件的 props 傳遞。若是連續調用 selector 都返回與上一次調用相同的對象(===),則不會從新渲染該組件。selector 的責任是在適當的時候返回之前的對象。
  • [connectOptions] (Object) 若是指定,則進一步自定義鏈接器(connector)的行爲。

    • [getDisplayName] (Function):計算鏈接器組件相對於被包裹的組件的 DisplayName 屬性。 一般被包裹函數覆蓋。 默認值: name => 'ConnectAdvanced('+name+')'
[methodName] (String):顯示在錯誤消息中。 一般被包裹函數覆蓋。 默認值: 'connectAdvanced'

- [`renderCountProp`] (String): 若是被定義, 名爲此值的屬性將添加到傳遞給被包裹組件的 props 中。它的值將是組件被渲染的次數,這對於跟蹤沒必要要的從新渲染很是有用。默認值: `undefined`

- [`shouldHandleStateChanges`] (Boolean): 控制鏈接器(`connector`)組件是否訂閱 redux store 的 state 更改。 若是設置爲 false,則只會在`componentWillReceiveProps`中從新渲染。 默認值: true

- [`storeKey`] (String): 能夠獲取 store 的 `props/context` key。 當你不明智地使用了多個 `store` 的時候,你纔可能須要這個。默認值: `'store'`

- [`withRef`] (Boolean): 若是爲 true,則將一個引用存儲到被包裹的組件實例中,並經過 `getWrappedInstance()` 方法使其可用。 默認值:` false`

- 此外,經過 `connectOptions` 傳遞的任何額外選項都將傳遞給 `factoryOptions` 參數中的 `selectorFactory`。

返回值

一個高階 React 組件類,它從 store 的 state 生成 props 並將它們傳遞給被包裹的組件。高階組件是接受組件參數並返回新組件的函數。

靜態屬性

  • WrappedComponent (Component): 原始組件類傳遞給 connectAdvanced(...)(Component)

靜態函數

組件的全部原始靜態方法都被掛起。

實例方法

getWrappedInstance(): ReactComponent

返回被包裹組件的實例。只有當你傳遞 { withRef: true } 做爲options 的參數纔可用。

注意

  • 由於 connectAdvanced 返回一個高階組件,因此須要調用它兩次。 第一次使用上面描述的參數,第二次使用組件: connectAdvanced(selectorFactory)(MyComponent)
  • connectAdvanced 不修改傳遞的 React 組件。它返回一個新的鏈接組件,您應該使用它。

實例

根據 props 將特定用戶的 todos 注入,並將 pros.userid 注入到操做中

import * as actionCreators from './actionCreators'
import { bindActionCreators } from 'redux'

function selectorFactory(dispatch) {
  let ownProps = {}
  let result = {}
  const actions = bindActionCreators(actionCreators, dispatch)
  const addTodo = text => actions.addTodo(ownProps.userId, text)
  return (nextState, nextOwnProps) => {
    const todos = nextState.todos[nextOwnProps.userId]
    const nextResult = { ...nextOwnProps, todos, addTodo }
    ownProps = nextOwnProps
    if (!shallowEqual(result, nextResult)) result = nextResult
    return result
  }
}
export default connectAdvanced(selectorFactory)(TodoApp)

createProvider([storeKey])

建立一個新的<Provider>,它將在上下文的傳遞 key 上設置 Redux Store。 當你不明智地使用了多個 store 的時候,你纔可能須要這個。您還須要將相同的 storeKey 傳遞給connectoptions 參數。

參數

  • [storeKey] (String): 要在其上設置 store 的context的 key。 默認值: 'store'

實例

在建立多個stores以前,請先查看這個FAQ:Can or should I create multiple stores?

import { connect, createProvider } from 'react-redux'

const STORE_KEY = 'componentStore'

export const Provider = createProvider(STORE_KEY)

function connectExtended(
  mapStateToProps,
  mapDispatchToProps,
  mergeProps,
  options = {}
) {
  options.storeKey = STORE_KEY
  return connect(
    mapStateToProps,
    mapDispatchToProps,
    mergeProps,
    options
  )
}

export { connectExtended as connect }

如今你能夠導入上述的Providerconnect來使用他們了。

Provider

<Provider/>

概覽

<Provider/>使得每個被connect()函數包裝過的嵌套組件均可以訪問到Redux store

既然任何Redux-React app中的React組件均可以被鏈接,那麼大多數應用都會在最頂層渲染<Provider>,從而包裹住整個組件樹。

一般,你若是不把已鏈接組件嵌套在<Provider>中那麼你就不能使用它。

注意: 若是實在是須要,你能夠手動把store做爲一個prop傳遞給一個鏈接組件。可是咱們僅建議在單元測試中對 store僞造(stub),或者在非徹底React代碼庫中才這麼作。一般,你就使用<Provider>吧。

Props

  • store (Redux Store): 應用程序中惟一的 Redux store 對象
  • children (ReactElement) 組件層級的根組件。

實例應用

Vanilla React

ReactDOM.render(
  <Provider store={store}>
    <MyRootComponent />
  </Provider>,
  rootEl
)

React Router

ReactDOM.render(
  <Provider store={store}>
    <Router history={history}>
      <Route path="/" component={App}>
        <Route path="foo" component={Foo}/>
        <Route path="bar" component={Bar}/>
      </Route>
    </Router>
  </Provider>,
  document.getElementById('root')
)

指南

排錯

確保在開始以前學習了Redux排錯

我收到如下警告:Accessing PropTypes via the main React package is deprecated. Use the prop-types package from npm instead.

這個警告會在你使用react 15.5.*時出現。基本上,現上它只是一個 warning, 可是在 React16 當中可能會致使你的應用崩潰。如今 PropTypes 應該從 'prop-types' 包中 import,而不是從 react 包中 import。

更新到最新版本的 react-redux。

個人視圖不更新啊!

閱讀上面的連接。 簡而言之,

  • Reducer 永遠不該該更改原有 state,應該始終返回新的對象,不然,React Redux 覺察不到數據變化。
  • 確保你使用了 connect()mapDispatchToProps 參數或者 bindActionCreators 來綁定 action creator 函數,你也能夠手動調用 dispatch() 進行綁定。直接調用 MyActionCreators.addTodo() 並不會起任何做用,由於它只會返回一個 action 對象,並不會 dispatch 它。

React Router 0.13 的 route 變化中,view 不更新

若是你正在使用 React Router 0.13,你可能會碰到這樣的問題。解決方法很簡單:當使用 <RouteHandler> 或者 Router.run 提供的 Handler 時,不要忘記傳遞 router state

根 view:

Router.run(routes, Router.HistoryLocation, (Handler, routerState) => {
// 注意這裏的 "routerState" 
  ReactDOM.render(
    <Provider store={store}>
      {/* note "routerState" here */}
      <Handler routerState={routerState} />
    </Provider>,
    document.getElementById('root')
  )
})

嵌套 view

render() {
  // 保持這樣傳遞下去
  return <RouteHandler routerState={this.props.routerState} />
}

很方便地,這樣你的組件就能訪問 router 的 state 了! 固然,你能夠將 React Router 升級到 1.0,這樣就不會有此問題了。(若是還有問題,聯繫咱們!)

Redux 外部的一些東西更新時,view 不更新

若是 view 依賴全局的 state 或是 React 「context」,你可能發現那些使用 connect() 進行修飾的 view 沒法更新。

這是由於,默認狀況下 connect() 實現了 shouldComponentUpdate,它假定在 props 和 state 同樣的狀況下,組件會渲染出一樣的結果。這與 React 中 PureRenderMixin 的概念很相似。

這個問題的最好的解決方案是保持組件的純淨,而且全部外部的 state 都應經過 props 傳遞給它們。這將確保組件只在須要從新渲染時纔會從新渲染,這將大大地提升了應用的速度。

當不可抗力致使上述解法沒法實現時(好比,你使用了嚴重依賴 React context 的外部庫),你能夠設置 connect() 的 pure: false 選項:

function mapStateToProps(state) {
  return { todos: state.todos }
}

export default connect(mapStateToProps, null, null, {
  pure: false
})(TodoApp)

這樣就表示你的 TodoApp 不是純淨的,只要父組件渲染,自身都會從新渲染。注意,這會下降應用的性能,因此只有在別無他法的狀況下才使用它。

在 context 或 props 中都找不到 「store」

若是你有 context 的問題,

  1. 確保你沒有引入多個 React 實例到頁面上。
  2. 確保你沒有忘記將根組件或者其餘祖先組件包裝進 <Provider>
  3. 確保你運行的 React 和 React Redux 是最新版本。

Invariant Violation:addComponentAsRefTo(...):只有 ReactOwner 纔有 refs。這一般意味着你在一個沒有 owner 的組件中添加了 ref

若是你在 web 中使用 React,就一般意味着你重複引用了 React。按照這個連接解決便可。

相關文章
相關標籤/搜索