第十二节: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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

 

posted @ 2021-10-10 21:37  Yaopengfei  阅读(4943)  评论(1编辑  收藏  举报