简化dva的设计流程
2020-04-16 本文已影响0人
你的时间非常值钱
dva 首先是一个基于 redux 和 redux-saga 的数据流方案,然后为了简化开发体验,dva 还额外内置了 react-router 和 fetch,所以也可以理解为一个轻量级的应用框架
上面是dva官网对此框架的简介,使用了2年时间,让我体会到非常不错的用户体验。但我发现项目里有些代码写得太多,就像redux其中的一个缺点,模板代码太多了。
用过这框架的小伙伴都知道dva其中一个流程,model里执行副作用需要接受一个service返回的函数做参数,如:
// xxx.model.js
import { urlA } from 'service'
...
effects: {
*funA({ payload }, { call }) {
const res = yield call(urlA, payload)
// 处理...
}
}
// xxx.service.js
// config是配置接口信息的文件
import { getLangList, getAreaData, getCashVoucherList, getMsgList } from 'config'
export function getLangList () {
return request({
url: findList,
method: 'get',
})
}
export function getAreaData (data) {
return request({
url: getAreaUrl,
method: 'post',
data: data,
})
}
export function getCashVoucherList (data) {
return request({
url: cashVoucherList,
method: 'post',
data: data,
})
}
export function getMsgList (data) {
return request({
url: msgList,
method: 'post',
data,
})
}
...
不知大家写service的时候有没感觉总是在ctrl c + ctrl v,然后改url,总是在做重复的动作,总是在写request({url, method, data}),于是我打算将service生成request逻辑封装出来。根据开发经验我们的业务请求主要是两种method,get和post。我先做了一个约定,在书写配置时,带有?结尾的url为get请求,否则为post请求
// config.js是配置url的模块
module.exports = {
// ...一堆配置
preUrl: '/api', // 所有接口的共用前缀
orderCar: {
getOrderCount: '/limousineOrder/getOrderCount?', // 约定是get请求
fetchList: '/limousineOrder/findPage', // post请求
}, // 命名空间为orderCar
xxx: {
xxx1: '...'
}, // 命名空间为xxx
// ...
}
/**
* @Descripttion:
* @name: wjj
* @param {obj, obj || []} module: 模块空间名, opt: 配置
* @return: {obj} 函数对象
*/
// 缓存各命名空间下的service
const serviceMap = {}
// 简单自动生成service
export function createService (module, opt) {
// 取缓存
if(serviceMap[module]) return serviceMap[module]
const urls = config[module]
const { preUrl } = config
if (!urls) return
let funs = {}
let method = 'post'
const optString = Object.prototype.toString.call(opt)
// 根据opt配置相应生成,form下的content-type为form-data,其他暂为json
if(optString === '[object Object]') {
let contentType = Object.keys(opt)
contentType.forEach(ct => {
for(const url of opt[ct]) {
funs[url] = (data = {}) => {
if(ct.toLowerCase() === 'form') {
const formData = new FormData()
Object.keys(data).forEach(d => {
formData.append(d, data[d])
})
data = formData
}
return request({
url: `${preUrl}${urls[url]}`,
method,
data,
})
}
}
})
} else if(optString === '[object Array]') {
// opt为数组时,遍历数组生成,都是content-type为json
for(const key of opt) {
let method = 'post'
let value = urls[key]
if (value.includes('?')) {
method = 'get'
value = value.replace('?', '')
}
funs[key] = data => request({
url: `${preUrl}${value}`,
method,
data,
})
}
} else {
// 默认配置全部生成一次
for (let [key, value] of entries(urls)) {
let method = 'post'
if (value.includes('?')) {
method = 'get'
value = value.replace('?', '')
}
funs[key] = function (data) {
return request({
url: `${preUrl}${value}`,
method,
data,
})
}
}
}
return funs
}
使用方式:
1. 命名空间下全部配置生成content-type为json请求,优点简单,缺点是跑多余的生成代码(可以添加缓存)
const urls = createService('equityDining')
2. 可根据两大contentType(form,json)分别生成指定配置,但写得多代码
const { batchListPreview, batchCreate } = createService('equityDining', {
form: ['batchListPreview', 'batchCreate'],
json: ['findProjectCountPage'],
})
3. 若该空间下并指定的接口全部约定content-type是json,可简写成数组
const { sendSms, getRecord, getTempletContent } = createService('orderCar', ['sendSms', 'getRecord', 'getTempletContent'])
同事的方案
在model的副作用call的第一个参数执行一个封装好的函数,每次调用再生成相应service
// uitls.js
export function commonRequest(url, data, header) {
let method = 'post'
if (url.includes('?')) {
method = 'get'
url = url.replace('?', '')
}
return request({
url: url,
method: method,
data,
}, header)
}
// model
*delCombo ({ proload },{call, put}) {
const result = yield call(commonRequest.bind(null, delcomboUrl),proload)
// ...
},
区别
感觉有点像amd和cmd的区别
后续
手写service的生成工作(逐个手动写request)已经删减,之后我在一个新项目是这样设计的
1.将config里的url配置移到services文件下,命名为api.js,不想config承载具体url配置
2.将生成方法createService从utils里写到外围services下,即直属src下面