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;
};
效果如图所示:
结论
今天这个就很简单了,也不是什么高级的东西,都是随手拿来用的,只不过封装起来方便使用一些,感觉这个脚手架到这真的可以直接进行项目的开发了,可能还差一个前端日志功能吧,下面有时间写写前端日志~