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 流程
知道大致什么样的形态我们可以称为流程后,就需要思考一下在开发阶段为什么需要抽象流程.
流程优势
我在实际使用场景下感觉到的优势:
在任何需要掉起流程地方都可以调起流程.
开发者只需关心流程成功还是失败, 无需知道内部复杂实现.
多个简单的流程可自由组合成一个复杂流程.
如果我们把上面的实名认证进行流程化抽象后, 我们可以得到下面这样的流程图.
当我们把流程拆分出来后, 逻辑就简单很多了, 每一个流程都是独立的模块. 各个模块之间还可以互相调用, 来组合出一个更大型的流程.
流程哲学
程序应该只关注一个目标, 并尽可能把它做好. 让程序能够互相协同工作. 应该让程序处理文本数据流, 因为这是一个通用的接口. -- Malcolm Douglas McIlroy 道格拉斯. 麦克罗伊
其实这里也符合 unix 哲学, 每个流程只做一件事. 这样我们只需要维护好单个流程内部的逻辑就可以了, 每个流程返回的数据还可以带到下一个流程内进行使用, 这非常像一个 Promise 链.
How to make 流程
在我们知道是什么, 为什么后, 就可以看看具体到代码层面上我们如何去构建流程, 当然这里的场景是小程序,但只要是 SPA 架构的 web 页面, 类似的思路一样是可以尝试使用的. 我们先来整理一下所需开发的功能点:
何时知道要跳回起始页面
怎么知道起始页面是哪个
多流程的数据流向是怎样的.
基本原理
其实问题很简单, 解决方法也是我为什么说需要 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
快去尝试将你的业务场景流程化吧!
来源: https://www.cnblogs.com/YikaJ/p/10314697.html