教程:使用 connect API
tip
如今我们使用 React-Redux hooks API 作为我们的默认推荐。但是,connect API 仍然可以正常工作。
本教程还展示了一些我们不再推荐的旧做法,例如按类型将 Redux 逻辑分成文件夹的做法。为了完整起见,我们将本教程保持原样,但建议通过 Redux 文档中的 Redux Essentials 教程和 Redux 样式指南,了解我们当前的最佳实践。
我们正在编写一个介绍 hooks API 的新教程。在此之前,我们建议阅读 Redux Fundamentals, Part 5: UI and React 以获得 hooks 教程。
我们将通过创建一个 todo list 应用来一步步展示如何在实践中使用 React Redux。
一个 Todo List 示例
跳到
- 🤞 给我看一下代码
- 👆 提供 store
- ✌️ 连接 Component
React UI 组件
我们已经实现的 React UI 组件像下面这样:
TodoApp是我们应用的入口。它渲染 header,AddTodo,TodoList和VisibilityFilters组件。AddTodo是一个允许用户输入代办事项并通过点击 Add Todo 按钮添加到列表中的组件:- 它通过 input 的
onChange事件去设置 state。 - 当用户点击 Add Todo 按钮时,它通过 dispatches action(我们将使用 React Redux 的提供)把 todo 加到 store 中
- 它通过 input 的
TodoList是一个渲染 todos 列表的组件:- 当其中一个
VisibilityFilters被选中时,它会渲染被过滤的 todos 列表。
- 当其中一个
Todo是一个渲染单个 todo 的组件:- 它渲染 todo 的内容,并显示一个 todo 是通过划掉它来显示已完成的。
- 它通过
onClick去 dispatches action 切换 todo 的完成状态。
VisibilityFilters渲染一组简单的过滤器: all,completed 和 incomplete。单击每一个过滤 todos:- 它接受来自父级的
activeFilter属性,指示用户当前选择了哪个过滤器。一个被激活的过滤器会在渲染时包含下划线。 - 它 dispatches
setFilteraction 去更新被选中的过滤器。
- 它接受来自父级的
constants保存我们应用的常量数据。- 最后
index将我们的应用程序渲染到 DOM。
Redux Store
应用程序的 Redux 部分已使用 Redux 文档中推荐的模式进行设置:
- Store
todos:一个归一化的 todos 的 reducer。它包含一个对于所有 todo 的byIdsmap 和一个包含所有 id 列表的allIds。visibilityFilters:一个简单的字符串all,completed或者incomplete。
- Action Creators
addTodo创建 action 添加到 todos 中。它采用单个字符串变量content并返回一个ADD_TODOaction 并且payload包含自增的id和contenttoggleTodo创建 action 去切换 todos。它采用单个数字变量id并返回一个TOGGLE_TODOaction 并且payload只包含idsetFilter创建 action 去设置 app 的激活过滤器。它采用单个字符串变量filter并返回一个SET_FILTERaction 并且payload包含filter自身。
- Reducers
- The
todosreducer- 添加
id到自身的allIds字段中并在收到ADD_TODOaction 后在其byIds字段中设置 todo - 在收到
TOGGLE_TODOaction 后切换 todo 的completed字段
- 添加
visibilityFiltersreducer 设置 slice store 为从SET_FILTERaction payload 中的新 filter
- The
- Action Types
- 我们使用一个
actionTypes.js文件去保存那些重复使用的 action types 常量
- 我们使用一个
- Selectors
getTodoList从todosstore 中返回allIds列表getTodoById通过id查到 todogetTodos稍微复杂一些。它从allIds中获取所有的id,在byIds中找到每个 todo,并在最后返回一个 todos 的数组getTodosByVisibilityFilter根据 visibility filter 过滤 todos
你可以通过查阅此 CodeSandbox 获取 UI components 的源码和上述未连接的 Redux store
现在我们将展示如何使用 React Redux 将 store 连接到我们的应用中。
提供 Store
第一步我们需要使得 store 对于我们的应用是可见的。为了做到这个,我们使用 React Redux 提供的 API <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';
// React 18 以后
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<TodoApp />
</Provider>
);
注意我们的 <TodoApp /> 现在是如何被 <Provider /> 包裹的,其中 store 作为 prop 传入。

连接 Components
React Redux 提供一个 connect 函数使你可以读取 Redux store(并且当 store 更新时会再次去读取值)的值。
connect 函数接收两个参数,都是可选的:
mapStateToProps:在每一次 store state 改变时被调用。它接收整个 store state,并返回该组件需要的数据对象。mapDispatchToProps: 此参数可以是一个 function,或者一个 object。- 如果它是 function,会在 component 创建时立马被调用。它将接收
dispatch作为一个参数,并且应该返回一个 object,其中包含使用dispatch来 dispatch actions 的函数。 - 如果它是一个充满 action creators 的 object,每个 action creator 都会变成一个 prop 函数,在调用时会自动 dispatches 其 action。注意:我们推荐使用这种 “object shorthand” 形式。
- 如果它是 function,会在 component 创建时立马被调用。它将接收
通常,你会以这种方式调用 connect:
const mapStateToProps = (state, ownProps) => ({
// ...依据 state 和 自定义 ownProps 生成 computed data
});
const mapDispatchToProps = {
// ... 通常是一个充满 action creators 的 object
};
// `connect` 返回一个接收要包装的组件的新函数:
const connectToStore = connect(mapStateToProps, mapDispatchToProps);
// 并且该函数返回连接的,包装的组件:
const ConnectedComponent = connectToStore(Component);
// 通常我们会将两者一步完成,像这样:
connect(mapStateToProps, mapDispatchToProps)(Component);
让我们先处理 <AddTodo />。它需要触发对 store 的更改以添加新的 todos。因此,他需要能够 dispatch actions 到 store。接下来我们是如何做到的。
我们的 addTodo action creator 像这样:
// redux/actions.js
import { ADD_TODO } from './actionTypes';
let nextTodoId = 0;
export const addTodo = (content) => ({
type: ADD_TODO,
payload: {
id: ++nextTodoId,
content,
},
});
// ... 其他 actions
通过将它传递给 connect,我们的组件将其作为 prop 接收,当它被调用时将自动 dispatch action。
// components/AddTodo.js
// ... 其他导入
import { connect } from 'react-redux';
import { addTodo } from '../redux/actions';
class AddTodo extends React.Component {
// ... 组件实现
}
export default connect(null, { addTodo })(AddTodo);
注意现在 <AddTodo /> 被一个叫做 <Connect(AddTodo) /> 的父组件包裹。此时,<AddTodo /> 现在获取一个 prop:addTodo action。

我们还需要实现 handleAddTodo 函数,使得它 dispatch addTodo action 并重置输入
// components/AddTodo.js
import React from 'react';
import { connect } from 'react-redux';
import { addTodo } from '../redux/actions';
class AddTodo extends React.Component {
// ...
handleAddTodo = () => {
// dispatches actions 添加 todo
this.props.addTodo(this.state.input);
// 设置 state 回到空的字符串
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,将 dispatch action 去改变 store。我们在应用程序中看不到它因为其他组件尚未连接。如果你连接了 Redux DevTools Extension,你应该可以看到正在 dispatched action:

你还应该看到 store 已相应改变:

<TodoList /> 组件负责渲染 todos 的列表。因此,它需要从 store 中读取数据。我们通过使用 mapStateToProps 参数调用 connect 来启用它,该函数描述了我们需要从 store 中获取哪一部分数据。
我们的 <Todo /> 组件将 todo item 作为 props。我们从 todos 的 byIds 字段中获得了这些信息。但是,我们还需要来自 store 的 allIds 字段的信息,指示哪些 todos 以及它们应该以什么顺序呈现。我们的 mapStateToProps 可能如下所示:
// components/TodoList.js
// ...其他导入
import { connect } from "react-redux";
const TodoList = // ... UI 组件实现
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
// ...其他导入
import { connect } from "react-redux";
import { getTodos } from "../redux/selectors";
const TodoList = // ... UI 组件实现
export default connect(state => ({ todos: getTodos(state) }))(TodoList);
我们建议在 selector 函数中封装任何复杂的数据查找和计算。此外,你可以通过使用 Reselect 编写可以跳过不必要工作的记忆化 selectors 来进一步优化性能。(请参阅 the Redux docs page on Computing Derived Data 和博客文章 Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance 了解有关为什么以及如何使用 selector 函数的更多信息。)
现在我们的 <TodoList /> 已连接到 store。它应该接收 todos 的列表,映射它们,并将每个 todo 传递给 <Todo /> 组件。<Todo /> 将依次把它们渲染到屏幕上。现在尝试添加 todo。它应该出现在我们的 todo 清单上!

我们将连接更多的组件。在我们这样做之前,让我们先暂停一下,并了解更多关于 connect 的信息。
常见的调用 connect 方式
根据你使用的组件类型,有不同的调用 connect 方式,最常见的总结如下:
| 不订阅 Store | 订阅 Store | |
|---|---|---|
| 不注入 Action Creators | connect()(Component) | connect(mapStateToProps)(Component) |
| 注入 Action Creators | connect(null, mapDispatchToProps)(Component) | connect(mapStateToProps, mapDispatchToProps)(Component) |
不订阅 store 且不注入 action creators
如果你在不提供任何参数的情况下调用 connect,你的组件将:
- 当 store 改变时 不会 重渲染
- 接收
props.dispatch你可以用它来手动 dispatch action
// ... Component
export default connect()(Component); // 组件将接收 `dispatch`(就像我们的 <TodoList />!)
订阅 store 不注入 action creators
如果你仅使用 mapStateToProps 调用 connect,你的组件将:
- 订阅
mapStateToProps从 store 中提取的值,并仅在这些值发生更改时重新渲染 - 接收
props.dispatch你可以用它来手动 dispatch action
// ... Component
const mapStateToProps = (state) => state.partOfState;
export default connect(mapStateToProps)(Component);
不订阅 store 注入 action creators
如果你仅使用 mapDispatchToProps 调用 connect,你的组件将:
- 当 store 改变时 不会 重渲染
- 接收你使用
mapDispatchToProps作为 props 注入的每个 action creators,并在被调用时自动 dispatch actions
import { addTodo } from './actionCreators';
// ... Component
export default connect(null, { addTodo })(Component);
订阅 store 注入 action creators
如果你同时调用 mapStateToProps 和 mapDispatchToProps connect,你的组件将:
- 订阅
mapStateToProps从 store 中提取的值,并仅在这些值发生更改时重新渲染 - 接收你使用
mapDispatchToProps注入的所有 action creators 作为 props 并自动在被调用时 dispatch actions。
import * as actionCreators from './actionCreators';
// ... Component
const mapStateToProps = (state) => state.partOfState;
export default connect(mapStateToProps, actionCreators)(Component);
这四种情况涵盖了 connect 最基本的用法。要了解有关 connect 的更多信息,请继续阅读我们的 API 部分 该部分对其进行了更详细的解释。
现在让我们连接 <TodoApp /> 的其余部分。
我们应该如何实现 toggling todos 的交互呢? 敏锐的读者可能已经有了答案。如果你已经设置好你的环境并一直跟进到这一点,那么现在是放下它并自己实现该功能的好时机。我们以类似的方式连接 <Todo /> 来 dispatch toggleTodo 也就不足为奇了:
// components/Todo.js
// ... 其他导入
import { connect } from "react-redux";
import { toggleTodo } from "../redux/actions";
const Todo = // ... 组件实现
export default connect(
null,
{ toggleTodo }
)(Todo);
现在我们的 todo 可以切换完成。我们快完成了!

最后,让我们去实现 VisibilityFilters 特性。
<VisibilityFilters /> 需要能够从 store 中读取当前活动状态的过滤器,并 dispatch actions 到 store。因此,我们需要同时传递 mapStateToProps 和 mapDispatchToProps。这里的 mapStateToProps 可以是 visibilityFilter 状态的简单访问器。并且 mapDispatchToProps 将包含 setFilter action creator。
// components/VisibilityFilters.js
// ... 其他导入
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 列表的选择器。让我们编写另一个选择器来帮助按状态过滤的 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;
}
};
并在选择器的帮助下连接到 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 应用的例子。我们的所有组件都被连接上了!这是不是很棒? 🎉🎊

链接
获取更多帮助
- Reactiflux Redux channel
- StackOverflow
- GitHub Issues