# 组件

我们可以对一段要复用的js/uts逻辑代码进行封装,抽出function、module等形式。

那么涉及UI的复用时,该如何抽象?

这就是vue的组件机制,把视图template、script、style都封装到独立的uvue组件文件中,在其他需要的地方使用组件的名称进行引用。

每个组件,包括如下几个部分:以组件名称为标记的开始标签和结束标签、组件text内容、组件属性、组件属性值。

组件还可以封装方法、事件、插槽,提供了组件的生命周期,提供了组件和页面的互通信机制,满足了各种高级需求。

如果您还不了解这些概念,请务必先阅读 组件概述文档

# 组件内容构成

uni-app x 组件基于 vue 单文件组件规范,一个组件内,有 3 个根节点标签:

  • <template>:组件的模板内容
  • <script>:组件的脚本代码
  • <style>:组件的样式

# 和页面的区别

组件的内容构成和页面大体上一致,都符合vue的sfc规范。

事实上,一个在pages.json注册的页面uvue文件,也可以被当做一个组件引入到其他页面。

组件和页面的差别有:

  1. 组件中不支持页面相关的生命周期和API,比如 onLoadonShow页面生命周期,比如$setPageStyle等API。
  2. 组件中有一批组件独有的生命周期和API,比如 mountedunmounted组件生命周期,比如页面和组件通信的API。
  3. 组件文件不需要在pages.json中注册

# 创建及引用组件

# 创建组件

# easycom

  1. 项目根目录/components 目录上右键(如果没有,在根目录新建一个 components 目录即可),选择 新建组件,输入组件名称,选择一个模板;可勾选创建同名目录,将组件放在同名目录下。
  2. 项目根目录/uni_modules 目录上右键(如果没有,在根目录新建一个 uni_modules 目录即可),选择 新建uni_modules插件,输入插件ID,分类选择前端组件-通用组件;将组件放在和插件ID同名的目录下。

# 创建自定义组件

  1. 在项目 pages 目录 下的任意地方创建 .uvue/.vue 文件并编写组件代码

注意事项

uni-app x 项目支持使用 .vue.uvue 文件作为组件使用,但同文件名的两个文件同时存在,.uvue 文件会优先编译。

# 引用组件

# easycom

传统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路径规则的详细教程详见

# 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>
// 引入 child 组件
import child from './child.vue'
export default {
  components: {
    child
  },
  data() {
    return {
      component1: null as ComponentPublicInstance | null // 手动引入组件时的类型
    }
  }
}
</script>
# 手动引入组件的类型规范

类型为:ComponentPublicInstance

# 使用及通信

# 页面与页面通信

  1. 使用 navigateTourl 地址中携带参数
  2. 使用 event-bus

# 页面与组件通信

# 向组件传递 props

示例 详情

注意

  • 选项式 API:this.$propsMap 类型,需要使用 this.$props["propName"] 来访问
  • 组合式 API:可以使用 . 点操作符来访问

选项式 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 lang="uts">
  import ArrayLiteral from './array-literal-options.uvue'
  import ObjectType from "./object-type-options.uvue";
  import SameNamePropDefaultValue from "./same-name-prop-default-value-options.uvue";
  import PropsWithDefaults from "./props-with-defaults.uvue";
  import ReferenceTypes from './reference-types-options.uvue'

  export default {
    components: {
      ArrayLiteral,
      ObjectType,
      SameNamePropDefaultValue,
      PropsWithDefaults,
      ReferenceTypes
    },
    data() {
      return {
        str: 'str',
        num: 10,
        bool: true,
        obj: { age: 18 },
        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>
import child from './child-options.uvue'

export default {
  components: {
    child
  },
  data () {
    return {
      value: ""
    }
  },
  methods: {
    callback (str: string) {
      this.value = str
    }
  }
}
</script>

<style>
.row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>

# 使用 provide/inject 来向下传递参数

示例 详情

选项式 API

组合式 API

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1">
    <!-- #endif -->
    <view class="page">
      <text>provide page</text>
      <button class="mt-10" @click="goProvidePage2">
        跳转函数方式定义 provide 示例
      </button>
      <ComponentForInject />
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
import ComponentForInject from '../inject/inject-options-1.uvue'

export default {
  components: {
    ComponentForInject
  },
  data(){
    return {
      title: '字面量方式定义 provide page title',
      obj: {
        title: 'data obj.title',
        content: 'data obj.content'
      },
    }
  },
  provide: {
    providePageStr: '字面量方式定义 provide page str',
    providePageNum: 1,
    providePageBool: true,
    providePageObject: {
      title: '字面量方式定义 provide page object title',
      content: '字面量方式定义 provide page object content'
    },
    providePageArr: ['字面量方式定义 provide page arr'],
    providePageMap: new Map<string, string>([['key', '字面量方式定义 provide page map']]),
    providePageSet: new Set<string>(['字面量方式定义 provide page set']),
  },
  methods: {
    goProvidePage2(){
      uni.navigateTo({
        url: '/pages/component-instance/provide/provide-options-2'
      })
    }
  },
}
</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></child>
  </view>
</template>

<script>
import child from './components/child.uvue'
import { setComponentMsg, state } from '@/store/index.uts'
export default {
  components: {
    child
  },
  computed: {
    msg(): number {
      return state.componentMsg
    }
  },
  methods: {
    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; padding-bottom: 20px">
    <!-- #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 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
}
export default {
	data() {
		return {
			myGlobalProperties: {
				str: '',
				num: 0,
				bool: false,
				obj: {},
				null: null,
				arr: [],
				set: [],
				map: {},
				reactiveObj: {
					str: '',
					num: 0,
					bool: false,
				} as UTSJSONObject,
				globalPropertiesFnRes: '',
			} as MyGlobalProperties,
		}
	},
	onLoad() {
		this.getGlobalProperties()
	},
	onUnload(){
		this.resetGlobalProperties()
	},
	methods: {
		getGlobalProperties() {
			this.myGlobalProperties.str = this.globalPropertiesStr
			this.myGlobalProperties.num = this.globalPropertiesNum
			this.myGlobalProperties.bool = this.globalPropertiesBool
			this.myGlobalProperties.obj = this.globalPropertiesObj
			this.myGlobalProperties.null = this.globalPropertiesNull
			this.myGlobalProperties.arr = this.globalPropertiesArr
			this.myGlobalProperties.set = []
			this.globalPropertiesSet.forEach(item => {
				this.myGlobalProperties.set.push(item)
			})
			this.myGlobalProperties.map = {}
			this.globalPropertiesMap.forEach((value: number, key: string) => {
				this.myGlobalProperties.map[key] = value
			})
			this.myGlobalProperties.reactiveObj = this.globalPropertiesReactiveObj
			this.myGlobalProperties.globalPropertiesFnRes = this.globalPropertiesFn()
		},
		resetGlobalProperties() {
			this.globalPropertiesStr = 'default string'
			this.globalPropertiesNum = 0
			this.globalPropertiesBool = false
			this.globalPropertiesObj = {
				str: 'default globalProperties obj string',
				num: 0,
				bool: false,
			}
			this.globalPropertiesNull = null
			this.globalPropertiesArr = []
			this.globalPropertiesSet = new Set()
			this.globalPropertiesMap = new Map()
			this.globalPropertiesReactiveObj['str'] = 'default reactive string'
			this.globalPropertiesReactiveObj['num'] = 0
			this.globalPropertiesReactiveObj['bool'] = false
		},
		updateGlobalProperties() {
			this.globalPropertiesStr = 'new string'
			this.globalPropertiesNum = 100
			this.globalPropertiesBool = true
			this.globalPropertiesObj = {
				str: 'new globalProperties obj string',
				num: 100,
				bool: true,
			}
			this.globalPropertiesNull = 'not null'
			this.globalPropertiesArr = [1, 2, 3]
			this.globalPropertiesSet = new Set(['a', 'b', 'c'])
			this.globalPropertiesMap = new Map([['a', 1], ['b', 2], ['c', 3]])
			this.globalPropertiesReactiveObj['str'] = 'new reactive string'
			this.globalPropertiesReactiveObj['num'] = 200
			this.globalPropertiesReactiveObj['bool'] = true
			this.getGlobalProperties()
		}
	},
}
</script>

<style>
.uni-padding-wrap {
  padding: 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>
  </view>
</template>

<script lang="uts">
const delay = (): Promise<string> =>
  new Promise((resolve, _) => {
    setTimeout(() => {
      resolve('')
    }, 1000)
  })

export default {
  data() {
    return {
      callEasyMethod1: null as CallEasyMethodComponentPublicInstance | null,
      customCallEasyMethod1: null as CustomCallEasyMethodComponentPublicInstance | null,
    }
  },
  onReady() {
    // 通过组件 ref 属性获取组件实例, 组件标签名首字母大写,驼峰+ComponentPublicInstance
    this.callEasyMethod1 = this.$refs['callEasyMethod1'] as CallEasyMethodComponentPublicInstance
    this.customCallEasyMethod1 = this.$refs['customCallEasyMethod1'] as CustomCallEasyMethodComponentPublicInstance
    this.call()
  },
  methods: {
    async call(): Promise<void> {
      this.callCustomMethod()
      this.callMethod1()
      await delay()
      this.callMethod2()
      await delay()
      this.callMethod3()
      await delay()
      this.callMethod4()
      await delay()
      this.callMethod5()
    },
    callMethod1() {
      // 调用组件的 foo1 方法
      this.callEasyMethod1?.foo1?.()
    },
    callMethod2() {
      // 调用组件的 foo2 方法并传递 1个参数
      this.callEasyMethod1?.foo2?.(Date.now())
    },
    callMethod3() {
      // 调用组件的 foo3 方法并传递 2个参数
      this.callEasyMethod1?.foo3?.(Date.now(), Date.now())
    },
    callMethod4() {
      // 调用组件的 foo4 方法并传递 callback
      this.callEasyMethod1?.foo4?.(() => {
        console.log('callback')
      })
    },
    callMethod5() {
      // 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
      const result = this.callEasyMethod1?.foo5?.('string1') as string
      console.log(result) // string1
    },
    callMethodTest(text: string): string | null {
      const result = this.callEasyMethod1?.foo5?.(text) as string
      return result
    },
    callCustomMethod() {
      // 调用组件的 foo 方法
      this.customCallEasyMethod1?.foo?.()
    },
    callCustomMethodTest(): string | null {
      const result = this.customCallEasyMethod1?.foo?.() as string
      return result
    },
  }
}
</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 -->
    <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 lang="uts">
  import { testInOtherFile } from './call-method-easycom-uni-modules'

  const delay = () : Promise<string> =>
    new Promise((resolve, _) => {
      setTimeout(() => {
        resolve('')
      }, 1000)
    })

  export default {
    data() {
      return {
        callEasyMethod1: null as CallEasyMethodUniModulesComponentPublicInstance | null,
        isWatched: false,
        changeTimes: 0,
        numList: [1] as number[], // 传递 props
        objList: [] as any[],
        isNumListValid: false,
        isObjListValid: false
      }
    },
    onReady() {
      // 通过组件 ref 属性获取组件实例, 组件标签名首字母大写,驼峰+ComponentPublicInstance
      this.callEasyMethod1 = this.$refs['callEasyMethod1'] as CallEasyMethodUniModulesComponentPublicInstance

      this.call()
    },
    methods: {
      async call() : Promise<void> {
        this.callMethod1()
        await delay()
        this.callMethod2()
        await delay()
        this.callMethod3()
        await delay()
        this.callMethod4()
        await delay()
        this.callMethod5()

      },
      callMethod1() {
        // 调用组件的 foo1 方法
        this.callEasyMethod1?.foo1?.()
      },
      callMethod2() {
        // 调用组件的 foo2 方法并传递 1个参数
        this.callEasyMethod1?.foo2?.(Date.now())
      },
      callMethod3() {
        // 调用组件的 foo3 方法并传递 2个参数
        this.callEasyMethod1?.foo3?.(Date.now(), Date.now())
      },
      callMethod4() {
        // 调用组件的 foo4 方法并传递 callback
        this.callEasyMethod1?.foo4?.(() => {
          console.log('callback')
        })
      },
      callMethod5() {
        // 注意: 返回值可能为 null,当前例子一定不为空,所以加了 !
        const result = this.callEasyMethod1?.foo5?.('string5') as string
        console.log(result) // string1
      },
      callMethodTest(text : string) : string | null {
        const result = this.callEasyMethod1?.foo5?.(text) as string
        return result
      },
      callMethodInOtherFile(text : string) : string {
        return testInOtherFile(this.callEasyMethod1!, text)
      },

      // #ifdef APP-ANDROID
      numListChange(res : Map<string, Map<string, any>>) {
    const value = res['detail']!['value'] as number[]
    const isArray = Array.isArray(value)
    const isLengthGt0 = value.length > 0
    this.isNumListValid = isArray && isLengthGt0
  },
  // #endif
  // #ifdef APP-IOS
  numListChange(res : any) {
    const value = res['detail']!['value'] as number[]
    const isArray = Array.isArray(value)
    const isLengthGt0 = value.length > 0
    this.isNumListValid = isArray && isLengthGt0
  },
  // #endif

  // #ifdef APP-ANDROID
  objListChange(res : Map<string, Map<string, any>>) {
    const value = res['detail']!['value'] as number[]
    const isArray = Array.isArray(value)
    const isLengthGt0 = value.length > 0
    this.isObjListValid = isArray && isLengthGt0
  },
  // #endif
  // #ifdef APP-IOS
  objListChange(res : any) {
    const value = res['detail']!['value'] as number[]
    const isArray = Array.isArray(value)
    const isLengthGt0 = value.length > 0
    this.isObjListValid = isArray && isLengthGt0
  },
  // #endif
  onButtonClick() {
    // 改变 props: 观察 props 返回值为非响应式值
    console.log('button click');
    this.numList = [3, 2, 1]
    this.objList = [{ id: '3' }, { id: '4' }]
  }
    }
  }
</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='child' />
  </view>
</template>

<script lang='uts'>
import child from './child-options.uvue'

export default {
  components: {
    child
  },
  data() {
    return {
      str: "parent str",
      num: 1
    }
  },
  methods: {
    getNum() : number {
      return this.num
    },
    callMethodByChild(): number {
      const child = this.$refs['child'] as ComponentPublicInstance
      return child.$parent!.$callMethod('getNum') as number
    }
  }
}
</script>

注意:

  • App-Android 平台 4.0 版本开始支持 $callMethod 调用 defineExpose 导出的方法
  • Web 平台、App-iOS 平台 4.13 版本开始支持 $callMethod 调用 defineExpose 导出的方法
  • 小程序 平台 支持 $callMethod 调用 defineExpose 导出的方法

# 内置组件的方法调用或设置属性 HBuilderX 3.93+

使用 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="slider1"></slider>
  </view>
</template>

<script>
export default {
  data() {
    return {
      slider1: null as UniSliderElement | null
    }
  },
  onReady() {
    // 通过组件 ref 属性获取组件实例, Uni组件名(驼峰)UniElement
    this.slider1 = this.$refs['slider1'] as UniSliderElement;
    this.setValue()
  },
  methods: {
    setValue() {
      this.slider1!.value = 80
    },
    callMethodTest(text: string): string | null {
      this.slider1?.setAttribute('str', text);
      const result = this.slider1?.getAttribute('str') as string;
      return result;
    },
  }
}
</script>

bug&tips

  • 目前uts组件,即封装原生ui给uni-app或uni-app x的页面中使用,类型与内置组件的 Uni组件名(驼峰)Element 方式相同。目前没有代码提示。

# 组件监听应用、页面生命周期

选项式 API 和 组件式 API 在监听页面生命周期时有所不同

比如选项式 API 中的 onShowonHide 监听页面生命周期在组合式 API 中分别对应 onPageShowonPageHide(在组合式 API 时会和 App 的生命周期冲突)

具体请查看 页面生命周期

注意

  1. onAppHide、onAppShow 目前只有 Android 支持
  2. onPageHide、onPageShow 需要写在选项式的 setup 函数 或者 组合式 <script setup> 中才能生效

示例 详情

选项式 API

组合式 API

<script lang="uts">
export default {
	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>

# 组件的生命周期

# 组件生命周期(选项式 API)兼容性

Web 微信小程序 Android iOS HarmonyOS 描述
beforeCreate 4.0 4.41 3.9 4.11 - 在组件实例初始化完成之后立即调用。
在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
created 4.0 4.41 3.9 4.11 - 在组件实例处理完所有与状态相关的选项后调用。
在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。
然而,此时挂载阶段还未开始,因此 $el 属性仍不可用。
beforeMount 4.0 4.41 3.9 4.11 - 在组件被挂载之前调用。
相关的 render 函数首次被调用。
当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。
它即将首次执行 DOM 渲染过程。
mounted 4.0 4.41 3.9 4.11 - 在组件被挂载之后调用。
el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
beforeUpdate 4.0 4.41 3.9 4.11 - 在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。
数据更新时调用,发生在虚拟 DOM 打补丁之前。
这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
updated 4.0 4.41 3.9 4.11 - 在组件因为一个响应式状态变更而更新其 DOM 树之后调用。
父组件的更新钩子将在其子组件的更新钩子之后调用。
这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的。
如果你需要在某个特定的状态更改后访问更新后的 DOM,请使用 nextTick() 作为替代。
beforeUnmount 4.0 4.41 3.9 4.11 - 在一个组件实例被卸载之前调用。
当这个钩子被调用时,组件实例依然还保有全部的功能。
unmounted 4.0 4.41 3.9 4.11 - 在一个组件实例被卸载之后调用。
可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。
errorCaptured 4.0 4.41 x x - 在捕获了后代组件传递的错误时调用。
这个钩子带有三个实参:错误对象、触发该错误的组件实例,以及一个说明错误来源类型的信息字符串。
这个钩子可以通过返回 false 来阻止错误继续向上传递。
renderTracked 4.0 4.41 x x - 在一个响应式依赖被组件的渲染作用追踪后调用。
跟踪虚拟 DOM 重新渲染时调用。钩子接收 debugger event 作为参数。
此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键。
renderTriggered 4.0 4.41 x x - 在一个响应式依赖被组件触发了重新渲染之后调用。
当虚拟 DOM 重新渲染为 triggered.Similarly 为renderTracked,接收 debugger event 作为参数。
此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键。
activated 4.0 x 4.0 4.11 - 若组件实例是 <KeepAlive> 缓存树的一部分,当组件被插入到 DOM 中时调用。
keep-alive 组件激活时调用。
deactivated 4.0 x 4.0 4.11 - 若组件实例是 <KeepAlive> 缓存树的一部分,当组件从 DOM 中被移除时调用。
keep-alive 组件停用时调用。
serverPrefetch x x x x - 当组件实例在服务器上被渲染之前要完成的异步函数。
如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。

# 组件生命周期(组合式 API)兼容性

Web 微信小程序 Android iOS HarmonyOS 描述
onMounted() 4.0 4.41 4.0 4.11 - el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
onUpdated() 4.0 4.41 4.0 4.11 - 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
onUnmounted() 4.0 4.41 4.0 4.11 - 在一个组件实例被卸载之后调用。
onBeforeMount() 4.0 4.41 4.0 4.11 - 在挂载开始之前被调用:相关的 render 函数首次被调用。
onBeforeUpdate() 4.0 4.41 4.0 4.11 - 数据更新时调用,发生在虚拟 DOM 打补丁之前。
这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
onBeforeUnmount() 4.0 4.41 4.0 4.11 - 在一个组件实例被卸载之前调用。
onErrorCaptured() x - x x - 注册一个钩子,在捕获了后代组件传递的错误时调用。
onRenderTracked() x - x x - 注册一个调试钩子,当组件渲染过程中追踪到响应式依赖时调用。
onRenderTriggered() x - x x - 注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。
onActivated() 4.0 x x x - keep-alive 组件激活时调用。
onDeactivated() 4.0 x x x - keep-alive 组件停用时调用。
onServerPrefetch() x x x x - 注册一个异步函数,在组件实例在服务器上被渲染之前调用。
如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。
这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。

示例 详情

选项式 API

组合式 API

<template>
  title: {{ title }}
  <button class="component-lifecycle-btn mt-10" @click="updateTitle">
    updateTitle
  </button>
</template>

<script lang='uts'>
  import { state, setLifeCycleNum } from '@/store/index.uts';
  export default {
    name: 'OptionsAPIComponentLifecycle',
    data() {
      return {
        title: 'component for options API lifecycle test',
      };
    },
    beforeCreate() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum + 1);
      console.log('component for lifecycle test beforeCreate');
    },
    created() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum + 1);
      console.log('component for lifecycle test created');
    },
    beforeMount() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum + 1);
      console.log('component for lifecycle test beforeMount');
    },
    mounted() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum + 1);
      console.log('component for lifecycle test mounted');
    },
    beforeUpdate() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum + 1);
      console.log('component for lifecycle test beforeUpdate');
    },
    updated() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum + 1);
      console.log('component for lifecycle test updated');
    },
    beforeUnmount() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum - 1);
      console.log('component for lifecycle test beforeUnmount');
    },
    unmounted() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum - 1);
      console.log('component for lifecycle test unmounted');
    },
    activated() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum + 1);
      console.log('component for lifecycle test activated');
    },
    deactivated() {
      // 自动化测试
      setLifeCycleNum(state.lifeCycleNum - 1);
      console.log('component for lifecycle test deactivated');
    },
    methods: {
      updateTitle() {
        this.title = 'component for lifecycle test updated';
      },
    },
  };
</script>

# 全局组件

详情见 app.component

# props

  • 支持对象方式声明。从 4.0+ 支持字符串数组方式声明。使用字符串数组方式声明时,所有 prop 类型均为 any | null。
  • 仅支持直接在 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 lang="uts">
  import ArrayLiteral from './array-literal-options.uvue'
  import ObjectType from "./object-type-options.uvue";
  import SameNamePropDefaultValue from "./same-name-prop-default-value-options.uvue";
  import PropsWithDefaults from "./props-with-defaults.uvue";
  import ReferenceTypes from './reference-types-options.uvue'

  export default {
    components: {
      ArrayLiteral,
      ObjectType,
      SameNamePropDefaultValue,
      PropsWithDefaults,
      ReferenceTypes
    },
    data() {
      return {
        str: 'str',
        num: 10,
        bool: true,
        obj: { age: 18 },
        arr: ['a', 'b', 'c']
      }
    },
  }
</script>

# ref

uni-app js 引擎版中,非 Web端 只能用于获取自定义组件,不能用于获取内置组件实例(如:viewtext)。
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="node">{{ 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 lang="uts">
import child from './child-options.uvue'

type DataInfo = {
  existRef: boolean
  existChildRef: boolean
  textItemsExpectNum: number
  existTextItems: boolean
}
export default {
  components: {
    child
  },
  data() {
    return {
      dataInfo: {
        existRef: false,
        existChildRef: false,
        textItemsExpectNum: 3,
        existTextItems: false
      } as DataInfo
    }
  },
  onReady() {
    this.dataInfo.existRef = this.$refs['node'] !== null
    this.dataInfo.existChildRef = this.$refs['childRef'] !== null
    const textItems = this.$refs['textItems'] as Array<UniElement>
    this.dataInfo.existTextItems = textItems.length === this.dataInfo.textItemsExpectNum
  }
}
</script>

<style>
.row {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  margin-bottom: 10px;
}
</style>

# 自定义组件 v-model 绑定复杂表达式

自定义组件 v-model 绑定复杂表达式时,需要通过 as 指定类型(仅App-Android 平台)。

选项式 API

组合式 API

<template>
  <input v-model="obj.str as string" />
</template>

<script lang="uts">
	type Obj = {
		str : string
	}
	export default {
		data() {
			return {
				obj: {
					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 lang="uts">
export default {
  name: 'child',
  slots: Object as SlotsType<{
    header: { msg: string }
    default: { msg: string }
    footer: { msg: string }
  }>
}
</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>
  // import componentA from './component-a' // 错误用法
  export default {
    name: "component-a",
    props: {
      text: {
        type: String,
        default: ''
      },
      limit: {
        type: Number,
        default: 2
      }
    },
    computed: {
      end() : boolean {
        return this.limit <= 0
      }
    }
  }
</script>