1. 程式人生 > 其它 >Vue3.0學習筆記

Vue3.0學習筆記

技術標籤:技術vue.js

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-[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))