完整原文地址見(jiàn)簡(jiǎn)書
更多完整Vue筆記目錄敬請(qǐng)見(jiàn)《前端 Web 筆記 匯總目錄(Updating)》
本文內(nèi)容提要
- Composition API 的作用
- setup函數(shù)
- 例程,打印查看setup內(nèi)容
- 非響應(yīng)引用的案例
ref()概念、原理 與 實(shí)戰(zhàn)reactive()概念、原理 與 實(shí)戰(zhàn)- 使用
readonly限制對(duì)象的訪問(wèn)權(quán)限- 使用
toRefs()對(duì)reactive對(duì)象進(jìn)一步封裝- 多個(gè)屬性進(jìn)行解構(gòu)
- 多個(gè)屬性 配合toRefs() 進(jìn)行解構(gòu)
- toRefs()無(wú)法處理 undefined的鍵值
- 使用toRef()應(yīng)對(duì)上述問(wèn)題
- 關(guān)于setup函數(shù)的三個(gè)參數(shù)【attrs、slots、emit】
- 回顧 沒(méi)有 CompositionAPI時(shí),emit的用法
- 使用setup的 context.emit 替代 this.$emit
- 使用Composition API開(kāi)發(fā) todoList
- 完善toDoList案例
- 優(yōu)化上例的邏輯結(jié)構(gòu)!
- setup的 computed 計(jì)算屬性
- 當(dāng)然以上是computed 的默認(rèn)用法,實(shí)際上它可以接收一個(gè)對(duì)象
- 將上例的處理值換成 Object類型,再例
- setup 中的 watch 監(jiān)聽(tīng)
- setup 中的 watch 監(jiān)聽(tīng):監(jiān)聽(tīng)Object類型
- setup 中的 watch 監(jiān)聽(tīng):監(jiān)聽(tīng)Object類型的 多個(gè)屬性
- setup 中的 watchEffect監(jiān)聽(tīng) 以及 與 watch 的異同比較
- 兩者都可以用以下的方式,在一個(gè)設(shè)定的時(shí)延之后,停止監(jiān)聽(tīng)
- 為 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)的功能代碼 可以比較 完整地聚合起來(lái),
提高可維護(hù)性、可讀性,提高開(kāi)發(fā)效率;
規(guī)避 同一個(gè)功能的代碼,
卻散落在 組件定義中的data、methods、computed、directives、template、mixin等各處 的問(wèn)題;
setup函數(shù)
--- Composition API 所有代碼編寫之前,
都要 建立在setup函數(shù) 之上;
--- 在created 組件實(shí)例 被完全初始化之前 回調(diào);
(所以注意在setup函數(shù)中,
使用與this相關(guān)的調(diào)用是沒(méi)有用的)
--- 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中 無(wú)法調(diào)用this等相關(guān) 如變量、methods中 等 其他內(nèi)容,但是其他內(nèi)容 卻可以調(diào)用 setup函數(shù)!!【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)引用的案例
如下,這樣沒(méi)有使用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)式;
原理:通過(guò) 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類型是沒(méi)有響應(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)行效果,兩秒后無(wú)反應(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ì)象的訪問(wèn)權(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即可;
--- ?。?!注意:
類似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()無(wú)法處理 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ì)上述問(wèn)題
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屬性,
子組件沒(méi)有用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ù), 可見(jiàn)其返回的是虛擬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,通過(guò)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)容
通過(guò)在其他函數(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是一樣的:

回顧 沒(méi)有 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開(kāi)發(fā) todoList
調(diào)測(cè)input框事件
setup中,
--- const inputValue = ref('6666');定義到一個(gè)proxy對(duì)象,
可以將此對(duì)象 用于setup外的 template中,
完成雙向綁定中的一環(huán)——數(shù)據(jù)字段映射到 UI?。。?;
--- handleInputValueChange定義一個(gè)方法;
運(yùn)行時(shí),將對(duì)應(yīng)觸發(fā)組件的 內(nèi)容,
賦值給 inputValue,
完成雙向綁定中的另一環(huán)——UI 映射到數(shù)據(jù)?。。?;
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ù)分明,方便定位問(wèn)題,可讀性、可維護(hù)性高??!
】
<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)聽(tīng)
如下,
---watch一個(gè)參數(shù)為要監(jiān)聽(tīng)的引用,
第二個(gè)參數(shù)為函數(shù)類型,當(dāng)監(jiān)聽(tīng)的引用發(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)聽(tīng):監(jiān)聽(tīng)Object類型
注意setup的watch的 可監(jiān)聽(tīng)數(shù)據(jù)類型

所以,這里主要是
---將watch的 第一個(gè)參數(shù)改成 函數(shù);
---使用toRefs,簡(jiǎn)化傳遞過(guò)程;
(不然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)聽(tīng):監(jiān)聽(tīng)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)聽(tīng) 以及 與 watch 的異同比較
函數(shù)中,使得純函數(shù) 變成 非純函數(shù)的 異步處理等部分邏輯塊,
稱之為effect塊;
---
watch是惰性的,只有watch監(jiān)聽(tīng)的字段發(fā)生變化時(shí),
watch的處理邏輯才會(huì)被回調(diào);
---watchEffect是即時(shí)性的,也就是除了watch的回調(diào)特性,
watchEffect的處理邏輯還會(huì)在頁(yè)面渲染完成時(shí)立馬先執(zhí)行一次,
即watchEffect監(jiān)聽(tīng)的字段未曾改變,
watchEffect就已經(jīng)執(zhí)行了一次;
(實(shí)例可以見(jiàn)下例運(yùn)行效果)
watch需要寫明監(jiān)聽(tīng)字段,
watchEffect不需要,直接寫處理邏輯即可,
底層封裝會(huì)!自動(dòng)監(jiān)聽(tīng)!所寫處理邏輯中用到的!所有字段!;
如下例子中,
watchEffect的處理邏輯——console.log(nameObj.name, nameObj.englishName);,
僅一行代碼,
完成對(duì)nameObj.name和nameObj.englishName兩個(gè)字段的監(jiān)聽(tīng),
并完成了處理邏輯;
watch可以直接從參數(shù)列表中獲取到之前(變化前)的值和當(dāng)前(變化后)的值,
watchEffect不行,處理邏輯中拿到的直接就是當(dāng)前(變化后)的值;
- 兩者都可以用以下的方式,在一個(gè)設(shè)定的時(shí)延之后,停止監(jiān)聽(tīng)
- 為
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)行效果:

注意第一行打印,第一行是頁(yè)面渲染完成時(shí)立馬執(zhí)行,
用戶未曾輸入內(nèi)容,watchEffect監(jiān)聽(tīng)的字段未曾改變,
watchEffect就已經(jīng)執(zhí)行了一次,體現(xiàn)watchEffect的即時(shí)性?。?!
兩者都可以用以下的方式,在一個(gè)設(shè)定的時(shí)延之后,停止監(jiān)聽(tīng)
將
watch / watchEffect的函數(shù)返回值賦給一個(gè)字段(如下stopWatch / stopWatchEffect);
接著在watch / watchEffect的處理邏輯中,
編寫類似setTimeout的異步函數(shù),
并在其中調(diào)用 與 剛剛定義的字段同名的 函數(shù)(如下stopWatch() / stopWatchEffect()),
即可停止對(duì)應(yīng)的監(jiān)聽(tīng);
<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)聽(tīng)的,
但是3s之后,無(wú)論怎么輸入內(nèi)容,console也不會(huì)打印log,
因?yàn)檫@時(shí)兩個(gè)監(jiān)聽(tīng)效果已經(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)聽(tīng)的變量還沒(méi)有變化,
所以就沒(méi)有先前值(prevValue)的說(shuō)法,只有當(dāng)前值(currentValue):

setup 中的 生命周期
--- Vue3.0提供了一些對(duì)應(yīng)生命周期的,可以寫在setup函數(shù)中的回調(diào)方法;
(具體請(qǐng)看 下方例程)
--- setup函數(shù)的執(zhí)行時(shí)間點(diǎn) 在于beforeCreate和Created之間,
所以CompositionAPI里邊是沒(méi)有類似onBeforeCreate和onCreated的方法的,
要寫在這兩個(gè)周期中的邏輯,
直接寫在setup中即可;
下面是兩個(gè)Vue3.0引入的新鉤子:
--- onRenderTracked
渲染跟蹤,
跟蹤 收集響應(yīng)式依賴的時(shí)機(jī),
每次準(zhǔn)備開(kāi)始渲染時(shí)(onBeforeMount后,onMounted前)回調(diào);
--- onRenderTriggered
渲染觸發(fā),
每次觸發(fā)頁(yè)面重新渲染時(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包裹,
使得子組件 需要更改 父組件傳遞過(guò)來(lái)的數(shù)據(jù)字段 時(shí),
無(wú)法直接 修改字段,
需調(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是前面說(shuō)的生成響應(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)起來(lái);
如下,
template中和setup中的字段heheda關(guān)聯(lián)起來(lái),
在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é)果: