【源码阅读】| 从零到一实现简易版axios (二)

253 阅读3分钟

我正在参加「掘金·启航计划」
接上一章节,我们实现了axios部分简易的逻辑:

  • xhr的基础上做了封装,并对url参数做了处理。

接下来,我们来处理request部分剩下的功能

image-20230427141947121

需求分析

处理body

我们可以在axios文档中,找到body部分,axios-http.com/docs/req_co…

详细的可参照 MDN developer.mozilla.org/en-US/docs/…

image-20230427142548609

处理data部分,需要处理的功能点:

  1. 字符串
  2. 普通对象
  3. 字节数组
  4. decode参数

在前面章节,我们已经处理了普通对象,可以复用该部分逻辑

// plain object
 function isPlainObject(obj) {
  if (typeof obj !== 'object' || obj === null) return false

  let proto = obj
  while (Object.getPrototypeOf(proto) !== null) {
    proto = Object.getPrototypeOf(proto)
  }

  return Object.getPrototypeOf(obj) === proto
}

对于普通对象,我们将其自动转化成JSON格式,其他格式保留

export function transformRequest (data: any): any {
  if (isPlainObject(data)) {
    return JSON.stringify(data)
  }
  return data
}

function transformRequestData(config: AxiosRequestConfig): any {
  return transformRequest(config.data)
}

随后,将函数接入至processConfig函数中

function processConfig (config: AxiosRequestConfig): void {
  config.url = transformUrl(config)
  config.data = transformRequestData(config)
}

测试用例

axios({
  method: 'post',
  url: '/post',
  data: {
    a: 1,
    b: 2
  }
})

const arr = new Int32Array([1, 99])

axios({
  method: 'post',
  url: '/buffer',
  data: arr
})
  • 此时会遇到一个问题,我们查看/post的请求,请求body中的数据是一个JSON对象的,但是返回的数据为空。
  • 原因:在request headers中,当前的Content-Typetext/plain;charset=UTF-8,服务端不能正确的解析我们的数据。接下来就处理header部分

处理header

首先,我们需要传入的config支持传入heders,我们拓展一下原有的接口类型

export interface AxiosRequestConfig {
  url?: string
  method?: string
  data?: any
  params?: any
  headers?: any
}

然后,我们可以尝试调用axios库来看,他是能够根据你输入的数据类型自动转换请求头的。

具体实现我们可以看源码 github.com/axios/axios…

image-20230427155636849

因为我们是简易实现,没有对其他类型做进一步的处理,这里只简单的赋值为application/json格式

// 规范化header
function normalizeHeaderName(headers: any, normalizeName: string): void {
  if (!headers) {
    return
  }
  Object.keys(headers).forEach(name => {
    if (name !== normalizeName && name.toUpperCase() === normalizeName.toUpperCase()) {
      headers[normalizeName] = headers[name]
      delete headers[name]
    }
  })
}
// 处理Headers,如果没有传值,默认赋值为application/json
export function processHeaders(headers: any, data: any): any {
  normalizeHeaderName(headers, 'Content-Type')

  if (isPlantObject(data)) {
    if (headers && !headers['Content-Type']) {
      headers['Content-Type'] = 'application/json;charset=utf-8'
    }
  }

  return headers
}

然后,我们将其接入至processConfig函数中

function transformHeaders(config: AxiosRequestConfig) {
  const { headers = {}, data } = config
  return processHeaders(headers, data)
}

function processConfig(config: AxiosRequestConfig): void {
  config.url = transformURL(config)
  // 为什么要插入这里
  // transformRequestData会对data进行处理,影响了plainObject的判断
  config.headers = transformHeaders(config)
  config.data = transformRequestData(config)
}

我们需要将处理后的headers传入至xhr中,所以需要改造下xhr

export default function xhr (config: AxiosRequestConfig): void {
  const { data = null, url, method = 'get', headers } = config

  const request = new XMLHttpRequest()

  request.open(method.toUpperCase(), url, true)
  // 新增部分
  Object.keys(headers).forEach((name) => {
      // content-type为空时,移除该key
    if (data === null && name.toLowerCase() === 'content-type') {
      delete headers[name]
    } else {
      request.setRequestHeader(name, headers[name])
    }
  })

  request.send(data)
}

测试用例

axios({
  method: 'post',
  url: '/post',
  data: {
    a: 1,
    b: 2
  }
})

axios({
  method: 'post',
  url: '/post',
  headers: {
    'content-type': 'application/json;'
  },
  data: {
    a: 1,
    b: 2
  }
})

const paramsString = 'q=axios&source=hp'
const searchParams = new URLSearchParams(paramsString)

axios({
  method: 'post',
  url: '/post',
  data: searchParams
})

我们可以通过上述例子,验证我们的代码是可行的。

  • 请求头部没有传header时,如果是普通对象,会自动加上application/json的请求头
  • 如果是其他类型,如URLSearchParams会加上对应的Content-Type

总结

在上述章节,我们处理了 axiosrequest部分数据处理的逻辑上,在下一章节中,我们来处理response部分的数据处理。

带着问题去查看源代码,以实现功能为目标,理论和实践相互结合,能够提高问题解决的能力,希望大家共同进步。