关于 Dva 源码解析,官网已有相关指南 Dva 源码解析。html
而本篇文章与其不一样的是:深刻解析 dva-core 源码,和探讨 dva-core 的相关技术及应用场景。前端
好了正文开始!react
大概翻一下 dva 的项目文件目录,能够知道 dva 是使用 lerna 进行多包管理的项目。主要分为如下四个包,以下所示:git
~/Desktop/dva $ tree ./packages/ -L 1
./packages/
├── dva
├── dva-core
├── dva-immer
└── dva-loading
4 directories, 0 files
复制代码
dva 使用 react-redux 实现了 view 层。github
dva-core 基于 redux 和 redux-saga 处理 model 层,好比包括了 state 管理、数据的异步加载、订阅-发布模式。web
dva-immer 依赖 immer 来优雅处理不可变状态。(备注:若是想在项目中轻量引入不可变状态管理的话,能够考虑 immer.js)编程
dva-loading 实现了自动处理 loading 状态。redux
dva-immer 和 dva-loading 其实都是做为 dva 的核心插件存在的。以下图方式注册插件便可:api
const { create, saga } = require("dva-core");
const createImmerPlugin = require("dva-immer");
const createLoadingPlugin = require("dva-loading");
const app = create();
app.use(createImmerPlugin());
app.use(createLoadingPlugin());
复制代码
我的仍是很喜欢 immer 的,能够用可变操做的方式生产不可变的值。dva-immer 的确值得推荐。数组
不过,实际上两个插件都是寥寥几行代码,而咱们应该学习的是 dva 的插件注册机制。
固然,dva 核心依赖了 dva-core。本文的重点也在于此。
dva-core 因为集成了 redux 和 redux-saga。那么在对于应用的状态管理和反作用管理这两种场景应该具有强大的能力。
dva-core 包只导出了三个主要的 API: create、saga、utils。咱们大致上能够忽略后面两者。将注意力集中在 create 上。
首先,使用 create 建立一个最简单的 app 对象。
const { create } = require("dva-core");
const app = create();
app.start();
复制代码
打印 app 对象以下:
{
// 能够认为私有属性
_models: [ { namespace: '@@dva', state: 0, reducers: [Object] } ],
_store:
{ dispatch: [Function],
subscribe: [Function: subscribe],
getState: [Function: getState],
replaceReducer: [Function: replaceReducer],
runSaga: [Function: bound runSaga],
asyncReducers: {},
[Symbol(observable)]: [Function: observable] },
_plugin:
Plugin {
_handleActions: null,
hooks:
{ onError: [],
onStateChange: [],
onAction: [],
onHmr: [],
onReducer: [],
onEffect: [],
extraReducers: [],
extraEnhancers: [],
_handleActions: [] } },
_getSaga: [Function: bound getSaga],
// 能够认为公有属性,即真正暴露给第三方的 api
start: [Function: start],
use: [Function: bound use],
model: [Function: bound injectModel],
unmodel: [Function: bound unmodel],
replaceModel: [Function: bound replaceModel]
};
复制代码
咱们忽略如下划线开头对象,由于语义上通常咱们认为是私有属性。因而咱们关注其余属性能够发现,存在如下几个 api:start、use、model、unmodel、replaceModel。
精简 dva/packages/dva-core/src/index.js
中的代码发现,的确如此。
// dva/packages/dva-core/src/index.js
// ...
export function create(hooksAndOpts = {}, createOpts = {}) {
// ...
const plugin = new Plugin();
// 挂载了use、model、start方法
const app = {
use: plugin.use.bind(plugin),
model,
start
};
return app;
function model(m) {
// ...
const prefixedModel = prefixNamespace({ ...m });
app._models.push(prefixedModel);
return prefixedModel;
}
function injectModel(createReducer, onError, unlisteners, m) {}
function unmodel(createReducer, reducers, unlisteners, namespace) {}
function replaceModel(createReducer, reducers, unlisteners, onError, m) {}
function start() {
// ...
// 更新挂载 model、unmodel、replaceModel
app.model = injectModel.bind(app, createReducer, onError, unlisteners);
app.unmodel = unmodel.bind(app, createReducer, reducers, unlisteners);
app.replaceModel = replaceModel.bind(app, createReducer, reducers, unlisteners, onError);
// ...
}
}
复制代码
这里咱们注意到 app.model 方法实际上在 start()里经过 injectModel.bind(app, createReducer, onError, unlisteners)
柯里化后更新了。
由于在一开始设置的 model 方法,只是简单更新了 app._models 列表。而 injectModel 才处理 model 相关的逻辑。
而 unmodel、replaceModel 方法在 app.start()以前都不存在。而是在 start() 里对内部 unmodel、replaceModel 方法进行柯里化返回到的新方法。
由此大概整理了 create 以后的几个 api 的来源。
顾名思义,就是注册 dva model,以及相反操做取消注册。
model 函数,本质上就是将从新定义命名空间的 model 推入内部的 model 列表。
// dva/packages/dva-core/src/index.js
function model(m) {
const prefixedModel = prefixNamespace({ ...m });
app._models.push(prefixedModel);
return prefixedModel;
}
复制代码
prefixNamespace 函数实际上将 model 上用户定义的 reducers 和 effects 的 key 映射在此 model 的命名空间下。好比如下 model:
app.model({
namespace: "users",
state: ["foo"],
reducers: {
add(state, { payload }) {
return [...state, payload];
}
},
effects: {
*fetch(_, { put }) {
yield delay(200);
yield put({ type: "add", payload: "{data}" });
}
}
});
复制代码
里面的 model 在通过 prefixNamespace 以后:
{
namespace: 'users',
state: [ 'foo' ],
reducers: { 'users/add': [Function: add] },
effects: { 'users/fetch': [GeneratorFunction: fetch] }
}
复制代码
能够看到,users/add 和 users/fetch 都是修改后的新 key。
所以,通过 prefixNamespace 的全部的 model 的 reducers 和 effects 在最后汇总成一个对象的时候,也能够错落有致地根据 namespace 进行归类。
固然,这只是新增 model 和从新映射 key 而已。咱们前面提到,model 函数其实是 injectModel 柯里化后的产生的函数。所以咱们有必要在 injectModel 函数里看到底干了什么事情、
// dva/packages/dva-core/src/index.js
function injectModel(createReducer, onError, unlisteners, m) {
// 推入model到内部model列表
m = model(m);
const store = app._store;
// 将namespace下的model全部reducers函数compose成为一个reducer
store.asyncReducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions);
store.replaceReducer(createReducer());
if (m.effects) {
store.runSaga(app._getSaga(m.effects, m, onError, plugin.get("onEffect"), hooksAndOpts));
}
if (m.subscriptions) {
unlisteners[m.namespace] = runSubscription(m.subscriptions, m, app, onError);
}
}
复制代码
咱们首先来看store.asyncReducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions);
这一句,里面的的确确干了不少事情。来,咱们深刻分析。
// dva/packages/dva-core/src/getReducer.js
import defaultHandleActions from "./handleActions";
export default function getReducer(reducers, state, handleActions) {
// Support reducer enhancer
// e.g. reducers: [realReducers, enhancer]
if (Array.isArray(reducers)) {
return reducers[1]((handleActions || defaultHandleActions)(reducers[0], state));
} else {
return (handleActions || defaultHandleActions)(reducers || {}, state);
}
}
复制代码
这里的 isArray 分支判断条件是由于 dva 中的 reducer 支持数组形式传入 enhancer。什么意思呢?请看如下示例代码:
app.model({
namespace: "char",
state: "sulirc",
reducers: [
{
timestamp(state, { payload }) {
return state + (payload || Date.now());
}
},
function enhancer(reducer) {
return (state, action) => {
if (action.type === "char/timestamp") {
return reducer(`[${state.toUpperCase()}]@`, action);
}
return reducer(state, action);
};
}
]
});
复制代码
留意咱们的 reducers 变成了数组。因此当咱们 dispatch 任意一个 action 的时候,都会通过 enhancer 先处理。
app._store.dispatch({ type: "char/timestamp" });
console.log(app._store.getState());
// => { '@@dva': 0, char: '[SULIRC]@1575169473563' }
复制代码
若是想搞懂以上 reducer 的 compose、enhancer 的实现,咱们先来看这一句:
(handleActions || defaultHandleActions)(reducers || {}, state);
复制代码
handlerActions 其实就是入口 getReducer 中传入 plugin._handleActions。本质上依赖外界传入。好比 dva-immer 这个 plugin 就是实现了_handleActions 这个钩子。
而咱们来看 defaultHandleActions,也便是如下代码的 handleActions。
// dva/packages/dva-core/src/handleActions.js
// 能够想象为一个大型的 switch case 语句,type匹配的时候才使用对应的reducer
function handleAction(actionType, reducer = identify) {
return (state, action) => {
const { type } = action;
// 将闭包中的 actionType 和动态传入的 type 进行匹配判断
if (actionType === type) {
return reducer(state, action);
}
return state;
};
}
// 好比 [reducer1, reducer2] 通过 reduceReducers 处理后变成:
// (state, action) => reducer2(reducer1(state, action), action)
function reduceReducers(...reducers) {
return (previous, current) => reducers.reduce((p, r) => r(p, current), previous);
}
function handleActions(handlers, defaultState) {
// 将全部reducer从新映射为有type类型判断的reducer。
const reducers = Object.keys(handlers).map(type => handleAction(type, handlers[type]));
// compose成为一个大的reducer
const reducer = reduceReducers(...reducers);
// 这里只是经过返回一个高阶函数作了 state = defaultState 默认操做
return (state = defaultState, action) => reducer(state, action);
}
export default handleActions;
复制代码
想要理解上面这一段代码,仍是须要熟悉闭包和高阶函数,以及函数组合等概念的。由于做者利用了闭包和高阶函数所要达成的目的,就是让使用 dva 的人写 reducer 时能够更加便利,不用写一个巨大的 switch case 语句。
因而咱们能够知道store.asyncReducers[m.namespace]
即得到了一个将 model 命名空间下的全部 reducers 进行 compose(组合)后的巨大 reducer。
而后 store.replaceReducer(createReducer());
store.replaceReducer 实际上是 redux 提供的 api。
官方代码注释中写到:
Replaces the reducer currently used by the store to calculate the state. You might need this if your app implements code splitting and you want to load some of the reducers dynamically. You might also need this if you implement a hot reloading mechanism for Redux.
同窗们,上述文字的意思很明显,若是开发者有 code splitting 或者 动态加载 reducers 的需求,那须要这个 api 来进行热重载。
结合 dva-core 的代码来看,asyncReducers 就是动态加载 reducers。具体理解就是说在 app.start()以后的 app.model()中的 reducers 就会被划分在 asyncReducers 里面。所以也就须要热重载。由于在 redux 里,reducers 实际上是文件里的一个对象,在初始化的 createStore 的时候就肯定了。而在 dva-core 中,app.start()时即进行了 createStore 操做,因此须要 replaceReducer 来指示 redux,替换更新 reducers 对象。
那么咱们看 createReducer 函数,其实就是将如下四者的一个有机整合:
代码以下:
// dva/packages/dva-core/src/index.js
function createReducer() {
// reducerEnhancer => plugin.get('onReducer')
return reducerEnhancer(
combineReducers({
...reducers,
// extraReducers => plugin.get('extraReducers');
...extraReducers,
...(app._store ? app._store.asyncReducers : {})
})
);
}
复制代码
在 injectModel 里最后两句判断语句,其实就是在注册 effects 和 subscriptions。
if (m.effects) {
// 注册 effects
store.runSaga(app._getSaga(m.effects, m, onError, plugin.get("onEffect"), hooksAndOpts));
}
if (m.subscriptions) {
// 注册 subscriptions。
unlisteners[m.namespace] = runSubscription(m.subscriptions, m, app, onError);
}
复制代码
runSaga 其实就是 sagaMiddleware.run。经过 app._getSaga 一样封装返回了一个巨大的 saga。具体逻辑下文会深刻描述。
runSubscription 将全部写在 model 的里的 subscriptions 运行,并将每一个 subscription 返回的取消订阅事件的函数,收集后返回出去。具体逻辑下文一样会深刻描述。
接下来,咱们看 unmodel 干了什么事情。若是你们理解了上面的 model 函数。下面的 unmodel 函数天然不难理解。
// dva/packages/dva-core/src/index.js
function unmodel(createReducer, reducers, unlisteners, namespace) {
const store = app._store;
// 删除reducers
delete store.asyncReducers[namespace];
delete reducers[namespace];
store.replaceReducer(createReducer());
store.dispatch({ type: "@@dva/UPDATE" });
// 经过分发一个内部事件,取消反作用
store.dispatch({ type: `${namespace}/@@CANCEL_EFFECTS` });
// 取消监听这个命名空间的全部订阅
unlistenSubscription(unlisteners, namespace);
// 在app的内部models列表里删除此model
app._models = app._models.filter(model => model.namespace !== namespace);
}
复制代码
从语义上来看,unmodel 是取消注册一个 model,如何作到干净的移除这个 model 呢?分如下几步:
${namespace}/@@CANCEL_EFFECTS
来通知取消 effects 反作用。能够在 app.start()以后替换或新增已有 model。逻辑大体与 model、unmodel 相似。同窗们能够参照上述两者理解。
// dva/packages/dva-core/src/index.js
function replaceModel(createReducer, reducers, unlisteners, onError, m) {
const store = app._store;
const { namespace } = m;
const oldModelIdx = findIndex(app._models, model => model.namespace === namespace);
if (~oldModelIdx) {
// 经过分发一个内部事件,取消反作用
store.dispatch({ type: `${namespace}/@@CANCEL_EFFECTS` });
// 删除reducers
delete store.asyncReducers[namespace];
delete reducers[namespace];
// 取消监听这个命名空间以前的全部订阅
unlistenSubscription(unlisteners, namespace);
// 在app的内部models列表里删除此model
app._models.splice(oldModelIdx, 1);
}
// 直接更新此model
app.model(m);
store.dispatch({ type: "@@dva/UPDATE" });
}
复制代码
执行订阅函数以下:
// dva/packages/dva-core/src/subscription.js
export function run(subs, model, app, onError) {
const funcs = [];
const nonFuncs = [];
for (const key in subs) {
// 只执行用户编写的订阅函数
if (Object.prototype.hasOwnProperty.call(subs, key)) {
const sub = subs[key];
const unlistener = sub(
{
dispatch: prefixedDispatch(app._store.dispatch, model),
history: app._history
},
onError
);
// 指望返回的是取消订阅函数
if (isFunction(unlistener)) {
funcs.push(unlistener);
} else {
nonFuncs.push(key);
}
}
}
return { funcs, nonFuncs };
}
复制代码
从 sub 的调用来看,传参格式为 ({ dispatch, history }, onError) => unlistenFunction。
dva 倡导你们写订阅函数的时候必定要返回取消订阅函数,就好比存在 addEventListener 就必定得存在 removeEventListener。这才是良好的开发习惯。一样在 React 的 useEffect 钩子里,也是如此。
若是开发者不返回取消订阅函数,在会被归类到 nonFuncs 里。在 unmodel、replaceModel 的时候则会喜提 dva 送你的大大的 warning。
// dva/packages/dva-core/src/subscription.js
export function unlisten(unlisteners, namespace) {
if (!unlisteners[namespace]) return;
const { funcs, nonFuncs } = unlisteners[namespace];
// dva不想看到你不写取消订阅函数,并绝不客气地向你丢了一个警告。
warning(
nonFuncs.length === 0,
`[app.unmodel] subscription should return unlistener function, check these subscriptions ${nonFuncs.join( ", " )}`
);
// 遍历执行取消订阅的函数
for (const unlistener of funcs) {
unlistener();
}
delete unlisteners[namespace];
}
复制代码
reducers 是惟一能够修改 state 的地方。由 action 触发。
dva-core 所处理的部分上文有解释过,大致上来讲就是将 reducers 的从新映射到对应命名空间。同时收集动态、静态、附加 reducers、以及经过 enhancer 加强处理。剩余的就是 redux 的工做了。
enhancer 的来源有 enhancers = [applyMiddleware(...middlewares), ...extraEnhancers]
。其中 extraEnhancers 经过 plugin.get('extraEnhancers') 获取。applyMiddleware(...middlewares)则是将里面全部中间件在 redux 中注入{ getState, dispatch }的 api 后,再组合(compose)全部中间件以后返回出去。
redux 中间件的约定格式:
const reduxMiddleware = store => next => action => {
// ...
next(action);
// => dispatch(action)
};
复制代码
其中 next 就是注入的 dispatch。
本质上 applyMiddleware 所作的工做是将 store.dispatch 经过注入的中间件不断加强,一层套一层(能够必定程度上参考 koa 中的中间件),最终返回的就是基本上是{ getState, dispatch }
,可是里面的 dispatch 倒是一个 compose 了全部中间件的 dispatch 了。
这一整套逻辑里面运用了大量的高阶函数、函数组合的技巧,的确让人头晕,建议你们能够自行整理一下。
帮助理解方式,结合函数堆栈的压栈和弹栈,这套中间件感受就像是在打乒乓球同样,来回来回,的确十分有趣。
// dva/packages/dva-core/src/createStore.js
// ...
// applyMiddleware这套逻辑得去redux里看。上文有描述。
const enhancers = [applyMiddleware(...middlewares), ...extraEnhancers];
// 再一次的compose enhancers,而后createStore也是去redux里看。
return createStore(reducers, initialState, composeEnhancers(...enhancers));
复制代码
能够看到,不管是 redux 仍是 dva。compose,也便是 reduce 都用到飞起。这里面涉及到了函数式编程里的函数组合概念,的确是高级、简洁又有用。
通过了这么多弯弯绕绕,带来的效果就是,dispatch 操做在走到 reducers 以前,必然得先通过层层中间件,最后才是最初的那个 dispatch 发挥它的做用。
effects 用于处理异步操做和业务逻辑,不直接修改 state。由 action 触发,能够触发 action。
关于 effects,实际上是 dva 依赖 redux-saga 实现对异步操做、业务逻辑的一种管理,对其在内部进行了一次封装,暴露更加便利的 api。
在 start 初始化,以及在 model、repaceModel 的时候都有 runSaga(便是 sagaMiddleware.run)方法对指定 model 下全部 effects 进行一次。
// dva/packages/dva-core/src/index.js
function injectModel() {
// ...
if (m.effects) {
// 对该 model 的 effects 注册 saga 并运行
store.runSaga(app._getSaga(m.effects, m, onError, plugin.get("onEffect"), hooksAndOpts));
}
// ...
}
function start() {
const sagas = [];
const reducers = { ...initialReducer };
for (const m of app._models) {
reducers[m.namespace] = getReducer(m.reducers, m.state, plugin._handleActions);
if (m.effects) {
// 对全部 model 经过 effects 注册saga
sagas.push(app._getSaga(m.effects, m, onError, plugin.get("onEffect"), hooksAndOpts));
}
}
// 运行全部 sagas
sagas.forEach(sagaMiddleware.run);
}
复制代码
而 app._getSaga 又是什么?本质上是将指定 model 下的全部 effect 集合生成一个大型的 saga,内部为一个根据 type 类型判断返回不一样 saga 处理函数的 switch case 语句。好比有 watcher、takeEvery、takeLatest、throttle、poll 等 type 类型。
type 类型由 create(hooksAndOpts, createOpts)中的 hooksAndOpts 传入,不一样的 type 类型返回不一样的 saga 处理逻辑。type 默认为 takeEvery。
那么做为 dva 处理 effects 的入口,实现逻辑以下,贴上 getSaga 代码和注释:
// dva/packages/dva-core/src/getSaga.js
export default function getSaga(effects, model, onError, onEffect, opts = {}) {
// 返回了一个集合 saga
return function*() {
for (const key in effects) {
if (Object.prototype.hasOwnProperty.call(effects, key)) {
const watcher = getWatcher(key, effects[key], model, onError, onEffect, opts);
// fork 是非阻塞调用
const task = yield sagaEffects.fork(watcher);
// 提供了一种取消反作用的方式
yield sagaEffects.fork(function*() {
// 等待取消该effect的信号
yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`);
// 执行取消
yield sagaEffects.cancel(task);
});
}
}
};
}
复制代码
基本上不一样 type,都会走如下这一套 getWatcher 函数里的 sagaWithCatch 这个逻辑。
// dva/packages/dva-core/src/getSaga.js
function* sagaWithCatch(...args) {
// createPromiseMiddleware中会用到的 __dva_resolve 和 __dva_reject
const { __dva_resolve: resolve = noop, __dva_reject: reject = noop } =
args.length > 0 ? args[0] : {};
try {
// 发起saga的开始信号
yield sagaEffects.put({ type: `${key}${NAMESPACE_SEP}@@start` });
// 执行 effect这个 generator
const ret = yield effect(...args.concat(createEffects(model, opts)));
// 发起saga的结束信号
yield sagaEffects.put({ type: `${key}${NAMESPACE_SEP}@@end` });
resolve(ret);
} catch (e) {
// onError钩子触发
onError(e, {
key,
effectArgs: args
});
if (!e._dontReject) {
reject(e);
}
}
}
复制代码
能够看到 __dva_resolve 和 __dva_reject 实际上是在 createPromiseMiddleware 这个中间件里抛出的。可是决定 resolve 和 reject 的权力在 saga 里。
// dva/packages/dva-core/src/createPromiseMiddleware.js
export default function createPromiseMiddleware(app) {
return () => next => action => {
const { type } = action;
if (isEffect(type)) {
return new Promise((resolve, reject) => {
next({
__dva_resolve: resolve,
__dva_reject: reject,
...action
});
});
} else {
return next(action);
}
};
// ...
}
复制代码
所以咱们能够手动实验, app._store.dispatch 一个 reducer 的时候,返回的是一个 action。可是 app._store.dispatch 一个 effect 的时候,却返回了一个 Promise,就是由于 createPromiseMiddleware 这个中间件的做用。
同时也会每一个 effect 初始化的时候也会触发 plugin.get('onEffect')
钩子。
具体 effect 的处理逻辑其实还可深挖,但笔者对 redux-saga 并非很熟,所以关于 saga 的介绍到此为止。
关于 dva 中的钩子是由 Plugin 进行触发的。有以下这么多钩子:
// dva/packages/dva-core/src/Plugin.js
const hooks = [
"onError",
"onStateChange",
"onAction",
"onHmr",
"onReducer",
"onEffect",
"extraReducers",
"extraEnhancers",
"_handleActions"
];
复制代码
dva-core 特地实现了一个 Plugin 类进行钩子的管理。
在 constructor 里经过 reduce 方式将上述钩子数组初始化为如下的形式:
{
"onError": [],
"onStateChange": [],
"onAction": [],
"onHmr": [],
"onReducer": [],
"onEffect": [],
"extraReducers": [],
"extraEnhancers": [],
"_handleActions": []
}
复制代码
经过 plugin.use 方式往对应钩子 key 里更新数组,经过 plugin.apply、plugin.get 获取并调用。本质上并不复杂。
不过,这里提一句,关于插件 Plugin 的使用,在前端的各类库里的确是层出不穷。好比 Webpack、Rollup 等都大量使用。笔者也偏心这种方式,它将内在的核心代码与外在注入的代码共同运行,但又从设计上,隔离了稳定与不稳定。同时甚至能够动态注册、插拔。
plugin 思想值得学习和实践。
因为 dva-core 依赖了 redux 和 redux-saga。在介绍 dva-core 以前,仍是先简单熟悉一下二者的基本概念。(redux 是 dva-core 的 peerDependencies)
依赖方向:
dva <- dva-core <- redux-saga & redux
复制代码
redux的介绍,官网已经很详尽。
首先,redux 是一个很轻量的可预测的状态管理库。API 也只有 createStore、store.subscribe、 store.dispatch、 store.getState。简单易于理解。
createStore(reducer)建立单一状态树,同时配置一个函数签名为 (state, action) => state 的纯函数 reducer,用于描述不一样的 action 应如何操做更新 state。 reducer 须要保持两个原则,第一是保持纯函数特性,第二是保持不可变数据,不修改上一次的 state,每次计算后返回新的 state。
那么 store.subscribe 和 store.dispatch 分别为状态树的订阅和事件分发。而 store.getState 能够获取当前的状态树快照。
所以使用的基本概念上能够理解为,将全部应用的状态 state 保存在单一的状态树 store 里,经过纯净的 reducer 控制如何更新状态,分发拥有具体描述和动做参数的 action 事件以应用对应的 reducer。而后通过 redux 处理后最终得以更新状态树,同时通知全部订阅者。
若是说 redux 是能够处理纯函数状况的话,那么 redux-saga 则是对应用反作用的一种加强管理工具。redux-saga 是 redux 的中间件。
笔者在半年之前,提起 redux 这些框架总会不自觉的与 react 相关联,事实上真的这些框架脱离了 react 难道就没法使用了么?以及如何扩展使用这些框架达成咱们的应用需求呢?
然后来笔者因项目缘由不进行 web 应用开发,第一次尝试开发业务底层相关 SDK 的时候,意识到虽然在使用 redux 上 react 是主要场景。可是 redux 做为状态管理库,做用域理应更大。那么基于 redux 衍生的 redux-saga、甚至于 dva(也就是今日的主题),那也能够脱离 web 应用开发场景进行理解与使用。
dva-core 实际上是脱离了视图限制的一个类库(dva 依赖于 dva-core 实现了 model 层的管理)
那么请同窗们细想一下,dva-core 其实相比 dva 扩展性更强,同时使用场景更广。好比,若是是构建一个 SDK,纯粹利用 dva-core 管理状态和反作用,也何尝不可。
同时 dva-core 封装了对于 redux 和 redux-saga 的一种更友好的使用方式。基于理解了 dva-core 的状况下,能够经过钩子加强、或者甚至本身 fork 下来进行加强。
所以,能够基于 dva-core 封装出适用于各类框架的状态管理方案。
做为一个底层类库,dva-core 是比上层类库 dva 更具备通用价值的。
同窗们能够尽情发挥想象~
文章最后仍是啰嗦的提了一下 redux 和 redux-saga。虽然本文的确在解析 dva 中的 dva-core 的代码,可是若是想要真正熟悉dva-core,前两者仍是要稍微熟悉一下的。
关于 dva-core 的代码,笔者也是在业余时间断断续续研读了一两周,源代码解析类文章也是第一次尝试,不必定讲的清楚,可是以后确定会愈来愈好。
另外关于读 dva-core 源代码的一点点建议:
此次的文章超乎想象的长,若是我发现有错别字或者不稳当的地方,会持续修改。
谢谢你们。