在线文档教程

React用法(Usage with React)

Usage with React

从一开始,我们需要强调 Redux 与 React 没有任何关系。您可以使用 React,Angular,Ember,jQuery 或 vanilla JavaScript 编写 Redux 应用程序。

也就是说,Redux与ReactDeku等库合作得非常好,因为它们让您将UI描述为状态的函数,并且Redux响应操作发出状态更新。

我们将使用React来构建我们简单的待办事项应用程序。

安装React Redux

React 绑定不包含在默认的 Redux 中。你需要明确地安装它们:

npm install --save react-redux

如果您不使用 npm,您可以从 unpkg 获取最新的 UMD 版本(开发版或生产版)。window.ReactRedux如果您通过<script>标记将其添加到您的网页,则 UMD 版本将导出一个全局调用。

展示和容器组件

Redux 的 React 绑定包含分离表示和容器组件的想法。如果您不熟悉这些条款,请先阅读有关条款,然后再回来。这些条款很重要,所以我们会等待!

完成阅读文章?我们来重述一下他们的区别:

演示组件容器组件
目标How things look (markup, styles)How things work (data fetching, state updates)
Aware of ReduxNoYes
读取数据Read data from propsSubscribe to Redux state
改变数据Invoke callbacks from propsDispatch Redux actions
写入By handUsually generated by React Redux

我们要编写的大多数组件都是表示性的,但我们需要生成一些容器组件以将它们连接到 Redux 存储。这和下面的设计概要并不意味着容器组件必须靠近组件树的顶部。如果一个容器组件变得太复杂了(即它有大量嵌套的 presentional 组件,并有无数的回调被传递下去),请在 FAQ 中引入组件树中的另一个容器。

技术上你可以手动使用store.subscribe()容器组件。我们不建议您这样做,因为 React Redux 会进行许多难以完成的性能优化。出于这个原因,我们将使用connect()React Redux提供的函数生成它们,而不是编写容器组件,如下所示。

设计组件层次结构

请记住我们如何设计根状态对象的形状?是时候我们设计 UI 层次结构来匹配它。这不是特定于 Redux 的任务。在 React 中思考是一个很好的教程,可以解释这个过程。

我们的设计简介很简单。我们想要显示待办事项列表。单击时,待办事项完成后划掉。我们想要显示一个字段,用户可以添加新的待办事项。在页脚中,我们想要显示切换显示全部,只显示完成或仅显示活动待办事项。

设计演示组件

我看到以下演示组件和它们的道具出现在这个简短的介绍中:

  • TodoList 是一个列表,显示可见的待办事项。

他们描述的外观,但不知道数据来自哪里,或者如何改变它。他们只渲染给他们的东西。如果你从 Redux 迁移到其他的东西,你将能够保持所有这些组件完全一样。他们没有依赖 Redux 。

设计容器组件

我们还需要一些容器组件将演示组件连接到 Redux。例如,演示TodoList组件需要一个像VisibleTodoList这样的容器订阅Redux商店,并知道如何应用当前的可见性过滤器。要更改可见性过滤器,我们将提供一个FilterLink容器组件,呈现一个Link在点击时发送适当操作的容器组件:

  • VisibleTodoList根据当前可见性过滤器过滤待办事项并呈现TodoList

设计其他组件

有时很难判断某个组件应该是一个表示组件还是一个容器。例如,有时窗体和函数真的耦合在一起,比如这个微小的组件:

  • AddTodo 是一个带有“添加”按钮的输入字段

从技术上讲,我们可以将它分成两个部分,但现阶段可能为时过早。在非常小的组件中混合呈现和逻辑是很好的。随着它的增长,如何分割它将会更加明显,所以我们会把它混合起来。

实现组件

我们来编写组件!我们从演示组件开始,所以我们不需要考虑绑定到 Redux。

演示组件

这些都是普通的 React 组件,所以我们不会详细检查。我们编写功能无状态的组件,除非我们需要使用本地状态或生命周期方法。这并不意味着表示组件必须是功能 - 这样更容易定义它们。如果您需要添加本地状态,生命周期方法或性能优化,则可以将它们转换为类。

components/Todo.js

import React from 'react' import PropTypes from 'prop-types' const Todo = { onClick, completed, text }) => ( <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none' }} > {text} </li> ) Todo.propTypes = { onClick: PropTypes.func.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired } export default Todo

components/TodoList.js

import React from 'react' import PropTypes from 'prop-types' import Todo from './Todo' const TodoList = { todos, onTodoClick }) => ( <ul> {todos.map(todo => ( <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> ))} </ul> ) TodoList.propTypes = { todos: PropTypes.arrayOf( PropTypes.shape{ id: PropTypes.number.isRequired, completed: PropTypes.bool.isRequired, text: PropTypes.string.isRequired }).isRequired ).isRequired, onTodoClick: PropTypes.func.isRequired } export default TodoList

components/Link.js

import React from 'react' import PropTypes from 'prop-types' const Link = { active, children, onClick }) => { if (active) { return <span>{children}</span> } return ( <a href="#" onClick={e => { e.preventDefault() onClick() }} > {children} </a> ) } Link.propTypes = { active: PropTypes.bool.isRequired, children: PropTypes.node.isRequired, onClick: PropTypes.func.isRequired } export default Link

components/Footer.js

import React from 'react' import FilterLink from '../containers/FilterLink' const Footer = () => ( <p> Show: {' '} <FilterLink filter="SHOW_ALL"> All </FilterLink> {', '} <FilterLink filter="SHOW_ACTIVE"> Active </FilterLink> {', '} <FilterLink filter="SHOW_COMPLETED"> Completed </FilterLink> </p> ) export default Footer

components/App.js

import React from 'react' import Footer from './Footer' import AddTodo from '../containers/AddTodo' import VisibleTodoList from '../containers/VisibleTodoList' const App = () => ( <div> <AddTodo /> <VisibleTodoList /> <Footer /> </div> ) export default App

实现容器组件

现在是时候通过创建一些容器来将这些表示组件连接到 Redux。从技术上讲,容器组件只是一个React组件,用于store.subscribe()读取Redux状态树的一部分,并为它呈现的呈现组件提供道具。您可以手动编写容器组件,但我们建议使用 React Redux 库的connect()函数生成容器组件,该函数提供了许多有用的优化以防止不必要的重新渲染。(这样做的一个结果就是你不必担心自己实现React性能的建议shouldComponentUpdate。)

要使用connect(),您需要定义一个特殊的函数调用mapStateToProps,告诉如何将当前的 Redux 存储状态转换为要传递给要包装的表示组件的道具。例如,VisibleTodoList需要计算todos传递给TodoList,所以我们根据过滤的函数state.visibilityFilter定义一个state.todos,并在其中使用它mapStateToProps

const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) } } const mapStateToProps = state => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } }

除了读取状态之外,容器组件还可以调度操作。以类似的方式,您可以定义一个名为mapDispatchToProps()的函数来接收dispatch()方法并返回要注入到表示组件中的回调支持。例如,我们要VisibleTodoList注入一种叫做道具onTodoClickTodoList组件,我们希望onTodoClick分派一个TOGGLE_TODO动作:

const mapDispatchToProps = dispatch => { return { onTodoClick: id => { dispatch(toggleTodo(id)) } } }

最后,我们通过调用connect()并传递这两个函数来创建VisibleTodoList

import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList

这些是 React Redux API 的基础知识,但有几个捷径和电源选项,因此我们鼓励您详细查看其文档。如果您担心mapStateToProps经常创建新对象,则可能需要了解重新选择计算派生数据。

查找下面定义的其余容器组件:

containers/FilterLink.js

import { connect } from 'react-redux' import { setVisibilityFilter } from '../actions' import Link from '../components/Link' const mapStateToProps = (state, ownProps) => { return { active: ownProps.filter === state.visibilityFilter } } const mapDispatchToProps = (dispatch, ownProps) => { return { onClick: () => { dispatch(setVisibilityFilter(ownProps.filter)) } } } const FilterLink = connect( mapStateToProps, mapDispatchToProps )(Link) export default FilterLink

containers/VisibleTodoList.js

import { connect } from 'react-redux' import { toggleTodo } from '../actions' import TodoList from '../components/TodoList' const getVisibleTodos = (todos, filter) => { switch (filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.completed) case 'SHOW_ACTIVE': return todos.filter(t => !t.completed) } } const mapStateToProps = state => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } } const mapDispatchToProps = dispatch => { return { onTodoClick: id => { dispatch(toggleTodo(id)) } } } const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList

实施其他组件

containers/AddTodo.js

import React from 'react' import { connect } from 'react-redux' import { addTodo } from '../actions' let AddTodo = { dispatch }) => { let input return ( <div> <form onSubmit={e => { e.preventDefault() if (!input.value.trim()) { return } dispatch(addTodo(input.value)) input.value = '' }} > <input ref={node => { input = node }} /> <button type="submit"> Add Todo </button> </form> </div> ) } AddTodo = connect()(AddTodo) export default AddTodo

通过存储

所有容器组件都需要访问 Redux 存储,以便他们可以订阅。一种选择是将其作为道具传递给每个容器组件。然而,它很乏味,因为store即使通过演示组件来渲染容器深处的组件,也必须连线。

我们推荐的选择是使用所谓的特殊阵营终极版组件<Provider>神奇地使存储提供应用程序中的所有容器组件没有传递给它明确。渲染根组件时,只需要使用它一次:

index.js

import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') )

下一步

阅读本教程的完整源代码以更好地内化您获得的知识。然后,请直接前往高级教程以了解如何处理网络请求和路由!