Redux Store以及数据流

Store

在前面的章节中,我们定义了代表“发生了什么”的事实的actions以及根据这些动作更新状态的Reducer。

Store是将他们聚集在一起的对象。Store有以下责任:

  • 持有应用程序状态;
  • 允许通过getState()访问状态;
  • 允许状态通过dispatch(action)更新;
  • 通过订阅注册subscribe(listener);
  • 通过subscribe(listener)返回的函数来处理注销监听器。

重要的是要注意,你将只有一个store在Redux应用程序中。当你想拆分你的数据处理逻辑时,你将使用reducer组合而不是许多store。
如果您有reducer,创建store很容易。在上一节中,我们使用了combineReducers()将多个reducer合并为一个。我们现在将其导入,并将其传递给createStore()。

1
2
3
import { createStore } from 'redux'
import todoApp from './reducers'
const store = createStore(todoApp)

您可以选择指定初始状态作为createStore()的第二个参数。这对于保证客户端的状态与服务器上运行的Redux应用程序的状态相匹配非常有用。

1
const store = createStore(todoApp, window.STATE_FROM_SERVER)

Dispatching Actions

现在我们已经创建了一个store,让我们来验证我们的程序的作品!即使没有任何UI,我们也可以测试更新逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {
addTodo,
toggleTodo,
setVisibilityFilter,
VisibilityFilters
} from './actions'


console.log(store.getState())

// Every time the state changes, log it
// Note that subscribe() returns a function for unregistering the listener
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)

// Dispatch some actions
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))

// Stop listening to state updates
unsubscribe()

你可以看到这是如何导致store的状态发生变化的:
display Image
在我们开始编写UI之前,我们指定了我们的应用程序的行为。我们不会在本教程中做到这一点,但是现在您可以为您的reducer和actionCreator编写测试。你不需要嘲笑任何东西,因为它们只是纯粹的功能。调用他们,并就他们返回的内容作出断言。

index.js

1
2
3
4
import { createStore } from 'redux'
import todoApp from './reducers'

const store = createStore(todoApp)

Data Flow

Redux体系结构围绕严格的单向数据流进行。
这意味着应用程序中的所有数据都遵循相同的生命周期模式,使您的应用程序的逻辑更具可预测性并更易于理解。它还鼓励数据规范化,以便最终不会出现多个相互不知道的相同数据的独立副本。
如果您仍然不确定,请阅读Motivation和The Case for Flux,以获得支持单向数据流的引人注目的论点。虽然Redux不完全是Flux,但它具有相同的关键优势。
任何Redux应用程序中的数据生命周期都遵循以下4个步骤:
1.你可以调用store.dispatch(action)
动作是一个描述发生的事情的简单对象。例如:

1
2
3
{ type: 'LIKE_ARTICLE', articleId: 42 }
{ type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
{ type: 'ADD_TODO', text: 'Read the Redux docs.' }

把行动看作是一个非常简短的新闻片段。“玛丽喜欢第42条”或“阅读Redux文档”。被添加到todos列表中。“
您可以从应用程序中的任何位置调用store.dispatch(action),包括组件和XHR回调,或者甚至以预定的时间间隔。
2.Redux store调用您提供的reducer功能。
store将向reducer传递两个参数:当前状态树和动作。例如,在todo应用程序中,根reducer可能会收到如下所示的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// The current application state (list of todos and chosen filter)
let previousState = {
visibleTodoFilter: 'SHOW_ALL',
todos: [
{
text: 'Read the docs.',
complete: false
}
]
}

// The action being performed (adding a todo)
let action = {
type: 'ADD_TODO',
text: 'Understand the flow.'
}

// Your reducer returns the next application state
let nextState = todoApp(previousState, action)

请注意,reducer是一个纯功能。它只计算下一个状态。它应该是完全可预测的:多次调用相同的输入应该产生相同的输出。它不应该执行API调用或路由器转换等任何副作用。这些应该在动作发出之前发生。
3.根reducer可以将多个reducer的输出组合成单个状态树。
您如何构建根部reducer完全取决于您。Redux提供了combineReducers()辅助函数,用于将根reducer“分解”为单独的函数,每个函数管理状态树的一个分支。
这是combineReducers()的工作原理。假设您有两个reducer,一个用于todos列表,另一个用于当前选择的过滤器设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function (state = [], action) {
// Somehow calculate it...
return nextState
}

function visibleTodoFilter(state = 'SHOW_ALL', action) {
// Somehow calculate it...
return nextState
}

let todoApp = combineReducers({
todos,
visibleTodoFilter
})

当你发出一个动作时,由combineReducers返回的todoApp将调用两个reducer:

1
2
let nextTodos = todos(state.todos, action)
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)

然后它会将两组结果合并为一个状态树:

1
2
3
4
 return {
todos: nextTodos,
visibleTodoFilter: nextVisibleTodoFilter
}

虽然combineReducers()是一个方便的助手实用程序,但您不必使用它;随时写你自己的根reducer!
4.Redux存储保存由根reducer返回的完整状态树。
这棵新树现在是你的应用程序的下一个状态!现在每个用store.subscribe(listener)注册的监听器都会被调用;监听器可以调用store.getState()来获取当前状态。
现在,UI可以更新以反映新的状态。如果使用React Redux之类的绑定,则这是调用component.setState(newState)的点。

React

从一开始,我们需要强调Redux与React没有任何关系。您可以使用React,Angular,Ember,jQuery或vanilla JavaScript编写Redux应用程序。
也就是说,Redux与React和Deku等库合作得非常好,因为它们让您将UI描述为状态的函数,Redux响应操作发出状态更新。
我们将使用React来构建我们简单的待办事项应用程序。

React Redux install

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

1
npm install --save react-redux

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

展示和容器组件

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

































展示 Components 容器 Components
含义 看起来如何(标记,样式) 工作原理(数据读取,状态更新)
Redux No
Yes
读取数据 从props读取数据 订阅Redux状态
改变数据 从props调用回调 Dispatch Redux actions
写入 手动写 通常由React Redux生成

我们要编写的大多数组件都是展示的,但我们需要生成一些容器组件以将它们连接到Redux存储。这和下面的设计概要并不意味着容器组件必须靠近组件树的顶部。如果一个容器组件变得太复杂了(即它有很多嵌套的展示组件,并有无数的回调被传递下去),那么在组件树中引入另一个容器,如FAQ中所述。
从技术上讲,你可以用store.subscribe()手工编写容器组件。我们不建议您这样做,因为React Redux会进行许多难以完成的性能优化。出于这个原因,我们将使用React Redux提供的connect()函数生成它们,而不是编写容器组件,如下所示。

设计组件层次结构

请记住我们如何设计根状态对象的形状?是时候我们设计UI层次结构来匹配它。这不是特定于Redux的任务。在React中思考是一个很好的教程,可以解释这个过程。
我们的设计简介很简单。我们想要显示待办事项列表。单击时,待办事项完成后划掉。我们想要显示一个字段,用户可以添加新的待办事项。在底部,我们想要显示切换显示全部,只显示完成或仅显示活动待办事项。

设计演示组件

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

  • ToDoList 是一个列表,显示可见的待办事项。
    • todos:Array是包含{id,text,completed}形状的todo项目的数组。
    • onTodoClick(id:number)是单击todo时调用的回调。
  • ToDo是一个单一的待办事项
    • text: string是要显示的文字。
    • completed: boolean是待办事项是否应该出现划掉。
    • onClick()是单击todo时调用的回调。
  • Link是一个回调链接。
    • onClick()是单击链接时调用的回调。
  • Footer是我们让用户更改当前可见的待办事项的地方。
  • App是呈现其他所有内容的根组件。
    他们描述的外观,但不知道数据来自哪里,或者如何改变它。他们只渲染给他们的东西。如果你从Redux迁移到其他的东西,你将能够保持所有这些组件完全一样。他们没有依赖Redux。

设计容器组件

我们还需要一些容器组件将演示组件连接到Redux。例如,演示TodoList组件需要一个容器,如VisibleTodoList,该容器订阅Redux存储并知道如何应用当前可见性过滤器。要更改可见性过滤器,我们将提供一个FilterLink容器组件,用于呈现链接,并在点击时分配适当的操作:

  • VisibleTodoList根据当前可见性过滤器过滤待办事项并呈现TodoList。
  • FilterLink获取当前可见性过滤器并呈现链接。
    • filter: string是它代表的可见性过滤器。

设计其他组件

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

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

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

实现组件

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

实现演示组件

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

大专栏 Redux Store以及数据流  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

importimport ReactReact fromfrom ''reactreact''
importimport PropTypesPropTypes fromfrom ''prop-typesprop-types''

constconst LinkLink == ({ active, children, onClick }) ({ active, childr => {
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

importimport ReactReact fromfrom ''reactreact''
importimport FilterLinkFilterLink fromfrom ''../containers/FilterLink../containers/Filter '
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

实现容器组件

现在是时候通过创建一些容器将这些表示性组件连接到Redux。从技术上讲,容器组件只是一个React组件,它使用store.subscribe()来读取Redux状态树的一部分,并为它呈现的呈现组件提供props。您可以手动编写容器组件,但我们建议使用React Redux库的connect()函数生成容器组件,该函数提供许多有用的优化以防止不必要的重新呈现。(这样做的一个结果是你不必担心自己实现shouldComponentUpdate的React性能建议。)
要使用connect(),需要定义一个名为mapStateToProps的特殊函数,它告诉如何将当前的Redux存储状态转换为要传递给要包装的表示组件的道具。例如,VisibleTodoList需要计算待办事项以传递给TodoList,所以我们定义一个函数,根据state.visibilityFilter过滤state.todos,并在其mapStateToProps中使用它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
}
}

除了读取状态之外,容器组件还可以调度操作。以类似的方式,您可以定义一个名为mapDispatchToProps()的函数,它接收dispatch()方法并返回要注入演示组件的回调支持。例如,我们希望VisibleTodoList将名为onTodoClick的道具注入TodoList组件,并且我们希望onTodoClick发送TOGGLE_TODO操作:

1
2
3
4
5
6
7
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}

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

1
2
3
4
5
6
7
8
import { connect } from 'react-redux'

const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)

export default VisibleTodoList

这些是React Redux API的基础知识,但有几个捷径和开关选项,因此我们鼓励您详细查看其文档。如果您担心mapStateToProps过于频繁地创建新对象,则可能需要了解使用重新选择计算派生数据。
查找下面定义的其余容器组件:
container/FilterLink.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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
如前所述,AddTodo组件的展示和逻辑都被混合到一个单一的定义中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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

如果您不熟悉ref属性,请阅读本文档以熟悉推荐使用此属性。

将容器捆绑在一个组件内

components/App.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

传递Store

所有容器组件都需要访问Redux存储,以便他们可以订阅它。一种选择是将其作为props传递给每个容器组件。然而它很乏味,因为即使是通过表示组件,也只是因为它们碰巧在组件树中渲染容器,所以你必须连线存储。
我们推荐的选项是使用名为 的特殊React Redux组件来神奇地使存储可供应用程序中的所有容器组件使用,而无需明确传递它。渲染根组件时,只需要使用它一次:
index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'

const store = createStore(todoApp)

render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)

猜你喜欢

转载自www.cnblogs.com/sanxiandoupi/p/11713241.html