Redux和React 一起使用
From the very beginning, we need to stress that Redux has no relation to React. You can write Redux apps with React, Angular, Ember, jQuery, or vanilla JavaScript.
复制代码
从一开始,我们需要强调Redux与React没有任何关系。您可以使用React,Angular,Ember,jQuery或vanilla JavaScript编写Redux应用程序。
That said, Redux works especially well with libraries like React and Deku because they let you describe UI as a function of state, and Redux emits state updates in response to actions.
复制代码
也就是说,Redux与React和Deku等库合作得非常好,因为它们让您将UI描述为状态的函数,并且Redux响应操作发出状态更新
We will use React to build our simple todo app.
复制代码
我们将使用React来构建我们自己的简单todo应用程序。
Installing React Redux
安装React Redux
React bindings are not included in Redux by default. You need to install them explicitly:
复制代码
React的绑定不包含默认的Redux。你需要明确地安装它们:
npm install --save react-redux
If you don't use npm, you may grab the latest UMD build from unpkg (either a development or a production build). The UMD build exports a global called window.ReactRedux if you add it to your page via a <script> tag.
复制代码
如果您不使用npm,您可以从unpkg获取最新的UMD版本(开发版或生产版)。如果您通过<script>
标签将其添加到您的网页,则UMD版本将导出一个全局调用window.ReactRedux
。
Presentational and Container Components
展示和容器组件
React bindings for Redux embrace the idea of separating presentational and container components. If you're not familiar with these terms, read about them first, and then come back. They are important, so we'll wait!
复制代码
Redux的React绑定包含分离表示和容器组件的想法。如果您不熟悉这些条款,请先阅读有关条款,然后再回来。他们很重要,所以我们会等待!
Finished reading the article? Let's recount their differences:
复制代码
完成阅读文章?我们来重述一下他们的区别:
演示组件 | Container Components | |
---|---|---|
Purpose | How things look (markup, styles) | How things work (data fetching, state updates) |
Aware of Redux | No | Yes |
To read data | Read data from props | Subscribe to Redux state |
To change data | Invoke callbacks from props | Dispatch Redux actions |
Are written | By hand | Usually generated by React Redux |
演示组件 | 容器组件 | |
---|---|---|
目的 | 看起来如何(标记,样式) | 如何工作 (数据读取,状态更新) |
意识到Redux | 否 | 是 |
读取数据 | 从props中读取数据 | 订阅Redux状态 |
改变数据 | 从props中调用回调 | 派发Redux行动 |
写入 | 手动 | 通常由React Redux生成 |
Most of the components we'll write will be presentational, but we'll need to generate a few container components to connect them to the Redux store. This and the design brief below do not imply container components must be near the top of the component tree. If a container component becomes too complex (i.e. it has heavily nested presentational components with countless callbacks being passed down), introduce another container within the component tree as noted in the FAQ.
复制代码
我们要编写的大多数组件都是表示性的,但我们需要生成一些容器组件以将它们连接到Redux存储。这和下面的设计概要并不意味着容器组件必须靠近组件树的顶部。如果一个容器组件变得太复杂了(比如它有很多嵌套的表示组件,并且有无数的回调被传递下去),那么在组件树中引入另一个容器,如常见问题中所述。
Technically you could write the container components by hand using store.subscribe(). We don't advise you to do this because React Redux makes many performance optimizations that are hard to do by hand. For this reason, rather than write container components, we will generate them using the connect() function provided by React Redux, as you will see below.
复制代码
技术上你可以使用store.subscribe()手动编写这个容器组件。但是我们不建议您这样做,因为React Redux会进行许多难以完成的性能优化。出于这个原因,我们将使用React Redux提供的connect()函数生成它们,而不是编写容器组件,如下所示。
Designing Component Hierarchy
设计组件层次结构
Remember how we designed the shape of the root state object? It's time we design the UI hierarchy to match it. This is not a Redux-specific task. Thinking in React is a great tutorial that explains the process.
复制代码
请记住我们如何设计根状态对象的形状?是时候我们设计UI层次结构来匹配它。这不是特定于Redux的任务。React中思考是一个很好的教程,可以解释这个过程。
Our design brief is simple. We want to show a list of todo items. On click, a todo item is crossed out as completed. We want to show a field where the user may add a new todo. In the footer, we want to show a toggle to show all, only completed, or only active todos.
复制代码
我们的设计简介很简单。我们想要显示todo事项列表。点击后,todo事项完成后划掉。我们想要显示一个字段,用户可以添加新的todo事项。在页脚中,我们想要显示切换显示全部,只显示完成或仅显示活动todo事项。
Designing Presentational Components
设计演示组件
I see the following presentational components and their props emerge from this brief:
复制代码
-
TodoList
is a list showing visible todos.todos: Array
is an array of todo items with{ id, text, completed }
shape.onTodoClick(id: number)
is a callback to invoke when a todo is clicked.
-
Todo
is a single todo item.text: string
is the text to show.completed: boolean
is whether the todo should appear crossed out.onClick()
is a callback to invoke when the todo is clicked.
-
Link
is a link with a callback.onClick()
is a callback to invoke when the link is clicked.
-
Footer
is where we let the user change currently visible todos. -
App
is the root component that renders everything else.
我看到以下演示组件和它们的道具出现在这个简短的介绍中:
-
TodoList
是一个列表,显示可见的todos事项。todos:Array
是一个带有{ id, text, completed }
属性的todo事项的数组外形。onTodoClick(id: number)
是单击todo时调用的回调函数。
-
Todo
是一个单一的todo事项。text: string
是要显示的文字。completed: boolean
是todo事项是否应该出现划掉。onClick()
是单击todo时调用的回调。
-
Link
是一个回调链接。onClick()
是单击链接时调用的回调。
-
Footer
是我们让用户更改当前可见的todo事项的地方。 -
App
是呈现其他所有内容的根组件。
Designing Container Components
设计容器组件
We will also need some container components to connect the presentational components to Redux. For example, the presentational TodoList component needs a container like VisibleTodoList that subscribes to the Redux store and knows how to apply the current visibility filter. To change the visibility filter, we will provide a FilterLink container component that renders a Link that dispatches an appropriate action on click:
复制代码
VisibleTodoList
filters the todos according to the current visibility filter and renders aTodoList
.FilterLink
gets the current visibility filter and renders aLink
.filter: string
is the visibility filter it represents.
我们还需要一些容器组件将演示组件连接到Redux。例如,演示组件需要一个像这样的容器订阅Redux商店,并知道如何应用当前的可见性过滤器。要更改可见性过滤器,我们将提供一个容器组件,呈现一个在点击时发送适当操作的容器组件:TodoListVisibleTodoListFilterLinkLink
VisibleTodoList
根据当前可见性过滤器过滤待todo项并渲染TodoList
组件。FilterLink
获取当前可见性过滤器并渲染一个Link
组件。filter: string
它代表的可见性过滤器。
Designing Other Components
设计其他组件
Sometimes it's hard to tell if some component should be a presentational component or a container. For example, sometimes form and function are really coupled together, such as in the case of this tiny component:
复制代码
AddTodo
is an input field with an “Add” button
有时很难判断某个组件应该是一个表示组件还是一个容器。例如,有时表单和函数实际上是耦合在一起的,比如在这个小部件的情况下:
AddTodo
是一个带有“添加”按钮的输入字段
Technically we could split it into two components but it might be too early at this stage. It's fine to mix presentation and logic in a component that is very small. As it grows, it will be more obvious how to split it, so we'll leave it mixed.
复制代码
从技术上讲,我们可以将它分成两个部分,但现阶段可能为时过早。在非常小的组件中混合呈现和逻辑是很好的。随着它的增长,如何分割它将会更加明显,所以我们会把它混合起来。
Implementing Components
实现组件
Let's write the components! We begin with the presentational components so we don't need to think about binding to Redux yet.
复制代码
我们来编写组件!我们从演示组件开始,所以我们不需要考虑绑定到Redux。
Implementing Presentational Components
实现演示组件
These are all normal React components, so we won't examine them in detail. We write functional stateless components unless we need to use local state or the lifecycle methods. This doesn't mean that presentational components have to be functions—it's just easier to define them this way. If and when you need to add local state, lifecycle methods, or performance optimizations, you can convert them to classes.
复制代码
这些都是普通的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, index) => (
<Todo key={index} {...todo} onClick={() => onTodoClick(index)} />
))}
</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'
import { VisibilityFilters } from '../actions'
const Footer = () => (
<p>
Show:
{' '}
<FilterLink filter={VisibilityFilters.SHOW_ALL}>
All
</FilterLink>
{', '}
<FilterLink filter={VisibilityFilters.SHOW_ACTIVE}>
Active
</FilterLink>
{', '}
<FilterLink filter={VisibilityFilters.SHOW_COMPLETED}>
Completed
</FilterLink>
</p>
)
export default Footer
复制代码
Implementing Container Components
实现容器组件
Now it's time to hook up those presentational components to Redux by creating some containers. Technically, a container component is just a React component that uses store.subscribe() to read a part of the Redux state tree and supply props to a presentational component it renders. You could write a container component by hand, but we suggest instead generating container components with the React Redux library's connect() function, which provides many useful optimizations to prevent unnecessary re-renders. (One result of this is that you shouldn't have to worry about the React performance suggestion of implementing shouldComponentUpdate yourself.)
复制代码
现在是时候通过创建一些容器来将这些表示组件连接到Redux。从技术上讲,容器组件只是一个使用store.subscribe()
去读取Redux状态树的一部分,并为它呈现的呈现组件提供props的React组件。您可以手动编写容器组件,但我们建议使用React Redux库的函数connect()
生成容器组件,该函数提供许多有用的优化以防止不必要的重新渲染。(这样做的一个结果就是你不必担心React性能的建议的你自己实现的shouldComponentUpdate
。)
To use connect(), you need to define a special function called mapStateToProps that tells how to transform the current Redux store state into the props you want to pass to a presentational component you are wrapping. For example, VisibleTodoList needs to calculate todos to pass to the TodoList, so we define a function that filters the state.todos according to the state.visibilityFilter, and use it in its mapStateToProps:
复制代码
要使用connect()
,您需要定义一个特殊的mapStateToProps
函数调用,告诉如何将当前的Redux存储状态转换为要传递给要包装的表示组件的props。例如,VisibleTodoList
需要计算todos
传递给TodoList
,所以我们定义一个根据state.visibilityFilter
过滤的state.todos
的函数,并在其中使用它的mapStateToProps
:
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
case 'SHOW_ALL':
default:
return todos
}
}
const mapStateToProps = state => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
复制代码
In addition to reading the state, container components can dispatch actions. In a similar fashion, you can define a function called mapDispatchToProps() that receives the dispatch() method and returns callback props that you want to inject into the presentational component. For example, we want the VisibleTodoList to inject a prop called onTodoClick into the TodoList component, and we want onTodoClick to dispatch a TOGGLE_TODO action:
复制代码
除了读取状态之外,容器组件还可以派发行为。以类似的方式,您可以定义一个名为mapDispatchToProps()
的函数来接收dispatch()
方法并返回要注入到表示组件中的回调支持的props。例如,我们要VisibleTodoList
注入一种叫做onTodoClick
的参数到TodoList
组件,我们希望onTodoClick
派发一个TOGGLE_TODO
行为:
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
复制代码
Finally, we create the VisibleTodoList by calling and passing these two functions:
复制代码
最后,我们通过调用connect()
并传递这两个函数来创建VisibleTodoListconnect()
:
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
复制代码
These are the basics of the React Redux API, but there are a few shortcuts and power options so we encourage you to check out its documentation in detail. In case you are worried about mapStateToProps creating new objects too often, you might want to learn about computing derived data with reselect.
复制代码
这些是React Redux API的基础知识,但有几个捷径和强大的选项,因此我们鼓励您详细查看其文档。如果你是担心mapStateToProps
过于频繁创建新的对象,你可能想了解计算得出的数据与重新选择。
Find the rest of the container components defined below:
复制代码
查找下面定义的其余容器组件:
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
复制代码
Implementing Other Components
实现其他组件
containers/AddTodo.js
Recall as mentioned previously, both the presentation and logic for the AddTodo component are mixed into a single definition.
复制代码
回忆如前所述,AddTodo
组件的表示和逻辑被混合到单个定义中。
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
复制代码
If you are unfamiliar with the ref attribute, please read this documentation to familiarize yourself with the recommended use of this attribute.
复制代码
如果您不熟悉ref
属性,请阅读文档以熟悉推荐使用此属性。
Tying the containers together within a component
将容器捆绑在一个组件内
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
复制代码
Passing the Store
通过Store
All container components need access to the Redux store so they can subscribe to it. One option would be to pass it as a prop to every container component. However it gets tedious, as you have to wire store even through presentational components just because they happen to render a container deep in the component tree.
复制代码
所有容器组件都需要访问Redux store,以便他们可以订阅它。一种选择是将其作为prop传递给每个容器组件。然而它很乏味,你必须连线store通过展示组件,因为他们渲染容器发生在组件树的深处。
The option we recommend is to use a special React Redux component called <Provider> to magically make the store available to all container components in the application without passing it explicitly. You only need to use it once when you render the root component:
复制代码
我们推荐的选择是使用所谓的特殊React Redux组件<Provider>
去神奇地使store提供应用程序中的所有容器组件不用明确传递给它。当你渲染根组件时,只需要使用它一次:
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')
)
复制代码
Next Steps
下一步
Read the complete source code for this tutorial to better internalize the knowledge you have gained. Then, head straight to the advanced tutorial to learn how to handle network requests and routing!
复制代码
阅读本教程的完整源代码以更好地内化您获得的知识。然后,请直接前往高级教程以了解如何处理网络请求和路由!
时间: 2018-02-27