简体中文
我们可以对一段要复用的js/uts逻辑代码进行封装,抽出function、module等形式。
那么涉及UI的复用时,该如何抽象?
这就是vue的组件机制,把视图template、script、style都封装到独立的uvue组件文件中,在其他需要的地方使用组件的名称进行引用。
每个组件,包括如下几个部分:以组件名称为标记的开始标签和结束标签、组件text内容、组件属性、组件属性值。
组件还可以封装方法、事件、插槽,提供了组件的生命周期,提供了组件和页面的互通信机制,满足了各种高级需求。
如果您还不了解这些概念,请务必先阅读 组件概述文档
uni-app x 组件基于 vue 单文件组件规范,一个组件内,有 3 个根节点标签:
<template>:组件的模板内容<script>:组件的脚本代码<style>:组件的样式组件的内容构成和页面大体上一致,都符合vue的sfc规范。
事实上,一个在pages.json注册的页面uvue文件,也可以被当做一个组件引入到其他页面。
组件和页面的差别有:
onLoad、onShow 等页面生命周期,比如$setPageStyle等API。mounted、unmounted 等组件生命周期,比如页面和组件通信的API。项目根目录/components 目录上右键(如果没有,在根目录新建一个 components 目录即可),选择 新建组件,输入组件名称,选择一个模板;可勾选创建同名目录,将组件放在同名目录下。项目根目录/uni_modules 目录上右键(如果没有,在根目录新建一个 uni_modules 目录即可),选择 新建uni_modules插件,输入插件ID,分类选择前端组件-通用组件;将组件放在和插件ID同名的目录下。pages 目录 下的任意地方创建 .uvue/.vue 文件并编写组件代码注意事项
uni-app x 项目支持使用 .vue、.uvue 文件作为组件使用,但同文件名的两个文件同时存在,.uvue 文件会优先编译。
传统vue组件,需要安装、引用、注册,三个步骤后才能使用组件。easycom 将其精简为一步。
只要组件安装在项目的 components 目录下或 uni_modules/插件 id/components/插件 id/插件 id.uvue 目录下,并符合 组件名称/组件名称.(vue|uvue) 目录结构。就可以不用引用、注册,直接在页面中使用。
比如 uni-loading,它导入到项目后,存放在了目录 /uni_modules/uni-loading/components/uni-loading/uni-loading.uvue
同时它的组件名称也叫 uni-loading,所以这样的组件,不用在 script 里注册和引用。如下:
<template>
<view>
<uni-loading></uni-loading><!-- 这里会显示一个loading -->
</view>
</template>
<script>
// 这里不用import引入,也不需要在components内注册组件。template里就可以直接用
// ...
</script>
这里出现了uni_module的概念,简单说下,它是uni-app的一种包管理方案。
uni_module其实不止服务于组件,它可以容纳组件、script库、页面、项目等所有DCloud插件市场所支持的种类。
在HBuilderX中点右键可方便的更新插件,插件作者也可以方便的上传插件。
uni_module有详细的专项文档,请另行查阅uni_module规范。
如果你的组件不满足easycom标准的目录规范,还有一种办法是在pages.json里声明自己的目录规则,以便编译器查找到你的组件。自定义easycom路径规则的详细教程详见
组件标签名首字母大写,驼峰+ComponentPublicInstance,如:
<test/> 类型为:TestComponentPublicInstance
<uni-data-checkbox/> 类型为:UniDataCheckboxComponentPublicInstance
不符合 easycom 规范的组件,则需要手动引入:
<!-- 组件 child.vue -->
<template>
<view>Child Component</view>
</template>
<!-- 页面(与 child.vue 组件在同级目录 -->
<template>
<view>
<child ref="component1"></child>
</view>
</template>
<script setup lang="uts">
// 引入 child 组件
import child from './child.vue'
const component1 = ref<ComponentPublicInstance | null>(null) // 手动引入组件时的类型
</script>
类型为:ComponentPublicInstance
url 地址中携带参数props示例 详情
注意
this.$props 是 Map 类型,需要使用 this.$props["propName"] 来访问. 点操作符来访问inheritAttrs 选项来关闭该行为,详见组合式 API
选项式 API
<template>
<view class="page">
<array-literal :str="str" :num="num" :bool="bool" :obj="obj" :arr="arr" />
<object-type str="str" :num="num" :bool="bool" :obj="obj" :arr="arr" />
<same-name-prop-default-value />
<props-with-defaults />
<!-- #ifdef APP-ANDROID -->
<reference-types :list="[1,2,3]" />
<!-- #endif -->
<!-- #ifndef APP-ANDROID -->
<reference-types :list="['a','b','c']" />
<!-- #endif -->
</view>
</template>
<script setup lang="uts">
import ArrayLiteral from './array-literal-composition.uvue'
import ObjectType from "./object-type-composition.uvue";
import SameNamePropDefaultValue from "./same-name-prop-default-value-composition.uvue";
import PropsWithDefaults from "./props-with-defaults.uvue";
import ReferenceTypes from './reference-types-composition.uvue'
const str = 'str'
const num = 10
const bool = true
const obj = { age: 18 }
const arr = ['a', 'b', 'c']
</script>
示例 详情
组合式 API
选项式 API
<template>
<view class="page">
<view class="row">
<text>子组件传的参数</text>
<text id="value">
{{ value }}
</text>
</view>
<child @callback="callback"></child>
</view>
</template>
<script setup lang="uts">
import child from './child-composition.uvue'
const value = ref('')
const callback = (str: string) => {
value.value = str
}
</script>
<style>
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 10px;
}
</style>
provide/inject 来向下传递参数 示例 详情
组合式 API
选项式 API
<template>
<view class="page">
<inject-comp />
</view>
</template>
<script setup lang="uts">
import InjectComp from '../inject/inject-composition.uvue';
provide('msg', 'hello');
provide('num', 0);
provide('obj', { a: 1 });
provide('arr', [1, 2, 3]);
provide('fn', () : string => 'hello');
</script>
store/index.uts 文件详情
示例 详情
组合式 API
选项式 API
<template>
<view class="page">
<view class="flex-row">
父组件:
<text class="parent-msg">{{ msg }}</text>
</view>
<button class="parent-btn" @click="change">父组件改变数据</button>
<child />
</view>
</template>
<script setup lang="uts">
import child from './components/child.uvue'
import { setComponentMsg, state } from '@/store/index.uts'
const msg = computed<number>((): number => state.componentMsg)
const change = () => {
setComponentMsg(state.componentMsg + 1)
}
</script>
main.uts 中使用 app.config.globalProperties如在 main.uts 中的 createApp 方法中使用:
app.config.globalProperties.globalPropertiesReactiveObj = reactive({
str: 'default reactive string',
num: 0,
bool: false,
} as UTSJSONObject)
示例 详情
组合式 API
选项式 API
<template>
<!-- #ifdef APP -->
<scroll-view style="flex: 1;">
<!-- #endif -->
<view class="uni-padding-wrap">
<text class="mt-10"
>globalProperties string: {{ globalPropertiesStr }}</text
>
<text class="mt-10"
>globalProperties number: {{ globalPropertiesNum }}</text
>
<text class="mt-10"
>globalProperties boolean: {{ globalPropertiesBool }}</text
>
<text class="mt-10"
>globalProperties object: {{ globalPropertiesObj }}</text
>
<text class="mt-10"
>globalProperties null: {{ globalPropertiesNull }}</text
>
<text class="mt-10"
>globalProperties array: {{ globalPropertiesArr }}</text
>
<text class="mt-10"
>globalProperties set: {{ globalPropertiesSet }}</text
>
<text class="mt-10"
>globalProperties map: {{ globalPropertiesMap }}</text
>
<text class="mt-10"
>globalProperties reactiveObj.str:
{{ globalPropertiesReactiveObj['str'] }}</text
>
<text class="mt-10"
>globalProperties reactiveObj.num:
{{ globalPropertiesReactiveObj['num'] }}</text
>
<text class="mt-10"
>globalProperties reactiveObj.boolean:
{{ globalPropertiesReactiveObj['bool'] }}</text
>
<text class="mt-10"
>globalProperties fun 返回值: {{ globalPropertiesFn() }}</text
>
<button @click="updateGlobalProperties" class="mt-10">
update globalProperties
</button>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup lang="uts">
type MyGlobalProperties = {
str : string;
num : number;
bool : boolean;
obj : UTSJSONObject;
null : string | null;
arr : number[];
set : string[];
map : UTSJSONObject;
reactiveObj : UTSJSONObject;
globalPropertiesFnRes: string;
}
const myGlobalProperties = reactive<MyGlobalProperties>({
str: '',
num: 0,
bool: false,
obj: {},
null: null,
arr: [] as number[],
set: [] as string[],
map: {},
reactiveObj: {
str: '',
num: 0,
bool: false,
},
globalPropertiesFnRes: '',
} as MyGlobalProperties)
const instance = getCurrentInstance()!.proxy!
const getGlobalProperties = () => {
myGlobalProperties.str = instance.globalPropertiesStr
myGlobalProperties.num = instance.globalPropertiesNum
myGlobalProperties.bool = instance.globalPropertiesBool
myGlobalProperties.obj = instance.globalPropertiesObj
myGlobalProperties.null = instance.globalPropertiesNull
myGlobalProperties.arr = instance.globalPropertiesArr
myGlobalProperties.set = []
instance.globalPropertiesSet.forEach(item => {
myGlobalProperties.set.push(item)
})
myGlobalProperties.map = {}
instance.globalPropertiesMap.forEach((value: number, key: string) => {
myGlobalProperties.map[key] = value
})
myGlobalProperties.reactiveObj = instance.globalPropertiesReactiveObj
myGlobalProperties.globalPropertiesFnRes = instance.globalPropertiesFn()
}
setTimeout(() => {
// 等待 globalProperties-options resetGlobalProperties 完成
getGlobalProperties()
}, 1000)
const updateGlobalProperties = () => {
instance.globalPropertiesStr = 'new string'
instance.globalPropertiesNum = 100
instance.globalPropertiesBool = true
instance.globalPropertiesObj = {
str: 'new globalProperties obj string',
num: 100,
bool: true,
}
instance.globalPropertiesNull = 'not null'
instance.globalPropertiesArr = [1, 2, 3]
instance.globalPropertiesSet = new Set(['a', 'b', 'c'])
instance.globalPropertiesMap = new Map([['a', 1], ['b', 2], ['c', 3]])
instance.globalPropertiesReactiveObj['str'] = 'new reactive string'
instance.globalPropertiesReactiveObj['num'] = 200
instance.globalPropertiesReactiveObj['bool'] = true
getGlobalProperties()
}
defineExpose({
myGlobalProperties,
updateGlobalProperties
})
</script>
<style>
.uni-padding-wrap {
padding: 10px 10px 40px 10px;
}
</style>
上述 页面与组件通信 方法同样适用于父组件与子组件通信。
easycom 组件方法 在调用组件方法的时候如报错
error: Reference has a nullable type则需要使用?.操作符(如:a?.b?.())。
easycom组件,用法和内置组件一样。也是使用 this.$refs 获取组件并转换为组件的类型,通过 .操作符 调用组件方法或设置属性。
语法
(this.$refs['组件ref属性值'] as 驼峰ComponentPublicInstance)?.foo?.();
示例 详情
组合式 API
选项式 API
<template>
<view>
<call-easy-method ref="callEasyMethod1"></call-easy-method>
<custom-call-easy-method ref="customCallEasyMethod1"></custom-call-easy-method>
<test-getter-setter-composition ref="getterAndSetterComposition"></test-getter-setter-composition>
<test-getter-setter-options ref="getterAndSetterOptions"></test-getter-setter-options>
<view>
<text>getter-setter: <text id="getterAndSetter">{{JSON.stringify(getterAndSetterResult)}}</text></text>
</view>
</view>
</template>
<script setup lang="uts">
const callEasyMethod1 = ref<CallEasyMethodComponentPublicInstance | null>(null)
const customCallEasyMethod1 = ref<CustomCallEasyMethodComponentPublicInstance | null>(null)
const callMethod = () => {
// 调用组件的 foo 方法
customCallEasyMethod1.value?.foo?.()
}
const callMethod1 = () => {
// 调用组件的 foo1 方法
callEasyMethod1.value?.foo1?.()
}
const callMethod2 = () => {
// 调用组件的 foo2 方法并传递 1个参数
callEasyMethod1.value?.foo2?.(Date.now())
}
const callMethod3 = () => {
// 调用组件的 foo3 方法并传递 2个参数
callEasyMethod1.value?.foo3?.(Date.now(), Date.now())
}
const callMethod4 = () => {
// 调用组件的 foo4 方法并传递 callback
callEasyMethod1.value?.foo4?.(() => {
console.log('callback')
})
}
const callMethod5 = () => {
// 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
const result = callEasyMethod1.value?.foo5?.('string1') as string
console.log(result) // string1
}
const callMethodTest = (text: string): string | null => {
const result = callEasyMethod1.value?.foo5?.(text) as string
return result
}
const callCustomMethodTest = (): string | null => {
const result = customCallEasyMethod1.value?.foo?.() as string
return result
}
const getterAndSetterComposition = ref<TestGetterSetterCompositionComponentPublicInstance | null>(null)
const getterAndSetterOptions = ref<TestGetterSetterOptionsComponentPublicInstance | null>(null)
const getterAndSetterResult = ref<number[]>([])
const callGetterAndSetterCompositionGetCount = (): number => {
return getterAndSetterComposition.value!.getCount()
}
const callGetterAndSetterCompositionGetCountWithCallMethod = (): number => {
return getterAndSetterComposition.value!.$callMethod('getCount') as number
}
const callGetterAndSetterOptionsGetCount = (): number => {
return getterAndSetterOptions.value!.getCount()
}
const callGetterAndSetterOptionsGetCountWithCallMethod = (): number => {
return getterAndSetterOptions.value!.$callMethod('getCount') as number
}
const callGetterAndSetterCompositionSetCount = (count: number) => {
getterAndSetterComposition.value!.setCount(count)
}
const callGetterAndSetterCompositionSetCountWithCallMethod = (count: number) => {
getterAndSetterComposition.value!.$callMethod('setCount', count)
}
const callGetterAndSetterOptionsSetCount = (count: number) => {
getterAndSetterOptions.value!.setCount(count)
}
const callGetterAndSetterOptionsSetCountWithCallMethod = (count: number) => {
getterAndSetterOptions.value!.$callMethod('setCount', count)
}
const callGetterAndSetter = (): number[] => {
const result: number[] = []
callGetterAndSetterCompositionSetCount(1)
result.push(callGetterAndSetterCompositionGetCount())
callGetterAndSetterCompositionSetCountWithCallMethod(2)
result.push(callGetterAndSetterCompositionGetCountWithCallMethod())
callGetterAndSetterOptionsSetCount(3)
result.push(callGetterAndSetterOptionsGetCount())
callGetterAndSetterOptionsSetCountWithCallMethod(4)
result.push(callGetterAndSetterOptionsGetCountWithCallMethod())
getterAndSetterResult.value = result
return result
}
const delay = (): Promise<string> =>
new Promise((resolve, _) => {
setTimeout(() => {
resolve('')
}, 1000)
})
const call = async (): Promise<void> => {
callGetterAndSetter()
callMethod()
callMethod1()
await delay()
callMethod2()
await delay()
callMethod3()
await delay()
callMethod4()
await delay()
callMethod5()
}
onReady(() => {
call()
})
defineExpose({
callMethodTest,
callCustomMethodTest
})
</script>
uni_modules easycom 组件方法 HBuilderX 3.97+使用 ref 属性拿到组件实例,调用 easycom 组件方法时不需要使用 $callMethod 方法,直接使用点操作符即可 .
在调用组件方法的时候如报错
error: Reference has a nullable type则需要使用?.操作符(如:a?.b?.())。
示例 详情
组合式 API
选项式 API
<template>
<view>
<call-easy-method-uni-modules ref="callEasyMethod1"></call-easy-method-uni-modules>
<!-- #ifdef APP-ANDROID || APP-IOS || APP-HARMONY -->
<!-- 在兼容组件中 ios 返回非普通对象数据,比如 Map 数据时候会被 jscore 统一处理为 object,和安卓产生了差异 -->
<!-- 测试用例用来验证返回数据特殊,安卓和其他平台无此限制 -->
<view>---</view>
<test-props id="btn1" :numList="numList" :objList='objList' @buttonclick='onButtonClick'
@numListChange='numListChange' @objListChange='objListChange'
style="width: 80px;height: 30px;background-color: lightblue"></test-props>
<view style="flex-direction: row ;">
<text>isNumListValid: </text>
<text id='isNumListValid'>{{isNumListValid}}</text>
</view>
<view style="flex-direction: row ;">
<text>isObjListValid: </text>
<text id='isObjListValid'>{{isObjListValid}}</text>
</view>
<!-- #endif -->
</view>
</template>
<script setup lang="uts">
import { testInOtherFile } from './call-method-easycom-uni-modules'
// #ifdef APP-ANDROID || APP-IOS
import { PropsChangeEvent } from '@/uni_modules/test-props'
// #endif
const delay = () : Promise<string> =>
new Promise((resolve, _) => {
setTimeout(() => {
resolve('')
}, 1000)
})
const callEasyMethod1 = ref<CallEasyMethodUniModulesComponentPublicInstance | null>(null)
const numList = ref<number[]>([1]) // 传递 props
const objList = ref<any[]>([])
const isNumListValid = ref(false)
const isObjListValid = ref(false)
const callMethod1 = () => {
// 调用组件的 foo1 方法
callEasyMethod1.value?.foo1?.()
}
const callMethod2 = () => {
// 调用组件的 foo2 方法并传递 1个参数
callEasyMethod1.value?.foo2?.(Date.now())
}
const callMethod3 = () => {
// 调用组件的 foo3 方法并传递 2个参数
callEasyMethod1.value?.foo3?.(Date.now(), Date.now())
}
const callMethod4 = () => {
// 调用组件的 foo4 方法并传递 callback
callEasyMethod1.value?.foo4?.(() => {
console.log('callback')
})
}
const callMethod5 = () => {
// 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
const result = callEasyMethod1.value?.foo5?.('string5') as string
console.log(result) // string1
}
const callMethodTest = (text : string) : string | null => {
const result = callEasyMethod1.value?.foo5?.(text) as string
return result
}
const callMethodInOtherFile = (text : string) : string => {
return testInOtherFile(callEasyMethod1.value!, text)
}
// #ifdef APP-ANDROID
const numListChange = (res : Map<string, Map<string, any>>) => {
const value = res['detail']!['value'] as number[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isNumListValid.value = isArray && isLengthGt0
}
// #endif
// #ifdef APP-IOS || APP-HARMONY
const numListChange = (res : any) => {
const value = res['detail']!['value'] as number[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isNumListValid.value = isArray && isLengthGt0
}
// #endif
// #ifdef APP-ANDROID
const objListChange = (res : Map<string, Map<string, any>>) => {
const value = res['detail']!['value'] as any[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isObjListValid.value = isArray && isLengthGt0
}
// #endif
// #ifdef APP-IOS || APP-HARMONY
const objListChange = (res : any) => {
const value = res['detail']!['value'] as any[]
const isArray = Array.isArray(value)
const isLengthGt0 = value.length > 0
isObjListValid.value = isArray && isLengthGt0
}
// #endif
const onButtonClick = () => {
// 改变 props: 观察 props 返回值为非响应式值
numList.value = [3, 2, 1]
objList.value = [{ id: '3' }, { id: '4' }]
}
const call = async () : Promise<void> => {
callMethod1()
await delay()
callMethod2()
await delay()
callMethod3()
await delay()
callMethod4()
await delay()
callMethod5()
}
onReady(() => {
call()
})
defineExpose({
callMethodTest,
callMethodInOtherFile,
onButtonClick
})
</script>
ref 属性搭配 $callMethod 方法 如果不是内置组件,也不是easycom组件,那么无法使用.操作符了。
此时需使用 this.$refs 获取组件实例,然后通过 $callMethod 调用组件的方法。也就是把组件的方法名、参数,当做callMethod的参数来传递。此时也就没有.操作符那样的代码提示和校验了。
callMethod可用于所有自定义组件,包括easycom组件也可以使用,只不过easycom组件有更简单的用法。
语法
(this.$refs['组件ref属性值'] as ComponentPublicInstance)?.$callMethod('方法名', ...args)
组件类型
ComponentPublicInstance
示例 详情
组合式 API
选项式 API
<template>
<view class="page">
<child ref="childRef" />
</view>
</template>
<script setup lang="uts">
import Child from './child-composition.uvue'
const childRef = ref<ComponentPublicInstance | null>(null)
const str = ref('parent str')
const num = ref(1)
const getNum = () : number => { return num.value }
const instance = getCurrentInstance()!.proxy!
const callMethodByChild = () : number => {
const childComponent = instance.$refs['childRef'] as ComponentPublicInstance
return childComponent.$parent!.$callMethod('getNum') as number
}
defineExpose({
str,
getNum,
callMethodByChild
})
</script>
注意:
4.0 版本开始支持 $callMethod 调用 defineExpose 导出的方法4.13 版本开始支持 $callMethod 调用 defineExpose 导出的方法$callMethod 调用 defineExpose 导出的方法<script setup>下的变量名不能和 easycom 组件的驼峰写法重复,比如:组件名为test-canvas,那么不能在<script setup>中定义const testCanvas变量名。$callMethod 调用性能低于easycom组件的强类型调用,如果遇到高频调用场景,建议使用easycom组件的强类型调用方法。使用 this.$refs 获取组件并as转换为组件对应的element类型,通过 .操作符 调用组件方法或设置属性。
语法
(this.$refs['组件ref属性值'] as Uni[xxx]Element)?.foo?.();
内置组件的element类型规范
Uni组件名(驼峰)Element
如:
<button>: UniButtonElement
<picker-view>: UniPickerViewElement
示例 详情
组合式 API
选项式 API
<template>
<view>
<slider :show-value="true" ref="sliderRef"></slider>
</view>
</template>
<script setup lang="uts">
const sliderRef = ref<UniSliderElement | null>(null)
onReady(() => {
sliderRef.value!.value = 80
})
const callMethodTest = (text: string): string | null => {
sliderRef.value?.setAttribute('str', text)
const result = sliderRef.value?.getAttribute('str') as string
return result
}
defineExpose({
callMethodTest
})
</script>
bug&tips
组件名(驼峰)Element 方式相同。目前没有代码提示。选项式 API 和 组合式 API 在监听页面生命周期时有所不同
比如选项式 API 中的
onShow、onHide监听页面生命周期在组合式 API 中分别对应onPageShow、onPageHide(在组合式 API 时会和 App 的生命周期冲突)具体请查看 页面生命周期
| 组件中监听应用生命周期 | Android | HarmonyOS | iOS | Web | 微信小程序 |
|---|---|---|---|---|---|
| onAppHide | 4.11 | x | x | 4.11 | x |
| onAppShow | 4.11 | x | x | 4.11 | x |
注意
onPageHide、onPageShow 需要写在选项式的 setup 函数或者组合式 <script setup> 中才能生效
示例 详情
组合式 API
选项式 API
<script lang="uts" setup>
// #ifdef APP-ANDROID
onAppHide(() => {
console.log('组件监听应用生命周期 => onAppHide')
})
onAppShow((onShowOptions: OnShowOptions) => {
console.log('组件监听应用生命周期 => onAppShow => onShowOptions', onShowOptions)
})
// #endif
onPageShow(() => {
console.log('组件监听页面生命周期 => onPageShow')
})
onPageHide(() => {
console.log('组件监听页面生命周期 => onPageHide')
})
</script>
<template>组件监听页面、应用生命周期(组合式 API)</template>
| Web | 微信小程序 | Android | iOS | HarmonyOS | 描述 | |
|---|---|---|---|---|---|---|
| beforeCreate | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在组件实例初始化完成之后立即调用。 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。 |
| created | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在组件实例处理完所有与状态相关的选项后调用。 在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。 然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。 |
| beforeMount | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在组件被挂载之前调用。 相关的 render 函数首次被调用。 当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。 它即将首次执行 DOM 渲染过程。 |
| mounted | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在组件被挂载之后调用。 el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。 如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。 |
| beforeUpdate | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。 数据更新时调用,发生在虚拟 DOM 打补丁之前。 这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。 |
| updated | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在组件因为一个响应式状态变更而更新其 DOM 树之后调用。 父组件的更新钩子将在其子组件的更新钩子之后调用。 这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。 如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。 |
| beforeUnmount | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在一个组件实例被卸载之前调用。 当这个钩子被调用时,组件实例依然还保有全部的功能。 |
| unmounted | 4.0 | 4.41 | 3.9 | 4.11 | 4.61 | 在一个组件实例被卸载之后调用。 可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。 |
| errorCaptured | 4.0 | 4.41 | x | x | x | 在捕获了后代组件传递的错误时调用。 这个钩子带有三个实参:错误对象、触发该错误的组件实例,以及一个说明错误来源类型的信息字符串。 这个钩子可以通过返回 false 来阻止错误继续向上传递。 |
| renderTracked | 4.0 | 4.41 | x | x | x | 在一个响应式依赖被组件的渲染作用追踪后调用。 跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。 此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。 |
| renderTriggered | 4.0 | 4.41 | x | x | x | 在一个响应式依赖被组件触发了重新渲染之后调用。 当虚拟 DOM 重新渲染为 triggered.Similarly 为renderTracked,接收 debugger event 作为参数。 此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。 |
| activated | 4.0 | x | 4.0 | 4.11 | 4.61 | 若组件实例是 <KeepAlive> 缓存树的一部分,当组件被插入到 DOM 中时调用。 keep-alive 组件激活时调用。 |
| deactivated | 4.0 | x | 4.0 | 4.11 | 4.61 | 若组件实例是 <KeepAlive> 缓存树的一部分,当组件从 DOM 中被移除时调用。 keep-alive 组件停用时调用。 |
| serverPrefetch | x | x | x | x | x | 当组件实例在服务器上被渲染之前要完成的异步函数。 如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。 |
| Web | 微信小程序 | Android | iOS | HarmonyOS | HarmonyOS(Vapor) | 描述 | |
|---|---|---|---|---|---|---|---|
| onMounted() | 4.0 | 4.41 | 4.0 | 4.11 | 4.61 | - | el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。 如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。 |
| onUpdated() | 4.0 | 4.41 | 4.0 | 4.11 | 4.61 | - | 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。 |
| onUnmounted() | 4.0 | 4.41 | 4.0 | 4.11 | 4.61 | - | 在一个组件实例被卸载之后调用。 |
| onBeforeMount() | 4.0 | 4.41 | 4.0 | 4.11 | 4.61 | - | 在挂载开始之前被调用:相关的 render 函数首次被调用。 |
| onBeforeUpdate() | 4.0 | 4.41 | 4.0 | 4.11 | 4.61 | - | 数据更新时调用,发生在虚拟 DOM 打补丁之前。 这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。 |
| onBeforeUnmount() | 4.0 | 4.41 | 4.0 | 4.11 | 4.61 | - | 在一个组件实例被卸载之前调用。 |
| onErrorCaptured() | x | - | x | x | x | - | 注册一个钩子,在捕获了后代组件传递的错误时调用。 |
| onRenderTracked() | x | - | x | x | x | - | 注册一个调试钩子,当组件渲染过程中追踪到响应式依赖时调用。 |
| onRenderTriggered() | x | - | x | x | x | - | 注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。 |
| onActivated() | 4.0 | x | x | x | x | - | keep-alive 组件激活时调用。 |
| onDeactivated() | 4.0 | x | x | x | x | - | keep-alive 组件停用时调用。 |
| onServerPrefetch() | x | x | x | x | x | - | 注册一个异步函数,在组件实例在服务器上被渲染之前调用。 如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。 这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。 |
| onRecycle() | x | x | x | x | x | 5.0 | 组件回收时的生命周期钩子 |
| onReuse() | x | x | x | x | x | 5.0 | 组件复用时的生命周期钩子 |
示例 详情
组合式 API
选项式 API
<template>
title: {{ title }}
<button class="component-lifecycle-btn mt-10" @click="updateTitle">
updateTitle
</button>
</template>
<script setup lang='uts'>
import { state, setLifeCycleNum } from '@/store/index.uts'
const title = ref('component for composition API lifecycle test')
const emit = defineEmits<{
(e : 'updateIsScroll', val : boolean) : void
}>()
onLoad((_ : OnLoadOptions) => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 100)
})
onPageShow(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 10)
})
onReady(() => {
// 自动化测试
// TODO: onReady 未触发
setLifeCycleNum(state.lifeCycleNum + 10)
})
onPullDownRefresh(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 10)
})
onPageScroll((_) => {
// 自动化测试
emit('updateIsScroll', true)
})
onReachBottom(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 10)
})
onBackPress((_ : OnBackPressOptions) : boolean | null => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 10)
return null
})
onPageHide(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 10)
})
onUnload(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 100)
})
onBeforeMount(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1)
console.log('component for lifecycle test onBeforeMount')
})
onMounted(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1)
console.log('component for lifecycle test mounted')
})
onBeforeUpdate(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1)
console.log('component for lifecycle test beforeUpdate')
})
onUpdated(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1)
console.log('component for lifecycle test updated')
})
onBeforeUnmount(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 1)
console.log('component for lifecycle test beforeUnmount')
})
onUnmounted(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 1)
console.log('component for lifecycle test unmounted')
})
onActivated(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum + 1)
console.log('component for lifecycle test onActivated')
})
onDeactivated(() => {
// 自动化测试
setLifeCycleNum(state.lifeCycleNum - 1)
console.log('component for lifecycle test onDeactivated')
})
const updateTitle = () => {
title.value = 'component for lifecycle test updated'
}
</script>
export default 内部声明,不支持其他位置定义后,在 export default 中引用。PropType 标记类型,详见。type 不支持使用自定义的构造函数。示例 详情
组合式 API
选项式 API
<template>
<view class="page">
<array-literal :str="str" :num="num" :bool="bool" :obj="obj" :arr="arr" />
<object-type str="str" :num="num" :bool="bool" :obj="obj" :arr="arr" />
<same-name-prop-default-value />
<props-with-defaults />
<!-- #ifdef APP-ANDROID -->
<reference-types :list="[1,2,3]" />
<!-- #endif -->
<!-- #ifndef APP-ANDROID -->
<reference-types :list="['a','b','c']" />
<!-- #endif -->
</view>
</template>
<script setup lang="uts">
import ArrayLiteral from './array-literal-composition.uvue'
import ObjectType from "./object-type-composition.uvue";
import SameNamePropDefaultValue from "./same-name-prop-default-value-composition.uvue";
import PropsWithDefaults from "./props-with-defaults.uvue";
import ReferenceTypes from './reference-types-composition.uvue'
const str = 'str'
const num = 10
const bool = true
const obj = { age: 18 }
const arr = ['a', 'b', 'c']
</script>
“透传 attribute”指的是传递给一个组件,却没有被该组件声明为 props 或 emits 的 attribute 或者 v-on 事件监听器。最常见的例子就是 class、style 和 id。
当一个组件以单个元素为根作渲染时,透传的 attribute 会自动被添加到根元素上。举例来说,假如我们有一个 <MyButton> 组件,它的模板长这样:
<!-- <MyButton> 的模板 -->
<button>Click Me</button>
一个父组件使用了这个组件,并且传入了 class:
<MyButton class="large" />
最后渲染出的 DOM 结果是:
<button class="large">Click Me</button>
这里,<MyButton> 并没有将 class 声明为一个它所接受的 prop,所以 class 被视作透传 attribute,自动透传到了 <MyButton> 的根元素上。
如果一个子组件的根元素已经有了 class 或 style attribute,它会和从父组件上继承的值合并。如果我们将之前的 <MyButton> 组件的模板改成这样:
<!-- <MyButton> 的模板 -->
<button class="btn">Click Me</button>
则最后渲染出的 DOM 结果会变成:
<button class="btn large">Click Me</button>
同样的规则也适用于 v-on 事件监听器:
<MyButton @click="onClick" />
click 监听器会被添加到 <MyButton> 的根元素,即那个原生的 <button> 元素之上。当原生的 <button> 被点击,会触发父组件的 onClick 方法。同样的,如果原生 button 元素自身也通过 v-on 绑定了一个事件监听器,则这个监听器和从父组件继承的监听器都会被触发。
有些情况下一个组件会在根节点上渲染另一个组件。例如,我们重构一下 <MyButton>,让它在根节点上渲染 <BaseButton>:
<!-- <MyButton/> 的模板,只是渲染另一个组件 -->
<BaseButton />
此时 <MyButton> 接收的透传 attribute 会直接继续传给 <BaseButton>。
请注意:
<MyButton> 上声明过的 props 或是针对 emits 声明事件的 v-on 侦听函数,换句话说,声明过的 props 和侦听函数被 <MyButton>“消费”了。<BaseButton>。如果你不想要一个组件自动地继承 attribute,你可以在组件选项中设置 inheritAttrs: false。
你也可以直接在 <script setup> 中使用 defineOptions:
<script setup>
defineOptions({
inheritAttrs: false
})
// ...setup 逻辑
</script>
最常见的需要禁用 attribute 继承的场景就是 attribute 需要应用在根节点以外的其他元素上。通过设置 inheritAttrs 选项为 false,你可以完全控制透传进来的 attribute 被如何使用。
这些透传进来的 attribute 可以在模板的表达式中直接用 $attrs 访问到。
<span>Fallthrough attribute: {{ $attrs }}</span>
这个 $attrs 对象包含了除组件所声明的 props 和 emits 之外的所有其他 attribute,例如 class,style,v-on 监听器等等。
有几点需要注意:
props 有所不同,透传 attributes 在 JavaScript 中保留了它们原始的大小写,所以像 foo-bar 这样的一个 attribute 需要通过 $attrs['foo-bar'] 来访问。@click 这样的一个 v-on 事件监听器将在此对象下被暴露为一个函数 $attrs.onClick。和单根节点组件有所不同,有着多个根节点的组件没有自动 attribute 透传行为。如果 $attrs 没有被显式绑定,将会抛出一个运行时警告。
<CustomLayout id="custom-layout" @click="changeValue" />
如果 <CustomLayout> 有下面这样的多根节点模板,由于 Vue 不知道要将 attribute 透传到哪里,所以会抛出一个警告。
<header>...</header>
<main>...</main>
<footer>...</footer>
如果 $attrs 被显式绑定,则不会有警告:
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
在 uni-app js 引擎版中,非 Web端 只能用于获取自定义组件,不能用于获取内置组件实例(如:view、text)。
在 uni-app x 中,内置组件会返回组件根节点的引用,自定义组件会返回组件实例。
注意事项:
ref 属性,将获取到最后一个节点或组件实例的引用。v-for 循环时,绑定 ref 属性会获取到节点或组件实例的集合。uni-app x 中,要访问 $refs 中的属性,需要使用索引方式。示例 详情
uni-app x(组合式)
uni-app x(选项式)
uni-app js 引擎版
<template>
<view class="page">
<view class="row">
<text>NodeRef: </text>
<text ref="nodeRef">{{ dataInfo.existRef }}</text>
</view>
<view class="row">
<text>childRef:</text>
<text>{{ dataInfo.existChildRef }}</text>
</view>
<view class="row">
<text>existTextItems:</text>
<text>{{ dataInfo.existTextItems }}</text>
</view>
<view>
<text v-for="item in dataInfo.textItemsExpectNum" ref="textItems" :key="item">{{
item
}}</text>
</view>
<child class="mt-10" ref="childRef">ComponentRef</child>
</view>
</template>
<script setup lang="uts">
import Child from './child-composition.uvue'
type DataInfo = {
existRef: boolean
existChildRef: boolean
textItemsExpectNum: number
existTextItems: boolean
}
const dataInfo = reactive<DataInfo>({
existRef: false,
existChildRef: false,
textItemsExpectNum: 3,
existTextItems: false
})
const nodeRef = ref<UniElement | null>(null)
const childRef = ref<UniElement | null>(null)
const textItems = ref<UniElement[] | null>(null)
onReady(() => {
dataInfo.existRef = nodeRef.value !== null
dataInfo.existChildRef = childRef.value !== null
dataInfo.existTextItems = textItems.value?.length === dataInfo.textItemsExpectNum
})
defineExpose({
dataInfo
})
</script>
<style>
.row {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 10px;
}
</style>
自定义组件 v-model 绑定复杂表达式时,需要通过 as 指定类型(仅App-Android 平台)。
组合式 API
选项式 API
<template>
<input v-model="obj.str as string" />
</template>
<script setup lang="uts">
type Obj = {
str: string
}
const obj = reactive({
str: "str"
} as Obj)
</script>
示例 详情
作用域插槽需在组件中指定插槽数据类型
组合式 API
选项式 API
<template>
<view class="container">
<view>
<slot name="header" msg="Here might be a page title"></slot>
</view>
<view>
<slot msg="A paragraph for the main content."></slot>
</view>
<view>
<slot name="footer" msg="Here's some contact info"></slot>
</view>
</view>
</template>
<script setup lang="uts">
defineOptions({
name: 'child'
})
defineSlots<{
default(props: { msg: string }): any
header(props: { msg: string }): any
footer(props: { msg: string }): any
}>()
</script>
实现递归组件时不要使用组件 import 自身的写法,直接在模板内使用组件名即可。
<!-- component-a.uvue -->
<template>
<view>
<text>component-a::{{text}}</text>
<component-a v-if="!end" :text="text" :limit="limit-1"></component-a>
</view>
</template>
<script setup lang="uts">
// import componentA from './component-a' // 错误用法
defineOptions({
name: "component-a"
})
const props = defineProps({
text: {
type: String,
default: ''
},
limit: {
type: Number,
default: 2
}
})
const end = computed(() : boolean => {
return props.limit <= 0
})
</script>