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