# 组件

我们可以对一段要复用的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/composition/provide/provide-page2'
      })
    }
  },
}
</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 导出的方法

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

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

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