想解决什么问题?打算怎么做?
简言之:dva想提供一个基于业界react&redux最佳实践的业务框架,以解决用裸redux全家桶作为前端数据层带来的种种问题
例如:
+ src
+ sagas
- user.js
+ reducers
- user.js
+ actions
- user.js
+ service
- user.js
怎么做了?
dva
react
react-dom
dva-core
redux
redux-saga
history
react-redux
react-router-redux
他最核心的是提供了app.model方法,用于把reducer, initialState, action, saga封装到一起
const model = {
// 用作顶层state key,以及action前缀
namespace
// module级初始state
state
// 订阅其它数据源,如router change,window resize, key down/up...
subscriptions
// redux-saga里的sagas
effects
// redux里的reducer
reducers
};
dva-core实际所作的主要工作是从model配置得到reducers,worker sagas, states
后,屏蔽接下来的一系列繁琐工作:
除了core里最重要的两部分外,dva还做了一些事情:
到这里差不多封装好了,那么,下面开一些口子增加一点灵活性:
猜测整个实现过程是这样:
遵从什么思想,想要怎么样?
借鉴自elm和choo,包括elm的subscription和choo的设计理念
通过订阅一些消息来从其它数据源取数据,比如websocket connection of server, keyboard input, geolocation change, history router change等等
例如:
subscriptions: {
setupHistory ({ dispatch, history }) {
history.listen((location) => {
dispatch({
type: 'updateState',
payload: {
locationPathname: location.pathname,
locationQuery: queryString.parse(location.search),
},
})
})
},
setup ({ dispatch }) {
dispatch({ type: 'query' })
let tid
window.onresize = () => {
clearTimeout(tid)
tid = setTimeout(() => {
dispatch({ type: 'changeNavbar' })
}, 300)
}
}
}
提供这种机制来接入其它数据源,并集中到model里统一管理
choo的理念是尽量精简,尽量降低选择/切换成本:
We believe frameworks should be disposable, and components recyclable. We don’t want a web where walled gardens jealously compete with one another. By making the DOM the lowest common denominator, switching from one framework to another becomes frictionless. choo is modest in its design; we don’t believe it will be top of the class forever, so we’ve made it as easy to toss out as it is to pick up. We don’t believe that bigger is better. Big APIs, large complexities, long files – we see them as omens of impending userland complexity. We want everyone on a team, no matter the size, to fully understand how an application is laid out. And once an application is built, we want it to be small, performant and easy to reason about. All of which makes for easy to debug code, better results and super smiley faces.
大意是说框架不应该发展成堡垒,应该随时可用可不用(低成本切换),API及设计应该保持最小化,不要丢给用户一坨“知识”,这样你好他(同事)也好
P.S.当然,这段话拿到哪里都是对的,至于dva甚至choo自身有没有做到就不好说了(从choo的实现上没看出来有什么拆除堡垒的有效措施)
在API设计上,dva-core差不多保持最小化了:
不过话说回来,dva-core实际做的只把redux和redux-saga通过model配置整合起来,并增强一些控制(错误处理等),引入的唯一外来概念是subscription,还挂在model上,即便用力设计API,也复杂不到哪去
有什么缺点,带来的收益是什么?
优点:
缺点:
invariant是源码出现最多的基本套路:
function start(container) {
// 允许 container 是字符串,然后用 querySelector 找元素
if (isString(container)) {
container = document.querySelector(container);
invariant(
container,
`[app.start] container ${container} not found`,
);
} // 并且是 HTMLElement
invariant(
!container || isHTMLElement(container),
`[app.start] container should be HTMLElement`,
); // 路由必须提前注册
invariant(
app._router,
`[app.start] router must be registered before app.start()`,
); oldAppStart.call(app);
//...
}
invariant用来保证强条件(不满足条件直接throw,生产环境也throw),warning用来保证弱条件(开发环境log error并无干扰throw,生产环境不throw,换成空函数)
invariant无差别throw
可以用,但warning不建议使用,因为含warning的release代码不如编译替换干净(还会执行空函数)
另一个技巧是包一层函数,在外面做参数检查,比如示例中的:
function start(container) {
//...参数检查
oldAppStart.call(app);
}
这样做的好处是把参数检查拿出去了,可读性会更好一些,但有多一层函数调用的性能开销,而且不如if-else
控制度高(只能通过throw阻断后续流程)
先看这部分源码:
// 把每一个effect都包一遍,为了实现effect级的控制
const sagaWithOnEffect = applyOnEffect(onEffect, sagaWithCatch, model, key);
function applyOnEffect(fns, effect, model, key) {
for (const fn of fns) {
effect = fn(effect, sagaEffects, model, key);
}
return effect;
}
然后用法是这样的(传入的onEffect Hook):
function onEffect(effect, { put }, model, actionType) {
const { namespace } = model;
return function*(...args) {
yield put({ type: SHOW, payload: { namespace, actionType } });
yield effect(...args);
yield put({ type: HIDE, payload: { namespace, actionType } });
};
}
(摘自dva-loading
这不就是环绕增强(AOP里的Around Advice)吗?
围绕一个连接点的增强,如方法调用。这是最强大的一种增强类型。环绕增强可以在方法调用前后完成自定义的行为。它也负责选择是继续执行连接点,还是直接返回它们自己的返回值或者抛出异常来结束执行
(摘自AOP(Aspect-Oriented Programming))
这里的实际作用是onEffect把saga包一层,把saga的执行权交出去,允许外部(通过onEfect hook)注入逻辑。把自己交给hook,不是什么了不起的技巧,但用法上很有意思,利用iterator可展开的特性,实现了装饰者的效果(交出去一个saga,拿回来一个增强过的saga,类型没变不影响流程)