第十二節:Vue3的Composition Api(生命周期、provide/Inject、綜合練習-hook封裝) 和 setup頂層開發模式


一. 生命周期鈎子

1. 說明

 setup 可以用來替代 data 、 methods 、 computed 、watch 等等這些選項,也可以替代 生命周期鈎子。

 注:因為 setup 是圍繞 beforeCreate 和 created 生命周期鈎子運行的,所以不需要顯式地定義它們。換句話說,在這些鈎子中編寫的任何代碼都應該直接在 setup 函數中編寫。

2. 實戰 

<template>
    <div>
        <h3>我是child1組件</h3>
        <h4>{{counter}}</h4>
        <h4><button @click="AddNum()">加1</button></h4>
    </div>
</template>

<script>
    import { ref, onMounted, onUpdated, onUnmounted } from 'vue';
    export default {
        setup(props, context) {
            // 編寫相關業務
            let counter = ref(100);
            const AddNum = () => counter.value++;

            // 測試生命周期
            onMounted(()=>{
                console.log('App onMounted');
            });
            onUpdated(()=>{
                console.log('App onUpdated');
            });
            onUnmounted(()=>{
                console.log('App onUnmounted');
            });
            
            // 返回值
            return {
                counter,
                AddNum
            }
        }
    }
</script>

 

二. provide/Inject

1. 說明

 之前學習的Option Api中有Provide和Inject,Composition API也可以替代之前的 Provide 和 Inject 的選項。

(1). Provide:用於向子組件中傳遞數據。provide可以傳入兩個參數:① name:提供的屬性名稱; ② value:提供的屬性值。

注:為了保證數據的響應性,一般傳遞ref對象;而且傳遞的數據要符合單向數據流原則,即傳遞的數據只允許子組件調用不允許子組件修改,所有通常再用readonly包裹一下。

(2). Inject:用於接收父組件傳遞過來的數據。

 inject 函數有兩個參數:

  A. 要 inject 的 property 的 name

  B. 默認值 (可選)

2. 實戰

 這里准備三個組件,從上往下依次是: App.vue → father1.vue → child1.vue, 下面代碼實現:App組件 向 child1組件傳值。

App.vue代碼

<template>
    <div>
        <h3>我是最上級App組件</h3>
        <h4>{{counter}}</h4>
        <h4><button @click="AddNum()">App.vue中加1</button></h4>
        
        <father1></father1>
    </div>
</template>

<script>
    import father1 from './father1.vue';
    import { ref, readonly, provide } from 'vue';
    export default {
        components: {
            father1
        },
        setup() {
            let counter = ref(100);
            const AddNum = () => counter.value++;

            provide('myCounter', readonly(counter));

            return {
                counter,
                AddNum
            }
        }
    }
</script> 

father1.vue代碼

<template>
    <div>
        <h3>我是father1組件</h3>
        <child1></child1>
    </div>
</template>

<script>
    import child1 from './child1.vue';
    export default {
        components:{
            child1
        },
        setup(props, context) {
            return {}

        }
    }
</script>
View Code

child1.vue代碼

<template>
    <div>
        <h3>我是child1組件</h3>
        <h4>{{counter}}</h4>
        <h4>{{msg}}</h4>
        <h4><button @click="AddNum()">child.vue加1</button></h4>
    </div>
</template>
<script>
    import { inject } from 'vue';
    export default {
        setup(props, context) {        
            let counter = inject('myCounter');
            let msg = inject('myMsg','hello ypf');  //如果沒有傳遞,則為默認值
            
            // 傳遞過來的是readonly對象,不能被修改,符合單向數據流原則,如果修改,需要通過emit向父組件發送通知,讓父組件修改
            const AddNum = () => counter.value++;

            // 返回值
            return {
                counter,
                msg,
                AddNum
            }
        }
    }
</script>

 

三. 綜合練習-hook封裝

1. 相關規則

(1). import導入的匹配規則

(2). ESModule導入導出規則

  詳見:https://www.cnblogs.com/yaopengfei/p/14496363.html

2. 代碼實操

(1). hooks文件夾下5個js文件

useCounter.js

/* 
    自增、自減、雙倍
 */
import { ref, computed } from 'vue';

// 對應外面的接收方式  import useCounter from './hooks/useCounter.js'
export default function() {
    let counter = ref(100);
    let doubleCounter = computed(() => counter.value * 2);
    const AddNum = () => counter.value++;
    const Reduce = () => counter.value--;
    return {
        counter,
        doubleCounter,
        AddNum,
        Reduce
    }    
}
View Code

useTitle.js

/* 
  修改標題 
 */
import { ref, watch } from 'vue'

export default function(title = '我是默認title哦') {
    let titleRef = ref(title);

    watch(titleRef, (newValue, oldValue) => {
        document.title = newValue;
    }, {
        immediate: true
    });

    return titleRef;
}
View Code

useScrollPosition.js

import { ref } from 'vue';

export default function() {
  const scrollX = ref(0);
  const scrollY = ref(0);

  document.addEventListener("scroll", () => {
    scrollX.value = window.scrollX;
    scrollY.value = window.scrollY;
  });

  return {
    scrollX,
    scrollY
  }
}
View Code

useMousePosition.js

import { ref } from 'vue';

export default function() {
  const mouseX = ref(0);
  const mouseY = ref(0);

  window.addEventListener("mousemove", (event) => {
    mouseX.value = event.pageX;
    mouseY.value = event.pageY;
  });

  return {
    mouseX,
    mouseY
  }
}
View Code

useLocalStorage.js

import { ref, watch } from 'vue'

export default function(key, value) {
    var data = ref(value);
    if (value) {
        window.localStorage.setItem(key, JSON.stringify(value));
    } else {
        data.value = JSON.parse(window.localStorage.getItem(key));
    }

    watch(data, (newValue, oldValue) => {
        window.localStorage.setItem(key, JSON.stringify(newValue));
    });

    return data;
}
View Code

(2). hooks文件夾中新建index.js統一入口

import useCounter from './useCounter.js';
import useTitle from './useTitle.js';
import useScrollPosition from './useScrollPosition.js'
import useMousePosition from './useMousePosition.js'
import useLocalStorage from './useLocalStorage.js' export {
    useCounter,
    useTitle,
    useScrollPosition,
    useMousePosition,
    useLocalStorage
}

(3). App.vue調用代碼 

<template>
    <div>
        <h4>{{counter}}</h4>
        <h4>{{doubleCounter}}</h4>
        <h4><button @click="AddNum()">加1</button></h4>
        <h4><button @click="Reduce()">減1</button></h4>

        <h4><button @click="Edit()">修改緩存</button></h4>

        <p class="content"></p>
        <div class="scroll">
            <div class="scroll-x">scrollX: {{scrollX}}</div>
            <div class="scroll-y">scrollY: {{scrollY}}</div>
        </div>
        <div class="mouse">
            <div class="mouse-x">mouseX: {{mouseX}}</div>
            <div class="mouse-y">mouseY: {{mouseY}}</div>
        </div>
    </div>
</template>

<script>
    // 方式一:逐個導入
    // import useCounter from './hooks/useCounter.js';
    // import useTitle from './hooks/useTitle.js';
    // import useScrollPosition from './hooks/useScrollPosition.js'
    // import useMousePosition from './hooks/useMousePosition.js'
    // import useLocalStorage from './hooks/useLocalStorage.js'

    //方式二: 統一封裝導入 (可以省略,直接寫成 ./hooks)
    import { useCounter, useTitle, useScrollPosition, useMousePosition, useLocalStorage } from './hooks/index.js'

    export default {
        setup() {

            // 1.自增、自減、雙倍
            const { counter, doubleCounter, AddNum, Reduce } = useCounter();

            // 2.修改title
            let titleRef = useTitle('ypf');
            setTimeout(() => {
                titleRef.value = 'lmr';
            }, 3000);

            // 3. 頁面滾動位置
            const { scrollX, scrollY } = useScrollPosition();

            // 4. 鼠標移動位置
            const { mouseX, mouseY } = useMousePosition();

            // 5. 緩存的使用
            var myData = useLocalStorage('myName', 'ypf');
            console.log(myData);
            const Edit = () => myData.value = 'lmr';

            return {
                counter,
                doubleCounter,
                AddNum,
                Reduce,

                Edit,

                scrollX,
                scrollY,

                mouseX,
                mouseY
            }
        }
    }

 

四. setup頂層開發模式

 (詳見官網:https://v3.cn.vuejs.org/api/sfc-script-setup.html)   

1. 說明

 <script setup> 是在單文件組件 (SFC) 中使用組合式 API 的編譯時語法糖。相比於普通的 <script> 語法,它具有更多優勢:

 (1). 更少的樣板內容,更簡潔的代碼。

 (2). 能夠使用純 Typescript 聲明 props 和拋出事件。

 (3). 更好的運行時性能 (其模板會被編譯成與其同一作用域的渲染函數,沒有任何的中間代理)。

 (4). 更好的 IDE 類型推斷性能 (減少語言服務器從代碼中抽離類型的工作)。

2. 用法說明

 (1). 將setup寫到<script>標簽中。

 (2). 變量,函數聲明,以及 import 引入的內容都可以直接在template中使用

 (3). 直接導入組件使用即可

 (4).  父子組件之間相互傳值,需要使用:defineProps 和 defineEmits。

3. 實戰

(1). 父組件

<template>
    <div>
        <h4>我是主組件</h4>
        <h4>{{counter}}</h4>
        <h4><button @click="AddNum">加1</button></h4>

        <child1 msg="hahahhaha" @MyTest='MyTest'></child1>
    </div>
</template>

<script setup>
    import { ref } from 'vue';

    // 1. 變量,函數聲明,以及 import 引入的內容都可以直接在template中使用
    var counter = ref(0);
    var AddNum = () => counter.value++;

    //2. 直接導入組件使用即可
    import child1 from './child1.vue';

    //3. 測試子傳父
    const MyTest = (obj) => {
        console.log(`我是子組件傳遞過來的值:${obj}`);
    }
</script>

(2). 子組件

<template>
    <div>
        <h4>我是child1組件</h4>
        <h4>我是父組件傳遞過來的值:{{msg}}</h4>
        <h4><button @click="myClick">對外發送</button></h4>
    </div>
</template>

<script setup>
    import { defineProps, defineEmits } from 'vue';

    // 接收父組件傳遞過來的值
    const props = defineProps({
        msg: {
            type: String,
            default: 'hello ypf'
        }
    })

    // 聲明對外傳遞事件,並執行傳遞
    const emit = defineEmits(['MyTest', '']);
    const myClick = () => {
        emit('MyTest', '10010');
    }
</script>

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鵬飛)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 聲     明1 : 如有錯誤,歡迎討論,請勿謾罵^_^。
  • 聲     明2 : 原創博客請在轉載時保留原文鏈接或在文章開頭加上本人博客地址,否則保留追究法律責任的權利。
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM