Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

小程序场景流程化构建

Yika丶J 2019-01-24 15:24:00 阅读数:195 评论数:0 点赞数:0 收藏数:0

What is 流程

第一步我们需要对流程有一个认识,需要知道一个流程的基本形态是怎样的。

流程案例

  • 使用 APP 第三方支付时,点击选择使用微信支付后会拉起应用,用户可以选择各种银行卡或信用卡进行支付,若密码失败则在微信内继续处理,最终跳回 APP 后确认支付成功后,即可进行后续处理。

  • 使用 APP 第三方登录时,点击选择使用 QQ 登录后会拉起应用,用户可以选择快速登录或输入账号密码进行登录,若密码失败则在 QQ 内继续处理,最终跳回 APP 后确认为登录成功后,即可进行后续处理。

  • 使用微信小程序人脸识别时,点击开始使用人脸识别后会拉起微信的人脸识别,若识别失败则在人脸识别内重新进行人脸识别,最终回到拉起人脸识别前的那个页面,得到是否成功的结果。

流程抽象

从上面的案例来看,其实我大部分都是 Copy/Paste 的文案,说明一个流程的基本形态是很固定的,它不仅限于在 APP 之间的跳转,小程序与微信 API 的使用,在任何我们认为属于流程的场景,我们都可以尝试去构建我们自己的流程。

基础流程

将刚才说的以流程图来表示就是这样的, 各种复杂的处理都在流程页面内,业务页面最终只需要知道成功还是失败即可。如果我们以开发者的角度来看,我们在业务侧只需要这样处理。

// 对开发者来说,是可以抽象成一个 Promise 来表示的。
// pending 表示流程未结束
// resolve 表示流程返回成功
// reject  表示流程返回失败
sdk.pay(opts).then(successHandler).catch(failHandler)
sdk.face(opts).then(successHandler).catch(failHandler)
sdk.login(opts).then(successHandler).catch(failHandler)

对开发者来说,这样的流程调用只能说非常的清爽!当流程结束后,开发者可以在 then 里进行后续处理。若无法正常开启流程或用户主动取消流程,则可以在 catch 内进行处理。

使用场景

一般我们会在各种通用场景下,都会需要调用流程。当你发现你的项目,在各种场景下都可能需要某个流程时,你就可以开始考虑将相关内容抽象流程化。

在我们开发政务服务相关的小程序时,在整个小程序内,我们都需要涉及实名校验,整个流程一环扣一环,远远不是检查一下是否要登录就选择登录这么简单的事情。

认证流程

这里是一个政务服务的认证场景,场景的流程是很长的。

Why we need 流程

知道大致什么样的形态我们可以称为流程后,就需要思考一下在开发阶段为什么需要抽象流程。

流程优势

我在实际使用场景下感觉到的优势:

  1. 在任何需要掉起流程地方都可以调起流程。
  2. 开发者只需关心流程成功还是失败,无需知道内部复杂实现。
  3. 多个简单的流程可自由组合成一个复杂流程。

如果我们把上面的实名认证进行流程化抽象后,我们可以得到下面这样的流程图。

抽象认证流程

当我们把流程拆分出来后,逻辑就简单很多了,每一个流程都是独立的模块。各个模块之间还可以互相调用,来组合出一个更大型的流程。

流程哲学

程序应该只关注一个目标,并尽可能把它做好。让程序能够互相协同工作。应该让程序处理文本数据流,因为这是一个通用的接口。 —— Malcolm Douglas McIlroy 道格拉斯·麦克罗伊

其实这里也符合 unix 哲学,每个流程只做一件事。这样我们只需要维护好单个流程内部的逻辑就可以了,每个流程返回的数据还可以带到下一个流程内进行使用,这非常像一个 Promise 链。

How to make 流程

在我们知道是什么、为什么后,就可以看看具体到代码层面上我们如何去构建流程,当然这里的场景是小程序,但只要是 SPA 架构的 web 页面,类似的思路一样是可以尝试使用的。我们先来整理一下所需开发的功能点:

  1. 何时知道要跳回起始页面
  2. 怎么知道起始页面是哪个
  3. 多流程的数据流向是怎样的。

基本原理

其实问题很简单,解决方法也是我为什么说需要 SPA 架构进行实现。我们只需要在调用 API 后,记录当前页面栈并全局监听一个事件。当流程结束后,我们再通过事件通知来决定是否需要跳回调用页。我们只需要记录页面栈,配合全局唯一的事件,跨页面通信并进行相应处理。而多数据流向,我们是可以通过前一个流程返回的数据直接带到新流程进行使用。

以下提供基本代码,除基础库外,示例代码不可直接运行(有许多伪代码)。

流程基础库

 // sdk.js
interface FlowOpts {
// 决定是如何开启一个流程
startType: 'navigateTo' | 'redirctTo',
// 流程结束后,是否需要保留当前页面
finishType: 'keep' | 'pop',
// 带给页面的参数
params: Record<string, any>
}
/**
* 创建通用流程
* @param url 跳转参数
* @param options 创建参数,具体类型参照 FlowOpts
* @return Promise Response
*/
function createFlow(url: string, options: FlowOpts = {}) {
const startPageLength = getCurrentPages().length
const successEvent = url + '-' + extend.generateGUID()
options.startType = options.startType || 'navigateTo'
options.finishType = options.finishType || 'pop'
options.params = options.params || {}
const urlWithOptions = urlJoinParams(url, {
successEvent,
...options.params
})
return new Promise((resolve, reject) => {
Event.addEventListener(successEvent, (res: any) => {
if (options.finishType === 'keep') {
// 保存到当前页面
resolve(res.target)
} else {
// 如果是弹回流程开始页
udb.navigatoBackToStart(startPageLength)
.then(() => {
resolve(res.target)
})
}
})
wx[options.finishType]({
url: urlWithOptions,
complete() {
Event.removeEventListener(successEvent)
}
})
})
},
// 流程跳回开始
navigatoBackToStart(startPageLength: number) {
return new Promise((resolve, reject) => {
const endPageLength = getCurrentPages().length
const delta = endPageLength - startPageLength
// 回退页面
wx.navigateBack({ delta })
delayResolve()
// 确保异步回退成功
function delayResolve() {
setTimeout(() => {
const currentPageLength = getCurrentPages().length
if (endPageLength > 1 && startPageLength === currentPageLength) {
resolve()
} else {
delayResolve()
}
}, 100)
}
})
},

创建流程

// my-flow.js
// 业务创建流程
function startFlowOne(options) {
if(!canStart) {
// 能否发起流程的业务逻辑判断
return Promise.reject()
}
return createFlow('/pages/flow-one/index', options)
}
function startFlowTwo(options) {
return createFlow('/pages/flow-two/index', options)
}
// 组合流程
function startFlowOneTwo(options) {
return startFlowOne({
startType: 'keep',
params: options
}).then(flowRes => {
// 多流程数据合并
return startFlowTwo({
params: {
...flowRes,
...options
}
})
})
}

具体流程页面,处理完业务后发起成功事件

// page/flow-one/index.js
Page({
onLoad(parmas) {
if(params) {
this.successEvent = params.successEvent
}
},
handleSubmit() {
request({
url: 'example.com',
data: params,
methods: 'POST'
}).then((res) => {
Event.dispatch(this.successEvent, res)
})
}
})

真正提供到给业务开发使用。

// pages/index/index.js
Page({
handleTap() {
// 非常简单!
return sdk.startFlowOneTwo({ id: 3 })
.then((res) => {
// 成功后的业务代码
})
.catch(() => {
// 无法正常调起流程
})
}
}) 

通过上面的代码,我们就可以把业务再进行合理抽象,通过流程化降低业务复杂度。

遗留问题

在小程序上使用还有哪些特别要注意的点呢,其中有一个就是要千万注意页面栈的问题。在小程序内是有十层页面栈限制的,如果你的流程特别的长,需要格外注意这一点并进行相应的优化。但如果我们在 web 页面使用,是没有这类问题的。

Ending

快去尝试将你的业务场景流程化吧!





版权声明
本文为[Yika丶J]所创,转载请带上原文链接,感谢
https://www.cnblogs.com/YikaJ/p/10314697.html

编程之旅,人生之路,不止于编程,还有诗和远方。
阅代码原理,看框架知识,学企业实践;
赏诗词,读日记,踏人生之路,观世界之行;