redux-docs
Table of Contents
Installation
- 除了不可避免的redux以外,通常还需要binding react的react-redux和一些helper工
具redux-devtools
npm install --save redux npm install --save react-redux npm install --save-dev redux-devtools
Gist
- 下面三条是一个redux框架的精髓,总结起来redux就三个动作
- 你的app的所有的state都存在一个object tree里面,而这个object tree存放在"唯 一的一个store里面"
- 而改动这个"唯一的store"的方法就是触发一个action,而这个action也是一个object, 这个object描述了"发生了某种事情"
- 而"发生了某种事情"以后,应该如何改动state tree,是由reducer来完成的
- 我们来看一个redux的例子
- 首先是从redux import一些文件
import { createStore } from 'redux'
- 然后是一个reducer, 一个reducer肯定是一个pure function,其声明如下, 也就是
说是一个输入'老的state'和action,然后返回'新的action'的过程.
(state, action) => state
- state到底是什么类型,不重要,它可以是primitive, array, object,或者是Immutable.js 但是的确重要的是:'老的state'不能被改动!如果有改动的话,我们要生成一个新的 state object 然后返回
- 下面就是我们reducer的例子,使用switch来判断action到底是什么
import { createStore } from 'redux' // function counter(state = 0, action) { switch (action.type) { case 'INCREMENT': return state + 1 case 'DECREMENT': return state - 1 default: return state } }
- 下面创建一个Redux store来保存我们'整个app的state', createStore的第一个参
数没有输入,那么就是默认state为一个Number, 其值为0. 我们的createStore函数
就是用来创建我们的store的(注意store就是全体state的别称)
let store = createStore(counter)
- 返回值肯定是一棵树(object)而不再是一个Number啦, 从源代码(src/createStore.js)
我们可以看到这是一个带有几个函数的object
return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }
- getState是获取当前的state,而subscribe则是用来更新UI(在state更新的情况下),
大多数情况下,要使用binding library(比如react-redux)来调用这个subscribe,当
然了我们也可以为了log而手动调用
store.subscribe( () => console.log(store.getState()); )
- 唯一能改动内部state的方法就是'didpatch一个action',方法如下
store.dispatch({type: 'INCREMENT'}) // 1 store.dispatch({type: 'INCREMENT'}) // 2 store.dispatch({type: 'DECREMENT'}) // 1
- 首先是从redux import一些文件
- 好,我们来总结一下这个例子:
- 我们没有"直接的改动state",而是定义了一些action(action本身就是简单的case object)
- 我们调用action的方法,是创建了一个reducer function,由它来统一管理这些action
- 而我们的store是用来管理'所有的state'的,它调用dispatch的时候,参数是一个action 很明显,store内部是调用reducer来应对这个action(store初始化的时候,会要求这个 reducer)
- 我们的redux,只有一个state,存放在store里面,也只有一个root reducer,但是你可以 把reducer分成多个来处理state的不同部分,当然了,最后要把reducer合并起来成一个 这样才能初始化我们的store(store只有一个初始化参数root reducer)
Introduction
Motivation
- js的single-page的application越来越多,我们的代码要处理比以往更多的state,这些 个state可能包括server的response和cached data或者是当前创建的还没有和远程同步 的数据
- UI的state也非常的多,因为我们要处理active route, selected tab,pagination等等
- 处理不停改变的state是非常难的,因为model之间可能会自动触发一些机制而自动改变, 我们无法控制这些东西
- 而这些复杂度的来源,在于我们弄混了下面两个concept:
- mutation
- asynchronicity
- react处理这些问题的办法是把asynchrony和direct DOM从view里面移除,但是处理state 依然需要你自己来做(当然了,react只是mvc中的v), 这就是redux所做的
- 和Flux不一样的是, redux试图让statemutation变得更容易预测到,其方法是施加了一 些restriction,这种restriction来决定了how and when updates can happen,我们把 这些restriction总结成三个准则,如下
Three Principles
- Redux可以简单的总结为如下三个准则:
Single source of truth
- 你整个application的state都存在一个单独的store里面
- 这会很容易的创建universal app,这也会让你的代码很容易调试
- 而且传统意义上面非常困难的事情,比如Undo/Redo可以里面变动很容易,因为你的state
都存在以single tree里面,下面就是我们打印的一个store的例子
console.log(store.getState()); //////////////////////////////////////////////////// // <===================OUTPUT===================> // // { // // visibilityFilter: 'SHOW_ALL', // // todos: [ // // { // // text: 'Consider using Redux', // // completed: true, // // }, // // { // // text: 'Keep all state in a single tree', // // completed: false // // } // // ] // // } // ////////////////////////////////////////////////////
State is read-only
- 改动state的唯一方法,就是赋予这个state一个action, 这个action是用来描述"发生 了什么事情(比如用户按了一个按钮)"
- 因为action只不过是一个plain object,它可以被log, serialize, store,并且以后 用做测试用途
- 而所有的change是统一管理在一个store里面的,也就不会出现race condition,如下,
我们总是对一个store进行操作,操作肯定是有顺序的,非异步的,也就不会有race condition
store.dispatch({ type: 'COMPLETE_TODO', index: 1 }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' })
Changes are made with pure functions
- action描述了"发生了什么", action可能有多种,而根据不同的action来决定state如 何改变的函数叫做reducer
- redux的reducer的特点在于,其pick一个state,分析了action以后,决定使用某种改动 之于state,然后会生成一个新的state来返回!
- 最开始的时候你可能只有一个reducer,但是随着项目的扩大,你可以把你的reducer分 成多个每个都对应一个state tree(然后最后再合起来).因为reducer是简单的function 所以你完全可以复用它们, 传递additional data给它们, 控制它们调用的顺序,redux 的灵活性,就体现在reducer里面了
- 下面就是我们的一个例子,我们有两个reducer,分别处理不同的state tree,最后把它
们合并成一个,再用这一个reducer来初始化一个store
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 'COMPLETED_TODO': return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: true }) } return todo }) default: return state } } import { combineReducers, createStore } from 'redux' let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer)
Prior Art
- Redux是多种科技的组合,有很多相同点和不同点
Flux
- redux从flux借鉴来的是"使用action来描述产生了哪些改变"
- 但是和flux不同的是:
- Redux没有Dispatcher的概念
- Redux不让你更改data,而是创建一个新的data
Elm
- Elm是一个functional programming language, 在elm里面更新要遵守如下格式
(action, state) => state
Immutable
- Immutable是一个js library,它和redux和合拍,所以我们推荐你使用这个library
- 说推荐使用,是因为redux根本不care你使用什么来存储state,可是object,可以是immutable js object,也可以是任何的其他什么对象.但是redux do care你的object必须是 immutable的.所以Backbone就不能做Redux的state啦,因为Backbone model是mutable 的
Baobab
- Baobab是另外一个immutable的API,用来更新plain js object
Rx
- Reactive Extensions的简称,是一个优秀的管理复杂asynchronous app的方法
Ecosystem
- 一些资料,重要的总结在https://github.com/xgrommx/awesome-redux
Example
- redux还有一些例子和自己的source code一起发布https://github.com/reactjs/redux/tree/master/examples
Basics
- redux的结构非常简单,下面我们通过Todo app的例子来介绍一下
Actions
- action名字看起来很"动态",其实就是一个object,其描述了"发生了什么事情".它们是 store所有信息的来源.你通过store.dispatch(xxaction)来发送action
- 下面是添加新的todo item的代码:
- action就是下面这个plain object的样子, 对这个plain object的唯一要求就是要
有string类型的type
{ type: ADD_TODO, text: 'Build my first Redux app' }
- 那我们就要设置一个const变量来存储这个string,使用const是ES6以来的一个特点
只有明确要改动的变量才不是const的
const ADD_TODO = 'ADD_TODO'
- 如果你的app足够大了,那么你就需要把这些const变量移到一个文件里面
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
- action就是下面这个plain object的样子, 对这个plain object的唯一要求就是要
有string类型的type
- 除了type这个property以外,其他的地方都自便,比如我们这里就加上了一个index
{ type: TOGGLE_TODO, index: 5 }
Action Creators
- action creator和action不是一回事,action creator说白了就是一个'创建并返回
action的function',比如下面就是一个action creator
function addTodo(text) { return { type: ADD_TODO, text } }
- 在传统的flux里面,也有action creator(action的概念就是从flux里面来的),但是flux
里面的action creator还得负责trigger dispatch, 但是我们的redux里面,dispatch
函数是要自己主动触发的(前面的例子是store调用了dispatch, 而我们这里是直接调
用dispatch函数, 看过源代码后就知道,js是定义了这个函数,然后把这个函数作为
store object的一个组成部分而已,在任何地方都可以使用这个函数Redux.dispatch)
- 在flux里面,action creator主动调用dispatch
function addTodoWithDispatch(text) { const action = { type: ADD_TODO, text } dispatch(action) }
- 在Redux里面,使用下面代码,直接调用dispatch(xxaction)
dispatch(addTodo(text)) dispatch(completeTodo(index))
- 当然也可以达到flux'自动'调用的样子,这种叫做bound action creator
const boundAddTodo = (text) => dispatch(addTodo(text)) // call example boundAddTodo(text)
- 虽然我们可以直接调用dispatch(),或者使用store.dispatch(),但是更多的情况下 你很可能会使用helper函数,比如react-redux里面的connect()
- 另外你可以给dispatch()函数邦迪很多个action creator,方法是函数bindActionCreators()
- 在flux里面,action creator主动调用dispatch
Reducers
- action提供了"发生了什么",而reducer就是app对"每一种情况"的response
Designing the State Shape
- 在redux里面,所有的application state都存在一个single object里面,我们先来看看
一个最最简单的object是什么样子的, 对于一个todo app,我们就存两种东西:
- 我们当前选择的filter方式(全显示,只显示已完成,还是只显示未完成)
- 全部的todo list
- 下面就是我们的state tree
{ visibilityFilter: 'SHOW_ALL', todos: [ { text: 'Consider using Redux', completed: true }, { text: 'Keep all state in a single tree', completed: false } ] }
Handling Actions
- 好了,设计好state的样子之后,下面就来创建reducer, reducer是一个pure function
它的输入是一个老的State和一个action,返回值是一个新的state
(previousState, action) => newState
- reducer之所以被叫做reducer,是因为像我们reducerFun()这样的函数(输入一个state, 更改后返回新的state)在历史上经常传递给Array.prototype.reduce, 也就是说reducerFun 就好像是reduce()的跟班一样.但是其实我们redux的reducer并不会传递给一个reduce函数!
- 下面是一个reduce的例子
// Sum all the values of an array var total = [0, 1, 2, 3].reduce((acc, cur) => acc + cur, 0); // total = 6
- 作为一个pure function,你不能再reducer里面做下面的事情. reducer最好只做计算
- 改变parameter
- 调用会产生side effect的API
- 调用non-pure的函数,比如Date.now()或者是Math.random()
- 我们的reducer还要设置initial state, 这个是和常规我们理解的reducer不一样的,
这是因为常规的reducer会传入reduce(),这个reduce()会自带initial value,而我们
没有这个便利,我们要自己设置初始化参数
import { VisibilityFilters } from './actions' const initialState = { visibilityFilter: VisibilityFilters.SHOW_ALL, todos: [] } function todoApp(state, action) { if (typeof state === 'undefined') { return initialState } // for now, don't handle any actions return state }
- 当然了我们可以使用ES6的default argument来写,也就是
function todoApp(state = initialState, action) { // ... return state }
- 好了,我们来处理一个action,比如SET_VISIBILITY_FILTER
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) default: return state } }
- 注意Object.assign()是创建一些新的object,注意,第一个参数还得设置成空的object, 这是为了保护state不被改动.Object.assign()会把自己从第二个参数开始的所有参数 加入第一个参数,然后返回第一个参数的copy
- 默认情况下返回原来的state,也是一个好主意(至少不会遭到破坏)
Handling More Actions
- 让我们来处理更多的action,加case就好了
function todoApp(state = initialState, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) case ADD_TODO: return Object.assign({}, state, { todos: [ ...state.todos, { text: action.text, completed: false } ] }) default: return state } }
- 这里我们还是新建了一个state, 这个state使用了array spread operator来展现array 的所有成员
- 再增加一个case也是很轻松的事情了
case TOGGLE_TODO: return Object.assign({}, state, { todos: state.todos.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { comleted: !todo.completed }) } }) })
- 如果经常做这样的"无损更新"可能你就会想要使用类似Immutable这样的原生的immutable 支持,所以deep update也就支持的很好了
Spliting Reducers
- 我们把代码写出来就发现了一个问题,更新todo和更新visibilityFilter其实是两个
完全独立的事件,我们写在一个reducer里面就会造成函数特别的大,一个解决的办法
就是把我们的reducer分开, reducer分开的同时,我们也要把state分开!:
- 一部分处理visibilityFilter,state初始值是一个string
function visibilityFilter(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter default: return state } }
- 另外一部分处理todos,初始值是一个数组
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 } }
- 最后,要把两个reducer结合起来
function todoApp(state = {}, action) { return { visibilityFilter: visibilityFilter(state.visibilityFilter, action), todos: todos(state.todos, action) } }
- 上面的结合由于经常会用到,redux设计了一个helper函数
import { combineReducers } from 'redux' const todoApp = combineReducers({ visibilityFilter, todos }) export default todoApp
- 一部分处理visibilityFilter,state初始值是一个string
Store
- 前面我们已经定义了action(发生了什么), reducer(根据action来更新state)
- 而store则是把action和reducer联系起来的纽带,它有如下的责任:
- 保存application的state
- 允许对state的访问-> getState()
- 允许对state的更新-> dispatch(action)
- 注册listener-> subscribe(listener)
- 处理没有注册过的listener
- 需要注意的是,你的app只有一个store,如果你想需要把store里面的data分成不同的logic 来处理的话,你需要的是把reducer分成几个,然后在combine,而不是把store分成几个
- 在有了reducer以后,创建store就很容易了.前面我们使用combineReducers()把多个reducer
合成了一个,现在我们使用这一个reducer来初始化store
import { createStore } from 'redux' import todoApp from './reducers' let store = createStore(todoApp)
- createStore也可以指定第二个参数为initial state
let store = createStore(todoApp, window.STATE_FROM_SERVER)
Dispatching Actions
- 我们使用pure function的好处,是我们可以在没有UI的情况下进行测试,我们连mock
都不需要,下面就是我们的例子:每次改动后,都去打印state
import { addTodo, toggleTodo, setVisibilityFilter, VisibilityFilters } from './actions' // Log the initial state console.log(store.getState()) // Every time the state changes, log it // Note that subscribe() returns a function for unregistering the listener let 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()
Data Flow
- redux其实是规定了一种"严格的,单向数据流动(data flow)"
- 这样就意味着你的app里面的data是follow了一个同样的lifeycle pattern,你的app的 logic也就更加的predictable并且易于理解
- 严格的单向数据流动也同时鼓励你的数据"范式化(数据库中的范式的意思,减少数据冗余)"
Usage with React
- 在最开始,我们需要强调下,redux并不是绑定了react,我们可以使用redux来写react, anguler, ember, jQuery,甚至是纯js
- 但是我们却真的可以说,Redux和react(或Deku)配合的非常的好,因为React(或Deku)让 你把UI描述成'function of state', 而我们的Redux则去处理这个'function of state' 里面的state就好了
Installing React Redux
- React bindings没有默认安装在Redux, 所以你需要如下命令安装
npm install --save react-redux
Presentational and Container Components
- React bindings for Redux的理论支持来源于对如下两个概念的区分:
- presentational component: 我们可以简单理解为只用来展示(往往有DOM对应),而 没有state的react component
- container component: 我们可以理解为用来应对更改,从而state比较丰富,但是一 般没有DOM对应的component
- 下面我们用表格来对这两种component来做个比较
Presentational Components Container Components Purpose How things look How thins work 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 - 我们大部分写出来的component都是presentational的,但是也需要一些和Redux store 联系的component:从技术上来讲,我们当然可以使用store.subscribe()来自己书写, 但是为了享受到React Redux为我们做的optimization, 我们要使用connect()来自动 生成这component!
Designing Component Hierarchy
- 我们前面已经设计好了root state object,现在来设计UI
Presentational Component
- 所有展现出来的component肯定就是我们自己写的啦,主要有如下的component, 并且
包含它们的props:
- TodoList是一个list来显示todos
- todos: 一个array包含了{id, text, completed}
- onTodoClick(id: number) 一个callback,当todo被点击的时候调用
- Todo是一个single todo item:
- text:是string类型的text
- completed: boolean类型的来表达一个todo是否显示的
- onClick()是当一个todo被点击的时候,被调用的callback
- Link是一个包含callback的link
- onClick()是触发时候的callback
- Footer:是让用户选择可见todo的类型
- App: root component用来render everything else
- TodoList是一个list来显示todos
- presentational component就设计好了,它们的问题是,它们只显示了look,但是不知 道data从哪里来,也不知道如何更改这些data.
- 说白了,它们只知道render发送给它们的东西
- 如果你要从Redux转向其他,那么这些项目都可以保留,它们是和redux无关的项目
Container Components
- 我们还需要如下container components来为Presentational component传送数据
- VisibleTodoList: 根据当前的visibility filter来筛选todos,并render出一个 TodoList来
- FilterLink: 根据当前的visibility Filter来render出一个Link来
- filter: string是它拥有的visibility filter
Other Component
- 某些情况下,有些特别小的component,分不出是不是presentational,我们就把这种 component单列出来
Implementing Components
- 好了我们下面来实现这些component,我们先实现presentational component,这些component 根本不需要redux的知识
Presentational Components
- 因为这些component都是没有state的,所以我们把它们写成funtional stateless component,当然了你也可以不这么functional,但是我们这么做肯定更容易define它 们.
- 这里插播一句,一个react component可以是function,也可以是class. class版本的
component会多一些功能(带有private state),但是如果没有privte state,完全就
可以使用function版本的component.下面两个版本的component是完全相等的:
- function版本,带有一个props object作为参数,然后返回React内置的component
function Welcome(props) { return <h1> Hello, {props.name} </h1>; }
- class版本(es6)component,要含有一个render()函数来返回合法的component
class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name} </h1>; } }
- function版本,带有一个props object作为参数,然后返回React内置的component
- 既然你要以functional的形式来书写这些component,那么这些component就会以js function的形式,被export出去.只有当它们的确需要local state的时候,才把它们 转换成class
- 下面是components/Todo.js的代码
import React, { PropTypes } from 'react' 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
- 下面是component/TodoList.js的代码
import React, { PropTypes } from 'react' import Todo from './Todo' const TodoList = ({todos, onTodoClick }) => ( <ul> {todos.map(todo => <Todo key={todo.id} {...todo} onClick={() => onTodoClick(todo.id)} /> )} </ul> ) TodoList.propTyps = { 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, { PropTypes } from 'react' 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就更简单了,没有props
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
Container Components
- 好了,下面就是真的redux时间了.我们要把这些presentational component和Redux联 系起来.联系的方式就是创建一些新的container component
- 从技术角度上讲, container component就是这么一类React Component:
- 使用了store.subscribe()读取一部分Redux state tree
- 然后把这些读取到的state,作为prop传递给presentational component,让他们来 进行展示
- 满足这两点即可,那么你当然可以自己"手写"一个container component,但是我们的 建议是你使用React Redux 的library connect()来"自动生成"这些container component 因为它会提供很多optimization来防止re-renders(哈哈,你再也不用去写那些麻烦的 shouldComponentUpdate之类的函数了)
- 为了使用connect(), 你必须定义一个特殊的函数叫做mapStateToProps,这个函数的
作用,是如何把当前的Redux store state转换成presentational component需要的props
举个例子VisibleTodoList需要计算传递给TodoList需要的的todos,回顾一下我们TodoList
的形式如下
const TodoList = ({todos, onTodoClick })
- 好了,我们的代码如下
const getVisibleTodos = (todos, filter) => { switch(filter) { case 'SHOW_ALL': return todos case 'SHOW_COMPLETED': return todos.filter(t => t.complted) case 'SHOW_ACTIVE': return todos.filter(t => !t.complted) } } const mapStateToProps = (state) => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } }
- 除了'从store读取state给presentational component', 还有一个作用就是'从 presentational component读取action,然后dispatch,然后改变state(当然这里是改 变currentState), 然后把这个新的state传递给presentational component的props'
- 后面这个其实是要把'更改curretState的能力'传递给presentational component,方
法是为presentional component的某个prop赋予一个callback
const mapDispatchToProps = (dispatch) => { return { onTodoClick: (id) => { dispatch(toggleTodo(id)) } } }
- 这两个map函数一创建,最后就是我们把它们'赋给'presentational component的过程
了,这个过程使用了函数connect()
import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList
Passing the Store
- 我们的store是所有的commponent都必须接触到的,给每个component都传递一个包含 store的props显然是可以的,但是这也就不能叫做store了对吧
- 我们的方法是使用<Provider>,这个方法可以让所有的component获得store,而不用再
props里面显示设置
import React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createSotre } from 'redux' import todoApp from './reducers' import App from './components/App' let store = createStore(todoApp) render( <Provider store= {store}> <App/> </Provider>, document.getElementById('root') )
Advanced
Async Actions
- 前面我们的例子都是synchronous的,也就是说每当一个action被dispatch了以后, state 会immediately的update
- 下面我们会讲解asynchronous 的app,会使用reddit api来讲解
Actions
- 当你使用asynchronous API的时候,会有两个特别的时间点:
- 你发出call的moment
- 你接受到answer(timeout也算)的moment
- 一般情况下一个API请求都需要处理三个action:
- 一种通知reducer请求开始的action
- 一种通知reducer请求成功结束的action
- 一种通知reducer请求失败的action
- 我们可以使用一个status字段来处理三种action
{ type: 'FETCH_POSTS' } { type: 'FETCH_POSTS' , status: 'error', error: 'Oops'} { type: 'FETCH_POSTS' , status: 'success', response: {...}}
- 但是更好的方案是定义不同的type
{ type: 'FETCH_POSTS_REQUEST' } { type: 'FETCH_POSTS_FAILURE', error: 'Oops'} { type: 'FETCH_POSTS_SUCCESS', response: { ... }}