重学vue(2, 3)及其生态+TypeScript 之 vue(下)

简介: 重学vue(2, 3)及其生态+TypeScript 之 vue

动画


Vue中为我们提供一些内置组件和对应的API来完成动画,利用它们我们可以方便的实现过渡动画效果。


没有动画的情况下,整个内容的显示和隐藏会非常的生硬: 如果我们希望给单元素或者组件实现过渡动画,可以使用 transition 内置组件来完成动画。


Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡:


  • 条件渲染 (使用 v-if)条件展示 (使用 v-show)


  • 动态组件


  • 组件根节点


当插入或删除包含在 transition 组件中的元素时,Vue 将会做以下处理:


  • 自动嗅探目标元素是否应用了CSS过渡(transition)或者动画(animation),如果有,那么在恰当的时机添加/删除 CSS类名。


  • 如果 transition 组件提供了JavaScript钩子函数,这些钩子函数将在恰当的时机被调用。


  • 如果没有找到JavaScript钩子并且也没有检测到CSS过渡/动画,DOM插入、删除操作将会立即执行。将不会有动画效果。


<button @click="isShow = !isShow">显示/隐藏</button>
    <transition name="zh">
      <h2 v-if="isShow">Hello World</h2>
    </transition>
    <style scoped>
        .zh-enter-from,
        .zh-leave-to {
          opacity: 0;
        }
        .zh-enter-to,
        .zh-leave-from {
          opacity: 1;
        }
        // 为添加过度属性,将不会出现动画效果。
        .zh-enter-active,
        .zh-leave-active {
          /* transition: opacity 2s ease; */
        }
    </style>


class属性添加时机


网络异常,图片无法展示
|


过渡动画class


Vue就是帮助我们在这些class之间来回切换完成的动画:


  • v-enter-from:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。


  • v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。


  • v-enter-to:定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter-from 被移除),在过渡/动画完成之后移除。


  • v-leave-from:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。


  • v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。


  • v-leave-to:离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave-from 被删除),在过渡/动画完成之后移除。 当我们没有对transition组件命名时,即给出name属性,那么它将默认使用v当做name属性值。即所有的class是以 v- 作为默认前缀。给定name属性后,将会根据name属性值多为class前缀。


css动画和css过度


vue中,完成动画我们需要借助动画animation和过度transition来完成。我们需要在 v--enter-activev-leave-active class属性值中定义上面两个属性即可。


// css过度
    .zh-enter-from,
    .zh-leave-to {
      opacity: 0;
    }
    .zh-enter-to,
    .zh-leave-from {
      opacity: 1;
    }
    .zh-enter-active,
    .zh-leave-active {
      transition: opacity 2s ease;
    }


// css动画
  .zh-enter-active {
    animation: bounce 1s ease;
  }
  .zh-leave-active {
    animation: bounce 1s ease reverse;
  }
  @keyframes bounce {
    0% {
      transform: scale(0)
    }
    50% {
      transform: scale(1.2);
    }
    100% {
      transform: scale(1);
    }
  }


transition组件属性


name属性


为了设置class前缀。


type属性


Vue为了知道过渡的完成,内部是在监听 transitionend 或 animationend,到底使用哪一个取决于元素应用的CSS规则:


  • 如果我们只是使用了其中的一个,那么Vue能自动识别类型并设置监听。但是如果我们同时使用了过渡和动画呢?


  • 并且在这个情况下可能某一个动画执行结束时,另外一个动画还没有结束。


  • 在这种情况下,我们可以设置 type 属性为 animation 或者 transition 来明确的告知Vue监听的类型。并且可以通过duration显式的指定动画时间。


<transition name="zh" type="transition" :duration="{enter: 800, leave: 1000}">
      <h2 class="title" v-if="isShow">Hello World</h2>
    </transition>


duration属性


设置过度动画的时间。


duration可以设置两种类型的值:


  • pnumber类型:同时设置进入和离开的过渡时间。


  • pobject类型:分别设置进入和离开的过渡时间。注意:显式设置的值会覆盖css过度和动画中指定的值。


mode属性


默认情况下,进入和离开同时发生。如果想改变默认状态。我们就需要添加mode属性。


  • in-out: 新元素先进行过渡,完成之后当前元素过渡离开。


  • out-in: 当前元素先进行过渡,完成之后新元素过渡进入。大多数情况下,我们需要前一个动画结束时,后一个动画开始。所以需要使用out-in


appear属性


默认情况下,首次渲染的时候是没有动画的,如果我们希望给他添加上去动画,那么就可以增加appear;ture


css属性


当我们通过js来操作动画的时候,我们就不需要vue来检测css中的动画了。所以需要将css设置为false。默认情况下css: true


JavaScript 钩子


当我们想要在动画执行的各个阶段,做一些事情。我们就可以使用这个钩子。


  • @before-enter="beforeEnter",执行到v-enter-from阶段


  • @enter="enter",执行到v-enter-active


  • @after-enter="afterEnter",执行到v-enter-to阶段


  • @enter-cancelled="enterCancelled"


  • @before-leave="beforeLeave",执行到v-leave-to阶段


  • @leave="leave",执行到v-leave-active阶段


  • @after-leave="afterLeave",执行到v-leave-to阶段


  • @leave-cancelled="leaveCancelled",执行到v-enter-to阶段 当只用 JavaScript 过渡的时候,在 enterleave 钩中必须使用 done 进行回调。否则,它们将被同步调用,过渡会立即完成。这时,我们可以添加 :css="false",让 Vue 会跳过 CSS 的检测,除了性能略高之外,这可以避免过渡过程中 CSS 规则的影响。


上面的钩子可以和一些js动画库来实现动画。例如jsap库。 它可以通过JavaScript为CSS属性、SVG、Canvas等设置动画,并且是浏览器兼容的。其中有两个比较重要的API来实现动画。


  • jsap.from(el, options): 表示动画从什么状态开始。


  • jsap.to(el, options): 表示动画以什么状态结束。 其中el表示动画作用的元素。options表示动画css属性。


// 进入动画
    enter(el, done) {
      console.log('enter')
      // from是表示开始的位置
      gsap.from(el, {
        scale: 0,
        x: 200,
        onComplete: done,
      })
    },
    // 离开动画
    leave(el, done) {
      console.log('leave')
      // to表示结束的位置
      gsap.to(el, {
        scale: 0,
        x: 200,
        onComplete: done,
      })
    },


我们来使用jsap库实现一个滚动数字动画。


<template>
  <div class="app">
    <input type="number" step="100" v-model="counter">
    <h2>当前计数: {{showNumber.toFixed(0)}}</h2>
  </div>
</template>
<script>
  import gsap from 'gsap';
  export default {
    data() {
      return {
        counter: 0,
        showNumber: 0
      }
    },
    watch: {
      counter(newValue) {
        gsap.to(this, {duration: 1, showNumber: newValue})
      }
    }
  }
</script>


网络异常,图片无法展示
|


其他动画知识,请访问官网,讲的非常详细。


v3.cn.vuejs.org/guide/trans…


composition API


一下使用的API都需要在vue中导入。


Options API的弊端


在Vue2中,我们编写组件的方式是Options API:


  • Options API的一大特点就是在对应的属性中编写对应的功能模块。比如data定义数据、methods中定义方法、computed中定义计算属性、watch中监听属性改变,也包括生命周期钩子;


但是这种代码有一个很大的弊端:


  • 当我们实现某一个功能时,这个功能对应的代码逻辑会被拆分到各个属性中。


  • 当我们组件变得更大、更复杂时,逻辑关注点的列表就会增长,那么同一个功能的逻辑就会被拆分的很分散。


  • 尤其对于那些一开始没有编写这些组件的人来说,这个组件的代码是难以阅读和理解的(阅读组件的其他人)。


composition API介绍


composition API的容器


其中composition API都是写在我们setup函数中的。并且在setup函数中是不能使用this的,因为vue内部再调用setup函数的时候没有绑定this。


下面我们就来研究一些setup函数。


  • 它主要有两个参数:


  • 第一个参数:props。组件接收的属性


  • 第二个参数:context。组件上下文对象


  • attrs:所有的非prop的attribute。


  • slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用)。


  • emit:当我们组件内部需要发出事件时会用到emit。


  • expose:当通过ref获取该组件时,向外暴露的一些setup中的数据。那我们如何定义响应式数据呢?


composition API处理数据


reactive: 将多个数据变成响应式数据


  • 当我们使用reactive函数处理我们的数据之后,数据再次被使用时就会进行依赖收集。


  • 当数据发生改变时,所有收集到的依赖都是进行对应的响应式操作(比如更新界面)。


  • 事实上,我们编写的data选项,也是在内部交给了reactive函数将其编程响应式对象的。


const state = reactive({
      counter: 100,
      name: 'zh'
    })


ref: 将单个数据变成响应式。


reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告。所以我们需要使用ref。


  • ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源。


  • 它内部的值是在ref的 value 属性中被维护的。


  • 在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式来使用。


  • 在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式。注意:ref对象在模板中的解包是浅层的解包


网络异常,图片无法展示
|


readonly: 返回一个传入的对象的只读代理


我们通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,我们传入给其他地方(组件)的这个响应式对象希望在另外一个地方(组件)被使用,但是不能被修改。例如我们想要将provide提供数数据传递给子孙组件,我们就可以使用readonly,让其是只读的,不能再子孙组件中修改。


该API返回普通对象, ref对象, reactive对象的只读代理。


  • readonly返回的对象都是不允许修改的。


  • 但是经过readonly处理的原来的对象是允许被修改的。


  • 其实本质上就是readonly返回的对象的setter方法被劫持了而已。


// 1.普通对象
      const info1 = {name: "zh"};
      const readonlyInfo1 = readonly(info1);
      // 2.响应式的对象reactive
      const info2 = reactive({
        name: "zh"
      })
      const readonlyInfo2 = readonly(info2);
      // 3.响应式的对象ref
      const info3 = ref("zh");
      const readonlyInfo3 = readonly(info3);


toRefs: 将传入的对象变成ref对象


当我们想要对reactive对象做解构的时候,直接解构,将使数据失去响应式。如果我们用toRefs将其包裹后解构,数据依然是响应式的。这种做法相当于已经在reactive中的属性和ref.value之间建立了 链接,任何一个修改都会引起另外一个变化


const info = reactive({ name: 'zh', age: 22 })
    // 1.toRefs: 将reactive对象中的所有属性都转成ref, 建立链接
    let { name, age } = toRefs(info)


toRef: 将指定传入的对象那个属性变成ref对象


如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法。


const info = reactive({ name: 'zh', age: 22 })
    // 将info对象中的age属性变成ref对象。
    let age = toRef(info, 'age')


isProxy


  • 检查对象是否是由 reactive 或 readonly创建的 proxy。


isReactive


  • 检查对象是否是由 reactive创建的响应式代理。


  • 如果该代理是 readonly 创建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true。


isReadonly


  • 检查对象是否是由 readonly 创建的只读代理。


toRaw


  • 返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。


shallowReactive


  • 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。


shallowReadonly


  • 创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。


unref


  • 如果我们想要获取一个ref引用中的value,那么也可以通过unref方法。


  • 如果参数是一个 ref,则返回内部值,否则返回参数本身。


  • 这是 val = isRef(val) ? val.value : val 的语法糖函数。


isRef


  • 判断值是否是一个ref对象。


shallowRef


  • 创建一个浅层的ref对象。只有修改了ref对象,他才是响应式的。如果修改内部对象,将不是响应式的。 这个api和shallowReactive不一样。后者是将传入的对象第一层变成一个响应式的,修改第一层对象属性依旧是可以做到响应式的。但是这个api只是修改ref对象才会是响应式的。


const info = shallowRef({ name: 'zh' })
    const changeInfo = () => {
      // 只有这样修改才是响应式的
      info.value = { name: 'llm' }
      // 这样改不是响应式的
      info.value.name = 'llm'
    }


triggerRef


  • 手动触发和 shallowRef 相关联的副作用。


const info = shallowRef({name: "zh"})
      const changeInfo = () => {
        info.value.name = "llm";
        // 让其是响应式的
        triggerRef(info);
      }


customRef: 自定义ref对象


创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制:


  • 它需要一个工厂函数,该函数接受 track 和 trigger 函数作为参数。


  • 并且应该返回一个带有 get 和 set 的对象。


import { customRef } from 'vue';
    // 自定义ref
    export default function(value, delay = 300) {
      let timer = null;
      return customRef((track, trigger) => {
        return {
          get() {
            track();
            return value;
          },
          set(newValue) {
            clearTimeout(timer);
            timer = setTimeout(() => {
              value = newValue;
              trigger();
            }, delay);
          }
        }
      })
    }
    // 直接使用
    const message = debounceRef("Hello World");


获取当前组件上下文 getCurrentInstance


由于setup函数中,没有绑定this。所以我们获取不到this,即当前组件对象。


如果我们想要获取呢?


vue提供了getCurrentInstance, 可以让我们获取当前组件对象。调用该API即可。


如果想要获取组件提供的全局属性。我们需要获取全局对象。


getCurrentInstance().appContext.config.globalProperties


计算属性 computed


当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理。


computed可以传入两种参数:


  • 接收一个getter函数,并为 getter 函数指定返回值


  • 接收一个具有 get 和 set 的对象,返回一个可变的(可读写)ref 对象。当修改computed时非常有用。


  • computed返回一个ref对象。


// 1.用法一: 传入一个getter函数
      // computed的返回值是一个ref对象
      const fullName = computed(() => firstName.value + " " + lastName.value);
      // 2.用法二: 传入一个对象, 对象包含getter/setter
      const fullName = computed({
        get: () => firstName.value + " " + lastName.value,
        set(newValue) {
          const names = newValue.split(" ");
          firstName.value = names[0];
          lastName.value = names[1];
        }
      });


监听数据 watch / watchEffect


当数据变化时执行某一些操作。我们可以通过watch / watchEffect来监听。


二者的区别:


  • watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)。


  • watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的。


  • watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行,而 watch 不需要,因为他一开始就指定了依赖。watchEffect


  • 首先,watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖。


  • 其次,只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行。


  • 并且返回一个函数,这个函数可以用来取消watchEffect的监听。或者组件卸载后自动停止监听。


// watchEffect: 自动收集响应式的依赖
      const name = ref("zh");
      const age = ref(20);
      const stop = watchEffect(() => {
        console.log("name:", name.value, "age:", age.value);
      });
      const changeName = () => name.value = "llm"
      const changeAge = () => {
        age.value++;
        // 当age属性大于30时,就取消watchEffect的监听。
        if (age.value > 30) {
          stop();
        }
      }


watchEffect的执行时机。默认情况下,watchEffect是在视图更新之前执行副作用函数。如果我们想要改变他的执行时机,怎么改变呢?


watchEffect还可以传入第二个参数,为一个对象。设置flush顺序性就可以改变watchEffect的执行时机。


flush: 'pre'(默认,在视图更新前执行) 
        'post'(在视图更新后执行)
        'sync'(同步触发,会出现问题,少用)


const title = ref(null);
      watchEffect(() => {
        console.log(title.value); // 获取dom元素。
      }, {
        flush: "post" // 在视图更新后执行,会减少watchEffect无效的执行。因为第一次执行会获取null
      })


watchEffect还可以清除副作用


什么是清除副作用呢?


比如在开发中我们需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,我们停止了侦听器,或者侦听器侦听函数被再次执行了。那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用。


在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate, 当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数。我们可以在传入的回调函数中,执行一些清楚工作。就类似于节流函数。


watchEffect((onInvalidate) => {
            const timer = setTimeout(() => {
              console.log("网络请求成功~");
            }, 2000)
            onInvalidate(() => {
              // 在这个函数中清除额外的副作用
              clearTimeout(timer);
              console.log("onInvalidate");
            })
      });


watch


watch的API完全等同于组件watch选项的Property:


  • watch需要侦听特定的数据源,并在回调函数中执行副作用。


  • 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调。 监听单个数据源


watch侦听函数的数据源有两种类型:


  • 一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref)。


  • 直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref)。 如果传入的是一个ref响应式对象或者getter并返回一个普通对象(通过...结构),监听函数中都是普通对象 / 普通值。而非ref, reactive对象。


网络异常,图片无法展示
|


网络异常,图片无法展示
|


如果传入的是一个reactive对象,那么监听函数中是reactive对象。并且新值和旧值都是一样的。


// 改变name
    const info = reactive({ name: 'zh', age: 20 })
    watch(info, (newValue, oldValue) => {
        // newValue, oldValue都是reactive对象,并且返回的都是新值。
      console.log('newValue:', newValue, 'oldValue:', oldValue)
    })


网络异常,图片无法展示
|


监听多个值


可以将多个源放在数组中。


// 1.定义可响应式的对象
    const info = reactive({ name: 'zh', age: 20 })
    const name = ref('zh')
    // 2.侦听器watch
    watch(
      [() => ({ ...info }), name],
      ([newInfo, newName], [oldInfo, oldName]) => {
        console.log(newInfo, newName, oldInfo, oldName)
      }
    )


watch的选项


如果想要深度监听对象,我们就需要给watch传入第二个参数。用于深度监听或者立即执行。


默认情况下,watch对于监听展开的reactive对象不能深度监听,但是我们如果先改变第一层的属性即info.name,那么info.friend.name也会被改变。但是如果只改变info.friend.name,是不会触发watch回调的。只有配置了deep: true,才会被监听到。


const info = reactive({
      name: 'zh',
      age: 18,
      friend: {
        name: 'jcl',
      },
    })
    // 2.侦听器watch
    watch(
      () => ({ ...info }),
      (newInfo, oldInfo) => {
        console.log(newInfo, oldInfo)
      },
      {
        // deep: true,
        // immediate: true,
      }
    )
    const changeData = () => {
      // info.name = 'llm'
      info.friend.name = 'zheng'
    }


网络异常,图片无法展示
|


但是对于直接监听reactive对象,他会自动深度监听,内部有设置deep: true。


const info = reactive({
      name: 'zh',
      age: 18,
      friend: {
        name: 'jcl',
      },
    })
     watch(info, (newInfo, oldInfo) => {
      console.log(newInfo, oldInfo)
    })
    const changeData = () => {
      info.friend.name = 'zheng'
    }


网络异常,图片无法展示
|


对于ref传入的对象,也是默认没有深度监听的。并且监听函数中参数都是一个对象。


const info = ref({
      name: 'zh',
      age: 18,
      friend: {
        name: 'jcl',
      },
    })
     watch(
      info,
      (newInfo, oldInfo) => {
        console.log(newInfo, oldInfo)
      },
      {
      // 需要加上,才会深度监听
        deep: true,
      }
    )
    const changeData = () => {
      info.value.friend.name = 'zheng'
    }


网络异常,图片无法展示
|


生命周期


options API中生命周期和composition API中生命周期对比


选项式 API Hook inside setup
beforeCreate Not needed*
created Not needed*
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered
activated onActivated
deactivated onDeactivated


provide 和 inject


如果我们想要向子孙组件传递数据,我们就可以通过provide API来完成。用inject API来接收传递的数据。


由于我们向下传递的数据,不需要子孙组件修改,只允许我们自己修改,然后影响下层组件,所以。可以使用readonlyAPI来传递只读数据。


provide可以传入两个参数:


  • 第一个是提供的属性名称


  • 第二个是传入的数据 inject可以传入两个参数:


  • 第一个是接收到provide传递的属性名


  • 第二个是提供默认值


// 向下传递
    const obj = reactive({
      name: 'zh',
      age: 20,
    })
    const name = ref('llm')
    provide('obj', readonly(obj))
    provide('name', readonly(name))


// 接收
    let obj = inject('obj')
    let name = inject('name')


如果我们真的想要在子孙组件中修改数据,我们可以提供一个函数,接收子孙组件的数据,然后在该祖先组件中修改。


// provide
    //这个也传入到inject中,供其修改
    const updateName = (e, val) => {
      console.log('e---------------', e, val)
      name.value = val
    }


// inject
    let updateName = inject('updateName')
    <button @click="(e) => {updateName(e, '子孙组件中修改name的数据')}">改变name</button>


渲染函数 render


Vue推荐在绝大数情况下使用模板来创建你的HTML,然后一些特殊的场景,你真的需要JavaScript的完全编程的能力,这个时候你可以使用 渲染函数 ,它比模板更接近编译器。


Vue在生成真实的DOM之前,会将我们的节点转换成VNode,而VNode组合在一起形成一颗树结构,就是虚 拟DOM(VDOM)。


事实上,我们之前编写的 template 中的HTML 最终也是使用渲染函数生成对应的VNode。


那么,如果你想充分的利用JavaScript的编程能力,我们可以自己来编写 createVNode 函数,生成对应的VNode。


我们可以通过vue提供的h函数来实现。h() 函数是一个用于创建 vnode 的一个函数。其实准备的命名是 createVNode() 函数,但是为了简便在Vue将之简化为 h() 函数。

下面就是h函数的参数传递和用法。


h(
      // {String | Object | Function} tag
      // 一个 HTML 标签名、一个组件、一个异步组件、或
      // 一个函数式组件。
      //
      // 必需的。
      'div',
      // {Object} props
      // 与 attribute、prop 和事件相对应的对象。
      // 这会在模板中用到。
      //
      // 可选的。
      {},
      // {String | Array | Object} children
      // 子 VNodes, 使用 `h()` 构建,
      // 或使用字符串获取 "文本 VNode" 或者
      // 有插槽的对象。
      //
      // 可选的。
      [
        'Some text comes first.',
        h('h1', 'A headline'),
        h(MyComponent, {
          someProp: 'foobar'
        })
      ]
    )


注意:如果没有props,那么通常可以将children作为第二个参数传入。如果会产生歧义,可以将null作为第二个参数传入,将children作为第三个参数传入。


h函数的基本使用。h函数可以在两个地方使用:


  • render函数选项中。作为render函数的返回值。如果想要使用事件。我们可以传入on+事件名的属性,函数作为他的值。


data() {
      return {
        counter: 0
      }
    },
    render() {
      return h("div", {class: "app"}, [
        h("h2", null, `当前计数: ${this.counter}`),
        h("button", {
          onClick: () => this.counter++
        }, "+1"),
        h("button", {
          onClick: () => this.counter--
        }, "-1"),
      ])
    }


  • setup函数选项中(setup本身需要是一个函数类型,函数再返回h函数创建的VNode)。作为setup函数的返回值。


setup() {
      const counter = ref(0);
      return () => {
        return h("div", {class: "app"}, [
          h("h2", null, `当前计数: ${counter.value}`),
          h("button", {
            onClick: () => counter.value++
          }, "+1"),
          h("button", {
            onClick: () => counter.value--
          }, "-1"),
        ])
      }
    }


h函数中使用插槽。可以通过三元运算符,提供默认插槽内容。


HelloWorld.vue
// 子组件提供插槽和插槽prop
 setup() {
    const instance = getCurrentInstance().ctx.$slots
    return () =>
      h('div', {}, [
        instance.first
          ? instance.first({ first: 'first=========' })
          : '默认插槽first',
        instance.second
          ? instance.second({ second: 'second=========' })
          : '默认插槽second',
      ])
  },


// 使用渲染函数插槽
setup() {
    return () =>
      h(
        HelloWorld,
        {class: 'hello-world'},
        {
        // 向HelloWorld传入插槽内容,并使用提供的插槽数据
          first: (slotProps) => h('span', slotProps.first),
          second: (slotProps) => h('span', slotProps.second),
        }
      )
  },


网络异常,图片无法展示
|


以上只是一些个人实验,如果想要了解更多,请访问官网v3.cn.vuejs.org/guide/rende…


自定义指令


在Vue的模板语法中我们学习过各种各样的指令:v-show、v-for、v-model等等,除了使用这些指令之外,Vue也允许我们来自定义自己的指令。用来复用代码,方便操作。

注意:在Vue中,代码的复用和抽象主要还是通过组件通常在某些情况下,你需要对DOM元素进行底层操作,这个时候就会用到自定义指令。


自定义指令分为两种:


  • 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用。


  • 自定义全局指令:app的 directive 方法,可以在任意组件中被使用。 下面我们来自定义一个自动获取焦点的指令。


局部指令: 直接在dom上通过v-focus使用即可


// 局部指令
    directives: {
      focus: {
        mounted(el, bindings, vnode, preVnode) {
          el.focus();
        }
      }
    }


全局指令


app.directive("focus", {
    mounted(el, bindings, vnode, preVnode) {
      el.focus();
    }
})


下面我们就来介绍一下自定义指令中的生命周期函数


一个指令定义的对象,Vue提供了如下的几个钩子函数:注意: 这些生命周期函数名称和vue2有一些不同


  • created:在绑定元素的 attribute 或事件监听器被应用之前调用;


  • beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用;


  • mounted:在绑定元素的父组件被挂载后调用;


  • beforeUpdate:在更新包含组件的 VNode 之前调用;


  • updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用;


  • beforeUnmount:在卸载绑定元素的父组件之前调用;


  • unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次; 生命周期函数的参数:其中el, binding比较常用


  • el: 指令绑定到的元素。这可用于直接操作 DOM。


  • binding: 包含以下 property 的对象。


  • instance:使用指令的组件实例。


  • value:传递给指令的值。例如,在 v-my-directive="1 + 1" 中,该值为 2


  • oldValue:先前的值,仅在 beforeUpdateupdated 中可用。值是否已更改都可用。


  • arg:参数传递给指令 (如果有)。例如在 v-my-directive:foo 中,arg 为 "foo"


  • modifiers:包含修饰符 (如果有) 的对象。例如在 v-my-directive.foo.bar 中,修饰符对象为 {foo: true,bar: true}


  • dir:一个对象,在注册指令时作为参数传递。(就是提供的生命周期函数)


  • vnode: 上面作为 el 参数收到的真实 DOM 元素的蓝图。


  • prevNode: 上一个虚拟节点,仅在 beforeUpdateupdated 钩子中可用。


网络异常,图片无法展示
|


下面我们来封装一个格式化时间戳的指令


import dayjs from 'dayjs';
     app.directive("format-time", {
        // 做一些初始化操作
        created(el, bindings) {
          bindings.formatString = "YYYY-MM-DD HH:mm:ss";
          // 当用户传入格式化字符串,我们将使用用户传入的格式
          if (bindings.value) {
            bindings.formatString = bindings.value;
          }
        },
        mounted(el, bindings) {
          const textContent = el.textContent;
          let timestamp = parseInt(textContent);
          if (textContent.length === 10) {
            // 将时间戳转化为毫秒
            timestamp = timestamp * 1000
          }
          el.textContent = dayjs(timestamp).format(bindings.formatString);
        }
      })
      // 可以让用户自定义格式化
      <h2 v-format-time="'YYYY/MM/DD'">{{timestamp}}</h2>


插件


通常我们向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式:


  • 对象类型:一个对象,但是必须包含一个 install 的函数,该函数会在安装插件时执行。install函数将接受两个参数,一是全局对象。二是用户传入的配置对象


  • 函数类型:一个function,这个函数会在安装插件时自动执行。将接受两个参数,一是全局对象。二是用户传入的配置对象。


插件可以完成的功能没有限制,比如下面的几种都是可以的:


  • 添加全局方法或者 property,通过把它们添加到 config.globalProperties 上实现。


  • 添加全局资源:指令/过滤器/过渡等。


  • 通过全局 mixin 来添加一些组件选。


  • 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。 使用插件调用全局对象的use方法即可。并将插件对象传入给use方法。


// plugin.js
    // 对象类型
    export default {
      install(app) {
        app.config.globalProperties.$name = "zh"
      }
    }
    // 函数类型
    export default function(app) {
      console.log(app);
    }


// 使用
    const app = createApp(App);
    app.use(plugin)


相关文章
|
4月前
|
JavaScript 前端开发 安全
【技术革新】Vue.js + TypeScript:如何让前端开发既高效又安心?
【8月更文挑战第30天】在使用Vue.js构建前端应用时,结合TypeScript能显著提升代码质量和开发效率。TypeScript作为JavaScript的超集,通过添加静态类型检查帮助早期发现错误,减少运行时问题。本文通过具体案例展示如何在Vue.js项目中集成TypeScript,并利用其类型系统提升代码质量。首先,使用Vue CLI创建支持TypeScript的新项目,然后构建一个简单的待办事项应用,通过定义接口描述数据结构并在组件中使用类型注解,确保代码符合预期并提供更好的编辑器支持。
85 0
|
4月前
|
JavaScript 前端开发 安全
立等可取的 Vue + Typescript 函数式组件实战
立等可取的 Vue + Typescript 函数式组件实战
|
5月前
|
JavaScript 前端开发
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
52 0
【Vue3+TypeScript】CRM系统项目搭建之 — 关于如何设计出优质的 Vue 业务组件
|
6月前
|
JavaScript 安全 前端开发
Vue 3 中的 TypeScript
【6月更文挑战第15天】
84 6
|
7月前
|
JavaScript 前端开发 开发者
类型检查:结合TypeScript和Vue进行开发
【4月更文挑战第24天】TypeScript是JavaScript超集,提供类型注解等特性,提升代码质量和可维护性。Vue.js是一款高效前端框架,两者结合优化开发体验。本文指导如何配置和使用TypeScript与Vue:安装TypeScript和Vue CLI,创建Vue项目时选择TypeScript支持,配置`tsconfig.json`,编写`.tsx`组件,最后运行和构建项目。这种结合有助于错误检查和提升开发效率。
57 2
|
7月前
|
JavaScript 前端开发 开发者
Vue工具和生态系统: Vue.js和TypeScript可以一起使用吗?
【4月更文挑战第18天】Vue.js与TypeScript兼容,官方文档支持在Vue项目中集成TypeScript。TypeScript作为JavaScript超集,提供静态类型检查和面向对象编程,增强代码准确性和健壮性。使用TypeScript能提前发现潜在错误,提升代码可读性,支持接口和泛型,使数据结构和函数更灵活。然而,不是所有Vue插件都兼容TypeScript,可能需额外配置。推荐尝试在Vue项目中使用TypeScript以提升项目质量。
117 0
|
7月前
|
JavaScript 前端开发
在Vue中使用TypeScript的常见问题有哪些?
在Vue中使用TypeScript的常见问题有哪些?
101 2
|
2月前
|
JavaScript 前端开发 安全
深入理解TypeScript:增强JavaScript的类型安全性
【10月更文挑战第8天】深入理解TypeScript:增强JavaScript的类型安全性
49 0
|
2月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧
|
3月前
|
存储 JavaScript
typeScript进阶(11)_元组类型
本文介绍了TypeScript中的元组(Tuple)类型,它是一种特殊的数组类型,可以存储不同类型的元素。文章通过示例展示了如何声明元组类型以及如何给元组赋值。元组类型在定义时需要指定数组中每一项的类型,且在赋值时必须满足这些类型约束。此外,还探讨了如何给元组类型添加额外的元素,这些元素必须符合元组类型中定义的类型联合。
48 0
下一篇
无影云桌面