111

介绍

快速开始

React-ReduxRedux的官方React绑定库。它能够使你的React组件从Redux store中读取数据,并且向store分发actions以更新数据

安装

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

npm install --save react-redux

或者

yarn add react-redux

Providerconnect

React-Redux 提供<Provider/>组件,能够使你的整个app访问到Redux store中的数据:

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连接起来。

通常你可以以下面这种方式调用connect方法:

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 listapp

一个Todo List实例

跳转到:

React UI 组件

我们所用到的React UI组件如下:

  • 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。按照这个链接解决即可。


Marckon
1k 声望169 粉丝

正在成长的小前端...