好记性不如烂笔头
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>必须:
- 将其
valueattribute 绑定到一个名叫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函数接收两个参数:
propscontext
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>