当前位置:   article > 正文

[uniapp] uview(1.x) 二次封装u-navbar 导致 :custom-back函数this.$emit / this.$props失效问题处理

u-navbar

背景介绍

uniapp 项目: 使用了uview框架 1.x版本
其中对于组件 u-navbar , 想要进行二次简单封装,把一些固定样式/固定配置等预写好, 同时对默认的返回按钮做一层前置拦截,方便后续扩展.

实现的效果

封装的组件为: navBar

	<navBar :title="pageTitle" :isBack="true" :navbarHeight.sync="navbarHeight" :customBack="abc" ></navBar>
  • 1

在这里插入图片描述
这其中, 返回的点击事件我们要自定义的话, 可以通过:customBack属性来传递 function , 如此处的 abc

而在 navbar组件内部:

<template>
	<u-navbar ref='navbarex' id="nmyui" class="navbar" :is-back="isBack" :custom-back="customBackBefore" :title="title" :title-bold="true" title-color="#fff"
		:title-width="titleWidth" back-icon-color="#fff" :is-fixed="fixed" :border-bottom="border" title-size="36" :background="background" :titleStyle="titleStyle">

		<view v-if="$slots.left" class="left">
			<slot name="left"></slot>
		</view>

		<view v-if="$slots.right" class="right">
			<slot name="right"></slot>
		</view>
	</u-navbar>


</template>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

navbar组件内部的 props之一

			// 自定义返回逻辑
			customBack: {
				type: Function,
				default: null
			},
  • 1
  • 2
  • 3
  • 4
  • 5

我们在组件内部其实就是直接摆放了一个 u-navbar组件
而在处理对默认返回按钮的前置拦截的时候, 我们其实是给 u-navbar的属性 :custom-back="customBackBefore"提供了一个组件内部实现的函数 customBackBefore

这里再说一下, navbar是二次封装组件, 其参数有 customBack, 外部会给他传递诸如 abc这种函数
u-nabaruview的导航组件, navbar就是对它的二次封装, 它的自定义返回事件参数名 custom-back , 在本次封装中, 固定给他一个 customBackBefore的处理方法(目的是用来做前置拦截等)

在这里插入图片描述

问题描述

问题就出在 customBackBefore
这里说一句, app端(Android是ok的, iOS没验证,大概率也是ok的)
出问题的是小程序端

先来看报错
在这里插入图片描述

弯路:

第一眼看到function, 又想到小程序, 第一个怀疑的是 小程序的特性对funtion的处理肯定不一样

于是 查到这一篇文章 :
为什么木有人啊??【报Bug】自定义组件模式中子组件props接收不到数组中的function
别的没仔细看, 就看了个这个
在这里插入图片描述
好吧, 那就用时间机制, 也就是发个 emit的方式

于是代码做修改
navbar外部使用:
在这里插入图片描述
navbar:
在这里插入图片描述
来看看什么下场…
在这里插入图片描述
点了多次返回按钮, 只打印, 没有后续了, 也没报错, 也没触发
于是去查了emit的处理, 有说不能用驼峰命名的, 有说该用 this.$parent.abc()

好像两条路都走不通了
好吧, 继续耐着性子排查(期间各种尝试不表.)
总之最后, 看回到

在这里插入图片描述
点进去发现:

在这里插入图片描述

这时候才开始, 把目光放到了 this 这个罪魁祸首上面

其实上面不管用 this.$props.customBack() or this.$emit(‘goback’), 最大的问题就是在 this上出了问题

因为平时主要做app端开发, 小程序接触不多, 对于这句话的理解不够深刻

uni-app坑:Props传递Function,该方法里的this指向子组件而不是父组件

这句话怎么理解呢?
举个例子 - (这里写伪代码了)
父组件 component-parent 中,我们设置一个state 叫做 parentTitle:'i am parent'
子组件 component -sub 中,我们设置一个state 叫做 subTitle:'i am sub'

// 父组件
<component-parent>
	<component-sub :mylogger="logger"/>
</component-parent>

//父组件方法
logger(){
	console.log(this.parentTitle)
	console.log(this.subTitle)
}


//子组件实现
<component-sub @tap="mylogger"/>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

由于在小程序中, Props传递Function,该方法里的this指向子组件而不是父组件
所以, 其实在父组件的 logger函数体中, this已经指向了 子组件 component-sub, 讲白了, 此时的this.就是子组件的this
所以日志会输出

undefined
i am sub
  • 1
  • 2

也就是说在父组件的 logger方法中, 能拿到 this.subTitle , 但是拿不到this.parentTitle, 因为this的指向以及改变了

回到u-navbar

由于上面的特性, 我们也看到了uview对于 u-navbar的处理, 他们为了使用者进行自定义返回的时候避免这个坑,
他们做了如下处理

goBack() {
				// 如果自定义了点击返回按钮的函数,则执行,否则执行返回逻辑
				if (typeof this.customBack === 'function') {
					// 在微信,支付宝等环境(H5正常),会导致父组件定义的customBack()函数体中的this变成子组件的this
					// 通过bind()方法,绑定父组件的this,让this.customBack()的this为父组件的上下文
					this.customBack.bind(this.$u.$parent.call(this))();
				} else {
					uni.navigateBack();
				}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

通过bind的方法,把this的指向又交还给了父组件, 这样作为使用者,就可以肆无忌惮的通过props传递function, 从而实现多种多样的自定义返回处理.

爸爸就该有爸爸的样子.jgp

再回到我们的问题

对于瞎玩二次封装的我们来讲, 在 navbar子组件内部,我们的:custom-back="customBackBefore"中, customBackBefore的函数体内部, this到底现在指向了谁??

打印一下
在这里插入图片描述
作为子组件的navbar竟然持有函数 abc? 那很显然在

methods: {
			// 自定义返回-前置拦截
			customBackBefore() {
				console.log("navbar-customer-back-before")
				// this.$props.customBack()
				console.log(this)
			}
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

此刻的子组件中的 customBackBefore函数体中, this指向的是父组件的
(当然,在子组件的 mounted的生命周期等其他函数中, this还是指向子组件本身的)

为啥customBackBefore中this指向的是父组件

我的理解:

1.在未做二次封装之前, 我们一般直接使用 uview的u-navbar, 一旦自定义导航事件函数, uview会帮我们处理, 把 Props传递Function,该方法里的this指向子组件而不是父组件 这个问题内部处理, 使得我们任何的自定义导航事件函数:custom-back="xxxx",在xxx中都能正确的拿回父组件的this

在这里插入图片描述

2.在做二次封装的时候, 最终的u-navbar拿到的是page组件的this. 在重新bind的时候, 被navbar组件内部的:custom-back拦截了
在这里插入图片描述

解决办法

这里有好几个方法,不过其中有坑, 我们来分析一下, 选那些合适~~~

先看看navbar组件的customBackBefore,这里会添加事件的触发

	// 自定义返回-前置拦截
			customBackBefore() {
				console.log("navbar-customer-back-before")
			}
		}
  • 1
  • 2
  • 3
  • 4
  • 5

再看看page 父组件中的abc函数体, 这里通过两个this,来打印确定this的指向.

		methods: {

			abc() {
				// page页的state
				console.log(this.pageTitle)
				// navbar子组件的dec
				console.log(this.dec)
				
				uni.showToast({
					title: "1abc2"
				})
			},
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

方案1: 将错就错? 直接调用abc

			// 自定义返回-前置拦截
			customBackBefore() {
				console.log("navbar-customer-back-before")
				// 既然this已经明确指向page父组件, 那显然直接调用this.abc()是可以生效的
				this.abc()
			}
		}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

看看日志
在这里插入图片描述
该方案直接就躺平了, 就直接拿父组件的函数abc来执行, 日志输出方面也表明了, 在abc函数内部的this指向是page的,大问题没有.缺点是导致组件无法通用化, 和封装的目的相违背 pass

方案2: 坚持props传funtion

坚持props传funtion - navbar子组件中,修正this指向 navbar自己

既然 问题出在this的指向不对,那我们做一下this的指向重置即可
navbar子组件全局设置一个 that 用来缓存 navbar的this
在这里插入图片描述

在mounted周期函数里赋值,(因为此时的子组件navbar在这个生命周期里,this是指向自身的)
在这里插入图片描述
然后再 customBackBefore函数体重使用即可
不过,这里插一句, 还记得那句话么

uni-app坑:Props传递Function,该方法里的this指向子组件而不是父组件

对于page父组件而言, 我们封装的 navBar同样会面临abc函数体中,this指向子组件(navbar)的问题
所以,我们来一各个测试看看this的指向

2.1

that.$props.customBack()的写法

在这里插入图片描述
打印结果
在这里插入图片描述
结果显示: this的指向既不指向page父组件, 也不指向navbar子组件… pass

2.2

直接的 that.customBack()的写法
在这里插入图片描述
打印结果
在这里插入图片描述
结果显示: 调用是成功, 但是 this的指向出现了问题, page父组件的abc函数体中,this指向了navbar子组件

2.3 bind方案

针对2.2的改进, 用 bind的方案来解决this的指向问题

在这里插入图片描述
打印结果
在这里插入图片描述
结果符合预期, this的指向也正确了 success

方案3: 改用$emit事件机制

在这里插入图片描述
当然, paga父组件里也要做一下改造,
在这里插入图片描述
打印结果
在这里插入图片描述
结果也非常符合预期, this的指向也是正确的指向了父组件~~ success

总结

一圈搞下来, 我们有了两种解决办法
方案2.3方案3

至此,对于uview(1.x)的组件 u-navbar的二次封装中遇到的自定义事件的处理, 也算完成.
至于选择 上面哪两种方案?

我从封装的角度出发, 我会采用方案2.3
毕竟, 二次封装也是为了后续使用方便, 还是延用 u-navbar的自定义风格,比较合适点~

能看到这的, 都是不嫌弃我文笔凌乱的, 总之~ 谢谢观看, 如果有帮助或启发, 那就是本篇的荣幸~

再次记录

以上两个方法, 都是基于 在 vue组件 mounted的生命周期里, 让全局变量 that = this
这里面存在一个重大的问题
that永远是只能赋值一次, 且 会被下个navBar组件的创建加载过程, 触发 that指向的覆盖…
这就造成了bug
如: A->B->C
A默认返回 that= A.this
B默认返回 that= B.this
C自定义返回 that=C.this
一圈路由跳转下拉, that最终指向了 C ,
当C返回到B的时候, B的navbar的that 依然是C的this…
就算及时清理that, 也会存在that 无法在B的时候 重新被 that= B.this
(其实也不是没有, 可以利用B的onShow来重新挂载, 但是这样就显得非常麻烦…)
所以… 放弃拦截方案了,
直接把自定义事件透传给 u-tabbar 组件, 等后续我有解决办法了再行尝试

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/622766
推荐阅读
相关标签
  

闽ICP备14008679号