1,Redux javascript
单一对象,单一store状态树的形式存储数据。css
多个reducer来编辑action 经过action对象修改 store,共同维护一个根store。html
redux就是纯函数,纯函数,纯函数,重要的事情说三遍。java
纯函数做为reducer也就是action返回新的state,更新state,这个是约定。node
中间件的嵌套要保证最后一个中间件是返回的state对象,而后用这些函数处理并返回对象。react
2,Redux适应的场景git
单页应用过于复杂,须要管理state,es6
包括服务区响应、本地数据、待提交数据,UI状态,路由状态,标签状态等。github
若是单纯用state开发复杂度会增大。。npm
在加上 更新调优、服务端渲染、路由跳转前调用接口等等。。复杂。。
复杂主要的两个缘由是:变化和异步,须要redux优化一下。
redux的三大原则试图让state的变化可预测!
redux实践举例
import { createStore } from 'redux'; /* * 这是一个 reducer,形式为 (state, action) => state 的纯函数。 * 描述了 action 如何把 state 转变成下一个 state。 */ function counter (state=0, action) { switch (action.type) { case 'INCREMENT': return state + 1; default: return state; } } // 建立store数据存储对象,提供3个api: getState subScribe dispatch let store = createStore(counter); // 手动监听 订阅更新,绑定在视图层 store.subscribe(() => { console.log(store.getState()); }); // 改变内部state 的惟一方法是触发dispatch一个action // action 能够被序列化,或者存储下来方便追溯 store.dispatch({ type: 'INCREMENT' }); // 要作的修改变成了一个对象,这个对象就是action // 写函数编辑这个action,获得想要的store,这个函数就是reducer ! // 只有 一个store 能够有多个reducer,想react有一个root根组件和其余子组件 export default store;
3,核心概念
举例,可能的state对象是这样,一个对象,包含一个对象数组的数据。
{ todos: [{ text: 'Eat food', completed: true }, { text: 'Exercise', completed: false }], visibilityFilter: 'SHOW_COMPLETED' }
规定:发起action来修改state值,有了规定就。
action就是普通的js对象,举例说明:
{ type: 'ADD_TODO', text: 'Go to swimming pool' } { type: 'TOGGLE_TODO', index: 1 } { type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
必定有一个type,而后还有对应的值
用 reducer函数联系 action 和 state,并返回新是state。
这样的 reducer 通常可能有多个。
// 过滤,传参是 state action function visibilityFilter(state = 'SHOW_ALL', action) { if (action.type === 'SET_VISIBILITY_FILTER') { return action.filter; } else { return state; } } // 能够修改state并返回 // action上获取条件,改 state并返回 function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return state.concat([{ text: action.text, completed: false }]); case 'TOGGLE_TODO': return state.map((todo, index) => // 用action中的条件,改state action.index === index ? { text: todo.text, completed: !todo.completed } : todo ) default: return state; } }
上面这两个就是 reducer。
再开发一个reducer,像下面这样:
function todoApp(state = {}, action) { return { todos: todos(state.todos, action), visibilityFilter: visibilityFilter(state.visibilityFilter, action) }; }
实际上是一个函数,返回的是对象,包含了以前声明的reducer。
4,3大原则 (继续重复这3大原则)
4.1 单一的数据源
整个应用的state保持在一个 object tree 中,而且整个 object tree 只存在于一个 store 中。(这样同构应用开发会容易?为啥?)。
来自客户端的数据能够很容易的序列化到客户端。加快开发速度。
单一的state tree ,像撤销和重作的操做更容易
4.2 State是只读的
只能经过触发 action 的方式改变state值。
这样视图和网络都不能直接修改state,全部修改都被集中处理,且按照one by one 的顺序执行。
所以不会出现 race condition竞争条件。
action是普通对象,全部能够被打印、序列化、存储、后期调试或测试时放出来。
store.dispatch({ type: 'COMPLETE_TODO', index: 1 }) store.dispatch({ type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_COMPLETED' })
4.3 使用纯函数进行修改
reducer 是一些纯函数,它接收以前的state和action,随着应用变大会有不少 reducer。能够这样写。
import { combineReducers, createStore } from 'redux' let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer)
5,先前技术
先不急猥琐发育。对比其余框架。
看下redux以前的数据管理技术方案。
Flux
redux的灵感来源Flux的几个特性。redux规定将模型的更新集中于一个特定的层。
redux和flux都不容许直接修改数据。
(state, action) => state ,这点两个框架同样。
支持用纯函数,若是是不纯的函数,时间旅行、记录/回放或热加载不可实现。
Elm
是一种编程语言,受到Haskell语言启发,遵循 (state, action) => state, 是 modal-view-update 的架构。
Elm 语言具备更好的 执行纯度、静态类型、不可变性、action、匹配模式等方面更具优点。
Immutable
这是一个不可变数据结构的库文件。看过react不可变数据的都知道。
和 redux对接的也很好
和backbone不一样,backbone是可变数据
Baobab
Baobab是另外一个优秀的库,实现了数据的不可变特性的api,用于更新纯js对象但方式和redux不一样
Rx
Reactive Extensions 一种重写的现代化方案,管理复杂异步应用。
Rx 提供构建将人机交互转变为 相互依赖可观测变量的库。
专家建议能够同时使用它和redux库。
组合获得一个新的action,在提交给 store.dispatch ()
扩展阅览
redux开发工具-> https://github.com/zalmoxisus/redux-devtools-extension/releases
redux项目引入开发工具 -> https://github.com/zalmoxisus/redux-devtools-extension#usage
redux 源码阅读 -> https://cdn.bootcss.com/redux/4.0.4/redux.js
6,生态系统
redux 生态系统体小精湛,还有很与之相匹配的库,https://github.com/xgrommx/awesome-redux
包括:中间件、工具库、样板示例、代码魔板等。https://www.redux.org.cn/docs/introduction/Ecosystem.html
而后不少示例,暂时不看。
7,基础
7.1 Action
const action = { type: ADD_TODO, text: 'Build my first Redux app', index: 1, filter: SHOW_COMPLETED } import { ADD_TODO, REMOVE_TODO, SHOW_COMPLETED } from '../actionTypes' // 不是超大文件能够不用这样写。
7.2 Reducer
指定了状态如何发生变化和响应的store的,action表示有什么事发生,reducer是具体操做。
处理reducer关系时,注意应该尽可能state范式化,不存在嵌套,放在一个对象,不一样实体或列表用id引用。
(previousState, action) => newState
---------------------------
永远不要在reducer函数中作这些事:
a, 修改传参。
b, 执行反作用操做。
c, 调用非纯函数,random() now() 等。
--------------------------------
特别纯:只要传入参数相同,返回计算获得的下一个 state 就必定相同。没有特殊状况、没有反作用,没有 API 请求、没有变量修改,单纯执行计算。
const todoApp = combineReducers({ visibilityFilter, todos }); const reducer = combineReducers({ a: doSomethingWithA, b: processB, c: c }); function reducer(state = {}, action) { return { a: doSomethingWithA(state.a, action), b: processB(state.b, action), c: c(state.c, action) } };
es6简介写法。
import { combineReducers } from 'redux' import * as reducers from './reducers' const todoApp = combineReducers(reducers)
8,Store
提供:
unsubscribe() //中止监听更新
9,实现容器组件
把展现组件和redux 关联起来,原理上就是 store.subscribe 从 redux.state 中读取数据做为props。
而后渲染展现组件。
connect方法作了性能优化,相似componentShouldUpdate。
须要先定义 mapStateToProps 这个函数来指定如何把state值映射到组件的props。
这个函数返回props须要的对象。
const mapStateToProps = state => { return { todos: getVisibleTodos(state.todos, state.visibilityFilter) } }
还能够分发action,定义 mapDispatchToProps方法返回 dispatch 传给展现组件props的回调方法。
传入一个dispatch 并返回一个有事件的对象。
const mapDispatchToProps = dispatch => { return { onTodoClick: id => { dispatch(toggleTodo(id)) } } }
如今就有了展现组件须要的 props和执行函数,还须要一步把这些集成到组件上。
import { connect } from 'react-redux'; import { TodoList } from './TodoList.jsx'; const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList); export default VisibleTodoList;
这样就能够和react结合使用。将视图和逻辑混合使用。
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,去更新state模拟提交数据。 dispatch(addTodo(input.value)) input.value = '' }} > <input ref={node => { input = node }} /> <button type="submit"> Add Todo </button> </form> </div> ) } // connect() 返回一个函数,函数的传参绑定了store 的api。 AddTodo = connect()(AddTodo) export default AddTodo
用react-redux 的 Provider 对根组件封装,传入store
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' // 建立一个store let store = createStore(todoApp) render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
----------------------------------------------
高级部分
1,异步action
当调用异步action时,须要注意两个注意的时间点:发起和响应。通常要dispatch三种action
这个过程可使用这种啰嗦的方式定义:
不一样的 status,可使用https://github.com/redux-utilities/redux-actions 辅助库返回reducer。
{ type: 'FETCH_POSTS' } { type: 'FETCH_POSTS', status: 'error', error: 'Oops' } { type: 'FETCH_POSTS', status: 'success', response: {
异步action就是在异步完成或结束以后执行dispatch。
redux结合使用的异步请求库通常用 cross_fetch。一点都很差用,还要引入补丁 babel-polyfill。
2,中间件
引入中间件的store能够支持异步流,好比redux-promise,redux-thunk能够异步处理action
须要注意的是,applyMiddleware 加强createStore中,拦截promise,最后一个middleware必须是返回对象。
在action发起后,到达reducer以前的扩展点。
中间件能够记录日志,建立报告,异步接口或路由等。
import thunkMiddleware from 'redux-thunk' import { createLogger } from 'redux-logger' import { createStore, applyMiddleware } from 'redux' import { selectSubreddit, fetchPosts } from './actions' import rootReducer from './reducers' const loggerMiddleware = createLogger() const store = createStore( rootReducer, applyMiddleware( thunkMiddleware, // 容许咱们 dispatch() 函数 loggerMiddleware // 一个很便捷的 middleware,用来打印 action 日志 ) )
3,语法尽可能符合es6规范
4,服务端渲染
两个部分:__INITIAL_STATE__ 和 renderToString。
// 引入服务端渲染接口 import { renderToString } from 'react-dom/server' function handleRender(req, res) { // 建立Redux store 实例 const store = createStore(counterApp); // 把组件渲染 HTML 字符串 const html = renderToString( <Provider store={store}> <App /> </Provider> ) // 从 store 中得到初始 state const preloadedState = store.getState(); // 把渲染后的页面内容发送给客户端 res.send(renderFullPage(html, preloadedState)); } 客户端能够经过 window.__INITIAL_STATE_ 获取state值。 function renderFullPage(html, preloadedState) { return ` <!doctype html> <html> <head> <title>Redux Universal Example</title> </head> <body> <div id="root">${html}</div> <script> window.__INITIAL_STATE__ = ${JSON.stringify(preloadedState)} </script> <script src="/static/bundle.js"></script> </body> </html> ` }
5,jest 编写测试
由于redux代码大部分都是纯函数,纯函数比较好测。
和其余jest mocha 测试同样,须要编写测试用例,断言查看结果。
import configureMockStore from 'redux-mock-store' import thunk from 'redux-thunk' import * as actions from '../../actions/TodoActions' import * as types from '../../constants/ActionTypes' import nock from 'nock' import mocha from 'mocha' // 你可使用任何测试库 const middlewares = [ thunk ] const mockStore = configureMockStore(middlewares) describe('async actions', () => { afterEach(() => { nock.cleanAll() }) it('建立对 action: FETCH_TODOS_SUCCESS 是否会完成 fetch 的判断', () => { nock('http://example.com/') .get('/todos') .reply(200, { body: { todos: ['do something'] }}) const expectedActions = [ { type: types.FETCH_TODOS_REQUEST }, { type: types.FETCH_TODOS_SUCCESS, body: { todos: ['do something'] } } ] const store = mockStore({ todos: [] }) return store.dispatch(actions.fetchTodos()) .then(() => { // 异步 actions 的返回 expect(store.getActions()).toEqual(expectedActions) }) }) })
6,实现数据撤销和可追溯
指望的数据结构
{ past: [0,1,2,3], preset: 4, feature: [5,6] }
引入插件 redux-undo 保留数据支持撤销
npm install --save redux-undo
使用undoable()
import undoable, { distinctState } from 'redux-undo' const todos = (state = [], action) => { } const undoableTodos = undoable(todos, { filter: distinctState() }); export default undoableTodos;