好记性不如烂笔头
Basic
通过 createApp 创建应用实例
在 vue2.x 中,使用的是new Vue()
,返回的是根组件实例,而createApp
返回的是应用实例,想要得到根组件实例,还需要使用mount
方法,个人认为这个改动是在语法层面上区分创建组件和创建应用
ES6 Proxy
vue2.x 使用 Object.defineProperty
来实现数据相应,vue3 中使用 new Proxy()
为数据架设一层代理,来实现数据响应
计算属性 computed
- 计算属性会缓存结果,只要依赖值不变,多次访问该计算属性也不会执行代码,而是直接返回缓存结果,减小性能开销
- 计算属性拥有
getter
、setter
侦听器 watch
- watch 与 computed 几乎相同,唯一不同的是,watch 允许在其中执行异步操作
class 与 style 的绑定
class
-
<div :class="{active: isActive}"></div> <div :class="classes"></div> data(){ return { classes: { avtive: true, container: true } } } <div :class="[activeClass, containerClass]"></div> data(){ return { activeClass: 'active', containerClass: 'container' } }
-
也可以直接在
v-bind:class
中使用三运运算符来切换 class -
在自定义组件上使用 class
-
若组件内只有一个根元素,那么这些 class 会自动合并在根元素上
-
若有多个根元素,需要定义哪些根元素接收 class,使用
$attrs
-
<div id="app"> <my-component class="baz"></my-component> </div> const app = Vue.createApp({}) app.component('my-component', { template: ` <p :class="$attrs.class">Hi!</p> <span>This is a child component</span> ` })
-
-
继承的 Attribute 可以使用
$arrts
来定义 Attribute 具体出现在哪个元素上-
app.component('date-picker', { inheritAttrs: false, template: ` <div class="date-picker"> <input type="datetime-local" v-bind="$attrs" /> </div> ` }) <!--这样一来,所有写在 date-picker 上的 attribute 将会绑定在 input 元素上-->
-
style
-
对象语法
-
<div :style="{color: 'red', fontSize: '14px', borderRadius: borderRadius}"></div> data(){ return { borderRadius: '4px' } }
-
-
也可以直接绑定到一个样式对象
-
<div :style="styleObject"></div> data(){ return { styleObject: { color: 'red', fontSize: '14px', borderRadius: '4px' } } }
-
-
数组语法
-
可以使用数组语法包含多个样式对象
-
<div :style="[borderStyle, textStyle]"></div> data(){ return { borderStyle: { border: '1px solid red', borderRadius: '4px', }, textStyle: { fontSize: '14px', color: 'gray' } } }
-
v-for
-
v-if
的优先级高于v-for
,当两个指令同时作用于一个元素身上时,你需要你知道你在干什么 -
当 Vue 正在更新使用
v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。 -
用比较简单的描述是,如果
v-for
渲染出来的列表没有绑定key
,那么在v-for
绑定值变化的时候,Vue 是基于绑定值的索引来更新 DOM 树的 -
查看这个例子
- 我们将三个 input 元素的值改为 test1、test2、test3
- 然后删除第二个元素,按照直觉来说,剩下的应该只有 test1 和 test3,但结果是 test1 和 test2,why?
- 很简单,我们改变了
v-for
绑定值的 index,之前是0,1,2
,删除后是0, 1
,相当于删除了索引为 2 的元素,那么在这个 list 对应的 DOM 树中,也会去删除索引为 2 的节点,所以反应到页面上是 test1 和 test2 - 这就是文档中所说的依赖子组件状态或临时DOM状态,在这种情况下我们需要绑定
key
值来明确地告诉 Vue 我们想要删除的节点到底是哪一个,而不是让索引做主
-
为了让 Vue 可以监听到数组的变化,Vue 改写了以下数组方法:
- push
- pop
- shift
- unshift
- splice
- sort
- reverse
-
代替变更方法更好的办法是直接替换数组
事件处理
-
在内联语句中使用
@eventName
这样的方法来处理事件-
<div @click="f"></div>
-
methods: { // e这个参数我们在绑定事件回调函数的时候并没有传啊?that's ok,vue 会帮我们传的 // 这样的写法,vue 会在调用回调函数时默认帮我们传一个 event 对象 f(e){ console.log(e) } }
-
<div @click="f('message', $event)"></div>
-
methods: { f(message,e){ // 当我们需要其他参数时,并且还想接收到 event 对象 // 我们需要在内联语句中显示的传递这个参数,使用 $event 这个变量,你问这个哪来的?去问 Evan You console.log(`message is ${message}`) console.log(e) } }
-
-
一个事件可以绑定多个回调函数,使用逗号分割
-
<div @click="doOne($event), doTwo($event)"></div>
-
-
事件修饰符
-
.stop
阻止事件冒泡 -
.prevent
阻止事件默认行为 -
.capture
使用事件捕获模式 -
.self
触发事件的元素只能是本身 -
.once
事件只会触发一次 -
.passive
文档解释我没太看懂,看了下 MDN,将 MDN 的解释照搬过来-
passive
:Boolean
,设置为true时,表示listener
永远不会调用preventDefault()
。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。查看 使用 passive 改善的滚屏性能 了解更多.
-
v-model
.lazy
会将触发事件由input
改变为change
.number
将输入值转变为Number
类型.trim
过滤输入值的首尾空白字符
-
Component
在自定义组件上使用 v-model
v-model
本质上是一个语法糖,当我们剥开包装纸,其实是这样的
<custom-input
v-model="searchText"
></custom-input>
<custom-input
:model-value="searchText"
@update:model-value="searchText = $event"
></custom-input>
为了让它正常工作,这个组件内的
<input>
必须:
- 将其
value
attribute 绑定到一个名叫modelValue
的 prop 上- 在其
input
事件被触发时,将新的值通过自定义的update:modelValue
事件抛出
当然,我们也可以自定这个值,并不一定是 modelValue
<template>
<input
type="text"
:value="value"
@input="$emit('update:xxx', $event.target.value)"
>
</template>
<script setup>
// props 接收一个名为 xxx 的 property
defineProps({
xxx: {
type: String,
required: true
}
})
</script>
当然,我们也可以在内部的input
上使用v-model
,不过需要改变一下写法,这里直接照搬文档
app.component('custom-input', {
props: ['xxx'],
emits: ['update:xxx'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
})
使用 emits 定义组件发出的事件
app.component('custom-form', {
emits: ['inFocus', 'submit']
})
当在 emits
选项中定义了原生事件 (如 click
) 时,将使用组件中的事件替代原生事件侦听器。
-
这句话的意思是,如果我们在一个自定义组件内使用
emits
定义了组件发出的事件,那么在该组件上定义相同的事件(也就是原生事件侦听器)将不会触发,而是改用组件内定义的事件触发 -
<script setup> import MyButton from './components/MyButton.vue' import {ref} from "vue"; const f = () => { console.log('customize element tigger'); } </script> <template> <MyButton label="click me" @click="f"></MyButton> </template>
-
<!-- MyButton.vue --> <template> <div> <button @click="f"> {{ label }} </button> </div> </template> <script setup> defineProps({ label: { type: String, default: '', required: false } }) const f = () => { console.log('component click tigger'); } // 如果没有使用emits,那么我们点击这个按钮将会触发两次click defineEmits(['click']) </script>
-
在 2.x 中,如果
MyButton
组件上定义了监听了 click 事件,但是组件内部没有对其处理的话,是触发不了这个事件的,必须使用.native
才能触发-
<!-- MyButton.vue --> <template> <button>click me</button> </template>
-
<my-button @click=f></my-button>
-
methods:{ // 这个f是不会被触发的,除非是组件内部对click做出反应抛出一个click事件,或者在组件上使用 .native 修饰符 // .native 会将组件上的事件监听绑定到组件内部的根元素上 f(){ console.log('tigger') } }
-
emits
还可以验证抛出事件
要添加验证,请为事件分配一个函数,该函数接收传递给
$emit
调用的参数,并返回一个布尔值以指示事件是否有效。
-
emits: { // 没有验证 click: null, // 验证 submit 事件 submit: ({ email, password }) => { if (email && password) { return true } else { console.warn('Invalid submit event payload!') return false } } }
插槽
备用内容/默认内容:
- 可以给插槽指定默认内容,当父组件在使用该组件时且并未提供任何插槽内容,默认内容将会被渲染
具名插槽:
- 给
slot
标签添加一个name
的 Attribute,然后在父组件中使用<template v-slot:slotName>
指定使用哪一个插槽,slot
没有name
的会当作default
- 插槽的名称也可以使用动态指令参数
- 具名插槽还可以使用缩写,但只能在有其他参数时使用(有多个插槽且需要提供名称时),将
v-sot
替换为#
,使用缩写是需要使用明确的插槽名
作用域插槽
- 让父组件能够访问子组件通过插槽暴露的数据
- 子组件:
<slot :dataName=data></slot>
,父组件通过v-slot:default="slotProps"
来访问,slotProps
是一个对象,里面包含了子组件插槽所暴露的数据项,default
表示这是默认插槽,参考上面的具名插槽 - 当然,我们还可以使用
ES6
的解构语法:v-slot:default={dataName}
Provide / Inject
相当于“长距离 props”,子组件并不需要知道 inject 的 property 来自哪里,父组件也不需要知道哪些子组件使用了它 provide 的 property。
provide 要访问组件实例的 property 需要将 provide 写为一个返回对象的函数
provide 传递的内容并不是响应式的,我们可以使用 computed
来做到响应式
Composition API
这个算是Vue3 的最大更新了,这个 composition api 让我感觉 Vue3 现在确实是一个 framework,而不是一个 library,composition api 允许我们分离逻辑代码,使得单文件组件中的逻辑更加清晰有条理,在以往的 options api 中,如果我们有一个较重的组件,里面的data
可能包含十多个 property,methods
中的方法可能每一个都包含N行代码,阅读十分困难,而且逻辑关联不清楚。
Setup
setup
函数接收两个参数:
props
context
Props
与 options api 的 props 一样,它是响应式的。
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
但是,因为
props
是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
如果要解构 prop,需要使用toRefs
函数来完成
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
如果title
是可选的prop(required: false),那么传入的props
中可能没有这个titile
,这种情况toRefs
不会创建一个ref,需要使用toRef
替代它
import { toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
Context
传递给 setup
函数的第二个参数是 context
。context
是一个普通 JavaScript 对象,暴露了其它可能在 setup
中有用的值:
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}
context是一个普通的 JavaScript 对象, 可以使用解构
Provide / Inject
在 composition api 中,provide 需要从 vue 中 import,其次 provide 变为一个函数,接收连个参数:key,value
inject 相同,接收两个参数:需要 inject 的 property 的 key,默认值(可选)
为了使 provide 和 inject 的值具有响应性,需要在 provide 时使用ref
或reactive
对于响应式的 property,尽量在 provide 处进行修改,跟 props 的单向数据流相似。如果需要在 inject 处更新 property,可以通过 provide 暴露一个更新方法。
模板引用
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// DOM 元素将在初始渲染后分配给 ref
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
}
</script>
watch()
和watchEffect()
在 DOM 挂载或更新之前运行,所以在这里面运行时,模板引用还未更新,可以使用选项{flush: 'post'}
,这将在 DOM 更新后运行,确保模板引用和 DOM 保持同步。
Mixin
mixin 对象的所有选项将被“混合”入组件本身
const myMixin = {
created() {
this.hello()
},
methods: {
hello() {
console.log('hello from mixin!')
}
}
}
// 定义一个使用此 mixin 对象的应用
const app = Vue.createApp({
mixins: [myMixin]
})
app.mount('#mixins-basic') // => "hello from mixin!"
- 当组件和 mixin 对象拥有同名选项时,这些选项将会合并
- data 返回对象中的 property 重名,将会以组件自身数据优先合并
- 钩子函数将会合并为一个数组,并依次调用,mixin的钩子先于组件钩子调用
- 其他值为对象的选项重名时,将会合并,对象中的 property 重名时,以组件自身的 property 优先
mixin 也可以全局注册,但会影响每一个创建的组件,谨慎使用
Teleport
允许我们控制在 DOM 中哪个父节点下渲染 HTML,一个例子时 toast 组件的 content 部分,我们希望他是基于body
定位的,在vue2 中我是通过模板引用拿到元素,然后将其append
到body
中,现在 teleport 可以直接让我们将 content 部分传送到body
标签下:<teleport to="body">组件内容</teleport>