完整原文地址見簡(jiǎn)書
更多完整Vue筆記目錄敬請(qǐng)見《前端 Web 筆記 匯總目錄(Updating)》
本文內(nèi)容提要
- Composition API 的作用
- setup函數(shù)
- 例程露戒,打印查看setup內(nèi)容
- 非響應(yīng)引用的案例
ref()
概念椒功、原理 與 實(shí)戰(zhàn)reactive()
概念捶箱、原理 與 實(shí)戰(zhàn)- 使用
readonly
限制對(duì)象的訪問權(quán)限- 使用
toRefs()
對(duì)reactive
對(duì)象進(jìn)一步封裝- 多個(gè)屬性進(jìn)行解構(gòu)
- 多個(gè)屬性 配合toRefs() 進(jìn)行解構(gòu)
- toRefs()無法處理 undefined的鍵值
- 使用toRef()應(yīng)對(duì)上述問題
- 關(guān)于setup函數(shù)的三個(gè)參數(shù)【attrs、slots动漾、emit】
- 回顧 沒有 CompositionAPI時(shí)丁屎,emit的用法
- 使用setup的 context.emit 替代 this.$emit
- 使用Composition API開發(fā) todoList
- 完善toDoList案例
- 優(yōu)化上例的邏輯結(jié)構(gòu)!
- setup的 computed 計(jì)算屬性
- 當(dāng)然以上是computed 的默認(rèn)用法旱眯,實(shí)際上它可以接收一個(gè)對(duì)象
- 將上例的處理值換成 Object類型悦屏,再例
- setup 中的 watch 監(jiān)聽
- setup 中的 watch 監(jiān)聽:監(jiān)聽Object類型
- setup 中的 watch 監(jiān)聽:監(jiān)聽Object類型的 多個(gè)屬性
- setup 中的 watchEffect監(jiān)聽 以及 與 watch 的異同比較
- 兩者都可以用以下的方式,在一個(gè)設(shè)定的時(shí)延之后键思,停止監(jiān)聽
- 為 watch 配置 immediate屬性础爬,可使具備同watchEffect的 即時(shí)性
- setup 中的 生命周期
- setup中的provide、inject用法
- 配合上ref實(shí)現(xiàn) 響應(yīng)特性 以及 readonly實(shí)現(xiàn) 單向數(shù)據(jù)流
- setup結(jié)合ref指令
Composition API 的作用
使得相同的吼鳞、相關(guān)的功能代碼 可以比較 完整地聚合起來看蚜,
提高可維護(hù)性、可讀性赔桌,提高開發(fā)效率供炎;
規(guī)避 同一個(gè)功能的代碼,
卻散落在 組件定義中的data疾党、methods音诫、computed、directives雪位、template竭钝、mixin
等各處 的問題;
setup函數(shù)
--- Composition API 所有代碼編寫之前雹洗,
都要 建立在setup函數(shù) 之上香罐;
--- 在created 組件實(shí)例 被完全初始化之前 回調(diào);
(所以注意在setup
函數(shù)中时肿,
使用與this
相關(guān)的調(diào)用是沒有用的)
--- setup函數(shù)中的內(nèi)容庇茫,
可以在 該組件的 模板template
中直接使用;
(如下例程)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
setup(props, context) {
return {
name: 'zhao',
handleClick: () => {
alert(666);
}
}
}
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果:例程螃成,打印查看setup內(nèi)容
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
console.log(this.$options);
}
},
mounted() {
this.test();
},
setup(props, context) {
return {
name: 'zhao',
handleClick: () => {
alert(666);
}
}
}
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果:由于調(diào)用時(shí)序的關(guān)系旦签,setup中 無法調(diào)用this等相關(guān) 如變量、methods中 等 其他內(nèi)容寸宏,但是其他內(nèi)容 卻可以調(diào)用 setup函數(shù)D拧!【setup生時(shí)眾為生击吱,眾生時(shí)setup已生】
<script>
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
console.log(this.$options.setup());
}
},
mounted() {
this.test();
},
setup(props, context) {
return {
name: 'zhao',
handleClick: () => {
alert(666);
}
}
}
});
const vm = app.mount('#heheApp');
</script>
非響應(yīng)引用的案例
如下淋淀,這樣沒有使用ref
/reactive
的寫法,是不會(huì)有響應(yīng)的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
let name = 'guan';
setTimeout(() => {
name = 'zhao';
}, 2000);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
如下,運(yùn)行之后朵纷,兩秒延時(shí)之后炭臭,DOM文本展示并不會(huì)自動(dòng)改成zhao
,而是一直展示初始化的guan
:
ref()
概念袍辞、原理 與 實(shí)戰(zhàn)
使用
ref
可以 用于處理基礎(chǔ)類型的數(shù)據(jù)
鞋仍,賦能響應(yīng)式
;
原理:通過 proxy 將數(shù)據(jù)
封裝成
類似proxy({value: '【變量值】'})
這樣的一個(gè)響應(yīng)式引用
搅吁,
當(dāng)數(shù)據(jù)
變化時(shí)威创,就會(huì) 觸發(fā)template
等相關(guān)UI的更新
【賦予 非data中定義的變量 以響應(yīng)式
的能力 ——
原先,我們是借助Vue的data函數(shù)
谎懦,完成響應(yīng)式變量
的定義的肚豺;
有了ref
之后,我們可以不借助data
中的定義界拦,
而直接在普通的函數(shù)
中對(duì)js變量
做proxy
封裝吸申,
就可以對(duì)普通的js引用
賦能響應(yīng)式
了】;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { ref } = Vue;
let name = ref('guan');
setTimeout(() => {
name.value = 'zhao';
}, 2000);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果:
兩秒后自動(dòng)變化:
reactive()
概念享甸、原理 與 實(shí)戰(zhàn)
使用
reactive()
用于處理非基礎(chǔ)類型的數(shù)據(jù)(如Object截碴、Array)
,賦能響應(yīng)式
蛉威;
原理類似ref()
日丹,只是處理的數(shù)據(jù)格式不同而已;
如下蚯嫌,普通的Object類型
是沒有響應(yīng)式
的效果的:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
setup(props, context) {
// const { ref } = Vue;
const nameObj = { name: 'guan'};
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
return { nameObj }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果哲虾,兩秒后無反應(yīng):使用reactive()
處理Object類型
后,具備響應(yīng)式
的能力:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj.name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
return { nameObj }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果:
兩秒后自動(dòng)變化:
使用reactive()
處理Array類型
數(shù)據(jù)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj[0]}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive([123, 99, 567]);
setTimeout(() => {
nameObj[0] = 666;
}, 2000);
return { nameObj }
}
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果:
兩秒后自動(dòng)變化:
使用readonly
限制對(duì)象的訪問權(quán)限
使用readonly()
封裝對(duì)象齐帚,可以限制對(duì)象為只讀權(quán)限
妒牙;
<script>
const app = Vue.createApp({
template: `
<div>{{nameObj[0]}}</div>
<div>{{copyNameObj[0]}}</div>
`,
setup(props, context) {
const { reactive, readonly } = Vue;
const nameObj = reactive([123, 99, 567]);
const copyNameObj = readonly(nameObj);//----
setTimeout(() => {
nameObj[0] = 666;
copyNameObj[0] = 666;//----
}, 2000);
return { nameObj, copyNameObj }//----
}
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行兩秒之后會(huì)有相應(yīng)的報(bào)錯(cuò):
錯(cuò)誤的解構(gòu)案例
如下的解構(gòu)是行不通的,
const { name } = nameObj;
只能拿到nameObj
的值对妄,拿不到proxy
對(duì)象;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const { name } = nameObj;
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
注意解構(gòu)技巧
賦值時(shí) 加上{}
敢朱,會(huì) 直取 其 賦值右側(cè) Object的值剪菱;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const { name } = nameObj;
console.log(name);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
直接賦值,便是直接賦值一份引用拴签;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const name = nameObj;
console.log(name);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
使用toRefs()
對(duì)reactive
對(duì)象進(jìn)一步封裝
---
toRefs() expects a reactive object
孝常;
首先,toRefs()
需要接受一個(gè)reactive
對(duì)象蚓哩;
即构灸,傳給toRefs()
之前,需要先用reactive()
進(jìn)行封裝岸梨;
--- 使用toRefs()
對(duì)reactive
封裝的對(duì)象 進(jìn)一步封裝喜颁,
便可以順利解構(gòu)稠氮;
--- 原理:toRefs()
將類似proxy({name:'guan'})
的結(jié)構(gòu),
轉(zhuǎn)化成類似{ name: proxy( {value:'guan'}) }
的結(jié)構(gòu)半开,
這里可以看到name
鍵的值隔披,其實(shí)就類似于ref
的處理結(jié)果;
然后使用const { name }
對(duì){ name: proxy( {value:'guan'}) }
進(jìn)行解構(gòu)賦值寂拆,
左側(cè)name
變量 拿到的就是proxy( {value:'guan'})
這一部分的值奢米,
所以放入DOM節(jié)點(diǎn)展示時(shí)候,
直接使用name
即可纠永;
--- w蕹ぁ!尝江!注意:
類似reactive()
的處理結(jié)果涉波,
即proxy({name:'guan'})
的結(jié)構(gòu),
放入DOM節(jié)點(diǎn)展示時(shí)候茂装,需要使用nameObj.name
的格式怠蹂;
而類似ref()
的處理結(jié)果,
即proxy( {value:'guan'})
的結(jié)構(gòu)少态,
放入DOM節(jié)點(diǎn)展示時(shí)候城侧,直接使用nameObj
的格式即可;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const nameObj = reactive({ name: 'guan'});
setTimeout(() => {
nameObj.name = 'zhao';
}, 2000);
const { name } = toRefs(nameObj);
console.log(name);
return { name }
}
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行兩秒后彼妻,自動(dòng)更新UI:
多個(gè)屬性進(jìn)行解構(gòu)
注意多個(gè)屬性解構(gòu)時(shí)的寫法 以及
return時(shí)的寫法嫌佑;
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
<div>{{age}}</div>
<div>{{address}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const nameObj = reactive({ name: 'guan', age: 22, address:'chaozhou'});
setTimeout(() => {
nameObj.name = 'zhao';
nameObj.age = 66;
nameObj.address = 'guangzhou';
}, 2000);
const { name, age, address } = (nameObj);
console.log(name, age, address);
return { name, age, address }
}
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行結(jié)果(當(dāng)然不會(huì)自動(dòng)更新):
多個(gè)屬性 配合toRefs() 進(jìn)行解構(gòu)
<script>
const app = Vue.createApp({
template: `
<div>{{name}}</div>
<div>{{age}}</div>
<div>{{address}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const nameObj = reactive({ name: 'guan', age: 22, address:'chaozhou'});
setTimeout(() => {
nameObj.name = 'zhao';
nameObj.age = 66;
nameObj.address = 'guangzhou';
}, 2000);
const { name, age, address } = toRefs(nameObj);
console.log(name, age, address);
return { name, age, address }
}
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行結(jié)果:
toRefs()無法處理 undefined的鍵值
如果意圖解構(gòu)的鍵,
不存在于toRefs()
封裝的對(duì)象中侨歉,
使用時(shí)會(huì)報(bào)錯(cuò):
<script>
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
setup(props, context) {
const { reactive, toRefs } = Vue;
const data = reactive({ name: 'guan'});
const { age } = toRefs(data);
setTimeout(() => {
age.value = 'zhao';
}, 2000);
return { age }
}
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行結(jié)果:使用toRef()應(yīng)對(duì)上述問題
toRef(data, key)
會(huì)嘗試從data
中讀取key
對(duì)應(yīng)的鍵值屋摇,
如果讀得到,就直接取值幽邓,
如果讀不到炮温,會(huì)賦值undefined,后續(xù)可以為之賦實(shí)值:
<script>
const app = Vue.createApp({
template: `
<div>{{age}}</div>
`,
setup(props, context) {
const { reactive, toRef } = Vue;
const data = reactive({ name: 'guan'});
const age = toRef(data, 'age');
setTimeout(() => {
age.value = 'zhao';
}, 2000);
return { age }
}
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行兩秒后自動(dòng)更新:
關(guān)于setup函數(shù)的三個(gè)參數(shù)
setup函數(shù)的context參數(shù)中的三個(gè)屬性牵舵,即
attrs, slots, emit
柒啤;
獲取方法(解構(gòu)context參數(shù)
):setup(props, context) { const { attrs, slots, emit } = context; return { }; }
attrs
-- 是一個(gè)
Proxy
對(duì)象;
-- 子組件的none-props屬性
都存進(jìn)attrs
中畸颅;
如下担巩,父組件調(diào)用子組件,傳遞myfield
屬性没炒,
子組件沒有用props
接收涛癌,則myfield
作為none-props屬性
被 子組件承接,
這時(shí)候會(huì)存進(jìn)setup
函數(shù)的attrs
中:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
template: `
<child myfield='heheda' />
`
});
app.component('child', {
template:`
<div>child</div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(attrs);
return { };
}
})
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果:可以看到attrs
也是一個(gè)Proxy
對(duì)象
(attrs.myfield)
打印取值:
<script>
const app = Vue.createApp({
template: `
<child myfield='heheda' />
`
});
app.component('child', {
template:`
<div>child</div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(attrs.myfield);
return { };
}
})
const vm = app.mount('#heheApp');
</script>
slots
-- 是一個(gè)
Proxy
對(duì)象拳话;
-- 其中有一個(gè) 以為default
為鍵的函數(shù)先匪,
這個(gè)函數(shù)會(huì)以虛擬DOM
的形式,
返回 傳給 子組件的slot插槽
的組件
假颇;
打印slots屬性:
<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
template:`
<div>child</div>
<div><slot/></div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(slots);
return { };
}
})
const vm = app.mount('#heheApp');
</script>
打印slots屬性中的 default函數(shù)胚鸯, 可見其返回的是虛擬DOM:
<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
template:`
<div>child</div>
<div><slot/></div>
`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(slots.default());
return { };
}
})
const vm = app.mount('#heheApp');
</script>
使用setup中 context參數(shù)的 slots屬性中的 default方法所返回的 虛擬DOM,通過render函數(shù)的形式笨鸡,在setup函數(shù)返回姜钳,可以覆蓋template
的內(nèi)容,渲染UI
<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
template:`
<div>child Text</div>
<div><slot/></div>
`,
setup(props, context) {
const { h } = Vue;
const { attrs, slots, emit } = context;
return () => h('div', {}, slots.default());
}
})
const vm = app.mount('#heheApp');
</script>
補(bǔ)充:不使用 Vue3 的這個(gè)compositionAPI形耗,子組件只能這樣去獲取slots
內(nèi)容
通過在其他函數(shù)中哥桥,使用this.$slots
的方式調(diào)用到slots
內(nèi)容
<script>
const app = Vue.createApp({
template: `
<child>hehehe</child>
`
});
app.component('child', {
mounted () {
console.log("this.$slots --- ",this.$slots);
},
setup(props, context) {
console.log("context.slots --- ", context.slots);
const { h } = Vue;
const { attrs, slots, emit } = context;
return () => h('div', {}, slots.default());
}
})
const vm = app.mount('#heheApp');
</script>
運(yùn)行結(jié)果如下,可以看到跟setup
函數(shù)的context.slots
是一樣的:
回顧 沒有 CompositionAPI時(shí)激涤,emit的用法
<script>
const app = Vue.createApp({
methods: {
handleChange() {
alert('hehehe');
}
},
template: `
<child @change="handleChange">hehehe</child>
`
});
app.component('child', {
template: `
<div >hehehe</div>
`,
mounted () {
this.$emit('change');
}
})
const vm = app.mount('#heheApp');
</script>
使用setup的 context.emit 替代 this.$emit
<script>
const app = Vue.createApp({
methods: {
handleChange() {
alert('hehehe');
}
},
template: `
<child @change="handleChange">hehehe</child>
`
});
app.component('child', {
template: `
<div @click="handleClick">6666</div>
`,
setup(props, context) {
const { h } = Vue;
const { attrs, slots, emit } = context;
function handleClick() {emit('change');}
return { handleClick };
}
})
const vm = app.mount('#heheApp');
</script>
運(yùn)行拟糕,點(diǎn)擊文本:
使用Composition API開發(fā) todoList
調(diào)測(cè)input框事件
setup中,
--- const inputValue = ref('6666');
定義到一個(gè)proxy
對(duì)象倦踢,
可以將此對(duì)象 用于setup外的 template中送滞,
完成雙向綁定
中的一環(huán)——數(shù)據(jù)字段映射到 UI
!H杌印@缧帷;
--- handleInputValueChange
定義一個(gè)方法晤碘;
運(yùn)行時(shí)褂微,將對(duì)應(yīng)觸發(fā)組件
的 內(nèi)容
,
賦值給 inputValue
园爷,
完成雙向綁定
中的另一環(huán)——UI 映射到數(shù)據(jù)
3杪臁!M纭求厕;
template中,
:value="inputValue"
使得對(duì)象的內(nèi)容 初始化顯示inputValue
的內(nèi)容扰楼;
--- @input="handleInputValueChange"
使得輸入框被用戶輸入新的內(nèi)容時(shí)甘改,
會(huì)調(diào)用對(duì)應(yīng)的方法,這里調(diào)用:
<script>
const app = Vue.createApp({
setup() {
const { ref } = Vue;
const inputValue = ref('6666');
const handleInputValueChange = (e) => {
console.log("e --- ",e);
console.log("e.target.value", e.target.value);
inputValue.value = e.target.value;
}
return {
inputValue,
handleInputValueChange
}
},
template: `
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<div>{{inputValue}}</div>
<button>提交</button>
</div>
<ul>
<li>1</li>
<li>2</li>
</ul>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果:完善toDoList案例
--- setup 中定義 數(shù)組list灭抑,以及 handleSubmit處理函數(shù),
并都在return中返回抵代;
--- template中腾节,
button添加click事件回調(diào);
li 標(biāo)簽,使用v-for案腺,完成列表渲染:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { ref, reactive } = Vue;
const inputValue = ref('6666');
const list = reactive([]);
const handleInputValueChange = (e) => {
inputValue.value = e.target.value;
}
const handleSubmit = () => {
console.log("now, inputValue.value --- ", inputValue.value);
list.push(inputValue.value);
}
return {
list,
inputValue,
handleInputValueChange,
handleSubmit
}
},
template: `
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="handleSubmit">提交</button>
</div>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果:優(yōu)化上例的邏輯結(jié)構(gòu)庆冕!
如下,將 setup()中劈榨,
--- 與 list 相關(guān)的定義和操作函數(shù)访递,
封裝到listHandleAction
中,然后return同辣;
--- 與 inputValue 相關(guān)的定義和操作函數(shù)拷姿,
封裝到inputHandleAction
中,然后return旱函;
--- 最后setup()
調(diào)用以上兩個(gè)業(yè)務(wù)模塊封裝函數(shù)
响巢,
解構(gòu)返回的內(nèi)容
,進(jìn)行中轉(zhuǎn)調(diào)用
棒妨;
【分模塊 聚合業(yè)務(wù)邏輯
踪古,只留一個(gè)核心方法
進(jìn)行統(tǒng)籌調(diào)度
,有MVP
那味了】
【這樣設(shè)計(jì)券腔,業(yè)務(wù)分明伏穆,方便定位問題,可讀性纷纫、可維護(hù)性高U砩ā!
】
<script>
const listHandleAction = () => {
const { reactive } = Vue;
const list = reactive([]);
const addItemToList = (item) => {
console.log("now, item --- ", item);
list.push(item);
}
return { list, addItemToList }
}
const inputHandleAction = () => {
const { ref } = Vue;
const inputValue = ref('');
const handleInputValueChange = (e) => {
inputValue.value = e.target.value;
}
return { inputValue, handleInputValueChange }
}
const app = Vue.createApp({
setup() {
const { list, addItemToList } = listHandleAction();
const { inputValue, handleInputValueChange } = inputHandleAction();
return {
list, addItemToList,
inputValue, handleInputValueChange
}
},
template: `
<div>
<div>
<input :value="inputValue" @input="handleInputValueChange"/>
<button @click="addItemToList(inputValue)">提交</button>
</div>
<ul>
<li v-for="(item, index) in list" :key="index">{{item}}</li>
</ul>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果:setup的 computed 計(jì)算屬性
<script>
const app = Vue.createApp({
setup() {
const { ref, computed } = Vue;
const count = ref(0);
const handleClick = () => {
count.value += 1;
}
const countAddFive = computed(() => {
return count.value + 5;
})
return { count, countAddFive, handleClick }
},
template: `
<div>
<div>
<span @click="handleClick">{{count}}</span> --- {{countAddFive}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行結(jié)果:當(dāng)然以上是computed 的默認(rèn)用法涛酗,實(shí)際上它可以接收一個(gè)對(duì)象
這個(gè)對(duì)象包含兩個(gè)函數(shù)屬性铡原,
第一個(gè)是get
函數(shù),其內(nèi)容即取值時(shí)候返回的內(nèi)容商叹,同默認(rèn)用法燕刻;
第二個(gè)是set
函數(shù),當(dāng)試圖修改computed
變量的值時(shí)剖笙,就會(huì)回調(diào)這個(gè)方法卵洗,
接收的參數(shù),即試圖修改的值
:
如下弥咪,試圖在3秒后修改computed
變量countAddFive
的值过蹂,
這時(shí)回調(diào)set方法:
<script>
const app = Vue.createApp({
setup() {
const { ref, computed } = Vue;
const count = ref(0);
const handleClick = () => {
count.value += 1;
}
const countAddFive = computed({
get: () => {
return count.value + 5;
},
set: (param) => {
count.value = param -5;
}
})
setTimeout(() => {
countAddFive.value = 1000;
}, 2000);
return { count, countAddFive, handleClick }
},
template: `
<div>
<div>
<span @click="handleClick">{{count}}</span> --- {{countAddFive}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行3s后:將上例的處理值換成 Object類型涮毫,再例
<script>
const app = Vue.createApp({
setup() {
const { reactive, computed } = Vue;
const countObj = reactive({ count: 0 });
const handleClick = () => {
countObj.count += 1;
}
const countAddFive = computed({
get: () => {
return countObj.count + 5;
},
set: (param) => {
countObj.count = param -5;
}
})
setTimeout(() => {
countAddFive.value = 1000;
}, 2000);
return { countObj, countAddFive, handleClick }
},
template: `
<div>
<div>
<span @click="handleClick">{{countObj.count}}</span> --- {{countAddFive}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果同上例涧团;
setup 中的 watch 監(jiān)聽
如下凉泄,
---watch
一個(gè)參數(shù)為要監(jiān)聽的引用盲赊,
第二個(gè)參數(shù)為函數(shù)類型候生,當(dāng)監(jiān)聽的引用發(fā)生變化時(shí)會(huì)回調(diào)私爷,
其有兩個(gè)參數(shù)扫尖,一個(gè)是當(dāng)前(變化后的)值伐坏,一個(gè)是變化前的值;
--- input
組件中击胜,v-model
完成雙向綁定
?髡!偶摔!
--- input
輸入內(nèi)容時(shí)暇唾,觸發(fā) 雙向綁定
的特性,
內(nèi)容映射到name
引用上辰斋,
由ref
的響應(yīng)特性
策州,name
的內(nèi)容又映射到{{name}}
這DOM節(jié)點(diǎn)
上:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { ref, watch } = Vue;
const name = ref('heheda')
watch(name, (currentValue, prevValue) => {
console.log(currentValue, prevValue);
})
return { name }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行效果:setup 中的 watch 監(jiān)聽:監(jiān)聽Object類型
注意setup的watch的 可監(jiān)聽數(shù)據(jù)類型
所以,這里主要是
---將watch的 第一個(gè)參數(shù)改成 函數(shù)亡呵;
---使用toRefs抽活,簡(jiǎn)化傳遞過程;
(不然template要接收和處理的就是 nameObject.name了锰什,而不是這里的name)
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'heheda'});
watch(() => nameObj.name, (currentValue, prevValue) => {
console.log(currentValue, prevValue);
});
const { name } = toRefs(nameObj);
return { name }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果同上例下硕;
setup 中的 watch 監(jiān)聽:監(jiān)聽Object類型的 多個(gè)屬性
注意watch的參數(shù)寫法,
一參寫成汁胆,以函數(shù)類型
為元素
的數(shù)組
梭姓;
二參,參數(shù)列表寫成兩個(gè)數(shù)組嫩码,
第一個(gè)為current值數(shù)組誉尖,第二個(gè)為prev值數(shù)組;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
watch([() => nameObj.name, () => nameObj.englishName],
([curName, curEngN], [prevName, prevEngN]) => {
console.log(curName, prevName, curEngN, prevEngN);
});
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行铸题,先在Name輸入框輸入铡恕,后再EnglishName框輸入,效果:setup 中的 watchEffect監(jiān)聽 以及 與 watch 的異同比較
函數(shù)中丢间,使得純函數(shù) 變成 非純函數(shù)的 異步處理等部分邏輯塊探熔,
稱之為effect塊
;
---
watch
是惰性
的烘挫,只有watch
監(jiān)聽的字段發(fā)生變化時(shí)诀艰,
watch
的處理邏輯才會(huì)被回調(diào);
---watchEffect
是即時(shí)性
的饮六,也就是除了watch
的回調(diào)特性
其垄,
watchEffect
的處理邏輯
還會(huì)在頁面渲染完成時(shí)
立馬先執(zhí)行一次,
即watchEffect
監(jiān)聽的字段未曾改變卤橄,
watchEffect
就已經(jīng)執(zhí)行了一次绿满;
(實(shí)例可以見下例運(yùn)行效果)
watch
需要寫明監(jiān)聽字段
,
watchEffect
不需要窟扑,直接寫處理邏輯
即可棒口,
底層封裝
會(huì)寄月!自動(dòng)監(jiān)聽!
所寫處理邏輯
中用到的无牵!所有字段!
厂抖;
如下例子中茎毁,
watchEffect
的處理邏輯——console.log(nameObj.name, nameObj.englishName);
,
僅一行代碼忱辅,
完成對(duì)nameObj.name
和nameObj.englishName
兩個(gè)字段的監(jiān)聽七蜘,
并完成了處理邏輯;
watch
可以直接從參數(shù)列表
中獲取到之前(變化前)的值
和當(dāng)前(變化后)的值
墙懂,
watchEffect
不行橡卤,處理邏輯中拿到的直接就是當(dāng)前(變化后)的值
;
- 兩者都可以用以下的方式损搬,在一個(gè)設(shè)定的時(shí)延之后碧库,停止監(jiān)聽
- 為
watch
配置 immediate屬性,可使具備同watchEffect
的 即時(shí)性
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, watchEffect, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
watchEffect(() => {
console.log(nameObj.name, nameObj.englishName);
})
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
跟緊console.log(nameObj.name, nameObj.englishName);
巧勤,
先在Name輸入框輸入123嵌灰,后再EnglishName框輸入456,運(yùn)行效果:
注意第一行打印颅悉,第一行是頁面渲染完成時(shí)立馬執(zhí)行沽瞭,
用戶未曾輸入內(nèi)容,watchEffect
監(jiān)聽的字段未曾改變剩瓶,
watchEffect
就已經(jīng)執(zhí)行了一次驹溃,體現(xiàn)watchEffect
的即時(shí)性
!Q邮铩豌鹤!
兩者都可以用以下的方式,在一個(gè)設(shè)定的時(shí)延之后搂鲫,停止監(jiān)聽
將
watch / watchEffect
的函數(shù)返回值
賦給一個(gè)字段(如下stopWatch / stopWatchEffect
)傍药;
接著在watch / watchEffect
的處理邏輯
中,
編寫類似setTimeout
的異步函數(shù)魂仍,
并在其中調(diào)用 與 剛剛定義的字段
同名的 函數(shù)(如下stopWatch() / stopWatchEffect()
)拐辽,
即可停止對(duì)應(yīng)的監(jiān)聽
;
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, watchEffect, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
const stopWatch = watch([() => nameObj.name, () => nameObj.englishName],
([curName, curEngN], [prevName, prevEngN]) => {
console.log(curName, prevName, curEngN, prevEngN);
setTimeout(() => {
stopWatch();
}, 3000);
});
const stopWatchEffect = watchEffect(() => {
console.log(nameObj.name, nameObj.englishName);
setTimeout(() => {
stopWatchEffect();
}, 3000);
})
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行擦酌,可以看到俱诸,
前3s(我們?cè)O(shè)定的時(shí)延)兩個(gè)輸入框是可以響應(yīng)監(jiān)聽的,
但是3s之后赊舶,無論怎么輸入內(nèi)容睁搭,console也不會(huì)打印log赶诊,
因?yàn)檫@時(shí)兩個(gè)監(jiān)聽效果已經(jīng)取消了:
為 watch
配置 immediate屬性,可使具備同watchEffect
的 即時(shí)性
如下园骆,使用{ immediate: true}
為 watch
配置 immediate屬性舔痪,
可使具備同watchEffect
的 即時(shí)性:
<script>
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'heheda', englishName:"lueluelue"});
const stopWatch = watch([() => nameObj.name, () => nameObj.englishName],
([curName, curEngN], [prevName, prevEngN]) => {
console.log(curName, prevName, curEngN, prevEngN);
}, { immediate: true});
const { name, englishName } = toRefs(nameObj);
return { name, englishName }
},
template: `
<div>
<div>
Name:<input v-model="name">
</div>
<div>
Name is {{name}}
</div>
<div>
EnglishName:<input v-model="englishName">
</div>
<div>
EnglishName is {{englishName}}
</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果如下,undefined
是因第一次執(zhí)行時(shí)锌唾,
監(jiān)聽的變量還沒有變化锄码,
所以就沒有先前值(prevValue)
的說法,只有當(dāng)前值(currentValue)
:
setup 中的 生命周期
--- Vue3.0提供了一些對(duì)應(yīng)生命周期的晌涕,可以寫在setup
函數(shù)中的回調(diào)方法滋捶;
(具體請(qǐng)看 下方例程)
--- setup
函數(shù)的執(zhí)行時(shí)間點(diǎn)
在于beforeCreate
和Created
之間,
所以CompositionAPI
里邊是沒有類似onBeforeCreate
和onCreated
的方法的余黎,
要寫在這兩個(gè)周期中的邏輯重窟,
直接寫在setup
中即可;
下面是兩個(gè)Vue3.0引入的新鉤子:
--- onRenderTracked
渲染跟蹤惧财,
跟蹤 收集響應(yīng)式依賴的時(shí)機(jī)巡扇,
每次準(zhǔn)備開始渲染時(shí)(onBeforeMount后,onMounted前)回調(diào)可缚;
--- onRenderTriggered
渲染觸發(fā)霎迫,
每次觸發(fā)
頁面重新渲染時(shí)回調(diào),
回調(diào)后帘靡,下一輪的 onBeforeMount緊跟其后知给;
<script>
const app = Vue.createApp({
setup() {
const { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
onRenderTracked, onRenderTriggered } = Vue;
const heheda = ref('heheda');
onBeforeMount(() => {
console.log('onBeforeMount');
}),
onMounted(() => {
console.log('onMounted');
}),
onBeforeUpdate(() => {
console.log('onBeforeUpdate');
}),
onUpdated(() => {
console.log('onUpdated');
}),
onRenderTracked((event) => {
console.log('onRenderTracked', event);
}),
onRenderTriggered((event) => {
console.log('onRenderTriggered', event);
})
const handleClick = () => {
heheda.value = 'lululu';
console.log("you had clicked the text!");
}
return { heheda, handleClick }
},
template: `
<div @click="handleClick">
{{heheda}}
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果:
setup中的provide、inject用法
--- 父組件拿出provide
描姚,提供鍵值對(duì)涩赢;
--- 子組件拿出inject
,一參為接收鍵轩勘,二參為默認(rèn)值筒扒;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello World! heheheheheheda</title>
<script src="https://unpkg.com/vue@next"></script>
</head>
<body>
<div id="heheApp"></div>
</body>
<script>
const app = Vue.createApp({
setup() {
const { provide } = Vue;
provide('name', 'li');
return { }
},
template: `
<div>
<child />
</div>
`
});
app.component('child', {
setup() {
const { inject } = Vue;
const name = inject('name', 'default');
return {name}
},
template: `
<div>{{name}}</div>
`
})
const vm = app.mount('#heheApp');
</script>
</html>
運(yùn)行結(jié)果:配合上ref
實(shí)現(xiàn) 響應(yīng)特性 以及 readonly
實(shí)現(xiàn) 單向數(shù)據(jù)流
--- setup中, 借provide
傳輸?shù)?數(shù)據(jù)字段 配合上ref
绊寻,
使之具備響應(yīng)
的特性花墩;
--- 父組件提供 更改數(shù)據(jù)的接口,
在使用provide
傳遞 數(shù)據(jù)字段時(shí)澄步,加上 readonly
包裹冰蘑,
使得子組件 需要更改 父組件傳遞過來的數(shù)據(jù)字段 時(shí),
無法直接 修改字段村缸,
需調(diào)用 父組件的接口方法 更改祠肥,
按 單向數(shù)據(jù)流 規(guī)范編程;
<script>
const app = Vue.createApp({
setup() {
const { provide, ref, readonly } = Vue;
const name = ref('li');
provide('name', readonly(name));
provide('changeName', (value) => {
name.value = value;
})
return { }
},
template: `
<div>
<child />
</div>
`
});
app.component('child', {
setup() {
const { inject } = Vue;
const name = inject('name', 'default');
const changeName = inject('changeName');
const handleClick = () => {
changeName('lin');
}
return { name, handleClick }
},
template: `
<div @click="handleClick">{{name}}</div>
`
})
const vm = app.mount('#heheApp');
</script>
運(yùn)行效果:setup結(jié)合ref指令
前面筆記《Vue3 | Mixin梯皿、自定義指令仇箱、Teleport傳送門县恕、Render函數(shù)、插件 詳解 及 案例分析》有寫到普通場(chǎng)景的ref指定剂桥;
--- setup中的ref是前面說的生成響應(yīng)式字段的意思忠烛;
--- template中的ref是獲取對(duì)應(yīng)的dom節(jié)點(diǎn);
--- 而當(dāng) template中的ref
指定的字段名渊额,
跟setup中的ref生成的響應(yīng)式字段名一樣的時(shí)候况木,兩者就會(huì)關(guān)聯(lián)起來;
如下旬迹,
template中和setup中的字段heheda
關(guān)聯(lián)起來,
在setup的onMounted中求类,使用這個(gè)DOM節(jié)點(diǎn)字段奔垦,
打印DOM代碼:
<script>
const app = Vue.createApp({
setup() {
const { ref, onMounted } = Vue;
const heheda = ref(null);
onMounted(() => {
console.log(heheda);
console.log(heheda.value);
})
return { heheda }
},
template: `
<div>
<div ref="heheda">lueluelue</div>
</div>
`
});
const vm = app.mount('#heheApp');
</script>
運(yùn)行結(jié)果: