封装一个 axios url encoding serialize util

该 util 遵从的是 Json API,即 swagger, 这一规范,可以将对应的 query 进行 encoding。

主自用,使用 TS,参考对象为 lodash-contrib


axios 默认传递到后台的数据为 json 格式,官方在文档:URL-Encoding Bodies 中建议在浏览器环境下可以直接使用 URLSearchParams 或是 qs,使用方法如下:

  1. URLSearchParams

    const params = new URLSearchParams();
    params.append('param1', 'value1');
    params.append('param2', 'value2');
    axios.post('/foo', params);
    
  2. qs

    const qs = require('qs');
    axios.post('/foo', qs.stringify({ bar: 123 }));
    

    ES6:

    import qs from 'qs';
    const data = { bar: 123 };
    const options = {
      method: 'POST',
      headers: { 'content-type': 'application/x-www-form-urlencoded' },
      data: qs.stringify(data),
      url,
    };
    axios(options);
    

我们的 JSON 规范遵从的是这一条:Json API,直接使用 URLSearchParams 好像是行不通的,因为有一些 filter 会使用 [],如:/author/1/book?filter[book]=genre=='Science Fiction';title==The*;price.total>100.00,之前直接使用 URLSearchParams 似乎就报错了。

当然,也有可能是因为 encoding 没有完全转换的关系。

出于同样的原因,直接使用 qs 也存在一定的问题,似乎可能是因为需要重新修改 encoder、queryPrefix 等因素,总之没有能够在有限的时间内完成调整。

最终还是稍微修改了一下 lodash-contrib 中的 toQuery,具体实现如下:

import _ from 'lodash';

// some of the util functions are from lodash-contrib but in TS form, credit: https://github.com/node4good/lodash-contrib
// recreated here due to lodash-contrib does not have TS support and cannot be used in the project.
type validQuery = string | number | boolean;

type validQueryObj =
  | { [key: string]: validQuery | validQueryObj }
  | validQuery[]
  | validQuery;

const buildParams = (
  prefix: string | number,
  val: validQueryObj,
  top = true
): string => {
  if (_.isUndefined(top)) top = true;
  if (_.isArray(val)) {
    return _.map(val, function (value, key) {
      return buildParams(top ? key : prefix + '[]', value, false);
    }).join('&');
  } else if (_.isObject(val)) {
    return _.map(val, function (value, key) {
      return buildParams(top ? key : prefix + '[' + key + ']', value, false);
    }).join('&');
  } else {
    return encodeURIComponent(prefix) + '=' + encodeURIComponent(val);
  }
};

export const toQuery = function (obj: validQueryObj): string {
  return buildParams('', obj);
};

使用方式如下:

export const fetch = async ({
  apiMethod,
  uri,
  params,
}: FetchProps): Promise<AxiosResponse<any, any>> => {
  return await axiosInstance({
    method: apiMethod,
    url: uri,
    params,
    paramsSerializer: (params) => {
      return toQuery(params);
    },
  });
};

type guard 目前不是最好的实现,还是需要根据 generics 进行对应的修改,不过这应该算是一个比较通用的解决方案了。

04-15 12:28