Vue3.0学习笔记

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-router@next

// 保证安装完毕的版本是 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 vuex@next --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))
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值