本文主要讲解redux的核心原理以及相关的周边技术原理。帮助你在使用的时候,知其因此然~javascript
涉及:redux、redux异步解决方法、中间件、react-redux原理、Dvahtml
做为状态容器,提供对状态的查询、改变
进行管理。
从具体操做
-> 状态变动
-> 触发视图更新
,这一单向数据流的控制,不只使得对状态变化的过程变得可控,同时解耦了数据M和视图V。java
1)应用复杂,将状态及页面逻辑reducer提取出来,利于代码结构的组织;
2)全局状态共享或可能被共享和改变的组件状态。react
1)web应用是一个状态机,视图和状态一一对应;
2)全部状态保存在一个对象内。web
state状态对象由store负责存储获取管理;
state为不可变对象(immutable data
),保证每次返回一个新的状态对象。json
action是改变状态对象state的惟一方式;
action由dispatch函数触发,其描述了一种行为其常见形式;redux
{ type: 'actionType', //行为 payload: {} //须要传递的信息 }
payload是更新store数据的惟一来源。并发
type Reducer<S, A> = (state: S, action: A) => S
对action作出响应的响应,返回新的状态对象state(保证可回溯的缘由),不该该产生反作用;
生成新的state有以下几种方式:app
- Object.assign({}, oldState, newState); - {...oldState, 更新值} - Immutable
javascript中的基本类型:Boolean、Number、String、Null、undefined、Symbol
等都是不可变的(Immutable
),只有Object
是可变的(Mutable
).异步
用于订阅事件,每次state变动后,都会触发其订阅的事件。
这里能够处理state -> props,以此更新视图组件的更新渲染(react-redux的connect就是这么实现的
);
//该方法用于生成action let actionCreator = (name) => ({ type: 'ADD_ITEM', payload: { name } }); //生成action dispatch(actionCreator('M2'));
1)redux-thunk
其经过扩展action,使得actionCreator返回一个function做为action。
let asyncActionCreator = postTitle => (dispatch, getState) => { dispatch(requestPosts(postTitle)); return fetch(`/some/API/${postTitle}.json`) .then(response => response.json()) .then(json => dispatch(receivePosts(postTitle, json))); }; }; //这里须要使用middleware支持异步,例如redux-thunk var thunkMiddleware = function ({ dispatch, getState }) { return function(next) { return function (action) { //若是是function,则传递dispatch、getState,并执行 return typeof action === 'function' ? //原始的dispatch action(dispatch, getState) : next(action) } } } //使用1 store.dispatch(fetchPosts('xx')); // 使用2:注意上面dispatch后返回的Promise,能够在dispatch异步数据,reducer处理后,作一些处理 store.dispatch(fetchPosts(genPromise)).then(() => console.log(store.getState()) );
thunk的应用:延迟执行、异步控制。做为Generator的next()的返回值,Thunk和Promise都是Generator自动执行的解决方案。
2)redux-sage
dispatch({type: 'USER_FETCH_REQUESTED', payload: {userId}})
redux-saga
是一个 redux 中间件;使用了 ES6 的 Generator 功能,以便于对流程作管理。
如何使用?
// 建立saga middleware const sagaMiddleware = createSagaMiddleware(); // 注入saga middleware applyMiddleware(sagaMiddleware); //启动 sagaMiddleWare.run(rootSaga);
先来看rootSaga的写法:
// worker Saga : 将在 USER_FETCH_REQUESTED action 被 dispatch 时调用 function* workerSaga(action) { try { const user = yield call(Api.fetchUser, action.payload.userId); yield put({type: "USER_FETCH_SUCCEEDED", user: user}); } catch (e) { yield put({type: "USER_FETCH_FAILED", message: e.message}); } } /* 在每一个 `USER_FETCH_REQUESTED` action 被 dispatch 时调用 fetchUser 容许并发(译注:即同时处理多个相同的 action) */ function* watcherSaga() { yield takeEvery("USER_FETCH_REQUESTED", workerSaga); } export default watcherSaga;
由上例可知,rootSaga是Generator函数,而sagaMiddleWare.run();
是其自执行器;
启动后会执行takeEvery(),看下它作了什么事:
function* takeEvery("USER_FETCH_REQUESTED", workerSaga) { //启动一个新的独立的task,执行一个构造的Generator yield fork(function* () { //注册挂起->消费注销,执行对应的saga->注册挂起... while(true) { //将当前actionType注册到发布中心,挂起,等待dispatch时在中间件中触发,匹配则执行该task的next()即workerSaga,同时注销该actionType的注册信息; yield take("USER_FETCH_REQUESTED"); //执行saga任务 yield task(workerSaga); } }); }
注意这里的task()、take()、fork()
,包括saga中call()、put()
等返回的都是effect对象,好比call()执行后:
{ isEffect: true, type: 'CALL', fn: Api.fetchUser }
task自执行器在根据next()的返回值,判断effect的类型作对应的操做,好比执行yield take('USER_FETCH_REQUESTED')
后,it.next()
返回的是:
{ type: 'take', pattern: 'USER_FETCH_REQUESTED' }
那么自执行器会判断:
//effect为take的执行逻辑 if (effect.type === 'take') { runTakeEffect(result.value, next); } //runTakeEffect将当前Generator挂起,注册到channel中心,等待唤醒 function runTakeEffect(effect, cb) { chan.take({cb, pattern:effect.pattern}); }
发布订阅中心channel的代码以下:
function channel() { let _task = null; //挂起task function take(task) { _task = task; } //dispatch时,中间件put触发对应的actionType,来匹配对应pattern的回调 function put(pattern, args) { if(!_task) return; if(pattern == _task.pattern) { taker = null; //仅消费一次,注销,等待下次循环时再注册 _task.cb.call(null, args);//唤醒挂起task;调用next()继续控制Generator执行 } } return { take, put } }
最后来看下redux-saga中间件是到channel中匹配对应action的:
const sagaMiddleware = store => { return next => action => { next(action); const { type, ...payload } = action; //调用channel的put方法,会判断是否匹配,匹配的话则执行挂起的task,即对应的saga channel.put(type, payload); } }
总结来讲:
核心仍是内部实现的Generator自执行器,对不一样的命令对象作不一样的处理逻辑;
循环过程:启动时,take挂起,等待put唤醒挂起的task调用next(),同时注销effect;让task自执行对应的gennerator函数。执行完后循环,再从新注册
发布订阅模式:chanel发布订阅中心,run执行时首先执行takeEvery,注册effect。在中间件中触发put(action)
问题:
1.为何不用Async做为自执行器呢?
redux-saga内部实现了Generator自执行器,能够自主的控制流程的暂停,运行等;
2.为何call()返回effect对象呢?
一个是根据effect类型能够作多种操做;再者这也是其比redux-thunk更利于测试的原理。
redux-sage相较于redux-thunk的优点:
Generator同步的写法,实现异步流程的控制管理;
注意:let res = yield call('xx');
, res的值为next(val)传入的参数,而不是yield后面的表达式!
Generator经过yield
和next
来传递数据来控制函数的内部流程(内外部的双向通讯
)。
洋葱圈模型,和KOA的中间件原理相似;
在action真正处理前,赋予诸如日志记录等能力。
//将中间件绑定到 let applyMiddleware = middlewares => (createStore) => (...args) => { let store = createStore(...args); let dispatch = store.dispatch; let params = { dispatch, getState: store.getState }; middlewares = middlewares.map(mw => mw(params)); //组合中间件,加强dispatch //装饰者模式 & 洋葱圈,compose对中间件的组合 dispatch = compose(middlewares)(dispatch); //返回加强后的dispatch return Object.assign(store, {dispatch}); }
基于容器组件和展现组件相分离的开发思想,将组件分为:UI组件
和容器组件
.
import { connect } from 'react-redux' const VisibleTodoList = connect( mapStateToProps, mapDispatchToProps )(TodoList) export default VisibleTodoList;
mapStateToProps:将state映射到展现组件的props;
mapDispatchToProps: 定义了组件内部须要的事件,内部注入了dispatch;
怎么获取state?
connect方法生成容器组件之后,须要让容器组件拿到state对象,才能生成 UI 组件的参数。一种方式是把它以 props 的形式传入到全部容器组件中,存在层层繁琐的传递,并且每每中间组件并不须要的问题。建议的方式是使用指定的 React Redux 组件 <Provider>
:
生产者
:<Provider>组件//使用 <Provider store={store}> <App /> </Provider> //Provider利用了React的context class Provider extends Component { getChildContext() { return { store: this.props.store }; } render() { return this.props.children; } } Provider.childContextTypes = { store: React.PropTypes.object }
消费者
:connect最后看下connect的简单实现:
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => { class Connect extends Component { static contextTypes = { store: PropTypes.object } constructor () { super(); this.state = { allProps: {} } } componentWillMount () { const { store } = this.context this._updateProps() store.subscribe(() => this._updateProps()) } _updateProps () { const { store } = this.context let stateProps = mapStateToProps ? mapStateToProps(store.getState(), this.props): {} let dispatchProps = mapDispatchToProps? mapDispatchToProps(store.dispatch, this.props) : {} this.setState({ allProps: { ...stateProps, ...dispatchProps, ...this.props } }) } render () { return <WrappedComponent {...this.state.allProps} /> } } return Connect }
Dva 是基于 React + Redux + Saga 的最佳实践沉淀, 作了 3 件很重要的事情, 大大提高了编码体验:
model
的概念, 写在一个 js 文件里面;更多技术分享,欢迎【扫码关注】~
参考:
https://zhuanlan.zhihu.com/p/...
https://zhuanlan.zhihu.com/p/...
https://blog.csdn.net/TurkeyC...
https://dvajs.com/guide/fig-s...