Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue3学习 #4

Open
zmj0920 opened this issue Dec 26, 2022 · 9 comments
Open

Vue3学习 #4

zmj0920 opened this issue Dec 26, 2022 · 9 comments

Comments

@zmj0920
Copy link
Owner

zmj0920 commented Dec 26, 2022

No description provided.

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 26, 2022

什么时候用setup()函数方式,什么时候不用?

官网推荐,对于结合单文件组件使用的组合式 API,推荐通过 <script setup>

  1. setup直接以属性的方式写在script标签上
  2. 在Vue3中,在xxx.vue单文件组件中的<script setup></script>,会有一个setup标记,
  3. 只要标记了它,那就说明,在script标签代码块内就具备了vue3的语法环境,
  4. 可以使用Vue3中的一些特性,比如组合式API函数,ref,reactive等
  5. 可以在script中声明响应式数据状态,定义声明完后,可以直接在模板中使用
<template>
     <!--在模板中不用.value,它会被自动解析,直接可以使用变量名--->
     {{name}}
</template>
<script setup>
  import {ref} from "vue";
  let name = ref("itclanCoder");
  //在逻辑中,想要读取name的值,需要.value方式
  console.log(name.value);
</script>

setup()以函数配置选项方式

在vue3当中,它是向下兼容的,如果想要在vue2中体验vue3当中的一些新特性,
在vue3中新增了一setup的配置选项 其他情况下,都应该优先使用<script setup></script>语法

<template>
    <div>
        {{name}}
    </div>
</template>
<script>
  import { ref } from "vue";
  export default {
    // 其他配置选项
    data() {
      return {
        num: 1
      }
    },
    setup() {
      const name = ref('itclanCoder');
      // 在逻辑中访问,同样需要使用.value的方式访问
      console.log(name.value);

      //在vue3的配置里去读取vue2的属性
      console.log(this.num);
      // 声明的变量或函数,都需要暴露出去,否则在模板中使用,会不起作用
      return {
        name
      }
    }
  }
</script>

重点注意
[1]. 在setup()函数内定义声明的响应式数据变量或函数,都需要对外暴露出去,否则在模板中直接使用的话,是不起作用的
[2]. 在模板中可以直接使用定义的响应式数据变量,或函数,因为它可以自动解析,而在逻辑中需要使用变量.value读取
[3]. setup()函数自身并不具备对组件实例的访问权限,所以在setup()函数作用域内,访问this会是undefined,但在选项式API中可以访问组合式API对外暴露出来的值,反过来却不行,也就是在setup()中无法通过this访问选项式API配置选项下的data数据或methods方法
[4]. 当选项式API中的data数据,methods方法名与setup()内定义的变量名或函数同名时,后者会覆盖前者
[5]. 凡是组合中用到数据,方法,均要配置在setup中,并且需要通过return对外暴露出去
[6]. setup函数有两种返回值,一个是对象,另一个是函数
[7]. 如果setup()函数返回一个对象,则对象中的属性,方法,在模板中可以直接使用,但在逻辑内,普通变量需要使用.value访问读取,若返回一个渲染函数,则可以自定义渲染内容
[8]. setup不能是一个async函数,因为返回值不在是return的对象,而是propmise,这样会令,模板看不到return对象中属性(其实也可以返回一个Promises实例,但需要结合Suspense和异步组件配合进行使用)

setup()是在什么时候执行的?

setup()是Vue提供的一个钩子,它的执行时机是在beforeCreate()函数之前执行的,在setup()函数里面访问this是undefined

总结

setup是vue3新增的一个特性,有两种使用,一种是直接写在script标签上,此时script标签代码块内可写vue3的新特性
而在选项式API编码风格当中,若想要使用vue3,那么需要借用setup()这配置选项,所有的组合响应式API函数,响应式变量,
都需要放在setup()函数里面同时,需要对外暴露出去
两种方式都是可以使用的,不过在官方推荐的使用当中,应当优先使用setup放在script标签上,
当需要基于选项式API的组合中,基于组合式API的代码时或第三方库整合的项目中,
或非单文件组件中使用组合式API时,可以用setup()函数。

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 27, 2022

ref和reactive异同

  1. ref接收内部值返回响应式Ref对象,reactive返回响应式代理对象
  2. reactive内部采用的proxy,ref内部采用的是defineProperty
  3. 从定义上看ref通常用于处理单值的响应式,reactive用于处理对象类型的数据响应式
  4. 两者均是用于构造响应式数据,但是ref主要解决原始值的响应式问题
  5. ref返回的响应式数据在JS中使用需要加上.value才能访问其值,在视图中使用会自动脱ref,不需要.value;ref可以接收对象或数组等非原始值,但内部依然是reactive实现响应式;reactive内部如果接收Ref对象会自动脱ref;使用展开运算符(...)展开reactive返回的响应式对象会使其失去响应性,可以结合toRefs()将值转换为Ref对象之后再展开。
  6. reactive内部使用Proxy代理传入对象并拦截该对象各种操作(trap),从而实现响应式。ref内部封装一个RefImpl类,并设置get value/set value,拦截用户对值的访问,从而实现响应式。

ref

<template>
    <!--vue3的组件模版结构可以没有根标签-->
    <h1>我叫{{ name }}, {{ age }}岁</h1>
    <h3>职位:{{ job.type }}</h3>
    <h3>薪水:{{ job.salary }}</h3>
    <button @click="changeInfo">修改人的信息</button>
</template>
<script setup lang="ts">
import { ref } from 'vue';
//ref实现响应式(基本类型)也是采用Object.definedProperty()来实现的 getter和setter
let name = ref('小明');
let age = ref(21);
//ref实现响应式(对象类型)也是采用Proxy来实现
let job = ref({
    type: '前端',
    salary: 30000
});

function changeInfo() {
    name.value = '李四';
    age.value = 42;
    job.value.type = 'UI developer';
    console.log(name, age); //不是响应式的
}
// export default {
//     setup() {
//         //ref实现响应式(基本类型)也是采用Object.definedProperty()来实现的 getter和setter
//         let name = ref('小明');
//         let age = ref(21);
//         //ref实现响应式(对象类型)也是采用Proxy来实现
//         let job = ref({
//             type: '前端',
//             salary: 30000
//         });

//         function changeInfo() {
//             name.value = '李四';
//             age.value = 42;
//             job.value.type = 'UI developer';
//             console.log(name, age); //不是响应式的
//         }

//         //返回一个对象
//         return {
//             name,
//             age,
//             job,
//             changeInfo
//         }
//     }
// }
</script>

reactive

<template>
	<!--vue3的组件模版结构可以没有根标签-->
	<h1>我是app组件</h1>
	<h1>我叫{{ person.name }}, {{ person.age }}岁</h1>
	<h3>职位:{{ person.type }}</h3>
	<h3>薪水:{{ person.salary }}</h3>
	<h3>爱好:{{ person.hobbies }}</h3>
	<h4>测试的数据c:{{ person.a.b.c }}</h4>
	{{ a.b.c }}
</template>
  

<!-- <script lang="ts">
import { reactive, toRefs } from 'vue';
export default {
	setup() {
		let person = reactive({
			name: 'py',
			age: 21,
			type: 'frontend developer',
			salary: '30',
			hobbies: ['抽烟', '喝酒', '烫头'],
			a: {
				b: {
					c: 666
				}
			}
		});
		function changeInfo() {
			person.name = '李四';
			person.age = 42;
			// job.value.type = 'xxx'
			person.type = 'UI developer';
			//测试reactive能否监测深层次变化
			person.a.b.c = 100;
			person.hobbies[0] = 'play tennis';
		}

		//返回一个对象
		return {
			person,
			...toRefs(person), //当从组合式函数中返回响应式对象时,toRefs 解构/展开返回的对象而不会失去响应性:
			changeInfo
		}
	}
}
</script> -->

<script setup lang="ts">
import { reactive, toRefs } from 'vue';

let person = reactive({
	name: 'py',
	age: 21,
	type: 'frontend developer',
	salary: '30',
	hobbies: ['抽烟', '喝酒', '烫头'],
	a: {
		b: {
			c: 666
		}
	}
});
function changeInfo() {
	person.name = '李四';
	person.age = 42;
	// job.value.type = 'xxx'
	person.type = 'UI developer';
	//测试reactive能否监测深层次变化
	person.a.b.c = 100;
	person.hobbies[0] = 'play tennis';
	return {
		...toRefs(person), //当从组合式函数中返回响应式对象时,toRefs 解构/展开返回的对象而不会失去响应性:
	}
}
const { a } = changeInfo();
</script>

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 27, 2022

watch和computed的区别以及选择?

  1. 先看两者定义,列举使用上的差异
  2. 列举使用场景上的差异,如何选择
  3. 使用细节、注意事项
  4. vue3变化

computed 表示计算属性, 通常用于处理数据, 方便在模板中的简化书写;
watch 表示监听,

在项目开发中, 当有一些数据需要处理: 随着其它数据变动而变动时,你很容易滥用 watch, 但是通常更好的做法是使用计算属性 computed, 而不是命令式的 watch 回调

使用过程中有一些细节,比如计算属性也是可以传递对象,成为既可读又可写的计算属性。watch可以传递对象,设置deep、immediate等选项。

vue3中watch选项发生了一些变化,例如不再能侦测一个点操作符之外的字符串形式的表达式; reactivity API中新出现了watch、watchEffect可以完全替代目前的watch选项,且功能更加强大。

computed

<template>
	<h1>一个人的信息</h1>
	姓:<input type="text" v-model="person.firstName" />
	<br />
	名:<input type="text" v-model="person.lastName" />
	<p>姓名:{{ fullName }}</p>
	<br />
	修改全名:<input type="text" v-model="fullName1" />
</template>
<script setup lang="ts">
import { reactive, computed } from 'vue';
let person = reactive({
	firstName: 'pan',
	lastName: 'yue',
	age: 21,
});

//计算属性(简写,没有考虑计算属性被修改的情况)
const fullName = computed(() => {
	return person.firstName + '-' + person.lastName;
})

//计算属性(完整写法,既考虑了读也考虑了改)
const fullName1 = computed({
	get() {
		return person.firstName + '-' + person.lastName
	},
	set(name) {
		let [fn, ln] = name.split('-');
		//响应式
		person.firstName = fn;
		person.lastName = ln;
	}
})
</script>

watch

<template>
	<h1>当前求和为:{{ sum }}</h1>
	<button @click="sum++">点我加一</button>
	<hr />
	<h2>当前的信息为:{{ msg }}</h2>
	<button @click="msg += '!'">修改信息</button>
	<hr />
	<h2>姓名:{{ person.name }}</h2>
	<h2>年龄:{{ person.age }}</h2>
	<h2>薪资:{{ person.job.j1.salary }}K</h2>
	<button @click="person.name = person.name + '~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
	<button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, reactive, watch } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let msg = ref('你好');
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		})

		//情况一: 监视ref所定义的响应式数据
		watch(sum, (nv, ov) => {
		  //这里我并不需要this,所以剪头函数,普通函数我可以乱粥
		  console.log('sum的值发生变化了');
		  console.log(`newValue:${nv}, oldValue:${ov}`);
		}, {
		  //监视的配置
		  immediate: true //一上来就更新
		});

		//情况二:监视ref所定义的多个响应式数据
		// watch([sum, msg], (nv, ov) => {
		//   //此时nv和ov都是被监视属性值的数组
		//   // console.log(Array.isArray(ov)); //true
		//   console.log('sum的值或者msg的值发生变化了');
		//   console.log(`newValue:${nv}, oldValue:${ov}`);
		// },{
		//   immediate: true
		// });

		/**
		 * 情况三:监视reactive所定义的一个响应式数据
		 * 坑:1.此处无法获取正确的ov(oldValue)
		 *    2.强制开启了深度监视
		 */
		// watch(person, (nv, ov) => {
		//   console.log('person变化了');
		//   console.log(nv, ov);
		// }, {
		//   deep: false //此处的deep配置是无效的
		// });

		//情况四:监视reactive所定义的响应式中的某一个属性
		// watch(() => person.age, (nv, ov) => {
		//   console.log('person的age变了', nv, ov);
		// })

		//情况五:监视reactive所定义的响应式中的某些属性:并不只是一个
		// watch([() => person.age, () => person.name], (nv, ov) => {
		//   //此时nv和ov都是数组
		//   console.log('person的age或name发生改变了',nv, ov);
		// });

		//特殊情况
		// watch(() => person.job, (nv, ov) => {
		//   //这里依然无法拿到正确的ov,因为依然监视的是对象
		//   console.log('person的job信息发生改变了',nv, ov);
		// }, {
		//   //这里必须要加deep:true注意
		//   deep: true //此处因为监视的是reactive所定义的响应式对象的一个属性(这个属性的值它依然是一个对象),所以deep配置有效
		// })

		//返回一个对象
		return {
			sum,
			msg,
			person
		}
	}
}
</script>

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 27, 2022

watch和watchEffect异同?

  • watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。watchEffect就是一种特殊的watch实现。

  • watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。

  • watchEffect立即运行一个函数,然后被动地追踪它的依赖,当这些依赖改变时重新执行该函数。

  • watch侦测一个或多个响应式数据源并在数据源变化时调用一个回调函数。

  • watchEffect(effect)是一种特殊watch,传入的函数既是依赖收集的数据源,也是回调函数。

  • 如果我们不关心响应式数据变化前后的值,只是想拿这些数据做些事情,那么watchEffect就是我们需要的。

  • watch更底层,可以接收多种数据源,包括用于依赖收集的getter函数,因此它完全可以实现watchEffect的功能,

  • 同时由于可以指定getter函数,依赖可以控制的更精确,还能获取数据变化前后的值,因此如果需要这些时我们会使用watch。

  • watchEffect在使用时,传入的函数会立刻执行一次。

  • watch默认情况下并不会执行回调函数,除非我们手动设置immediate选项。

  • 从实现上来说,watchEffect(fn)相当于watch(fn,fn,{immediate:true})

设置 flush: 'post' =>watchPostEffect 将会使侦听器延迟到组件渲染之后再执行。

在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' =>watchSyncEffect来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

watchEffect

<template>
	<h1>当前求和为:{{ sum }}</h1>
	<button @click="sum++">点我加一</button>
	<hr />
	<h2>当前的信息为:{{ msg }}</h2>
	<button @click="msg += '!'">修改信息</button>
	<hr />
	<h2>姓名:{{ person.name }}</h2>
	<h2>年龄:{{ person.age }}</h2>
	<h2>薪资:{{ person.job.j1.salary }}K</h2>
	<button @click="person.name = person.name + '~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
	<button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { reactive, ref, watchEffect } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let msg = ref('你好');
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});
		//watchEffect
		//不确定监视对象
		//默认开启了immediate:true
		watchEffect(() => {
			console.log(`watch effect指定的回调执行了!!`)
			//依赖收集,你用到了谁它就监视谁!!
			//这里用到sum, person.job.j1.salary了,所以可以被监视到(只要它们发生变化就重新执行watchEffect)
			//与computed有点类似,依赖收集.(侧重点不一致,watchEffect注重过程,而computed注重计算函数的返回值)
			const x1 = sum.value;
			const x2 = person.job.j1.salary;
		})

		//返回一个对象
		return {
			sum,
			msg,
			person
		}
	}
}
</script>

watch

<template>
	<h1>当前求和为:{{ sum }}</h1>
	<button @click="sum++">点我加一</button>
	<hr />
	<h2>当前的信息为:{{ msg }}</h2>
	<button @click="msg += '!'">修改信息</button>
	<hr />
	<h2>姓名:{{ person.name }}</h2>
	<h2>年龄:{{ person.age }}</h2>
	<h2>薪资:{{ person.job.j1.salary }}K</h2>
	<button @click="person.name = person.name + '~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
	<button @click="person.job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, watch } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let msg = ref('你好');
		let person = ref({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});

		//监测的不是一个值,而是一个保存值的结构(不能写成sum.value) 不能监视一个具体的值注意
		watch(sum, (nv, ov) => {
			console.log(nv, ov);
		});

		console.log(person);

		//person是RefImpl
		//开启深度监视不会存在问题
		// watch(person, (nv, ov) => {
		//   console.log(nv, ov);
		// },{
		//   deep: true
		// });

		//这里如果不是person.value则会出现问题 因为person是一个RefImpl(默认没开启深度监视)
		//但是person.value是一个借助了proxy生成的reactive响应式对象 所以这里可以解决问题
		// watch(person.value, (nv, ov) => {
		//   console.log(nv, ov);
		// });

		console.log(sum);
		console.log(msg);
		console.log(person);

		//返回一个对象
		return {
			sum,
			msg,
			person
		}
	}
}
</script>

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 27, 2022

readOnly和shallowReadOnly

readOnly接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。
要避免深层级的转换行为,请使用 [shallowReadonly()]作替代。

<template>
	<h2>当前求和为:{{ sum }}</h2>
	<button @click="sum++">sum+1</button>
	<hr />
	<h2>姓名:{{ name }}</h2>
	<h2>年龄:{{ age }}</h2>
	<h2>薪资:{{ job.j1.salary }}K</h2>
	<button @click="name = name + '~'">修改姓名</button>
	<button @click="age++">增长年龄</button>
	<button @click="job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, reactive, toRefs, readonly, shallowReadonly } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});

		// person = readonly(person); //此时person里面的属性值都不允许修改
		//person = shallowReadonly(person); //第一层不能改(name,age), 但j1和salary仍然可以改动
		// sum = readonly(sum); //同理
		// sum = shallowReadonly(sum)

		return {
			sum,
			...toRefs(person),
		};

	}
}
</script>

1 similar comment
@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 27, 2022

readOnly和shallowReadOnly

readOnly接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。
要避免深层级的转换行为,请使用 [shallowReadonly()]作替代。

<template>
	<h2>当前求和为:{{ sum }}</h2>
	<button @click="sum++">sum+1</button>
	<hr />
	<h2>姓名:{{ name }}</h2>
	<h2>年龄:{{ age }}</h2>
	<h2>薪资:{{ job.j1.salary }}K</h2>
	<button @click="name = name + '~'">修改姓名</button>
	<button @click="age++">增长年龄</button>
	<button @click="job.j1.salary++">增长薪资</button>
</template>

<script lang="ts">
import { ref, reactive, toRefs, readonly, shallowReadonly } from 'vue';
export default {
	setup() {
		let sum = ref(0);
		let person = reactive({
			name: '张三',
			age: 18,
			job: {
				j1: {
					salary: 20
				}
			}
		});

		// person = readonly(person); //此时person里面的属性值都不允许修改
		//person = shallowReadonly(person); //第一层不能改(name,age), 但j1和salary仍然可以改动
		// sum = readonly(sum); //同理
		// sum = shallowReadonly(sum)

		return {
			sum,
			...toRefs(person),
		};

	}
}
</script>

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 27, 2022

响应式 API:工具函数

unref

省略ref调用值时的.value操作,直接进行数据的操作获取;
当使用.value太频繁的时候,不知道后面的值到底有没有.value,此时就可以用该API进行包裹访问

import { unref } from "vue";
let msgref = ref('你好')
console.log(unref(msgref)) // 你好
let  msg = '你好'
console.log(unref(msg)) // 你好

toRef

可以为源响应式对象上的某个属性新创建一个ref,且ref可以被传递,会保持对源属性的响应式连接


import { ref, toRef } from "vue";
// 使用 toRef 后 两个变量数据 会产生链式关系 互相响应 一个数据发送改变 另一个也会跟随 改变
const user = ref({
  name: "Lbxin",
  age: 22,
});

const newAge = toRef(user.value, "age");

newAge.value = 20;
console.log(user.value.age); // 20

user.value.age = 18;
console.log(newAge.value); // 18

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 28, 2022

vue3生命周期

Vue生命周期总共可以分为8个阶段:创建前后, 载入前后, 更新前后, 销毁前后,以及一些特殊场景的生命周期。vue3中新增了三个用于调试和服务端渲染场景。
微信截图_20221228174912

beforeCreate:通常用于插件开发中执行一些初始化任务
created:组件初始化完毕,可以访问各种数据,获取接口数据等
mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。
beforeUpdate:此时view层还未更新,可用于获取更新前各种状态
updated:完成view层的更新,更新后,所有状态已是最新
beforeunmounted:实例被销毁前调用,可用于一些定时器或订阅的取消
unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器

@zmj0920
Copy link
Owner Author

zmj0920 commented Dec 28, 2022

defineComponent

defineComponent 是 Vue 3 推出的一个全新 API ,可用于对 TypeScript 代码的类型推导,帮助开发者简化掉很多编码过程中的类型声明。
defineComponent最重要的是:在TypeScript下,给予了组件 正确的参数类型推断 。

import { defineComponent } from 'vue'

// 使用 `defineComponent` 包裹组件的内部逻辑
export default defineComponent({
  setup(props, context) {
    // ...

    return {
      // ...
    }
  },
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant