官方文档:Vue.js
一、Virtual DOM, Render, diff
- 利用JavaScript脚本操作DOM的行为是性能损耗的大头,故产生虚拟DOM的概念。初始化在内存中得到相应的虚拟DOM树,然后将该结果一次性作用于真实DOM,逻辑层的变动导致部分视图层的改变这渲染逻辑也是经过虚拟DOM加上diff得到一次性结果然后作用于真实DOM
- 结构渲染方面,vue文件中的template利用vue的render函数以及AST语法树解析来生成虚拟DOM树对象
- 当视图发生更新,需要比对更新前以及更新后的虚拟DOM差异,这里面的比对的逻辑叫做diff算法,最终diff出来的结果一次性只更新需要更新的真实DOM节点
二、globalProperties
- 响应式变量是利用ES6的Proxy(Vue3)来实现的
- 计算属性和侦听器
- 计算属性和方法之间,计算方法具备缓存能力,而方法不具备
- 默认计算属性是只读的,不过可以对具体计算属性写成get set的形式组织
- 计算属性适合:多个值影响一个值的应用
- 侦听器适合:一个值影响多个值的应用
- 侦听器支持异步,计算属性不支持
- 属性:
name
,props
,emits
,components
,directives
,computed
,methods
,watch
- 方法:
data
,mounted
,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>learn vue</title>
<script src="./vue.global.js"></script>
</head>
<body>
<div id="app">
<div>parent count: {{count}}</div>
<my-head
:title="headerTitle"
@custom-event="handleCustomEvent"
v-model="count"
></my-head>
</div>
<script>
let app = Vue.createApp({
data() {
return {
headerTitle: "header-title",
count: "1",
};
},
methods: {
handleCustomEvent(data) {
console.log(data);
},
},
});
// local component
const HeaderLocalComponent = {
template: `header-local-component`,
};
// global component
app.component("my-head", {
props: {
title: {
type: String,
},
modelValue: {
type: String,
},
}, // or props: ["title"]
emits: ["custom-event", "update:modelValue"],
template: `
<header>
<h1>{{headTitle}}</h1>
<p>message: {{message}}</p>
<p>reverse message: {{reverseMessage}}</p>
<header-local-component></header-local-component>
<input type="text" :value="modelValue" @input="handleInput" />
</header>
`,
components: {
HeaderLocalComponent,
},
data() {
return {
message: "hello world",
headTitle: this.title, // props data -> responsive...
};
},
mounted() {
setTimeout(() => {
this.message = "hello world2";
this.headTitle = "head change self title...";
this.$emit("custom-event", {
info: "child to parent.",
});
}, 2000);
},
computed: {
// reverseMessage() {
// return this.message.split(" ").reverse().join(" ");
// },
reverseMessage: {
get() {
return this.message.split(" ").reverse().join(" ");
},
set(value) {
this.message = value;
},
},
},
methods: {
handleInput(e) {
this.$emit("update:modelValue", e.target.value);
},
},
watch: {
modelValue(newValue, oldValue) {
console.log("modelValue change...", oldValue, newValue);
},
},
});
const vm = app.mount("#app");
</script>
</body>
</html>
custom globalProperties
自定义全局属性
app.config.globalProperties.$something = something
使用
this.$something
$attrs
- 组件默认传递属性,如果子组件没有接收,那么会作为默认属性添加到dom身上
- 如果要去除上述的默认传递效果,可以添加inheritAttrs参数
app.component("my-head", {
data() {
return {
message: "hello world",
};
},
inheritAttrs: false
···
});
- 如果添加了inheritAttrs参数,但又需要对指定dom添加自定义属性
<h1 v-bind:info="$attrs.info"></h1>
- this.$attrs也可以进行通信
$refs
- ref属性作用在元素上,this.$refs.xxx获取的是原生DOM
- ref属性作用在组件上,this.$refs.xxx获取的是组件实例对象,可以调用其方法或是响应式变量
console.log('作用在DOM上的ref: ', this.$refs.domRef)
console.log('作用在组件上的ref: ', this.$refs.homeHomeChild1Ref)
- 可以间接进行组件间的通信
$nextTick
- 在DOM渲染之后的执行时机,同updated生命周期
mounted() {
console.log('全局变量$globalVar: ', this.$globalVar)
this.$nextTick(() => {
console.log('作用在DOM上的ref: ', this.$refs.domRef)
console.log('作用在组件上的ref: ', this.$refs.homeHomeChild1Ref)
})
}
- 有两种写法,一种是hook风格,另一种是Promise链式调用风格
$event
原生DOM事件传参(内部封装)
- 不传参数:只传递函数名,methods中的函数形参可传默认e
- fun($event, param)
<button @click="clickButton1">不传参数</button>
<button @click="clickButton2('data')">传1个参数</button>
<button @click="clickButton3($event, 'data')">传2个参数</button>
三、逻辑复用
组件(components)
<template>
<div class="box">HomeChild1组件</div>
<HomeChild1 :msg="var4" @customEvent="customEvent" />
</template>
<script>
import HomeChild1 from '../components/HomeChild1.vue'
export default {
name: 'HomeView',
components: {
HomeChild1
},
data() {
return {
var4: 'HomeView组件传递给HomeChild1组件的值',
}
},
methods: {
customEvent(data) {
console.log('customEvent', data)
}
}
}
</script>
- 组件的注册以及使用
- 根组件以及普通组件:根组件template比html结构中的子内容优先级更高(覆盖渲染)
- 全局组件以及局部组件的选项式写法
自定义指令(v-)
自定义指令也是逻辑复用的常见形式之一,比如对节点赋予拖拽功能,基本语法如下:
<template>
<div class="box">自定义指令</div>
<input v-focus />
</template>
<script>
export default {
name: 'HomeView',
directives: {
focus: {
mounted: (el) => el.focus()
}
}
}
</script>
插件
定义
app.use(
{
install: (app, options) => {
app.config.globalProperties.$translate = (key) => {
return key.split('.').reduce((o, i) => {
if (o) return o[i]
}, options)
}
}
},
{
greetings: {
hello: 'Hello, world!'
}
}
)
使用
<template>
{{ $translate('greetings.hello') }}
</template>
⛔mixins
- 一种复用逻辑的抽离的方式,但是更为推荐组合式的hook来复用代码逻辑,vue的最新文档甚至去除了mixins相关介绍
- 使用:
minins: [module]
- 缺点
- 无法差异化
- 可存在同名函数(代码风格)
四、生命周期
初始化实例时触发
- beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用
- 响应式数据没有,dom未渲染
- created:在实例创建完成后被立即调用
- 响应式数据有了,dom未渲染
- 一般在这里异步请求初始化数据
- beforeMount:在挂载开始之前被调用
- 响应式数据有了,dom未渲染
- mounted:el被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子
- 响应式数据有了,dom已渲染
- activated(KeepAlive相关)
- deactivated(KeepAlive相关)
更新数据时触发
- beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前
- 响应式数据变了,但是dom还没有更新
- updated:由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子
- 响应式数据变了,dom也更新了
卸载实例时触发
- beforeDestroy:实例销毁之前调用
- 响应式数据还有,dom也有
- destroyed:实例销毁后调用
- 响应式数据还有,dom没了
- errorCaptured
- renderTracked(开发模式才有,且SSR没有)
- renderTriggered(开发模式才有,且SSR没有)
- serverPrefetch(SSR才有)
五、内置组件
component
- 配合:is实现动态切换组件的行为
<template>
<div class="box1">内置组件</div>
<button @click="temp = 'TemplateChild1'">TemplateChild1</button>
<button @click="temp = 'TemplateChild2'">TemplateChild2</button>
<component :is="temp" />
</template>
<script>
import TemplateChild1 from '../components/TemplateChild1.vue'
import TemplateChild2 from '../components/TemplateChild2.vue'
export default {
name: 'TemplateView',
data() {
return {
temp: 'TemplateChild1'
}
},
components: {
TemplateChild1,
TemplateChild2
}
}
</script>
KeepAlive
在上述组件切换过程中,被切换组件的状态会被清除,也就是输入框会输入框输入的数据会被清空,如果需要保留状态,外面需要套一层keep-alive组件
<KeepAlive>
<component :is="temp" />
</KeepAlive>
defineAsyncComponent
动态组件中(component),会一次性加载所有组件,如果需要按需加载则使用defineAsyncComponent,
<template>
···
</template>
<script>
import { defineAsyncComponent } from 'vue'
export default {
name: 'TemplateView',
data() {
return {
temp: 'TemplateChild1'
}
},
components: {
TemplateChild1: defineAsyncComponent(() => import('../components/TemplateChild1.vue')),
TemplateChild2: defineAsyncComponent(() => import('../components/TemplateChild2.vue'))
}
}
</script>
Suspense
内置边界加载中组件
<template>
<div class="box1">内置组件</div>
<button @click="temp = 'TemplateChild1'">TemplateChild1</button>
<button @click="temp = 'TemplateChild2'">TemplateChild2</button>
<KeepAlive>
<Suspense>
<component :is="temp" />
<template #fallback>
<div>loading...</div>
</template>
</Suspense>
</KeepAlive>
</template>
Teleport
“传送门”逻辑,相当于指定要渲染的DOM至指定文档树位置,有益于DOM结构合理
如果DOM结构不合理,会造成一些样式上不必要的写法
<Teleport to="body"><div>Teleport组件</div></Teleport>
如果不使用vue内置组件teleport,传送门的同类型实现逻辑则是利用原生DOM api以及Vue的createApp结合实现
<script>
import { createApp } from 'vue'
import { Component } from 'xxxx'
const target = document.createElement('div')
document.body.append(target)
createApp(Component).mount(target)
</script>
Transition
vue内置动画组件
<transition name="slide" mode="out-in">
<keep-alive>
<component :is="compo"></component>
</keep-alive>
</transition>
.fade-enter-from {
opacity: 0;
}
.fade-enter-to {
opacity: 1;
}
.fade-enter-active {
transition: 1s;
}
.fade-leave-from {
opacity: 1;
}
.fade-leave-to {
opacity: 0;
}
.fade-enter-active {
transition: 1s;
}
.slide-enter-from {
opacity: 0;
transform: translateX(20px);
}
.slide-enter-to {
opacity: 1;
transform: translateX(0);
}
.slide-leave-from {
opacity: 1;
transform: translateX(0);
}
.slide-leave-to {
opacity: 0;
transform: translateX(20px);
}
.slide-leave-active {
transition: 1s;
}
评论区