redux
-
什么是redux? 官方给出解释:redux 是js应用的 一种可预测的状态容器
-
Redux 本身很简单 eg:
-
1、当使用普通对象来描述的 todo应用的state
{ todos: [{ name: 'Yc header', completed: true }], visibilityFilter: 'SHOW_COMPLETED' }
-
2、这个就是简单的对象,并没有什么魔法,如果我们把他想象成是 Model,如果我要想去修改这个Model, 想要更新 state 中的数据,需要发起一个 action,而 action 就是一个普通的javascript对象如下示例:
{ type: 'ADD_TODO', text: 'Go to swimming pool' } { type: 'TOGGLE_TODO', index: 1 } { type: 'SET_VISIBILITY_FILTER', filter: 'SHOW_ALL' }
-
3、强制 action 的好处是可以清晰的知道应用发生了什么
-
4、reducer 只是一个接收 state 和 action,并返回新的 state 的函数通俗的说:为了把 action 和 state 窜起来而开发的的函数,这就是reducer
-
5、对于复杂的应用来说 reducer 不仅仅只有一个,所以我们需要写很多的小函数来分别管理state的一部分:
function visibilityFilter(state = 'SHOW_ALL', action) { if (action.type === 'SET_VISIBILITY_FILTER') { return action.filter } else { return 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.index === index ? { text: todo.text, completed: !todo.completed } : todo ) default: return state } }
-
再开发一个 reducer 调用这两个 reducer,进而来管理整个应用的 state:
function todoApp(state = {}, action) { return { todos: todos(state.todos, action), visibilityFilter: visibilityFilter(state.visibilityFilter, action) } }
-
可以得出:Reducers 指定了应用状态的变化如何响应 actions 并发送到 store,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
-
Tips:需要理解几个术语:
- Action:是把数据从应用传到 Store 的有效载荷,它是 Store 的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store ,后面结合栗子深入体验,不需要死记硬背。
- State:应用的状态
- Store:一个应用的多个State合并成一个状态树 = [Object Three] = Store
-
-
为什么要使用
-
没有使用Redux的情况,如果两个组件(非父子关系)之间需要通信的话,可能需要多个中间组件为他们进行消息传递,这样既浪费了资源,代码也会比较复杂。
-
Redux中提出了单一数据源Store 用来存储状态数据,所有的组建都可以通过Action修改Store,也可以从Store中获取最新状态。使用了redux就可以完美解决组件之间的通信问题。
-
复杂的应用复杂的状态,复杂的交互,就需要一个专门管理状态的容器,这样不仅有利于开发更利于维护,降低开发的成本。
-
-
开始使用redux
-
安装: npm install --save redux
-
附加包:
- npm install --save react-redux
- npm install --save-dev redux-devtools
-
src/index.js 简单的计数器简单实现下redux的流程
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; import { createStore } from 'redux' const store = createStore(reducer,0); // function reducer(state,action){ let {type,val} = action; switch(type){ case "INCREATE": return state+val; default: return state; } } //返回一个action对象 let increateAction = (val)=>({type:"INCREATE",val}) //为来避免许多的样板,造成杂乱,这里是actionCreator let boundDecreace = (val)=>{ store.dispatch(increateAction(val)) } // 绑定一个点击事件触发boundDecreace document.onclick = ()=>{ boundDecreace(2) } //监听dispatch store.subscribe(()=>{ let state = store.getState(); console.log(state) //输出dispatch后的内容 }) ReactDOM.render( <App/>, document.getElementById('root') ); serviceWorker.unregister();
-
redux与它的中间件 redux-thunk, redux-sags , redux-obserable
-
redux官方文档这样说的:
服务端渲染须知 异步 action 创建函数对于做服务端渲染非常方便。你可以创建一个 store,dispatch 一个异步 action 创建函数,这个 action 创建函数又 dispatch 另一个异步 action 创建函数来为应用的一整块请求数据,同时在 Promise 完成和结束时才 render 界面。然后在 render 前,store 里就已经存在了需要用的 state。 Thunk middleware 并不是 Redux 处理异步 action 的唯一方式: 你可以使用 redux-promise 或者 redux-promise-middleware 来 dispatch Promise 来替代函数。 你可以使用 redux-observable 来 dispatch Observable。 你可以使用 redux-saga 中间件来创建更加复杂的异步 action。 你可以使用 redux-pack 中间件 dispatch 基于 Promise 的异步 Action。 你甚至可以写一个自定义的 middleware 来描述 API 请求,就像这个 真实场景的案例 中的做法一样。 你也可以先尝试一些不同做法,选择喜欢的,并使用下去,不论有没有使用到 middleware 都行。
-
中间件的定义:个人理解拿后端数据的请求做栗子比较贴切,请求的开始到请求的结束,整个过程中都参与的,可以决定响应和不响应这就是中间件,中间件的作用是拦截action然后再决定什么时候到达render
redux-thunk
-
首先实现栗子一波然后在进行总结
-
步骤开始:
-
1、
npm install --save redux-thunk
-
2、'src/store/index'
import { createStore, applyMiddleware, combineReducers , compose } from 'redux'; import { routerReducer, routerMiddleware } from 'react-router-redux'; import thunk from 'redux-thunk'; import reducers from '../reducers/index'; const { createBrowserHistory } = require('history'); let history = createBrowserHistory(); const rootReducers = combineReducers({ router: routerReducer, ...reducers }); const Middleware = applyMiddleware( thunk, routerMiddleware(history) ); function configureStore() { if (process.env.NODE_ENV === 'production') { return createStore(rootReducers, compose( Middleware )); } else { return createStore(rootReducers, compose( Middleware, window.devToolsExtension ? window.devToolsExtension() : f => f )); } export { configureStore, history };
3、'src/reducers/index'
//默认的state树 const initState ={ todoList:[{ text:'Header', isShow:false },{ text:'Footer', isShow:true }], isVisibility:false } export default function reducers(state = initState,action){ let {type, val} = action; switch (type) { case "TODO_LIST": return { ...state, todoList: val }; case "IS_ViSIBILITY": return { ...state, isVisibility: val }; default: return state } }
4、src/action/index.js
export default const actions = { changeIsVisibility: (val = 0) => (dispatch, getState) => { if (getState().isVisibility !== val) { dispatch({ type: "IS_ViSIBILITY", val }); } }, changeTodoList: (val = 0) => (dispatch, getState) => { if(getState().todoList){ dispatch({ type: "TODO_LIST", val }); } } }
5、src/index.js
import 'react-app-polyfill/stable'; import 'lib-flexible'; import React from 'react'; import ReactDOM from 'react-dom'; import { Route , HashRouter } from 'react-router-dom'; //通过单向流(即历史 - >存储 - >路由器 - >组件)将路由器状态与redux存储同步。 import { configureStore , history } from './store/configureStore'; import { Provider } from 'react-redux'; import App from './App'; const store = configureStore(); ReactDOM.render( <Provider store={store}> <HashRouter history={history}> <Route component={App} exact path="/" /> </HashRouter> </Provider>, document.getElementById('root') );
6、src/app.js
import React, {Component} from 'react'; import {connect} from 'react-redux'; import {bindActionCreators} from 'redux'; import {actions} from './action/index'; import './App.css'; class App extends Component{ constructor(props){ super(props); this.state = {}; } componentWillMount(){ } componentDidMount(){ } handleTodo=()=>{ const {changeIsVisibility} = this.props; //改变了todoList的状态 changeIsVisibility(false); } render(){ const {isVisibility} = this.props; //拿到dispatch的状态 console.log(isVisibility) return ( <div className="App" > <div>{isVisibility}</div> <button onClick={this.handleTodo}>点击我发送一个dispatch</button> </div> ); } } export default connect( state=>{ return { ...state.reducers }; }, (dispatch)=>bindActionCreators({ ...actions, //...其他的actions }, dispatch) )(App);
7、于是一个redux+redux-thunk栗子大功告成了,下面是一些名词术语的理解:
-- Store 就是用来维持应用所有的 state 树 的一个对象。 改变 Store 内 state 的惟一途径是对它 dispatch 一个 action。
Store.getState() 返回应用当前的 state 树。 Store.dispatch(action) 分发 action。这是触发 state 变化的惟一途径。 Store.dispatch(action) 添加一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。 你可以在回调函数里调用 getState() 来拿到当前 state。 Store.subscribe(listener) 添加一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。你 可以在回调函数里调用 getState() 来拿到当前 state。 replaceReducer(nextReducer) 替换 store 当前用来计算 state 的 reducer, 在实现 Redux 热加载机制的时候也可能会用到
-- reducer可以根据业务逻辑情况拆分多个,但是最终使用 combineReducers 来把多个 reducer 创建成一个根 reducer。 官方示例:cn.redux.js.org/docs/api/co…
-- applyMiddleware Middleware 可以让你包装 store 的 dispatch 方法来达到你想要的目的 官方示例:cn.redux.js.org/docs/api/ap…
✈️...更多术语在官方文档中http://cn.redux.js.org/docs/api/
8、尽管redux-thunk很简单,而且也很实用,但是总有人追求更优雅的的方式来实现redux的 异步流控制,下面轮到了redux-promise闪亮登场
-
redux-promise
-
像react-thunk比较适合简单的请求场景。像操作流这种输入、输出的则Promise更适合,拿一个fetch函数来说返回的结果就是一个promise对象,下面举个栗子吧:
import { isFSA } from 'flux-standard-action'; function isPromise(val) { return val && typeof val.then === 'function'; } export default function promiseMiddleware({ dispatch }) { return next => action => { if (!isFSA(action)) { return isPromise(action) ? action.then(dispatch) : next(action); } return isPromise(action.payload) ? action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } ) : next(action); }; }
-
以上栗子所要阐述的逻辑有两部分:
- 判断flux action,是否为标准的flux action,如果不是,接着判断是否是 promise,是 promise 话就执行 action.then(dispatch),否则执行next(action).
- 如果是标准的flux action,判断payload是不是promise,如果是payload.then 获取数据,然后吧数据做为 payload 从新dispatch({ ...action, payload: result });不是promise的话就执行 next(action)
redux-saga
-
redux官方文档这样说的:你可以使用 redux-saga 中间件来创建更加复杂的异步 action。
-
我们可以理解为:redux-saga是一个管理redux应用异步操作的中间件,用于代替 redux-thunk 的。它通过创建 Sagas 将所有异步操作逻辑存放在一个地方进行集中处理,以此将react中的同步操作与异步操作区分开来,以便于后期的管理与维护。对于Saga,我们可这么认为:Saga = Worker + Watcher
//创建一个异步的action import { call, put } from 'redux-saga/effects' //saga function* loadUserOnClick(){ yield* takeLatest('LOAD_DATA',fetchUser) } function* fetchUser(action){ try{ yield put({type:'LOAD_START'}); const user = yield call(asyncRequest,action.payload); yield put({type:'LOAD_SUCESS',user}) }catch(err){ yield put({type:'LOAD_EROOR',error}) } } <button onclick={ e=>dispatch({type:'LOAD_DATA',payload:'1'}) } > load </button>
-
Saga特点如下
- Saga 的应用场景是复杂异步。
- 用 takeEvery 打印 logger,便于测试。
- 提供 takeLatest/takeEvery/throttle 方法,可以便利的实现对事件的仅关注最近实践还是关注每一次实践的时间限频。
- 提供 cancel/delay 方法,可以便利的取消或延迟异步请求。
- 提供 race(effects),[...effects] 方法来支持竞态和并行场景。
- 提供 channel 机制支持外部事件。
-
与redux-thunk相比,使用redux-saga有明显的变化:
- 在组件中,不再是dispatch(action creator),而是 dispatch(pure action)
- action经由*root saga分发
- 使用ES6 Generator愈发,简化异步代码语法 redux-saga与基于RxJS的处于用于处理异步请求的中间件
redux-observable
-
observable是基于RxJS的用于处理异步请求的中间件,借助RxJs的各种操作符和帮助方法,redux-observable也能实现对各类事件的细粒度操作,例如取消,限频、延迟请求等。
-
redux-observable的基础是Epics。Epics与Redux-Saga中的sagas相似,不同之处在于,不是等待动作调度并将动作委托给worker,而是暂停执行,直到使用yield关键字进行相同类型的另一个动作,epics分别运行并且听取一系列动作,然后在流上收到特定动作时作出反应。主要组件是ActionsObservableRedux-Observable,它扩展了ObservableRxJS。此observable表示一系列操作,每次从应用程序发送操作时,都会将其添加到流中
//在了解这个中间件之前首先要去熟悉以下Rxjs传送门:http://reactivex.io/rxjs/。 //这样依赖项注入到您的Epics中帮助测试。 //从rxjs引入网络请求ajax帮助函数 //错误处理 import { ajax } from 'rxjs/ajax'; const fetchUserEpic = action$ => action$.pipe( ofType(FETCH_USER), mergeMap(action=>ajax.getJSON(`/api/user/${action.payload}`).pipe( map(respone => fetchUserFulfilled(response)), catchError(error => of({ type:FETCH_USER_REJECTED, payload: error.xhr.response, error: true })) )) )
-
过程是ajax调用之后在mergeMap放入catchError(),当错误达到action$.pipe(),它将终止它,并且不再侦听新操作
//取消隔离ajax的请求 import {ajax} from 'rxjs/ajax' const fetchUserEpic = action$ => action$.pipe( ofType(FETCH_USER), mergeMap(action => ajax.getJSON(`/api/users/${action.payload}`).pipe( map(response => fetchUserFulfilled(response)), takeUntil(action$.pipe( ofType(FETCH_USER_CANCELLED) )) )) )
-
在ajax调用之后放入了takeUntil取消ajax请求而且不是阻断Epic的监听 (它允许多个并发FETCH_USER请求。如果您想取消任何待处理的请求,而是切换到最新的请求,则可以使用switchMap运算符)
//取消副作用而且还要发起其他不同的动作。 import { ajax } from 'rxjs/ajax'; const fetchUserEpic = action$ => action$.pipe( ofType(FETCH_USER), mergeMap(action => race( ajax.getJSON(`/api/users/${action.payload}`).pipe( map(response => fetchUserFulfilled(response)) ), action$.pipe( ofType(FETCH_USER_CANCELLED), map(() => incrementCounter()), take(1) ) )) );
-
假设有人调度时进行来AJAX调用FETCH_USER,如果有人调度了,FETCH_USER_CANCELLED 这时取消了AJAX请而且发起了增加计数器的不同操作
下面是高能的互黑模式: redux-observable官方文档提到:如果您还不熟悉RxJS,则可以考虑使用redux-thunk产生简单的副作用, 然后使用redux-observable处理复杂的内容。这样一来,您就可以保持工作效率并随时学习RxJS。redux-thunk更易于学习和使用, 但这也意味着它的功能远不如以前。当然,如果您像我们一样已经喜欢Rx,则可能会将它用于所有用途。如果您熟悉redux-saga, 则redux-observable非常相似。但是由于它使用RxJS,因此更具声明性,您可以利用和扩展现有的RxJS功能。 这一波波黑的自在!#@!%¥!@%!!!!。
-
mobox
简介:mbox和redux都是状态管理库,用来管理应用的内部状态。
优点:
- 1、mobox和redux想比,显然mbox上手更快些。
- 2、相对于redux代码量不需要很大就能实现我们的需求。
- 3、内存开销比较:在用redux的时候时不时用到对象的拓展语句和Object.assign()得到一个新的state对象,这个过程是浅拷贝 的过程,它对内存的开销肯定大于mobox直接操作对象属性要大得多。
使用注意:项目足够大的时候建议还是使用redux,要是真的很喜欢 mobox 请在 使用前对mobx状态管理指定一套规范和引入严格模式。
我们先吃个栗子:
//redux:一个counter 类
const { createStore } = require('redux')
const ADD = 'ADD'
const initState = {count: 0}
// reducer
function counter(state = initState, action) {
switch(action.type) {
case ADD:
return {...state, count: state.count + action.payload.count}
default:
return state
}
}
// actions
const AddOne = () => ({type: ADD, payload: {count:1}})
// store
const store = createStore(counter)
store.subscribe(() => console.log(store.getState()))
setInterval(() => store.dispatch(AddOne()), 1000)
不算注释,大约15行左右
下面我们来看下mobx怎么实现 conter 类的
const { observable, autorun } = require('mobx')
const appState = observable({
counter: 0,
add(value){this.counter += value}
})
autorun(() => console.log(appState.counter))
setInterval(() => appState.add(1), 1000)
计算类一下下7行
//很显然二者对比,mobx的代码量更少
mobox好用是好用但是存在一定的问题下面举个栗子:
栗如:
集中状态管理
const {observable} = require('mobx')
const appState = observable({ NUM: 0 })
appState.NUM += 1
- 直接修改状态redux不通过action是改变不来state的,这节奏,这要疯了,这要是不小心改其他同志的代码,噢~!啊!~#¥%%%噗!!!!这样,就这样的被别人按在地上锤,所以这时候要引入严格模式来协助开发
更详细的解说还在后头.....待续。