uni实现类似component api

63 阅读2分钟

缘由

在uniapp开发的小程序项目中需要用到登录模块,不像别的系统一样是页面级别的login,而是类似弹窗的组件。

  1. 封装login组件,在每个需要鉴权的页面/组件手动引入该组件,然后根据逻辑去判断显示与否, 这种方法能实现,但是感觉很笨,pass
  2. 封装login组件,在每个需要鉴权的页面,直接通过this.就可以调用出对应的login弹窗,类似this.$message,支持回调success,也可以通过async,await的方式接收对应的登录结果

采用2方式引发的问题

  1. 研究了vue的component api的实现方法,其实就是利用vue.extend(具体可自行查阅相关文档)去扩展一个子类,在调用的时候创建子类,并通过document.body.appendChild去添加对应的dom节点,然后调用子类的销毁回调,并移除dom上节点....

  2. 将上述思路,直接在uniapp上实现,因为uniapp用的vue语法以及小程序标签,类小程序api,所以以为也行得通,但是最后运行发现,document报错!!!才反应过来小程序并没有document,运行在h5就没问题

  3. 查阅相关文档,可以使用vue-inset-loader曲线完成目标

step1

安装依赖: npm install vue-inset-loader


#vue.config.js添加loader处理

const path = require('path')
module.exports = {
	configureWebpack: {
		module: {
			rules: [{
				test: /\.vue$/,
				use: {
					loader: path.resolve(__dirname, "./node_modules/vue-inset-loader")
				},
			}]
		},
	}
}

# 在pages.json添加配置
"insetLoader": {
        //配置
        "config": {
                //将需要引入的组件名起了个confirm的名字在下面label中使用
                //右侧"<login ref='confirm' />"为需要插入的组件标签
                "login": "<login ref='login' />"
        },
        // 全局配置  
        //需要挂在的组件名
        "label": ["login"],
        //根元素的标签类型 也就是插入到页面哪个根元素下默认为div 但是uniapp中需要写为view
        "rootEle": "view"
}

#main.js 挂载全局组件
import Login from '@/components/login/login.vue'
Vue.component('login', Login)

(其实到这里,你打开页面就会发现,login被挂载到每一个页面级别的组件中,component组件里面就不会挂载,使用了这个loader,配置了那么多东西,也就是给你省了在每个页面添加,但是也给一些没有必要挂在login的页面,挂载上去了...)

step2

login组件

  isShow: false // 控制组件的显示与否,默认false
  
  showLogin (options) {
        return new Promise((resolve, reject) => {
                const defaultOptions = {
                        success: null, // 成功后的回调
                        fail: null // 失败后的回调
                }
                this.options = Object.assign({}, defaultOptions, options)
                this.loginShow = true // 显示
                // 处理成这样 是为了兼容success回调/async await的写法
                this.options.success = this.options.success || function successResolve () {
                        resolve(true)
                }
                this.options.fail = this.options.fail || function failResolve () {
                        reject(new Error('登录失败'))
                }
        })
},


// 成功的处理函数
loginSuccess () {
        const confirmHandler = this.options.success
        if (confirmHandler && typeof confirmHandler === 'function') {
                uni.showToast({
                        title: '登录成功',
                        duration: 1000
                })
                this.loginShow = false
                // 如果传入success就直接执行回调,如果没传,就resolve(true),外部await后同步执行后续逻辑
                confirmHandler()
        }
}

main.js


// 全局挂载该方法
Vue.prototype.$showLogin = function (that, options) {
	const currentCase = seekLogin(that)
	// 将当前页面级别的实例传进来
	return currentCase.$refs.login.showLogin(options)
}

// 往上遍历找出页面级别实例,因为只有页面级才有login组件
function seekLogin (currentCase) {
	if (!currentCase.$refs.login) {
		return seekLogin(currentCase.$parent)
	}
	return currentCase
}

调用


// 调用一,this为当前实例
await this.$showLogin(this)
// ...成功登录后续逻辑

// 调用二
this.$showLogin(this, {
    success: function () {
        console.log('成功登录后续逻辑')
    },
    fail: function () {
        console.log('登录失败后续逻辑')
    }
)

总结

就是为了强行用this.$的方式去使用login,但是这样的写法,容易拓展,后续的页面开发就可以不用关心login的引入,成功登录父子组件通信的问题; 缺点也很明显,在每个页面级都会引入该组件