0%

vue3系列一

项目搭建

  • node.js 安装16.0以上的版本
1
2
3
4
5
C:\Users\Administrator>node -v
v18.17.0

D:\project>npm -v
9.6.7
  • vite初始化Vue3项目
1
2
3
4
5
6
7
8
9
10
11
12
D:\project>npm init vite-app vue3Demo
Need to install the following packages:
create-vite-app@1.21.0
Ok to proceed? (y) y

cd vue3Demo
npm install
D:\project\vue3Demo>npm run dev
Dev server running at:
> Network: http://192.168.57.198:3000/
> Local: http://localhost:3000/

项目结构分析

  • 这里的项目目录结构分析主要是main.js文件
  • Vue2里面的main.js
1
2
3
4
5
6
new Vue({
el: '#app',
components: {},
template: ''
});

  • Vue3里面的main.js
1
2
3
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
  • 在Vue2里面,通过new Vue({})构造函数创建应用实例对象,而Vue3引入的不再是Vue的构造函数,引入的是一个名为createApp的工厂函数创建应用实例对象。

  • Vue3-devtool获取

Composition API

setp

  • setup是所有Composition API(组合式API)的入口,组件中所用到的数据、方法等等,均要配置在setup里面

  • 组件中所用到的数据、方法等等,均要配置在setup里面

  • setup的执行时机

    • 在beforeCreate之前执行一次,此时this为undefined
  • setup函数的两种返回值

    • 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用
    • 若返回一个渲染函数,则可以自定义渲染内容
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
    • context:上下文对象,接收参数 context 内部函数props接受的自定义属性
      • attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
      • slots:收到的插槽内容,相当于this.$slots
      • emit:分发自定义事件的函数,相当于this.$emit

注意事项:

尽量不要与Vue2x的配置使用

  • Vue2x的配置(data、methods、computed)均可以访问到setup中的属性、方法
  • setup中不能访问Vue2x的配置(data、methods、computed)
  • 如果data里面的属性和setup里面的属性有重名,则setup优先

返回值

示例一:setup函数的两种返回值

  • 安装路由依赖
1
npm install vue-router@4 --save
  • src\router\routers.js 手动新建自定义路由
1
2
3
4
5
6
7
8
const routes = [
{
name: "home",
path: "/home",
component: () => import("../components/views/home.vue")
}
]
export default routes;

src\router\index.js 对外暴露路由

1
2
3
4
5
6
7
8
9
10
import { createRouter, createWebHistory } from "vue-router"

// createRouter方法用于创建路由实例对象
// createWebHashHistory方法用于指定路由的工作模式(hash模式
import routes from "./routers"
var router=createRouter({
history:createWebHistory(),
routes
})
export default router
  • src-main.js内容引用路由
1
2
3
4
5
6
7
8
9
10
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from "./routers/index.js"

const app = createApp(App)

app.use(router)
app.mount("#app")

  • 编写src-components-views-home.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
30
31
32
33
34
35
36
37
38
<template>
<h2>练习setup相关内容</h2>
<!--<h2>setup返回一个对象,并使用对象中的属性和方法</h2>-->
<!--<p>姓名:{{student.name}}</p>-->
<!--<p>年龄:{{student.age}}</p>-->
<!--<button @click="hello">点击查看控制台信息</button>-->
<hr>
<h2>setup返回一个函数</h2>
</template>

<script>
import {h} from 'vue'
export default {
name: "setupComponent",
setup(){
// 属性
let student={
name:'张三',
age:18,
}
// 方法
function hello() {
console.log(`大家好,我叫${student.name},今年${student.age}`)
}
return{ // 返回一个对象
student,
hello,
}
// return()=>h('h1','你好') // 返回一个函数
}
}
</script>

<style scoped>

</style>


这里需要注意的是setup里面定义的属性和方法均要return出去,否则无法使用

  • 启动测试
1
npm run dev

示例二:setup里面的参数和方法和配置项混合使用

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
<template>
<h2>setup和配置项混用</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<button @click="sayHello">sayHello(Vue3里面的方法)</button>
<button @click="sayWelcome">sayWelcome(Vue2里面的方法)</button>
</template>

<script>
export default {
name: "setup01_component",
data(){
return{
sex:'男',
sum:0,
}
},
methods:{
sayWelcome(){
console.log(`sayWelcome`)
},
},
setup(){
let sum=100;
let name='张三';
let age=18;
function sayHello() {
console.log(`我叫${name},今年${age}`)
}
return{
name,
age,
sayHello,
sum
}
}
}
</script>

<style scoped>

</style>


  • 这段代码是先实现了setup里面的属性和方法,以及Vue2中配置项里面的属性和方法。接下来添加对应的混合方法
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
69
70
<template>
<h2>setup和配置项混用</h2>
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
<h2>性别:{{sex}}</h2>
<button @click="sayHello">sayHello(Vue3里面的方法)</button>
<button @click="sayWelcome">sayWelcome(Vue2里面的方法)</button>
<br>
<br>
<button @click="test01">测试Vue2里面调用Vue3里面的属性和方法</button>
<br>
<br>
<button @click="test02">测试Vue3setup里面调用Vue2里面的属性和方法</button>
<br>
<h2>sum的值是:{{sum}}</h2>
</template>

<script>
export default {
name: "setup01_component",
data(){
return{
sex:'男',
sum:0,
}
},
methods:{
sayWelcome(){
console.log(`sayWelcome`)
},
test01(){
console.log(this.sex); // Vue2里面的属性(data里面的属性)
// setup里面的属性
console.log(this.name);
console.log(this.age);
// setup里面的方法
this.sayHello();
}
},
setup(){
let sum=100;
let name='张三';
let age=18;
function sayHello() {
console.log(`我叫${name},今年${age}`)
}
function test02() {
// setup里面的属性
console.log(name);
console.log(age);

// data里面的属性
console.log(this.sex);
console.log(this.sayWelcome);
}
return{
name,
age,
sayHello,
test02,
sum
}
}
}
</script>

<style scoped>

</style>

测试Vue3setup里面调用Vue2里面的属性和方法时,this.sex无数据

Vue2里面props和slot的使用

讲解setup这里面的两个参数之前,先回顾一下Vue2里面的相关知识

  • props和自定义事件的使用
  • attrs
  • slot(插槽)

示例一:Vue2props和自定义事件的使用

准备两个组件,分别为parent.vue组件和child.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
<template>
<div class="parent">
我是父组件
<child msg="传递信息" name="张三" @sendParentMsg="getMsg"/>
</div>
</template>
<script>
import Child from "./child.vue";
export default {
name: "Parent",
components: {Child},
methods:{
getMsg(msg){
console.log(msg)
}
}
}
</script>
<style scoped>
.parent{
padding: 10px;
background-color: red;
}
</style>
  • child
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
<template>
<div class="child">
<h2>我是子组件</h2>
<p>父组件传递过来的消息是:{{msg}}</p>
<p>父组件传递过来的消息是:{{name}}</p>
<button @click="sendMsg">向父组件的传递信息</button>
</div>
</template>
<script>
export default {
name: "Child",
props:{
msg:{
type:String,
default:''
},
name:{
type:String,
default:''
}
},
mounted(){
console.log(this);
},
methods:{
sendMsg(){
this.$emit("sendParentMsg",'通知父组件更新123')
}
}
}
</script>
<style scoped>
.child{
padding: 10px;
background-color: orange;
}
</style>

vue2_props.gif

总结
  • 子组件通过props接收父组件传递的信息,通过this.$emit()自定义事件向父组件传递信息。当使用props接收数据的时候,attrs里面的数据为空,如果没有使用props接收数据的话,那么props里面就有值。

Vue2里面slot的使用

  • 这两个分类栏里的数据都不一样,但是整体结构是一样的,这就要求组件的结构一样,但是内部 DOM 结构是由使用组件的时候决定的,这就需要插槽

  • 同理准备两个组件,一个Index.vue组件,另一个为MySlot.vue组件

  • Index.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
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <template>
    <div class="index">
    <h2>我是Index组件</h2>
    <!--写法一-->
    <my-slot>
    <!--插槽里面的内容-->
    <h2>传入的slot参数</h2>
    <h2>传入的slot参数</h2>
    <h2>传入的slot参数</h2>
    <h2>传入的slot参数</h2>
    </my-slot>
    <!--写法二-->
    <my-slot>
    <div slot="header">
    <span>我在header插槽中</span>
    </div>

    <div slot="header">
    <span>我是footer附件</span>
    </div>
    </my-slot>
    </div>
    </template>

    <script>
    import MySlot from "./MySlot.vue";
    export default {
    name: "Index",
    components: {MySlot}
    }
    </script>

    <style scoped>
    .index{
    padding: 10px;
    background: red;
    }
    </style>

  • MySlot

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
<template>
<div class="slot">
<h2>我是MySlot组件1</h2>
<slot></slot>
<br>
<slot name="header">header</slot>
<br>
<slot name="footer">footer</slot>
</div>
</template>

<script>
export default {
name: "MySlot",
mounted(){
console.log(this);
}
}
</script>

<style scoped>
.slot{
padding: 10px;
background: orange;
}
</style>

image-20240229145232356

ref

  • 作用:ref( ) 接受一个内部值,返回一个ref 对象,这个对象是响应式的、可更改的,且只有一个指向其内部值的属性 .value。

  • 语法:const xxx=ref(initValue)

  • 创建一个包含响应式数据的引用对象(reference对象);

  • JS中操作数据:xxx.value=xxx,模板中读取数据:不需要.value,直接:{{xxx}}

  • 备注:

    • 接收的数据可以是:基本类型,也可以是对象类型

    • 基本类型的数据:响应式依然是靠Object.defineProperty()的get和set完成的

    • 对象类型的数据:内部求助了Vue3.0中的一个新函数-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
47
48
49
50
51
52
53
54
<template>
<h1>ref</h1>
<h2>ref定义基本数据类型</h2>
<p>姓名:{{name}}</p>
<p>年龄:{{age}}</p>
<p>婚否:{{isMarry}}</p>
<h2>ref定义对象类型</h2>
<p>爱好:{{hobby}}</p>
<p>证件类型:{{user.idCard}}</p>
<p>国籍:{{user.nation}}</p>
<button @click="changeName">修改信息</button>
</template>

<script>
import {ref} from 'vue'
export default {
name: "refComponent",
setup(){
// 使用基本数据类型 number,string,boolean,
let name=ref('张三');
let age=ref(18);
let isMarry=ref(false);
// 使用ref定义数组
let hobby=ref(['吃饭','睡觉','打豆豆']);
// 使用ref定义对象
let user=ref({
idCard:'身份证',
nation:['中国','美国','英国','俄罗斯']
})
function changeName() {
// 修改基本数据数据类型
name.value='李四'; // ref定义的响应式数据修改数据时必需要.value
age.value=20;
isMarry.value=true;
// 修改对象数据类型
hobby.value[0]='玩游戏';
user.value.idCard='港澳台居民身份证';
user.value.nation[0]='挪威';
}
return{
name,
age,
isMarry,
changeName,
user,
hobby
}
}
}
</script>

<style scoped>

</style>

image-20240229150642949

点击修改信息数据变化

image-20240229150702474

  • ref定义的响应式数据修改数据时必需要.value
  • ref定义的对象数据类型,内部求助了Vue3.0中的一个新函数-reactive函数(看下面的介绍)
  • 模板中使用数据时不需要.value

reactive

  • 作用:定义一个对象类型的响应式数据(基本类型别用它,用ref函数)

  • const 代理对象=reactive(被代理的对象)接收一个对象(或数组),返回一个代理器对象(Proxy的实例对象,简称Proxy对象)

  • reactive定义的响应式数据是深层次的

  • 内部基于ES6的Proxy实现,通过代理对象的操作源对象的内部数据都是响应式的

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>
<h2>reactive响应式数据</h2>
<p>姓名:{{student.name}}</p>
<p>年龄:{{student.age}}</p>
<p>爱好:{{student.hobbies}}</p>
<button @click="changeStuInfo">改变学生信息</button>
</template>

<script>
import {reactive} from 'vue'
export default {
name: "reactiveComponent",
setup(){
// 数据
let student=reactive({
name:'张三',
age:19,
hobbies:['吃饭','睡觉','打豆豆']
});
console.log(student)
// 方法
function changeStuInfo() {
student.name='李四';
student.age=20;
student.hobbies[0]='做家务'
}
return{
student,
changeStuInfo,
}
}
}
</script>

<style scoped>

</style>

image-20240229153509613

  • 点击改变学生信息后

image-20240229153542699

reactive对比ref

  • 从定义数据的角度对比

    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型的数据,它内部会自动通过reactive转为代理对象
  • 从原理角度对比

    • ref通过Object.defineProperty()的get和set实现(响应式)数据劫持
    • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
  • 从使用角度

    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据均不需要.value

watch和watchEffect

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
   //	attr表示需要监视的属性
// 情况一:监视单个ref定义的响应式数据
watch(attr,(newValue,oldValue)=>{
console.log('attr变化了',newValue,oldValue);
})

// 情况二; 监视多个ref定义的响应式数据
watch([attr1,attr2,....,attrn],(newValue,oldValue)=>{
console.log('attr1或attrn变化了',newValue,oldValue);
})

// obj表示需要监听的对象
// 情况三:监视reactive定义的响应式数据
// 若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue
// 若watch监视的是reactive定义的响应式数据,则强制打开开启了深度监视
watch(obj,(newValue,oldValue)=>{
console.log('obj变化了',newValue,oldValue)

},{immediate:true,deep:false}); // 此处deep配置不在奏效

// 情况四,监视reactive定义的响应式数据中的某个属性
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})

// 情况五:监视reactive定义的响应式数据中的某一些属性
watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
})
// 特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:false});// 此处由于是监视reactive所定义的对象中的某个属性,所以deep配置有效

  • watch

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)

    • 监视reactive定义的响应式数据中某个属性时deep配置有效

示例一:wath监听ref定义的响应式数据

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
<template>
<h2>watch监听ref定义的响应式数据</h2>
<h2>姓名:{{userName}}</h2>
<h2>年龄:{{age}}</h2>
<button @click="userName+='!'">修改姓名</button>
<button @click="age++">修改年龄</button>
<hr>
<h2>姓名:{{user.name}}</h2>
<h2>年龄:{{user.age}}</h2>
<button @click="user.name+='!'">修改姓名</button>
<button @click="user.age++">修改年龄</button>
</template>

<script>
import {ref,watch} from 'vue';
export default {
name: "watch_component01",
setup(){
let userName=ref('张三');
let age=ref(18);
let user=ref({
name:'张三',
age:21,
})
// watch监听ref定义的单个响应式数据
watch(userName,(newValue,oldValue)=>{
console.log(`userName发生了变化,新值是:${newValue},旧值是:${oldValue}`)
});
watch(age,(newValue,oldValue)=>{
console.log(`age发生了变化,新值是:${newValue},旧值是:${oldValue}`);
});

// 如果需要监听多个ref定义的响应式数据的话,代码如下
/**
* newValue和oldValue都是数组的形式,其中数组的第n位表示监听位置的第n位
* 例如:此例子中,监听属性的第一位是userName,那位newValue和oldValue对应的第一位也是
* userName。
* 如果有立即执行,那么最开始的值为[],而不是[undefined,undefined]
*/
watch([userName,age],(newValue,oldValue)=>{
console.log('userName或age中的其中一个发生了变化,',newValue,oldValue)
})

// watch监视ref定义的响应式对象数据
watch(user.value,(newValue,oldValue)=>{
console.log('person发生了变化',newValue,oldValue)
})
watch(user,(newValue,oldValue)=>{
console.log('person发生了变化',newValue,oldValue);
},{deep:false})

return{
userName,
age,
user
}
}
}
</script>

<style scoped>

</style>

image-20240229162030615

  • 点击修改按钮

image-20240229164328671

示例二:watch监听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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<template>
<h1>watch监听reactive定义的响应式数据</h1>
<p>姓名:{{user.name}}</p>
<p>年龄:{{user.age}}</p>
<p>薪水:{{user.job.salary}}K</p>
<button @click="user.name+='!'">改变姓名</button>
<button @click="user.age++">改变年龄</button>
<button @click="user.job.salary++">改变薪水</button>
</template>

<script>
import {watch,reactive} from 'vue'
export default {
name: "watch_component02",
setup(){
let user=reactive({
name:'张三',
age:18,
job:{
salary:20
}
});

// 情况一:监听reactive定义的响应式数据,无法正确获取oldValue
/**
* 此时的newValue和oldValue都是最新的数据
* 默认强制开启深度监视,此时深度监视失效
*/
watch(user,(newValue,oldValue)=>{
console.log(newValue,oldValue);
},{deep:false});


// 情况二,监视reactive定义的响应式数据的单个属性
// watch(()=>user.name,(newValue,oldValue)=>{
// console.log('name发生了变化',newValue,oldValue);
// });
// watch(()=>user.age,(newValue,oldValue)=>{
// console.log('age发生了变化',newValue,oldValue);
// })


// 情况三:监视reactive定义的响应式数据的多个属性
/**
* newValue和oldValue都是数组的形式,其中数组的第n位表示监听位置的第n位
* 例如:此例子中,监听属性的第一位是userName,那位newValue和oldValue对应的第一位也是
* userName,
*/
// watch([()=>user.name,()=>user.age],(newValue,oldValue)=>{ // 写法一
// console.log('name或age中的某个属性发生了变化',newValue,oldValue);
// })
// watch(()=>[user.name,user.age],(newValue,oldValue)=>{ // 写法二
// console.log('name或者age中的某个属性发生了变化',newValue,oldValue)
// })

// 情况四:监视reactive定义的响应式数据的对象的某个属性,此时deep有效
/**
* 注意:此时需要区别是reactive定义的对象还是reactive定义的对象里面的某个属性
* 此时deep有效,关闭了监视
*/
// watch(()=>user.job,(newValue,oldValue)=>{
// console.log(newValue,oldValue);
// },{deep:false});
return{
user
}
}
}
</script>

<style scoped>

</style>

  • watchEffect

    • watch的套路是:既要指明监视的属性,也要指明监视的回调

    • watchEffect的套路是:不用指明监视那个属性,监视的回调中用到那个属性,那就监视那个属性

    • watchEffect有点像computed

      • 但computed注重的是计算出来的值(回调函数的返回值),所以必需要写返回值

      • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值

  • 代码

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
<template>
<h1>watchEffect监视ref和reactive定义的响应式数据</h1>
<h2>当前求和:{{sum}}</h2>
<button @click="sum++">点我加1</button>
<hr>
<h2>当前的信息:{{msg}}</h2>
<button @click="msg+='!'">修改信息</button>
<hr>
<h2>姓名:{{person.name}}</h2>
<h2>年龄:{{person.age}}</h2>
<h2>薪资:{{person.job.j1.salary}}</h2>
<button @click="person.name+='!'">修改姓名</button>
<button @click="person.age++">修改年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
</template>

<script>
import {ref,reactive,watchEffect} from 'vue';
export default {
name: "watch_effect_component01",
setup(){
let sum=ref(0);
let msg=ref('你好');
let person=reactive({
name:'张三',
age:18,
job:{
j1:{
salary:100,
}
}
});
/**
* 在watchEffect里面写需要监视的属性,默认会执行一次
* 如果是监视ref定义的响应式书则需要.value
* 如果是监视reactive定义的响应式数据则直接监视
*/
watchEffect(()=>{
let x1=sum.value;
let x2=person.job.j1.salary;
console.log('watchEffect所指定的回调函数执行了');
})

return{
sum,
msg,
person
}
}
}
</script>

<style scoped>

</style>

  • 点我加1和涨薪,就会触发watchEffect监控中的内容

image-20240229175711760

说明