1.Vue3基础

因项目需要,开始了解与学习 vue。这里用于记录 vue3 的学习历程。根据以往的经验,感觉还是边学边记,对我比较适合一点。因为当前对 vue 一点都不了解,因此这个时间节点的目标:编写出从 0 到 1,由浅入深的一系列学习查询文档。好了,这篇将是基本语法的快速查询文档,自己开始吧。

自己提前使用npm create vue@latest,先将框架初始化好,方便语法学习测试:

javascript 复制代码
// main.ts
import { createApp } from 'vue'
import AppVue from './App.vue';
const app = createApp(AppVue);
app.mount('#app');


// App.vue
<script setup lang="ts">
</script>
<template>
  <div class="start">
    Hello, Let's start to learn Vue
  </div>
</template>

<style scoped>
.start {
  color: red;
}
</style>

接下来,我们开始细细学习具体基础语法吧。

1. 基础语法与指令

语法类型 实例
Mustache双大括号语法 ,纯文本插入 <span>你好,{{yehonzi}}</span>
v-html指令,插入 html <span v-html="rawHtml"></span> 动态渲染 html 比较危险,容易造成 XSS 漏洞
v-bind指令,属性绑定 单一绑定: <div v-bind:id="customId">hello</div> 简写 => <div :id="customId">hello</div> 动态绑定: const obj = { id:'cusId', class: 'cusCls'}, <div v-bind="obj">hello</div>等价 => <div id="cusId" class="cusCls">hello</div>
js 表达式,{{}}中,或v-开头的指令中 只支持单一表达式。{{ isTrue ? 'yes' : 'no'}},{{ message.split('').reverse().join('') }}
动态参数绑定 const obj ='href';<a :[obj]="url">hello</a> 等价=> <a :href="url">hello</a>
class 绑定 对象绑定: const cusCls = reactive({isActive=true, isHidden = false}); <div class="defaultCls" :class=cusCls></div> 等价 => <div class="defaultCls active"></div> 数组绑定:const cusCls = ['active', 'isShow']; <div class="defaultCls" :class=cusCls></div> 等价 => <div class="defaultCls active isShow"></div>
组件设置 class 组件:<p class="foo">Hi!</p>, 组件使用<Compnent :class={active: true} class="custCls"/> => 渲染:<p class="foo custCls active">Hi!</p>
绑定内联样式(推荐驼峰编写) 对象绑定:const cusStyle = reactive({color: 'red', fontsize: fontSize+'px'}); const fontSize = 10; <div :style="cusStyle"></div> 数组绑定:<div :style="[baseStyles, overridingStyles]"></div>
v-if /v-else-if / v-else <h1 v-if="type===A">A</h1><h1 v-else-if="type===B">B</h1><h1 v-else="type=C">C</h1> 当期望显示的是一整个模块,可以将v-if使用在<template>上:<template v-if="isShow">.....</template>
v-show <h1 v-show=isShow">A</h1> 当期望显示的是一整个模块,可以将v-show使用在<template>上:<template v-if="show">.....</template>
v-for 列表渲染 1:遍历顺序基于 Object.keys()返回值: <ul><li :key="item.index" v-for="item in items"><span :Key="index" v-for="{ index, childName } in item.children">{{ item.name }}: {{ childName }}</span></li></ul> 2:v-for 可比遍历一个整数,可用于 template。<template v-for="n in 10"><span>{{ n }}</span></template> 3:组件不自动注入 v-for 迭代数据: <MyComponent v-for="(item, index) in items" :item="item :key="item.id" /> 当需要循环的数组不是原始数据,而是需要经过处理的数据,可以使用 computed 进行过滤处理。
v-on:eventName 简写 @eventName 1. 内联事件:const count = ref(1);<button @click="count++">click me</button> 3:内联事件,使用event 或箭头函数中的 event 传递事件对象:`fuction myName(event,name) {console.log(name)}; 事件修饰符 <a @click.stop.prevent="doThat"></a> 或者 <form @submit.prevent="onSubmit">...</form> .stop:阻止事件冒泡 .prevent: 阻止默认事件 .self: 元素本身才出发事件 .capture: 会在该类型的事件捕获阶段传播到该 EventTarget 时触发。指向内部元素的事件,在被内部元素处理前,先被外部处理 .once: 只触发一次 passive: 永远不会调用preventDefault()
按键修饰符 <input @keyup.page-down="onPageDown" />,当$event.key='PageDown'时调用 提供以下按键别名:.enter,.tab, .delete, .esc, .space, .down, .left, .right, .up
系统修饰符 Alt + Enter: <input @keyup.alt.enter="clear"/> 提供以下修饰符:.ctrl, .alt, .shift, .meta
.exact 允许控制触发一个事件所需的确定组合的系统按键修饰符。 @click.ctrl: 按下的键只要包含ctrl就触发 @click.ctrl.exact: 只有按下 ctrl 键才触发 @click.exact:什么键都不按触发
鼠标按键修饰符 .left, .right, .middle
表单绑定基础 text: <input v-model="message" placeholder="edit" /> textarea : <textarea v-model="message" placeholder="multiple lines"></textarea> checkbox: <input type="checkbox" id="checkbox" v-model="checked" /> checkbox 值绑定(选中 yes,未选中 no):<input type="checkbox" id="checkbox" v-model="checked" true-value="yes" false-value="no" radio: <input type="radio" id="one" value="one" v-model="selected" /><label for="one">One</label> raido 值绑定(选中后 pick 的值为 first 的值):<input type="radio" v-model="pick" :value="first" /> multiCheckbox: const names = ref([]); <input type="checkbox" id="jack" value="Jack" v-model="names" <label for="jack">Jack</label> <input type="checkbox" id="john" value="John" v-model="names"> <label for="john">John</label> select: <select value="selected"><option v-for="option in options" :key="option.value" :value="option.value">{{ option.text }}</option></select> select 值绑定(选中后,selected 对象值变为{number:123}):<select v-model="selected"><option :value="{ number: 123 }">123</option></select>
表单修饰符 .lazy(每次 change 事件后更新数据): <input v-model.lazy="msg"/> .trim(自动去掉用户输入的两端空格):<input v-model.trim="msg"/> .number(用户输入内容自动转为 number,无法转换时返回原始值):<input v-model.number="msg"/>
侦听器 watch watch 追踪明侦听的 dataOrign 数据源。watch(dataOrgin, (newData, oldData) => { ...业务操作}, { deep: true, immediate: true,flush: 'post'}) dataOrgin:ref, 响应式数据、数组、getter 函数 deep:深层侦听器,不设置时只会监听对象的地址变化,属性变化没有效果 immediate:创建立马执行一次 flush:'post' : dom 更新完成后在执行。
侦听器 watchEffect 自动跟踪回调的响应式依赖,首次立即执行。同步执行所有响应式数据被追踪,异步追踪 await 执行前的数据源 const unwatch = watchEffect(async() => { const res = await fetch(http://ww.com/$dataOrgin})}, {flush: 'post'}); .... unwatch(); // 停止侦听器 flush:'post' : dom 更新完成后在执行。可以使用 watchPostEffect 实现该效果。

v-show vs v-if

v-if有更高的切换开销,v-show有公告的初始化渲染开销。频繁切换请使用v-show

  • v-if真实的按条件渲染。条件区块内所有事件监听器与子组件都将被销毁和重建
  • v-show懒惰的,如果初始值为 false 将不会渲染任何内容,只有为 true 才会被渲染。渲染后,不会被销毁,使用display属性被切换。

v-for vs v-if

v-if 与 v-for 不建议同时使用,因为优先级不明显。但是当一起使用时,v-if优先级更高,因此v-if中访问不了v-for的变量别名。使用以下方式解决:

javascript 复制代码
<template v-for="todo in todos">
  <li v-if="!todo.isShow">
    {{ todo.name }}
  </li>
</template>

Vue 侦听响应式数组变更

Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。以下方法都会触发数组的变更:

  • push、pop、shift、unshift、splice、sort、reverse

tips: filter, contat, slice 不会改变原数组。

:key 的作用

2. 组件的使用(组件复用主要是构建模块)

2.1 组件注册实例

  • 全局注册(可链式注册)
javascript 复制代码
import { createApp } from 'vue'
import GlobalRegisterComponentOne from './GlobalRegisterComponentOne.vue'
import GlobalRegisterComponentTwo from './GlobalRegisterComponentTwo.vue'

const app = createApp({})
app
  .component('GlobalRegisterComponentOne', GlobalRegisterComponentOne)
  .component('GlobalRegisterComponentTwo', GlobalRegisterComponentTwo)
  • 局部注册
javascript 复制代码
<script setup>
import ComponentA from './ComponentA.vue';
</script>

<template>
  <CompnentA />
</tempate>

2.2 组件的 Props 传递实例 (defineProps 定义)

  • 单向数据流: 父组件的值变化,子组件自动变化,不能在子组件中改变props的值,但是能修改 props 数组内部值,对象的属性值,只是不能更改 props 的值的地址。
  • 子组件可将 props 作为初始值,定义内部数据。也可以依赖 props 值进行 computed 计算。
  • 属性传递时建议使用 kebab-case 形式, example: 传递时使用user-school, 使用时是userSchool
javascript 复制代码
// ParentComponent
<script setup>
import ChildCompnent from './ChildCompnent.vue'
import { ref  } from 'vue'
const user = ref({ name: 'yezi', age: 10});
const userSchool= ref('chengdu');
const objectInfo = { id: "100", loveFruit: "香蕉"};
</script>
<template>

  <ChildCompnent
  :user="user"
  :user-school="userSchool"
  is-boy {/* is-body: boolean类型,默认转为true */}
  :array-data="['跳舞', '唱歌']" {/*  虽然是静态数据,不是动态数据,但是因为数组是一个表达式,不是一个字符串,因此需要动态绑定。 object对象也一样,静态object也需要动态绑定 */}
  v-bind="objectInfo" {/* 绑定对象,会被解析为 :id="objectInfo.id" :love-fruit="objectInfo.loveFruit" */}
  />
</template>


// ChildCompnent
<script setup>
{/* type可以是String, Number, Boolean, Array, Object, Data, Funciton, Symbol, 自定义Persion类等。Vue通过 instanceof来校验 */}
const props =  defineProps({
  user: Object,
  userSchool: {
    type: String,
    required: true, // 传入值必填,不填会报错
  },
  isBoy: {
    type: [Boolean, Number], // 类型允许多类型,但是转换会按类型顺序进行转换。
    default: 'defaultValue'
  },  // Boolean默认值为false, 其他类型默认为undefined。 可以自定义设置default值
  arrayData: Array,
  id: String,
  loveFruit: string,
});

{/* script中访问props */}
console.log(props.user.name)

{/* 可以将传入的props作为自己内部的值的初始值,操作修改,不影响父级 */}
const selfLoveFruit = ref(props.loveFruit);

{/* 可以对传入值进行computed计算 */}
const selfId = computed(() => props.id + 'self');

{/* 可改变数组的值,不能改变地址 */}
props.arrayData.push('睡觉');
</script>

<template>
  <div>id: {{id}}</div>
  <div>name: {{user.name}} </div>
  <div>userSchool:{{userSchool}} </div>
  <div>isBody:{{isBoy}} </div>
  <div>兴趣:{{arrayData}} </div>
  <div>喜欢的水果:{{loveFruit}}</div>
</template>

2.3 组件事件实例(defineEmits)

  • 同 props 一样,推荐使用 kebab-case 形式来编写监听器。
  • 组件触发事件没有冒泡机制,同级组件通信需要外部的事件总线或利用全局状态管理方案
javascript 复制代码
// ParentCompnent
<script setup>
const addMethod = (a, b) => {
  alert(a + b);
}
</script>
<template>
  <ChildComponet @add-method="addMethod"/>
</template>



// ChildComponent
<script setup>
 const emit = defineEmits(['addMethod']);

const selfAddMethod = () => {
  {/*  script setup 中不能使用$emit, 需要使用defineEmits的返回值进行调用 */}
  emit('addMethod', 10, 11)
}
</script>
<template>
  <button @click="selfAddMethod">click me</button>
  {/* 方法中调用 */}
  <button @click="$emit('addMethod', 10, 11)">click me</button>
</template>

2.4 v-model 自定义修饰符

javascript 复制代码
// ParentCompnent
<script setup>
  import { ref } from 'vue';
  import ChildComponent from './ChildComponent.vue';
  const customName = ref('zhaoyezi');
</script>
<template>
  {/* 有参数custom-name, 生成的props名称名称 `customNameModifiers` */}
  {/* 没有参数,生成props名称:modelModifiers */}
  <ChildComponent v-model:custom-name.upper="customName" v-model.upper="customName"/>
</template>


// ChildComponent
<script setup>
 const props = defineProps({
  customName: String,
  customNameModifiers:  { default: () => ({}) },
  modelValue: String,
  modelModifiers:  { default: () => ({}) },
 });

const emit = defineEmits(['update:modelValue', 'update:customName']);
 const upper = (e, type, needUper) => {
  let value = e.target.value;
  if (needUper) {
    value = value.charAt(0).toUpperCase() + value.slice(1)
  }

  type === 'modelValue' ? emit(`update:modelValue`, value) : emit(`update:modelValue`, value);
 };

</script>
<template>
  <input type="text" :value="modelValue" @input="(e) => upper(e, 'modelValue', modelModifiers)"/>
  <input type="text" :value="customName" @input="(e) => upper(e, 'customName', customNameModifiers)"/>
</template>

v-bind 与 v-model:

  • v-model: 双向数据绑定,用于表单控件,如果用在表单控件之外的标签没有效果
  • v-bind: 单向数据绑定,会将数据投影到绑定的地方。但是绑定的地方对数据更改时,原始数据不会更改。html 中的属性、css 的样式、对象、数组、number 类型、bool 类型都支持。
javascript 复制代码
<script setup>
import { ref} from 'vue';
 const test = ref('aaa');
</script>
<template>
  <input v-model="test" />
  <input v-model="test" />

  {/* v-model 等价于 */}

  <input :value="test" @input="test = $event.target.value"/>
</template>

2.5 v-model 在自定义组件工作(实现双向绑定)

javascript 复制代码
// ParentComponent
<script setup>
  import { ref } from 'vue';
  import ChildComponent from './ChildComponent.vue';
  const customName = ref('zhaoyezi');
  const title = ref('zhaoyezi--title');
</script>
<template>
  <ChildComponent  v-model="customName"  v-model:title="title"/>
  {/* 等价: */}
  <CustomInput
  :modelValue="customName"
  @update:modelValue="newValue => customName = newValue"
  :title="title"
  @update:title="newValue => Value = newValue"/>
</template>


// ChildComponent
<script setup>
defineProps({
  modelValue: String,
  title: String,
})
defineEmits({
  'update:modelValue': Function,
  'update:title': Function,
})
</script>
<template>
  <input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)"/>
  <input :value="modelValue"/>

  <input :value="title" @input="$emit('update:title', $event.target.value)"/>
  <input :value="title" />
</template>

2.6 Attributes 的透传

透传Attribute: 传递给子组件的属性或者事件,没有在子组件中声明为propsemits 或者 v-on 的事件监听器, 将被透传(可以跨组件层级,父组件属性透传给 子子组件)。

javascript 复制代码
// ParentComponent
<script setup>
  import { ref } from 'vue';
  import ChildComponent from './ChildComponent.vue';
  const customName = ref('zhaoyezi');
  const clickEven = () => {
   alert(customName.value)
  }
</script>
<template>
  <ChildComponent v-model="customName"  class="wrapperCls" style="color:red" @click="clickEven" />
</template>

// ChildComponent
<script setup>
defineProps({
  modelValue: String,
})
</script>
<template>
 <button class="selfCls">{{modelValue}}</button>
</template>

最后子组件 button dom 中简析内容为, 并且 click 事件注册在该 button 上。如果子组件嵌套的是另一个子组件,那么同样的道理,会透传给下一层的子组件:

javascript 复制代码
<button class="wrapperCls selfCls" style="color: red;">
  zhaoyezi
</button>
  • 禁用 Attributes: 使用inheritAttrs属性,并使用$attrs访问。如果父级中使用wrapper-cls,则通过$attrs['wrapper-cls']获取。@click事件使用$attrs.onClick获取
  • 多根节点访问:默认解析到子组件的根节点,如果想在子组件的子节点中访问,使用$attrs进行
  • script 中访问透传 attribute, 使用useAttrs()
javascript 复制代码
// ParentComponent
<HomeView v-model="customName" custom="custom" class="wrapperCls" style="color:red" @click="clickEven" />

// ChildComponent
<script setup>
import { useAttrs } from 'vue';
defineProps({ modelValue: String})
defineOptions({
  inheritAttrs: false
})
const attrs = useAttrs();
console.log(attrs.class)
</script>
<template>
  <div class="parent">
    <span v-bind="$attrs">{{ $attrs.custom }}</span>
    <button>{{ modelValue }}</button>
    <TheWelcome />
  </div>
</template>

2.7 插槽 Slots

  • <slot></slot>可以想象成在子组件中的一个占位符,占位符可提供默认的内容。父级传入的内容将占位符替换掉。
  • 插槽的内容无法访问到子组件的状态。可通过插槽像组件那样传递属性,则可在父级中访问子组件的属性
javascript 复制代码
// ParentCompoennt
<script setup lang="ts">
import { ref } from 'vue';
import ChildComponent from '../components/ChldComponent.vue'
const dynamicSoltName = ref('footer');
</script>

<template>
  <ChildComponent>
    {/* 子级传递数据给父级 */}
    <template v-slot:header="header"> {{ header.text }}  parent header <button @click="header.click">click me</button></template>

    {/* slot没有name, 默认名字为default */}
    <template #default>没有soltname, 默认解析为 default 的 插槽。 但是建议写上#default</template>

    {/* 没有传递内容,使用slot默认内容 */}
    <template #left></template>

    {/* 动态slot Name  */}
    <template #[dynamicSoltName]>parent footer</template>
  </ChildComponent>
</template>


// ChildComponet
<script setup lang="ts">
import { ref } from 'vue';

const clickChild = () => { alert('parent click child')}
const childText = ref('parent show child content')
</script>
<template>
  <div class="chid">
    <header>
      {/* 传递属性给父级,使得父级可访问自己的数据 */}
      <slot name="header"  @click="clickChild" :text="childText">headerDetault</slot>
    </header>
    <main>
      <slot name="left">leftDefault</slot>
      <slot>content default</slot> <!-- default -->
    </main>
    <footer>
      <slot name="footer">footerDefault</slot>
    </footer>
  </div>
</template>

<style scoped>
.chid {
  display: flex;
  flex-direction: column;
}
</style>

2.8 依赖注入

props都是逐级透传。我们希望父组件定义的属性,无论层级多深,后代组件都能够访问。可使用provide(依赖提供者)和inject(注入)来解决。

  • 建议尽可能将任何对响应式状态的变更都保持在供给方组件中。如果要在注入方更改数据,可在供给方提供函数方法
  • 希望提供的数据不被注入方变更,课使用readonly包装提供的值
  • 如果项目过大,请将注入明使用 Symbol 数据类型,专门放入一个文件中管理
javascript 复制代码
// ParentComponent
<script setup lang="ts">
import { provide, ref, readonly } from 'vue';
import ChildComponent from '../components/ChldComponent.vue'

const parentSelf = ref('parentSelf');
const location = ref('chengdu');
const updateLocation = (event) => {
  location.value = event.target.value;
}
provide('prodiveName', parentSelf);
provide('provideUpdate', {
  location: readonly(location),
  updateLocation
})
</script>

<template>
  parentSelf: <input v-model="parentSelf"/>
  parentLocation: <input v-model="location"/>
  <ChildComponent />
</template>

// 第一层ChildComponent
<script setup lang="ts">
import Child2Component from './Child2Component.vue';

</script>
<template>
  <Child2Component />
</template>


// 第二层component
<script setup lang="ts">
import { inject } from 'vue';
  const message = inject('prodiveName', 'default value');
  const { location, updateLocation } = inject('provideUpdate') as any;
</script>

<template>
  childself: <input v-model="message"/>
  childlocation: <input :value="location" @input="updateLocation"/>

  {/* 不能变更,因为设置了readonly */}
  <input v-model="location"/>
</template>

2.9 异步组件

当希望将应用拆分为更小的块,并实现按需加载,在需要的时候才从服务器加载相关组件。可使用defineAsyncComponent

javascript 复制代码
// ParentComponet
<script setup lang="ts">
import AsyncComponent from '@/components/AsyncComponent.vue';
import { ref } from 'vue';

const loadChild2 = ref(false);
</script>

<template>
  <input type="checkbox" v-model="loadChild2" />
  <input type="text" :value="loadChild2"/>
  <AsyncComponent v-if="loadChild2" />
</template>

// 异步Component
<script setup lang="ts">
import { defineAsyncComponent } from 'vue';
  import LoadingComponent from './LoadingComponent.vue';

  const AsyncCompnent = defineAsyncComponent({
    loader: () => import('./Child2Component.vue'),

    loadingComponent: LoadingComponent,  // 加载异步组件时使用的组件

    delay: 200, // 展示加载组件前的延迟时间,默认为 200ms

    // 加载失败后展示的组件
    // errorComponent: ErrorComponent,

    timeout: 3000   // 如果提供了一个 timeout 时间限制,并超时也会显示这里配置的报错组件,默认值是:Infinity
  })
</script>
<template>
  <AsyncCompnent />
</template>

显示结果:可以看到,当点击 radios 选中,才回去加载Child2Component组件。

3. 逻辑的复用

3.1 组合式函数(复用主要是有状态的逻辑封装)

利用 Vue 的组合式 API 来封装和复用 有状态逻辑的函数。例如以下例子,注册事件与清除事件在项目中是比较常见的,将其抽取为组合式函数进行使用:

  • 组合式函数约定用驼峰命名法命名,并以"use"作为开头
  • toValue: 输入组合是函数的参数是refgetter,请利用toValue()工具函数。将可能的 ref 或 getter 解包。
  • 组合式函数中推荐使用ref(),而不是reactive()。因为ref()函数作为返回之后能在组建中正确解构后仍然可以保持响应式
  • 只能在<script setup>或者setup()钩子中被调用。有时可以在onMounted()这样的生命周期钩子中调用
javascript 复制代码
// Composables.js 事件钩子
import { onMounted, onUnmounted } from "vue"

export const useEventComposables = (target, event, callback) => {
  onMounted(() => {
    target.addEventListener(event, callback);
  });
  onUnmounted(() => {
    target.removeEventListener(event, callback);
  })
}


// MouseComposables.js 鼠标移动钩子
import { ref, toValue, watch, watchEffect } from 'vue';
import { useEventComposables } from './Composables'
export const useMouseComposables = (url) => {

  const mouseX = ref(0);
  const mouseY = ref(0);
  const newUrl = ref(toValue(url) + '_' + (new Date().valueOf()) );

  // watch(url, (newOption, oldOption) => {
  //   newUrl.value = newOption + '_' + (new Date().valueOf());
  //   console.log(newUrl.value)
  // });

  watchEffect(() => {
    newUrl.value = toValue(url) + '_' + (new Date().valueOf());
  })

  // 内部使用组合式函数
  useEventComposables(window, 'mousemove' , (event) => {
    mouseX.value = event.pageX;
    mouseY.value = event.pageY;
  });

  return { mouseX, mouseY, newUrl }
}


// ParentComponent.vue 钩子使用
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { useMouseComposables } from '../components/MouseComposables';

  // 组件式函数可以接收响应式状态数据,内部使用监听函数处理。例如异步请求可以按这种方式封装
  const url =  ref('customURL');
  const { mouseX, mouseY, newUrl } = useMouseComposables(url);

  // 如果喜欢以对象的形式访问,可以使用reactive包裹,mouseInfo的mouseX, mouseY, newUrl也能正确解构
  const mouseInfo = reactive(useMouseComposables(url));

</script>

<template>
  鼠标位置:<br>
  <div>mouseX: {{ mouseX }}</div>
  <div>mouseY: {{ mouseY }}</div>

  模拟URL 外部变化
  <input v-model="url"/>
  <div>newUrl通过组合是函数变化值: {{ newUrl }}</div>
</template>

3.2 自定义指令(重用涉及普通元素的底层 DOM 访问的逻辑)

定义格式(各个方法参数通简格式一致):
javascript 复制代码
const myDirective = {
  // 在绑定元素的 attribute 前 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {},
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {},
}
app.directive('customDirective', myDirective)
简写格式:
javascript 复制代码
app.directive('customDirective', (el, binding, vnode, prevNode) => {})
  1. el: 绑定到的元素。这可以用于直接操作 DOM
  2. binding:
  • value: 传递给指令的值。v-customDirective="zhaoyezi", 则 value 为zhaoyezi
  • oldValue: 更改前的值。仅在beforeUpdateupdated中可用
  • arg: 传递给指令的参数。v-customDirective:foo="zhaoyezi", 则argfoo
  • modifiers: 修饰符对象。v-customDirective:foo.stop.once="zhaoyezi", 则修饰符是{stop:true, once:true}
  • instance: 指令的组件实例
  • dir:指令的定义对象(不是简写里的那些生命周期方法 mounted, beforeMount)
节流函数指令(防止表单重复提交)

网上的指令实例普遍有 3 个,节流函数,图片延时加载,一键 copied 功能。这里使用节流函数实例。

javascript 复制代码
// 指令注入
app.directive('throttle', (el, binding, vnode, prevNode) => {
  const throttleTime = binding.value || 2000;
  let func;
  el.addEventListener('click', event => {
    if (!func) {
      func = setTimeout(() => {
        func = null;
      }, throttleTime)
    } else {
      // stopImmediatePropagation: 多个事件监听器被附加到相同元素的相同事件类型上,当此事件触发时,它们会按其被添加的顺序被调用。如果在其中一个事件监听器中执行 stopImmediatePropagation() ,那么剩下的事件监听器都不会被调用。
      event && event.stopImmediatePropagation();
    }
  }, true)
})

// 指令使用
<script setup>
const helloEvent = () => {
  console.log('hello yezi');
}
</script>

<template>
  <button @click="helloEvent" v-throttle>zhaoyezi</button>
</template>

3.3 插件

插件就是一种可以为Vue添加全局功能的工具代码。插件主要应用的场景:

  • 通过app.component()app.directive()注册一到多个全局组件或自定义指令
  • 通过app.provide()使得资源被注入到整个应用
  • app.config.globalProperties 中添加一些全局实例属性或方法
  • 可能是以上 3 中的任意组合或者全部功能都有(例如 vue-router)

基本格式:

javascript 复制代码
const app = createApp(AppVue)
app.use(
  {
    // options: 第二个参数options就是app的第二个自己输入的参数{ customOptions: 'xxx'}
    install: (app, options) => {
      // $zhaoyezi: 将在全局可以使用
      app.config.globalProperties.$zhaoyezi = { a: 1 }
      console.log(options)
    },
  },
  { customOptions: 'xxx' },
)

// 还可以使用以下方式定义
app.use(
  {
    install(app, options) {
      app.config.globalProperties.$zhaoyezi = { a: 1 }
      console.log(options)
    },
  },
  { customOptions: 'xxx' },
)

各种类型的注入,使用的时候不再需要引入:

javascript 复制代码
// 入口main.ts
import { createApp } from 'vue'
import ChildComponent from './ChildComponent.vue';
import LoadingComponentVue from './components/LoadingComponent.vue';

const app = createApp(AppVue);
const myPligun = {
  install: (app, options) => {
    console.log(options)

    // 添加全局属性
    app.config.globalProperties.$zhaoyehong = { buttonText: 'click me' };

    // 添加全局指令
    app.directive('throttle', (el, binding, vnode, prevNode) => {
      const throttleTime = binding.value || 2000;
      let func;
      el.addEventListener('click', event => {
        if (!func) {
          func = setTimeout(() => {
            func = null;
          }, throttleTime)
        } else {
          event && event.stopImmediatePropagation();
        }
      }, true)
    });

    // 依赖注入
    app.provide('i18n', options);

    // 组件注入
    app.component('LoadingComponent', LoadingComponentVue)
  }
}
app.use(myPligun, { mutiName: '多国语文件' })
app.mount('#app');


// 消费组件ChildComponent
<script setup lang="ts">
import { inject } from 'vue';

const helloEvent = () => {
  console.log('hello yezi');
}
const i18n = inject('i18n');
</script>

<template>
  <button @click="helloEvent" v-throttle>{{$zhaoyehong.buttonText}}</button>
  <div>多国语文件消费:{{ i18n.mutiName }}</div>
  {/* 直接使用,不引入 */}
  <LoadingComponent/>
</template>

4. 内置组件

4.1 <Transition>组件

组件class

  • v-enter-from: 进入动画的起始状态。元素插入之前添加,元素插入完成的下一帧移除
  • v-enter-active: 进入动画的生效状态。整个动画阶段应用。元素插入之前添加,动画过渡或完成后移除。
  • v-enter-to: 进入动画结束状态。v-enter-from 移除时添加,动画过度完成后移除
  • v-leave-from:起来动画的起始状态。离开过渡效果被触发时添加,在一帧后被移除
  • v-leave-active: 离开动画的生效状态,整个离开动画阶段应用。在过渡或动画完成后移除。
  • v-leave-to: 离开动画的结束状态。v-leave-to移除的时候添加,在过渡或动画完成之后移除
    v-enter-active, v-leave-active: 提供了为进入和离开动画指定不同速度曲线的能力。

组件钩子函数

可通过监听<Transition/>组件事件的方式在过度过程中挂上钩子函数。

javascript 复制代码
// 在元素被插入到 DOM 之前被调用。用这个来设置元素的 "enter-from" 状态
function onBeforeEnter(el) {}

// 在元素被插入到 DOM 之后的下一帧被调用。用这个来开始进入动画
function onEnter(el, done) {
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  done()
}

// 当进入过渡完成时调用。
function onAfterEnter(el) {}

// 当进入过渡在完成之前被取消时调用
function onEnterCancelled(el) {}

// 在 leave 钩子之前调用。 大多数时候,你应该只会用到 leave 钩子
function onBeforeLeave(el) {}

// 在离开过渡开始时调用。用这个来开始离开动画
function onLeave(el, done) {
  // 调用回调函数 done 表示过渡结束
  // 如果与 CSS 结合使用,则这个回调是可选参数
  done()
}

// 在离开过渡完成、且元素已从 DOM 中移除时调用
function onAfterLeave(el) {}

// 仅在 v-show 过渡中可用
function onLeaveCancelled(el) {}
<Transition
  @before-enter="onBeforeEnter" 
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
</Transition>

组件应用(css版本)

  • 可以自定义 Transition name。 class会将由v-enter-active => customName-enter-active
  • 可自定义class名称: enter-active-class="animate_animated", 则将使用animate_animated替换enter-active-class。有以下class可以自定义enter-from-class/enter-active-class/enter-to-class/leave-from-class/leave-active-class/leave-to-class
  • 解决内嵌dom的动画:因为Transition只能监听过渡根元素上的第一个transitionendanimationend来判断过渡动画何时结束,而在嵌套的过渡中,期望的行为应该是等待所有内部元素的过渡完成,因此需要向组件传递duration来指定过度动画的持续时间(例子请看下面的第二个动画)。
javascript 复制代码
<script setup lang="ts">
import { ref } from 'vue';
  const isShow = ref(false);
</script>

<template>
  <button @click="isShow = !isShow">change is SHOW</button>
  <Transition>
    <div v-if="isShow">Transation</div>
  </Transition>
  <!-- 也可以详细指定enter, leave时间 :duration="{ enter: 500, leave: 800 }" -->
  <Transition 
    duration="1000" 
    name="custom-transition"
    enter-active-class="animate_animated"
  >
    <div v-if="isShow" class="outer"> <div class="inner"> Transation 内嵌</div></div>
  </Transition>
  <div>
    <div :class="isShow ? 'v-enter-show' : 'v-enter-hidden'">Transation 2</div>
  </div>
</template>
<style scoped>

/* 原始名字 */
.v-enter-active, .v-leave-active {
  transition: all 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.v-enter-from, .v-leave-to {
  transform: translateX(20px);
  opacity: 0;
}

/* 自定义名字,自定义class */
.animate_animated, .animate_animated .inner, .custom-transition-leave-active, .custom-transition-leave-active .inner{
  transition: all 1s cubic-bezier(1, 0.5, 0.8, 1);
}
.animate_animated  .inner {
	transition-delay: 0.25s;
}
.custom-transition-enter-from, .custom-transition-enter-from .inner, .custom-transition-leave-to, .custom-transition-leave-to .inner {
  transform: translateX(20px);
  opacity: 0;
}
.outer {
  background-color: blanchedalmond;
}

/* 自己实现普通的动画效果 */
.v-enter-show {
  opacity: 1;
}
.v-enter-hidden {
  opacity: 0;
  transform: translateX(20px);
}
.v-enter-show, .v-enter-hidden {
  transition: all 1s cubic-bezier(1, 0.5, 0.8, 1);
}
</style>

组件应用 mode="out-in"

等上一个动画结束再进行下一个动画,感觉和使用动画延迟属性实现的效果一样。

javascript 复制代码
<script setup lang="ts">
import { ref } from 'vue'
const showType = ref(-1)
const changeType = () => {
  if (showType.value === 2) {
    showType.value = 0
    return
  }
  showType.value++
}
</script>

<template>
  <button @click="changeType">click me</button>
  <div style="height: 50px;display: flex; flex-direction: column; justify-content: center; border: 1px solid #ddd; width: 500px;overflow: hidden;">
    <Transition mode="out-in">
      <button v-if="showType === 0">Saved</button>
      <button v-else-if="showType === 1">edited</button>
      <button v-else-if="showType===2">editing</button>
  </Transition>
  </div>
</template>
<style scoped>
button {
  width: 100px;
}
.v-enter-active,
.v-leave-active { 
  transition: all 0.4s ease-in;
}

{/* 不添加mode="out-in",添加这个效果差不多 */}
{/* .v-enter-active {
  transition-delay: 0.3s;
} */}
.v-enter-from {
  transform: translateY(30px);
  opacity: 0;
}
.v-leave-to {
  transform: translateY(-30px);
  opacity: 0;
}
</style>

4.2 内置组件 <TransitionGroup>组件

<Transition>组件基本有相同的props、css过度class,和JavaScript钩子监听函数。但是有以下区别:

  • 默认<TransitionGroup>该容器是不渲染,可通过tag属性,指定将父容器渲染为什么标签。例如tag='ul', 则TransitionGrop将被渲染为ul标签
  • 过度模式mode="out-in"不可用
  • 列表的每个元素需要有唯一的key
  • css过度class被应用在列表元素上,而不是容器元素上
javascript 复制代码
<script setup lang="ts">
import { ref } from 'vue'
const showData = ref([1, 2, 3, 4, 5])
const removeData = () => {
  showData.value.shift();
}
const addData = () => {
  showData.value.push(new Date().valueOf())
}
</script>

<template>
  <button @click="removeData">removeData</button>
  <button @click="addData">addData</button>
  <div style="display: flex; flex-direction: column; justify-content: center; border: 1px solid #ddd; width: 500px;overflow: hidden;">
    <TransitionGroup tag="ul">
      <li v-for="item in showData" :key="item">{{ item }}</li>
    </TransitionGroup>
  </div>
</template>
<style scoped>
.v-move,.v-enter-active .v-leave-active { 
  transition: all 0.4s ease-in;
}
.v-enter-from,.v-leave-to {
  transform: scaleY(0.01) translate(30px, 0);
}
.v-leave-active {
  position: absolute;
}
</style>

4.3 内置组件 <KeepAlive>组件

  • 在多个组件相互切换过程中,可以缓存被移除的组件实例。将保留之前组件所有的状态。
  • KeepAlive默认保留所有的组件实例,但是可以通过includeexclude来包含或排出。例如下面的例子:只有当值为B或者C的时候,才进行缓存
  • :max="10":被缓存的最大组件实例数。缓存的实例数量即将超过指定的那个最大数量,则最久没有被访问的缓存实例将被销毁,以便为新的实例腾出空间
  • onActivated 与 onDeactivated: 缓存实例的生命周期。疑问(为什么没有执行)?
javascript 复制代码
// Child.vue
<script setup lang="ts">
import { ref, onActivated, onDeactivated } from 'vue'
const witchBox = ref('A')
onActivated(() => {
  console.log('调用时机为首次挂载以及每次从缓存中被重新插入时');
})

onDeactivated(() => {
  console.log('在从 DOM 上移除、进入缓存.以及组件卸载时调用');
})
</script>
<template>
    <input type="text" v-model="witchBox" />
</template>
<style scoped></style>


// Parent.vue
<script setup lang="ts">
import { ref } from 'vue'
import Child from '../components/Child.vue'
const witchBox = ref('A')
</script>
<template>
  <input type="radio" id="A" value="A" v-model="witchBox" /><label for="A">A</label>
  <input type="radio" id="B" value="B" v-model="witchBox" /><label for="B">B</label>
  <input type="radio" id="C" value="C" v-model="witchBox" /><label for="C">C</label><br />
  <KeepAlive :max="10" include="A"><Child v-if="witchBox === 'B' || witchBox === 'C'" /> </KeepAlive><br />
  <Child v-if="witchBox === 'A'" /><br />
</template>
<style scoped></style>

4.4 内置组件 <Teleport>组件

  • 将一个组件内部的一部分模板"传送"到该组件的 DOM 结构外层的位置去。
  • 多个Teleport挂在到同一个目标元素(例子中的#root,记得在index.html模板中必须有#root 元素)
  • :disabled: 禁用Teleport,不会挂在到外部元素
javascript 复制代码
<script setup lang="ts">
import { ref } from 'vue';

const isShow = ref(true);
</script>
<template>
  <div>this is vue content</div>
  <Teleport to="#root">
    <div class="modal">
      this is modal1
    </div>
  </Teleport>
  <Teleport to="#root">
    <div class="modal">
      this is modal2
    </div>
  </Teleport>
  <Teleport to="body"  :disabled="isShow">
    <div class="modal">
      this is modal
    </div>
  </Teleport>
</template>
<style scoped>
.modal {
  border: 1px solid;
}
</style>
相关推荐
一路向前的月光3 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   3 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Jiaberrr4 小时前
Element UI教程:如何将Radio单选框的圆框改为方框
前端·javascript·vue.js·ui·elementui
程序员大金7 小时前
基于SpringBoot+Vue+MySQL的装修公司管理系统
vue.js·spring boot·mysql
道爷我悟了8 小时前
Vue入门-指令学习-v-html
vue.js·学习·html
无咎.lsy8 小时前
vue之vuex的使用及举例
前端·javascript·vue.js
工业互联网专业9 小时前
毕业设计选题:基于ssm+vue+uniapp的校园水电费管理小程序
vue.js·小程序·uni-app·毕业设计·ssm·源码·课程设计
计算机学姐9 小时前
基于SpringBoot+Vue的在线投票系统
java·vue.js·spring boot·后端·学习·intellij-idea·mybatis
twins352010 小时前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky10 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js