Vue3 | Composition API 包括setup、ref等新特性詳解 與 實(shí)戰(zhàn)

完整原文地址見(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é)果:

兩秒后自動(dòng)刷新:


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.namenameObj.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) 在于beforeCreateCreated之間,
所以CompositionAPI里邊是沒(méi)有類似onBeforeCreateonCreated的方法的,
要寫在這兩個(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)行效果:

點(diǎ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é)果:






最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容