介绍
快速开始
React-Redux
是Redux
的官方React
绑定库。它能够使你的React
组件从Redux store
中读取数据,并且向store
分发actions
以更新数据
安装
在你的React app中使用React-Redux:
npm install --save react-redux
或者
yarn add react-redux
Provider
和connect
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 list
app
一个Todo List实例
跳转到:
React UI 组件
我们所用到的React UI
组件如下:
-
TodoApp
:我们应用的入口组件,它render
出AddTodo
,TodoList
和VisibilityFilters
组件 -
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
名为setFilter
的action
以更新已选过滤条件
- 从父组件接收一个
-
constants
:保存我们的app所有需要的常量数据 - 最后,
index
将app渲染到DOM中
the Redux Store
应用的Redux
部分遵循Redux
官方文档建议模式进行搭建:
-
Store:
-
todos
:标准化的todos的reducer
。包含了byIds
的待办项map
对象结构,和一个包含了所有待办项id的allIds
数组 -
visibilityFilters
:简单的字符串all
,completed
, orincomplete
.
-
-
Action Creators:
-
addTodo
:创建增添待办项的action
。接收一个string
变量content
,返回ADD_TODO
类型的action,以及一个payload
对象(包含了自增的id
和content
属性) -
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_FILTER
action 时负责更新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-Redux
将store
连接到我们的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/>
现在拥有了一个prop
:addTodo
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
。我们从todos
的btIds
对象获取到所需信息。然而,我们也需要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
列表中!
我们接下来会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
方法中传入了mapStateToProps
和mapDispatchToProps
,你的组件将会:
- 订阅
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
状态了。马上就好了!
终于,让我们开始实现VisibilityFilters
功能。
<VisiblityFilterse/>
组件需要从store中读取当前选中的过滤条件,并且分发actions
。因此,我们需要把mapStateToProps
以及mapDispatchToProps
都传递给connect
方法。mapStateToProps
能够作为visiblityFilter
状态的一个简单的访问器。mapDispatchToProps
会包括setFilter
action创建函数。
// 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。是不是很棒呢?🎉🎊
链接
- Usage with React
- Using the React-Redux Bindings
- Higher Order Components in Depth
- Computing Derived Data
- Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance
获取更多帮助
使用 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) => { } )
的形式定义的——它都能够以同样的方式生效。
参数
state
-
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
}
链接和参考
教程
- Practical Redux Series, Part 6: Connected Lists, Forms, and Performance
- Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance
性能
- Lee Byron's Tweet Suggesting to avoid
toJS
,toArray
andtoObject
for Performance - Improving React and Redux performance with Reselect
- Immutable data performance links
Q&A
- Why Is My Component Re-Rendering Too Often?
- Why isn't my component re-rendering, or my mapStateToProps running
- How can I speed up my mapStateToProps?
- Should I only connect my top component, or can I connect multiple components in my tree?
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。你对dispatch
和ownProps
都具有访问权限。你可以借此机会编写你的连接组件的自定义函数。
参数
dispatch
-
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
接收两个参数:
- 一个函数(action creator)或一个对象(每个属性为一个action creator)
- 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
。但你可以通过在mapDispatchToProps
的return
中添加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
仅在下面这些情况下注入组件:
- 你没有提供
mapDispatchToProps
默认的mapDispatchToProps
只是简单的dispatch => ({ dispatch })
。如果你不提供mapDispatchToProps
,dispatch
会以上面的形式自动提供给你。
换言之,也就是你这么做了:
//组件接收 `dispatch`
connect(mapStateToProps /** 没有第二参数*/)(Component);
- 你自定义的
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
?
当然。你可以通过给第一个参数传入null
或undefined
来跳过它。你的组件就不会订阅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
- How to get simple dispatch from
this.props
using connect with Redux? this.props.dispatch
isundefined
if usingmapDispatchToProps
- Do not call
store.dispatch
, callthis.props.dispatch
injected byconnect
instead - Can I
mapDispatchToProps
withoutmapStateToProps
in Redux? - Redux Doc FAQ: React Redux
API 参考
API
Provider
使Redux store可用于connect()
调用下面组件层次结构中。通常,如果没有<Provider>
包装父级或祖先组件,则无法使用connect()
。
如果你 实在 是需要,你可以手动地将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')
)
connect
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
将一个React组件连接到Redux Store。connect
是advancedConnect
的对外提供的一个较为常用的API,能够用于大多数场景。
它不会改变传递给它的组件类,而是返回了一个新的、被连接的组件供你使用。
参数
- [
mapStateToProps(state, [ownProps]): stateProps
] (Function):
如果指定过了这个参数,那么新组件就会向Redux store订阅更新。这意味着任何时候store一旦更新,mapStateToProps
就会被调用。mapStateToProps
的结果必须是一个纯对象,之后该对象会合并到组件的props
如果你的mapStateToProps
函数被声明为接收两个参数,那么第一个参数就是store state,第二个参数就是传递给连接组件的props
。mapStateToProps
函数也会在连接组件的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
,第二个参数就是传递给连接组件的props
。mapDispatchToProps
函数也会在连接组件的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()
会避免重新渲染以及对mapStateToProps
、mapDispatchToProps
和mergeProps
的调用。假设我们的被包装组件是一个不依赖任何input
或state
的“纯”组件,仅依赖于props
和被选中的Redux store state
除外。默认值:true
。 - [
areStatesEqual
](Function):当pure
为true
,比较下一个store state
和其先前值。默认值:严格相等(===)
- [
areOwnPropsEqual
](Function):当pure
为true
,比较下一个`props
- [
`和其先前值。默认值:`浅比较`
- [`areStatePropsEqual`](Function):当`pure`为`true`,比较`mapStateToProps
`的结果和其先前值。默认值:`浅比较`
- [`areMergedPropsEqual`](Function):当`pure`为`true`,比较`mergeProps
`的结果和其先前值。默认值:`浅比较`
- [`storeKey`] (String):读取`store`的地方的`context`的`key`。可能你只有在不建议的具有多个`store`的位置才会需要这个。默认值:`store`
mapStateToProps
和mapDispatchToProps
的参数数量决定了他们是否接收ownProps
注意:在位于前面的mapStateToProps
和mapDispatchToProps
函数定义时只有一个强制参数(函数长度为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.pure
为true
时优化connect
当option.pure
为true
时,connect
会进行一系列的等性判断来避免不必要的对mapStateToProps
、mapDispatchToProps
和mergeProps
的调用,以及最终的render
。这些判断包括areStatesEqual
, areOwnPropsEqual
, areStatePropsEqual
, and areMergedPropsEqual
。尽管大多数情况下默认判断都可以适用,但是你也许处于性能或其他原因需要重写这些判断。下面是几个例子:
- 在你的
mapStateToProps
函数计算开销大、并且总是关心一小部分的state
时,你也许希望重写areStatesEqual
。举个例子:areStatesEqual: (next, prev) => prev.entities.todos === next.entities.todos
。这样一来,便可以有效地忽略无关的state
变化,而只关心你那一小部分state
。 - 如果你有使你
store state
突变的不纯净reducers
,那么你可能想重写areStatesEqual
以使它总是返回false
(areStatesEqual: () => false
)。(这样可能会影响你的其他等性判断,取决于你的mapStateToProps
函数) - 你可能想通过重写
areOwnPropsEqual
把将来的props
列入白名单。你还要实现mapStateToProps
、mapDispatchToProps
和mergeProps
把props
列入白名单。(实现方式还可以更简单,比如使用重构的mapProps
) - 如果你的
mapStateToProps
使用了一种只在一个相关prop
变化时才返回一个新对象的memoized selector
,你可能想重写areStatePropsEqual
使其使用strictEqual
。这可能仅是一个很小的性能提升,但却可以省去每一次mapStateToProps
调用时对每一个props
进行等性判断。 - 如果你的
selectors
制造了一个复杂的props
,你可能想去重写areMergedPropsEqual
来实现deepEqual
深比较。比如:嵌套对象,新数组等。(深比较会比直接re-rendering
要更快)
返回值
一个高阶React组件类,它能够把state
和action creators
传递给由所提供的参数生成的组件中去。这一高阶组件由connectAdvanced
生成,这里会把它的详情罗列出来。
实例
仅注入dispatch
而不监听store
export default connect()(TodoApp)
注入所有action创建函数(addTodo
,completeTodo
,...)而不订阅store
import * as actionCreators from './actionCreators'
export default connect(null, actionCreators)(TodoApp)
注入dispatch
以及全局state的所有字段
不要这么做!这将会扼杀所有的性能优化,因为TodoApp
会在每一次state变化后重新渲染。最好把更多细粒度的connect()
用在你的每个展示层里的组件中去监听相关的一小部分state
export default connect(state => state)(TodoApp)
注入dispatch
和todos
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创建函数(addTodo
,completeTodo
,...)的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 传递给connect
的 options
参数。
参数
- [
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 }
现在你可以导入上述的Provider
和connect
来使用他们了。
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
的问题,
- 确保你没有引入多个 React 实例到页面上。
- 确保你没有忘记将根组件或者其他祖先组件包装进
<Provider>
。 - 确保你运行的 React 和 React Redux 是最新版本。
Invariant Violation:addComponentAsRefTo(...):只有 ReactOwner 才有 refs。这通常意味着你在一个没有 owner 的组件中添加了 ref
如果你在 web 中使用 React,就通常意味着你重复引用了 React。按照这个链接解决即可。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。