vue3 技術整理(基礎)


0、前言

vue2基礎篇鏈接:https://www.cnblogs.com/xiegongzi/p/15782921.html

vue2組件化開發篇鏈接:https://www.cnblogs.com/xiegongzi/p/15823605.html

vue2高級篇鏈接:https://www.cnblogs.com/xiegongzi/p/15875808.html


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

image

image

image

image


1.2、使用vite創建

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

image

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

vite的優勢:

  • 開發環境中,無需打包操作,可快速的冷啟動
  • 輕量快速的熱重載( HMR ) ps:webpack也可以熱部署,只是vite更輕量和快
    • 快體現在熱部署的方式不同 ps:官網中有對比圖

image

  • 真正的按需編譯,無需等待整個應用編譯完成再啟動【 ps:圖中我解讀的哪里,是根據路由再找模塊 】

整理vite創建vue3的相關指令

創建項目
npm init vite-app 項目名

進入項目目錄
cd 項目名

安裝依賴
npm install

運行項目
npm run dev

image

image

image

image


2、分析vue-cli創建的vue3項目

使用vscode打開vue-cli創建的項目

2.1、查看整個目錄結構

image

image


2.2、分析入口文件main.js

image

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


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

image


3、安裝vue3的開發者工具

注意:這里安裝的測試版(beta)

直接使用google進行安裝即可,要是無法進入google應用商店的話,那么:去百度chrome插件網就可以了;或者:下載一個佛跳牆 / 極光
ps:佛跳牆現在的名字貌似改為極光了,下載安裝之后,直接鏈接,然后就可以FQ了,當然還有其他更好的F牆方式


注意: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>

image


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

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

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

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

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

image

image


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

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

image

image

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

但是:說過不建議vue3和vue2混合使用。來演示一下bug

image

image

注意:反過來就可以啊,即:在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>

image

沒實現出來,原因就是: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>

image

既然知道了ref函數的真身,那么:想要實現數據的改變就變得輕松了

image

image


有個注意點

image


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>

image

既然知道了ref函數中套了對象的樣子長什么樣的,那么:想要修改ref里面套的對象的屬性就很好操作了


4.2.2.2、修改ref函數中對象的屬性值

image

image


小小總結一下

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>

image


了解reactive的細節問題

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

image

image

reacitve()函數托管數組類型

image

image

想讓reactive()函數也能夠托管基本類型的數據,怎么辦?

  • 把基本類型 使用 對象寫法嘛,包裝一下唄

image

image

reactive()函數深度監視效果

image

image


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長什么樣子

image

image


使用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>

image


4.4.2、Reflect數據監視原理

在vue3中數據監視不止用了window的Proxy對象,還用了window的Reflect對象

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


認識Reflect對象

看看Reflect長什么樣

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

image

經過上圖的查看之后,其實也就知道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');

image

注意:使用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對象說明的官網鏈接

Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect


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>

image


監視多個屬性:監視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>

image

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

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

image

image


監視reactive托管的一個響應式數據中的某一個屬性

// 類型二、監視reactive托管的一個響應式數據中的某一個屬性

/*
  奇葩的地方:
      1、要監視的這個屬性需要寫成函數式 ()=> person.name
      2、可以爭取獲取newValue、oldValue
*/
watch(()=> person.name , (newValue,oldValue)=>{
  console.log("person中的name屬性被修改了",newValue,oldValue);
})

image


監視reactive托管的一個響應式數據中的某些屬性 - 函數式數組寫法

// 類型三、監視reactive托管的一個響應式數據中的某些屬性

/*
  奇葩的地方:
      1、監視的多個屬性需要使用數組套起來
      2、數組中的每一個屬性需要寫成函數式
*/
watch([()=> person.name , ()=> person.sex] , (newValue,oldValue)=>{
  console.log("person中的name和sex屬性被修改了",newValue,oldValue);
})

image


特殊情況:監視reactive托管的一個響應式數據中的某一個屬性【 ps:此屬性套娃了,又是一個對象 】

// 類型四、監視reactive托管的一個響應式數據中的某個屬性,但:此屬性又套娃了

/*
  奇葩的地方:
      1、需要開啟深度監視 即:deep:true 又生效了
      2、不加 deep:true配置,代碼會無效
*/
watch(()=> person.address , (newValue,oldValue)=>{
  console.log("person中的address屬性被修改了",newValue,oldValue);
},{deep:true})

image

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

image

image


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>

image

image

image

image


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

watch函數的套路是:既要指明要監視哪個屬性,也有指明監視的回調

watchEffect函數的套路是:不用指明要監視哪個屬性,回調中用到了哪個屬性,那就監視哪個屬性

watchEffect函數和Computed函數有點像:

  • Computed函數注重:計算出來的值 ps:回調函數的返回值。所以必須寫返回值
  • watchEffect函數注重:過程 ps:回調函數的函數體。所以不用寫返回值

4.10、vue3中的生命周期圖

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

image

上面這張圖官網中有

image


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

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

image

image


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>

image


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>

image


現在回過來看一下,為什么通過toRef() 和 toRefs()函數可以做到數據簡化

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

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

image


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

作用:創建一個RefImpl引用對象,而此對象的value屬性是指向你要交出數據的那個對象的某個屬性

  • 解讀:創建的這個RefImpl引用對象就相當於中間商,這個中間商代表的就是你要交出去的那個數據 ,注意:是代表啊,意思就相當於是此中間商的引用指向的地址 和 原本你要交出去的對象的某個屬性指向的是同一個地方,因此:此中間商RefImpl可以修改原數據,即:你把數據交出去之后,被修改了,那么原數據中的數據也會被修改,數據保持同步嘛

語法:const name = toRef(person,'name') 記得先引入對應的函數

應用場景:要將響應式對象中的某個屬性單獨提供給外部使用時就可以用這兩個函數

擴展:toRefstoRef功能一致,但:可以批量創建多個RefImpl引用對象

  • toRefs的語法:toRefs(person)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM