Vue3

Vue3介绍

Vue.JS 3.0兼容2.0的大多数语法。

性能提升

  1. 项目打包体积更小;
  2. 需要的运行内存更小;
  3. 初次渲染更快,更新渲染更快;
  4. 使用Proxy代替Object.defineProperty实现数据响应式;
  5. 重写虚拟Dom的实现和Tree-Shaking;(提升模板编译的速度;摇树操作–>减少项目打包的体积)
  6. 更好的支持TypeScript

获取Vue3.0

  1. cdn方式
    • 创建实例对象的方式不一样;
    • 挂载应用实例的方式不一样;
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<!-- 创建一个挂载点 -->
<div id="apps">
<h2>{{msg}}</h2>
</div>
</body>
<!-- method1 cdn方式导入Vue3源码包 下载到本地引入(不受网络限制的影响) -->
<script src="./vue.global.js"></script>
<script>
/**
vue2: 创建实例对象的方式 new一个类

new Vue({
el:"#apps",
data:{
},
methods:{
}
})
*/


// <!-- vue3 创建实例对象的方式 -->
/**
调用Vue的createApp()静态方法: Vue.createApp();
*/
const app = Vue.createApp({
// data必须是个函数
data(){
return{
msg:"hello Vue3"
}
}
});

// 挂载应用实例
app.mount("#apps");
</script>
</html>

现象:

  • 通过app.mount("#apps")挂载应用实例,浏览器会展现出经h2渲染后的hello Vue3
  • 通过 el:"#apps"方式挂载应用实例,浏览器会展现出经h2渲染后的{{msg}};即挂载点挂载失败了;
  1. vue cli脚手架

    • 安装最新版本的vue cli脚手架:npm i @vue/cli -g
    • 创建项目:vue create 项目名称
    • 选择vue3版本的默认选项
    • 现象:目录结构3与2大致一样;语法部分不一样;例如:

    main.js(入口文件):

    1
    2
    3
    4
    5
    //vue3 
    import { createApp } from 'vue' //从 vue包中 按需导入 createApp 方法
    import App from './App.vue' //导入根组件

    createApp(App).mount('#app') //把根组件交给createApp,然后挂载节点;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //vue2
    import Vue from 'vue'
    import App from './App.vue'

    Vue.config.productionTip = false

    new Vue({
    render: h => h(App),
    }).$mount('#app')
  2. Vite构建工具

    Vite是一个web开发构建工具

    Vite仅支持vue3.0+的项目,也即是说我们无法在其中使用vue2.x

    在成功安装 vue-cli 之后即可使用 Vue3 带来的新工具 Vite 构建项目

    Vite的底层不是基于webpack

    Vite的底层是基于浏览器对原生ES模块语法的支持来进行项目构建,所以搭建速度很快

    1. 创建项目
    1
    npm init vite@latest  项目名称
    1. 安装项目依赖

      在项目的根目录下运行命令

    1
    2
    3
    4
    5
    //在项目的根目录下运行命令
    cd 项目名称
    npm install
    //或者
    npm i
    1. 启动项目

      在项目的根目录下运行命令

    1
    npm run dev
    1. 默认监听端口号为3000
    1
    http://localhost:3000

新增特性

Fragment模板碎片

  • vue2中组件的模板必须有一个唯一的根标签
  • vue3中组件模板可以有多个根标签(可能编辑器会报错,是因为Vetur插件的问题,看下面的第二个参考文献来解决)

在Vite创建的项目中 src–>components下新建一个home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{msg}}</h2>
<h2>{{texts}}</h2>
</template>

<script>
export default {
data() {
return {
msg: "hello vue3",
texts: "你好 vue3"
};
}
};
</script>

<style>
</style>

在App.vue里面进行 导入,注册,调用 三部曲

在vue3 里面 script添加了setup属性,有这个属性则可以不需要手动注册子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script setup>
//一旦在script标签上添加了setup属性,则不需要手动注册子组件;
//在vite开发环境中 .vue 的后缀不能省略,因为vite是根据文件的后缀来决定如何处理该文件的;
import Home from './components/home.vue'

// 如果没有setup属性,则需要手动注册子组件
// export default{
// components:{Home}
// }
</script>

<template>
<!-- 调用 -->
<Home/>
</template>

Composition(组合)API

  • 作用:将不同根组件的相同部分抽离出来,单独维护,提高代码的复用率
  • setup()方法
  • 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
  • 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
  • 在setup方法中返回的数据,会自动和组件data中的数据进行合并;
  • 在setup方法中返回的方法,会自动和methods对象进行合并;
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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<hr>
<button @click="getName">按钮</button>
</template>

<script>
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup(){
// console.log(this); //undefined

// 在setup方法中返回的数据,会自动和组件data中的数据进行合并;
// 在setup方法中返回的方法,会自动和methods对象进行合并;

return{
name:"张三",
age:20,
getName(){
console.log(this.name); //张三
}
}
},
data() {
return {
msg: "hello vue3",
texts: "你好 vue3"
};
}
};
</script>

<style>
</style>

现象:页面展示经h2渲染后的张三 20与一个按钮,点击按钮,控制台输出张三

那如果直接在setup方法里面进行修改data的操作呢?

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<hr>
<button @click="getName">按钮</button>
</template>

<script>
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup(){
// console.log(this); //undefined

// 在setup方法中返回的数据,会自动和组件data中的数据进行合并;
// 在setup方法中返回的方法,会自动和methods对象进行合并;

return{
name:"张三",
age:20,
getName(){
this.name="李四"; //这里并不是响应式数据
console.log(this.name); //李四
}
}
},
data() {
return {
msg: "hello vue3",
texts: "你好 vue3"
};
}
};
</script>

<style>
</style>

现象:点击按钮之后,页面上的值依旧是张三,但是如果打印this.name,输出的是李四;这是因为setup方法里面的data并不是响应式数据

那如何实现响应式数据呢?

  • reactive

    • 作用:帮助我们创建响应式数据对象

    • 语法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      import {reactive} from "vue";  //在vue包里面按需引入 reactive
      export default{
      setup(){
      //创建一个响应式对象
      const obj = reactive({ // reactive需要一个对象作为实参;返回一个obj,obj为响应式对象
      });
      return obj; //一定要返回出去(将数据展现到视图上)
      }
      }

具体代码如下

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<hr>
<button @click="getName">按钮</button>
</template>

<script>
import { reactive } from "vue";
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup() {
//将要实现响应式的data数据写到reactive的对象实参里面;然后直接返回整体的响应式对象obj;
const obj = reactive({
name: "张三",
age: 20
});

return obj; //将数据展现到视图上
},
data() {
return {
msg: "hello vue3",
texts: "你好 vue3"
};
},
methods: {
getName() {
this.name = "李四";
this.age=24;
}
}
};
</script>

<style>
</style>

现象:点击按钮,页面上原本的数据张三 20 改变成了 李四 24

那如果需求为:name和age单独维护,而不是包装成一个对象呢;

  • ref

    • 作用:基于基本数据类型(字符串、布尔、数值)创建一个响应式的数据对象;

    • 语法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import {ref} from "vue";
      export default{
      setup(){
      //使用ref 基于基本数据类型 创建响应式数据对象
      const name = ref("张三");
      const age = ref(20);
      console.log(name);
      console.log(age);
      //将数据展现到视图上
      return{
      name,age;
      }
      }
      }

    具体代码如下

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<hr>
<button @click="getName">按钮</button>
</template>

<script>
import { ref } from "vue";
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup() {
const name = ref("张三");
const age = ref(20);
// 修改name默认值
// name.value= "李四"; //name是个对象 有个value属性(在setup里,我们要使用ref的响应式对象时候 需用到value)
console.log(name); //name是个对象 有个value属性(在setup里,我们要使用ref的响应式对象时候 需用到value)

return {
name,
age
};
},
data() {
return {
msg: "hello vue3",
texts: "你好 vue3"
};
},
methods: {
getName() {
this.name = "李四";
this.age = 24;
}
}
};
</script>

<style>
</style>

现象:点击按钮,页面上原本的数据张三 20 改变成了 李四 24

那如果reactive通过解构赋值,是不是也可以完成 name和age的单独维护呢?

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<hr>
<button @click="getName">按钮</button>
</template>

<script>
import { reactive,ref } from "vue";
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup() {
const {name,age} = reactive({
name:"张三",
age:20
});
return {
name,age
}
},
data() {
return {
msg: "hello vue3",
texts: "你好 vue3"
};
},
methods: {
getName() {
this.name = "李四";
this.age = 24;
console.log(this.name); //李四
}
}
};
</script>

<style>
</style>

现象:点击按钮,页面上原本的数据张三 20 并未发生数据的改变;但是输出的 this.name李四

发生如上的原因是因为:reactive创建响应式数据对象,不支持ES6的结构赋值,结构赋值会失去响应式的特性;

那有什么办法吗?

  • toRefs

    • 作用:可以让reactive创建的响应式数据对象,支持ES6的解构赋值,同时保持响应式的特性;

    • 语法:toRefs();参数为reactive创建的响应式数据对象;(小括号把整个reactive({})包裹起来;)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      import {toRefs} from "vue";
      export default{
      setup(){
      //创建响应式对象
      const {nage,age} = toRefs(reactive({
      name:"张三",
      age:20
      }));
      return{
      name,age
      }
      }
      }

具体代码如下

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{name}}</h2>
<h2>{{age}}</h2>
<hr>
<button @click="getName">按钮</button>
</template>

<script>
import { reactive,ref,toRefs } from "vue";
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup() {
const {name,age} = toRefs(reactive({
name:"张三",
age:20
}));
return {
name,age
}
},
data() {
return {
msg: "hello vue3",
texts: "你好 vue3"
};
},
methods: {
getName() {
this.name = "李四";
this.age = 24;
console.log(this.name);
}
}
};
</script>

<style>
</style>

现象:点击按钮,页面上原本的数据张三 20 改变成了 李四 24

  • computed计算属性

    • vue2
    1
    2
    3
    4
    5
    export default{
    computed:{
    //计算属性方法
    }
    }
    • vue3

    使用方式1:(和2一样)

    1
    2
    3
    4
    5
    export default{
    computed:{
    //计算属性方法
    }
    }

    使用方式2:(在setup中方式1不可用)

    1
    2
    import {computed} from "vue";
    const computedData = computed(()=>{})

小案例:使用方式二,完成字符串的反转

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<h2>{{msg}}</h2>
<h2>{{msgReverse}}</h2>
<hr>
<button @click="getName">按钮</button>
</template>

<script>
import { reactive,ref,toRefs,computed } from "vue";
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup() {
const {name,age} = toRefs(reactive({
name:"张三",
age:20
}));

// 使用ref实现对基本类型的响应式数据
const msg = ref("hello world");
// 实现字符串的翻转
// computed:创建计算属性方法
const msgReverse = computed(()=>{
return msg.value.split('').reverse().join(''); //在setup中,我们要使用ref的响应式对象时候 需用到value
});

return {
name,age,msg,msgReverse
}
},
data() {
return {
};
},
methods: {
getName() {
this.name = "李四";
this.age = 24;
console.log(this.name);
}
}
};
</script>

<style>
</style>

现象:页面上展示hello world dlrow olleh

  • watch侦听器

    • vue2
    1
    2
    3
    4
    5
    export default{
    //侦听器
    watch:{
    }
    }
    • vue3

    使用方式1:(和2一样)

    1
    2
    3
    4
    5
    export default{
    //侦听器
    watch:{
    }
    }

    使用方式2:(在setup中方式1不行)

    1
    import {watch} from "vue";

使用ref创建响应式数据时进行侦听(基本数据类型)

基本数据类型的监听,在vue3中被ref定义的数据通常在使用时需要在后面加一个.value,但是这里需要监听整个RefImpl对象才能起作用,所以说在写watch时不需要加.value

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<input type="text" v-model="names">
<hr>
<input type="text" v-model="names">
</template>

<script>
import { reactive,ref,toRefs,computed,watch } from "vue";
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup() {

// 使用ref对基本数据类型进行响应式
const names = ref(""); //定义一个names响应式数据,值为空
// 使用watch 侦听数据的变化; 一共两个参数,第一个参数为监听的对象,第二个参数是个回调函数,函数内置两个参数:
// value: 系统自动注入,更新之后的最新数据
// oldValue: 系统自动注入,更新之前的数据
watch(names,(value,oldValue)=>{
console.log(value,oldValue);
});
//注意:
// 基本数据类型的监听,在vue3中被ref定义的数据通常在使用时需要在后面加一个.value,但是这里需要监听整个RefImpl对象才能起作 // 用,所以说在写watch时不需要加.value
return{
names,
}

},
data() {
return {
};
},
methods: {
}
};
</script>

<style>
</style>

现象:在输入框输入 123,打印输出 112 1123 12,即value与oldValue;

如果有两个数据需要监听可以直接写成两个watch函数

1
2
3
4
5
6
7
watch(sum,(value,oldValue)=>{
console.log("SUM",value,oldValue)
});

watch(msg,(value,oldValue)=>{
console.log('msg',value,oldValue);
});

或者把监听的对象写成一个数组,这样写的话,oldValue和newValue全都会变成数组

1
2
3
watch([sum,msg],(value,oldValue)=>{
console.log(value,oldValue) //['1', ''] ['', '']
});

使用reactive创建响应式数据时进行侦听(引用数据类型)

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
<template>
<!-- 在vue2 中 template里面只能有唯一的一个根标签,一般设置div,在div里面写代码 -->
<!-- 在vue3中,可以有多个根标签,当然也可以套个div作为唯一根标签 -->
<input type="text" v-model="sum.name">

<hr>
<h2>{{sum.name}}</h2>
</template>

<script>
import { reactive, ref, toRefs, computed, watch } from "vue";
export default {
// 在组件渲染完成之前自动执行,所以不能在setup中通过this访问组件对象
// 组件相同的代码块放到setup里面,让组件在第一次创建的时候执行里面的逻辑,完成对应的操作;
setup() {
// 使用watch 侦听数据的变化; 一共三个参数,第一个参数为监听的对象,第二个参数是个回调函数,函数内置两个参数:
// value: 系统自动注入,更新之后的最新数据
// oldValue: 系统自动注入,更新之前的数据
//第三个参数{deep:true}
let sum = reactive({
name: "",
age: 20,
friend: {
name: "李四",
age: 25
}
});


watch(sum, (value, oldValue) => {
console.log(value, oldValue);
});

return {
sum
};
},
data() {
return {};
},
methods: {}
};
</script>

<style>
</style>

现象:value与oldValue值一样,都是value的值,其实是获取不到oldValue;

那有什么办法获取到oldValue吗?

如果只对对象中的某个属性进行监听,需要把第一个参数写成函数的形式才会生效

1
2
3
watch(()=>sum.name, (newValue, oldValue) => {
console.log(newValue, oldValue);
});

如果要监听对象中的多个属性可以把第一个参数写成数组的形式

1
2
3
watch([()=>sum.name,()=>sum.age], (value, oldValue) => {
console.log(value, oldValue);
});
  • Composition(组合)API中调用生命周期函数
    • 如果要在setup方法中调用组件生命周期钩子函数,在原来的生命周期钩子函数名称前加on关键字,并且保持小驼峰的命名方式
    • 因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们

那如何在setup中使用钩子函数呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
//1.导入需要的钩子函数
import {onMounted, onUpdated} from "vue";
//2.参数是个函数
export default {
setup(){
onMounted(function(){
console.log("is mounted")
}),
onUpdated(()=>{
console.log("is updated")
})
}
}

h渲染函数

h函数就是vue中的createElement方法,这个函数作用就是创建虚拟dom,追踪dom变化的;

还有个作用就是渲染组件的;

  • vue2中

系统会自动在render中注入渲染函数

1
2
3
4
5
6
7
8
import APP from "./APP.vue";

new Vue({
//h 是系统自动注入的渲染函数,不需要引入
render(){
return h(APP); //将组件对象交给h渲染函数,进行视图的展示
}
}).$mount("#app");
  • vue3中

需要 手动 按需导入 h渲染函数

1
2
3
4
5
6
7
8
9
import { createApp,h} from 'vue'
import App from './App.vue'

// createApp(App).mount('#app');
createApp({
render(){
return h(App); //将组件对象交给h渲染函数,进行视图的展示
}
}).mount("#app")

h创建元素对象(vue2也可以的)

h(“元素名称”,{属性集合},[标签文本内容])

1
2
3
4
5
6
7
8
9
import { createApp,h} from 'vue'
// import App from './App.vue'

// createApp(App).mount('#app');
createApp({
render(){
return h("h2",{style:{color:"red"}},"hello Vue3")
}
}).mount("#app")

现象:界面显示 红色的h2标签渲染的 hello Vue3

Teleport:瞬移组件的位置

  • Teleport也是一种组件,他能够将我们的模板移动到DOM中Vue app 之外的其他位置;
  • 在处理较大的Vue项目时,有逻辑处理组织代码库是很重要的。 但是,当处理某些类型的组件(如模式,通知或提示)时,模板HTML的逻辑可能位于与我们希望渲染元素的位置不同的文件中;
  • 实际上,在很多时候,与我们的Vue应用程序的DOM完全分开处理时,这些元素的管理要容易得多。 所有这些都是因为处理嵌套组件的位置,z-index和样式可能由于处理其所有父对象的范围而变得棘手;
  • 例如下面的代码,能够将<child-component name="John" />组件传送到#endofbody的标签里去渲染,同时,props参数name="John"可以正常传递。
1
2
3
<teleport to="#endofbody">
<child-component name="John" />
</teleport>
  • Teleport组件,在代码中能保持原有布局层级、参数传递逻辑,但对应生成的Dom,则传送到了to参数指定的标签;
  • Teleport主要用于“全屏模式的组件”,比如以下几种:

图片查看全屏显示

弹框(广告跳转框、提示框)

对话框(带有表单输入的、带有按钮的)

小案例:

假设我们有一些子组件,我们想在其中触发弹出的通知。 正如刚才所讨论的,如果将通知以完全独立的DOM树渲染,而不是Vue的根#app元素,则更为简单;

首先,我们要打开index.html,即Vue中唯一的html文件,在#app同级下,创建一个div;

1
2
3
4
5
<body>
<div id="app"></div>
<div id="myTelePort"></div>
<script type="module" src="/src/main.js"></script>
</body>

然后,将我们的通知组件放到新建的#myTelePort里面;

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
<template>
<div class="box">
<button class="boxBtn" @click="showNotification">toggle</button>

<Teleport to="#myTelePort">
<div class="showMessage" v-if="isOpen">Teleport 消息~~</div>
</Teleport>

</div>
</template>

<script>
import { ref } from "vue";
export default {
setup() {
// 创建一个响应式数据
const isOpen = ref(false);

// console.log(isOpen) ref创建的响应式数据 是一个RefImpl对象 在setup中需要操作其 .value的属性;

var hideNotification;

// 创建方法
const showNotification = () => {
isOpen.value = true;
clearTimeout(hideNotification);
hideNotification = setTimeout(() => {
isOpen.value = false;
}, 2000);
};

return {
isOpen,
showNotification
};
}
};
</script>

<style scoped>
.box {
width: 500px;
height: 400px;
margin: 0 auto;
background-color: #e1e97bc7;
position: relative;
}
.boxBtn {
padding: 10px;
background-color: azure;
cursor: pointer;
position: absolute;
top: 10%;
left: 10%;
}
.showMessage {
width: 240px;
height: 40px;
background-color: aquamarine;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>

现象:

在此代码段中,当按下按钮时,将渲染2秒钟的通知。 但是,我们的主要目标是使用Teleport获取通知以在我们的Vue应用程序外部渲染。

由于我们在#myTelePort中传递了代码,因此 Vue会找到包含在index.html中的#myTelePort div,它会把 Teleport 内的所有代码渲染到该div中。

效果图:

Teleport

全局组件注册方式

  • vue2注册全局组件
1
2
3
Vue.component("组件名称",{
/*组件对象*/
})
  • vue3注册全局组件
1
2
3
4
5
6
7
import {createApp} from "vue";
//创建实例对象
const app = createApp();
//注册全局组件
app.component("组件名称",{
/*组件对象*/
})

具体代码如下:

入口文件 main.js中

1
2
3
4
5
6
7
8
9
10
11
12
import { createApp} from 'vue'
import App from './App.vue'

// createApp(App).mount('#app'); //下面分两步创建和挂载了

import Home from "./components/home.vue"; //引入需要全局注册的组件
//创建vue实例对象
const app = createApp(App);
//挂载前注册全局组件
app.component("myHome",Home);
// 挂载实例
app.mount("#app");

根组件 App.vue中

1
2
3
4
5
6
7
8
9
10
11
<script setup>
</script>

<template>
<!-- 不需要引用,直接调用全局注册的 myHome -->
<myHome/>
</template>

<style>

</style>

现象:不需要在App.vue引入注册调用三部曲了;直接调用在入口文件中全局注册的组件的名字即可;

函数式组件

  • 通过函数方式定义的组件
  • 特点:函数组件默认没有状态数据和生命周期
  • 语法:
1
2
3
4
5
6
import {h} from "vue";

const funComponent = function(){
//返回组件的视图
return h("h1",{},'函数组件')
}

创建一个js文件,这里为 functionComponent.js

1
2
3
4
5
import {h} from "vue";
export default function FunctionCompon(props){ //props是形参,可以接受调用时候传的参数
console.log(props)
return h("h1",{style:{color:"red"}},props.msg)
}

App.vue 根组件里

1
2
3
4
5
6
7
8
9
10
11
12
13
<script setup>
// 引入函数式组件 setup自动注册
import funCompon from "./components/functionComponent.js";
</script>

<template>
<!-- 调用函数式组件 , 可以传参 -->
<funCompon msg="函数式组件传递数据"/>
<Home/>
</template>

<style>
</style>

现象:页面展示经h1渲染的红色函数式组件传递数据字样;

异步组件

  • vue2中

普通写法

1
const Home =()=>import("./components/home.vue");

高阶写法

1
2
3
4
5
6
7
const asyncModal = {
component: () => import('./Modal.vue'), //vue2中通过component属性节点指定目标组件
delay: 200, //延时时常
timeout: 3000, //超时时间
error: ErrorComponent, //错误处理组件
loading: LoadingComponent //加载中的组件(可以加载个动画啥的)
}
  • vue3中

普通写法

1
2
import {defineAsyncComponent} from "vue";
const Home = defineSyncComponent(()=>import("./components/home.vue"));

高阶写法

1
2
3
4
5
6
7
8
9
10
11
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

const asyncModalWithOptions = defineAsyncComponent({
loader: () => import('./Modal.vue'), //vue2中通过loader属性节点指定目标组件
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})

在App.vue 根组件里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
// 异步导入 setup自动注册
import {defineAsyncComponent} from "vue";

// 简单写法
// const Home = defineAsyncComponent(()=>import("./components/home.vue"));

// 高阶写法
const Home = defineAsyncComponent({
loader:()=>import("./components/home.vue")
})

</script>

<template>
<!-- 调用 -->
<Home/>
</template>

<style>

</style>

v-for中使用ref属性

通过ref来获取dom元素

当ref和v-for一起使用的时候,得到的ref为一个数组

  • vue2中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div id='app'>
<div>
<p v-for="item in 5" :key="item.name" ref="nodes"></p>
</div>
</div>
</template>

<script>
export default {
mounted() {
console.log(this.$refs.nodes); //[p, p, p, p, p]
}
}
</script>
<style>
</style>

缺点: Vue 2 中,在 v-for 语句中使用ref属性时,会生成refs数组插入$refs属性中。当存在嵌套的 v-for 时,这种行为会变得不明确且效率低下。

  • vue3

在 v-for 语句中使用ref属性 将不再会自动在$refs中创建数组。而是,将 ref 绑定到一个 function 中,在 function 中可以灵活处理ref。

1
2
3
<ul>
<li :ref="list" v-for="(item,index) in arr" :key="index"></li>
</ul>

选择式API

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
<template>
<!-- vue3 ref前面有一个 : -->
<p v-for="(item,index) in 5" :key="index" :ref="getRefList"></p>
</template>

<script>
export default {
data() {
return {
refList: []
}
},
methods: {
// :ref 所以后面的是变量,变量名和方法名保持一致 ;
// 参数el:系统自动注入的参数,子元素的元素对象;(当然取啥名字都可以)
getRefList(el) {
this.refList.push(el);
console.log(this.refList);
}
},
//在组件更新之前,清空数组中宏的元素,否则会越来越多
beforeUpdate() {
this.refList = [];
}
};
</script>

<style>
</style>

组合式API

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
<template>
<!-- 这里的ref前面有一个 : -->
<p v-for="(item,index) in 5" :key="index" :ref="getRefList"></p>
</template>

<script>
import {ref,onBeforeUpdate} from "vue";
export default {
setup(){
const refList = ref([]);
// console.log(refList); //RefImpl对象,有value属性;

const getRefList = (el)=>{
refList.value.push(el);
console.log(refList)
};

onBeforeUpdate(()=>{
refList.value=[];
})

return{
refList,getRefList,
}
}
};
</script>

<style>
</style>

v-for与v-if优先级

  • vue2

v-for与v-if指令同时应用于同一个元素之上,v-for指令优先级比v-if高;即 v-for指令先执行,v-if指令后执行;

vue2 简单小案例:99乘法表

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
<template>
<div>
<div v-for="(rowItem,rowIndex) in row" :key="rowIndex">
<!-- v-for渲染了之后,v-if才开始,所以会形成正三角的效果 -->
<span
v-for="(colItem,colIndex) in col"
:key="colIndex"
v-if="colItem<=rowItem"
>{{`${colItem}*${rowItem}=${colItem*rowItem}&nbsp;&nbsp;`}}</span>
</div>
</div>
</template>

<script>
export default {
data() {
return {
row: 9,
col: 9
};
},
methods: {}
};
</script>

<style>
</style>
  • vue3

vue3中的优先级与vue2相反,即v-if的优先级比v-for高;

自定义指令

  • el:指令所绑定的元素,可以直接操作DOM。
  • binding:是一个对象,包含该指令的所有信息。
  • 有自定义指令中的生命周期钩子函数

什么时候需要使用?(用的其实比较少)(有局部注册和全局注册,因为用的少,所以只说全局注册(main.js)的情况)

需要对少量的普通 DOM 元素进行底层操作,这时候就会用到自定义指令

但对于需要操作大量DOM元素或者大变动时候,推荐使用组件,而不是指令

  • vue2
1
2
Vue.directive("指令名称",function(el,binding,vnode){
})

实现高亮效果

1
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
1
2
3
4
5
Vue.directive('highlight', {
bind(el, binding, vnode) {
el.style.background = binding.value
}
})
  • vue3
1
2
3
4
import {createApp}  from "vue";
const app =createApp();
app.directive("指令名称",function(el,binding,vnode){
})

实现高亮效果

1
2
<!-- 自定义指令 -->
<p v-highLight="'yellow'">自定义指令 高亮效果</p>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 全局注册  自定义指令(入门文件 main.js)
import { createApp} from 'vue'
import App from './App.vue'

// createApp(App).mount('#app');

const app = createApp(App);

app.directive("highLight",{
beforeMount(el,binding,vnode){
el.style.background= binding.value;
}
})
app.mount("#app");

app.config.globalProperties全局属性

  • vue2

vue2中给vue注册全局功能使用的是Vue.prototype,这样每一个vue组件都可以访问,因为每一个组件都是Vue的实例对象;

1
2
3
4
//main.js 入口文件
Vue.prototype.$version = '1.0.0'
//任意vue组件 使用全局属性
console.log(this.$version)
  • vue3

Vue3.x已经不支持直接Vue.prototype.$xxx =xxx这种方式来挂载全局对象,这是由于globalVue不再是构造函数,因此不再支持该构造函数

1
2
3
4
5
6
7
//入口文件  声明
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App);
// Vue3全局挂载名称
app.config.globalProperties.$vueName = 'Vue3全局挂载名称'
app.mount('#app')

组合式API获取方法:通过 getCurrentInstance 获取proxy,再获取全局挂载的实例;

因为setup没有this,若按照选择式API,可直接this.$globalNames,这里的this是一个Proxy对象

1
2
3
4
5
6
7
8
9
10
<script>
import { defineComponent, getCurrentInstance } from 'vue'
export default defineComponent({
setup(){
const { proxy } = getCurrentInstance()
console.log(proxy.$vueName)
return {}
}
})
</script>

移除属性

$children

  • vue2

获取子组件的实例,在vue2中,除了$refs方法,还有$children

children.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<h2>{{title}}</h2>
</template>

<script>
export default {
data(){
return{
title:"this is children"
}
},
methods:{
changeTitle(){
this.title="vue2中的$children获取子组件的实例"
}
}

}
</script>
<style>
</style>

parent.vue

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
<template>
<div>
<h2>this is parent</h2>
<button @click="getChild">按钮</button>
<vChild/>
</div>
</template>

<script>
import vChild from "./child.vue";

export default {
components: {
vChild
},

methods:{
getChild(){
this.$children[0].changeTitle(); //直接通过 this.$children[x]来获取子组件的实例
}
},
created(){
console.log(this.$children); //得到的是一个数组;
}
};
</script>

<style>
</style>
  • vue3

在 3.x 中,$children property 已被移除,且不再支持。如果你需要访问子组件实例,我们建议使用 $refs

filter过滤器

  • vue2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
<h2>{{context|doubleFilter}}</h2>
</template>

<script>
export default {
data(){
return{
context:3.1415926
}
},
//局部注册
filters:{
doubleFilter(val){
return val.toFixed(2);
}
}
}
</script>

<style>
</style>
  • vue3

虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本。

我们建议用计算属性或方法代替过滤器,而不是使用过滤器。

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
<template>
<h2>{{doubleContext()}}</h2>
<h2>{{computedContext}}</h2>
</template>

<script>
import { ref,computed } from "vue";
export default {
setup() {
const context = ref(3.1415926);

// method1:方法
const doubleContext =()=>{
return context.value.toFixed(2);
}

// method2:计算属性
const computedContext= computed(()=>{
return context.value.toFixed(2);
})
return {
context,doubleContext,computedContext
};
}
};
</script>

<style>
</style>

以上两种方法都可以实现过滤器效果,但是批量使用的话,好像得批量注册(个人观点)

vm.$set(Vue.set)

vm.$on

vue2中,通过Eventbus$emit$on来实现兄弟组件间传值,在vue3中,$on被移除,如果需要继续使用此功能改为使用第三方mitt库(见下方参考文献)

参考文献

Vue3 - 环境安装和启动配置

Vue3-使用多个根标签报错

Vue3中的watch

Vue3的ref和reactive对比(总结)

ref、reactive、toRef、toRefs的区别

ES6拓展运算符

Vue中的h函数

render: h => h(App)解析

Vue的渲染函数render&h

Vue3 Teleport 简介,请过目,这个是真的好用!

Vue3 Teleport的应用示例

Vue3新特性之Teleport介绍

Vue3 学习笔记 (三)——Vue3 自定义指令

Vue2$attrs组件传值

Vue3 全局挂载对象和方法

Vue3兄弟组件传值方式mitt.js