使用vite創(chuàng)建vue項目
- 什么是vite?
- 新一代前端構(gòu)建工具
- 優(yōu)勢如下:
- 開發(fā)環(huán)境中,無須打包操作,可快速的冷啟動
- 清亮快速的熱重載
- 真正的按需編譯,不在等待整個應用編譯完成
## 創(chuàng)建工程
npm init vite-app <project-name>
## 進入工程目錄
cd <project-name>
## 安裝依賴
npm install
## 運行
npm run dev
- 注意,vite初始化后需要你手動安裝一下node_modules
vue3中的根標簽可以不止一個
vue2就不行
setup
- vue3中一個新的配置項,值為一個函數(shù)
- setup是所有Composition API(組合式API)表演的舞臺
- 組件中所用到的:數(shù)據(jù),方法等等,均要配置在setup
- setup函數(shù)的兩種返回值
- 若返回一個對象,則對象中的屬性,方法在模板中均可以直接使用
- 若返回一個渲染函數(shù),則可以自定義熏染內(nèi)容
- 注意點:
- 盡量不要與vue2.0配置混用
- vue2.0配置(data,methos,computed…)中可以訪問到setup中的屬性,方法
- 但在setpup中不能訪問到vue2配置(data,methods,computed)
- 如果有重名,setup優(yōu)先
- setup不能是一個async函數(shù),因為返回值不再是return對象,而是promise,模板看不到return對象中的屬性
vue3向下兼容
vue3中也可以按照vue2的方式來寫,vue3是向下兼容的
<template>
<div class="index">
index頁面
name:
{{ name }}
age:
{{ age }}
<br>
<router-link to="/test1">test1</router-link>
</div>
</template>
<script>
export default {
name: "Index",
// 測試只是測試函數(shù),并不考慮響應式
setup () {
let name = "張三"
let age = 18
// 方法
function sayHello () {
}
// 返回對象
return {
name,
age,
sayHello
}
}
}
</script>
這里面的returb也可以這樣寫:
<template>
<div class="index">
index頁面
name:
{{ name }}
age:
{{ age }}
<br>
<router-link to="/test1">test1</router-link>
</div>
</template>
<script>
import { h } from "vue"
export default {
name: "Index",
// 測試只是測試函數(shù),并不考慮響應式
setup () {
let name = "張三"
let age = 18
// 方法
function sayHello () {
}
// 返回渲染函數(shù)
// 此時你頁面中寫什么都不重要了,直接渲染下面這里寫的東西
return () => {
return h('h1', '白馬')
}
}
}
</script>
而此時,頁面中的內(nèi)容只剩下return h('h1', '白馬')
這里指定渲染的內(nèi)容了
關于你可以在vue3中按照vue2那樣寫
<template>
<div class="test2">
<button @click="test1">點擊測試</button>
</div>
</template>
<script>
export default {
name: "test2",
data () {
return {
name2:'ls'
}
},
created () {
},
methods: {
test1 () {
console.log(this.name)
console.log(this.name2)
}
},
setup () {
const name = 'zs'
return {
name
}
}
}
</script>
像是上面的代碼,可以訪問到setup中的變量
- 注意,setup不可以使用async修飾
因為如果被async修飾了,那么它的返回值就不再是對象了,而是一個被promise包裹的對象,而你的模板不認識這個返回值
ref
當初在使用vue2的時候,使用ref是為了給元素打標識之類的作用
在vue3中ref是個函數(shù),原來的沒有廢掉
非響應式數(shù)據(jù):
像是下面一樣,定義了非響應式的數(shù)據(jù):
<template>
<div class="ref">
<h5>一個人的信息:</h5>
<h5>姓名:{{ name }}</h5>
<h5>年齡:{{ age }}</h5>
<button @click="changeInfo">修改數(shù)據(jù)</button>
</div>
</template>
<script>
export default {
setup () {
let age = 24
let name = 'zs'
const changeInfo = () => {
name = '李四'
age = 50
console.log(name,age)
}
return {
age,
name,
changeInfo
}
}
}
</script>
這里的按鈕點擊了,在控制臺輸出的值會更改,但是頁面中的值并沒有發(fā)生改變,因為此時他不是響應式數(shù)據(jù)
雖然這里不是響應式數(shù)據(jù)了,但是也沒有一些多余的getter,setter,當你想要使用響應式數(shù)據(jù)的時候,可以進行按需引入ref來使用
例如下面的:
<template>
<div class="ref">
<h5>一個人的信息:</h5>
<h5>姓名:{{ name }}</h5>
<h5>年齡:{{ age }}</h5>
<h5>愛好:{{ hobby }}</h5>
<button @click="changeInfo">修改數(shù)據(jù)</button>
<button @click="changeHobby">修改愛好</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
let age = 24
let name = 'zs'
let hobby = ref('唱跳rap,籃球')
const changeInfo = () => {
name = '李四'
age = 50
console.log(name, age)
}
const changeHobby = () => {
hobby.value = '學習'
}
return {
age,
name,
changeInfo,
hobby,
changeHobby
}
}
}
</script>
此時如果你直接輸出一下hobby這個響應式對象,可以發(fā)現(xiàn)它是:
{
"__v_isShallow": false,
"dep": {
"w": 0,
"n": 0
},
"__v_isRef": true,
"_rawValue": "學習",
"_value": "學習"
}
這樣的
而這里的RefImpl
的意思是引用的實現(xiàn)
標準的稱呼為引用實現(xiàn)對象
,或是引用對象
,簡稱ref對象
在vue3的模板中,解析html部分的{{}}
內(nèi)容時,它會自動.value
ref中的對象會使用proxy來包裝
例如下面的代碼:
setup () {
let job = ref({
salary: 30,
work: 'black man'
})
const getObj = () => {
console.log(job.value)
}
getObj()
}
這里輸出的對象是:
![[Pasted image 20230603141346.png]]
這里的對象包裝方式不同了
像是refimpl的實例對象,是通過getter,setter實現(xiàn)響應式的
ref處理對象的時候,會將里面的對象使用proxy來包裝
對象類型的數(shù)據(jù),內(nèi)部會求助vue3中的一個新函數(shù):reactive
reactive函數(shù)
作用:定義一個對象類型的響應式數(shù)據(jù)(基本類型不要用它,要用ref函數(shù))
語法:const 代理對象 = reactive(源對象)
接收一個對象(或數(shù)據(jù)),返回一個代理對象(proxy對象)
reactive定義的響應式數(shù)據(jù)是深層次的
內(nèi)部基于es6的proxy實現(xiàn),通過代理對象操作源對象內(nèi)部數(shù)據(jù)進行操作
reactive函數(shù)處理不了基本類型
用reactive處理對象要比ref簡單,像是下面這個:
setup () {
let job = ref({
salary: 30,
work: 'black man'
})
let company=reactive({
name:'武漢輕工大學',
year:4,
numbers:9000
})
const getObj = () => {
console.log(job.value)
console.log("company:")
console.log(company)
console.log(company.name)
}
getObj()
}
直接.
屬性名即可,不用在.value之后再.
屬性名
vue3中的響應式原理
vue2的響應式:
實現(xiàn)原理:
- 對象類型:
- 通過
Object.defineProperty
對屬性的讀取,修改進行攔截(數(shù)據(jù)劫持) - 數(shù)組類型:通過重寫更新數(shù)組的一系列方法來實現(xiàn)攔截(對數(shù)組的變更方法進行了包裹)
- 通過
Object.defineProperty(data,'count',{
get(){},
set(){}
})
- 存在問題:
- 新增屬性,刪除屬性,界面不會更新
像是下面的代碼在vue2環(huán)境下跑:
- 新增屬性,刪除屬性,界面不會更新
<template>
<div class="index">
<h5>
測試vue2中為對象新增/刪除屬性是否能被監(jiān)聽到
</h5>
<div class="list-column">
<span>
name:{{ person.name }}
</span>
<span> age:{{ person.age }}
</span>
<span> gender:{{ person.gender }}
</span>
<span> school:{{ person.school }}
</span>
<span v-if="person.sclary">
sclary:{{ person.sclary }}
</span>
</div>
<br> <button @click="delAttr()">刪除school屬性</button>
<button @click="addAttr()">添加sclary屬性</button>
</div>
</template>
<script>
export default {
name: "Index",
data () {
return {
person: {
name: '椎間孔',
age: 22,
gender: '大三',
school: '武漢輕工大學'
}
}
},
methods: {
delAttr () {
delete this.person.school
console.log('刪除之后的對象:')
console.log(this.person)
},
addAttr () {
this.person.sclary = 3000
console.log('添加之后的對象:')
console.log(this.person)
}
}
}
</script>
依次點擊刪除和添加:
![[Pasted image 20230603170440.png]]
對象是發(fā)生了改變,但是頁面中沒有改變
- 監(jiān)聽不到,那么你可以使用
this.$set
來設置:
this.$set(this.person,'sclary','3000')
那么此時添加屬性,頁面上也會有響應的
- 實現(xiàn)同樣的目的也可以使用vue.set:
import Vue from 'vue'
// 使用它之前要引入
Vue.set(this.person, 'sclary', '3000')
- 實現(xiàn)響應式的對象刪除屬性:
Vue.set(this.person, 'sclary', '3000')
// 下面這個:
this.$delete(this.person, 'school')
// 或者:
Vue.delete(this.person,'school')
- 對象中的數(shù)組實現(xiàn)響應式:
<script>
import Vue from 'vue'
export default {
name: "Index",
data () {
return {
person: {
name: '椎間孔',
age: 22,
gender: '大三',
school: '武漢輕工大學',
hobby: ['唱', '跳', 'tap', '籃球']
}
}
},
methods: {
change () {
// 下面兩種方式都可以
// this.$set(this.person.hobby, 0, '逛街')
// Vue.set(this.person.hobby, 0, '洗黑錢')
// 當然你也可以使用數(shù)組的方法來修改:
this.person.hobby.splice(0,1,'洗錢')
}
}
}
</script>
vue3的響應式:
實現(xiàn)原理:
- 通過proxy(代理):攔截對象中任意屬性的變化,包括:屬性值的讀寫,屬性的添加,屬性的刪除等
- 通過Reflect(反射):對被代理對象的屬性進行操作
- MDN文檔中描述的Proxy與Reflect:
實現(xiàn)一個proxy的響應式對象:
const person = {
name: 'zs',
age: 18
}
const p = new Proxy(person, {
get (target, propName) {
console.log('有人讀取了')
return target[propName]
},
set (target, propName, value) {
console.log(`將${ propName }修改為${ value }`)
return target[propName] = value
}
})
在控制臺嘗試使用:
![[Pasted image 20230604000442.png]]
![[Pasted image 20230604000624.png]]
但是像上面的代碼,捕獲到了他的更新和讀取,并沒有捕獲到它的刪除和新增屬性
其實proxy也可以監(jiān)聽刪除:
const person = {
name: 'zs',
age: 18
}
const p = new Proxy(person, {
get (target, propName) {
console.log('有人讀取了')
return target[propName]
},
set (target, propName, value) {
console.log(`將${ propName }修改為${ value }`)
target[propName] = value
},
deleteProperty (target, p) {
console.log(`刪除屬性${p}`)
delete target[p]
}
})
![[Pasted image 20230604002915.png]]
要注意,delete target[p]
這里的刪除是有返回值的,其實把這里的刪除返回即可:
deleteProperty (target, p) {
console.log(`刪除屬性${p}`)
return delete target[p]
}
關于proxy的增加屬性:
-
get
是有人讀取某個屬性時會被調(diào)用 -
set
有人修改屬性,或追加
屬性時會被調(diào)用 -
deleteProperty
刪除屬性時調(diào)用
雖然proxy可以監(jiān)聽到增刪改查,但是vue3不是這樣做的
![[Pasted image 20230604003421.png]]
使用兩種方式實現(xiàn)對象的映射
let obj = { a: 1, b: 2 }
/* console.log('使用Object.defineProperties來實現(xiàn)映射:')
Object.defineProperties(obj, 'c', { get () { return 3 } }) Object.defineProperties(obj, 'c', { get () { return 4 } })*/
console.log('使用reflect來實現(xiàn)映射')
const x1 = Reflect.defineProperty(obj, 'c', {
get () {
return 3
}
})
console.log(x1)
const x2 = Reflect.defineProperty(obj, 'c', {
get () {
return 4
}
})
console.log(x2)
第一種方式使用Object.defineProperties
重復覆蓋屬性會報錯,此時可以使用try,catrch來捕獲,但是為了程序的壯行這樣是否有些麻煩了
所以可以使用Reflect.defineProperty
來進行映射,它重復覆蓋并不會出錯,而且在每一次覆蓋是都會有一個為boolean的返回值
vue3中響應式對象的雛形:
let person = {
name: 'zs',
age: 24,
gender: '大三',
school: '武漢輕工大學'
}
const p = new Proxy(person, {
get (target, propName) {
console.log(`有人獲取了${ propName }`)
return Reflect.get(target, propName)
},
set (target, propName, value) {
console.log(`有人修改了${ propName }`)
Reflect.set(target, propName, value)
},
deleteProperty (target, propName) {
console.log(`有人刪除了p身上的${ propName }`)
return Reflect.deleteProperty(target, propName)
}})
reactive對比ref
-
從定義數(shù)據(jù)角度對比:
ref用來定義:基本類型數(shù)據(jù)
reactive用來定義:對象(或數(shù)組)類型數(shù)據(jù)
備注:ref也可以用來定義對象(或數(shù)組)類型數(shù)據(jù),它內(nèi)部會自動通過reactive
轉(zhuǎn)為代理對象 -
從原理角度對比:
ref通過Object.defineProperty()
的get
與set
來實現(xiàn)響應式(數(shù)據(jù)劫持)
reactive通過使用Proxy來實現(xiàn)響應式(數(shù)據(jù)劫持),并通過Reflect操作源對象內(nèi)部的數(shù)據(jù) -
從使用角度對比:
ref定義的數(shù)據(jù):操作數(shù)據(jù)需要.value
,讀取數(shù)據(jù)時,模板中直接讀取不需要.value
reactive定義的數(shù)據(jù):操作數(shù)據(jù)與讀取數(shù)據(jù):均不需要.value
關于vue2中的props
如果你像是在下面的子組件中不寫props:
父組件:
<template>
<div class="index">
<chindren msg="你好" about="關于信息"></chindren>
</div>
</template>
<script>
import Vue from 'vue'
import Chindren from '@/components/chindren.vue'
export default {
name: "Index",
components: { Chindren },
}
}
</script>
子組件:
<template>
<div class="lim">
</div></template>
<script>
export default {
name: "chindren",
mounted () {
console.log(this)
}
}
</script>
注意,此時在子組件的props中是沒有定義的
,但是在頁面的控制臺可以看到輸出的this:
![[Pasted image 20230605234406.png]]
在vm的$attrs
上掛載了傳入的值
到這里你可能想著,不定義props,直接傳過來多方便,
但是,如果不定義props的話,你是無法限制傳入的類型
關于vue2的slot
在子組件中不去定義slot:
父組件:
<chindren msg="你好" about="關于信息">
<h5>這是一個slot的內(nèi)容</h5>
</chindren>
子組件:
<template>
<div class="lim">
</div>
</template>
<script>
export default {
name: "chindren",
mounted () {
console.log(this)
}
}
</script>
控制臺中:
![[Pasted image 20230605234843.png]]
slot是存在的
使用多個插槽:
父組件:
<chindren msg="你好" about="關于信息">
<template slot="name">
<h5>張三</h5>
</template>
<template slot="gender">
<h5>gender:5</h5>
</template>
</chindren>
子組件:
<template>
<div class="lim">
<slot name="name"></slot>
<slot name="gender"></slot>
</div>
</template>
<script>
export default {
name: "chindren",
mounted () {
console.log(this)
}
}
</script>
setup的兩個注意點:
-
setup的執(zhí)行時機:
在beforeCreate之前執(zhí)行一次,this是undefined -
setup的參數(shù)
props:值為對象,包含:組件外部傳遞過來,且組件內(nèi)部聲明接收了的屬性
context:上下文對象attrs:值為對象,包含:組件外部傳遞過來,但沒有在props配置中聲明的屬性,相當于this. a t t r s s l o t s : 收到的插槽內(nèi)容 , 相當于 t h i s . attrs slots:收到的插槽內(nèi)容,相當于this. attrsslots:收到的插槽內(nèi)容,相當于this.slota
emit:分發(fā)自定義事件的函數(shù),相當于this.$emit
例如在下面的例子中,父組件向子組件傳值,但是子組件并沒有定義props:
父組件:
<template>
<div class="lim">
你好
<demo msg="來自setup的信息" index="20"></demo>
</div>
</template>
<script>
import Demo from './demo.vue'
export default {
name: "setup",
components: { Demo }
}
</script>
子組件:
<template>
<div>
子組件的內(nèi)容
</div>
</template>
<script>
export default {
name: "demo",
setup (props) {
console.log('setup props:')
console.log(props)
}
}
</script>
控制臺輸出:
![[Pasted image 20230611142633.png]]
如果此時在子組件中定義了props:
<template>
<div>
子組件的內(nèi)容
</div>
</template>
<script>
export default {
name: "demo",
props:['msg','index'],
setup (props) {
console.log('setup props:')
console.log(props)
}
}
</script>
此時setup中接收的第一個參數(shù)輸出:
![[Pasted image 20230611143321.png]]
注意,此時它也是響應式對象
那么關于傳入的其他形參:
父組件:
<template>
<div class="lim">
你好
<demo @click="hello" msg="來自setup的信息" index="20">
<slot>這是第一個slot</slot>
<template slot="qwe">
自定義插槽
</template>
<template v-slot:two>
自定義插槽的第二個插槽
</template>
</demo>
</div>
</template>
<script>
import Demo from './demo.vue'
export default {
name: "setup",
components: { Demo },
setup () {
const hello = () => {
console.log('hello world')
}
return {
hello
}
}
}
</script>
子組件:
<template>
<div>
子組件的內(nèi)容
<button @click="log">測試觸發(fā)子組件事件</button>
</div>
</template>
<script>
export default {
name: "demo",
props: ['msg', 'index'],
emits: ['hello'],
setup (props, context) {
console.log('setup props:')
console.log(props)
console.log('context.attrs:')
// vue2中的$attrs
console.log(context.attrs)
console.log('context.emit:')
// 插槽
console.log('context.slots:')
console.log(context.slots)
const log = () => {
console.log('觸發(fā)了子組件的click')
}
return {
log
}
},
}
</script>
在控制臺中查看:
![[Pasted image 20230611144611.png]]
計算屬性
<template>
<div class="computed">
<div class="inputLim">
性:
<el-input v-model="lastName" placeholder="Please input"/>
</div>
<br> <div class="inputLim">
名:
<el-input v-model="firstName" placeholder="Please input"/>
</div>
<br>
<div class="inputLim">
名字:
<el-input v-model="fullName" placeholder="Please input"/>
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
const lastName = ref('')
const firstName = ref('')
// 簡寫,沒有考慮計算屬性被修改的情況
/*const fullName = computed(() => {
return lastName.value + ' ' + firstName.value})*/
// 完整寫法
const fullName = computed({
get () {
return lastName.value + ' ' + firstName.value
},
set (val) {
console.log(val)
}})
</script>
<style scoped lang="less">
.computed {
display: flex;
flex-direction: row;
.inputLim {
display: flex;
flex-direction: row;
}
}
</style>
![[Pasted image 20230611162743.png]]
watch屬性
一個簡單的使用:
<template>
<div class="watch">
<h2>sum:{{ sum }}</h2>
<button @click="sum++">add</button>
<br> <h2>當前的信息為:{{ msg }}</h2>
<button @click="msg+='!'">msg改變</button>
<br> <h2> 姓名:{{ person.name }}
年齡:{{ person.age }}
學校:{{ person.school }}
薪資:{{ person.job.salary }}
</h2>
<el-text> 注意在vue3中只要對象使用active包裹,那么不管多深,都是可以監(jiān)聽到的
</el-text>
<div class="btns">
<el-button @click="person.name+='~'">修改名字</el-button>
<el-button @click="person.age++">修改年齡</el-button>
<el-button @click="person.school+='school'">修改學校</el-button>
<el-button @click="person.job.salary++">薪資增加</el-button>
</div>
</div>
</template>
<script setup>
import { reactive, ref, watch } from 'vue'
let sum = ref(0)
let msg = ref("你好")
let person = reactive({
name: 'zs',
age: 24,
school: '武漢',
job: {
salary: '3000'
}
})
// 監(jiān)視ref所定義的數(shù)據(jù)
watch(sum, (newVal) => {
console.log('sum發(fā)生改變')
})
// 監(jiān)視ref所定義的多個數(shù)據(jù)
watch([sum, msg], (newVal, oldVal) => {
console.log(newVal, oldVal)
})
// 配置一上來就監(jiān)聽
watch(sum, (newVal) => {
console.log(newVal)
}, { immediate: true })
// 監(jiān)聽reactive 定義的數(shù)據(jù),
// 如果監(jiān)聽使用reactive 定義的數(shù)據(jù),那么在獲取oldVal上會有問題,無法正確地獲取oldVal,目前無法解決
// 但如果你使用ref來定義一個對象,它走的還是reactive的邏輯,也是行不通的
watch(person, (newVal, oldVal) => {
console.log("newVal:")
console.log(newVal)
console.log("oldVal:")
console.log(oldVal)
})
</script>
vue3中無法正確監(jiān)聽到對象中watch函數(shù)中的oldVal
像是上面代碼中,監(jiān)聽persojn的修改中,修改學校字段,此時在控制臺查看:
![[Pasted image 20230612232234.png]]
注意,不論你是用ref還是reactive包裹對象,他都是無法監(jiān)聽到oldVal的,這是目前vue3中的問題
當你使用ref包裹對象時,它會自動使用reactive來包裹對象的
監(jiān)聽多個
let sum = ref(0)
let msg = ref("你好")
watch([sum, msg], (newVal, oldVal) => {
console.log(newVal, oldVal)
})
在vue2中watch為配置對象,只能調(diào)用一次,而vue3中 ,watch為函數(shù),可以調(diào)用多次
監(jiān)聽中的配置項
一上來就監(jiān)聽一次:
watch(sum, (newVal) => {
console.log(newVal)
}, { immediate: true })
vue3中可以監(jiān)聽嵌套很深的對象
強制開啟了deep,你關閉了也沒用文章來源:http://www.zghlxwxcb.cn/news/detail-480886.html
監(jiān)聽對象中的某一屬性
let person = reactive({
name: 'zs',
age: 24,
school: '武漢',
job: {
salary: '3000'
}
})
watch(() => person.name, (newVal, oldVal) => {
console.log('值發(fā)生改變了')
console.log(newVal)
})
![[Pasted image 20230612235713.png]]
此時你修改其它字段是不會被監(jiān)聽到文章來源地址http://www.zghlxwxcb.cn/news/detail-480886.html
到了這里,關于vue3對比vue2的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!