Next.js 脚手架进阶 — 封装fetch && 增加中间件

6,855 阅读4分钟

Next.js脚手架进阶系列

封装fetch

首先先来说一下为什么要用fetch而不是axios吧,主要有以下两点:

  • 第一,我在另一个脚手架express-react-scaffold里使用的就是axios,秉着学习新东西的想法,想自己封装一下fetch。
  • 第二,个人觉得fetch的功能更为强大,因为fetch是原生支持的API,更加的底层,所以可扩展性更好,经过封装扩展过后的fetch应该是很强大的,由于看了很多成熟脚手架用的都是fetch,我觉得这个观点还可以接受吧。(P.S.个人观点,不喜勿喷)

fetch的response

为什么要单独说一下fetch的response呢,个人认为这一点既是fetch的缺点又是fetch的优点,我们先来对比一下正常的请求fetch和axios的区别

// fetch
fetch(url)
    .then(res => { // 第一层res
        res.json() // 需要json过后才是我们想要的数据
            .then(r => { // 第二层res        
               console.log(r) // 获取到最后数据       
        })
});

// axios
axios(url)
    .then(res=>{ // 第一层res
        console.log(res); // 获取到我们想要的数据
});

如上面所示,我们需要先对fetch的res进行res.json()之后才拿到我们想要的json数据,而axios帮我们做了,使用起来更简单~

这算是一个缺点吧~但是!!!我们通过封装完全可以解决的,接下来就是为啥这也是fetch的优点了,首先来说说为啥fetch需要两层才可以,第一层进行的是res.json(),因为fetch的response是一个综合各种方法的对象,并不是请求的数据,也就是说其实fetch不仅可以是一个json对象,还可以是其他很多的,如果我们需要的是json对象,就res.json(),如果需要的是文本字符串,可以res.text(),它还可以直接获取blob内容,res.blob()。综上所述,fetch是真的真的很强大,就看你擅不擅长封装使用了。

事先声明,我上面吹了那么久,其实我个人觉得我也不算太会使用哈,下面的封装也就是简单的封装,各位看官按需使用,可以在我基础之上针对自己的项目进行更好的封装,或者就直接自己封装就可以了。

fetch的缺点

  • 获取数据的方式需要两层
  • fetch只对网络请求报错,对400,500都当做成功的请求
  • fetch不会携带cookie,需要添加配置
  • fetch不支持timeout ...

上面讲了fetch的几个缺点,其实这些缺点都是可以通过封装以及插件来进行弥补的。

封装fetch的流程以及目的

  • 人性化的使用方式,get, post, put, delete, patch等方法的调用
  • 满足大部分场景的数据获取方式,也就是直接res.json()
  • 支持timeout
  • 错误处理
  • 配合日志组件

封装代码

import fetch from 'isomorphic-unfetch';
import qs from 'query-string';
import { filterObject } from './util';

// initial fetch
const nextFetch = Object.create(null);
// browser support methods
// ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PATCH', 'PUT']
const HTTP_METHOD = ['get', 'post', 'put', 'patch', 'delete'];
// can send data method
const CAN_SEND_METHOD = ['post', 'put', 'delete', 'patch'];

HTTP_METHOD.forEach(method => {
  // is can send data in opt.body
  const canSend = CAN_SEND_METHOD.includes(method);
  nextFetch[method] = (path, { data, query, timeout = 5000 } = {}) => {
    let url = path;
    const opts = {
      method,
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        Accept: 'application/json'
      },
      credentials: 'include',
      timeout,
      mode: 'same-origin',
      cache: 'no-cache'
    };

    if (query) {
      url += `${url.includes('?') ? '&' : '?'}${qs.stringify(
        filterObject(query, Boolean),
      )}`;
    }

    if (canSend && data) {
      opts.body = qs.stringify(filterObject(data, Boolean));
    }

    console.info('Request Url:', url);

    return fetch(url, opts)
      .then(res => res.json())
      .then(({ errcode = 0, errmsg, data }) => {
        if (errcode !== 0) {
          const err = new Error(errmsg);
          err.message = errmsg;
          err.code = errcode;
          err.data = data;
          throw err;
        }
        return data;
      });
  };
});

export default nextFetch;

封装完之后的使用:

// /redux/sagas/user/userList.js
...
import nextFetch from '../../../core/nextFetch';

export function* userList() {
  while (true) {
    yield take(FETCH_USER_LIST);
    try {
      // 新写法
      const data = yield nextFetch.get(api.getUserList);
      // 原来的写法
      // const res = yield fetch(api.getUserList);
      // const { data } = yield res.json();
      yield put(fetchUserListDataSuccess(data));
    } catch (error) {
      console.log(error.code, error.message, error.data);
      yield put(fetchUserListDataFali(error));
    }
  }
}

为什么使用的是isomorphic-unfetch, 因为Next.js是服务端渲染,这个标榜的是前后端都通用~以前我用的服务端渲染框架因为是分离的,前后端使用的是不同的fetch(前端是whatwg-fetch,后端是node-fetch)。

上面是我自己的封装方式,我默认后端返回的是一个json格式的数据,然后里面有三个字段{ data, errcode, errmsg },并且成功的响应code = 0。小伙伴们根据自己的项目结构适当进行更改。

增加中间件

这个就很简单了,redux的中间件系统,为什么增加中间件呢,因为上面我们增加了fetch的error处理,那么我们就可以对失败的请求在中间件这个地方进行提前处理~

// /redux/store.js
import userMiddleware from '../middlewares/client/user';
...
// 增加redux中间件
const bindMiddleware = (middleware) => {
  // add route middleware
  middleware.push(userMiddleware);
  ...
  return applyMiddleware(...middleware);
};

// /middles/client/user.js
import { message } from 'antd';
import {
  FETCH_USER_LIST_FAIL
} from '../../constants/ActionTypes';

export default ({ getState }) => next => action => {
  // redux中间件
  const ret = next(action);
  switch (action.type) {
    case FETCH_USER_LIST_FAIL: {
      message.error('获取用户列表失败, 请稍后重试');
      break;
    }
    default:
  }
  return ret;
};

效果如图所示:

结论

今天这个就很简单了,也不是什么高级的东西,都是随手拿来用的,只不过封装起来方便使用一些,感觉这个脚手架到这真的可以直接进行项目的开发了,可能还差一个前端日志功能吧,下面有时间写写前端日志~

代码地址