1. 程式人生 > 其它 >vue3技術整理 - 更新完畢

vue3技術整理 - 更新完畢

0、前言




1、建立vue3專案

官網文件:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create



1.1、使用vue-cli建立

注意:需要保證自己的vue-cli版本在4.5.0以上


	檢視自己@vue/cli版本
	Vue -V

	安裝或升級@vue/cli
	npm install -g @vue/cli

	建立Vue3專案
	vue create 專案名

	使用ui視覺化介面建立【 ps:會在瀏覽器中開啟一個管理視窗,進行手動建立和配置選項,目前不建議用,學完所有就可以使用了 】
	vue ui

	啟動Vue3專案
	cd 進入到建立的專案中
	npm run serve





1.2、使用vite建立

官網地址;https://v3.cn.vuejs.org/guide/installation.html#vite



vite是另一門技術,是下一代的前端構建工具【 ps:是vue官網打造的,久一代的就是webpack 】。vite官網地址 https://vitejs.cn/

  • vite的優勢:

    • 開發環境中,無需打包操作,可快速的冷啟動
    • 輕量快速的熱過載( HMR )【 ps:webpack也可以熱部署,只是vite更輕量和快 】
      • 快體現在熱部署的方式不同【 ps:官網中有對比圖 】
    • 真正的按需編譯,無需等待整個應用編譯完成再啟動【 ps:圖中我解讀的哪裡,是根據路由再找模組 】


整理vite建立vue3的相關指令


	建立專案
	npm init vite-app 專案名

	進入專案目錄
	cd 專案名

	安裝依賴
	npm install


	執行專案
	npm run dev




2、分析vue-cli建立的vue3專案

  • 使用vscode開啟vue-cli建立的專案


2.1、檢視整個目錄結構




2.2、分析入口檔案main.js


其他的東西和vue2中沒什麼兩樣


注意點:template中的寫法和vue2相比有點變化




3、安裝vue3的開發者工具

注意:這裡安裝的測試版( data )


直接使用google進行安裝即可,要是無法進入google應用商店的話,那麼:去百度chrome外掛網就可以了;或者:下載一個佛跳牆 / 極光【 ps:佛跳牆現在的名字貌似改為極光了,下載安裝之後,直接連結,然後就可以FQ了,有其他APN代理更好 】


  • 另外:Edge中的"Iguge訪問助手"也可以進行FQ,去Edge中下載了,找到安裝目錄,然後拷貝,拖到chrome-google的擴充套件程式中即可

注意:vue2和vue3的開發者工具最好別一起開啟,開了vue2就別開vue3,開了vue3就別開vue2,很容器出問題




4、認識基本的Composition API / 組合式API

4.1、認識setup函式

setup是Compostition API / 組合式API的地基,setup是一個函式,且必須有返回值,玩vue3,那麼就需要提供一個平臺,而這個平臺就是setup




4.1.1、對setup函式快速上手


	<template>
	  <!-- 這裡面能夠拿到以下的東西,全靠的是setup的return返回回來的結果 -->
	  <h1>姓名: {{name}}</h1>
	  <h1>性別: {{sex}}</h1>
	  <h1>工種: {{job}}</h1>

	  <br>
	  <br>

	  <button @click="helloword">呼叫一下vue3的setup中定義的方法</button>
	</template>

	<script>

	export default {
	  name: 'App',

	  // 一、配置setup平臺
	  setup(){
		// 這裡面的配置和vue2中的差不多,什麼資料、方法、計算屬性、生命週期.......只是寫法有區別

		// 配置資料【 ps:直接定義即可 】
		let name = '紫邪情';
		let sex = '女';
		let job = 'Java';

		// 配置方法
		function helloword(){
		  // 注意點:alert()裡面用的是模板字串 - 飄字元嘛;${name}就是取上面定義的資料
		  alert(`我叫: ${name},性別: ${sex}.工種: ${job}`)
		}

		// setup必須有返回值 return - 就是為了把setup配置的東西交出去嘛,不然別人怎麼拿到
		// 但是:return有兩種寫法

		// 1、物件寫法
		return {
		  // 返回資料
		  name,sex,job,

		  // 返回方法
		  helloword
		}
	  }
	}
	</script>



補充:setup函式返回值的另一種寫法:返回渲染函式寫法 - 瞭解介面【 ps:這是為了自定義渲染內容的 】

  • 第一步:引入渲染函式h,指令:import {h} from 'vue'

  • 第二步:使用渲染函式 並 返回

		// 2、第二種寫法:返回渲染函式 - 瞭解即可
		// 這種寫法:會將下面自定義寫的渲染內容 放到 前面template中去渲染
		// 即:template的渲染依賴於下面自定義內容

		// 第一步:需要在本元件中引入渲染函式h
		// 第二步:使用渲染函式 並 返回
		// return ( (h) => h( 'h1', '這是setup中的返回渲染函式使用者') )
		// 簡寫
		return () => h('h1','這是setup中的返回渲染函式使用者')




4.1.2、聊聊setup函式的細節問題

用setup和vue2的寫法一起用 - 最好:堅決別用


  • 原因:是因為vue3向下相容嘛,所以:可以加入vue2的寫法

  • 但是:說過不建議vue3和vue2混合使用 - 來演示一下bug
  • 注意:反過來就可以啊,即:在vue2的配置中可以獲取setup函式中配置的東西,演示就跳過了
  • 另外還有一個注意點:如果在vue2和vue3的setup中配置了相同的東西,那麼:優先使用setup中的,如:在vue2的配置中配置了address資料,又在vue3的setup中配置了address資料,那麼:優先使用的是setup中的


setup()的另外一個注意點 和 其可以接收的兩個引數 - 演示自行玩

  • setup執行時機:
    • 在beforeCreate之前執行一次,同時在setup(){ }中的this是undefined,即:在setup中不可以用this拿到東西 【 ps:可以試著定義一個beforeCreate和setup,裡面都輸出一句話,看控制檯誰的話在前面即可 】
  • setup可以接受的兩個引數:
    • props:值為物件,包含:元件外部傳遞過來 且 元件內部宣告接收了的屬性 【 ps:也就是在元件內部和vue2一樣配置了props配置項 - 有三種配置方式,vue2的基礎知識,濾過了 】
      • 注意點:若在外部傳遞了資料,而內部沒有配置props配置項進行宣告接收,那麼:vue2中不會有什麼錯誤,但是:在vue3中就會在控制檯拋錯
    • context:上下文物件 【 ps:它裡面有三個屬性 】
      • attrs:俗稱撿漏王。值為物件,包含:元件外部傳遞過來,但沒有在props配置中宣告的屬性,相當於vue2中的this.$attrs
        • 換言之:就是如果父元件傳遞了資料,但:子元件中的props配置沒有宣告要接收,那麼:傳遞的資料就在子元件的attrs屬性上
      • slots:看名字就知道,就是收到的插槽內容,相當於vue2中的this.$slots
      • emit:也是看名字就知道的,觸發自定義事件的函式嘛,相當於vue2中的this.$emit
        • 但是:注意在vue3中,這個東西根據vue2的正常寫法,好使,可是:控制檯會拋警告,不想看到警告,那就在使用了這個emit的元件中,配置一個emits配置項即可 - 和props宣告接收屬性的配置一樣,如:emits: ['getField']



4.1.3、vue3的setup函式總結

  • 所謂的setup,就是vue3中的一個全新配置項而已,值是一個函式,它是vue3中Composition API / 組合式API的地基,且這個setup函式必須有一個返回值
  • 元件中所用的:資料、方法、計算屬性、生命週期等,均可配置在setup函式中【 ps:注意寫法不太一樣 】
  • setup函式的兩種返回值寫法:
    • 1、返回一個物件,若是這種:那麼物件中的屬性、方法等,在template模板中均可以直接使用 - 重點
    • 2、返回一個渲染函式,這種方式是為了自定義渲染內容,玩法如下:
      • 1)、引入渲染函式h,指令:import {h} from 'vue'
      • 2)、使用,如指令:return () => h('h1','這是setup中的返回渲染函式使用者')

  • 注意點:
    • 1、儘量不要與vue2的配置混合使用
      • 在vue2配置( data、methods、computed... )中可以訪問到setup函式中的屬性、方法等
        • 但是:在setup函式中不能訪問到vue2中配置的內容
      • 如果setup和vue2中的配置有重名的,則:優先使用setup中的配置
    • setup不能是一個async函式,因為返回值不再是retunr的物件,而是promise,這樣的話:模板看不到return物件中的屬性

setup()的另外一個注意點 和 其可以接收的兩個引數

  • setup執行時機:
    • 在beforeCreate之前執行一次,同時在setup(){ }中的this是undefined
  • setup可以接受的兩個引數:
    • props:值為物件,包含:元件外部傳遞過來 且 元件內部宣告接收了的屬性
      • 注意點:若在外部傳遞了資料,而內部沒有配置props配置項進行宣告接收,那麼:vue2中不會有什麼錯誤,但是:在vue3中就會在控制檯拋錯
    • context:上下文物件 【 ps:它裡面有三個屬性 】
      • attrs:俗稱撿漏王。值為物件,包含:元件外部傳遞過來,但沒有在props配置中宣告的屬性,相當於vue2中的this.$attrs
        • 換言之:就是如果父元件傳遞了資料,但:子元件中的props配置沒有宣告要接收,那麼:傳遞的資料就在子元件的attrs屬性上
      • slots:看名字就知道,就是收到的插槽內容,相當於vue2中的this.$slots
      • emit:也是看名字就知道的,觸發自定義事件的函式嘛,相當於vue2中的this.$emit
        • 但是:注意在vue3中,這個東西根據vue2的正常寫法,好使,可是:控制檯會拋警告,不想看到警告,那就在使用了這個emit的元件中,配置一個emits配置項即可 - 和props宣告接收屬性的配置一樣,如:emits: ['getField']



4.2、ref函式

先做一個例項:修改setup中的資料

    <template>
      <h1>vue3的setup函式得到的操作</h1>
      <h2>姓名: {{name}}</h2>
      <h2>性別: {{sex}}</h2>
      <button @click="changeData">修改setup中的資料</button>
    </template>

    <script>
    export default {
      name: 'App',

      // 一、配置setup平臺
      setup(){

        let name = '紫邪情';
        let sex = '女';

        function changeData(){
          name = '紫邪晴'
          sex = '男'
          console.log("修改之後的資料: ",name,sex);
        }

        // 1、物件寫法
        return {
          // 返回資料
          name,sex,

          // 返回方法
          changeData
        }
      }
    }
    </script>


沒實現出來,原因就是:vue不認你的修改,因此:需要藉助ref函式來套娃




4.2.1、看看ref函式的真身

    <template>
      <h1>vue3的setup函式得到的操作</h1>
      <h2>姓名: {{name}}</h2>
      <h2>性別: {{sex}}</h2>
      <button @click="changeData">修改setup中的資料</button>
    </template>

    <script>

    import {ref} from 'vue'
    export default {
      name: 'App',

      // 一、配置setup平臺
      setup(){

        // 使用ref函式來進行實現,進行套娃,把資料丟給ref函式進行管理
        let name = ref('紫邪情');
        let sex = ref('女');

        function changeData(){
          // console.log("修改之後的資料: ",name,sex);

          // 看一下ref函式的真身
          console.log(name);
        }

        // 1、物件寫法
        return {
          // 返回資料
          name,sex,

          // 返回方法
          changeData
        }
      }
    }
    </script>



既然知道了ref函式的真身,那麼:想要實現資料的改變就變得輕鬆了



有個注意點




4.2.2、使用ref處理物件型別

4.2.2.1、看一下ref函式中套物件的樣子是怎樣的
    <template>
      <h2>工種: {{job.type}}</h2>
      <h2>薪資: {{job.salary}}</h2>
      <button @click="changeData">檢視一下ref函式中套物件的樣子</button>
    </template>

    <script>

    import {ref} from 'vue'
    export default {
      name: 'App',

      // 一、配置setup平臺
      setup(){
        // 套物件在ref中
        let job = ref({
          type: 'Java',
          salary: '20k'
        });

        function changeData(){
          // 先看一下ref中套物件的樣子是怎樣的
          console.log(job.value);
        }

        // 1、物件寫法
        return {
          // 返回資料
          job,
          // 返回方法
          changeData
        }
      }
    }
    </script>

既然知道了ref函式中套了物件的樣子長什麼樣的,那麼:想要修改ref裡面套的物件的屬性就很好操作了




4.2.2.2、修改ref函式中物件的屬性值

小小總結一下

  • ref函式修飾的是基本型別時【 ps:即直接用let name = ref('紫邪情') 】,則:資料代理就是Object.dinfineProperty的setter和getter
  • ref函式修飾的是物件型別時【 ps:即let job = ref( { } 】,則:資料代理的原理是Proxy物件【 ps:window的ES6的全新配置 。這個物件後續會進行說明 】



4.2.3、對ref函式小小總結一波

  • 作用:定義一個響應式的資料【 ps:即,修改資料之後可以把改後的資料渲染到頁面中 】

  • 語法:const xxx = ref(initValue)

    • 建立一個包含響應式資料的引用對應
    • js中操作資料:xxx.value
    • 模板中讀取資料:不需要.value,直接:<div>{{xxx}}</div>
  • 注意點:

    • ref()函式中接收的資料可以是:基本型別、也可以是物件型別
      • ref函式修飾的是基本型別時【 ps:即直接用let name = ref('紫邪情') 】,則:資料代理就是Object.dinfineProperty的setter和getter
      • ref函式修飾的是物件型別時【 ps:即let job = ref( { } 】,則:資料代理的原理是Proxy物件【 ps:這個物件其實是由Object轉了一遍,即:Object ——> Proxy,而這個物件window的ES6的全新配置 。這個物件後續會進行說明 】,物件內部 / 屬性實質是藉助了vue3的一個新函式 —— reactive()函式



4.3、認識reactive()函式 - 深度監視

  • 這個函式就是專門用來處理資料是物件 / 陣列型別的
  • reactive()函式不能處理基本型別,想要處理基本型別,那麼:就使用ref()函式
  • ref( { } )這裡面套物件的型別時,它的原理就是呼叫了reactive()函式


簡單玩一下reactive()函式

  • 1、引入reactive()函式,指令: import {reactive} from 'vue'
  • 2、使用reactive()函式
    <template>
      <h1>ref託管的資料</h1>
      <h2>{{name}}</h2>

      <br>
      <br>

      <h1>reactive託管的資料</h1>
      <h2>{{job.type}}</h2>
      <h2>{{job.salary}}</h2>

      <br>
      <br>

      <button @click="changeData">修改ref和reactive託管的資料</button>
    </template>

    <script>

    import {ref,reactive} from 'vue'
    export default {
      name: 'App',

      // 一、配置setup平臺
      setup(){
        // 配置基本型別資料 - 通過ref實現
        let name = ref('紫邪情');
        // 使用reactive來管理資料
        let job = reactive({
          type: 'Java',
          salary: '20k'
        })
        // 修改基本型別資料
        function changeData(){
          // 修改ref管理的資料型別
          name.value = '紫邪晴';

          // 修改reactive託管的資料 - 相比ref,不再跟value了
          job.type = 'C';
          job.salary = '3毛';
        }

        return {
          // 返回基本型別資料 - ref託管
          name,

          // 返回reactive託管的資料
          job,

          // 返回函式
          changeData,
        }
      }
    }
    </script>



瞭解reactive的細節問題

  • 前面說:reacitve()函式不能處理基本型別,那測試一下

  • reacitve()函式託管陣列型別

  • 想讓reactive()函式也能夠託管基本型別的資料,怎麼辦?
    • 把基本型別 使用 物件寫法嘛,包裝一下唄

  • reactive()函式深度監視效果



4.3.1、對reactive()函式總結一波

  • 作用:定義一個物件型別的響應式資料【 ps:基本類型別用它,用ref函式 】
  • 語法:
    • 引入reactive()函式,指令:import {reactive} from 'vue'
    • 使用:const 代理物件 = reactive(源物件)接收一個物件 / 陣列,返回一個代理物件 / proxy物件
  • reactive定義的響應式資料時“深層次的”
  • reactive的內部是基於ES6的Proxy實現的,通過代理物件操作源物件內部資料



4.4、vue3中資料監視的原理

4.4.1、Proxy資料監視原理

vue2中資料監視如果是物件型別的,那麼是通過Object.defineProperty()的getter和setter來做到資料監視的;如果是陣列型別那麼就是通過那7個API做到資料監視,但是這種方式有弊端,如下:

  • 新增屬性、刪除屬性,介面不會更新
  • 直接通過下標修改陣列,介面不會更新

但是:vue3中就不會出現上面的幾種情況



先來看一下Proxy長什麼樣子



使用Proxy進行修改資料



    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>研究Proxy監視資料原理</title>
    </head>
    <body>
        <script>
            // 定義一個物件
            let person = {
                name: '紫邪情',
                sex: '女'
            }

            // 利用Window.Proxy()來進行修改person

            // 先看一下Proxy的樣子
            // console.log( new Proxy(person,{} ) );

            // 使用Proxy進行資料修改
            /* 
                people 就是代理物件 它代理的就是person
                new Proxy()就是建立一個代理物件嘛 - 後端的人太熟悉不過了
            */
            const people = new Proxy( person, {
                // 獲取物件的屬性時呼叫
                /* 
                target 就是源物件 即:person
                propName  就是物件中的屬性名  如:name、sex.....
                */
                get(target,propName){
                    console.log( "target,propName這兩個引數為: ", target,propName);
                    console.log(`有人獲取person中的${propName}屬性`);
                    return target[propName];
                },

                // 修改物件中的屬性時呼叫【 ps:修改含增、改、刪除是另一個配置 】
                // value就是修改之後的值
                set( target,propName,value ){
                    console.log( "target,propName,value這三個引數為: ", target,propName,value);
                    console.log( `有人修改person中的${propName}屬性` );
                    return target[propName] = value;
                },

                // 刪除物件中的屬性時呼叫
                deleteProperty(target,propName){
                    console.log( "target,propName這兩個引數為: ", target,propName);
                    return delete target[propName];
                }
            })
        </script>
    </body>
    </html>





4.4.2、Reflect資料監視原理

在vue3中資料監視不止用了window的Proxy物件,還用了window的Reflect物件

Reflect就是反射的意思,這個東西對於玩Java的人來說再熟悉不過了,所以不再過多介紹,在前段中這個是ES6的特性



認識Reflect物件

  • 看看Reflect長什麼樣
		// 先看看Reflect長什麼樣
        console.log(window.Reflect);



經過上圖的檢視之後,其實也就知道Reflect改怎麼玩了,調對應的API就可以了【 ps:ECMA組織正打算把常用的一些API放到Reflect物件身上,如:目前把Object.defineProperty()就放在Reflect中了 - vue2的資料代理原理的API 】



使用Reflect實現資料監視

        let person = {
            name: '紫邪情',
            sex: '女'
        }

        // 先看看Reflect長什麼樣
        // console.log(window.Reflect);

        // 使用Reflect實現資料監視

        // 1、獲取物件的屬性 - key-value的形式
        /* 
            key 就是物件名
            value 就是物件的屬性名
        */
		Reflect.get( person,'name' );


        // 2、修改物件的屬性 
		Reflect.set( person,'sex','男');
		Reflect.set( person,'age', '18');

        // 3、刪除物件的屬性
		Reflect.deleteProperty( person,'sex');



注意:使用Reflect做對應的操作之後是有返回值的,如:Reflect.set( person,'age',18 ),返回值是true,所以:就可以利用這個返回值做很多事情,如:進行封裝,而Object.defineProperty()並沒有返回值
同時:Reflect支援屬性名重複,即:若用set()這個API對同一個物件的同一個屬性做多次相同的操作,則:不會返回異常,而是返回true / false,因此:才說可以用這個返回值做很多事情;若用Object.defineProperty()來進行相同的操作,則:會直接拋異常,甚至想要後續的程式碼還能執行,就只能使用try......catch....來對該部分的程式碼進行包裹了



vue3真正做到資料監視的原理 - 使用Proxy和Reflect物件進行套娃


	<!DOCTYPE html>
	<html lang="en">
	<head>
		<meta charset="UTF-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>vue3實現資料監視的真正原理</title>
	</head>
	<body>
		<script>
			let person = {
				name: '紫邪情',
				sex: '女'
			}

			const people = new Proxy( person, {
				// 獲取物件的屬性時呼叫
				get(target,propName){
					console.log( "target,propName這兩個引數為: ", target,propName);
					console.log(`有人獲取person中的${propName}屬性`);
					// 此處進行了Reflect套娃
					// return target[propName];
					return Reflect.get(target,propName);
				},

				// 修改物件中的屬性時呼叫【 ps:修改含增、改、刪除是另一個配置 】
				set( target,propName,value ){
					console.log( "target,propName,value這三個引數為: ", target,propName,value);
					console.log( `有人修改person中的${propName}屬性` );
					// 此處進行了Reflect套娃
					// return target[propName] = value;
					return Reflect.set(target,propName,value);
				},

				// 刪除物件中的屬性時呼叫
				deleteProperty(target,propName){
					console.log( "target,propName這兩個引數為: ", target,propName);
					// 此處進行了Reflect套娃
					// return delete target[propName];
					return Reflect.defineProperty(target,propName);
				}
			})
		</script>
	</body>
	</html>





4.4.3、vue3中資料監視原理總結

  • 通過Proxy代理物件:攔截物件中任意屬性的變化,包括:屬性值的讀寫、屬性的新增、屬性的刪除等

  • 通過Reflect反射物件:對被代理物件的屬性進行操作,例子如下:

    •         const people = new Proxy( person, {
                  // 攔截讀取屬性值
                  get(target,propName){
                      console.log( "target,propName這兩個引數為: ", target,propName);
                      console.log(`有人獲取person中的${propName}屬性`);
                      // 此處進行了Reflect套娃
                      // return target[propName];
                      return Reflect.get(target,propName);
                  },
      
                  // 攔截修改屬性值【 ps:是修改和新增 】
                  set( target,propName,value ){
                      console.log( "target,propName,value這三個引數為: ", target,propName,value);
                      console.log( `有人修改person中的${propName}屬性` );
                      // 此處進行了Reflect套娃
                      // return target[propName] = value;
                      return Reflect.set(target,propName,value);
                  },
      
                  // 攔截刪除屬性值
                  deleteProperty(target,propName){
                      console.log( "target,propName這兩個引數為: ", target,propName);
                      // 此處進行了Reflect套娃
                      // return delete target[propName];
                      return Reflect.defineProperty(target,propName);
                  }
              })
      
      
  • 另外:附上Proxy和Reflec物件說明的官網連結




4.5、reactive() 和 ref()的對比

  • 從定義資料角度對比
    • ref用來定義:基本型別資料
    • reactive用來定義:物件 / 陣列型別資料
    • 注:ref也可以用來定義物件 / 陣列型別資料,它內部會自動通過reactive轉成Proxy代理物件
  • 從原理角度對比:
    • ref通過Object.defineProperty()的get和set來實現的資料劫持 / 資料監視 / 響應式
    • reactive通過使用Proxy代理物件來實現資料劫持,並通過Reflect操作源物件內部的資料
  • 從使用角度對比:
    • ref定義的資料:操作資料需要.value,讀取資料時模板中直接讀取,不需要.value
    • reactive定義的資料:操作資料與讀取資料,均不需要.value



4.6、Vue3中的Computed 計算屬性函式

  • 其實和uve2中的計算屬性沒什麼兩樣,只是多了一步引入的問題 以及 排放的問題問題而已
    <template>
      姓;<input type="text" v-model="person.firstName">

      <br>

      名: <input type="text" v-model="person.lastName">

      <br>

      <span>全名: {{person.fullName}}</span>
    </template>

    <script>
    import { reactive } from '@vue/reactivity'

    // 1、引入computed計算屬性函式
    import { computed } from '@vue/runtime-core'

    export default {
      name: 'App',

      setup(){

        // 資料
        let person = reactive({
          firstName: '紫',
          lastName: '邪情'
        })

        // 2、使用計算屬性函式 setup中this無效,所以computed()中使用蘭姆達和正常寫法都無所謂
        // 簡寫形式 - 只考慮讀的問題
        person.fullName = computed( ()=>{
          return person.firstName + "-" + person.lastName;
        })

        // 完整寫法 - 考慮讀和改的問題
       /*  person.fullName = computed({
          get(){
            return person.firstName + "-" + person.lastName;
          },

          set(value){
            const nameDataArr = value.split('-')
            person.firstName = nameDataArr[0]
            person.lastName = nameDataArr[1]
          }
        }) */

        // 返回資料
        return {
          person,
        }
      }
    }
    </script>




4.7、vue3中的watch 監視屬性函式

  • 和vue2中的watch也差不多


4.7.1、監視ref()定義的資料

  • 這個貓玩意兒和vue2中差不多

簡單寫法:監視ref託管的單個響應式資料

    <template>
      <h1>當前值為: {{num}}</h1>
      <button @click="num ++ ">num++</button>
    </template>

    <script>

    // 1、引入watch函式
    import { ref, watch } from '@vue/runtime-core'


    export default {
      name: 'App',

      setup(){

        // 準備資料 - 用ref託管
        let num = ref(0)


        // 一、簡單寫法
        // 2、使用watch函式
        /* 
          可以接受三個引數
              第一個:監視的是誰?
              第二個:回撥函式 - 新值 和 舊值
              第三個:配置項 - deep深度監視也可以配置
        */
        watch( num , (newValue,oldValue)=>{
          console.log("num的值發生改變了",newValue,oldValue);
        },{immediate:true})


        return {
          num,
        }
      }
    }
    </script>



監視多個屬性:監視ref託管的多個響應式資料【 ps:陣列寫法 】

    <template>
      <span>當前名字為: {{name}}</span>
      <br>
      <button @click="name += '!'">改變name</button>
    </template>

    <script>

    // 1、引入watch函式
    import { ref, watch } from '@vue/runtime-core'


    export default {
      name: 'App',

      setup(){

        // 準備資料 - 用ref託管
        let num = ref(0)

        let name = ref('紫邪情')

        // 監視ref託管的多個響應式資料 - 變化就在這裡 監事的是誰?採用陣列寫法即可
        watch( [num,name],(newValue,oldValue)=>{
          console.log("num 和 name的值發生改變了",newValue,oldValue);
        },{immediate:true})
        return {
          num,name
        }
      }
    }
    </script>




4.7.2、監視reactive()定義的資料

監視reactive託管的一個響應式資料中的全部屬性

    <template>
      姓名: <input type="text" v-model="person.name"> <br>
      性別: <input type="text" v-model="person.sex"> <br>
      地址: <input type="text" v-model="person.address.detailed.value">

      <br>
      <br>

      <span>姓名: {{person.name}}</span> <br>
      <span>性別: {{person.sex}}</span> <br>
      <span>地址: {{person.address.detailed.value}}</span>
    </template>

    <script>
    import { reactive } from '@vue/reactivity'
    import { watch } from '@vue/runtime-core'

    export default {
      name: 'App',

      setup(){

        // 準備資料 - 用reactive託管
        let person = reactive({
          name: '紫邪情',
          sex: '女',
          address: {
            detailed: {
              value: '浙江省杭州市'
            }
          }
        })

        // 監視reactive託管的一個響應式資料中的全部屬性
        watch( person,(newValue,oldValue)=>{
          console.log( "person被修改了", newValue,oldValue);
        })

        return {
          person,
        }
      }
    }
    </script>


上面這種坑就是在監視此種reactive託管的一個響應式資料的全部屬性時,並不能獲得舊值oldValue,因為:舊值oldValue和新值newValue一樣



但是:還有一種坑,就是:此種類型是強制開啟了深度監視,即:配置deep:false不頂用



監視reactive託管的一個響應式資料中的某一個屬性

    // 型別二、監視reactive託管的一個響應式資料中的某一個屬性
    /* 
      奇葩的地方:
          1、要監視的這個屬性需要寫成函式式 ()=> person.name
          2、可以爭取獲取newValue、oldValue
    */
    watch( ()=> person.name , (newValue,oldValue)=>{
      console.log("person中的name屬性被修改了",newValue,oldValue);
    })



監視reactive託管的一個響應式資料中的某些屬性 - 函式式陣列寫法

    // 型別三、監視reactive託管的一個響應式資料中的某些屬性
    /* 
      奇葩的地方:
          1、監視的多個屬性需要使用陣列套起來
          2、陣列中的每一個屬性需要寫成函式式
    */
    watch( [ ()=> person.name , ()=> person.sex ] , (newValue,oldValue)=>{
      console.log("person中的name和sex屬性被修改了",newValue,oldValue);
    })



特殊情況:監視reactive託管的一個響應式資料中的某一個屬性【 ps:此屬性套娃了,又是一個物件 】

    // 型別四、監視reactive託管的一個響應式資料中的某個屬性,但:此屬性又套娃了
    /* 
      奇葩的地方:
          1、需要開啟深度監視 即:deep:true 又生效了
          2、不加 deep:true配置,程式碼會無效
    */
    watch( ()=> person.address , (newValue,oldValue)=>{
      console.log("person中的address屬性被修改了",newValue,oldValue);
    },{deep:true})


但是:如果不加deep:true配置呢?




4.7.2.1、監視reactive託管的一個響應式資料的各種型別總結
  • 注:在vue3中可以同時配置多個watch,而在vue2中配置重複的,那只有前者有效

    // 準備資料 - 用reactive託管
    let person = reactive({
      name: '紫邪情',
      sex: '女',
      address: {
        detailed: {
          value: '浙江省杭州市'
        }
      }
    })

    // 型別一、監視reactive託管的一個響應式資料中的全部屬性
    /* 
      此種類型的坑:
          1、無法正確獲得oldValue的值【 ps:因newValue和oldValue的值一樣 】
          2、簡直強制開啟了深度監視 【 ps:即deep:false配置無效 】
    */
    watch( person,(newValue,oldValue)=>{
      console.log( "person被修改了", newValue,oldValue);
    },{deep:false})
    /* 
      如:這裡關閉深度監視 理論上:應該監視不到address.detailed.value
          但是:天真
    */


    // 型別二、監視reactive託管的一個響應式資料中的某一個屬性
    /* 
      奇葩的地方:
          1、要監視的這個屬性需要寫成函式式 ()=> person.name
          2、可以爭取獲取newValue、oldValue
    */
    watch( ()=> person.name , (newValue,oldValue)=>{
      console.log("person中的name屬性被修改了",newValue,oldValue);
    })


    // 型別三、監視reactive託管的一個響應式資料中的某些屬性
    /* 
      奇葩的地方:
          1、監視的多個屬性需要使用陣列套起來
          2、陣列中的每一個屬性需要寫成函式式
    */
    watch( [ ()=> person.name , ()=> person.sex ] , (newValue,oldValue)=>{
      console.log("person中的name和sex屬性被修改了",newValue,oldValue);
    })

    // 型別四、監視reactive託管的一個響應式資料中的某個屬性,但:此屬性又套娃了
    /* 
      奇葩的地方:
          1、需要開啟深度監視 即:deep:true 又生效了
          2、不加 deep:true配置,程式碼會無效
    */
    watch( ()=> person.address , (newValue,oldValue)=>{
      console.log("person中的address屬性被修改了",newValue,oldValue);
    },{deep:true})

    return {
      person,
    }




4.8、vue3中的watchEffect 智慧監視函式

  • 注:此種監視對應ref託管和reactive託管都可以監視到*

    <template>
      姓名: <input type="text" v-model="person.name"> <br>
      性別: <input type="text" v-model="person.sex"> <br>
      地址: <input type="text" v-model="person.address.detailed.value">

      <br>
      <br>

      <span>姓名: {{person.name}}</span> <br>
      <span>性別: {{person.sex}}</span> <br>
      <span>地址: {{person.address.detailed.value}}</span>
    </template>

    <script>
    import { reactive } from '@vue/reactivity'

    // 1、引入watchEffect函式
    import { watchEffect } from '@vue/runtime-core'

    export default {
      name: 'App',

      setup(){

        let person = reactive({
          name: '紫邪情',
          sex: '女',
          address: {
            detailed: {
              value: '浙江省杭州市'
            }
          }
        })

        // 2、使用watchEffect函式對響應式資料進行智慧監視
        /* 
          1、不需要指名要監視誰
          2、不需要newValue 和 oldValue【 ps:因為都不知道要監視誰 】
        */
        watchEffect( ()=>{
          // 所謂智慧:就體現在這裡面的函式體中
          //          要監視誰,取決於這個函式體裡面用到了誰,那就監視誰

          // 如:要監視person中的name,那就直接寫改寫的程式碼即可,此函式會自動判定,從而監視
          const personName = person.name

          // 如:要監視person中的sex,那就用它就可以了
          const personSex = person.sex

          console.log("watchEffect智慧監視函式被呼叫了");

          // 而此函式體中沒有用到的,那麼:就不會去監視它
        })

        return {
          person,
        }
      }
    }
    </script>




4.9、vue3中的watch函式 和 watchEffect函式的對比

  • watch函式的套路是:既要指明要監視哪個屬性,也有指明監視的回撥
  • watchEffect函式的套路是:不用指明要監視哪個屬性,回撥中用到了哪個屬性,那就監視哪個屬性
  • watchEffect函式和Computed函式有點像:
    • Computed函式注重:計算出來的值 【 ps:回撥函式的返回值 】 ,所以必須寫返回值
    • watchEffect函式注重:過程 【 ps:回撥函式的函式體 】,所以不用寫返回值



4.10、vue3中的生命週期圖

  • 和vue2的生命週期差不多,只是需要注意一些點而已

上面這種圖官網中有




4.10.1、vue3中生命週期的注意項

對比vue2中的生命週期,vue3中改動的地方,如下所示



2、vue3中生命週期的寫法問題

  • 配置項寫法 - 和name、setuo()保持平級,寫法就是:按照官網中說的哪些名字直接寫即可
    <script>

        export default {
          name: 'App',

          setup() {},

          // vue3中的生命週期 - 配置項寫法 【 ps:和name、setup保持平級即可 】
          beforeCreate(){ console.log("------beforeCreate-----"); },
          created(){ console.log("------created-----"); },
          beforeMount(){ console.log("------beforeMount-----"); },
          mounted(){ console.log("------mounted-----"); },
          beforeUpdate(){ console.log("------beforeUpdate-----"); },
          updated(){ console.log("------updated-----"); },
          beforeUnmount(){ console.log("------beforeUnmount-----"); },
          unmounted(){ console.log("------unmounted-----"); },

        }
    </script>



另一種寫法:組合式API寫法 - 萬事引入對應函式嘛 - 不過此種方式名字有點區別

  • beforeCreate ====> setup()
  • created ====> setup()
  • beforeMount ====> onBeforeMount
  • mounted ====> onMounted
  • beforeUpdate ====> onBeforeUpdate
  • updated ====> onUpdated
  • beforeUnMount ====> onBeforeUnMount
  • UnMounted ====> onUnMount
<script>
// 1、引入對應的鉤子函式
import { onBeforeMount, onMounted } from '@vue/runtime-core'

export default {
  name: 'App',

  setup() {

    // 另一種寫法 - 組合式API寫法 - 萬事引入對應的函式嘛
    /* 
      只是注意:setup()就相當於beforeCreate() 和 created()
    */

    // 2、使用對應的鉤子函式
    onBeforeMount( ()=>{
      console.log("------beforeMount-----");
    })

    onMounted( ()=>{
      console.log("------onMounted-----");
    })

    // 其他的都是一樣的,就不寫了,注意名字即可
  },

</script>



需要注意一個點:配置項寫法和組合式API寫法同時存在同一個鉤子函式時

則:setup()中所用的組合式API寫法比配置項寫法優先執行




4.12、vue3中的 toRef 和 toRefs 資料拆分函式

  • 這兩個東西就是為了解決在模板中渲染時稍微方便點而已,因為在模板中使用插值表示式xxx.xxxx.xxx這種取值並不合理,插值表示式的宗旨就是簡單取值嘛,所以通過xxx.xxxx.xxx的方式並不好
  • 當然:toRef和toRefs也不一定能夠完全解決插值表示式的問題【 ps:主要看自己設計 】


1、使用toRef()函式交出單個數據

    <template>
      <h2>姓名: {{person.name}}</h2>
      <h2>性別: {{person.sex}}</h2>
      <h2>地址: {{person.address.value}}</h2>
      <!-- 上面這種方式並不好,簡化 -->
      <br>
      <br>
      <h1>使用toRef和toRefs函式進行簡化</h1> <br>
      <!-- 下面就可以直接簡寫了 -->
      <h2>姓名: {{name}}</h2>
      <h2>性別: {{sex}}</h2>
      <h2>地址: {{address}}</h2>
    </template>

    <script>
    // 1、組合式還是逃不開引入的問題
    import { reactive, toRef } from '@vue/reactivity'


    export default {
      name: 'App',

      setup() {
        let person = reactive({
          name: '紫邪情',
          sex: '女',
          address: {
            value: '浙江杭州'
          }
        })

        return {
          person,
          // 2、使用toRef()函式
          // 使用toRef函式交出單個數據
          /* 
            第一個引數: 交出的資料是哪個物件中的
            第二個引數: 要交出的是物件中的哪個屬性
          */
          name: toRef(person,'name'),
          sex: toRef(person,'sex'),
          // 這裡需要注意一下:要交出的物件裡面又套娃了,那麼:第一個引數需要再進一步
          address: toRef(person.address,'value'),
        }
      },
    }
    </script>



2、使用toRefs()函式

    <template>
      <h2>姓名: {{person.name}}</h2>
      <h2>性別: {{person.sex}}</h2>
      <h2>地址: {{person.address.value}}</h2>
      <!-- 上面這種方式並不好,簡化 -->
      <br>
      <br>
      <h1>使用toRefs函式進行簡化</h1> <br>
      <!-- 下面就可以直接簡寫了 -->
      <h2>姓名: {{name}}</h2>
      <h2>性別: {{sex}}</h2>
      <!-- 但是:美中不足就是,這裡是裡面套的娃,所以還得需要xxx.xxx一下 -->
      <h2>地址: {{address.value}}</h2>
    </template>

    <script>
    // 1、組合式還是逃不開引入的問題
    import { reactive, toRefs } from '@vue/reactivity'


    export default {
      name: 'App',

      setup() {
        let person = reactive({
          name: '紫邪情',
          sex: '女',
          address: {
            value: '浙江杭州'
          }
        })

        return {
          person,
          // 利用toRef()交出資料,需要寫多次toRef,所以還是不喜歡,那就用toRefs()函式
          /* 
            直接說要交出哪個物件即可
            注意點:return{}是一個物件,所以:使用toRefs就是物件中套物件,因此注意寫法
          */
          ...toRefs(person)
        }
      },
    }
    </script>



現在回過來看一下,為什麼通過toRef() 和 toRefs()函式可以做到資料簡化

使用toRef()舉例,去看一下它長什麼樣子? - toRefs()函式是一樣的原理

	console.log( toRef(person,'name') );



4.12.1、vue3的toRef() 和 toRefs()函式總結

  • 作用:建立一個RefImpl引用物件,而此物件的value屬性是指向你要交出資料的那個物件的某個屬性
    • 解讀:建立的這個RefImpl引用物件就相當於中間商,這個中間商代表的就是你要交出去的那個資料 ,注意:是代表啊,意思就相當於是此中間商的引用指向的地址 和 原本你要交出去的物件的某個屬性指向的是同一個地方,因此:此中間商RefImpl可以修改原資料,即:你把資料交出去之後,被修改了,那麼原資料中的資料也會被修改,資料保持同步嘛
  • 語法:const name = toRef(person,'name') 記得先引入對應的函式
  • 應用場景:要將響應式物件中的某個屬性單獨提供給外部使用時就可以用這兩個函式
  • 擴充套件:toRefstoRef功能一致,但:可以批量建立多個RefImpl引用物件
    • toRefs的語法:toRefs(person)