欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Vue3.0学习笔记

程序员文章站 2024-01-02 12:50:52
...

2020年9月18日,尤雨溪大神终于推出了Vue3.0的正式版本,一时间众多前端码农顶礼膜拜之。余亦景从,学习之。常言道:好记性不如一个烂笔头。然也。现将笔记记录如下:

新的改变

Vue3.0相对于2.x的版本,底层响应式数据本质发生了变化。在Vue2.x中是通过Object.defineProperty来实现响应式数据的,而在Vue3.0中是通过new Proxy来实现响应式数据的。此外,Vue3.0还新增了Composition API来增加对大型项目的更好的适配。

详情

Vue3.0 响应式数据本质

示例如下

 let obj = {name:"lnj",age:17};
// let arr = [1,4,7];
let state = new Proxy(obj,{
// let state = new Proxy(arr,{
	get(obj,key){
		console.log(obj,key);
		return obj[key];
	},
	set(obj, key, value){
		// [ 1, 4, 7 ] '3' 9
		console.log(obj, key, value);
		// [ 1, 4, 7, 9 ] 'length' 4
		obj[key] = value;
		console.log("更新界面");
		return true;
	}
});
console.log(state.name);
state.name = "zdf";
console.log(state);
// console.log(state[1]);
// state.push(9);

组合API–1

<template>
	<div>		
		<ul>
			<li v-for="(stu, index) in state.stus" :key="stu.id" @click="remStu(index)">{{ stu.name }}---{{ stu.age }}</li>
		</ul>		
	</div>
</template>

<script>
import { reactive } from 'vue';
export default {
	name: 'App',
	// setup函数是Composition API的入口函数
	setup() {
		/*
		// ref函数注意点:
		// ref函数只能监听简单类型的变化,不能监听复杂类型的变化(对象,数组)
		let state = reactive({
			stus: [{ id: 1, name: 'zs', age: 10 }, { id: 2, name: 'ls', age: 20 }, { id: 3, name: 'ww', age: 30 }]
		});
		function remStu(index) {
			state.stus = state.stus.filter((stu, idx) => idx != index);
		}
		*/
		let { state, remStu } = useRemoveStudent();
		return { state, remStu };
	},
	data() {
		return {};
	},
	methods: {}
};
function useRemoveStudent() {
	let state = reactive({
		stus: [{ id: 1, name: 'zs', age: 10 }, { id: 2, name: 'ls', age: 20 }, { id: 3, name: 'ww', age: 30 }]
	});
	function remStu(index) {
		state.stus = state.stus.filter((stu, idx) => idx != index);
	}
	return { state, remStu };
}

</script>

新建add.js和remove.js如下

//add.js
import { reactive } from 'vue';
function useAddStudent(state) {
	let state2 = {
		stu: { id: '', name: '', age: '' }
	};
	function addStu(e) {
		e.preventDefault();
		const stu = Object.assign({}, state2.stu);
		state.stus.push(stu);
		state2.stu.id = '';
		state2.stu.name = '';
		state2.stu.age = '';
	}
	return { state2, addStu };
}
export default useAddStudent;
//remove.js
import { reactive } from 'vue';
function useRemoveStudent() {
	let state = reactive({
		stus: [{ id: 1, name: 'zs', age: 10 }, { id: 2, name: 'ls', age: 20 }, { id: 3, name: 'ww', age: 30 }]
	});
	function remStu(index) {
		state.stus = state.stus.filter((stu, idx) => idx != index);
	}
	return { state, remStu };
}
export default useRemoveStudent;

组合API–2

<template>
	<div>
		<form action="">
			<input type="text" v-model="state2.stu.id" />
			<input type="text" v-model="state2.stu.name" />
			<input type="text" v-model="state2.stu.age" />
			<input type="submit" @click="addStu" />
		</form>
		<ul>
			<li v-for="(stu, index) in state.stus" :key="stu.id" @click="remStu(index)">{{ stu.name }}---{{ stu.age }}</li>
		</ul>
	</div>
</template>

<script>
import useAddStudent from './add';
import useRemoveStudent from './remove';
export default {
	name: 'App',
	// setup函数是组合API的入口函数
	setup() {
		/*
		// ref函数注意点:
		// ref函数只能监听简单类型的变化,不能监听复杂类型的变化(对象,数组)
		let state = reactive({
			stus: [{ id: 1, name: 'zs', age: 10 }, { id: 2, name: 'ls', age: 20 }, { id: 3, name: 'ww', age: 30 }]
		});
		function remStu(index) {
			state.stus = state.stus.filter((stu, idx) => idx != index);
		}
		*/
		let { state, remStu } = useRemoveStudent();
		let { state2, addStu } = useAddStudent(state);
		return { state, remStu, state2, addStu };
	},
	data() {
		return {};
	},
	methods: {}
};
/*
function useRemoveStudent() {
	let state = reactive({
		stus: [{ id: 1, name: 'zs', age: 10 }, { id: 2, name: 'ls', age: 20 }, { id: 3, name: 'ww', age: 30 }]
	});
	function remStu(index) {
		state.stus = state.stus.filter((stu, idx) => idx != index);
	}
	return { state, remStu };
}
function useAddStudent(state) {
	let state2 = {
		stu: { id: '', name: '', age: '' }
	};
	function addStu(e) {
		e.preventDefault();
		const stu = Object.assign({}, state2.stu);
		state.stus.push(stu);
		state2.stu.id = '';
		state2.stu.name = '';
		state2.stu.age = '';
	}
	return { state2, addStu };
}
*/
</script>

setup执行时机和注意点

<template>
	<div>
		<p>{{name}}</p>
		<button @click="myFn1">按钮</button>
		<p>{{age}}</p>
		<button @click="myFn2">按钮</button>
	</div>
</template>

<script>
/*
  1.Composition API和Option API混合使用
  2.Composition API 本质(组合API/注入API)
  3.setup执行时机
    setup: Composition API 的入口函数,在beforeCreate之前执行
    beforeCreate:表示组件刚刚被创建出来 ,组建的data和methods还没有初始化好  
    created:表示组件刚刚被创建出来 ,并且组建的data和methods已经初始化好
  4.setup注意点
  - 由于在执行setup函数的时候,还没有执行created生命周期方法
	所以在setup函数中,是无法使用data和methods 
  - 由于我们不能再setup函数中使用data和methods,
    所以Vue为了避免我们错误的使用,它直接将setup函数中的this修改成了undefined
  - setup函数只能是同步的,不能是异步的		
 */
import {ref} from 'vue';
export default {
	name: 'App',
	data() {
		return {
			name:'sdf'
		};
	},
	methods: {
		myFn1(){
			alert("asdf");
		}
	},
	// setup函数是组合API的入口函数
	setup() {
		let age = ref(18);
		function myFn2(){
			alert("www.ksdf.com");
		}
		// console.log(this);//undefined
		// console.log(this.name);
		// this.myFn1();
		return {age, myFn2};
	},
	
};
</script>

reactive的理解

<template>
	<div>
		<!-- <p>{{state}}</p>
		<p>{{state.age}}</p>
		 button @click="myFn">按钮</button> 

	 	<p>{{state}}</p>
		<button @click="myFn">按钮</button> -->
		 
		<p>{{state.time}}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/*
1.什么是reactive?
- reactive是vue3.0中提供的响应式数据的方法
- 在vue2.0中响应式数据是通过defineProperty来实现的
  而在vue3.0中响应式数据是通过ES6的Proxy来实现的
2.reactive注意点:
- reactive参数必须是对象(json/arr)
- 如果给reactive传递了其他对象
   + 默认情况下修改对象,界面不会自动更新
   + 若想更新,可通过重新赋值的方式
 */
import {reactive} from 'vue';
export default {
	name: 'App',	
	// setup函数是组合API的入口函数
	setup() {
		// let state = reactive(123);
		// let state = reactive({
		// 		age:123
		// 	});
		// function myFn(){
			// state = 666;	
		// 	state.age = 666;
		// 	console.log(state);
		// }
		// let state = reactive([1,2,4,5]);
		// function myFn(){			
		// 	state[0] = 666;
		// 	console.log(state);
		// }
		let state = reactive({
			time: new Date()
		});
		function myFn(){
			// state.time.setDate(state.time.getDate() + 1);
			const newTime = new Date(state.time.getTime());
			newTime.setDate(state.time.getDate() + 1);
			state.time = newTime;
			console.log(state.time);
		}
		return {state, myFn};
	},
	
};
</script>

ref和reactive区别

<template>
	<div>
		<!-- 
		 ref和reactive区别
		 如果在template中使用的是ref类型的数据,那么Vue会自动帮我们添加.value
		 如果在template中使用的是reactive类型的数据,那么Vue不会自动帮我们添加.value
		 
		 Vue是如何决定是否需要自动添加.value的?
		 Vue在解析数据之前,会自动判断这个数据是否是ref类型的,
		 如果是就自动添加.value,若不是就不自动添加.value
		 
		 Vue是如何判断当前数据是否是ref类型的?
		 通过当前数据的__v_ref来判断的
		 若有这个私有属性,且取值为true,那么当前数据为ref类型的
		 -->
		<!-- <p>{{ state.age }}</p> -->
		<!-- 
		 注意点:
		 如果是通过ref创建的数据,那么在template中使用时不用通过.value的方式来获取
		 因为Vue会自动给我们添加.value
		 -->
		<p>{{ age }}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/*
1.什么是ref?
 - ref和reactive一样,也是用来实现响应式数据的方法
 - 由于reactive必须传递一个对象,所以导致在企业开发中
   如果我们只想让某个变量实现响应式的时候会非常麻烦,
   所以Vue3为我们提供了ref方法,来实现对简单值的监听
2.ref本质:
  - ref本质还是一个reactive
    当我们给ref函数传递一个值后,ref函数底层会自动将ref转换成reactive
    ref(18) -> reactive({value: 18})
3.ref注意点:
  - 在Vue中使用ref的值不用通过value获取
  - 在Js中使用ref的值必须通过value获取
 */

// import { reactive } from 'vue';
import { ref } from 'vue';
import {isRef, isReactive} from 'vue';
export default {
	name: 'App',
	// setup函数是组合API的入口函数
	setup() {
		// let state = reactive({
		// 	age: 18
		// });	
		// function myFn() {
		// 	state.age = 666;
		// }
		// return { state, myFn };
		/**
		 *ref本质:
		 * ref本质还是一个reactive
		 * 当我们给ref函数传递一个值后,ref函数底层会自动将ref转换成reactive
		 * ref(18) -> reactive({value: 18})
		 */
		let age = ref(18);
		// let age = reactive({value: 18});
		function myFn() {
			// age = 666;
			age.value = 666;
			console.log(age);
			console.log(isRef(age));
			console.log(isReactive(age));
		}
		return { age, myFn };
	}
};
</script>

递归、非递归监听

<template>
	<div>
		<p>{{ state.a }}</p>
		<p>{{ state.gf.b }}</p>
		<p>{{ state.gf.f.c }}</p>
		<p>{{ state.gf.f.s.d }}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/**
	 1.递归监听
	 默认情况下,无论通过ref还是通过reactive都是递归监听
	 
	 2.递归监听存在的问题
	 如果数据量比较大,非常消耗性能
	 
	 3.非递归监听
	 shallowRef / shallowReactive
	 
	 4.如何触发非递归监听属性更新界面?
	 如果是shallowRef类型的数据,可以通过triggerRef来触发
	 
	 5.应用场景
	 一般情况下,我们使用ref和reactive就可以了
	 只有在监听的数据量比较大的时候,我们才使用shallowRef和shallowReactive
	 */

// import { reactive } from 'vue';
// import { shallowReactive } from 'vue';
import { ref, shallowRef, triggerRef } from 'vue';
// import {isRef, isReactive} from 'vue';
export default {
	name: 'App',
	// setup函数是组合API的入口函数
	setup() {
		// let state = reactive({
		// let state = shallowReactive({
		// 	a: 'a',
		// 	gf: {
		// 		b: 'b',
		// 		f: {
		// 			c: 'c',
		// 			s: {
		// 				d: 'd'
		// 			}
		// 		}
		// 	}
		// });
		// function myFn() {
		// 	state.a = '1';
		// 	state.gf.b = '2';
		// 	state.gf.f.c = '3';
		// 	state.gf.f.s.d = '4';

		// 	console.log(state);
		// 	console.log(state.gf);
		// 	console.log(state.gf.f);
		// 	console.log(state.gf.f.s);
		// }

		// shallowRef -> shallowReactive
		// shallowRef(10) -> shallowReactive({value: 10})
		// let state = ref({
		let state = shallowRef({
			a: 'a',
			gf: {
				b: 'b',
				f:{
					c: 'c',
					s: {
						d:'d'
					}
				}
			}
		});
		function myFn() {
			// state.value.a = '1';
			// state.value.gf.b = '2';
			// state.value.gf.f.c = '3';
			// state.value.gf.f.s.d = '4';
			
			// state.value = {
			// 	a: '1',
			// 	gf: {
			// 		b: '2',
			// 		f:{
			// 			c: '3',
			// 			s: {
			// 				d:'4'
			// 			}
			// 		}
			// 	}
			// };
			state.value.gf.f.s.d = '4';
			// 注意点:Vue3只提供了triggerRef,没有提供triggerReactive方法
			triggerRef(state);
			
			// 注意点:如果是通过shallowRef创建的数据,
			//那么Vue监听的是.value的变化,并不是第一层的变化			
			console.log(state);
			console.log(state.value);
			console.log(state.value.gf);
			console.log(state.value.gf.f);
			console.log(state.value.gf.f.s);
		}
		return { state, myFn };
	}
};
</script>

toRaw

<template>
	<div>
		<p>{{ state }}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/**
 * 1.toRaw
 * 从Reactive或Ref中得到原始数据
 *
 * 2.toRaw作用
 * 做一些不想被监听的事情(提升性能)
 */
import { reactive, toRaw, ref } from 'vue';
export default {
	name: 'App',
	// setup函数是组合API的入口函数
	setup() {
		let obj = { name: 'asdf', age: 18 };
		/**
		 * ref/reactive数据类型的特点:
		 * 每次修改都会被追踪,都会更新UI界面,如此是非常消耗性能的,
		 * 所以若我们有一些操作不需要被追踪,不需要更新UI界面,那么此时,
		 * 我们可以使用toRaw方法拿到它的原始数据,对原始数据进行修改,
		 * 这样就不会被追踪,不会更新UI界面,这样性能就好了
		 */
		// let state = reactive(obj);
		// let obj2 = toRaw(state);
		let state = ref(obj);
		//注意点:如果想通过toRaw拿到ref类型的原始数据
		//        那么必须明确的告诉toRaw方法,要获取的是.value的值
		//        因为进过Vue处理之后,.value中保存的才是当初创建时传入的那个原始数据
		let obj2 = toRaw(state.value);
		console.log(obj === obj2);//true
		
		// console.log(obj === state);//false
		// state和obj的关系
		// 引用关系,state的本质是一个Proxy对象,在这个Proxy对象中引用了obj

		function myFn() {
			// 如果直接修改obj,是无法触发界面更新的
			// 只有通过包装之后的对象来修改,才会触发界面更新
			// obj2.name = 'zs';
			// console.log(obj2); //{name: "zs", age: 18}
			// console.log(state); //{name: "zs", age: 18}
			// state.name = 'zs';
			// console.log(state);
		}
		return { state, myFn };
	}
};
</script>

markRaw

<template>
	<div>
		<p>{{ state }}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/**
 * 1.markRaw
 * 数据永远不会被追踪
 */
import { reactive, markRaw } from 'vue';
export default {
	name: 'App',
	// setup函数是组合API的入口函数
	setup() {
		let obj = { name: 'asdf', age: 18 };
		obj = markRaw(obj);
		let state = reactive(obj);
		function myFn() {
			state.name = 'zs';
		}
		return { state, myFn };
	}
};
</script>

toRef

<template>
	<div>
		<p>{{ state }}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/**
 * 1.toRef
 * 响应式数据和原始数据产生关联,数据变化,且不会更新UI界面
 */
import { ref, toRef } from 'vue';
export default {
	name: 'App',
	// setup函数是组合API的入口函数
	setup() {
		let obj = { name: 'asdf', age: 18 };
		/*
		ref(obj.name) -> ref('asdf') -> reactive({value:'asdf'})
		*/
	    // ref -> 复制
		// let state = ref(obj.name);
		//toRef -> 引用
		/**
		 ref和toRef区别:
		 ref->复制,修改响应式数据不会影响以前的数据
		 toRef->引用,修改响应式数据会影响以前的数据
		 ref:数据发生改变,界面自动更新
		 toRef:数据发生改变,界面不会自动更新
		 
		 应用场景:
		 如果想让响应式数据和以前的数据关联起来,并且更新响应式数据之后还不想更新UI界面,那么就可以使用toRef
		 */		
		let state = toRef(obj,'name');
		function myFn() {
			state.value = 'zs';
			/**
			 * 结论:如果利用ref将某一个对象中的属性变成 响应式的数据
			 * 我们修改响应式的数据是不会影响到原始数据的
			 */
			/**
			 * 结论:如果利用ToRef将某一个对象中的属性变成 响应式的数据
			 * 我们修改响应式的数据是会影响到原始数据的
			 * 但是如果相应式数据是通过toRef创建的,那么修改了数据是不会触发UI界面的更新的
			 */
			console.log(obj);
			console.log(state);
		}
		return { state, myFn };
	}
};
</script>

toRefs

<template>
	<div>
		<p>{{ state.name }}</p>
		<p>{{ state.age }}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/**
 * 1.toRefs
 * 响应式数据和原始数据产生关联,数据变化,且不会更新UI界面
 */
import { ref, toRef, toRefs } from 'vue';
export default {
	name: 'App',
	// setup函数是组合API的入口函数
	setup() {
		let obj = { name: 'asdf', age: 18 };
			
		// let name = toRef(obj,'name');
		// let age = toRef(obj,'age');
		let state = toRefs(obj);
		function myFn() {
			// name.value = 'zs';
			// age.value = 666;
			
			console.log(obj);
			state.name.value = "zs";
			state.age.value = 666;
			console.log(state);
			// console.log(name);
			// console.log(age);
		}
		// return { name,age, myFn };
		return { state, myFn };
	}
};
</script>

customRef上

<template>
	<div>
		<p>{{age}}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
/**
 * 1.customRef
 * 返回一个ref对象,可以显式的控制依赖追踪和触发响应
 */
import { ref, customRef } from 'vue';

function myRef(value){
	return customRef((track,trigger)=>{
		return {
			get(){
				track();// 告诉Vue这个数据是需要追踪的
				console.log("get",value);
				return value;
			},
			set(newValue){
				console.log("set",newValue);
				value = newValue;
				trigger();//告诉Vue触发界面更新
			}
		}
	});
	
}
export default {
	name: 'App',
	setup() {
		// let age = ref(18);
		let age = myRef(18);
		function myFn(){
			age.value +=1;
		}
		return {age,myFn};
	}
};
</script>

customRef下.vue

<template>
	<ul>
		<li v-for="(item,index) in state" 
		:key="index">{{item.name}}</li>
	</ul>
</template>

<script>
/**
 * 1.customRef
 * 返回一个ref对象,可以显式的控制依赖追踪和触发响应
 */
import { ref, customRef } from 'vue';

function myRef(value){
	return customRef((track,trigger)=>{
		fetch(value)
				.then((res)=>{
					return res.json();
				})
				.then((data)=>{
					console.log(data);
					value = data;
					trigger();
				})
				.catch((error)=>{
					console.log(error);
				});
		return {
			get(){
				track();// 告诉Vue这个数据是需要追踪的
				console.log("get",value);
				// 注意点
				// 不能再get方法中发送网络请求
				// 渲染界面 -> 调用get -> 发送网络请求
				// 保存数据 -> 更新界面 -> 调用get				
				return value;
			},
			set(newValue){
				console.log("set",newValue);
				value = newValue;
				trigger();//告诉Vue触发界面更新
			}
		}
	});
	
}
export default {
	name: 'App',
	setup() {
		
		let state = myRef("../public/data.json");
		return {state};
	}
};
</script>

ref获取元素

<template>
	<div ref="box">
		我是一个div
	</div>
</template>

<script>
/**
 * 1.获取元素
 * 在Vue2.x中我们可以通过给元素添加ref="xxx",
 * 然后在代码中通过ref.xxx的方式来获取元素
 * 在Vue3.x中我们可以通过ref来获取元素
 */
/**
 * 
 * setup
 * beforeCreate
 * created
 */
import { ref,onMounted } from 'vue';
export default {
	name: 'App',
	setup() {
		let box = ref(null);
		
		onMounted(()=>{			
			console.log("onMounted",box.value);
		});
		
		console.log(box.value);//null
		
		return {box};
	}
};
</script>

readonly家族

<template>
	<div>
		<p>{{state.name}}</p>
		<p>{{state.attr.age}}</p>
		<p>{{state.attr.height}}</p>
		<button @click="myFn">按钮</button>
	</div>
</template>

<script>
import {readonly, isReadonly, shallowReadonly} from "vue";
export default {
	name: 'App',
	setup() {
		// readonly 用于创建一个只读的数据,并且是递归只读
		// let state = readonly({
		// 	name:"lng",
		// 	attr:{
		// 		age:18,
		// 		height:1.88
		// 	}
		// });
		// shallowReadonly 用于创建一个只读的数据,但是不是递归只读
		let state = shallowReadonly({
			name:"lng",
			attr:{
				age:18,
				height:1.88
			}
		});
		// const和readonly的区别
		// const:赋值保护,不能给变量重新赋值
		// readonly:属性保护,不能给属性重新赋值
		// const value = 123
		const value = {name:"ls",age:123};
		function myFn(){
			state.name = "sdlkfj",
			state.attr.age = 14;
			state.attr.height = 1.22;
			console.log(state);
			console.log(isReadonly(state));
			// value = 456;
			// console.log(value);
			value.name = "zs";
			value.age = 456;
			console.log(value);
		}		
		return {state,myFn};
	}
};
</script>

手写reactive、ref、isRef、isReactive

const { createBaseRollupPlugins } = require("vite");

function isRef(state){
    return state.__v_ref||false;
}

function isReactive(state){
    return state.__v_reacitve||false;
}

function ref(val){
    return reactive({value:val},{__v_ref:true});
}

function reactive(obj,tem){
    if(typeof obj === 'object' && obj != null){
        if(obj instanceof Array){
            // 如果是一个数组,那么取出数组中每个元素,
            // 判断每个元素是否又是一个对象,如果又是一个对象,那么也需要包装成一个Proxy
            obj.forEach((item, index)=>{
                if(typeof item === 'object'){
                    obj[index] = reactive(item,tem);
                }
            });
        }else{
            // 如果是一个对象,那么取出对象属性的值
            // 判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成一个Proxy
            for(let key in obj){
                let item = obj[key];
                if(typeof item === 'object'){
                    obj[key] = reactive(item,tem);
                }
            }
        }

        if(tem){
            obj = Object.assign(tem,obj);
        }else{
            obj = Object.assign({ __v_reacitve:true},obj);
        }   
        return new Proxy(obj,{
            get(obj,key){
                return obj[key];
            },
            set(obj,key,val){
                obj[key] = val;
                console.log("更新界面");
                return true;
            }
        });
    }else{
        console.log(`${obj} is not object`);
    }
    
}


let obj = {
    a: 'a',
    gf: {
        b: 'b',
        f:{
            c: 'c',
            s: {
                d:'d'
            }
        }
    }
};

let state = ref(obj);

state.a = "1";
state.gf.b = "2";
state.gf.f.c = "3";
state.gf.f.s.d = "4";

console.log("isRef",isRef(state));
console.log("isReactive",isReactive(state));

/*
let arr = [{id:"1", name:"鲁班"},{id:"2", name:"虞姬"}];
let state = reactive(arr);
state[0].name = "zs";
state[1].name = "ls";

console.log("isReactive",isReactive(state));
console.log("isRef",isRef(state));
*/

手写readonly、shallowReadonly、isReadonly

function shallowReadonly(obj){
    return new Proxy(obj,{
        get(obj,key){
            return obj[key];
        },
        set(obj,key,value){
           console.warn(`${key} 是只读的,不能赋值`);
        }
    });
}

/*
let obj = {
    a:1,
     gf:{
        b:2
    }
}

let state = shallowReadonly(obj);
state.a = 2;
*/

function readonly(obj){
    if(typeof obj === 'object' && obj != null){
        if(obj instanceof Array){
            // 如果是一个数组,那么取出数组中每个元素,
            // 判断每个元素是否又是一个对象,如果又是一个对象,那么也需要包装成一个Proxy
            obj.forEach((item, index)=>{
                if(typeof item === 'object'){
                    obj[index] = readonly(item);
                }
            });
        }else{
            // 如果是一个对象,那么取出对象属性的值
            // 判断对象属性的取值是否又是一个对象,如果又是一个对象,那么也需要包装成一个Proxy
            for(let key in obj){
                let item = obj[key];
                if(typeof item === 'object'){
                    obj[key] = readonly(item);
                }
            }
        }
        obj = Object.assign({ __v_readonly:true},obj);
        return new Proxy(obj,{
            get(obj,key){
                return obj[key];
            },
            set(obj,key,val){
                console.warn(`${key} 是只读的,不能赋值`);
            }
        });
    }else{
        console.log(`${obj} is not object`);
    }
    
}

function isReadonly(state){
    return state.__v_readonly||false;
}

let obj = {
    a:1,
    gf:{
        b:2
    }
}

let state = readonly(obj);
state.a = 2;
state.gf.b = 4;

console.log("isReadonly",isReadonly(state));

手写shallowReactive、shallowRef

function shallowRef(val){
    return shallowReactive({value:val});
}

function shallowReactive(obj){
    return new Proxy(obj,{
        get(obj,key){
            return obj[key];
        },
        set(obj,key,value){
            obj[key] = value;
            console.log("更新UI界面")
            return true;
        }
    });
}

let obj = {
    a: 'a',
    gf: {
        b: 'b',
        f:{
            c: 'c',
            s: {
                d:'d'
            }
        }
    }
};
/*

let state = shallowReactive(obj);

state.a = "1";
state.gf.b = "2";
state.gf.f.c = "3";
state.gf.f.s.d = "4";
*/

let state = shallowRef(obj);

state.value = {
    a: '1',
    gf: {
        b: '2',
        f:{
            c: '3',
            s: {
                d:'4'
            }
        }
    }
}

生命周期

对于生命周期函数这块,Vue3.0和2.x是有差异的。Vue3.0中,在 setup 中使用的 hook 名称和原来生命周期的对应关系:

beforeCreate -> 不需要
created -> 不需要
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeUnmount -> onBeforeUnmount
unmounted -> onUnmounted
errorCaptured -> onErrorCaptured
renderTracked -> onRenderTracked
renderTriggered -> onRenderTriggered

此外,被替换的生命周期函数若要在setup函数中使用须提前从vue中导入,如:
import { onMounted } from ‘vue’;

Vue Router 的安装使用

安装新版的 vue router

npm install vue-[email protected]

// 保证安装完毕的版本是 4.0.0 以上的

vue-router 添加路由

import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'
import Login from './views/Login.vue'

const routerHistory = createWebHistory()
const router = createRouter({
  history: routerHistory,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/login',
      name: 'login',
      component: Login
    }
  ]
})

使用 vue-router 获取参数和跳转路由

import { useRoute } from 'vue-router'
// 它是一个函数,调用后可以返回对应的对象。
const route = useRoute() 
// 我们返回出去,在页面中把它全部显示出来看看
return {
 route
}
// 对于一个object,如果我们想再页面显示它的全部内容,除了在 js 中使用 console,也可以使用 pre 标签包裹这个变量。
// pre 标签可定义预格式化的文本。在pre元素中的文本会保留空格和换行符。文本显现为等宽字体
<pre>{{route}}</pre>

// 替换 URL 为比较丰富的地址
http://localhost:8080/column?abc=foo#123

router-link 组件跳转的方式

我们第一种方法可以将 to 改成不是字符串类型,而是 object 类型,这个object 应该有你要前往route 的 name ,还有对应的 params。

:to="{ name: 'column', params: { id: column.id }}"

第二种格式,我们可以在里面传递一个模版字符串,这里面把 column.id 填充进去就好。

 :to="`/column/${column.id}`"

使用 useRouter 钩子函数进行跳转

const router = useRouter()
// 特别注意这个是 useRouter 而不是 useRoute,差一个字母,作用千差万别,那个是获得路由信息,这个是定义路由的一系列行为。在这里,我们可以掉用
router.push('/login') 

// router.push 方法跳转到另外一个 url,它接受的参数和 router-link 的 to 里面的参数是完全一致的,其实router link 内部和这个 router 分享的是一段代码,可谓是殊途同归了。

添加导航守卫

vue-router 导航守卫文档 : https://router.vuejs.org/zh/guide/advanced/navigation-guards.html.

router.beforeEach((to, from, next) => {
  if (to.name !== 'login' && !store.state.user.isLogin) {
    next({ name: 'login' })
  } else {
    next()
  }
})

添加元信息完成权限管理

vue-router 元信息文档 : https://router.vuejs.org/zh/guide/advanced/meta.html.

添加元信息:

   {
      path: '/login',
      name: 'login',
      component: Login,
      meta: { redirectAlreadyLogin: true }
    },
    {
      path: '/create',
      name: 'create',
      component: CreatePost,
      meta: { requiredLogin: true }
    },

更新路由守卫

router.beforeEach((to, from, next) => {
  console.log(to.meta)
  if (to.meta.requiredLogin && !store.state.user.isLogin) {
    next({ name: 'login' })
  } else if (to.meta.redirectAlreadyLogin && store.state.user.isLogin) {
    next('/')
  } else {
    next()
  }
})

Vuex 的安装使用

新版 Vuex 安装

npm install [email protected] --save

// 保证安装完毕的版本是 4.0.0 以上的

测试 Vuex store

import { createStore } from 'vuex'
// 从 vuex 导入 createStore 这个函数,我们发现 vue3 以后,这些第三方的官方库,名字出奇的相似,vue-router 也是以create 开头的,看起来非常的清楚。
const store = createStore({
  state: {
    count: 0
  },  
})
// createStore 接受一个对象作为参数,这些对象中包含了 vuex 的核型概念,第一个概念称之为 state,这里面包含的是我们想放入的在全局共享的数据,这里我们放入一个简单的 count。

// 现在我们已经可以直接访问这个值了,我们可以直接使用 store.state.count 来访问它。

console.log('store', store.state.count)
// 接下来我们来更改状态,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
  mutations: {
    add (state) {
      state.count++
    }
  }
  
// 有了 mutations 以后,让我们来触发它,要唤醒一个 mutation handler,你需要以相应的 type 调用 store.commit 方法:
store.commit('add')
console.log('count', store.state.count)

Vuex 整合当前应用

定义 store 文件

import { createStore } from 'vuex'
import { testData, testPosts, ColumnProps, PostProps } from './testData'
interface UserProps {
  isLogin: boolean;
  name?: string;
  id?: number;
}
export interface GlobalDataProps {
  columns: ColumnProps[];
  posts: PostProps[];
  user: UserProps;
}
const store = createStore<GlobalDataProps>({
  state: {
    columns: testData,
    posts: testPosts,
    user: { isLogin: false }
  },
  mutations: {
    login(state) {
      state.user = { ...state.user, isLogin: true, name: 'viking' }
    }
  }
})

export default store

使用

import { useStore } from 'vuex'
import { GlobalDataProps } from '../store'

...
const store = useStore<GlobalDataProps>()
const list = computed(() => store.state.columns)

Vuex getters

vuex getters 文档 :https://vuex.vuejs.org/zh/guide/getters.html.

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

getters: {
  biggerColumnsLen(state) {
    return state.columns.filter(c => c.id > 2).length
  }
}
// 定义完毕,就可以在应用中使用这个 getter 了
// Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值:
const biggerColumnsLen =computed(()=>store.getters.biggerColumnsLen)
getColumnById: (state) => (id: number) => {
  return state.columns.find(c => c.id === id)
},
getPostsByCid: (state) => (id: number) => {
  return state.posts.filter(post => post.columnId === id)
}
// 定义完毕以后就可以在应用中使用 getter 快速的拿到这两个值了
const column = computed(() => store.getters.getColumnById(currentId))
const list = computed(() => store.getters.getPostsByCid(currentId))
相关标签: 技术 vue.js

上一篇:

下一篇: