【文末包邮送书】
script setup 语法糖
组合式 API:setup()
基本使用
Vue 3 的 Composition API 系列里,推出了一个全新的 setup
函数,它是一个组件选项,在创建组件之前执行,一旦 props 被解析,并作为组合式 API 的入口点。
setup
选项是一个接收 props
和 context
的函数,我们参考文档进行讨论。此外,我们将 setup
返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
<script>
// 这是一个基于 TypeScript 的 Vue 组件
import { defineComponent } from 'vue'
export default defineComponent({
setup(props, context) {
// 在这里声明数据,或者编写函数并在这里执行它
return {
// 需要给 `<template />` 用的数据或函数,在这里 `return` 出去
}
},
})
</script>
setup
选项是在组件创建之前, props
被解析之后执行,是组合式 API 的入口。注意:在
setup
中你应该避免使用this
,因为它不会找到组件实例。setup
的调用发生在data
property、computed
property 或methods
被解析之前,所以它们无法>在setup
中被获取。
在添加了setup的script标签中,我们不必声明和方法,这种写法会自动将所有顶级变量、函数,均会自动暴露给模板(template)使用这里强调一句 “暴露给模板,跟暴露给外部不是一回事”
TIP: 说的通俗一点,就是在使用 Vue 3 生命周期的情况下,整个组件相关的业务代码,都可以放在 setup
里执行。
因为在 setup
之后,其他的生命周期才会被启用,我们对比一下Vue2的Vue3生命周期的变化
组件生命周期
关于 Vue 生命周期的变化,可以从下表直观地了解:
Vue 2 生命周期 | Vue 3 生命周期 | 执行时间说明 |
---|---|---|
beforeCreate | setup | 组件创建前执行 |
created | setup | 组件创建后执行 |
beforeMount | onBeforeMount | 组件挂载到节点上之前执行 |
mounted | onMounted | 组件挂载完成后执行 |
beforeUpdate | onBeforeUpdate | 组件更新之前执行 |
updated | onUpdated | 组件更新完成之后执行 |
beforeDestroy | onBeforeUnmount | 组件卸载之前执行 |
destroyed | onUnmounted | 组件卸载完成后执行 |
errorCaptured | onErrorCaptured | 当捕获一个来自子孙组件的异常时激活钩子函数 |
可以看到 Vue 2 生命周期里的 beforeCreate
和 created
,在 Vue 3 里已被 setup
替代。
script setup 语法糖
它是 Vue3 的一个新语法糖,在 setup
函数中。所有 ES 模块导出都被认为是暴露给上下文的值,并包含在 setup() 返回对象中。相对于之前的写法,使用后,语法也变得更简单。
自动注册属性和方法无需返回,直接使用
1.<script setup>
语法糖并不是新增的功能模块,它只是简化了以往的组合API(compositionApi)的必须返回(return)的写法,并且有更好的运行时性能。
2.在 setup 函数中:所有 ES 模块导出都被认为是暴露给上下文的值,并包含在 setup() 返回对象中。相对于之前的写法,使用后,语法也变得更简单。
你不必担心setup语法糖的学习成本,他是组合式API的简化,并没有新增的知识点。你只需要了解一些用法和细微的不同之处,甚至比之前写setup()还要顺手!
使用方式也很简单,只需要在 script 标签加上 setup 关键字即可
<script setup>
</script>
组件核心 API 的使用
ref 暴露变量到模板
ref是最常用的一个响应式 API,它可以用来定义所有类型的数据,包括 Node 节点和组件。返回一个响应式对象,所有的值都通过 .value 属性获取。
<template>
<div>{{counter}}</div>
</template>
<script setup >
import { ref } from 'vue'
const counter = ref(0);//不用 return ,直接在 templete 中使用
const timer = setInterval(() => {
counter.value++
}, 1000)
onUnmounted(() => {
clearInterval(timer);
})
</script>
reactive
返回一个对象的响应式代理。
<script setup>
import { reactive, onUnmounted } from 'vue'
const state = reactive({
counter: 0
})
// 定时器 每秒都会更新数据
const timer = setInterval(() => {
state.counter++
}, 1000);
onUnmounted(() => {
clearInterval(timer);
})
</script>
<template>
<div>{{state.counter}}</div>
</template>
使用ref也能达到我们预期的'counter',并且在模板中,vue进行了处理,我们可以直接使用counter而不用写counter.value
ref和reactive的关系:
ref是一个{value:'xxxx'}的结构,value是一个reactive对象。
reactive 的底层是 Proxy ,ref 的本质也是用 reactive 来包装,所以也是 Proxy,ref本质也是reactive
ref(obj)等价于reactive({value: obj})
组件自动注册
在 script setup 中,引入的组件可以直接使用,无需再通过components进行注册,并且无法指定当前组件的名字,它会自动以文件名为主,也就是不用再写name属性了。
示例:
<template>
<Child />
</template>
<script setup>
import Child from '@/components/Child.vue'
</script>
定义组件的 props
defineProps ----> [用来接收父组件传来的 props] 代码示列
:
通过defineProps
指定当前 props 类型,获得上下文的props对象。
示例:
<script setup>
// defineEmits,defineProps无需导入,直接使用
const props = defineProps({
title: String,
})
</script>
<!-- 或者 -->
<script setup lang="ts">
import { ref,defineProps } from 'vue';
type Props={
msg:string
}
defineProps<Props>();
</script>
定义 emit
defineEmit ----> [子组件向父组件传递事件]
使用defineEmit
定义当前组件含有的事件,并通过返回的上下文去执行 emit。
代码示列:
<script setup>
// defineEmits,defineProps无需导入,直接使用
const emit = defineEmits(['change', 'delete'])
</script>
defineExpose API
defineExpose ----> [组件暴露出自己的属性]
传统的写法,我们可以在父组件中,通过 ref 实例的方式去访问子组件的内容,但在 script setup 中,该方法就不能用了,setup 相当于是一个闭包,除了内部的 template
模板,谁都不能访问内部的数据和方法。
<script setup>
的组件默认不会对外部暴露任何内部声明的属性。
如果有部分属性要暴露出去,可以使用defineExpose
注意:目前发现
defineExpose
暴露出去的属性以及方法都是unknown
类型,如果有修正类型的方法,欢迎评论区补充。
//子组件
<template>
{{msg}}
</template>
<script setup>
import { ref } from 'vue'
let msg = ref("Child Components");
let num = ref(123);
// defineExpose无需导入,直接使用
defineExpose({
msg,
num
});
</script>
//父组件
<template>
<Child ref="child" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
import Child from '@/components/Child.vue'
let child = ref(null);
onMounted(() => {
console.log(child.value.msg); // Child Components
console.log(child.value.num); // 123
})
</script>
父子组件通信
defineProps 用来接收父组件传来的 props ; defineEmits 用来声明触发的事件。
//父组件
<template>
<div class="wrapper">
<Child :page="page"
@pageFn="pageFn"
ref="childRef"
/>
<button @click="doSth1"> defineExpose</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
import Child from './Child.vue'
const childRef = ref();
const page = ref(1)
const pageFn = (num) => {
page.value += num
}
function doSth1() {
console.log(childRef.value)
childRef.value.doSth();
}
</script>
<style scoped>
div {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
</style>
//子组件
<template>
<!-- <p>子</p>-->
<button @click="butFn">改变page值: {{ page }} </button>
</template>
<script setup>
// defineEmits,defineProps无需导入,直接使用
const props = defineProps({
page: Number
}); //接收父组件传来的值
const emit = defineEmits(["pageFn"]); //定义一个变量来接收父组件传来的方法
const butFn = () => {
emit("pageFn", props.page)
}
function doSth() {
console.log(333,'defineExpose');
}
defineExpose({ doSth });
// 获取父组件传递过来的数据
console.log(props.page, "parent value"); // parent value
</script>
<style scoped>
button {
margin: 20px;
}
</style>
监听、计算属性
watch(
source, // 必传,要侦听的数据源
callback // 必传,侦听到变化后要执行的回调函数
// options // 可选,一些侦听选项
)
<template>
<div>
<p><input type="number" v-model="count"></p>
<p><input type="number" v-model="plusOne"></p>
</div>
</template>
<script setup>
import {ref, computed, watchEffect, watch} from 'vue';
const count = ref(0);
//创建一个只读的计算属性 ref:
// const plusOne = computed(() => count.value + 1)
// 创建一个可写的计算属性 ref
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
//定义监听,使用同上 //...some code else
watchEffect(() => console.log(count.value, "watchEffect"));
watch(count, (val, oldval) => {
console.log(val, oldval, "watch")
}, {immediate: true})
</script>
<style scoped>
div {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
margin: 10px;
}
input {
margin: 10px;
}
</style>
watchEffect和watch区别
1、watch是惰性执行,也就是只有监听的值发生变化的时候才会执行,但是watchEffect不同,每次代码加载watchEffect都会执行(忽略watch第三个参数的配置,如果修改配置项也可以实现立即执行)
2、watch需要传递监听的对象,watchEffect不需要
3、watch只能监听响应式数据:ref定义的属性和reactive定义的对象,如果直接监听reactive定义对象中的属性是不允许的,除非使用函数转换一下
4、watchEffect如果监听reactive定义的对象是不起作用的,只能监听对象中的属性。
useSlots()
和 useAttrs()
获取 slots 和 attrs
注:useContext API 被弃用,取而代之的是更加细分的 api。
可以通过useContext
从上下文中获取 slots 和 attrs。不过提案在正式通过后,废除了这个语法,被拆分成了useAttrs
和useSlots
。
-
useAttrs
:见名知意,这是用来获取 attrs 数据,但是这和 vue2 不同,里面包含了class
、属性
、方法
。
<template>
<component v-bind='attrs'></component>
</template>
<srcipt setup lang='ts'>
const attrs = useAttrs();
<script>
-
useSlots
: 顾名思义,获取插槽数据。
使用示例:
// 旧
<script setup>
import { useContext } from 'vue'
const { slots, attrs } = useContext()
</script>
// 新
<script setup>
import { useAttrs, useSlots } from 'vue'
const attrs = useAttrs()
const slots = useSlots()
</script>
其他 Hook Api
-
useCSSModule
:CSS Modules 是一种 CSS 的模块化和组合系统。vue-loader 集成 CSS Modules,可以作为模拟 scoped CSS。允许在单个文件组件的setup
中访问CSS模块。此 api 本人用的比较少,不过多做介绍。 -
useCssVars
: 此 api 暂时资料比较少。介绍v-bind in styles
时提到过。 -
useTransitionState
: 此 api 暂时资料比较少。 -
useSSRContext
: 此 api 暂时资料比较少。
支持 async await 异步
注意在vue3的源代码中,setup执行完毕,函数 getCurrentInstance 内部的有个值会释放对 currentInstance 的引用,await 语句会导致后续代码进入异步执行的情况。所以上述例子中最后一个 getCurrentInstance() 会返回 null,建议使用变量保存第一个 getCurrentInstance() 返回的引用.
<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>
<script setup>
中可以使用顶层 await
。结果代码会被编译成 async setup()
另外,await 的表达式会自动编译成在 await
之后保留当前组件实例上下文的格式。export default {
async setup() {
const res = await fetch(...)
const posts = await res.json()
return {
posts
}
}
}
注意
async setup()
必须与Suspense
组合使用,Suspense
目前还是处于实验阶段的特性。我们打算在将来的某个发布版本中开发完成并提供文档 - 如果你现在感兴趣,可以参照 tests 看它是如何工作的。
定
义组件其他配置
配置项的缺失,有时候我们需要更改组件选项,在setup
中我们目前是无法做到的。我们需要在上方
再引入一个 script
,在上方写入对应的 export
即可,需要单开一个 script。
<script setup>
可以和普通的 <script>
一起使用。普通的 <script>
在有这些需要的情况下或许会被使用到:
-
无法在
<script setup>
声明的选项,例如inheritAttrs
或通过插件启用的自定义的选项。 -
声明命名导出。
-
运行副作用或者创建只需要执行一次的对象。
<script>
// 普通 `<script>`, 在模块范围下执行(只执行一次)
runSideEffectOnce()
// 声明额外的选项
export default {
name: "MyComponent",
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
import HelloWorld from '../components/HelloWorld.vue'
// 在 setup() 作用域中执行 (对每个实例皆如此)
// your code
</script>
<template>
<div>
<HelloWorld msg="Vue3 + TypeScript + Vite"/>
</div>
</template>
<keep-alive>
包含或排除或直接检查组件的选项时,你需要这个名字。限制
<script setup>
中的代码依赖单文件组件的上下文。当将其移动到外部的 .js
或者 .ts
文件中的时候,对于开发者和工具来说都会感到混乱。因此, <script setup>
不能和 src
attribute 一起使用。包邮送书
包邮送书
参与规则:
1:从「本文在看」中随机抽取 3名幸运读者
2:在03月3日 开奖前我会随机时间发布一条朋友圈,第 33、66个点赞的人即可中奖
一共送 五 本!!
本书并非简单地介绍两种语言和框架API相关的图书,而是以 Django 与 Vue.js 为载体,诠释前、后端技术生态中最新的优化方案和思路。
本书主要内容包括网络编程与异步并发的基础,软件工程的设计模式在前端技术中的演进,从 Vue.js 的核心开发指南到 Webpack 编译打包的优化经验分享,Web/Service Workers 与 WebSocket 为 Vue.js 实现多线程离线加速,揭秘Vue.js全方位异步惰性加载优化,Django、PyPy、WSGI 和Gevent 的全套异步方案实战,Asyncio、gRPC、Channels 与 Django 的分布式应用实战,Python Agent 技术分享。
快快扫码抢购吧!
开奖时间:2023 年 03 月 03 日 21:00
注意事项:提前加我微信好友,避免开奖后联系不到导致机会作废
发表评论