<address id="ttjl9"></address>

      <noframes id="ttjl9"><address id="ttjl9"><nobr id="ttjl9"></nobr></address>
      <form id="ttjl9"></form>
        <em id="ttjl9"><span id="ttjl9"></span></em>
        <address id="ttjl9"></address>

          <noframes id="ttjl9"><form id="ttjl9"></form>

          vue3的基本使用(超詳細)

          2023-1-16    前端達人

          一、初識vue3

          1.vue3簡介

          • 2020年9月18日,vue3發布3.0版本,代號大海賊時代來臨,One Piece
          • 特點:
            • 無需構建步驟,漸進式增強靜態的 HTML
            • 在任何頁面中作為 Web Components 嵌入
            • 單頁應用 (SPA)
            • 全棧 / 服務端渲染 (SSR)
            • Jamstack / 靜態站點生成 (SSG)
            • 開發桌面端、移動端、WebGL,甚至是命令行終端中的界面

          2.Vue3帶來了什么

          • 打包大小減少40%
          • 初次渲染快55%,更新渲染快133%
          • 內存減少54%

          3.分析目錄結構

          • main.js中的引入
          • 在模板中vue3中是可以沒有根標簽了,這也是比較重要的改變
          • 應用實例并不只限于一個。createApp API 允許你在同一個頁面中創建多個共存的 Vue 應用,而且每個應用都擁有自己的用于配置和全局資源的作用域。
          //main.js //引入的不再是Vue構造函數了,引入的是一個名為createApp的工廠函數 import {createApp} from 'vue import App from './App.vue //創建應用實例對象-app(類似于之前vue2中的vm實例,但是app比vm更輕) createApp(APP).mount('#app') //卸載就是unmount,卸載就沒了 //createApp(APP).unmount('#app') //之前我們是這么寫的,在vue3里面這一塊就不支持了,會報錯的,引入不到 import vue from 'vue';  new Vue({ render:(h) => h(App) }).$mount('#app') //多個應用實例 const app1 = createApp({ /* ... */ }) app1.mount('#container-1') const app2 = createApp({ /* ... */ }) app2.mount('#container-2') 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30

          安裝vue3的開發者工具

          • 方式一: 打開chrome應用商店,搜索vue: 里面有個Vue.js devtools,且下面有個角標beta那個就是vue3的開發者工具
          • 方式二: 離線模式下,可以直接將包丟到擴展程序

          二、 常用Composition API(組合式API)

          1. setup函數

          1. 理解:Vue3.0中一個新的額配置項,值為一個函數

          2. 2.setup是所有Composition API(組合api) “表演的舞臺”

          3. 組件中所用到的:數據、方法等等,均要配置在setup中

          4. setup函數的兩種返回值:

            • 若返回一個對象,則對象中的屬性、方法,在模板中均可以直接使用。(重點關注)
            • 若返回一個渲染函數:則可以自定義渲染內容。
          5. 注意點:

            • 盡量不要與Vue2.x配置混用
              • Vue2.x配置(data ,methos, computed…)中訪問到setup中的屬性,方法
              • 但在setup中不能訪問到Vue2.x配置(data.methos,compued…)
              • 如果有重名,setup優先
            • setup不能是一個async函數,因為返回值不再是return的對象,而是promise,模板看不到return對象中的屬性
              在這里插入圖片描述
          import {h} from 'vue' //向下兼容,可以寫入vue2中的data配置項 module default { name: 'App', setup(){ //數據 let name = '張三', let age = 18, //方法 function sayHello(){ console.log(name) }, //f返回一個對象(常用) return { name, age, sayHello } //返回一個函數(渲染函數) //return () => {return h('h1','學習')}  return () => h('h1','學習') } } 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26

          1.1關于單文件組件<script setup></script >

          • 每個 *.vue 文件最多可以包含一個 <script setup>。(不包括一般的 <script>)
          • 這個腳本塊將被預處理為組件的 setup() 函數,這意味著它將為每一個組件實例都執行。<script setup> 中的頂層綁定都將自動暴露給模板。
          • <script setup> 是在單文件組件 (SFC) 中使用組合式 API 的編譯時語法糖。當同時使用 SFC 與組合式 API 時該語法是默認推薦。相比于普通的 <script> 語法,它具有更多優勢:
            • 更少的樣板內容,更簡潔的代碼。
            • 能夠使用純 TypeScript 聲明 props 和自定義事件。這個我下面是有說明的
            • 更好的運行時性能 (其模板會被編譯成同一作用域內的渲染函數,避免了渲染上下文代理對象)。
            • 更好的 IDE 類型推導性能 (減少了語言服務器從代碼中抽取類型的工作)。
          (1)基本語法:
          /* 里面的代碼會被編譯成組件 setup() 函數的內容。
            這意味著與普通的 `<script>` 只在組件被首次引入的時候執行一次不同,
            `<script setup>` 中的代碼會在每次組件實例被創建的時候執行。*/ <script setup> console.log('hello script setup') </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          頂層的綁定會被暴露給模板

          當使用 <script setup> 的時候,任何在 <script setup> 聲明的頂層的綁定 (包括變量,函數聲明,以及 import 導入的內容) 都能在模板中直接使用:

          <script setup> // 變量 const msg = '王二麻子' // 函數 function log() { console.log(msg) } </script> <template> <button @click="log">{{ msg }}</button> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13

          import 導入的內容也會以同樣的方式暴露。這意味著我們可以在模板表達式中直接使用導入的 action 函數,而不需要通過 methods 選項來暴露它:

          <script setup> import { say } from './action' </script> <template> <div>{{ say ('hello') }}</div> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          (2)響應式

          響應式狀態需要明確使用響應式 API 來創建。和 setup() 函數的返回值一樣,ref 在模板中使用的時候會自動解包:

          <script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">{{ count }}</button> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          (3)使用組件:
          • <script setup> 范圍里的值也能被直接作為自定義組件的標簽名使用:
          /**
          *這里 MyComponent 應當被理解為像是在引用一個變量。
          *如果你使用過 JSX,此處的心智模型是類似的。
          *其 kebab-case 格式的 <my-component> 同樣能在模板中使用——不過,
          *強烈建議使用 PascalCase 格式以保持一致性。同時這也有助于區分原生的自定義元素。
          */ <script setup> import MyComponent from './MyComponent.vue' </script> <template> <MyComponent /> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          動態組件
          /**
          *由于組件是通過變量引用而不是基于字符串組件名注冊的,
          *在 <script setup> 中要使用動態組件的時候,應該使用*動態的 :is 來綁定:
          */ <script setup> import Foo from './Foo.vue' import Bar from './Bar.vue' </script> <template> <component :is="Foo" /> <component :is="someCondition ? Foo : Bar" /> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          遞歸組件
          • 一個單文件組件可以通過它的文件名被其自己所引用。例如:名為 FooBar.vue 的組件可以在其模板中用 <FooBar/> 引用它自己。
          • 注意這種方式相比于導入的組件優先級更低。如果有具名的導入和組件自身推導的名字沖突了,可以為導入的組件添加別名:
          import { FooBar as FooBarChild } from './components' 
          
          • 1
          命名空間組件
          • 可以使用帶 . 的組件標簽,例如 <Foo.Bar> 來引用嵌套在對象屬性中的組件。這在需要從單個文件中導入多個組件的時候非常有用:
          <script setup> import * as Form from './form-components' </script> <template> <Form.Input> <Form.Label>label</Form.Label> </Form.Input> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          (4)使用自定義指令:
          • 全局注冊的自定義指令將正常工作。本地的自定義指令在 <script setup> 中不需要顯式注冊,但他們必須遵循 vNameOfDirective 這樣的命名規范:
          <script setup> const vMyDirective = { beforeMount: (el) => { // 在元素上做些操作 } } </script> <template> <h1 v-my-directive>This is a Heading</h1> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 如果指令是從別處導入的,可以通過重命名來使其符合命名規范:
          <script setup> import { myDirective as vMyDirective } from './MyDirective.js' </script> 
          
          • 1
          • 2
          • 3
          (5)defineProps() 和 defineEmits():
          • 為了在聲明 props 和 emits 選項時獲得完整的類型推導支持,我們可以使用 defineProps 和 defineEmits API,它們將自動地在 <script setup> 中可用:
          <script setup> const props = defineProps({ foo: String }) const emit = defineEmits(['change', 'delete']) // setup 代碼 </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • defineProps 和 defineEmits 都是只能在 <script setup> 中使用的編譯器宏。他們不需要導入,且會隨著 <script setup> 的處理過程一同被編譯掉。
          • defineProps 接收與 props 選項相同的值,defineEmits 接收與 emits 選項相同的值。
          • defineProps 和 defineEmits 在選項傳入后,會提供恰當的類型推導。
          • 傳入到 defineProps 和 defineEmits 的選項會從 setup 中提升到模塊的作用域。因此,傳入的選項不能引用在 setup 作用域中聲明的局部變量。這樣做會引起編譯錯誤。但是,它可以引用導入的綁定,因為它們也在模塊作用域內。
          (5)defineExpose:
          • 使用 <script setup> 的組件是默認關閉的——即通過模板引用或者 $parent 鏈獲取到的組件的公開實例,不會暴露任何在 <script setup> 中聲明的綁定。
          //可以通過 defineExpose 編譯器宏來顯式指定在 <script setup> 組件中要暴露出去的屬性: <script setup> import { ref } from 'vue' const a = 1 const b = ref(2) defineExpose({ a, b }) </script> //當父組件通過模板引用的方式獲取到當前組件的實例, //獲取到的實例會像這樣 { a: number, b: number } (ref 會和在普通實例中一樣被自動解包) 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          (6)useSlots() 和 useAttrs():
          • 在 <script setup> 使用 slots 和 attrs 的情況應該是相對來說較為罕見的,因為可以在模板中直接通過 $slots 和 $attrs 來訪問它們。在你的確需要使用它們的罕見場景中,可以分別用 useSlots 和 useAttrs 兩個輔助函數:
          <script setup> import { useSlots, useAttrs } from 'vue' const slots = useSlots() const attrs = useAttrs() </script> //useSlots 和 useAttrs 是真實的運行時函數,它的返回與 setupContext.slots 和 setupContext.attrs 等價。 //它們同樣也能在普通的組合式 API 中使用。 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          (7)與普通的 <script> 一起使用:

          <script setup> 可以和普通的 <script> 一起使用。普通的 <script> 在有這些需要的情況下或許會被使用到:

          • 聲明無法在
          <script> // 普通 <script>, 在模塊作用域下執行 (僅一次) runSideEffectOnce() // 聲明額外的選項 export default { inheritAttrs: false, customOptions: {} } </script> <script setup> // 在 setup() 作用域中執行 (對每個實例皆如此) </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          (8)頂層 await:
          • <script setup> 中可以使用頂層 await。結果代碼會被編譯成 async setup():
          <script setup> const post = await fetch(`/api/post/1`).then((r) => r.json()) </script> // 另外,await 的表達式會自動編譯成在 await 之后保留當前組件實例上下文的格式。 
          
          • 1
          • 2
          • 3
          • 4

          2.ref 函數

          • 作用:定義一個響應式的數據
          • 語法: const xxx = ref(initValue)
            • 創建一個包含響應式數據引用對象(reference對象)
            • JS中操作數據:xxx.value
            • 模板中讀取數據:不需要.value,直接:
              {{xxx}}
          • 備注:
            • 接收的數據可以是:基本類型、也可以是對象類型
            • 基本類型的數據:響應式依然靠的是Object.defineProperty()的get和set完成的
            • 對象類型的數據: 內部”求助“了Vue3.0中的一個新的函數——reactive函數

          3.reactive 函數

          • 作用:定義一個對象類型的響應式數據(基本類型別用他,用ref函數)
          • 語法:const 代理對象 = reactive(被代理對象)接收一個對象(或數組),返回一個代理對象(proxy對象)
          • reactive定義的響應式數據是”深層次的“
          • 內部基于ES6的Proxy實現,通過代理對象操作源對象內部數據進行操作

          4.Vue3.0中響應式原理

          • 先來看一看vue2的響應式原理
            • 對象類型: 通過Object.defineProperty()對屬性的讀取、修改進行攔截(數據劫持)
            • 數組類型:通過重寫更新數組的一系列方法來實現攔截。(對數組的變更方法進行了包裹)
          Object.defineProperty( data, 'count', { get(){}, set(){} }) //模擬實現一下 let person = { name: '張三', age: 15, } let p = {} Object.defineProperty( p, 'name', { configurable: true, //配置這個屬性表示可刪除的,否則delete p.name 是刪除不了的 false get(){ //有人讀取name屬性時調用 return person.name }, set(value){ //有人修改時調用 person.name = value } }) 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 存在問題:
            1. 新增屬性。刪除屬性。界面不會更新
            2. 直接通過下表修改數組,界面不會自動更新
          • vue3的響應式
            • 實現原理:
              • 通過Proxy(代理):攔截對象中任意屬性的變化,包括:屬性值的讀寫、屬性的添加、屬性的刪除等等。
              • 通過Reflect(反射):對被代理對象的屬性進行操作
              • MDN文檔中描述的Proxy與Reflect:可以參考對應的文檔
          //模擬vue3中實現響應式 let person = { name: '張三', age: 15, } //我們管p叫做代理數據,管person叫源數據 const p = new Proxy(person,{ //target代表的是person這個源對象,propName代表讀取或者寫入的屬性名 get(target,propName){ console.log('有人讀取了p上面的propName屬性') return target[propName] }, //不僅僅是修改調用,增加的時候也會調用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}屬性,我要去更新界面了`) target[propName] = value }, deleteProperty(target,propName){ console.log(`有人刪除了p身上的${propName}屬性,我要去更新界面了`) return delete target[propName] } }) //映射到person上了,捕捉到修改,那就是響應式啊 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          //vue3底層源碼不是我們上面寫的那么low,實現原理一樣,但是用了一個新的方式 window.Reflect ![Reflect的寫法](https://img-blog.csdnimg.cn/565f96b1be74435cacbc42e06706791d.png) let obj = { a: 1, b:2, } //傳統的只能通過try catch去捕獲異常,如果使用這種那么底層源碼將會有一堆try catch try{ Object.defineProperty( obj, 'c', { get(){ return 3 }, }) Object.defineProperty( obj, 'c', { get(){ return 4 }, }) } catch(error) { console.log(error) } //新的方式: 通過Reflect反射對象去操作,相對來說要舒服一點,不會要那么多的try catch const x1 = Reflect.defineProperty( obj, 'c', { get(){ return 3 }, }) const x2 = Reflect.defineProperty( obj, 'c', { get(){ return 3 }, }) //x1,和x2是有返回布爾值的 if(x2){ console.log('某某操作成功了') }else { console.log('某某操作失敗了') } 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 所以vue3最終的響應式原理如下:
          let person = { name: '張三', age: 15, } //我們管p叫做代理數據,管person叫源數據 const p = new Proxy(person,{ //target代表的是person這個源對象,propName代表讀取或者寫入的屬性名 get(target,propName){ console.log('有人讀取了p上面的propName屬性') return Reflect.get(target, propName) }, //不僅僅是修改調用,增加的時候也會調用 set(target,propName,value){ console.log(`有人修改了p身上的${propName}屬性,我要去更新界面了`) Reflect.set(target, propName, value) }, deleteProperty(target,propName){ console.log(`有人刪除了p身上的${propName}屬性,我要去更新界面了`) return Reflect.deleteProperty(target,propName) } }) 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21

          5.reactive對比ref

          • 從定義數據角度對比:

            • ref用來定義: 基本數據類型
            • reactive用來定義: 對象(或數組)類型數據
            • 備注: ref也可以用來定義對象(或數組)類型數據,它內部會自動通過reactive轉為代理對象
          • 從原理角度對比:

            • ref通過Object.defineProperty()的get和set來實現響應式(數據劫持)
            • reactive通過Proxy來實現響應式(數據劫持),并通過Reflect操作源對象內部的數據
          • 從使用角度對比:

            • ref定義數據:操作數據需要 .value ,讀取數據時模板中直接讀取不需要 .value
            • reactive 定義的數據: 操作數據和讀取數據均不需要 .value

          5.setup的兩個注意點

          • setup執行的時機
            • 在beforeCreate之前執行一次,this是undefined
            • setup的參數
              • props:值為對象,包含: 組件外部傳遞過來,且組件內部聲明接收了屬性
              • context:上下文對象
                • attrs: 值為對象,包含:組件外部傳遞過來,但沒有在props配置中聲明的屬性,相當于 this.$attrs
                • slots:收到插槽的內容,相當于$slots
                • emit: 分發自定義事件的函數,相當于this.$emit
          //父組件 <script setup> // This starter template is using Vue 3 <script setup> SFCs // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import HelloWorld from './components/test3.vue'; const hello = (val) =>{ console.log('傳遞的參數是:'+ val); } </script> <template> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="傳遞吧" @hello="hello"> <template v-slot:cacao> <span>是插槽嗎</span> </template> <template v-slot:qwe> <span>meiyou</span> </template> </HelloWorld> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          //子組件 export default { name: 'test3', props: ['msg'], emits:['hello'], //這里setup接收兩個參數,一個是props,一個是上下文context setup(props,context){ /**
                   * props就是父組件傳來的值,但是他是Porxy類型的對象
                   * >Proxy:{msg:'傳遞吧'}
                   * 可以當作我們自定義的reactive定義的數據
                   */ /**
                   * context是一個對象 包含以下內容:
                   * 1.emit觸發自定義事件的 
                   * 2.attrs 相當于vue2里面的 $attrs 包含:組件外部傳遞過來,但沒有在props配置中聲明的屬性
                   * 3.slots 相當于vue2里面的 $slots
                   * 3.expose 是一個回調函數
                   */ console.log(context.slots); let person = reactive({ name: '張三', age: 17, }) function changeInfo(){ context.emit('hello', 666) } //返回對象 return { person, changeInfo } //返回渲染函數(了解) 這個h是個函數 //return () => h('name','age') } } </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43

          6.計算屬性與監視

          (1)computed函數
          • 與vue2.x中的寫法一致
          • 需要引入computed
          <template> <h1>一個人的信息</h1> <div> 姓: <input type="text" v-model="person.firstName"> 名:<input type="text" v-model="person.lastName"> <div> <span>簡名:{{person.smallName}}</span> <br> <span>全名:{{person.fullName}}</span> </div> </div> </template> <script> import { computed,reactive } from 'vue' export default { name: 'test4', props: ['msg'], emits:['hello'], setup(){ let person = reactive({ firstName: '張', lastName: '三' }) //簡寫形式 person.smallName = computed(()=>{ return person.firstName + '-' + person.lastName }) //完全形態 person.fullName = computed({ get(){ console.log('調用get'); return person.firstName + '*' + person.lastName }, set(value){ console.log('調用set'); const nameArr = value.split('*') person.firstName = nameArr[0] person.firstName = nameArr[1] }, }) return { person, } }, } </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          (2)watch函數
          • 和computed一樣,需要引入api
          • 有兩個小坑:

          1.監視reactive定義的響應式數據的時候:oldValue無法獲取到正確的值,強制開啟了深度監視(deep配置無效)
          2.監視reactive定義的響應式數據中某個屬性的時候:deep配置有效
          具體請看下面代碼以及注釋

          <template> <h1>當前求和為: {{sum}}</h1> <button @click="sum++">點我+1</button> <hr> <h1>當前信息為: {{msg}}</h1> <button @click="msg+='!' ">修改信息</button> <hr> <h2>姓名: {{person.name}}</h2> <h2>年齡: {{person.age}}</h2> <button @click="person.name += '~' ">修改姓名</button> <button @click="person.age++">增長年齡</button> </template> <script> //使用setup的注意事項 import { watch,ref,reactive } from 'vue' export default { name: 'test5', props: ['msg'], emits:['hello'], setup(){ let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name: '張三', age: 18, job:{ salary: '15k' }, }) //由于這里的this是指的是undefined,所以使用箭頭函數 //情況一:監視ref所定義的一個響應式數據 // watch(sum, (newValue,oldValue)=>{ //     console.log('新的值',newValue); //     console.log('舊的值',oldValue); // }) //情況二:監視ref所定義的多個響應式數據 watch([sum,msg], (newValue,oldValue)=>{ console.log('新的值',newValue); //['sum的newValue', 'msg的newValue'] console.log('舊的值',oldValue); //['sum的oldValue', 'msg的oldValue'] },{immediate: true,deep:true}) //這里vue3的deep是有點小問題的,可以不用deep,(隱式強制deep) //情況三:監視reactive定義的所有響應式數據, //1.此處無法獲取正確的oldValue(newValue與oldValue是一致值),且目前無法解決 //2.強制開啟了深度監視(deep配置無效) /**
                      * 受到碼友熱心評論解釋: 此處附上碼友的解釋供大家參考:
                      * 1. 當你監聽一個響應式對象的時候,這里的newVal和oldVal是一樣的,因為他們是同一個對象【引用地址一樣】,
                      *    即使里面的屬性值會發生變化,但主體對象引用地址不變。這不是一個bug。要想不一樣除非這里把對象都換了
                      * 
                      * 2. 當你監聽一個響應式對象的時候,vue3會隱式的創建一個深層監聽,即對象里只要有變化就會被調用。
                      *    這也解釋了你說的deep配置無效,這里是強制的。
                      */ watch(person, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); }) //情況四:監視reactive對象中某一個屬性的值, //注意: 這里監視某一個屬性的時候可以監聽到oldValue watch(()=>person.name, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); }) //情況五:監視reactive對象中某一些屬性的值 watch([()=>person.name,()=>person.age], (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); }) //特殊情況: 監視reactive響應式數據中深層次的對象,此時deep的配置奏效了 watch(()=>person.job, (newValue,oldValue)=>{ console.log('新的值',newValue); console.log('舊的值',oldValue); },{deep:true}) //此時deep有用 return { sum, msg, person, } }, } </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
          • 76
          • 77
          • 78
          • 79
          • 80
          • 81
          • 82
          • 83
          • 84
          • 85
          • 86
          • 87
          (3)watchEffect函數
          • watch的套路是:既要指明監視的屬性,也要指明監視的回調
          • watchEffect的套路是:不用指明監視哪個屬性,監視的回調中用到哪個屬性,那就監視哪個屬性
          • watchEffect有點像computed:
            • 但computed注重的計算出來的值(回調函數的返回值),所以必須要寫返回值
            • 而watchEffect更注重的是過程(回調函數的函數體),所以不用寫返回值
          <script> //使用setup的注意事項 import { ref,reactive,watchEffect } from 'vue' export default { name: 'test5', props: ['msg'], emits:['hello'], setup(){ let sum = ref(0) let msg = ref('你好啊') let person = reactive({ name: '張三', age: 18, job:{ salary: '15k' }, }) //用處: 如果是比較復雜的業務,發票報銷等,那就不許需要去監聽其他依賴,只要發生變化,立馬重新回調 //注重邏輯過程,你發生改變了我就重新執行回調,不用就不執行,只執行一次 watchEffect(()=>{ //這里面你用到了誰就監視誰,里面就發生回調 const x1 = sum.value
                          console.log('我調用了'); }) return { sum, msg, person, } }, } </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36

          7.生命周期函數

           <template> <h1>生命周期</h1> <p>當前求和為: {{sum}}</p> <button @click="sum++">加一</button> </template> <script> //使用setup的注意事項 import { ref,reactive,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted } from 'vue' export default { name: 'test7', setup(){ let sum = ref(0) //通過組合式API的形式去使用生命周期鉤子 /**
                       * beforeCreate 和  created 這兩個生命周期鉤子就相當于 setup 所以,不需要這兩個
                       * 
                       * beforeMount   ===>  onBeforeMount
                       * mounted       ===>  onMounted
                       * beforeUpdate  ===>  onBeforeUpdate
                       * updated       ===>  onUpdated
                       * beforeUnmount ===>  onBeforeUnmount
                       * unmounted     ===>  onUnmounted
                       */ console.log('---setup---'); onBeforeMount(()=>{ console.log('---onBeforeMount---'); }) onMounted(()=>{ console.log('---onMounted---'); }) onBeforeUpdate(()=>{ console.log('---onBeforeUpdate---'); }) onUpdated(()=>{ console.log('---onUpdated---'); }) onBeforeUnmount(()=>{ console.log('---onBeforeUnmount---'); }) onUnmounted(()=>{ console.log('---onUnmounted---'); }) return { sum } }, //這種是外層的寫法,如果想要使用組合式api的話需要放在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> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
          • 76
          • 77
          • 78

          8.自定義hook函數

          • 什么是hook函數: 本質是一個函數,把setup函數中使用的Composition API進行了封裝
          • 類似于vue2.x中的 mixin
          • 自定義hook的優勢: 復用代碼,讓setup中的邏輯更清楚易懂
          • 使用hook實現鼠標打點”:
            創建文件夾和usePoint.js文件
            在這里插入圖片描述
          //usePoint.js import {reactive,onMounted,onBeforeUnmount } from 'vue' function savePoint(){ //實現鼠標打點的數據 let point = reactive({ x: null, y: null }) //實現鼠標點的方法 const savePoint = (e)=>{ point.x = e.pageX
                   point.y = e.pageY } //實現鼠標打點的生命周期鉤子 onMounted(()=>{ window.addEventListener('click',savePoint) }) onBeforeUnmount(()=>{ window.removeEventListener('click',savePoint) }) return point } export default savePoint 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          //組件test.vue <template> <p>當前求和為: {{sum}} </p> <button @click="sum++">加一</button> <hr> <h2>當前點擊時候的坐標: x: {{point.x}} y:{{point.y}}</h2> </template> <script> import { ref } from 'vue' import usePoint from '../hooks/usePoint' export default { name: 'test8', setup(props,context){ let sum = ref(0) let point = usePoint() return { sum, point } } } </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25

          9.toRef

          • 作用: 創建一個ref對象,其value值指向另一個對象中的某個屬性值
          • 語法: const name = toRef(person, ‘name’)
          • 應用:要將響應式對象中的某個屬性單獨提供給外部使用
          • 擴展: toRefs與toRef功能一致,但是可以批量創建多個ref對象,語法: toRefs(person)
           <template> <h2>姓名: {{name2}}</h2> <h2>年齡: {{person.age}}</h2> <button @click="person.name += '~' ">修改姓名</button> <button @click="person.age++">增長年齡</button> </template> <script> //使用setup的注意事項 import { reactive, toRef, toRefs } from 'vue' export default { name: 'test9', setup(){ let person = reactive({ name: '張三', age: 18, job:{ salary: '15k' }, }) //toRef const name2 = toRef(person,'name') //第一個參數是對象,第二個參數是鍵名 console.log('toRef轉變的是',name2); //ref定義的對象 //toRefs,批量處理對象的所有屬性 //const x  = toRefs(person) //console.log('toRefs轉變的是',x); //是一個對象 return { person, name2, ...toRefs(person) } }, } </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38

          三、TypeScript 與組合式 API

          1.為組件的 props 標注類型

          //場景一: 使用<script setup> <script setup lang="ts"> const props = defineProps({ foo: { type: String, required: true }, bar: Number }) props.foo // string props.bar // number | undefined </script> //也可以將 props 的類型移入一個單獨的接口中 <script setup lang="ts"> interface Props { foo: string
            bar?: number } const props = defineProps<Props>() </script> //場景二: 不使用<script setup> import { defineComponent } from 'vue' export default defineComponent({ props: { message: String }, setup(props) { props.message // <-- 類型:string } }) 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 注意點:為了生成正確的運行時代碼,傳給 defineProps() 的泛型參數必須是以下之一:
          //1.一個類型字面量: defineProps<{ /*... */ }>() //2.對同一個文件中的一個接口或對象類型字面量的引用 interface Props {/* ... */} defineProps<Props>() //3.接口或對象字面類型可以包含從其他文件導入的類型引用,但是,傳遞給 defineProps 的泛型參數本身不能是一個導入的類型: import { Props } from './other-file' // 不支持! defineProps<Props>() 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • Props 解構默認值
          //當使用基于類型的聲明時,失去了對 props 定義默認值的能力。通過目前實驗性的響應性語法糖來解決: <script setup lang="ts"> interface Props { foo: string
            bar?: number } // 對 defineProps() 的響應性解構 // 默認值會被編譯為等價的運行時選項 const { foo, bar = 100 } = defineProps<Props>() </script> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11

          2.為組件的 emits 標注類型

          //場景一: 使用<script setup> <script setup lang="ts"> const emit = defineEmits(['change', 'update']) // 基于類型 const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>() </script> //場景二: 不使用<script setup> import { defineComponent } from 'vue' export default defineComponent({ emits: ['change'], setup(props, { emit }) { emit('change') // <-- 類型檢查 / 自動補全 } }) 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20

          3.為 ref() 標注類型

          import { ref } from 'vue' import type { Ref } from 'vue' //1.ref 會根據初始化時的值推導其類型: // 推導出的類型:Ref<number> const year = ref(2020) // => TS Error: Type 'string' is not assignable to type 'number'. year.value = '2020' //2.指定一個更復雜的類型,可以通過使用 Ref 這個類型: const year: Ref<string | number> = ref('2020') year.value = 2020 // 成功! //3.在調用 ref() 時傳入一個泛型參數,來覆蓋默認的推導行為: // 得到的類型:Ref<string | number> const year = ref<string | number>('2020') year.value = 2020 // 成功! //4.如果你指定了一個泛型參數但沒有給出初始值,那么最后得到的就將是一個包含 undefined 的聯合類型: // 推導得到的類型:Ref<number | undefined> const n = ref<number>() 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20

          4.為reactive() 標注類型

          import { reactive } from 'vue' //1.reactive() 也會隱式地從它的參數中推導類型: // 推導得到的類型:{ title: string } const book = reactive({ title: 'Vue 3 指引' }) //2.要顯式地標注一個 reactive 變量的類型,我們可以使用接口: interface Book { title: string
            year?: number } const book: Book = reactive({ title: 'Vue 3 指引' }) 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11

          5.為 computed() 標注類型

          import { ref, computed } from 'vue' //1.computed() 會自動從其計算函數的返回值上推導出類型: const count = ref(0) // 推導得到的類型:ComputedRef<number> const double = computed(() => count.value * 2) // => TS Error: Property 'split' does not exist on type 'number' const result = double.value.split('') //2.通過泛型參數顯式指定類型: const double = computed<number>(() => { // 若返回值不是 number 類型則會報錯 }) 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14

          6.為事件處理函數標注類型

          //在處理原生 DOM 事件時,應該為我們傳遞給事件處理函數的參數正確地標注類型 <script setup lang="ts"> function handleChange(event) { // 沒有類型標注時 `event` 隱式地標注為 `any` 類型, // 這也會在 tsconfig.json 中配置了 "strict": true 或 "noImplicitAny": true 時報出一個 TS 錯誤。 console.log(event.target.value) } </script> <template> <input type="text" @change="handleChange" /> </template> //因此,建議顯式地為事件處理函數的參數標注類型,需要顯式地強制轉換 event 上的屬性: function handleChange(event: Event) { console.log((event.target as HTMLInputElement).value) } 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17

          7.為 provide / inject 標注類型

          /*
          provide 和 inject 通常會在不同的組件中運行。要正確地為注入的值標記類型,
          Vue 提供了一個 InjectionKey 接口,它是一個繼承自 Symbol 的泛型類型,
          可以用來在提供者和消費者之間同步注入值的類型:
          */ import { provide, inject } from 'vue' import type { InjectionKey } from 'vue' const key = Symbol() as InjectionKey<string> provide(key, 'foo') // 若提供的是非字符串值會導致錯誤 const foo = inject(key) // foo 的類型:string | undefined //建議將注入 key 的類型放在一個單獨的文件中,這樣它就可以被多個組件導入。 //當使用字符串注入 key 時,注入值的類型是 unknown,需要通過泛型參數顯式聲明: const foo = inject<string>('foo') // 類型:string | undefined //注意注入的值仍然可以是 undefined,因為無法保證提供者一定會在運行時 provide 這個值。 //當提供了一個默認值后,這個 undefined 類型就可以被移除: const foo = inject<string>('foo', 'bar') // 類型:string //如果你確定該值將始終被提供,則還可以強制轉換該值: const foo = inject('foo') as string 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24

          8.為模板引用標注類型

          //模板引用需要通過一個顯式指定的泛型參數和一個初始值 null 來創建: <script setup lang="ts"> import { ref, onMounted } from 'vue' const el = ref<HTMLInputElement | null>(null) onMounted(() => { el.value?.focus() }) </script> /**
              注意為了嚴格的類型安全,有必要在訪問 el.value 時使用可選鏈或類型守衛。這是因為直到組件被掛載前,
              這個 ref 的值都是初始的 null,并且在由于 v-if 的行為將引用的元素卸載時也可以被設置為 null。
          */ <template> <input ref="el" /> </template> 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17

          9.為組件模板引用標注類型

          //有時,你可能需要為一個子組件添加一個模板引用,以便調用它公開的方法。舉例來說,我們有一個 MyModal 子組件,它有一個打開模態框的方法 <!-- MyModal.vue --> <script setup lang="ts"> import { ref } from 'vue' const isContentShown = ref(false) const open = () => (isContentShown.value = true) defineExpose({ open }) </script> //為了獲取 MyModal 的類型,我們首先需要通過 typeof 得到其類型,再使用 TypeScript 內置的 InstanceType 工具類型來獲取其實例類型: <!-- App.vue --> <script setup lang="ts"> import MyModal from './MyModal.vue' const modal = ref<InstanceType<typeof MyModal> | null>(null) const openModal = () => { modal.value?.open() } </script> //注意,如果你想在 TypeScript 文件而不是在 Vue SFC 中使用這種技巧,需要開啟 Volar 的Takeover 模式。 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24

          四、Vuex與組合式API

          • 組合式API 可以通過調用 useStore 函數,來在 setup 鉤子函數中訪問 store。這與在組件中使用選項式 API 訪問 this.$store 是等效的。
          import { useStore } from 'vuex' export default { setup () { const store = useStore() } } 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7

          1.訪問 state 和 getter

          • 為了訪問 state 和 getter,需要創建 computed 引用以保留響應性,這與在選項式 API 中創建計算屬性等效。
          import { computed } from 'vue' import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 在 computed 函數中訪問 state count: computed(() => store.state.count), // 在 computed 函數中訪問 getter double: computed(() => store.getters.double) } } } 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16

          2.訪問 Mutation 和 Action

          • 要使用 mutation 和 action 時,只需要在 setup 鉤子函數中調用 commit 和 dispatch 函數。
          import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 使用 mutation increment: () => store.commit('increment'), // 使用 action asyncIncrement: () => store.dispatch('asyncIncrement') } } } 
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15

          藍藍設計建立了UI設計分享群,每天會分享國內外的一些優秀設計,如果有興趣的話,可以進入一起成長學習,請加藍小助,微信號:ben_lanlan,報下信息,藍小助會請您入群。歡迎您加入噢~~希望得到建議咨詢、商務合作,也請與我們聯系01063334945。


          分享此文一切功德,皆悉回向給文章原作者及眾讀者.
          免責聲明:藍藍設計尊重原作者,文章的版權歸原作者。如涉及版權問題,請及時與我們取得聯系,我們立即更正或刪除。


          藍藍設計www.syprn.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 、平面設計服務、UI設計公司、界面設計公司、UI設計服務公司、數據可視化設計公司、UI交互設計公司、高端網站設計公司、UI咨詢、用戶體驗公司、軟件界面設計公司

          日歷

          鏈接

          個人資料

          藍藍設計的小編 http://www.syprn.cn

          存檔

          亚洲va欧美va天堂v国产综合