Actions
Actions是用于存放数据的载体,通过store.dispatch()
函数来将数据从app发送到store
Actions 是一个Js对象,它有一个默认的属性:type
,它用于表明action的类型,它的值是一个字符串常量
定义一个Actions:
addTodo = {
type: 'ADD_TODO',
text: 'Build my first Redux app'
}
Action Creators
上面我们定义了一个actions,现在需要一个Action Creators,用于创造actions
在Redux中creators 仅仅是返回一个action, 如下:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
在传统的 Flux 中,当action cretors被调用时 常常用于触发一个dispatch,如下:
function addTodoWithDispatch(text) {
const action = {
type: ADD_TODO,
text
}
dispatch(action)
}
在Redux中不是这样的,它并不与dispatch绑定,如下:
dispatch(addTodo(text))
dispatch(completeTodo(index))
另外,我们可以将其封装一下,如下:
const boundAddTodo = text => dispatch(addTodo(text))
const boundCompleteTodo = index => dispatch(completeTodo(index))
这样,我们就可以直接调用他们,如下:
boundAddTodo(text)
boundCompleteTodo(index)
actions.js部分
/*
* action types
*/
export const ADD_TODO = 'ADD_TODO'
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
/*
* other constants
*/
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
/*
* action creators
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
Reducers
Actions描述了有事情发生,但是没有指定在响应中,app的state应该如何改变,这个工作由 Reducers来完成
设计state
在Redux中,所有的state都被存储到一个单一的对象上,因此在写代码之前,先思考状态的设计,是一个良好的习惯,
在实际应用中,我们经常需要存储一些数据,以及一些UI state在state tree上,最好做到:保持数据与UI状态分离
设state时,尽量要保持标准化,不要有任何嵌套,参考JSON数据标准化
处理状态
reducer函数是一个纯函数,它接收两个参数:prestate和一个action,然后返回下一个state,如下:
(previousState, action) => newState
纯函数:就是传入什么,输出什么,不会改变输入,没有副作用
注意:一定要保证reducer是一个纯函数,有以下几点要注意:
- 不要修改它的传入参数
- 不要执行API的调用和路由转换等有副作用的操作
- 不要调用不纯的函数,如:Date.now() 、Math.random()
也就是:给reducer参数,它应该计算出next state,然后返回next state,不能有副作用、不能有API调用,仅仅只是计算
接下来定义一个reducer,并设置它的初始状态, 如下:
import { VisibilityFilters } from './actions'
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
最终分割reducer之后的代码,如下:
reducers.js
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
function visibilityFilter(state = SHOW_ALL, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state,
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Store
之前讲过Actions表示发生了什么,Reducers就是根据Actions来更新state,
而Store是将它们组合在一起,
Store有以下职责:
- Holds application state;
- Allows access to state via getState();
- Allows state to be updated via dispatch(action);
- Registers listeners via subscribe(listener);
Handles unregistering of listeners via the function returned by subscribe(listener).
需要注意的是在一个Redux应用中,只有一个Store,如果需要分割应用的数据逻辑,可以使用reducer composition 来实现
使用createStore,如下:
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
Data Flow 数据流向
Redux遵循严格的单向数据流,这意味着所有的数据在APP中的流向具有相同的生命周期,且它们是可预测的,一个数据的生命周期主要包括以下四个阶段:
- 调用
store.dispatch(action)
我们可以在任何地方调用该函数; - Redux的store调用我们提供的reducer函数
store将传递两个参数给reducer:当前state tree和action, reducer函数会返回下一个start,它是一个纯函数
例如,上面的todo代码
// 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函数是可预测的,当输入相同的参数,调用它很多次,它应该输出相同的值
- root reducer 可以将多个reducer输出组合到一个state tree中
root reducer的结构完全由我们自己来决定,Redux提供了一个combineReducers()
函数来帮助我们将多个reducer合并为一个,如下所示:
function todos(state = [], action) {
// Somehow calculate it...
return nextState
}
function visibleTodoFilter(state = 'SHOW_ALL', action) {
// Somehow calculate it...
return nextState
}
let todoApp = combineReducers({
todos,
visibleTodoFilter
})
当触发一个action时,todoApp通过combineReducers将会返回下面两个reducers:
let nextTodos = todos(state.todos, action)
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)
然后它将所有的结果合并到一个的单一的state tree中:
return {
todos: nextTodos,
visibleTodoFilter: nextVisibleTodoFilter
}
combineReducers()
是一个很方便的函数,可以充分利用它
- Redux store 将root reducer返回的state tree保存起来
这个新的state tree 就是app的下一个 state tree,接下来,每一个通过store.subscribe(listener)
注册的listener都将被调用,listener可以通过调用store.getstate()
去获取最新的state tree
现在,如果使用了react,那么可以通过component.setState(newState)
来更新UI
与React配合使用
展示和容器组件(Presentational and Container Component)
Presentational Component | Container Component | |
---|---|---|
Purpose | How thing look(markup, styles) | How thing work(data fetching,data updates |
Awre of Redux | No | Yes |
To read data | Read data from pops | Subscribe to Redux data |
To change data | Invoke callbacks from props | Dispatch Redux actions |
Are written | By hand | Usually generated by React Redux |
注意一点:Redux配合React使用时,加强了展示与容器组件分离这一原则
关于两者的对比如下:
Presentational Component | Container Component | |
---|---|---|
Purpose | How thing look(markup, styles) | How thing work(data fetching,data updates |
Awre of Redux | No | Yes |
To read data | Read data from pops | Subscribe to Redux data |
To change data | Invoke callbacks from props | Dispatch Redux actions |
Are written | By hand | Usually generated by React Redux |
大多数情况下,我们写的都是Presentation Component,而展示组件本身并不存储数据,它通过Props来获得数据,因此,还需要编写一些Container Component,通过它从Redux获取数据,并传给Presentation Component;注意:虽然我们可以将store.subscribe()
写入Container component来获取数据,但是不推荐这样做,因为Redux在内部做了很多性能优化,而这些优化是我们手动无法达成的,最好的办法是:使用Redux提供的connect()
方法来生成,而不是写入Container component
Designing Component Hierarchy
设计组件的层级时,我们要将其匹配root state object,在设计之前,对功能做一个简短的陈述很有必要
设计Presentation component
几个原则:
- 当在设计展示组件时,不要考虑它如何获取数据,例如它是如何绑定Redux,先不需要考虑这些j,这样便可以将其与Container component 分离开来,这些Presentation component就成为一个独立的组件,没有任何牵连,提供了它的复用性
- 一般来说,Presentation component 本身是不保持状态的(stateless component)或者说是不存储数据,所以,我们在最开始可以将它们设计为一个function而不是class,当随着程序的扩展,它们需要local state 或者使用 lifecycle methods,我们可以将它们转换为class
Implementing Container Components
一般说来,Container componennt是一个React组件,它主要通过调用store.subscribe()
来从Redux中获取state,并通过props将state传入到它负责渲染的Presentation component中,然后Presentation component 根据传入的props进行渲染;虽然,我们可以自己编写Container component,但是推荐使用Redux提供的connetc()
来进行编写。
对于Container component 来说,它与Redux进行通信,无非就是两种操作:
- 从Redux获取state,然后将state 传输到 Presentation component中
- 因为Presentation component是最接近用户的,因此肯定需要处理一些用户的操作,当用户操作了,便需要对Redux中的state 进行更新,而Presentation component不直接对state进行更新,而是通过调用dispatch来分发事件来对state进行更新,而这个事件也是通过Container来定义,并通过props传递个Presentation component
React是基于MVC架构的,所以可以通过MVC模型来理解这个过程:
Presentation component: 是View层
Container component:是Controller层
Redux:是Model层
View层不能直接与Model层进行交互,只能通过Controller来连接View层和Model层
明确这点之后,再看我们这个例子,我们在Container component 中定义了一个方法来从Redux中获取数据(Controller 从 Model中取数据),然后,我们需要将这个数据传输到Presentation component(Controller 传输数据到 View层),Redux提供了一个接口mapStateToProps
,来便于我们高效的进行传输,这是第一种操作。
Presentation component(View层)是最接近用户的,因此,它需要处理用户的操作;而我们知道Presentation component是不保存数据,而用户的操作可能需要更改数据,因此它需要通过Container component来处理Presentation component的操作,对Redux中的数据进行更改。
对应到这个例子中就是:我们在Container component中定义一些事件处理函数,并将其绑定到Presentation component中(通过props传递),在Redux中,对数据更新是通过store.dispatch()
来分发action处理的, 所以我们的事件处理函数就是要通过store.dispatch()
来分发action;Redux提供了一个接口mapDispacthToProps()
,来便于我们将dispatch通过props传输到Presentation component中
View层要处理用户的操作,这个过程就是View 通知 Controller 有事件发生了,Controller再通知Redux进行数据更新,
从Redux获取数据
通过 mapStateToProps 将数据传入Presentation component中
为了使用connect()
,我们需要定义一个叫做mapStateToProps
的函数,该函数会告诉我们如何将当前的Redux store state 传递到该Container component 负责渲染的 presentation component的props中
例如:VisibleTodoList组件(它是Container component)需要根据传入的TodoList计算出todos,因此,我们定义一个函数,它根据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)
}
}
分发事件来更新Redux
const mapDispatchToProps = dispatch => {
return {
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
通过 connnet()
来传输mapStateToProps
和mapDispatchToProps
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
这个便是我们的一个 Container component,也就是Controller 层
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
关于connect()方法的使用
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
作用:将React 组件与Redux store 连接起来
它不会更改传入的组件,而是将一个与Redux绑定的新的组件返回给你
Arguments:
-
[mapStateToProps(state, [ownProps]):stateProps](function)
:如果这个参数被指定,那么返回的新的组件将会subscribe(订阅) Redux store的更新,也就是说不管Redux store什么时候被更新,mapStateToProps都将会被调用,它是一个纯对象,会被合并新组件的props上,如果你不想要订阅 Redux store 的更新,那么就在该参数位置填写:null
或undefined
-
[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function):
:
网友评论