Vue3.0 基础知识点总结
vue3.0重要的优化点
- 模板编译速度的提升, 对静态数据的跳过处理.
- 对数组的监控
- 对ts有了很好的支持
- 对2.x版本的完全兼容
- 可以有多个根节点 (也有bug, 比如外层开了display:flex 那么里面会收到影响, 也就是说布局更灵活但也更要小心, 总之请对自己与他人的代码负责)
- 支持Source map, 虽然没演示但是这点真的重要
# 1. vue3.0 环境搭建
# 1.1. 安装 @vue/cli (将cli升级到4版本)
npm install -g @vue/cli
1
# 1.2. 创建项目模板
vue create next-test
1
# 2. vuex/router/vue 的变化
# 2.1. vue 初始化时的变化
import { createApp } from 'vue';
import App from './App.vue'
import router from './router'
import store from './store'
// 方法一. 创建实例变成了链式, 直接写下去感觉语义与结构有点模糊, 但是我们要理解vue这样做的良苦用心, 前端趋近于函数化.
// createApp(App).use(router).use(store).mount('#app')
// 方法二.
const app = createApp(App);
app.use(router);
app.use(store);
app.mount('#app');
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
# 2.2. vuex 初始化时的变化
import { createStore } from 'vuex'
// 专门创建实例的一个方法
export default createStore({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
});
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 2.3. router 初始化时的变化
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
}
]
const router = createRouter({
// 专门创建history的函数
history: createWebHistory(process.env.BASE_URL),
routes
})
export default router
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3. 变量的定义及使用方法
# 3.1. ref 的使用方法
import { ref } from "vue";
export default {
// 1: 这个版本基本逻辑都在setup里面完成了, 有人说把他当成2.x的data.
setup() {
// 2: 定义一个追踪的变量,也就是双向绑定.
const n = ref(1); // 生成的n是一个对象, 这样方便vue去监控它
function addN() {
n.value++; // 注意这里要使用.value的形式, 因为n是对象↑, value才是他的值
}
return {
n, // 返回出去页面的template才可以使用它, {{n}} 不需要.value
addN
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 3.2. reactive 的使用方法
import { reactive, toRefs } from "vue";
export default {
setup() {
// 注意事项: reactive的对象不可以结构返回或导入, 会导致失去响应式
const obj = reactive({
name: "金毛",
age: 4
});
function addObj() {
obj.age++;
}
return {
...obj, // 这样写不好, 里面会失去响应式
obj, // 这样写那么外面就要都基于obj来调取, 类型{{obj.age}}
...toRefs(obj) // 必须是reactive生成的对象, 普通对象不可以, 他把每一项都拿出来包了一下, 我们可以这样用了 {{age}}, 放心咱们多深的obj也可以响应
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.3. 之前的ref的使用方法
<div>
<div ref="content">第一步, 在dom上面定义, 他会有一个回调</div>
</div>
<ul>
<li>v-for 出来的ref</li>
<li>可以写为表达式的形式, 可以推导出vue是如何实现的</li>
<li>vue2.x的时候v-for不用这么麻烦, 直接写上去会被组装成数组</li>
<li :ref="el => { items[index] = el }" v-for="(item,index) in 6" :key="item">{{item}}</li>
</ul>
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
import { ref, onMounted, onBeforeUpdate } from "vue";
export default {
setup() {
// 2: 定义一个变量接收dom, 名字无所谓, 但是与dom统一的话会有很好的语义化
const content = ref(null);
const items = ref([]);
// 4: 在生命周期下, 这个值已经完成了变化, 所以当然第一时间就拿到
onMounted(() => {
console.log(content.value);
console.log("li标签组", items.value);
});
// 5: 确保在每次变更之前重置引用
onBeforeUpdate(() => {
items.value = [];
});
// 3: 返出去的名称要与dom的ref相同, 这样就可以接收到dom的回调
return {
content,
items
};
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 4. 生命周期函数的使用
<template>
<div>
<button @click="add">点击增加, 触发updata</button>
<p>{{obj.count}}</p>
</div>
<p>
2.x与 3.0的对照
beforeCreate -> 使用 setup()
created -> 使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
</p>
</template>
<script>
//这些生命周期注册方法只能用在 setup 钩子中
import { onMounted, onUpdated, onBeforeUnmount, reactive } from "vue";
export default {
// 1: setup显得冗长, 可以自己动手做一些插件来优化
// 2: 本身更加明确意图
// 3: 需要树立工程师的正确代码意识
// 4: 能力不足可能会写出更差的代码
// 作者说: 提升上界的收益远远大于降低下界的损失。值得深思, 前端需要提高门槛
// 5: 调用时机: 创建组件实例,然后初始化 props ,紧接着就调用setup 函数。从生命周期钩子的视角来看,它会在 beforeCreate 钩子之前被调用
// 6: 这些生命周期钩子注册函数只能在 setup() 期间同步使用, 因为它们依赖于内部的全局状态来定位当前组件实例(正在调用 setup() 的组件实例), 不在当前组件下调用这些函数会抛出一个错误。
// 7: 原则上生命周期里面不会放具体的逻辑,哪怕只有一句赋值一个三元都不可放, 这也正好符合当前的工程模式
// 讨论: 有多少种方式, 可以判断出某个函数 当前处于哪个函数?
// 比如有多层嵌套的组件是否有影响
setup() {
onMounted(() => {
console.log("is mounted!");
});
onUpdated(() => {
console.log("is onUpdated!");
});
onBeforeUnmount(() => {
console.log("is onBeforeUnmount!");
});
const obj = reactive({
count: 1
});
function add() {
obj.count++;
}
return {
obj,
add
};
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 5. 路由的使用方法
<template>
<div>
{{id}}
</div>
</template>
<script>
import { getCurrentInstance, ref } from 'vue';
export default {
setup(){
const { ctx } = getCurrentInstance()
// 1. 这样也是为了去掉this
// 2. 方便类型推导
console.log(ctx.$router); // push等方法
console.log(ctx.$router.currentRoute.value); // 路由实例
// 这个其实没有必要变成ref因为这个值没必要动态
// 但是他太长了, 这个真的不能忍
const id = ref(ctx.$router.currentRoute.value.query.id)
// 4: 页面拦截器
ctx.$router.beforeEach((to, from,next)=>{
console.log('路由的生命周期')
next()
})
return {
id
}
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 6. vuex 的使用方法
import { createStore } from 'vuex'
// 难道前端趋势只有函数这一种吗
export default createStore({
state: {
name:'牛逼, 你拿到我了',
age: 24,
a:'白',
b:'黑'
},
mutations: {
updateName(state, n){
state.name += n
}
},
actions: {
deferName(store) {
setTimeout(()=>{
// 必须只有commit可以修改值, 这个设定我比较反对, 可以讨论
// vuex本身结构就很拖沓, 定义域使用个人都不喜欢
store.state.name = '牛逼, 你改回来了'
},1000)
}
},
getters: {
fullName(state){ return `${state.a} - + -${state.b}` }
},
modules: {
}
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template>
<div>
<p>{{name}}</p>
<button @click="updateName('+')">点击改变名字</button>
<br />
<button @click="deferName('+')">改回来</button>
<p>{{fullName}}</p>
</div>
</template>
<script>
import { useStore } from "vuex";
import { computed } from "vue";
export default {
setup() {
const store = useStore();
// 1: 单个引入
const name = computed(() => store.state.name);
// 2: 引入整个state
const state = computed(() => store.state);
console.log("vuex的实例", state.value); // 别忘了.value
// 3: 方法其实就直接从本体上取下来了
const updateName = newName => store.commit("updateName", newName);
// 4: action一个意思
const deferName = () => store.dispatch("deferName");
// 5: getter 没变化
const fullName = computed(() => store.getters.fullName);
return {
name,
fullName,
deferName,
updateName,
};
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# 7. composition 使用方法
composition 使得前端工程师的编程规范,更接近于原生js。
另外,我们在开发工程而不是插件的话,还是不要使用mixin了,这东西无法追溯来源,搞得语义太差了,例如:
<template>
<div>
<button @click="addN1">上面的增加</button>---> {{n1}}
</div>
<div>
<button @click="addN2">下面的增加</button>---> {{n2}}
<button @click="addN210">每次n2+10</button>
</div>
<div>
<p>组件里面也可以如此引用, 这就可以代替mixin一部分功能了</p>
<button @click="addN3">n3的增加</button>---> {{n3.value}}
</div>
<div>
<com></com>
</div>
</template>
<script>
import { ref} from 'vue';
import n3Change from './mixin';
import com from '../components/composition.vue';
export default {
components:{
com
},
setup(){
// 1: setup只是一个整合函数
// 2: 甚至整个函数里面可能会没有具体的逻辑
// 3: 以此推断, ref等方式定义的变量, 会自动识别在哪个setup内部, 从而达到逻辑的复用
// 4: 由此方法可以很好的代替mixin了
// 5: 当然, 这里也可以截获数据,来做一些事情
const {n2, addN2} = n2Change();
function addN210(){
n2.value += 10
}
return {
...n1Change(),
...n3Change(),
n2,
addN2,
addN210
}
}
}
// 甚至已经可以写在任何地方了, 响应式自由度大大提高
function n1Change(){
const n1 = ref(1);
let addN1 = ()=>{
n1.value++
}
return {
n1,
addN1
}
}
function n2Change(){
const n2 = ref(1);
let addN2 = ()=>{
n2.value++
}
return {
n2,
addN2
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
写在任何地方, 然后导入就成了 mixin
import { reactive } from 'vue';
export default ()=> {
const n3 = reactive({
name: 'mixin',
value: 1
})
const addN3=()=>{
n3.value++
}
return {
n3,
addN3
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 8. 关于插件的新思路
::: info 开发插件并不一定要挂载到vue的原型上 导致vue原型臃肿, 命名冲突等等(比如两个ui都叫 message) 原理就是 provide 和 inject, 依赖注入 :::
import {provide, inject} from 'vue';
// 这里使用symbol就不会造成变量名的冲突了, 这个命名权交给用户才是真正合理的架构设计
const StoreSymbol = Symbol()
export function provideString(store){
provide(StoreSymbol, store)
}
export function useString() {
const store = inject(StoreSymbol)
return store
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
app.vue页面统一的初始化一下
export default {
setup(){
// 一些初始化'配置/操作'可以在这里进行
// 需要放在对应的根节点, 因为依赖provide 和 inject
provideString({
a:'可能我是axios',
b:'可能我是一个message弹框'
})
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
在需要使用的组件里面接收
<template>
<div>
插件的演示
</div>
</template>
<script>
import { useString } from '../插件';
export default {
setup(){
const store = useString();
// 不光是拿到值, 可以由app定义什么可以被拿到
console.log('拿到值了',store)
}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 9. 新观察者简单使用
<template>
<div>
<button @click="addn1">n1增加--{{n1}}</button>
<button @click="addn2">n2增加--{{n2}}</button>
<button @click="addn3">n3增加--{{n3}}</button>
</div>
</template>
<script>
import { watch, ref } from "vue";
export default {
setup() {
const n1 = ref(1);
const n2 = ref(1);
const n3 = ref(1);
// 1: 监听一个
// 第一个参数是函数返回值, 当然也可以 直接写n1
// 如果监听的是一个对象里面的某个属性, 那就需要这种函数的写法了, 比2.x的字符串写法高明很多
watch(
() => n1.value,
(val, oldVal) => {
console.log("新值", val);
console.log("老值", oldVal);
}
);
// 2: 监听多个
// 数组的形式定义多个, 这就出现问题了吧, 如果我观察的对象就是个数组, 并且每一项都是一个返回值的函数, 岂不是会被他误认为是多监控的结构, 苦恼
watch(
[() => n2.value, ()=>n3.value],
([val, val3],[val2, val4]) => {
// val 是 n2的新值 val2是 n2的老值
// val3 是 n3的新值 val4是 n3的老值
console.log("新值 与 老值 是这种对应关系", val, val2);
console.log("新值 与 老值 是这种对应关系", val3, val4);
}
);
function addn1() {
n1.value++;
}
function addn2() {
n2.value++;
}
function addn3() {
n3.value++;
}
return {
addn1,
addn2,
addn3,
n1,
n2,
n3
};
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 10. 新的计算属性使用方法
别看 watchEffect 带个 watch ,但是他的功能可以归为计算属性里面
<template>
<div>
<button @click="addCount">点击计算</button>
<button @click="setCount(1)">点击出发set</button>
<p>count--{{count}}</p>
<p>count2--{{count2}}</p>
<p>count3--{{count3}}</p>
</div>
</template>
<script>
// 弄得类似react了
import { computed, ref, watchEffect } from "vue";
export default {
setup() {
const count = ref(1);
// 1. 默认的定义方式
const count2 = computed(() => count.value * 2);
console.log(count2.value); // 也要value因为可能是简单类型
// 2. getter与setter当然可以定义
const count3 = computed({
get: () => count.value * 3,
set: val => {
// 这里重置了count
count.value = val;
}
});
// 3. watchEffect 更像是计算函数
// 立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数
// 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
// Vue 的响应式系统会缓存副作用函数,并异步地刷新它, 比如同时改变了count与conut4此时watchEffect只是执行一次
// 初始化运行是在组件 mounted 之前执行的。因此,如果你希望在编写副作用函数时访问 DOM(或模板 ref),请在 onMounted 钩子中进行
// 并不是返回值, 而是监听里面所有的值, 任何有变化都会重新执行, 他应该可以玩出点东西。
const count4 = ref(1);
const stop = watchEffect(() => {
if (count4.value) {
console.log("作为判断条件也可以根据count4的变化而重新执行");
}
console.log(count.value);
});
setTimeout(() => {
stop(); // 停止监听
}, 10000);
function addCount() {
count.value++;
setTimeout(() => {
count4.value++;
}, 1000);
}
// 触发setter
function setCount() {
count3.value = 2;
}
return {
count,
count2,
addCount,
count3,
setCount
};
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# 11. customRef 简单使用(防抖)
当然这里说的防抖不是重点, 重点是这种代码的思维
<template>
<div>
<input type="text" v-model="text" />
</div>
</template>
<script>
import { customRef, onUnmounted } from "vue";
export default {
setup() {
let timeout = null; // 并不需要响应式
const text = useDebouncedRef("hello", (time) => {
// 毕竟是延时的不用担心获取不到这个值
console.log("延时执行回调", text.value);
console.log('时间实例', time)
timeout = time;
});
// 好习惯也是成为合格工程师的必要条件
onUnmounted(()=>{
clearTimeout(timeout);
})
return {
text
};
}
};
// 并不用纯粹的js编写, 可以利用customRef来监控这个值的一举一动
// 写法一般, 但是思路又多了一条, 感谢
function useDebouncedRef(value, callback, delay = 200) {
let timeout;
// 两个参数分别是用于追踪的 track 与用于触发响应的 trigger
// 这两个参数对 值的追踪 在当前并没有用,比如watchEffect的出发机制
// 不调用这两个值没问题, 但是如果写成插件的话还是要调用的, 因为别人没准在追踪这个值,
// 注意: 这个函数不可以有太大的delay, 如果超过500的话就需要考虑在组件销毁时候的清除定时器, 反而逻辑加深了, 此时我们可以每次把演示器的实例拿到
return customRef((track,trigger) => {
return {
get() {
track()
return value;
},
set(newValue) {
clearTimeout(timeout);
// callback接受的太晚了, 可以在这里用另一个函数或对象接收
timeout = setTimeout(() => {
value = newValue;
trigger()
callback(timeout);
}, delay);
}
};
});
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# 12. 组件与注入
父级组件
<template>
<div>
组件:
<zj :type="type" @ok="wancheng"></zj>
</div>
</template>
<script>
import zj from "../components/子组件.vue";
import { ref } from 'vue';
import { provide } from 'vue'
export default {
components: {
zj
},
setup() {
provide('name','向下传值'); // 基础值
provide('name2', ref('向下传值')); // 监控值
const type = ref('大多数');
function wancheng(msg){
console.log('子组件-->',msg)
setTimeout(()=>{
type.value = 'xxxxxxx'
},2000)
}
return {
type,
wancheng
}
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
子组件
<template>
<div>props的属性不用setup去return --- {{type}}</div>
</template>
<script>
import { inject, ref } from 'vue'
// 为了让 TypeScript 正确的推导类型,我们必须使用 createComponent 来定义组件:
export default {
props: {
type: String
},
// 1: props也是不可以解构的, 会失去响应式
// 2: context是上下文, 我们可以获取到slots emit 等方法
// 3: props, context 分开也是为了ts更明确的类型推导
// setup({type}){
setup(props, context) {
// 1: props
console.log("props", props.type);
console.log("上下文", context);
context.emit('ok','传递完成')
// 2: 注入
console.log('inject',inject('name'));
console.log('inject',inject('xxxx','我是默认值'))
inject('name1', ref('默认值')) // 接收方也可以这样
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
上次更新: 2024/01/30, 00:35:17
- 02
- Node与GLIBC_2.27不兼容解决方案08-19
- 03
- Git清空本地文件跟踪缓存08-13