Skip to the content.

使用 connect API

我们现在建议使用React-Redux hooks API作为默认值。然而,connect API仍然可以正常工作。

We now recommend using the React-Redux hooks API as the default. However, the connect API still works fine.

本教程还展示了一些我们不再推荐的老做法,比如按类型将Redux逻辑划分到文件夹中。为了完整,我们将本教程保持原样,但建议您阅读Redux文档中的“Redux Essentials”教程和Redux风格指南,了解当前的最佳实践。

This tutorial also shows some older practices we no longer recommend, like separating Redux logic into folders by type. We’ve kept this tutorial as-is for completeness, but recommend reading through the “Redux Essentials” tutorial and the Redux Style Guide in the Redux docs for our current best practices.

为了了解如何在实践中使用React Redux,我们将通过创建一个待办事项列表应用程序来展示一个逐步的示例。

To see how to use React Redux in practice, we’ll show a step-by-step example by creating a todo list app.

一个待办事项列表的例子

The React UI Components

我们已经实现了我们的React UI组件如下:

We have implemented our React UI components as follows:

The Redux Store

应用程序的Redux部分已经使用Redux文档中推荐的模式设置:

The Redux portion of the application has been set up using the patterns recommended in the Redux docs:

现在,我们将展示如何使用React Redux将这个store连接到我们的应用程序。

We will now show how to connect this store to our app using React Redux.

提供Store

首先,我们需要让appstore可用。为了做到这一点,我们用React Redux提供的<Provider /> API包装我们的应用。

First we need to make the store available to our app. To do this, we wrap our app with the API provided by React Redux.

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

注意我们的<TodoApp />现在是如何被<Provider />包装起来的,并将store作为一个道具传入。

Notice how our is now wrapped with the with store passed in as a prop.

todo list

连接组件

React Redux提供了一个连接函数,用于从Redux存储读取值(并在存储更新时重新读取值)。

React Redux provides a connect function for you to read values from the Redux store (and re-read the values when the store updates).

connect函数有两个参数,都是可选的:

通常,你会这样调用connect:

Normally, you’ll call connect in this way:

const mapStateToProps = (state, ownProps) => ({
  // ... computed data from state and optionally ownProps
})

const mapDispatchToProps = {
  // ... normally is an object full of action creators
}

// `connect` returns a new function that accepts the component to wrap:
const connectToStore = connect(mapStateToProps, mapDispatchToProps)
// and that function returns the connected, wrapper component:
const ConnectedComponent = connectToStore(Component)

// We normally do both in one step, like this:
connect(mapStateToProps, mapDispatchToProps)(Component)

让我们先处理<AddTodo />。它需要触发对商店的更改,以添加新的待办事项。因此,它需要能够将操作分派到商店。我们是这样做的。

Let’s work on first. It needs to trigger changes to the store to add new todos. Therefore, it needs to be able to dispatch actions to the store. Here’s how we do it.

我们的addTodo动作创建者看起来像这样:

Our addTodo action creator looks like this:

// 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,我们的组件将它作为一个道具接收,当它被调用时,它将自动分派动作。

By passing it to connect, our component receives it as a prop, and it will automatically dispatch the action when it’s called.

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

Notice now that is wrapped with a parent component called <Connect(AddTodo) />. Meanwhile, now gains one prop: the addTodo action.

todo list

我们还需要实现handleAddTodo函数,让它分派addTodo动作并重置输入

We also need to implement the handleAddTodo function to let it dispatch the addTodo action and reset the input

// 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 to add todo
    this.props.addTodo(this.state.input)

    // sets state back to empty string
    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时,它会分派一个动作来更改store。我们在应用中没有看到它,因为其他组件还没有连接。如果你已经连接了Redux DevTools扩展,你应该会看到正在调度的动作:

Now our is connected to the store. When we add a todo it would dispatch an action to change the store. We are not seeing it in the app because the other components are not connected yet. If you have the Redux DevTools Extension hooked up, you should see the action being dispatched:

todo list

你还应该看到store也发生了相应的变化:

You should also see that the store has changed accordingly:

todo list

<TodoList />组件负责呈现待办事项列表。因此,它需要从store中读取数据。我们通过调用带有mapStateToProps参数的connect来启用它,该参数是一个描述我们需要从store中获取哪一部分数据的函数。

The component is responsible for rendering the list of todos. Therefore, it needs to read data from the store. We enable it by calling connect with the mapStateToProps parameter, a function describing which part of the data we need from the store.

我们的<Todo />组件将Todo条目作为道具。我们从todos的byIds字段中获得了这些信息。然而,我们还需要来自storeallIds字段的信息,指示应该呈现哪些todo以及它们的顺序。我们的mapStateToProps函数可能是这样的:

Our component takes the todo item as props. We have this information from the byIds field of the todos. However, we also need the information from the allIds field of the store indicating which todos and in what order they should be rendered. Our mapStateToProps function may look like this:

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

幸运的是,我们有一个选择器可以做到这一点。我们可以简单地导入选择器并在这里使用它。

Luckily we have a selector that does exactly this. We may simply import the selector and use it here.

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

我们建议将所有复杂的查询或数据计算封装在选择器函数中。此外,您可以通过使用“Reselect”编写可以跳过不必要工作的“memoized”选择器来进一步优化性能。 (有关为什么以及如何使用选择器功能的更多信息,请参阅计算派生数据上的Redux文档页面和博客文章Idiomatic Redux:使用Reselect选择器进行封装​​和性能)。

We recommend encapsulating any complex lookups or computations of data in selector functions. In addition, you can further optimize the performance by using Reselect to write “memoized” selectors that can skip unnecessary work. (See the Redux docs page on Computing Derived Data and the blog post Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance for more information on why and how to use selector functions.)

现在,我们的<TodoList />已经连接到商店。它应该接收todo列表,映射它们,并将每个todo传递给<Todo />组件。<Todo />将依次把它们渲染到屏幕上。现在尝试添加一个待办事项。它应该出现在我们的待办事项清单上!

Now that our is connected to the store. It should receive the list of todos, map over them, and pass each todo to the component. will in turn render them to the screen. Now try adding a todo. It should come up on our todo list!

todo list

我们将连接更多的组件。在我们这样做之前,让我们先暂停一下,了解更多关于connect的知识。

We will connect more components. Before we do this, let’s pause and learn a bit more about connect first.

调用connect的常用方法

根据您正在使用的组件类型,有不同的调用connect的方法,其中最常见的总结如下:

Depending on what kind of components you are working with, there are different ways of calling connect , with the most common ones summarized as below:

  Do Not Subscribe to the Store Subscribe to the Store
Do Not Inject Action Creators connect()(Component) ` connect(mapStateToProps)(Component)`
Inject Action Creators connect(null, mapDispatchToProps)(Component) connect(mapStateToProps, mapDispatchToProps)(Component)

不订阅store也不注入action creators

Do not subscribe to the store and do not inject action creators

如果你在不提供任何参数的情况下调用connect,你的组件将:

// ... Component
export default connect()(Component) // Component will receive `dispatch` (just like our <TodoList />!)

订阅store,不注入action creators

Subscribe to the store and do not inject action creators

如果你只使用mapStateToProps来调用connect,你的组件会:

// ... Component
const mapStateToProps = (state) => state.partOfState
export default connect(mapStateToProps)(Component)

不订阅store并注入动作创建者

Do not subscribe to the store and inject action creators

如果你只使用mapDispatchToProps来调用connect,你的组件会:

import { addTodo } from './actionCreators'
// ... Component
export default connect(null, { addTodo })(Component)

订阅store并注入动作创建者

Subscribe to the store and inject action creators

如果你同时使用mapStateToPropsmapDispatchToProps调用connect,你的组件将:

import * as actionCreators from './actionCreators'
// ... Component
const mapStateToProps = (state) => state.partOfState
export default connect(mapStateToProps, actionCreators)(Component)

这四种情况涵盖了connect最基本的用法。要阅读更多关于connect的内容,请继续阅读我们的API部分,该部分将更详细地解释它。

These four cases cover the most basic usages of connect. To read more about connect, continue reading our API section that explains it in more detail.


现在让我们连接<TodoApp />的其余部分。

Now let’s connect the rest of our .

我们应该如何实现todos切换的交互?热心的读者可能已经有了答案。如果您已经设置好了环境,并一直执行到现在,那么现在是时候把它放在一边,自己实现这个特性了。用类似的方式连接<Todo />来分派toggleTodo也就不足为奇了:

How should we implement the interaction of toggling todos? A keen reader might already have an answer. If you have your environment set up and have followed through up until this point, now is a good time to leave it aside and implement the feature by yourself. There would be no surprise that we connect our to dispatch toggleTodo in a similar way:

// components/Todo.js

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

const Todo = // ... component implementation

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

现在我们的待办事项可以切换完成。我们快到了!

Now our todo’s can be toggled complete. We’re almost there!

todo list

最后,让我们来实现我们的VisibilityFilters特性。

Finally, let’s implement our VisibilityFilters feature.

<VisibilityFilters />组件需要能够从过滤器当前处于活动状态的store中读取,并将操作分派给存储。因此,我们需要传递一个mapStateToProps和一个mapDispatchToProps。这里的mapStateToProps可以是visabilityfilter状态的一个简单访问器。mapDispatchToProps将包含setFilter动作创建器。

The component needs to be able to read from the store which filter is currently active, and dispatch actions to the store. Therefore, we need to pass both a mapStateToProps and mapDispatchToProps. The mapStateToProps here can be a simple accessor of the visibilityFilter state. And the mapDispatchToProps will contain the setFilter action creator.

// components/VisibilityFilters.js

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

const VisibilityFilters = // ... component implementation

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

同时,我们还需要更新我们的<TodoList />组件,根据active filter过滤todos。以前我们传递给<TodoList /> connect函数调用的mapStateToProps只是选择器,它选择了整个todos列表。让我们编写另一个选择器来帮助根据状态筛选待办事项。

Meanwhile, we also need to update our component to filter todos according to the active filter. Previously the mapStateToProps we passed to the connect function call was simply the selector that selects the whole list of todos. Let’s write another selector to help filtering todos by their status.

// 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:

And connecting to the store with the help of the selector:

// components/TodoList.js

// ...

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

export default connect(mapStateToProps)(TodoList)

现在,我们已经用React Redux完成了一个非常简单的todo应用示例。我们所有的组件都是连接在一起的!那不是很好吗?🎉🎊

Now we’ve finished a very simple example of a todo app with React Redux. All our components are connected! Isn’t that nice? 🎉🎊

todo list