前言
前文传送地址:
连续四篇设计模式都是前端经常使用到的,相信大家参考博文中的项目实战之后再去琢磨自己的项目代码、或者看一些优秀的开源代码对比后,能更深刻的体会到设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结这句话的含义。
(小声BB,某人的埋点博客终于快到尾声了)
在开发的过程中,同一种功能采用不同的或者组合的设计模式实现,可以将代码质量提升。这里要 copy 第一篇博文的话重申一下为什么前端需要了解设计模式
在日常开发中大部分前端都在开发的中,进行组件、方法等封装、提炼的时候或多或少已经使用了一些设计模式的理念, 但是由于对设计模式的概念模糊,理解不够,从而导致设计整体架构的时候,会有各种局限性,拓展性、可读性、维护性变差,不得不多次重构甚至重写。在 ts 在前端开发中加速推进的同时,合理的设计模式使得项目从架构、设计、迭代、维护都有一定质量的保障。
接下来我们通过使用设计模式中的工厂、代理模式来继续改造我们的 fetch 工程
封装 fetch 业务部分
业务层普通封装
业务层的封装,我们在上一篇的文末已经提到过,这边再结合代码展示一下
一般来说我们的工程会有多个模块,这边我们先根据各个模块封装一层 service 层,方便我们业务侧调用。
import Fetch from './util/fetch';
const prefix = 'https://api.github.com/users'
const fetch = new Fetch({ requestType: "JSON", cacheType: 'local', BASE_URL: prefix });
const getUser = (params) => {
return new Promise((resolve, reject) => {
fetch.get({
url: '/octocat',
params
}).then(response => {
const { data, code, errMessage } = response
if (code) {
resolve(data)
} else {
reject(errMessage)
}
})
})
}
const setUser = (params) => {
return new Promise((resolve, reject) => {
fetch.post({
url: '/octocat',
params
}).then(response => {
const { data, code, errMessage } = response
if (code) {
resolve(data)
} else {
reject(errMessage)
}
})
})
}
export {
getUser,
setUser
};
export default {
getUser,
setUser
}
如上我们封装了一个用户 service 层,一般来说,除了正常的 http 的请求状态异常之外,会有业务处理的异常。所以在 service 层调用的时候,可以预先处理掉错误的异常,返回给业务侧正常的数据,业务侧在调用的时候,可以直接使用 try/catch 去承接数据。同时在多个业务侧都需要调用相同的接口的时候,可以在用户 service 层处理、过滤一些后台返回的参数,这样可以使得业务侧调用到方便前端展示的数据(比如组装列表数据,日期、金额格式化等)。
但是当业务过多,都要处理统一的业务错误的时候,会显得非常麻烦,造成冗余代码跟维护困难,所以在这之上,我们可以在针对 service 层再做一层业务报错封装。
import Fetch from '../util/fetch';
const prefix = 'https://api.github.com'
const fetch = new Fetch({ requestType: "JSON", cacheType: 'local', BASE_URL: prefix });
const get = (url, params) => {
return new Promise((resolve, reject) => {
fetch.get({
url,
params
}).then(response => {
const { data, code, errMessage } = response
if (code) {
resolve(data)
} else {
reject(errMessage)
}
})
})
}
export {
get
};
export default {
get
}
如上可以将业务层的统一处理跟单独的业务接口数据处理分开,用户的 service 层改造如下
import { get, post } from './baseFetch';
const prefix = 'users'
const getUser = async (params) => {
try {
const data = get({
url: `${prefix}/octocat`,
params
})
return data
}
}
const setUser = (params) => {
try {
const data = post({
url: `${prefix}/octocat`,
params
})
return data
}
}
export {
getUser,
setUser
};
export default {
getUser,
setUser
}
业务层 restful 格式封装
有些后台接口是按照 restful 风格封装的接口,如果还是按照上面的封装,会显得比较累赘,我们可以如下封装一下
import baseFetch from './baseFetch';
const prefix = 'users'
const methods = ['get', 'post', 'put', 'delete']
const userUrl = {
users: 'octocats',
user: 'octocat'
}
const user = {}
Object.keys(userUrl).forEach(key => {
user[key] = {}
methods.forEach(method => {
user[key][method] = (params) => {
return baseFetch[method](`${prefix}/${userUrl[key]}`, params)
}
})
})
export {
user
};
export default {
user
}
user.user.get({ test: 1 }) // 业务侧调用
user.user.post({ test: 1 }) // 业务侧调用
如上我们将 url 跟请求方法组装起来,业务侧调用会简便很多,但是中间的业务层数据处理似乎就没了,我们可以将 userUrl 拓展成 userObj,把数据处理方法也放入对象里面,改造如下
const userObj = {
users: {
url: 'octocats',
get(data) { return data }
},
user: {
url: 'octocats',
post(data) { return data }
}
}
const user = {}
Object.keys(userObj).forEach(key => {
user[key] = {}
methods.forEach(method => {
user[key][method] = (params) => {
return new Promise((resolve) => {
baseFetch[method](`${prefix}/${userObj[key].url}`, params).then(data => {
if (userObj[key][method]) resolve(userObj[key][method](data))
resolve(data)
})
})
}
})
})
尾声
完整的 demo 地址:项目实战 demo,喜欢的朋友可以 star 一下,后续会根据设计模式博文的推出,逐步的将此项目继续拓展出来。
网友评论