We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dva 初探
前言: 最近正在学习 dva ,整理出一些学习笔记,笔者默认阅读此文的读者有一定的react , redux , redux-saga 基础,如果没有,可先自行了解这些技术,本文不再赘述。
dva是基于现有应用框架(redux+react-router+redux-saga等)封装的一个框架(不是库),基本上没有引入新概念,也没有创建新语法,对于熟悉前言中涉及的技术栈的童鞋来说会非常容易上手。详细介绍可移步dva介绍
redux
react-router
redux-saga
在处理复杂异步请求的业务中,一开始我们是使用 redux-thunk + async/await 结合使用,比如在异步登录的逻辑中,使用 redux-thunk 处理如下:
// action/auth.js import request from 'axios'; import { loadUserData } from './user'; export const login = (user, pass) => async (dispatch) => { try { dispatch({ type: LOGIN_REQUEST }); let { data } = await request.post('/login', { user, pass }); await dispatch(loadUserData(data.uid)); dispatch({ type: LOGIN_SUCCESS, data }); } catch(error) { dispatch({ type: LOGIN_ERROR, error }); } }
这种处理之后,组件调用的是dispatch(action creator),此时的 action 被赋予了太多的逻辑功能,不再是一个 pure action 。为了保持 action 的简洁性,继而引入 redux-saga ,它提供了一个 saga 文件用来存放异步逻辑,引入 redux-saga 之后,上面的验证用户登录逻辑就变成如下:
dispatch(action creator)
// sagas/index.js import { take, call, put } from 'redux-saga/effects' import Api from '...' export function* login(user, pass) { try { const data = yield call(Api.authorize, user, pass) yield put({type: 'LOGIN_SUCCESS', data.uid}) } catch(error) { yield put({type: 'LOGIN_ERROR', error}) } }
使用 redux-saga 之后,action 又回归其纯粹性。并且将异步操作全部抽离在 sagas 中一层进行处理,这样方便我们进行多种异步处理操作。 redux-saga 虽然在处理较为复杂的异步逻辑时提供了比较好的解决方案,但是当业务变复杂时,随着模块的逐渐增加,由于项目通常要分 reducer, action, saga, component 等等,所以项目中的文件个数也会变得很多,如下:
+ src + actions - user.js - detail.js + reducers - user.js - detail.js + sagas - user.js - detail.js + components
这样在项目开发过程中,就需要不断地切换文件目录,大大影响开发效率。于是 dva 应运而生,dva 的主要解决的项目开发中的痛点:
上面的例子使用 dva 来实现如下:
// models/login.js import Api from '...' export default { namespace: 'login', state: { user: null }, effects: { *login(){ const data = yield call(Api.authorize, user, pass) yield put({type: 'LOGIN_SUCCESS', data.uid}) }, }, reducers: {} }
其中,reducers 可以看成是同步的请求逻辑,effects 可以看成是异步的请求逻辑,所有的逻辑都放在了 models 目录下的文件中,省去了文件之间的切换成本,让开发人员可以专注于业务逻辑。 具体可以参考支付宝前端应用架构的发展和选择
dva中只有5个 API,8个新的概念,其中所有的 API 如下:
app = dva(Opts)
app.use(Hooks)
app.model(ModelObject)
app.router(Function)
app.start([HTMLElement], opts)
具体的使用可以移步这里
8个概念如下所示:
{ type: String, payload: Any?, error? Error, }
调用的时候有如下两种方式:
dispatch(Action);
dispatch({ type: 'todos/add', payload: 'todo content' });
如何基于 dva 开发一个项目,dva 的作者给出了一个一步步开发 dva 项目的教程, 笔者仿照该教程,并且基于 dva2.0, 做出了一个 demo,该 demo 类似于 dva中的范例,只是初步体验一下 dva 的开发。
借用描述 dva 数据流动的一张图,如下所示:
如图所示:用户在浏览器中访问某个 URL,由此渲染一个页面,该页面可能包含多个 Components, 当用户在页面进行操作的时候,由此 dispatch 某个 action,同步的 action 逻辑放在 Reducer 中,异步的 action 逻辑存放在 Effect 中。通过 model 中的数据处理,将新的 state 传入页面中,从而触发页面数据的更新。
这次的解读主要是针对 [email protected] 和 [email protected]。
首先是 dva 中的入口文件所暴露出来的方法,主要是const app = dva();这行代码的作用,返回一个 app实例。该方法如下:
const app = dva();
export default function (opts = {}) { const history = opts.history || createHashHistory(); //history默认是HashHistory const createOpts = { initialReducer: { routing, }, setupMiddlewares(middlewares) { return [ routerMiddleware(history), ...middlewares, ]; }, setupApp(app) { app._history = patchHistory(history); }, }; const app = core.create(opts, createOpts); const oldAppStart = app.start; app.router = router; app.start = start; return app; } // 此处略去一些方法的定义
这个函数很简单,主要是调用了 dva-core 里面的 create 方法,并且返回了一个包含如下方法的 app 对像:
var app = { _models: [(0, _prefixNamespace2.default)((0, _extends3.default)({}, dvaModel))], _store: null, _plugin: plugin, use: plugin.use.bind(plugin), model: model, start: start };
对 app 的初始化定义在 dva-core/lib/index.js 文件中。在这个文件中,实现了 app 对象的所有方法。接下来一个一个进行分析:
dva-core/lib/index.js
这个方法比较简单,只是将传进来的 model push 进 _models 这个属性中。这就意味着每次我们注册 model 时,只能单个进行传递,不能以数组的形式进行传递,例如:
app.model(Model1); app.model(Model2); //而不是 app.model([Model1,Model2])
其实 app.model 在调用 app.start 之后会变成 injectModel(), 它的源码如下:
injectModel()
function injectModel(createReducer, onError, unlisteners, m) { model(m); var store = app._store; if (m.reducers) { store.asyncReducers[m.namespace] = (0, _getReducer2.default)(m.reducers, m.state); store.replaceReducer(createReducer(store.asyncReducers)); } if (m.effects) { store.runSaga(app._getSaga(m.effects, m, onError, plugin.get('onEffect'))); } if (m.subscriptions) { unlisteners[m.namespace] = (0, _subscription.run)(m.subscriptions, m, app, onError); } }
这个函数里面调用了上面的 model() ,除此之外,该函数还将 model 定义的 reducers,effects, subscriptions 进行分别处理。
sagaMiddleware.run()
app._getSaga()
export default function getSaga(resolve, reject, effects, model, onError, onEffect) { return function *() { for (const key in effects) { if (Object.prototype.hasOwnProperty.call(effects, key)) { const watcher = getWatcher(resolve, reject, key, effects[key], model, onError, onEffect); const task = yield sagaEffects.fork(watcher); yield sagaEffects.fork(function *() { yield sagaEffects.take(`${model.namespace}/@@CANCEL_EFFECTS`); yield sagaEffects.cancel(task); }); } } }; }
这个方法主要实现了 saga 那一套的watch/worker(监听->执行) 的工作形式。其中该函数传入的 resolve,reject 是 createPromiseMiddleware.js 这个文件生成的。之所以有这个文件,主要是提供一种机制,提供给某些需要 effect 返回 resolve, reject 等方法的场景。
watch/worker
这个函数用来启动整个应用, 其中 dva 中的 start() 主要是根据传入的 container 容器来渲染页面,核心代码如下:
if (container) { render(container, store, app, app._router); app._plugin.apply('onHmr')(render.bind(null, container, store, app)); } else { return getProvider(store, this, this._router); }
如果传入的参数是 DomElement 或者 DomQueryString,那么直接启动应用,渲染页面,否则就返回一个 <Provider /> (React Component)。 除此之外,dva-core 中的 start(), 则是将 dva 的所涉及的一些概念全部整合到一个 store 的对象中,并执行一些赋值操作,具体源码移步这里
<Provider /> (React Component)
这是 dva-core 中唯一暴露的一个函数,里面包含了上面介绍的三个函数,并且还夹杂了一些其他的逻辑。比如插件的使用,关于插件,它的主要逻辑是放在了 dva-core/Plugin.js 这个文件里面,这个文件提供了一个插件管理类,提供了 apply(), get(), use() 成员方法,这个类主要对钩子函数进行了一些处理,并且限制了钩子函数的几个可选项:
const hooks = [ 'onError', 'onStateChange', 'onAction', 'onHmr', 'onReducer', 'onEffect', 'extraReducers', 'extraEnhancers', ];
关于 hooks 的概念,可以移步这里进行查阅
以上只是笔者在这几天的学习中总结的一些技术要点,由于时间比较仓促,所以有些地方可能总结得有点问题,如有错误,欢迎指正~
The text was updated successfully, but these errors were encountered:
No branches or pull requests
dva 初探
什么是 dva
dva是基于现有应用框架(
redux
+react-router
+redux-saga
等)封装的一个框架(不是库),基本上没有引入新概念,也没有创建新语法,对于熟悉前言中涉及的技术栈的童鞋来说会非常容易上手。详细介绍可移步dva介绍为什么会有 dva
在处理复杂异步请求的业务中,一开始我们是使用 redux-thunk + async/await 结合使用,比如在异步登录的逻辑中,使用 redux-thunk 处理如下:
这种处理之后,组件调用的是
dispatch(action creator)
,此时的 action 被赋予了太多的逻辑功能,不再是一个 pure action 。为了保持 action 的简洁性,继而引入 redux-saga ,它提供了一个 saga 文件用来存放异步逻辑,引入 redux-saga 之后,上面的验证用户登录逻辑就变成如下:使用 redux-saga 之后,action 又回归其纯粹性。并且将异步操作全部抽离在 sagas 中一层进行处理,这样方便我们进行多种异步处理操作。
redux-saga 虽然在处理较为复杂的异步逻辑时提供了比较好的解决方案,但是当业务变复杂时,随着模块的逐渐增加,由于项目通常要分 reducer, action, saga, component 等等,所以项目中的文件个数也会变得很多,如下:
这样在项目开发过程中,就需要不断地切换文件目录,大大影响开发效率。于是 dva 应运而生,dva 的主要解决的项目开发中的痛点:
上面的例子使用 dva 来实现如下:
其中,reducers 可以看成是同步的请求逻辑,effects 可以看成是异步的请求逻辑,所有的逻辑都放在了 models 目录下的文件中,省去了文件之间的切换成本,让开发人员可以专注于业务逻辑。
具体可以参考支付宝前端应用架构的发展和选择
dva 的相关知识点
dva中只有5个 API,8个新的概念,其中所有的 API 如下:
app = dva(Opts)
创建应用,返回 dva 实例app.use(Hooks)
配置 hooks 或者注册插件app.model(ModelObject)
注册 modelapp.router(Function)
注册路由表app.start([HTMLElement], opts)
启动应用具体的使用可以移步这里
8个概念如下所示:
调用的时候有如下两种方式:
dispatch(Action);
dispatch({ type: 'todos/add', payload: 'todo content' });
dva 的使用
如何基于 dva 开发一个项目,dva 的作者给出了一个一步步开发 dva 项目的教程, 笔者仿照该教程,并且基于 dva2.0, 做出了一个 demo,该 demo 类似于 dva中的范例,只是初步体验一下 dva 的开发。
深入 dva
借用描述 dva 数据流动的一张图,如下所示:
如图所示:用户在浏览器中访问某个 URL,由此渲染一个页面,该页面可能包含多个 Components, 当用户在页面进行操作的时候,由此 dispatch 某个 action,同步的 action 逻辑放在 Reducer 中,异步的 action 逻辑存放在 Effect 中。通过 model 中的数据处理,将新的 state 传入页面中,从而触发页面数据的更新。
dva 源码解读
这次的解读主要是针对 [email protected] 和 [email protected]。
首先是 dva 中的入口文件所暴露出来的方法,主要是
const app = dva();
这行代码的作用,返回一个 app实例。该方法如下:这个函数很简单,主要是调用了 dva-core 里面的 create 方法,并且返回了一个包含如下方法的 app 对像:
对 app 的初始化定义在
dva-core/lib/index.js
文件中。在这个文件中,实现了 app 对象的所有方法。接下来一个一个进行分析:model()
这个方法比较简单,只是将传进来的 model push 进 _models 这个属性中。这就意味着每次我们注册 model 时,只能单个进行传递,不能以数组的形式进行传递,例如:
injectModel()
其实 app.model 在调用 app.start 之后会变成
injectModel()
, 它的源码如下:这个函数里面调用了上面的 model() ,除此之外,该函数还将 model 定义的 reducers,effects, subscriptions 进行分别处理。
sagaMiddleware.run()
来执行管理一部 action,在这之前,先调用了app._getSaga()
方法:这个方法主要实现了 saga 那一套的
watch/worker
(监听->执行) 的工作形式。其中该函数传入的 resolve,reject 是 createPromiseMiddleware.js 这个文件生成的。之所以有这个文件,主要是提供一种机制,提供给某些需要 effect 返回 resolve, reject 等方法的场景。start()
这个函数用来启动整个应用, 其中 dva 中的 start() 主要是根据传入的 container 容器来渲染页面,核心代码如下:
如果传入的参数是 DomElement 或者 DomQueryString,那么直接启动应用,渲染页面,否则就返回一个
<Provider /> (React Component)
。除此之外,dva-core 中的 start(), 则是将 dva 的所涉及的一些概念全部整合到一个 store 的对象中,并执行一些赋值操作,具体源码移步这里
create()
这是 dva-core 中唯一暴露的一个函数,里面包含了上面介绍的三个函数,并且还夹杂了一些其他的逻辑。比如插件的使用,关于插件,它的主要逻辑是放在了 dva-core/Plugin.js 这个文件里面,这个文件提供了一个插件管理类,提供了 apply(), get(), use() 成员方法,这个类主要对钩子函数进行了一些处理,并且限制了钩子函数的几个可选项:
关于 hooks 的概念,可以移步这里进行查阅
总结
以上只是笔者在这几天的学习中总结的一些技术要点,由于时间比较仓促,所以有些地方可能总结得有点问题,如有错误,欢迎指正~
参考资料
The text was updated successfully, but these errors were encountered: