经验使你在第二次犯相同错误时及时发现。 —— 琼斯
- 需要导入再使用。(使用ts需借助自动推导)
- defineComponent 返回传递给它的对象或有一个有合成类型的构造函数。用于类型推导,简化类型定义。
- 只要是vue本身API(比如setup或vue2.x的配置项),defineComponent 都可以自动推导。在编写组件中,只需维护自定义的数据类型,专注于业务。
- 参数:[ 具有组件选项的对象 | setup函数]
// 栗子
import { defineComponent } from 'vue'
// vue2.x 配置项
export default defineComponent({
data() {
return { count: 1 }
},
methods: {
increment() {
this.count++
}
}
})
// vue3
export default defineComponent({
setup (props, context) {
// ...
return {
// ...template 中用到的业务数据
}
}
})
// 参数为 setup函数 --- 备忘
const HelloWorld = defineComponent(function HelloWorld () {
return {
// ...template 中用到的业务数据
}
})
- 一个组件选项,创建组件前执行,作为入口点
3.x中,整个组件相关的业务代码,都可放到 setup 里编写。因为setup后其他的生命周期才会被启用
- 参数(props,context)
props :它是响应式的(只要不解构它,或者使用 toRef / toRefs 进行响应式解构),当传入新的 prop 时,它将被更新。
context :一个普通的对象,它暴露三个组件的 property(attrs, slots, emit)
官方解释: “setup 是围绕 beforeCreate 和 created 生命周期钩子运行”
3.x中用setup的周期可以替代2.x版本中的beforeCreate和created的两个钩子
3.x中,生命周期必须先导入再使用的,且必需放setup中执行
<script setup>script>
<template>
<div>
<h2>父组件!h2>
<Child />
div>
template>
<script>
import Child from './Child.vue'
export default {
component: {
Child // 注册子组件后方可在template中使用
}
}
script>
(2)vue3语法
<template>
<div>
<h2>父组件!h2>
<Child />
div>
template>
<script>
import { defineComponent, ref } from 'vue';
import Child from './Child.vue'
export default defineComponent({
components: {
Child // 注册子组件后方可在template中使用
},
setup() {
return {
}
}
});
script>
(3)setup script语法
<template>
<div>
<h2>父组件!-setup scripth2>
<Child />
div>
template>
<script setup>
import Child from './Child.vue'
// 省略了子组件注册的过程,import后可直接在template中使用
script>
<template>
<div>
<h2 @click="addCount">购买数量 {{ count}} 件h2>
div>
template>
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
setup() {
const count = ref(1)
const addCount = () => {
count.value++
}
return {
count,
addCount
}
}
});
script>
(3)setup script语法
<template>
<div>
<h2 @click="addCount">购买数量 {{ count}} 件h2>
div>
template>
<script setup>
import { ref } from 'vue';
const count = ref(1)
const addCount = () => {
count.value++
}
script>
// father
<template>
<div class="radio-card">
<radio-card name="规格" :options="standards" @select="changeOption"/>
div>
template>
<script setup>
import { reactive } from 'vue'
import RadioCard from './RadioCard.vue'
const standards = reactive([])
const changeOption = (option) => {
console.log(option)
}
script>
//child
<template>
<div class="radio-card">
<header class="card-header">
<div class="title"> {{ name }} div>
header>
<main class="card-body">
<slot>
<div class="radio-group">
<template v-for="option in options">
<van-button
class="radio-item"
@touchstart="onSelect(option)"
>{{option.value}}van-button>
template>
div>
slot>
main>
div>
template>
<script setup>
// import { useContext, defineProps, defineEmits } from 'vue'; 待验证-useContext放最后import会报错?
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
name: {
type: String,
default: () => ''
},
options: {
type: Array,
default: () => []
}
});
const emit = defineEmits(['select'])
const onSelect = (option) => {
emit('select', option)
}
script>
总结:
- setup() 在创建组件之前执行,因此在其中不能使用this。也就不能在setup里用this取对应熟悉数据、方法、computed计算属性里的数据。
- setup可以借助props和context参数,读取属性props,attrs,slots,emit。
- props响应式,慎用解构;context可普通对象解构。eg.const { attrs, slots, emit } = context
如下图:
(1) 编写自定义组件,import了“defineEmits”、“defineProps”、“defineExpose”,终端提示这些api是个编译宏,不再需要导入。
(2) 是因为“defineEmits”、“defineProps”、“defineExpose”是使用setup script语法是定义对应属性、事件等的API,在
vue规则:props 是单向向下绑定的,子组件不能修改props接收过来的外部数据。
- 如果在子组件中修改 props ,Vue会向你发出一个警告。(无法通过修改子组件的props来更改父组件。)而若需要在子组件更新数据时通知父组件同步更新,需要结合$emit和v-on实现。
- 而sync修饰符的作用则是简化事件声明及监听的写法。
如下栗子,比较sync和正常修改数据通知外层的写法:
// 父组件
<template>
<div> 数量: {{num}}div>
<ChildComponent :num.sync="num" />
template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
component: {
ChildComponent
},
data() {
return {
num: 1
}
}
}
scrip>
//子组件
<template>
<div @click="addNum"> 接收数量: {{num}}div>
template>
<script>
export default {
props: ['num'],
// data() {
// return {
// childNum: this.num
// }
// },
methods: {
addNum() {
// this. childNum++
// this.$emit('increase', this. childNum)
this.$emit('update:num', this.num + 1)
}
}
}
scrip>
vue3中,通过 v-model:propName 实现自定义组件的间数据的双向绑定。看栗子:
// 子组件 ActionSheet.vue
<template>
<div class="ds-action-sheet">
<van-action-sheet
v-model:show="visible"
:title="title"
:closeable="closeable"
:close-on-click-overlay="closeOnClickOverlay"
:actions="actions"
@select="onSelect"
@close="onClose"
@click-overlay="onClickOverlay"
@cancel="onCancel"
>
<slot>slot>
van-action-sheet>
div>
template>
<script setup>
const props = defineProps({
visible: {
type: Boolean,
default: () => false
},
// 是否在点击遮罩层后关闭
closeOnClickOverlay: {
type: Boolean,
default: () => true
},
title: {
type: String,
default: () => ''
},
// 面板选项列表
actions: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['update:visible', 'select', 'close', 'click-overlay'])
const onSelect = () => {
emit('select')
}
const onClose = () => {
// 关键句,父组件则可通过 v-model:visible 同步子组件更新后的数据
emit('update:visible')
emit('close')
}
const onCancel = () => {
// cancel事件,修复打包后在微信浏览器无法更新visible值问题
emit('update:visible')
emit('cancel')
}
const onClickOverlay = () => {
emit('click-overlay')
if(props.closeOnClickOverlay) {
// click-overlay事件 修复打包后在微信浏览器无法更新visible值问题
emit('update:visible')
}
}
script>
// 父组件
<template>
<van-button @click="showActionSheet">弹出弹层van-button>
<ds-action-sheet
title="do a test"
v-model:visible="mVisilbe"
>
<div>弹层内容div>
ds-action-sheet>
template>
<script setup>
import { ref } from 'vue';
import DsActionSheet from './ActionSheet.vue';
const mVisilbe = ref(false)
const showActionSheet = () => {
mVisilbe.value = true
}
script>
总结:
- 父组件通过 “v-model:绑定的属性名”传递数据属性,支持绑定多个属性;
- 子组件配置emits,通过 “update:属性名” 的格式定义更新事件
问题:打包后在微信浏览器中点击弹层的关闭按钮或点击遮罩层(已配置点击遮罩层关闭弹层配置项),无法正常关闭弹层。(本地运行可正常关闭)
解决:在对应事件中,添加 emit(‘update:visible’) ,手动添加更新visible值。见上栗子中的47行、52行及onClickOverlay 函数中的条件判断处理
在 3.x 中,处理通用文本格式的过滤器功能已删除,不再支持。建议用方法调用或计算属性替换它们。
<template>
<div>
<span>总价:span>
<span class="unit">¥span>
<span class="price">
{{ amountSeparatorFormatter(totalPrice) }}
span>
div>
template>
<script setup>
const amountSeparatorFormatter = (value, fixedNum = 2) => {
if (!value) {
return;
}
if (isNaN(value)) {
return value;
}
let val = Number(value).toFixed(fixedNum).toString();
val = val.replace(/\./, ',')
while (/\d{4}/.test(val)) {
val = val.replace(/(\d+)(\d{3}\,)/, '$1,$2')
}
return val.replace(/\,(\d*)$/, '.$1')
}
script>
若应用中存在全局过滤器,可以定义全局属性在所有组件应用次全局属性。eg:
<template>
<h1>使用全局属性h1>
<p>{{ $filters. currencyShow(money) }}p>
template>
// main.js
const app = createApp(App)
app.config.globalProperties.$filters = {
currencyShow(value) {
return '¥' + value
}
}
转载请标明出处!