吴志勇的博客 吴志勇的博客
  • h5

    • HTML5&CSS3
  • scss

    • css预处理语言
  • JavaScript

    • JavaScript教程
    • Ajax
    • ES6教程
    • NodeJS
    • Typescript
  • 框架

    • Jquery
    • VUE
    • React
  • Swing专题
  • java基础
  • javaweb
  • 框架
  • 数据库
  • netty
  • 设计模式
  • 微服务及架构
  • 云原生
  • maven
  • 单元测试
工具
我的
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

吴志勇

......
  • h5

    • HTML5&CSS3
  • scss

    • css预处理语言
  • JavaScript

    • JavaScript教程
    • Ajax
    • ES6教程
    • NodeJS
    • Typescript
  • 框架

    • Jquery
    • VUE
    • React
  • Swing专题
  • java基础
  • javaweb
  • 框架
  • 数据库
  • netty
  • 设计模式
  • 微服务及架构
  • 云原生
  • maven
  • 单元测试
工具
我的
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • HTML5&CSS3

  • CSS预处理语言

  • JavaScript

  • nodejs

  • webpack

  • VUE

    • vue2

    • vue3

      • 01 【创建vue3项目】
      • 02 【setup reactive ref】
      • 03 【响应式原理 ref和reactive总结 setup注意点】
        • 1.Vue3.0中的响应式原理
          • 1.1 vue2.x的响应式
          • 1.2 Vue3.0的响应式
        • 2.ref和reactive总结
          • 2.1 reactive对比ref
          • 2.2 ref
          • 2.3 reactive
          • 2.4 注意事项
        • 3.setup的两个注意点
      • 04 【计算属性 侦听器】
      • 06 【生命周期 模板引用】
      • 07 【动态组件 组件注册】
      • 08 【Props 组件事件】
      • 09 【Attributes继承 provide与inject】
      • 10 【异步组件 组合式函数(hooks)】
      • 11 【Teleport CSS功能】
      • 12 【其它组合式API】
      • 13 【script setup 总结】
      • 14 【TS类型声明 keepAlive】
      • 15 【Pinia】
      • 16 【Router 4】
      • 17 【vue3自动导入配置】
      • 18 【Vue3组件通信方总结式】
  • react

  • Typescript
  • 前端
  • VUE
  • vue3
wuzhiyong
2024-09-22

03 【响应式原理 ref和reactive总结 setup注意点】

# 03 【响应式原理 ref和reactive总结 setup注意点】

# 1.Vue3.0中的响应式原理

# 1.1 vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
      1
      2
      3
      4
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新。
    • 直接通过下标修改数组, 界面不会自动更新。
      const person = {
        name: 'ds',
        age: 18,
      };

      // vue2
       let p = {};
      Object.defineProperty(p, 'name', {
        get() {
          console.log('有人读取了name');
          return person.name;
        },
        set(value) {
          console.log('有人修改了name');
          person.name = value;
        },
      }); 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 1.2 Vue3.0的响应式

  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。

    • 通过Reflect(反射): 对源对象的属性进行操作。

    • MDN文档中描述的Proxy与Reflect:

      • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

              //源数据
        	const person = {
                name: 'ds',
                age: 18,
              };
        
              //代理数据
        	  //target就是目标对象person,propName就是操作的属性
              const p = new Proxy(person, {
                  //有人读取p的某个属性时调用
                get(target, propName) {
                  console.log(`有人读取了p的${propName}`);
                  console.log(target, propName);
                  return target[propName];
                },
                  //有人修改p的某个属性、或给p追加某个属性时调用
                set(target, propName, value) {
                  console.log(`有人修改了p身上的${propName}`);
                  target[propName] = value;
                },
                  //有人删除p的某个属性时调用
                deleteProperty(target, propName) {
                  console.log(`有人删除了p身上的${propName}`);
                   //这个函数需要一个结果来判断成功与否所以return
                  return delete target[propName];
                },
              });
        
        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

        image-20220705120849166

      • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

        			//源数据
        			let person = {
        				name:'张三',
        				age:18
        			}
        			//模拟Vue3中实现响应式
        			const p = new Proxy(person,{
        				//有人读取p的某个属性时调用
        				get(target,propName){
        					console.log(`有人读取了p身上的${propName}属性`)
        					return Reflect.get(target,propName)
        				},
        				//有人修改p的某个属性、或给p追加某个属性时调用
        				set(target,propName,value){
        					console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
        					Reflect.set(target,propName,value)
        				},
        				//有人删除p的某个属性时调用
        				deleteProperty(target,propName){
        					console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
        					return Reflect.deleteProperty(target,propName)
        				}
        			})
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23

通过Reflect操作的属性,报错时会返回false,这样就不要try-catch捕获异常了。

# 2.ref和reactive总结

# 2.1 reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据。
    • reactive用来定义:对象(或数组)类型数据。
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象。
  • 从原理角度对比:
    • ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value。
    • reactive定义的数据:操作数据与读取数据:均不需要.value。

# 2.2 ref

1.ref通常用于声明基础类型响应式数据。

import {ref} from 'vue'

const age =ref(10) //声明响应式数据
1
2
3

2.ref返回的是被包装过的响应式对象,在setup中访问和修改ref需要使用.value属性

age.value=21
1

3.在模板中使用时无需使用.value,直接使用即可

<div>{{age}}</div>
1

4.当ref数据作为props传递给子组件的时候,在子组件里需要使用toRef或者toRefs建立引用,否则数据不是响应式的。且需要注意,如果在子组件中直接操作了这个引用之后,则和父组件不在具有联系。

# 2.3 reactive

1.reactive用于声明复杂类型响应式数据。

import {reactive} from 'vue'

const man=ref({name:'jolin',age:21}) //声明响应式数据

1
2
3
4

2.reactive返回的是被包装过的响应式对象,在setup中访问和修改直接使用属性即可

man.age=20
1

3.声明时未定义,动态添加的属性也会是响应式的

man.weight = '50kg' //weight具有响应性
1

4.在模板中使用属性的形式

<div>{{man.name}}</div>
1

5.将reactive声明的响应式数据传递给子组件时,在子组件可以直接使用。

1.当ref的值是数组时,我们可以通过索引来修改数组值是响应式的。

# 2.4 注意事项

1.注意当ref用于在模板中作为真值判断时,直接使用ref恒为true, 需要使用.value才能正确显示

<div v-if="age"></div> //恒为true
<div v-if="age.value"></div> //正确
1
2

2.注意不能解构reactive数据,解构出的数据会失去响应式。

3.在任何地方访问响应式数据都能拿到最新的。

4.同vue2的data,只有数据被应用到模板中时,数据的改变才会触发updated生命周期,否则即使数据被修改了,也不会触发updated生命周期,导致视图不更新。

5.同vue2,当将ref和reactive作为props传递给组件时,原则上不应该在子组件上修改props的值。

# 3.setup的两个注意点

  • setup执行的时机
    • 在beforeCreate之前执行一次,this是undefined。
  • setup的参数
    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs。 如果子组件没有使用props:[‘xxx’]接收,attr这个对象就能看到父组件传来的数据
      • slots: 收到的插槽内容, 相当于 this.$slots。
      • emit: 分发自定义事件的函数, 相当于 this.$emit。 一定要在配置里写emits:['hello'], 这里要写父组件的自定义函数名称,不然会有警告

App.vue

<template>
	<Demo @hello="showHelloMsg" msg="你好啊" school="尚硅谷">
		<template v-slot:qwe>
			<span>尚硅谷</span>
		</template>
		<template v-slot:asd>
			<span>尚硅谷</span>
		</template>
	</Demo>
</template>

<script>
	import Demo from './components/Demo'
	export default {
		name: 'App',
		components:{Demo},
		setup(){
			function showHelloMsg(value){
				alert(`你好啊,你触发了hello事件,我收到的参数是:${value}!`)
			}
			return {
				showHelloMsg
			}
		}
	}
</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

Demo.vue

<template>
	<h1>一个人的信息</h1>
	<h2>姓名:{{person.name}}</h2>
	<h2>年龄:{{person.age}}</h2>
	<button @click="test">测试触发一下Demo组件的Hello事件</button>
</template>

<script>
	import {reactive} from 'vue'
	export default {
		name: 'Demo',
		props:['msg','school'],
		emits:['hello'], //这里要写父组件的自定义函数名称,不然会有警告
		setup(props,context){
			// console.log('---setup---',props)传过来的属性存在这个对象里面
			// console.log('---setup---',context)
			// console.log('---setup---',context.attrs) //相当与Vue2中的$attrs
			// console.log('---setup---',context.emit) //触发自定义事件的。
			console.log('---setup---',context.slots) //插槽
			//数据
			let person = reactive({
				name:'张三',
				age:18
			})

			//方法
			function test(){
				context.emit('hello',666)
			}

			//返回一个对象(常用)
			return {
				person,
				test
			}
		}
	}
</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
上次更新: 2024-09-30 01:09:25

← 02 【setup reactive ref】 04 【计算属性 侦听器】→

Copyright © 2020-2025 wuzhiyong
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式