无限踩坑之如何在uniapp版微信小程序中做一个全局Notify组件?

1,634 阅读4分钟

前言:最近做一个todo类型的应用(因为本人忘事太严重了/(ㄒoㄒ)/~~),刚好手头上的微信云开发后台还有一点时间没用完,就想着做个微信小程序好了。而刚好有一个功能需要提示框,个人觉得uni-ui的提示框不太好看(低情商),就想着自己写一个也没多难,结果就踏上了漫漫踩坑路。

先来看一下预期的结果~

// 可以这样调用
Notify.tip({
    context:'这是一个tip',
    duration:2000,
    closeCallback:()=> console.log('close')
})
<!-- 只需要在页面中插入组件即可 -->
<MyNotify id='notify' />

这是预期的效果👇(没错,就是Vant和Antd的缝合版😭)

notifycation效果.gif

但是没关系,缝合只是塑造自己风格的一个过程而已,下面就开始踩坑吧~~

1、创建组件

当我们需要一个组件时,首先要做的事就是创建它(废话),需求是这个组件在出现时会伴随动画,刚好uni-ui组件库有一个uni-transition组件可供使用,那就直接上手吧~

根据官方文档的描述,uni-transition有7个默认动画,当然也可以自定义动画,不过为了简单方便我选择直接使用官方的slide-out动画(懒鬼本性~)

贴个代码(具体还有些样式,没啥特别的,看着写就行)

<uni-transition
        v-if="visible"
        class="transition"
        :show="visible"
        mode-class="slide-top"
    >
        <view
            class="message-container"
        >
           <span class="message-content">这是一个tip</span>
        </view>
    </uni-transition>

下面是第一版的效果👇大概就这个意思(别问我问什么和刚才的预期效果不一样了,问就是项目特色)

第一版效果.gif

2、加个icon

既然是消息提示,那就得有个icon来标识这个消息到底是什么类型的,不然就一个背景颜色有点太过单调了 那就上iconfont找几个自己觉得好看的svg凑合用吧~

图片.png 天真的我以为只要在iconfont上把自动生成的代码贴到文件里,就可以直接用了,然而我却忘了现在写的是坑比月球多的微信小程序,不管怎么引入最后的图标都是一个绝望的矩形

经过我不懈地百度,才知道原来在引入之前需要把.tff文件的代码转换成64位的字符,不然小程序不认(详情见文章转换工具

终于的终于,才把svg引入到项目中可以用在组件里了😭

带svg效果.gif

3、函数调用组件

既然缝合那就缝合到底喽~Vant Weapp函数调用组件的形式我个人挺喜欢的,虽然由于小程序的限制不能做到完全使用函数调用(就是省略往页面添加组件的步骤),但也是非常简洁明了的方法

这个部分没啥好说的,我个人比较喜欢的写法,看起来舒服一点

class NotifyClass {
    tip(config) {
        // 调用组件内部函数传递参数并通知组件展示
    }
    done(config) {
        ...
    }
    fail(config) {
        ...
    }
    warn(config) {
        ...
    }
}
const Notify = new NotifyClass()
export default Notify

其实函数调用组件的关键点就在于如何获取页面的组件实例,通过调用组件的实例方法来展示组件

web端我们只要使用appendChild()来往页面中添加dom节点就可以直接把组件展示到页面中了,但是微信小程序并没有dom的概念,那我们怎么才能在调用函数的时候通知组件展示呢?

最开始我使用的是,uni.$on的总线传递参数的方法,但是这种方法就导致组件只能在生命周期中调用才能展示,而很多场景下Notify都是应用于函数回调中的,因此得换一个思路。

其实组件也只是页面中的一个节点,那如果能直接拿到当前页面的实例对象,然后查找到组件节点获取实例对象,不就可以准确地通知组件展示了吗

在反复阅读Vant Weapp的源码后我发现,小程序自身提供的getCurentPages()方法可以完美的实现这个功能!(还是我太菜了😭找了好久)

const pages = getCurrentPages()
const currentPage = pages[pages.length - 1] // 这个就是当前页面的实例对象

那么问题来了,获取了实例对象之后,怎么准确地查找到组件的实例对象呢?莫慌,贴心的小程序还为currentPage实例对象准备了selectComponent()方法,只要给组件添加一个特定的选择器然后直接作为参数传入方法就可以得到组件的实例对象了~

但是这里还有一个坑(如果你用的是Vue3的话,Vue2不知道会不会也有这种情况),如果直接使用获取的组件的实例对象来调用组件的方法是不可行的,还需要把组件的实例对象中的$vm属性提取出来才能使用方法

<script>
    const notify = currentPages.selectComponent('#notify).$vm
    notify.show()
</script>

<MyNotify id='notify' />

下面是NotifyClass的完整代码👇

class NotifyClass {
    tip(config) {
        this.sendDataToInstance(this.assignConfig(config, 'tip'))
    }
    done(config) {
        this.sendDataToInstance(this.assignConfig(config, 'done'))
    }
    fail(config) {
        this.sendDataToInstance(this.assignConfig(config, 'fail'))
    }
    warn(config) {
        this.sendDataToInstance(this.assignConfig(config, 'warn'))
    }
    getContext() {
        const pages = getCurrentPages()
        return pages[pages.length - 1]
    }
    assignConfig(config, type) {
        return Object.assign({}, config, { type })
    }
    sendDataToInstance(config) {
        let notify = this.getContext().selectComponent('#notify').$vm
        if (notify) {
            notify.show(config)
        }
    }
}

正当我兴高采烈以为大功告成的时候,无情的控制台再次让我认清了我的菜鸡身份。

图片.png

如果你和我一样用的Vue3 + setup语法糖,那么请在组件中使用defineExpose()show函数暴露出来,以避免被无情的控制台伤害。

那么剩下的工作就是逐步完善组件内部的函数了,这个部分就不赘述了,根据个人喜好敲就行,照例贴下我的作为反面案例~

const show = (data) => {
    config.value = Object.assign({}, {
        context: data.context,
        duration: data.duration ?? 1500,
        type: data.type ?? 'tip',
        closeCallback: data.closeCallback ?? '',
    })
    defineNotifyIcon(data.type ?? 'tip')
    openNotify(config.value)
}

let visible = ref(false),
    openTimer,
    closeTimer
const openNotify = (config) => {
    visible.value = true
    openTimer = setTimeout(() => {
        closeNotify()
        clearTimeout(openTimer)
    }, config.duration ?? 1500)
}

const closeNotify = () => {
    visible.value = false
    closeTimer = setTimeout(() => {
        config.value.closeCallback ? config.value.closeCallback() : ''
        clearTimeout(closeTimer)
    }, 100)
}

const defineNotifyIcon = (iconName) => {
    switch (iconName) {
        case 'done':
            ...
            break
        case 'fail':
            ...
            break
        case 'warn':
            ...
            break
        case 'tip':
            ...
            break
    }
}

defineExpose({
    show,
})

至此,一个简单的Notify组件就大功告成了,当然这个组件还有很多需要优化的地方,例如当重复多次调用时如何处理弹窗消失不够平滑等,这些可以根据个人需要进行完善和改进

最后,如果发现文中有任何需要修改或者改进的地方,请务必提出,不胜感激