Vue3 实用的开发技巧

一、前言

Vue3已经发布很长时候了,官方也将默认版本切换到了vue3,Vue官网也发布了完善的中文文档,不知同志们是否已经开始使用了了呢?下面给大家介绍一些在开发中Vue3实用的开发技巧,让大家开发更加流畅。

二、开发技巧

1. 善用Composition API抽离通用逻辑

众所周知,vue3最大的新特性,当属Composition API 也叫组合api ,用好了它,在开发中可以去掉许多重复的逻辑代码,提高开发效率。

什么是Composition API

Vue2使用的是Options API,即选项api,使用 (datacomputedmethodswatch) 组件选项来组织逻辑通常都很有效。但是当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。

于是在vue3中为了解决当前痛点,避免在大型项目中出现代码逻辑分散,散落在当前组件的各个角落,从而变得难以维护,Composition API横空出世。

所谓Composition API就是在组件配置对象中声明 setup函数,我们可以将所有的逻辑封装在setup函数中,然后在配合vue3中提供的响应式API钩子函数、计算属性API等,我们就能达到和常规的选项式同样的效果,但是却拥有更清晰的代码以及逻辑层面的复用。

基础使用


通过以上代码我们可以看出,一个setup函数我们干出了在传统选项式中的所有事情。通过这些api的组合可以实现逻辑复用,这样我们就能封装很多通用逻辑,实现复用,大大提高开发效率。

举个例子:大家都用过复制剪贴板的功能,在通常情况下,利用navigator.clipboard.writeText 方法就能将复制内容写入剪切板。然而,细心的你会发现,其实赋值剪切板他是一个通用功能,比如:你做b端业务的,管理系统中到处充满了复制id、复制文案等功能。

于是我们就可以使用Composition API的将剪贴板功能的逻辑提取封装一个功能函数,实现复用。

import { watch, getCurrentScope, onScopeDispose, unref, ref } from "vue"
export const isString = (val) => typeof val === 'string'
export const noop = () => { }
export function unrefElement(elRef) {
    const plain = unref(elRef)// 拿到本来的值
    return (plain).$el ?? plain //前面的值为null、undefined,则取后面的值,否则都取前面的值
}
export function tryOnScopeDispose(fn) {
    // 如果有活跃的effect
    if (getCurrentScope()) {
        //在当前活跃的 effect 作用域上注册一个处理回调。该回调会在相关的 effect 作用域结束之后被调用
        //能代替onUmounted
        onScopeDispose(fn)
        return true
    }
    return false
}
//带有控件的setTimeout包装器。
export function useTimeoutFn(
    cb,// 回调
    interval,// 时间
    options = {},
) {
    const {
        immediate = true,
    } = options
 
    const isPending = ref(false)
 
    let timer
 
    function clear() {
        if (timer) {
            clearTimeout(timer)
            timer = null
        }
    }
 
    function stop() {
        isPending.value = false
        clear()
    }
 
    function start(...args) {
        // 清除上一次定时器
        clear()
        // 是否在pending 状态
        isPending.value = true
        // 重新启动定时器
        timer = setTimeout(() => {
            // 当定时器执行的时候结束pending状态
            isPending.value = false
            // 初始化定时器的id
            timer = null
            // 执行回调
            cb(...args)
        }, unref(interval))
    }
    if (immediate) {
        isPending.value = true
 
        start()
    }
 
    tryOnScopeDispose(stop)
 
    return {
        isPending,
        start,
        stop,
    }
}
//轻松使用EventListener。安装时使用addEventListener注册,卸载时自动移除EventListener。
export function useEventListener(...args) {
    let target
    let event
    let listener
    let options
    // 如果第一个参数是否是字符串
    if (isString(args[0])) {
        //结构内容
        [event, listener, options] = args
        target = window
    }
    else {
        [target, event, listener, options] = args
    }
    let cleanup = noop
    const stopWatch = watch(
        () => unrefElement(target),// 监听dom
        (el) => {
            cleanup() // 执行默认函数
            if (!el)
                return
            // 绑定事件el如果没有传入就绑定为window
            el.addEventListener(event, listener, options)
            // 重写函数方便改变的时候卸载
            cleanup = () => {
                el.removeEventListener(event, listener, options)
                cleanup = noop
            }
        },
        //flush: 'post' 模板引用侦听
        { immediate: true, flush: 'post' },
    )
    // 卸载
    const stop = () => {
        stopWatch()
        cleanup()
    }
 
    tryOnScopeDispose(stop)
 
    return stop
}
 
export function useClipboard(options = {}) {
    //获取配置
    const {
        navigator = window.navigator,
        read = false,
        source,
        copiedDuring = 1500,
    } = options
    //事件类型
    const events = ['copy', 'cut']
    // 判断当前浏览器知否支持clipboard
    const isSupported = Boolean(navigator && 'clipboard' in navigator)
    // 导出的text
    const text = ref('')
    //导出的copied
    const copied = ref(false)
    // 使用的的定时器钩子
    const timeout = useTimeoutFn(() => copied.value = false, copiedDuring)
 
    function updateText() {
        //解析系统剪贴板的文本内容返回一个Promise
        navigator.clipboard.readText().then((value) => {
            text.value = value
        })
    }
 
    if (isSupported && read) {
        // 绑定事件
        for (const event of events)
            useEventListener(event, updateText)
    }
    // 复制剪切板方法
    //navigator.clipboard.writeText 方法是异步的返回一个promise
    async function copy(value = unref(source)) {
        if (isSupported && value != null) {
            await navigator.clipboard.writeText(value)
            // 响应式的值,方便外部能动态获取
            text.value = value
            copied.value = true
            timeout.start()// copied.value = false 
        }
    }
 
    return {
        isSupported,
        text,
        copied,
        copy,
    }
}

这时我们就复用了复制的逻辑,如下代码中直接引入在模板中使用即可


以上代码参考vue版本的Composition API库所有完整版请参考

2. 善用 //btn2.vue

用JSX配合函数式组件来做一个容器组件

// 容器组件
import btn1 from './btn1.vue'
import btn2 from './btn2.vue'
export const renderFn = function (props, context) {
  return props.type == 1 ? {context.slots.default()} : {context.slots.default()}
}

引入业务组件

//业务组件

5. 善用依赖注入(Provide / Inject)

什么是依赖注入

依赖注入 用大白话来说:就是将实例变量传入到一个对象中去。

vue中的依赖注入

Vue3 实用的开发技巧_第1张图片

 基本用法

//parent.vue

 

子组件中注入进来

//child.vue
//使用inject 注入

 

因为依赖注入的特性,我们很大程度上代替了全局状态管理,相信谁都不想动不动就引入那繁琐的vuex吧。

接下来我们来举个例子,现在我么有个页面主题色,他贯穿所有组件,并且可以在某一些组件内更改主题色,那我们常规的解决方案中,就是装个vuex然后通过他的api下发颜色值,这时候如果想改,首先要发起dispatch到Action,然后在Action 中触发Mutation接着在Mutation中再去改state,如此一来,你是不是发现有点杀鸡用牛刀了,我就改个颜色而已。

我们来看有了依赖注入应该怎么处理,首先我们知道vue是单项数据流,也就是子组件不能修改父组件的内容,于是我们就应该想到使用 $attrs 使用它将方法透传给祖先组件,在组件组件中修改即可。

//子孙组件child1.vue

 

将当前子孙组件嵌入到child.vue中去,就能利用简洁的方式来修改颜色了。

6. 善用$attrs

$attrs 现在包含了_所有_传递给组件的 attribute,包括 class 和 style

$attrs在我们开发中到底有什么用呢?

通过他,我们可以做组件的事件以及props透传。

首先有一个标准化的组件,一般是组件库的组件等等。

//child.vue

 

接下来有一个包装组件,他对当前的标准化组件做修饰,从而使结果变成我们符合我们的预期的组件

//parent.vue
 
 

我们发现当前包装组件中使用了$attrs,通过他透传给标准化组件,这样一来,我们就能对比如element UI中的组件做增强以及包装处理,并且不用改动原组件的逻辑。

7. 优雅注册全局组件技巧

vue3的组件通常情况下使用vue提供的component 方法来完成全局组件的注册。

const app = Vue.createApp({})
 
app.component('component-a', {
  /* ... */
})
app.component('component-b', {
  /* ... */
})
app.component('component-c', {
  /* ... */
})
 
app.mount('#app')

使用时

我们发现可能使用注册vue插件的方式,也能完成组件注册,并且是优雅的!

vue插件注册

插件的格式

//plugins/index.js
export default {
  install: (app, options) => {
      // 这是插件的内容
  }
}

插件的使用

import { createApp } from 'vue'
import Plugin from './plugins/index.js'
const app = createApp(Root)
app.use(Plugin)
app.mount('#app')

其实插件的本质,就是在use的方法中调用插件中的install方法,那么这样一来,我们就能在install方法中注册组件。

index.js中抛出一个组件插件

// index.js
import component from './Cmponent.vue'
const component = {
    install:function(Vue){
        Vue.component('component-name',component)
    }  
//'component-name'这就是后面可以使用的组件的名字,install是默认的一个方法 component-name 是自定义的,我们可以按照具体的需求自己定义名字
}
// 导出该组件
export default component

组件注册

// 引入组件
import install from './index.js'; 
// 全局挂载utils
Vue.use(install);

上述案例中,就是一个简单的优雅的组件注册方式,大家可以发现包括element-plus、vant 等组件都是用如此方式注册组件。

8. v-model的最新用法

我们知道在vue2中想要模拟v-model,必须要子组件要接受一个value props 吐出来一个 叫input的emit

然而在vue3中他升级了

父组件中使用v-model


 

 

子组件中使用 title的props 以及规定吐出update:title的emit


 

有了以上语法糖,我们在封装组件的时候,就可以随心所欲了,比如我自己封装可以控制显示隐藏的组件我们就能使用v-model:visible单独控制组件的显示隐藏。使用正常的v-model 控制组件内部的其他逻辑,从而拥有使用更简洁的逻辑,表达相同的功能。

你可能感兴趣的:(前端,vuejs,JavaScript,vue.js,前端,javascript)