记在实战项目中对于请求模块的无敌封装

821 阅读4分钟

这是我参与更文挑战的第3天,活动详情查看: 更文挑战

困扰

遇到这个项目时,查看老代码发现很多页面上的请求方法非常复杂。

  1. 获取浏览器缓存中的路由对象,轮询找到需要请求的别名路由对象。
  2. 通过函数传入路由参数,并返回需要请求的url
  3. 接着使用函数传入urlbody参数。
  4. 最后获取到服务器返回的值。
//第一步传入路由参数,获取到url
const url = getApiUrl("user.edit.name", {
        user_id: this.user_id
})
//第二步创建body参数
const httpObj = {name: 'Kev1nzh'}
//第三步结合url和参数,请求返回结果。
const result = http.get(url, httpObj).subscribe(res => {
    ...do something
})

这样的做法其实非常正确,一步步单独获取正确的数据,但是我们能不能优化一下呢,最好是一行代码就可以解决上述的代码。

分析

浏览器缓存中存在一个路由对象。如下代码表述:

路由对象下的子元素以aliaskey路由数据value


routeObj: {
	base: {
		'user.task': [
			{
				alias: 'user.task',
				method: 'get',
				module_name: 'base',
				url: 'api/base/user/task'
			}
		],
		'user.edit.name': [
			{
				alias: 'user.edit.name',
				method: 'get',
				module_name: 'base',
				url: 'api/base/user/edit/name/{user_id}'
			}
		],
	},
	login: {
		....
	},
	.....
}

思考了优化的逻辑, 如果想要以最少的代码来请求并保证功能完整,应该如下。

async editUserName(name: string, user_id: sumber) {
    //user_id为路由参数,name为body参数。
    const result = await fetch('user.edit.name', {user_id, name}) 
    ...do something
}

传入参数分为alias路由别名和httpObj不管传入的是路由参数还是body参数都可以放在一个对象内。

所以我们的处理逻辑:

  1. 从浏览器缓存中获取路由对象,根据alias路由别名获取路由对象。
  2. 如果没有路由参数则直接返回请求,如果有就区分参数并保存,
  3. 最后分别传入两种不同的参数,最后返回请求。

需要的函数为:

  1. getRoutesParams: 通过传入的url来获取路由参数
  2. getParamsPosition: 递归获取url的参数,结合上面的函数。
  3. fetchPost: 统一所有的数据开始请求并返回结果。
  4. getQueryObject: 根据对象或数组返回query string
  5. fetch: 主函数。

实现

//首先我们根据逻辑写几个需要的函数。

/**
* 获取路由所需参数
* @param {string} url
* @return {array} 路由参数数组
**/
getRoutesParams(url: string): string[] {
    let params = []
    getParamsPosition(url, params)
    return params
}

/**
* 根据url获取
* @param {string} url
* @param {array} 传入的路由参数数组
* @return {array} 传入的路由参数数组
**/
getParamsPosition(string, array) {
    // 获取url中参数的位置
    // 例: api/base/user/edit/{team_id}name/{user_id}
    let startP = string.indexOf("{")
    let endP = string.indexOf("}")
    
    let param = string.slice(startP + 1, endP)
    array.push(param)
    // 因为路由参数的数量是不定的,所以这边需要轮询至没有参数为止
    string = string.replace(string.slice(startP, endP + 1), "")

    if (string.includes("{") && string.includes("}")) {
            getParamsPosition(string, array)
    } else {
            return array
    }
}


/**
 * 减少相同代码,统一请求接口
 * @param  {string} aclString 权限acl
 * @param  {object} routeHttpObj 路由别名参数
 * @param  {string} method 请求方法
 * @param  {object} postHttpObj post参数
 * @param  {object} paramsHttpObj 路由后缀参数
 * @return {Promise<Response>} result 请求接口后的返回参数
 **/
async fetchPost(
        aclString: string,
        routeHttpObj: any,
        method: string,
        postHttpObj: any,
        paramsHttpObj: any
): Promise<any> {
        let httpUrl: any = Object.keys(routeHttpObj).length
                ? await getApi(aclString, routeHttpObj)
                : await getApi(aclString)
        //处理有路由后缀参数:/api/base/user/edit?user_id={user_id}&name={name}
        if (paramsHttpObj && Object.keys(paramsHttpObj).length) {
                const paramsUrl: string = getQueryObject(paramsHttpObj)
                httpUrl = `${httpUrl}?${paramsUrl}`
        }
    
        const result = await .http[method](httpUrl, postHttpObj).toPromise()

        return result
}

/**
*  url + '?' + result
* 通过此函数转译成query参数 result
* @params {object} 需要转译的对象
* @return {string} 转译后的参数string  page_size=20&page=1&search[acquisition][student][0]=10&
**/
getQueryObject(obj): string {
    let getPairs = (obj, keys = []) =>
            Object.entries(obj).reduce((pairs, [key, value]) => {
                    if (typeof value === "object") pairs.push(...getPairs(value, [...keys, key]))
                    else pairs.push([[...keys, key], value])
                    return pairs
            }, [])
    let x = getPairs(obj)
            .map(([[key0, ...keysRest], value]) => `${key0}${keysRest.map((a) => `[${a}]`).join("")}=${value}`)
            .join("&")

    return x
}

需要的函数都已经搞定了,正式开写主函数,

/**
* 简单封装
* @params {string} 路由别名
* @params {Object} 传入参数
* @return Response
**/
async fetch(aclString: string, httpObj?: Object, paramsObj?: Object) {
    let paramsSuccess = true
    const acl: any = await getAcl(aclString) // 根据路由别名获取路由对象
    const { url, method }: { url: string; method: string } = acl
    const isHaveRouteParams: boolean = acl.url.includes("{") && acl.url.includes("}")

    // 首先 根据别名获取路由数据,然后判断是否有路由参数
    if (isHaveRouteParams) {
            // 创建路由参数、body参数、路由所需参数
            let [routeHttpObj, postHttpObj, routesArray]: [any, any, string[]] = [{}, {}, getRoutesParams(url)]
            // 根据路由所需参数来填入
            for (let route of routesArray) {
                    if (httpObj[route]) {
                            routeHttpObj[route] = httpObj[route]
                    } else {
                            paramsSuccess = false
                    }
            }
            // 不是路由参数的 params
            for (const key in httpObj) {
                    if (httpObj.hasOwnProperty(key)) {
                            const element = httpObj[key]
                            if (!routesArray.includes(key)) {
                                    postHttpObj[key] = element
                            }
                    }
            }
           
            if (paramsSuccess) {
                    const result = await fetchPost(aclString, routeHttpObj, method, postHttpObj, paramsObj)
                    return result
            } else {
                    console.error(routesArray)
                    console.error("参数传入不全或者参数value为空。")
            }
    } else {
            // 没有路由参数直接请求
            const result = await fetchPost(aclString, {}, method, httpObj, paramsObj)
            return result
    }
}

打完收工。

最后

这是一个项目中真实的案例,自从我封装完后,大家似乎都逐渐改成了上述的方法来请求接口获取数据。对于程序方面的热情就是来源于这些优化后的反馈哈哈。

好哥哥学到的话点个赞再走吧。

另外推荐下新写的Vite源码解析,点我本站跳转

查看线上文档体验更佳 查看文档 Powered by dumi

看完有帮助的可以进入github给我一个🌟小星星 谢谢!