Skip to content

基础知识

模板

vue.js的模板使用了基于html的模板语法,在模板中数据的绑定是使用"Mustache"语法(双括号)。双括号会将数据解析成普通文本,而非html代码,如果想输出真正的html需要使用v-html,双括号语法不能作用于Html Attribute上,这种情况需要借助v-bind指令。

html
<div v-bind:id="dynamicId"></div>

如果 dynamicId 的值是 null、undefined 或 false,则 disabled attribute 甚至不会被包含在渲染出来的 <div> 元素中。

在模板提供了完全的表达式支持,但每个绑定只能包含单个表达式,所以下面不会生效

vue
<!-- 这是语句,不是表达式 -->
{{ var a = 1 }}

<!-- 流控制也不会生效,请使用三元表达式 -->
{{ if (ok) { return message } }}

TIP

模板表达式都放在沙盒中,只能访问全局变量的一个白名单,不要在模板中试图访问用户自定义的全局变量

指令

指令 (Directives) 是带有 v- 前缀的特殊 attribute。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。

常用指令

  • v-if 条件渲染指令,代表存在和销毁
  • v-bind绑定指令,用来绑定属性(简写:)
  • v-on事件绑定指令(简写@)
  • v-for 循环指令
  • v-text 内容显示为文本相当于{
  • v-html内容按普通 HTML 插入
  • v-once只渲染一次

常用指令缩写

html
<!-- 完整语法 -->
<a v-bind:href="url">...</a>

<!-- 缩写 -->
<a :href="url">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a :[key]="url"> ... </a>
html
<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

<!-- 动态参数的缩写 (2.6.0+) -->
<a @[event]="doSomething"> ... </a>

自定义指令

在 Vue 2,自定义指令是通过使用下面列出的钩子来创建的,这些钩子都是可选的

  • bind - 指令绑定到元素后发生。只发生一次。
  • inserted - 元素插入父 DOM 后发生。
  • update - 当元素更新,但子元素尚未更新时,将调用此钩子。
  • componentUpdated - 一旦组件和子级被更新,就会调用这个钩子。
  • unbind - 一旦指令被移除,就会调用这个钩子。也只调用一次。
html
<p v-highlight="yellow">高亮显示此文本亮黄色</p>
javascript
Vue.directive('highlight', {
  bind(el, binding, vnode) {
    el.style.background = binding.value
  }
})

computed 和watch

computed:🎉:

  1. 计算属性的结果会被缓存,除非依赖的响应式 property 变化才会重新计算。
  2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
  3. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed

模板内的表达式逻辑复杂时应当使用computed计算属性。
它为什么需要缓存:因为一个开销比较大的计算属性A,它需要遍历一个巨大的数组做大量计算,然后我们有其它属性依赖于A。如果它没有做缓存,我们将不可避免的多次执行A的getter。

watch:🎉:

  1. 不支持缓存,数据变,直接会触发相应的操作

  2. watch支持异步

  3. 当一个属性发生变化时,需要执行对应的操作;一对多

  4. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数:

  5. watch加immediate才能触发立即执行 immediate:组件加载立即触发回调函数执行,
    deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。

    v-for循环

v-for指令循环要携带key,因为dom做diff对比时,会把开始和结束做一系列对比,命中则拿新节点的key对比旧节点的key,未对应上会new element,如果key对应上再用旧节点的tag和新节点的tag是同样的,会认为这是一个旧节点。减少对比次数。

生命周期

beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、activated、deactivated、beforeDestroy、destroyed、errorCaptured

一般将ajax请求放到mounted中,放到create中会被重复执行

生命周期函数

在父子组件中,在某个阶段,会先执行父组件生命周期,再执行子组件生命周期,子组件先完成生命周期然后父组件完成生命周期如:

父beforeCreate 父created

子beforeCreate 子created

beforeCreate 数据检测事件配置之前调用

create 数据监测、事件(watch、event)生效后调用

beforeMount 相关的render首次被调用后执行

mounted 实例挂载后调用

beforeUpdate 数据更新时调用,在虚拟dom打补丁之前,适合更新访问现有dom,比如移除已添加的事件监听

update 数据已经更新,避免此期间更改状态。update不保证所有子组件的更新,如果想在整个视图绘制完毕可以在updated中用vm.$nextTick中执行

activated 被keep-alive缓存的组件激活时调用

deactivate 被keep-alive缓存的组件停用时调用

beforeDestroy 实例销毁之前调用,在这一步实例仍然完全可用。解绑自定义事件event.$off、清除定时器、解绑自定义的dom事件,如window.scroll等

destroyed 实例销毁后调用对应的vue指令解绑、事件监听移除、子实例也被销毁。

errorCaptured 捕获一个来自子孙组件的错误时调用。此钩子会收到三个参数:错误对象、发生错误的组件实例、错误来源信息字符串。此钩子可以返回false以阻止该错误继续向上传播。

组件通信

  1. 父组件A向子组件B传递数据可以通过props向下传递给子组件。不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

提示

注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。

  1. 子组件向父组件传递值通过事件的形式。子组件通过$emit向父组件调用时携带的事件发送数据
  2. 通过sync修饰符对一个prop进行双向数据绑定 相当于第1,2的简写方式
vue
// 子组件
this.$emit('update:title', newTitle)
// 父组件
<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>
// 简写
<text-document v-bind:title.sync="doc.title"></text-document>
4. 自定义事件
5. slot 作用域插槽
6. vuex
    //修改方式: 1)可以直接使用 this.$store.state.变量 = xxx;
                2)this.$store.dispatch(actionType, payload) 
                   或者:this.$store.commit(commitType, payload)

自定义组件v-model

一个组件v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model 选项可以用来避免这样的冲突:

vue
// 子组件
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

// 父组件

<base-checkbox v-model="lovingVue"></base-checkbox>

nextTick

vue是异步渲染的,data修改后dom不会李可渲染。$nextTick会在dom渲染之后被触发。以获取最新的dom节点。
Vue 会根据当前浏览器环境优先使用原生的 Promise.then 和 MutationObserver,如果都不支持,就会采用 setTimeout 代替,目的是 延迟函数到 DOM 更新后再使用。

Vue $nextTick 原理

动态组件&异步组件

  • 动态组件
html
<!-- 失活的组件将会被缓存!-->
<keep-alive>
  <!-- 动态组件 -->
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>
  • 异步组件 在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会触发该工厂函数,且会把结果缓存起来供未来重渲染。
js
Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})

Vue.component(
  'async-webpack-example',
  // 这个动态导入会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

提示

注意如果你希望在 Vue Router 的路由组件中使用上述语法的话,你必须使用 Vue Router 2.4.0+ 版本。

keep-alive

  • 缓存组件
  • 频繁切换不需要重复渲染
  • 与v-show的区别是,v-show是css样式来实现,keep-alive是vue层的控制,简单的可用v-show,像tab切换这种复杂的可以keep-alive

Props:

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
  • max - 数字。最多可以缓存多少组件实例。

include 和 exclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

vue
<!-- 逗号分隔字符串 -->
<keep-alive include="a,b"> // 这里的a,b是注册的component名
  <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
  <component :is="view"></component>
</keep-alive>

有keep-alive时生命周期:
加载时:parent-beforeCreate ==> parent-created ==> parent-beforeMount ==> child-beforeCreate ==> child-created ==> child-beforeMount ==> child-Mounted ==> child-actived ==> parent-mounted

销毁时:parent-beforeDestroy==> child-deactived ==> child-beforeDestroy ==> child-Destroyed ==> parent-Destroyed

mixin

将多个组件相同的逻辑抽离出来放到一起,引用的钩子,按照传入顺序依次调用,并在调用组件钩子之前会被调用。

问题:

  • 变量来源不明确
  • 多个可能会有变量冲突
  • 和组件可能出现多对多关系,复杂度较高

vue-router

vue-router的模式分三种:"hash" | "history" | "abstract"
默认值是: "hash" (浏览器环境) | "abstract" (Node.js 环境)

  • hash: 使用 URL hash 值来作路由。支持所有浏览器,包括不支持 HTML5 History Api 的浏览器。
  • history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式
  • abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。

创建路由常用参数

  • path 路径
  • name 命名路由
  • components 命名视图组件
  • meta
  • alias
  • children 嵌套路由
  • redirect 重定向
  • props // 2.6.0+
  • caseSensitive?: boolean, // 匹配规则是否大小写敏感?(默认值:false)
  • pathToRegexpOptions?: Object // 编译正则的选项

router实例方法

全局钩子函数

  • router.beforeEach 全局前置守卫,在路由切换开始时调用,必须调用 next。当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于 等待中。

next方法

next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。

next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

  • router.beforeResolve 全局解析守卫,必须调用 next

在 2.5.0+ 你可以用 router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

  • router.afterEach 路由切换离开时调用,钩子不会接受 next 函数也不会改变导航本身
  • router.beforeEnter 路由独享的守卫。这些守卫与全局前置守卫的方法参数是一样的。
  • 组件内的守卫
    • beforeRouteEnter
    • beforeRouteUpdate (2.2 新增)
    • beforeRouteLeave
    js
      const Foo = {
      template: `...`,
      beforeRouteEnter(to, from, next) {
        // 在渲染该组件的对应路由被 confirm 前调用
        // 不!能!获取组件实例 `this`
        // 因为当守卫执行前,组件实例还没被创建
        // 你可以通过传一个回调给 next来访问组件实例。
      },
      beforeRouteUpdate(to, from, next) {
        // 在当前路由改变,但是该组件被复用时调用
        // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
        // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
        // 可以访问组件实例 `this`
      },
      beforeRouteLeave(to, from, next) {
        // 导航离开该组件的对应路由时调用
        // 可以访问组件实例 `this`
      }
    }
  • 编程式导航
    • router.push
    • router.replace
    • router.go
    • router.back

局部到单个路由

  • beforeEnter

组件的钩子路由

  • beforeRouterEnter
  • beforeRouterUpdate
  • beforeRouterLeave

🎉 全局路由的使用

javascript
router.beforeEach((to, from, next) =>{
  // to 表示即将进入的目标对象
  // from 当前要离开的路由对象
  // next 是一个函数 调用resolve 执行下一步
})

完整的导航解析流程

钩子被触发顺序: beforeEach全局路由守卫 => beforeEnter 路由独享守卫 => beforeRouteEnter 组件内的守卫 => beforeResolve全局解析守卫 => afterEach全局后置钩子

TIP

如果是根路径的话beforeEnter路由独享守卫不会执行。
如果是一个组件路由切换到另外一个组个,会先触发组件内beforeRouteLeave再触发beforeEach全局路由守卫

  1. 导航被触发。
  2. 在失活的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

vue3的差异

  1. 去掉了filters
  2. 以前用prototype,现在不建议用
  3. ref不能直接使用
  4. 添加了setup
  5. 添加组合式api
  6. 生命周期
  7. 定义props的方式不同

父子组件传参

Vue 3中使用 emit 和 update: 来实现父子组件之间的数据传递。

update: 用于在父组件中通过 v-model 绑定子组件的数据,并在子组件中触发一个 update:modelValue 事件来更新数据。例如,在子组件中:

html
<!-- 子组件 -->
<template>
  <input :value="modelValue" @input="updateValue">
</template>

<script>
export default {
  props: ['modelValue'],
  methods: {
    updateValue(event) {
      this.$emit('update:modelValue', event.target.value);
    }
  }
}
</script>

<!-- 父组件 -->
<template>
  <child-component v-model="data"></child-component>
</template>

<script>
export default {
  data() {
    return {
      data: 'initial data'
    }
  }
}
</script>

定义props

html
<!--  子组件声明了的 props ,若父组件未传,则该值为 undefined -->
<script lang='ts' setup>
const props = defineProps({
  child: {
    type:String, // 参数类型
    default: 1, //默认值
    required: true, //是否必传
    validator: value => {
      return value >= 0 // 除了验证是否符合type的类型,此处再判断该值结果是否符合验证
    }
  },
  sda: String, //undefined
  strData: String,
  arrFor: Array
})
</script>

<!-- 类型声明方式 -->
<script lang='ts' setup>
const props = defineProps<{
  either: '必传且限定'|'其中一个'|'值', // 利用TS:限定父组件传 either 的值
  child?: string|number,
  strData?: string,
  arrFor: any[]
}>();
console.log(props);
</script>

<!-- 类型声明添加默认值 :默认值为引用类型的,需要包装一个函数 return 出去。-->
<script lang='ts' setup>
interface Props {
  either: '必传且限定'|'其中一个'|'值', // 利用TS:限定父组件传 either 的值
  child: string|number,
  sda?: string, // 未设置默认值,为 undefined
  strData: string,
  msg?: string
  labels?: string[],
  obj?:{a:number}
}
const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two'],
  obj: () => { return {a:2} }
})
</script>

其它

  1. 从elementUi中el-dialog转到elementPlus后,visible.sync修改为v-model="visible"