Maison  >  Article  >  9 compétences de développement vue3 pour améliorer l'efficacité et vous aider à quitter le travail plus tôt !

9 compétences de développement vue3 pour améliorer l'efficacité et vous aider à quitter le travail plus tôt !

青灯夜游
青灯夜游avant
2022-09-14 10:59:153275parcourir

vue3 est sorti depuis longtemps. Le responsable a également basculé la version par défaut vers vue3, et un document chinois complet est également apparu. Je me demande si les camarades l'ont déjà utilisé ? Je l'utilise depuis un certain temps et il est toujours assez fluide. J'aimerais partager une expérience de développement. J'espère que tout le monde pourra quitter le travail plus tôt

faire bon usage de h (createVNode) et des fonctions de rendu.

Nous savons qu'une magie exportée dans vue3 La fonction actuelle de la fonction createVNode peut créer un vdom. Ne sous-estimez pas vdom si nous en faisons bon usage, nous pouvons créer des effets inattendus Par exemple, si nous le voulons. implémenter un composant pop-up比如我们要实现一个弹窗组件

我们通常的思路是写一个组件在项目中引用进来,通过v-model来控制他的显示隐藏,但是这样有个问题,我们复用的时候的成本需要复制粘贴。我们没有办法来提高效率,比如封装成npm 通过调用js来使用。【相关推荐:vuejs视频教程

然而,有了 createVNode 和render 之后所有问题就迎刃而解了

// 我们先写一个弹窗组件
        const message = {
            setup() {
                const num = ref(1)
                return {
                    num
                }
            },
            template: `<div>
                        <div>{{num}}</div>
                        <div>这是一个弹窗</div>
                      </div>`
        }
  // 初始化组件生成vdom
  const vm = createVNode(message)
  // 创建容器,也可以用已经存在的
  const container = document.createElement('div')
  //render通过patch 变成dom
  render(vm, container)
// 弹窗挂到任何你想去的地方  
document.body.appendChild(container.firstElementChild)

经过上面这一通骚操作,我们发现我们可以将他封装为一个方法,放到任何想放的地方。

善用JSX/TSX

文档上说了,在绝大多数情况下,Vue 推荐使用模板语法来搭建 HTML。然而在某些使用场景下,我们真的需要用到 JavaScript 完全的编程能力。这时渲染函数就派上用场了。

jsx和模板语法的优势对比

jsx和模板语法都是vue 支持的的书写范畴,然后他们确有不同的使用场景,和方式,需要我们根据当前组件的实际情况,来酌情使用

什么是JSX

JSX 是一种 Javascript 的语法扩展,JSX = Javascript + XML,即在 Javascript 里面写 XML,因为 JSX 的这个特性,所以他即具备了 Javascript 的灵活性,同时又兼具 html 的语义化和直观性

模板语法的优势

  • 1、模板语法书写起来不怎么违和,我们就像在写html一样
  • 2、在vue3中由于模板的可遍历性,它能在编译阶段做更多优化,比如静态标记、块block、缓存事件处理程序等
  • 3、模板代码逻辑代码严格分开,可读性高
  • 4、对JS功底不那么好的人,记几个命令就能快速开发,上手简单
  • 5、vue官方插件的完美支持,代码格式化,语法高亮等

JSX的优势

  • 1、灵活、灵活、灵活(重要的事情说三遍)
  • 2、一个文件能写好多个组件
  • 3、只要JS功底好,就不用记忆那么多命令,上来就是一通输出
  • 4、JS和JSX混用,方法即声明即用,对于懂行的人来说逻辑清晰

对比

由于vue对于JSX的支持,社区里,也是争论来争论去,到底要分个高低,然后本渣认为,他俩本来没有高低,您觉得哪个适合,就用哪个即可,缺点放在对的地方他就是优势 要发扬咱们老前辈们传下来的中庸之道,做集大成者,将两者结合使用,就能发挥无敌功效,乱军之中博老板青睐。

接下来说一下本人的一点粗浅理解,我们知道组件类型,分为容器型组件和展示展示型组件 在一般情况下,容器型组件,他由于可能要对于当前展示型组件做一个标准化或者宰包装,那么此时容器型组件中用JSX就再好不过

举个例子:现在有个需求,我们有两个按钮,现在要做一个通过后台数据来选择展示哪一个按钮,我们通常的做法,是通过在一个模板中通过v-if去控制不同的组件

comme d'habitude L'idée est d'écrire un composant et de le référencer dans le projet, et de contrôler son affichage et son masquage via v-model. Cependant, il y a un problème lorsque nous. la réutilisation doit être copiée et collée. Nous n'avons aucun moyen d'améliorer l'efficacité, comme l'encapsuler dans npm et l'utiliser en appelant js. [Recommandations associées : tutoriel vidéo vuejs

]

Cependant, il are Après avoir ajouté createVNode et render, tous les problèmes sont résolus

//btn1.vue
<template>
  <div>
      这是btn1{{ num }}
      <slot></slot>
  </div>
</template>
<script>
import { ref, defineComponent } from &#39;vue&#39;
export default defineComponent({
  name: &#39;btn1&#39;,
  setup() {
      const num = ref(1)
      return { num }
  }
})
</script>
//btn2.vue
<template>
  <div>
      这是btn2{{ num }}
      <slot></slot>
  </div>
</template>
<script>
import { ref, defineComponent } from &#39;vue&#39;
export default defineComponent({
  name: &#39;btn2&#39;,
  setup() {
      const num = ref(2)
      return { num }
  }
})
</script>
// 容器组件
import btn1 from './btn1.vue'
import btn2 from './btn2.vue'
export const renderFn = function (props, context) {
  return props.type == 1 ? <btn1>{context.slots.default()}</btn1> : <btn2>{context.slots.default()}</btn2>
}

Après l'opération ci-dessus, nous avons constaté que nous pouvons l'encapsuler en tant que méthode et la placer où nous voulons.

Faites bon usage de JSX/TSX

La documentation indique que dans la plupart des cas, Vue recommande d'utiliser la syntaxe de modèle pour créer du HTML. Cependant, dans certains cas d’utilisation, nous devons réellement utiliser toutes les capacités de programmation de JavaScript. C'est là que la fonction de rendu est utile.

Comparaison des avantages de jsx et de la syntaxe des modèles

jsx et la syntaxe des modèles sont tous deux des catégories d'écriture prises en charge par vue, et elles ont des scénarios et des méthodes d'utilisation différents. Nous devons les utiliser en fonction de la situation réelle de. le composant actuel. À utiliser comme approprié

Qu'est-ce que JSX🎜🎜🎜JSX est une extension de syntaxe de Javascript = Javascript + XML, c'est-à-dire l'écriture de XML en Javascript. JSX, il a Il combine la flexibilité de Javascript avec la nature sémantique et intuitive du HTML. 🎜🎜🎜Avantages de la syntaxe des modèles🎜🎜
  • 1. La syntaxe des modèles n'est pas très incohérente à écrire, nous sommes comme si nous écrivions du HTML dans vue3, en raison de la traversabilité des modèles. Il peut effectuer davantage d'optimisations pendant la phase de compilation, telles que des balises statiques, des blocs, des gestionnaires d'événements mis en cache, etc.
  • 3. Le code logique du code du modèle est strictement séparé et a une grande lisibilité
  • >4. Pour ceux qui ne sont pas très bons en JS, ils peuvent développer rapidement en mémorisant quelques commandes, et c'est facile de démarrer
  • 5 Prise en charge parfaite du plug-in officiel Vue, formatage du code. , coloration syntaxique, etc.
🎜🎜Avantages de JSX🎜🎜
  • //业务组件
    <template>
     <renderfn>1111111</renderfn>
    </template>
    <script>
    import { renderFn } from &#39;./components&#39;
    console.log(renderFn)
    export default {
     components: {
       renderFn
     },
     setup() {
     },
    };
    </script>
  • //parent.vue
    <template>
        <child></child>
        <button>添加</button>
    </template>
    
    <script>
    import { defineComponent, provide, ref } from "vue";
    import Child from "./child.vue";
    export default defineComponent({
        components: {
            Child
        },
        setup() {
            const count = ref(0);
            const color = ref(&#39;#000&#39;)
            provide(&#39;count&#39;, count)
            provide(&#39;color&#39;, color)
            function setColor(val) {
                color.value = val
            }
            return {
                count,
                setColor
            }
        }
    })
    </script>
  • //child.vue
    //使用inject 注入
    <template>
        <div>这是注入的内容{{ count }}</div>
        <child1></child1>
    </template>
    
    <script>
    import { defineComponent, inject } from "vue";
    import child1 from &#39;./child1.vue&#39;
    export default defineComponent({
        components: {
            child1
        },
        setup(props, { attrs }) {
            const count = inject(&#39;count&#39;);
            console.log(count)
            console.log(attrs)
            return {
                count
            }
        }
    })
    </script>
  • //子孙组件child1.vue
    <template>
        <div>这是注入的内容的颜色</div>
    </template>
    
    <script>
    import { defineComponent, inject } from "vue";
    
    export default defineComponent({
        setup(props, { emit }) {
            const color = inject(&#39;color&#39;);
            function setColor() {
                console.log(0)
                emit(&#39;setColor&#39;, &#39;red&#39;)
            }
            return {
                color,
                setColor
            }
        }
    })
    </script>
🎜🎜Comparaison🎜🎜🎜En raison du support de vue pour JSX, il y a des débats dans la communauté pour savoir s'ils devraient être classés plus haut ou plus bas. Ensuite, je pense qu'il n'y a pas de différence entre eux. Quel que soit celui qui vous semble approprié, utilisez-le. , Les faiblesses sont des avantages lorsqu'elles sont placées au bon endroit Nous devons perpétuer le juste milieu transmis par nos prédécesseurs et être un maître en combinant les deux. vous pouvez exercer un effet invincible et gagner les faveurs du chaos. 🎜🎜 Ensuite, permettez-moi de parler de ma compréhension approximative. Nous savons que les types de composants sont divisés en composants de conteneur et composants d'affichage En général, les composants de conteneur peuvent être utilisés pour l'affichage actuel pour standardiser ou empaqueter le. composant de type, il serait préférable d'utiliser JSX dans le composant de type conteneur pour le moment🎜🎜Par exemple : il y a une exigence maintenant, et maintenant nous devons en créer un pour choisir celui à afficher en fonction du. données d'arrière-plan. Pour un bouton, notre approche habituelle consiste à contrôler différents composants via v-if dans un modèle🎜🎜Cependant, avec JSX et les composants fonctionnels, nous avons constaté que la logique est plus claire et le code plus concis, une qualité supérieure et plus emballée🎜🎜Jetons un coup d'oeil🎜🎜Intégrez d'abord deux composants🎜
<template>
    <div>测试compositionApi</div>
</template>
<script>
import { inject, ref, onMounted, computed, watch } from "vue";
export default {
    // setup起手
    setup(props, { attrs, emit, slots, expose }) {

        // 获取页面元素
        const composition = ref(null)
        // 依赖注入
        const count = inject(&#39;foo&#39;, &#39;1&#39;)
        // 响应式结合
        const num = ref(0)
        //钩子函数
        onMounted(() => {
            console.log(&#39;这是个钩子&#39;)
        })
        // 计算属性
        computed(() => num.value + 1)
        // 监听值的变化
        watch(count, (count, prevCount) => {
            console.log(&#39;这个值变了&#39;)
        })
        return {
            num,
            count
        }

    }
}
</script>
🎜Utilisez JSX avec des composants fonctionnels pour créer un composant conteneur🎜
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,
    }
}
🎜Introduisez des composants métier🎜
<template>
    <div>
        <p>
            <code>{{ text || '空' }}</code>
        </p>
        <input>
        <button>
            <span>复制</span>
            <span>复制中!</span>
        </button>
    </div>
    <p>您的浏览器不支持剪贴板API</p>
</template>
<script>
import { ref, getCurrentScope } from &#39;vue&#39;
import { useClipboard } from &#39;./copy.js&#39;
const input = ref(&#39;&#39;)
const { text, isSupported, copied, copy } = useClipboard()
console.log(text)// 复制内容
console.log(isSupported)// 是否支持复制剪切板api 
console.log(copied)//是否复制完成延迟
console.log(copy) // 复制方法
</script>
🎜🎜 Faites bon usage de l'injection de dépendances ( Provide/Inject)🎜🎜🎜Avant de faire bon usage de l'injection de dépendances, comprenons d'abord quelques concepts pour nous aider à comprendre le passé et le présent de l'injection de dépendances de manière plus complète🎜🎜🎜🎜Que sont IOC et DI🎜🎜🎜

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。

什么是依赖注入

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

vue中的依赖注入

在vue中,我们套用依赖注入的概念,其实就是在父组件中声明依赖,将他们注入到子孙组件实例中去,可以说是能够很大程度上代替全局状态管理的存在

9 compétences de développement vue3 pour améliorer lefficacité et vous aider à quitter le travail plus tôt !

我们先来看看他的基本用法

父组件中声明provide

//parent.vue
<template>
    <child></child>
    <button>添加</button>
</template>

<script>
import { defineComponent, provide, ref } from "vue";
import Child from "./child.vue";
export default defineComponent({
    components: {
        Child
    },
    setup() {
        const count = ref(0);
        const color = ref(&#39;#000&#39;)
        provide(&#39;count&#39;, count)
        provide(&#39;color&#39;, color)
        function setColor(val) {
            color.value = val
        }
        return {
            count,
            setColor
        }
    }
})
</script>

子组件中注入进来

//child.vue
//使用inject 注入
<template>
    <div>这是注入的内容{{ count }}</div>
    <child1></child1>
</template>

<script>
import { defineComponent, inject } from "vue";
import child1 from &#39;./child1.vue&#39;
export default defineComponent({
    components: {
        child1
    },
    setup(props, { attrs }) {
        const count = inject(&#39;count&#39;);
        console.log(count)
        console.log(attrs)
        return {
            count
        }
    }
})
</script>

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

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

我们来看有了依赖注入 应该怎么处理

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

我们来看代码

//子孙组件child1.vue
<template>
    <div>这是注入的内容的颜色</div>
</template>

<script>
import { defineComponent, inject } from "vue";

export default defineComponent({
    setup(props, { emit }) {
        const color = inject(&#39;color&#39;);
        function setColor() {
            console.log(0)
            emit(&#39;setColor&#39;, &#39;red&#39;)
        }
        return {
            color,
            setColor
        }
    }
})
</script>

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

善用Composition API抽离通用逻辑

众所周知,vue3最大的新特性,当属Composition API 也叫组合api ,用好了他,就是你在行业的竞争力,你也有了不世出的技能

我们一步步来分析

什么是Composition API

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

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

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

基础使用

<template>
    <div>测试compositionApi</div>
</template>
<script>
import { inject, ref, onMounted, computed, watch } from "vue";
export default {
    // setup起手
    setup(props, { attrs, emit, slots, expose }) {

        // 获取页面元素
        const composition = ref(null)
        // 依赖注入
        const count = inject(&#39;foo&#39;, &#39;1&#39;)
        // 响应式结合
        const num = ref(0)
        //钩子函数
        onMounted(() => {
            console.log(&#39;这是个钩子&#39;)
        })
        // 计算属性
        computed(() => num.value + 1)
        // 监听值的变化
        watch(count, (count, prevCount) => {
            console.log(&#39;这个值变了&#39;)
        })
        return {
            num,
            count
        }

    }
}
</script>

通过以上代码我们可以看出,一个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,
    }
}

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

<template>
    <div>
        <p>
            <code>{{ text || '空' }}</code>
        </p>
        <input>
        <button>
            <span>复制</span>
            <span>复制中!</span>
        </button>
    </div>
    <p>您的浏览器不支持剪贴板API</p>
</template>
<script>
import { ref, getCurrentScope } from &#39;vue&#39;
import { useClipboard } from &#39;./copy.js&#39;
const input = ref(&#39;&#39;)
const { text, isSupported, copied, copy } = useClipboard()
console.log(text)// 复制内容
console.log(isSupported)// 是否支持复制剪切板api 
console.log(copied)//是否复制完成延迟
console.log(copy) // 复制方法
</script>

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

善于使用getCurrentInstance 获取组件实例

getCurrentInstance 支持访问内部组件实例, 通常情况下他被放在 setup中获取组件实例,但是getCurrentInstance 只暴露给高阶使用场景,典型的比如在库中。

强烈反对在应用的代码中使用 getCurrentInstance。请不要把它当作在组合式 API 中获取 this 的替代方案来使用。

那他的作用是什么呢?

还是逻辑提取,用来代替Mixin,这是在复杂组件中,为了整个代码的可维护性,抽取通用逻辑这是必须要去做的事情,我们可以看element-plus 中table的复用逻辑,在逻辑提取中由于涉及获取props、proxy、emit 以及能通过当前组件获取父子组件的关系等,此时getCurrentInstance 的作用无可代替

如下element-plus代码中利用getCurrentInstance 获取父组件parent中的数据,分别保存到不同的变量中,我们只需要调用当前useMapState即可拿到数据

// 保存数据的逻辑封装
function useMapState<t>() {
  const instance = getCurrentInstance()
  const table = instance.parent as Table<t>
  const store = table.store
  const leftFixedLeafCount = computed(() => {
    return store.states.fixedLeafColumnsLength.value
  })
  const rightFixedLeafCount = computed(() => {
    return store.states.rightFixedColumns.value.length
  })
  const columnsCount = computed(() => {
    return store.states.columns.value.length
  })
  const leftFixedCount = computed(() => {
    return store.states.fixedColumns.value.length
  })
  const rightFixedCount = computed(() => {
    return store.states.rightFixedColumns.value.length
  })

  return {
    leftFixedLeafCount,
    rightFixedLeafCount,
    columnsCount,
    leftFixedCount,
    rightFixedCount,
    columns: store.states.columns,
  }
}</t></t>

善用$attrs

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

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

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

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

//child.vue
<template>
    <div>这是一个标准化组件</div>
    <input>
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
    props: [&#39;num&#39;],
    emits: [&#39;edit&#39;],
    setup(props, { emit }) {
        function setInput(val) {
            emit(&#39;edit&#39;, val.target.value)
        }
        return {
            setInput
        }
    }
})
</script>

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

//parent.vue
 <template>
    <div>这一层要做一个单独的包装</div>
    <child></child>
</template>

<script>
import { defineComponent } from "vue";
import child from &#39;./child.vue&#39;
export default defineComponent({
    components: {
        child
    },
    setup(props, { emit }) {
        function edit(val) {
            // 对返回的值做一个包装
            emit(&#39;edit&#39;, `${val}time`)
        }
        return {
            edit
        }
    }
})
</script>

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

优雅注册全局组件技巧

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

代码如下:

const app = Vue.createApp({})

app.component('component-a', {
  /* ... */
})
app.component('component-b', {
  /* ... */
})
app.component('component-c', {
  /* ... */
})

app.mount('#app')

使用时

<div>
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

然而经过大佬的奇技淫巧的开发,我们发现可能使用注册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 等组件都是用如此方式注册组件。

善用setup>

<script setup></script> 是在单文件组件 (SFC) 中使 的编译时语法糖。相比于普通的 <script></script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 Typescript 声明 props 和抛出事件。
  • 更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
  • 更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。

它能代替大多数的setup函数所表达的内容,具体使用方法,大家请看请移步文档

但是由于setup函数它能返回渲染函数的特性,在当前语法糖中却无法展示,于是遍寻资料,找到了一个折中的办法

<script>
import { ref,h } from &#39;vue&#39;

const msg = ref(&#39;Hello World!&#39;)
const dynode = () => h(&#39;div&#39;,msg.value);

</script>

<template>
    <dynode></dynode>
  <input>
</template>

如此一来,我们就能在语法糖中返回渲染函数了

v-model的最新用法

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

然而在vue3中他升级了

父组件中使用v-model

 <template>
    <child></child>
</template>

<script>
import { defineComponent, ref } from "vue";
import child from &#39;./child.vue&#39;
export default defineComponent({
    components: {
        child
    },
    setup(props, { emit }) {
        const pageTitle = ref(&#39;这是v-model&#39;)
        return {
            pageTitle
        }
    }
})
</script>

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

<template>
    <div>{{ title }}</div>
    <input>
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
    props: [&#39;title&#39;],
    emits: [&#39;update:title&#39;],
    setup(props, { emit }) {
        function setInput(val) {
            emit(&#39;update:title&#39;, val.target.value)
        }
        return {
            setInput
        }
    }
})
</script>

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

最后

目前开发中总结的经验就分享到这里了,错误之处,请大佬指出!

然后对vue源码有兴趣的大佬,可以看下这个文章 写给小白(自己)的vue3源码导读

也可以直接看本渣的源码解析github   vue-next-analysis

其中包含了vue源码执行思维导图,源码中的代码注释,整个源码的结构,各个功能的单独拆解等。错误之处请大佬指出!

(学习视频分享:web前端开发编程基础视频

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer