本文主要是在自己看过
IMWeb前端社区分享的 腾讯在线教育小程序开发实践之路 文章之后,对其中提到的采用可插拔插件式封装方法,来做各种插件的扩展的理解。作为前端小白,水平有限,以下只是分享下自己的见解。
在 Koa 中采用中间件模式使用插件,我们可以将其借鉴到平时的开发中,主要使用其中的 compose 函数
function compose(middleware) {
if (!Array.isArray(middleware)) {
throw new TypeError('Middleware stack must be an array!');
}
for (const fn of middleware) {
if (typeof fn !== 'function') {
throw new TypeError('Middleware must be composed of functions!');
}
}
return function(context, next) {
let index = -1;
return dispatch(0);
function dispatch(i) {
if (i <= index) {
return Promise.reject(new Error('next() called multiple times'));
}
index = i;
let fn = middleware[i];
if (i === middleware.length) fn = next;
if (!fn) return Promise.resolve();
try {
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err);
}
}
};
}
关于对 compose 函数的理解,不在此赘述,简单说下使用方式
function fetch() {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() < 0.5 ? resolve('Fetch is success') : reject('Fetch is error');
}, 3000);
});
}
async function a(ctx, next) {
console.log('a start');
let res;
try {
res = await next();
} catch (err) {
res = Promise.reject(err);
}
console.log('a end');
return res;
}
async function b(ctx, next) {
console.log('b start');
await next();
console.log('b end');
return fetch();
}
const ctx = { a: 1, b: true };
const p = compose([a, b])(ctx, ctx => {
console.log('c start');
console.log('ctx', ctx);
console.log('c end');
});
p.then(res => {
console.log('res', res);
}).catch(err => {
console.log('err', err);
});
执行结果如下
// => a start
// => b start
// => c start
// => ctx { a: 1, b: true }
// => c end
// => b end
// 等待 3 秒
// => a end
// => res Fetch is success 随机成功或失败
下面将以在小程序开发中的需求为例,介绍中间件模式在开发中的使用
使用普通封装方法
简单的对 wx.request 方法进行封装
function fetch(option) {
return new Promise((resolve, reject) => {
wx.request({
...option,
success: res => {
res.statusCode === 200 ? resolve(res) : reject(res);
},
fail: reject,
});
});
}
fetch({ url: 'https://...' })
.then(res => {})
.catch(err => {});
通常情况下,有些请求需要在 header 中携带 token 数据进行身份验证,而有些请求不需要,所以修改 fetch 函数
function fetch(option, config = {}) {
const { takeToken = true } = config;
if (takeToken) {
const { header = {} } = option;
const token = wx.getStorageSync('token');
option.header = { ...header, token };
}
return new Promise((resolve, reject) => {
wx.request({
...option,
success: res => {
res.statusCode === 200 ? resolve(res) : reject(res);
},
fail: reject,
});
});
}
fetch({ url: 'https://...' }, { takeToken: true })
.then(res => {})
.catch(err => {});
目前看起来还好,该功能只是在函数顶部增加了一些逻辑判断处理
此时我们需要有些请求能够在发起请求的时候自动显示 loading,请求完成后自动隐藏 loading,那么继续修改 fetch 函数
function fetch(option, config = {}) {
const { takeToken = true, wrapLoading = false } = config;
if (takeToken) {
const { header = {} } = option;
const token = wx.getStorageSync('token');
option.header = { ...header, token };
}
if (wrapLoading) wx.showLoading({ title: '加载中' });
const hide = () => {
if (wrapLoading) wx.hideLoading();
};
return new Promise((resolve, reject) => {
wx.request({
...option,
success: res => {
res.statusCode === 200 ? resolve(res) : reject(res);
},
fail: reject,
complete: () => {
hide();
},
});
});
}
fetch({ url: 'https://...' }, { wrapLoading: false })
.then(res => {})
.catch(err => {});
现在再看 fetch 函数,自动显示和隐藏 loading 的功能处理代码不仅仅只出现在一处,请求函数 wx.request 的参数的 complete 方法中也存在
随着功能的逐步增加,
fetch函数会越来越大,功能逻辑代码也越来越多,逻辑代码很容易混杂在一起。如果哪天需要修改或者删除某项功能,那么需要查看整个fetch函数的所有部分,确保修改或删除后的功能完善无遗漏。如果函数出现了问题,也要从所有代码中进行排查
使用可插拔插件式封装方法
同样的添加使用 token 功能
function fetch(option) {
return new Promise((resolve, reject) => {
wx.request({
...option,
success: res => {
res.statusCode === 200 ? resolve(res) : reject(res);
},
fail: reject,
});
});
}
function useToken(ctx, next) {
const { takeToken = true } = ctx.config;
if (takeToken) {
const { header = {} } = ctx.option;
const token = wx.getStorageSync('token');
ctx.option.header = { ...header, token };
}
return next();
}
function request(option, config = {}) {
const ctx = { option, config };
return compose([useToken])(ctx, ctx => fetch(ctx.option));
}
request({ url: 'https://...' }, { takeToken: true })
.then(res => {})
.catch(err => {});
保持 fetch 函数代码不动,增加 request 函数作为请求函数,增加 useToken 中间件,并在 request 函数中使用该中间件。可以发现使用 token 的功能逻辑是独立的
继续添加 loading 功能
function fetch(option) {
return new Promise((resolve, reject) => {
wx.request({
...option,
success: res => {
res.statusCode === 200 ? resolve(res) : reject(res);
},
fail: reject,
});
});
}
function useToken(ctx, next) {
const { takeToken = true } = ctx.config;
if (takeToken) {
const { header = {} } = ctx.option;
const token = wx.getStorageSync('token');
ctx.option.header = { ...header, token };
}
return next();
}
async function useLoading(ctx, next) {
const { wrapLoading = false } = ctx.config;
if (wrapLoading) wx.showLoading({ title: '加载中' });
let res;
try {
res = await next();
} catch (err) {
res = Promise.reject(err);
}
if (wrapLoading) wx.hideLoading();
return res;
}
function request(option, config = {}) {
const ctx = { option, config };
return compose([useToken, useLoading])(ctx, ctx => fetch(ctx.option));
}
request({ url: 'https://...' }, { wrapLoading: true })
.then(res => {})
.catch(err => {});
可以发现,之前的代码并没有做修改,只是增加了 useLoading 中间件,并在 request 函数中使用该中间件
如果之后再有什么新的功能或者功能调整,可以继续以中间件的形式进行增加或对相应中间件进行修改或删除
对比上面两种封装方式,不难发现,以中间件方式的封装,可以将各个功能逻辑相互独立起来,方便维护和理解,其他的优势可以自己对比去发现,这只是个人的见解。