[js] redux
#
为什么使用redux:
1.顶层分发状态,让React组件被动地渲染。
2.监听事件,事件有权利回到所有状态顶层影响状态。
props意味着父级分发下来的属性,state意味着组件内部可以自行管理的状态,
并且整个React没有数据向上回溯的能力,也就是说数据只能单向向下分发,或者自行内部消化。
一般构建的React组件内部可能是一个完整的应用,它自己工作良好,可以通过属性作为API控制它。
但是更多的时候发现React根本无法让两个组件互相交流,使用对方的数据。
然后这时候不通过DOM沟通(也就是React体制内)解决的唯一办法就是提升state,
将state放到共有的父组件中来管理,再作为props分发回子组件。
子组件改变父组件state的办法只能是通过onClick触发父组件声明好的回调,
也就是父组件提前声明好函数或方法作为契约描述自己的state将如何变化,
再将它同样作为属性交给子组件使用。
这样就出现了一个模式:数据总是单向从顶层向下分发的,
但是只有子组件回调在概念上可以回到state顶层影响数据。这样state一定程度上是响应式的。
为了面临所有可能的扩展问题,最容易想到的办法就是把所有state集中放到所有组件顶层,然后分发给所有组件。
为了有更好的state管理,就需要一个库来作为更专业的顶层state分发给所有React应用,这就是Redux。
单向数据流优点:
1. 视图组件变得很薄,只包含了渲染逻辑和触发 action 这两个职责,即所谓 "dumb components"。
2. 要理解一个 store 可能发生的状态变化,只需要看它所注册的 actions 回调就可以。
3. 任何状态的变化都必须通过 action 触发,而 action 又必须通过 dispatcher 走,所以整个应用的每一次状态变化都会从同一个地方流过。
Redux 的核心是一个 store。
store 是一个 JavaScript 对象,通过 Redux 提供的 createStore(reducers) 方法创建。
就是把 reducer 和 action 联系到一起的对象。
store 有两个核心方法: .getState() 和 .dispatch()。
.getState() 返回一个 JavaScript 对象,代表 store 当前的状态。
.dispatch() 接受一个 action 作为参数,将这个 action 分发给所有订阅了更新的 reducer,来让它改变状态。
其他还有通过 subscribe(listener) 注册监听器。
action 的任务是描述“发生了什么事情?”
action 是一个 JavaScript 对象,通常包含了 type、payload 等字段,用于描述发生的事件及相关信息(使用 Redux 中间件可以让你 dispatch 其它类型的 action)。
reducer 的任务是根据传入的 action 对象去修改状态树。
或者简单地讲 Reducer 就是一个纯函数, 根据传入的 当前state 和 action ,返回一个新的 state :
(state, action) => newState
redux
三大原则:
单一数据源:
整个应用的 state 被储存在一棵 object tree 中,
并且这个 object tree 只存在于唯一一个 store 中。
State只读:
惟一改变 state 的方法就是触发 action,
action 是一个用于描述已发生事件的普通对象。
通过纯函数修改State:
为了描述 action 如何改变 state tree ,需要编写 reducers。
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。
基本组成:
Action:
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。
一般来说会通过 store.dispatch() 将 action 传到 store。
Action本质上是一个普通的javascript对象,用于描述发生的事情,其中type属性是约定的,一般使用常量标识发生的事情。
Action Creator
Action Creator是生成action的方法。
使用 reducers 根据 action 更新 state
combineReducers
拆分 Reducer
Store
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener) 注册监听器。
将redux和react绑定,需要一个额外的绑定库:react-redux
react-redux提供两个组件:Provider和connect
Provider在根部接收一个store属性,并传给子组件的context。
connect包裹需要使用store的组件。
这个包裹组件从context中获取store对象,并在内部维护一个state绑定到store的state tree,
然后监听store的变化,并更新state。
同时,被包裹的组件通过mapStateToProps等函数,从store中选取关心的内容。
Action 是把数据从应用传到 store 的有效载荷。
它是 store 数据的唯一来源。
一般来说你会通过 store.dispatch() 将 action 传到 store。
尽量减少在 action 中传递的数据。
Action 本质上是 JavaScript 普通对象。
按约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。
多数情况下,type 会被定义成字符串常量。
当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。
永远不要在 reducer 里做这些操作:
修改传入参数;
执行有副作用的操作,如 API 请求和路由跳转;
调用非纯函数,如 Date.now() 或 Math.random()。
注意:
不要修改 state。
在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state
Redux 应用中数据的生命周期:
调用 store.dispatch(action),可以在任何地方调用 store.dispatch(action),
包括组件中、XHR 回调中、甚至定时器中。
Redux store 调用传入的 reducer 函数。
Store 会把两个参数传入 reducer: 当前的 state 树和 action。
根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
Redux store 保存了根 reducer 返回的完整 state 树。
这个新的树就是应用的下一个 state 。
所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getState() 获得当前 state。
现在,可以应用新的 state 来更新 UI。
如果使用了 React Redux 这类的绑定库,这时就应该调用 component.setState(newState) 来更新。
view ---> action ---> reducer ---> store(state) ---> view
redux的state和react组件的state没有关系
createStore 函数必须接收一个能够修改应用状态的函数。
使用 reducer 函数修改数据(在传统的 Flux 中我们称之为 store)。
Reducer 函数是 action 的订阅者。
Reducer 函数只是一个纯函数,它接收应用程序的当前状态以及发生的 action,然后返回修改后的新状态(或者有人称之为归并后的状态)。
如何把数据变更传播到整个应用程序?
使用订阅者来监听状态的变更情况。
往 createStore 传 Reducer 的过程就是给 Redux 绑定 action 处理函数(也就是 Reducer)的过程。
store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下你会使用 react-redux 提供的 connect() 帮助器来调用。
bindActionCreators() 可以自动把多个 action 创建函数 绑定到 dispatch() 方法上。
createStore(reducer, [initialState])
创建一个 Redux store 来以存放应用中所有的 state。
应用中应有且仅有一个 store。
reducer (Function): 接收两个参数,分别是当前的 state 树和要处理的 action,返回新的 state 树。
[initialState] (any): 初始时的 state。
在同构应用中,你可以决定是否把服务端传来的 state 水合(hydrate)后传给它,或者从之前保存的用户会话中恢复一个传给它。
如果你使用 combineReducers 创建 reducer,它必须是一个普通对象,与传入的 keys 保持同样的结构。否则,你可以自由传入任何 reducer 可理解的内容。
combineReducers({ counter, todos })。
与 combineReducers({ counter: counter, todos: todos }) 一样。
#action-creator
// action creator 就是函数而已... var actionCreator = function() { // ...负责构建一个 action (是的,action creator 这个名字已经很明显了)并返回它 return { type: 'AN_ACTION' } } console.log(actionCreator()) // 输出: { type: 'AN_ACTION' }
#about-state-and-meet-redux
import { createStore } from 'redux' var store = createStore(() => {})
#reduce
var reducer = function (...args) { console.log('Reducer was called with args', args) } var store_1 = createStore(reducer) // 输出:Reducer was called with args [ undefined, { type: '@@redux/INIT' } ] // reducer 被调用了,但并没有 dispatch 任何 action... // 这是因为在初始化应用 state 的时候, // Redux dispatch 了一个初始化的 action ({ type: '@@redux/INIT' }) // 在被调用时,一个 reducer 会得到这些参数:(state, action) // 在应用初始化时,state 还没被初始化,因此它的值是 "undefined",
#
import { createStore } from 'redux' var reducer_0 = function (state, action) { console.log('reducer_0 was called with state', state, 'and action', action) } var store_0 = createStore(reducer_0) // 输出: reducer_0 was called with state undefined and action { type: '@@redux/INIT' } // 为了读取 Redux 保存的 state,可以调用 getState console.log('store_0 state after initialization:', store_0.getState()) // 输出: store_0 state after initialization: undefined
#get-state
#试着在 reducer 收到 undefined 的 state 时,给程序发一个初始状态:
var reducer_1 = function (state, action) { console.log('reducer_1 was called with state', state, 'and action', action) if (typeof state === 'undefined') { return {} } return state; } var store_1 = createStore(reducer_1) // 输出:reducer_1 was called with state undefined and action { type: '@@redux/INIT' } console.log('store_1 state after initialization:', store_1.getState()) // 输出:store_1 state after initialization: {} // 现在 Redux 初始化以后返回的 state 变成 {} 了
#初始化ES6写法
#调用 reducer ,只是为了响应一个派发来的 action 。
var reducer_2 = function (state = {}, action) { console.log('reducer_2 was called with state', state, 'and action', action) return state; } var store_2 = createStore(reducer_2) // 输出: reducer_2 was called with state {} and action { type: '@@redux/INIT' } console.log('store_2 state after initialization:', store_2.getState()) // 输出: store_2 state after initialization: {} //给 reducer_2 的 state 参数传了默认值之后, // reducer 就不会再取到 undefined 的 state 了。
#在 response 里模拟一个 state 修改,其响应的 action 类型是 'SAY_SOMETIHG'
在 reducer 里用 switch 来响应对应的 action 是常见模式
用 switch 的时候, **永远** 不要忘记放个 “default” 来返回 “state”,否则,
reducer 可能会返回 “undefined”
var reducer_3 = function (state = {}, action) { console.log('reducer_3 was called with state', state, 'and action', action) switch (action.type) { case 'SAY_SOMETHING': return { //这里可以考虑用object.assign()合并 ...state, message: action.value } default: return state; } } var store_3 = createStore(reducer_3) // 输出: reducer_3 was called with state {} and action { type: '@@redux/INIT' } console.log('store_3 state after initialization:', store_3.getState()) // 输出: store_3 state after initialization: {}
#拥有很多 action 的 reducer
var reducer_1 = function (state = {}, action) { console.log('reducer_1 was called with state', state, 'and action', action) switch (action.type) { case 'SAY_SOMETHING': return { ...state, message: action.value } case 'DO_SOMETHING': // ... case 'LEARN_SOMETHING': // ... case 'HEAR_SOMETHING': // ... case 'GO_SOMEWHERE': // ... // etc. default: return state; } }
#定义 2 个 reducer
reducer 是可以处理任何类型的数据结构的。可以选择那些符合需求的
数据结构作为 state 的值。(例如,字面量对象、数组、布尔值、字符串或其它不可变结构)
var userReducer = function (state = {}, action) { console.log('userReducer was called with state', state, 'and action', action) switch (action.type) { // etc. default: return state; } } var itemsReducer = function (state = [], action) { console.log('itemsReducer was called with state', state, 'and action', action) switch (action.type) { // etc. default: return state; } }
#combineReducers 接收一个对象并返回一个函数,当 combineReducers 被调用时,它会去调用每个
reducer,并把返回的每一块 state 重新组合进Redux 中的 Store。
import { createStore, combineReducers } from 'redux' var reducer = combineReducers({ user: userReducer, items: itemsReducer }) // 输出: // userReducer was called with state {} and action { type: '@@redux/INIT' }//这个action 是 combineReducers 实施的一次安全检查,用以确保 reducer 永远不会返回 // undefined // userReducer was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_9.r.k.r.i.c.n.m.i' } // itemsReducer was called with state [] and action { type: '@@redux/INIT' }//这个action 是 combineReducers 实施的一次安全检查,用以确保 reducer 永远不会返回 // itemsReducer was called with state [] and action { type: '@@redux/PROBE_UNKNOWN_ACTION_4.f.i.z.l.3.7.s.y.v.i' } var store_0 = createStore(reducer) // 输出: // userReducer was called with state {} and action { type: '@@redux/INIT' } // itemsReducer was called with state [] and action { type: '@@redux/INIT' } console.log('store_0 state after initialization:', store_0.getState()) // 输出: // store_0 state after initialization: { user: {}, items: [] } //Redux 正确处理了 state 的各个部分。最终的 state 完全是一个简单的对象,由 // userReducer 和 itemsReducer 返回的部分 state 共同组成。
#
"为了 dispatch 一个 action,我们需要一个 dispatch 函数。"
dispatch 函数,是 Redux 提供的,并且它会将 action 传递给任何一个 reducer,
dispatch 函数本质上是 Redux 的实例的属性 "dispatch"
#dispatch 一个 action:
store_0.dispatch({ type: 'AN_ACTION' }) // 输出: // userReducer was called with state {} and action { type: 'AN_ACTION' } // itemsReducer was called with state [] and action { type: 'AN_ACTION' } // 每一个 reducer 都被调用了,但是没有一个 action type 是 reducer 需要的, // 因此 state 是不会发生变化的 console.log('store_0 state after action AN_ACTION:', store_0.getState()) // 输出:store_0 state after action AN_ACTION: { user: {}, items: [] }
#使用 action creator 发送一个我们想要的 action
var setNameActionCreator = function (name) { return { type: 'SET_NAME', name: name } } store_0.dispatch(setNameActionCreator('bob')) // 输出: // userReducer was called with state {} and action { type: 'SET_NAME', name: 'bob' } // itemsReducer was called with state [] and action { type: 'SET_NAME', name: 'bob' } console.log('store_0 state after action SET_NAME:', store_0.getState()) // 输出: // store_0 state after action SET_NAME: { user: { name: 'bob' }, items: [] }
#希望达到异步效果
按照目前调用 action creator 的方式
import { createStore, combineReducers } from 'redux' var reducer = combineReducers({ speaker: function (state = {}, action) { console.log('speaker was called with state', state, 'and action', action) switch (action.type) { case 'SAY': return { ...state, message: action.message } default: return state; } } }) var store_0 = createStore(reducer) var sayActionCreator = function (message) { return { type: 'SAY', message } } console.log("\n", 'Running our normal action creator:', "\n") console.log(new Date()); store_0.dispatch(sayActionCreator('Hi')) console.log(new Date()); console.log('store_0 state after action SAY:', store_0.getState()) // 输出(忽略初始输出): // Sun Aug 02 2015 01:03:05 GMT+0200 (CEST) // speaker was called with state {} and action { type: 'SAY', message: 'Hi' } // Sun Aug 02 2015 01:03:05 GMT+0200 (CEST) // store_0 state after action SAY: { speaker: { message: 'Hi' } } // ... 结果 store 被立即更新了。
#试试这种方式
var asyncSayActionCreator_0 = function (message) { setTimeout(function () { return { type: 'SAY', message } }, 2000) } // 但是这样 action creator 返回的不是 action 而是 undefined。
#再试试不返回 action,而是返回 function。
这个 function 会在合适的时机 dispatch action。但是如果我们希望
这个 function 能够 dispatch action,那么就需要向它传入 dispatch 函数。
var asyncSayActionCreator_1 = function (message) { return function (dispatch) { setTimeout(function () { dispatch({ type: 'SAY', message }) }, 2000) } } // action creator 返回的不是 action 而是 function。 // 所以 reducer 函数很可能不知道如何处理这样的返回值 // 输出: // ... // /Users/classtar/Codes/redux-tutorial/node_modules/redux/node_modules/invariant/invariant.js:51 // throw error; // ^ // Error: Invariant Violation: Actions must be plain objects. Use custom middleware for async actions. // 提示我们使用middleware
#A ---> middleware 1 ---> middleware 2 ---> middleware 3 --> ... ---> B
action ---> dispatcher ---> middleware 1 ---> middleware 2 ---> reducers
在 Redux 中,中间件是纯粹的函数,
有明确的使用方法并且严格的遵循以下格式:
var anyMiddleware = function ({ dispatch, getState }) { return function(next) { return function (action) { // 你的中间件业务相关代码 } } }
中间件由三个嵌套的函数构成(会依次调用):
1) 第一层向其余两层提供分发函数和 getState 函数
(因为你的中间件或 action creator 可能需要从 state 中读取数据)
2) 第二层提供 next 函数,它允许你显式的将处理过的输入传递给下一个中间件或 Redux
(这样 Redux 才能调用所有 reducer)。
3) 第三层提供从上一个中间件或从 dispatch 传递来的 action,
这个 action 可以调用下一个中间件(让 action 继续流动) 或者
以想要的方式处理 action。
#使用柯里化,简化上述函数
// "curry" may come any functional programming library (lodash, ramda, etc.) var thunkMiddleware = curry( ({dispatch, getState}, next, action) => ( // 你的中间件业务相关代码 ) );
#为异步 action creator 提供的中间件叫 thunk middleware
它看上去是这样,ES5写法:
var thunkMiddleware = function ({ dispatch, getState }) { // console.log('Enter thunkMiddleware'); return function(next) { // console.log('Function "next" provided:', next); return function (action) { // console.log('Handling action:', action); return typeof action === 'function' ? action(dispatch, getState) : next(action) } } }
#
辅助函数:applyMiddleware.
applyMiddleware 接收所有中间件作为参数,返回一个供 Redux createStore 调用的函数。
当最后这个函数被调用时,它会产生一个 Store 增强器,用来将所有中间件应用到 Store 的 dispatch 上。
如何将一个中间件应用到 Redux store:
import { createStore, combineReducers, applyMiddleware } from 'redux' const finalCreateStore = applyMiddleware(thunkMiddleware)(createStore) // 针对多个中间件, 使用:applyMiddleware(middleware1, middleware2, ...)(createStore) var reducer = combineReducers({ speaker: function (state = {}, action) { console.log('speaker was called with state', state, 'and action', action) switch (action.type) { case 'SAY': return { ...state, message: action.message } default: return state } } }) const store_0 = finalCreateStore(reducer) // 输出: // speaker was called with state {} and action { type: '@@redux/INIT' } // speaker was called with state {} and action { type: '@@redux/PROBE_UNKNOWN_ACTION_s.b.4.z.a.x.a.j.o.r' } // speaker was called with state {} and action { type: '@@redux/INIT' } // 现在 store 的 middleware 已经准备好了,再来尝试分发异步 action: var asyncSayActionCreator_1 = function (message) { return function (dispatch) { setTimeout(function () { console.log(new Date(), 'Dispatch action now:') dispatch({ type: 'SAY', message }) }, 2000) } } console.log("\n", new Date(), 'Running our async action creator:', "\n") store_0.dispatch(asyncSayActionCreator_1('Hi')) // 输出: // Mon Aug 03 2015 00:01:20 GMT+0200 (CEST) Running our async action creator: // Mon Aug 03 2015 00:01:22 GMT+0200 (CEST) 'Dispatch action now:' // speaker was called with state {} and action { type: 'SAY', message: 'Hi' } // 调用异步 action creator 两秒之后,action 成功被分发出去。
#监视 Redux store 更新有一个很简单的办法
store.subscribe(function() { // retrieve latest store state here // Ex: console.log(store.getState()); })
#试一下
import { createStore, combineReducers } from 'redux' var itemsReducer = function (state = [], action) { console.log('itemsReducer was called with state', state, 'and action', action) switch (action.type) { case 'ADD_ITEM': return [ ...state, action.item ] default: return state; } } var reducer = combineReducers({ items: itemsReducer }) var store_0 = createStore(reducer) store_0.subscribe(function() { console.log('store_0 has been updated. Latest store state:', store_0.getState()); // 在这里更新视图 }) var addItemActionCreator = function (item) { return { type: 'ADD_ITEM', item: item } } store_0.dispatch(addItemActionCreator({ id: 1234, description: 'anything' })) // 输出: // ... // store_0 has been updated. Latest store state: { items: [ { id: 1234, description: 'anything' } ] }
#