# 选项式 API

选项式 API,要求在script里编写export default {},在其中定义data、methods、生命周期等选项。

# 状态选项

Web Android iOS
data 4.0 3.9 4.11
props 4.0 3.9 4.11
computed 4.0 3.9 4.11
methods 4.0 3.9 4.11
watch 4.0 3.9 4.11
emits 4.0 3.9 4.11
expose 4.0 x x

# 示例代码

# data

用于声明组件初始响应式状态的函数。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>str: </text>
      <text id="str">{{ str }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>num: </text>
      <text id="num">{{ num }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>arr: </text>
      <text id="arr">{{ arr.join(',') }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>obj.str: </text>
      <text id="obj-str">{{ obj.str }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>obj.num: </text>
      <text id="obj-num">{{ obj.num }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>obj.arr: </text>
      <text id="obj-arr">{{ obj.arr.join(',') }}</text>
    </view>
    <view ref='htmlRef' id="idRef" class="flex justify-between flex-row mb-10">
      <text>data 存储 element不需要被包装</text>
      <text id="isSameRefText">{{ refElementIsSame }}</text>
    </view>

    <button @click="updateData">update data</button>
  </view>
</template>

<script lang="uts">
  type Obj = {
    str : string,
    num : number,
    arr : number[]
  }
  export default {
    data() {
      return {
        str: 'default str',
        num: 0,
        arr: [1, 2, 3],
        // 特殊类型需要通过 as 指定类型
        obj: {
          str: 'default obj.str',
          num: 10,
          arr: [4, 5, 6]
        } as Obj,
        refElement: null as UniElement | null,
        refElementIsSame: false
      }
    },
    methods: {
      refTest() {
        const queryElementById1 = uni.getElementById('idRef')
        const queryElementById2 = uni.getElementById('idRef')
        const htmlRefElement = this.$refs['htmlRef'] as UniElement;
        this.refElement = htmlRefElement
        if (queryElementById1 === queryElementById2
          && queryElementById1 === htmlRefElement
          && queryElementById1 === this.refElement) {
          this.refElementIsSame = true
        }
      },
      updateData() {
        this.str = 'new str'
        this.num = 1
        this.arr = [4, 5, 6]

        this.obj.str = 'new obj.str'
        this.obj.num = 100
        this.obj.arr = [7, 8, 9]

        this.refTest()


      },
    },
  }
</script>

# props

用于声明一个组件的 props。

示例 详情

<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>

# computed

用于声明要在组件实例上暴露的计算属性。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>count:</text>
      <text id="count">{{ count }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>computed double count:</text>
      <text id="double-count">{{ doubleCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>obj.arr:</text>
      <text id="obj-arr">{{ JSON.stringify(obj.arr) }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>computed obj.arr.length:</text>
      <text id="obj-arr-len">{{ objArrLen }}</text>
    </view>
    <button id="update-btn" @click="update">update</button>
  </view>
</template>

<script lang="uts">
type Obj = {
  arr : number[]
}

export default {
  data(){
    return {
      count: 0,
      obj:{
        arr: [1,2,3]
      } as Obj
    }
  },
  computed: {
    doubleCount(): number {
      return this.count * 2
    },
    objArrLen(): number {
      return this.obj.arr.length
    }
  },
  methods: {
    update(){
      this.count++
      this.obj.arr.push(this.obj.arr.length + 1)
    }
  }
}
</script>

# methods

用于声明要混入到组件实例中的方法。

声明的方法可以直接通过组件实例访问,或者在模板语法表达式中使用。所有的方法都会将它们的 this 上下文自动绑定为组件实例,即使在传递时也如此。

在声明方法时避免使用箭头函数,因为它们不能通过 this 访问组件实例。

详情点击查看

# watch

用于声明在数据更改时调用的侦听回调。

注意

  • watch immediate 第一次调用时,App-Android 平台旧值为初始值,web 平台为 null。

示例 详情

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1; padding-bottom: 20px">
  <!-- #endif -->
    <view class="page">
      <view class="flex justify-between flex-row mb-10">
        <text>count:</text>
        <text id="count" ref="countRef">{{ count }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch count result:</text>
        <text id="watch-count-res">{{ watchCountRes }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>watch count track number:</text>
        <text id="watch-count-track-num">{{ watchCountTrackNum }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch count cleanup number:</text>
        <text id="watch-count-cleanup-res">{{ watchCountCleanupRes }}</text>
      </view>

      <button class="increment-btn mb-10" @click="increment">increment</button>
      <button class="stop-watch-count-btn mb-10" @click="triggerStopWatchCount">
        stop watch count
      </button>

      <view class="flex justify-between flex-row mb-10">
        <text>obj.str:</text>
        <text id="obj-str" ref="objStrRef">{{ obj.str }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>watch obj.str trigger number:</text>
        <text id="watch-obj-str-trigger-num">{{ watchObjStrTriggerNum }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>obj.num:</text>
        <text id="obj-num">{{ obj.num }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>obj.bool:</text>
        <text id="obj-bool" ref="objBoolRef">{{ obj.bool }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>obj.arr:</text>
        <text id="obj-arr" ref="objArrRef">{{ JSON.stringify(obj.arr) }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj result:</text>
        <text id="watch-obj-res">{{ watchObjRes }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj.str result:</text>
        <text id="watch-obj-str-res">{{ watchObjStrRes }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj.bool result:</text>
        <text id="watch-obj-bool-res">{{ watchObjBoolRes }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj.arr result:</text>
        <text id="watch-obj-arr-res">{{ watchObjArrRes }}</text>
      </view>

      <button class="update-obj-btn mb-10" @click="updateObj">
        update obj
      </button>
    </view>
  <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
  type Obj = {
    num : number,
    str : string,
    bool : boolean,
    arr : number[]
  }

  export default {
    data() {
      return {
        countRef: null as UniTextElement | null,
        count: 0,
        watchCountRes: '',
        watchCountCleanupRes: '',
        watchCountTrackNum: 0,
        stopWatchCount: () => { },
        obj: {
          num: 0,
          str: 'num: 0',
          bool: false,
          arr: [0]
        } as Obj,
        watchObjRes: '',
        objStrRef: null as UniTextElement | null,
        watchObjStrRes: '',
        watchObjStrTriggerNum: 0,
        objBoolRef: null as UniTextElement | null,
        watchObjBoolRes: '',
        watchObjArrRes: '',
      }
    },
    onReady() {
      // TODO: app-android this.$watch 返回类型不对
      // watchCountTrackNum 各端表现也不一致
      const self = this
      // #ifdef APP
      this.$watch('count',
        (count : number, prevCount : number, onCleanup : OnCleanup) => {
          this.watchCountRes = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${(this.$refs['countRef'] as UniTextElement).value}`
          const cancel = () => {
            this.watchCountCleanupRes = `watch count cleanup: ${count}`
          }
          onCleanup(cancel)
        },
        {
          // 侦听器在响应式依赖改变时立即触发
          flush: 'sync',
          // 响应属性或引用作为依赖项被跟踪时调用
          onTrack(event : DebuggerEvent) {
            if (event.type === 'get') {
              self.watchCountTrackNum++
            }
          }
          // TODO: vue>3.4.15 开始 监听函数、onTrack、onTrigger 同时存在修改响应式数据时,会报错 Maximum call stack size exceeded
          // 所以将 onTrack 与 onTrigger 调整到两个 watch 里
        })
      // #endif
      // #ifdef WEB
      this.stopWatchCount = this.$watch(
        'count',
        (count : number, prevCount : number, onCleanup : OnCleanup) => {
          this.watchCountRes = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${(this.$refs['countRef'] as UniTextElement).childNodes[0].getAttribute('value')}`
          const cancel = () => {
            this.watchCountCleanupRes = `watch count cleanup: ${count}`
          }
          onCleanup(cancel)
        },
        {
          // 侦听器在响应式依赖改变时立即触发
          flush: 'sync',
          // 响应属性或引用作为依赖项被跟踪时调用
          onTrack(event : DebuggerEvent) {
            if (event.type === 'get') {
              self.watchCountTrackNum++
            }
          }
          // TODO: vue>3.4.15 开始 监听函数、onTrack、onTrigger 同时存在修改响应式数据时,会报错 Maximum call stack size exceeded
          // 所以将 onTrack 与 onTrigger 调整到两个 watch 里
        })
      // #endif
    },
    watch: {
      obj: {
        handler(obj : Obj, prevObj ?: Obj) {
          if (prevObj == null) {
            this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: ${JSON.stringify(prevObj)}`
          } else {
            // #ifdef WEB
            this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: {"num":${prevObj?.num},"str":"${prevObj?.str}","bool":${prevObj?.bool},"arr":${JSON.stringify(prevObj?.arr)}}`
            // #endif
            // #ifndef WEB
            this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: {"num":${prevObj.num},"str":"${prevObj.str}","bool":${prevObj.bool},"arr":${JSON.stringify(prevObj.arr)}}`
            // #endif
          }
        },
        // immediate: true 第一次触发, 旧值应该是 undefined, 现在 app 是初始值
        immediate: true,
        deep: true
      },
      'obj.str': function (str : string, prevStr : string) {
        // #ifdef APP
        this.watchObjStrRes = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${(this.$refs['objStrRef'] as UniTextElement).value}`
        // #endif
        // #ifdef WEB
        this.watchObjStrRes = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${(this.$refs.objStrRef as UniTextElement).childNodes[0].getAttribute('value')}`
        // #endif
      },
      'obj.bool': {
        handler: function (bool : boolean, prevBool : boolean) {
          // #ifdef APP
          this.watchObjBoolRes = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${(this.$refs['objBoolRef'] as UniTextElement).value}`
          // #endif
          // #ifdef WEB
          this.watchObjBoolRes = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${(this.$refs.objBoolRef as UniTextElement).childNodes[0].getAttribute('value')}`
          // #endif
        },
        // 侦听器延迟到组件渲染之后触发
        flush: 'post',
        deep: true
      },
      'obj.arr': {
        handler: function (arr : number[], prevArr : number[]) {
          this.watchObjArrRes = `arr: ${JSON.stringify(arr)}, prevArr: ${JSON.stringify(prevArr)}`
        },
        deep: true
      }
    },
    methods: {
      triggerStopWatchCount() {
        // #ifdef WEB
        this.stopWatchCount()
        // #endif
      },
      increment() {
        this.count++
      },
      updateObj() {
        this.obj.num++
        this.obj.str = `num: ${this.obj.num}`
        this.obj.bool = !this.obj.bool
        this.obj.arr.push(this.obj.num)
      }
    }
  }
</script>

# emits

用于声明由组件触发的自定义事件。

示例 详情

<template>
  <view>
    <button @click="click" class="call-parent-btn">调用父组件事件</button>
  </view>
</template>

<script>
export default {
  emits: ['callback'],
  methods: {
    click () {
      this.$emit('callback', `${Date.now()}`)
    }
  }
}
</script>

<style scoped>

</style>

# 渲染选项

Web Android iOS
template x x x
render 4.0 3.9 4.11
compilerOptions x x x
slots 4.0 3.9 4.11

# 示例代码

# template

用于声明组件的字符串模板。

示例 详情

<template>
  <view class="page">
    <template v-if="dataInfo.isShow">
      <text id="title">{{ title }}</text>
    </template>
    <template v-else>
      <text>隐藏标题时显示</text>
    </template>
    <button id="show-botton" @click="handleShow">{{ dataInfo.isShow ? '点击隐藏' : '点击显示' }}</button>
    <template v-for="(item, index) in list" :key="index">
      <text :class="'item'">{{ index + 1 }}.{{ item.name }}</text>
    </template>
    <button @click="goMapStyle">跳转绑定 Map 类型 style 页面</button>
  </view>
</template>

<script lang='uts'>
type DataInfo = {
  isShow: boolean
}
type objType = {
  name : string
}
export default {
  data() {
    return {
      title: "hello",
      dataInfo: {
        isShow: false,
      } as DataInfo,
      list: [{
        name: 'foo1'
      },
      {
        name: 'foo2'
      }
      ] as objType[]
    }
  },
  methods: {
    handleShow() {
      this.dataInfo.isShow = !this.dataInfo.isShow
    },
    goMapStyle() {
      uni.navigateTo({ url: '/pages/built-in/special-elements/template/template-map-style-options' })
    }
  }
}
</script>

<style>
.item {
  margin: 15px;
  padding: 4px 8px;
  border: #eee solid 1px;
}
</style>

# render

用于编程式地创建组件虚拟 DOM 树的函数。

render 是字符串模板的一种替代,可以使你利用 JavaScript 的丰富表达力来完全编程式地声明组件最终的渲染输出。

预编译的模板,例如单文件组件中的模板,会在构建时被编译为 render 选项。如果一个组件中同时存在 rendertemplate,则 render 将具有更高的优先级。

示例 详情

<script lang="uts">
import CompForHFunction from '@/components/CompForHFunction.uvue'
import CompForHFunctionWithSlot from '@/components/CompForHFunctionWithSlot.uvue'
import Foo from './Foo.uvue'
// 故意外部声明为UTSJSONObject
const msgProps = { class: 'uni-common-mt msg', style: { color: 'blue' } }
export default {
  data() {
    return {
      msg: 'default msg'
    }
  },
  render() : VNode {
    return h('view', { class: 'page' }, [
      h(CompForHFunctionWithSlot, {}, () : VNode[] => [h('text', { class: 'comp-slot' }, 'component slot')]),
      h(CompForHFunction, { msg: this.msg }),
      h('text', msgProps, this.msg),
      h(Foo, null, {
          header: (): VNode[] => [h('text', { id: "header" }, 'header')],
          footer: (): VNode[] => [h('text', { id: "footer" }, 'footer')]
      }),
      h(
        'button',
        {
          class: 'uni-common-mt btn',
          type: 'primary',
          onClick: () => {
            this.msg = 'new msg'
          }
        },
        'click'
      )
    ])
  }
}
</script>

<style>
.btn {
  color: red;
}
</style>

# slots

一个在渲染函数中以编程方式使用插槽时辅助类型推断的选项。

示例 详情

作用域插槽需在组件中指定插槽数据类型

<template>
  <view class="page">
    <Foo>
      <template #header="{ msg }">
        <view class="mb-10 flex justify-between flex-row">
          <text>header slot msg:</text>
          <text id="slot-header">{{ msg }}</text>
        </view>
      </template>
      <template #default="{ num }">
        <view class="mb-10 flex justify-between flex-row">
          <text>default slot num:</text>
          <text id="slot-default">{{ num }}</text>
        </view>
      </template>
      <template v-for="item in 2" #[`num${item}`]="{ num }">
        <view class="mb-10 flex justify-between flex-row">
          <text>num{{ item }} slot:</text>
          <text :id="`slot-num${item}`">{{ num }}</text>
        </view>
      </template>
      <template v-if="msgTrue['isShow']" #[msgTrue['name']]="{ msg }">
        <view class="mb-10 flex justify-between flex-row">
          <text>{{ msgTrue['name'] }} slot msg:</text>
          <text id="slot-msg-true">{{ msg }}</text>
        </view>
      </template>
      <template v-if="msgFalse['isShow']" #[msgFalse['name']]="{ msg }">
        <view class="mb-10 flex justify-between flex-row">
          <text>{{ msgFalse['name'] }} slot msg:</text>
          <text id="slot-msg-false">{{ msg }}</text>
        </view>
      </template>
      <template #footer="{ arr }">
        <view class="mb-10 flex justify-between flex-row">
          <text>footer slot arr:</text>
          <text id="slot-footer">{{ JSON.stringify(arr) }}</text>
        </view>
      </template>
    </Foo>
  </view>
</template>

<script lang="uts">
import Foo from './Foo-options.uvue'
export default {
  components: {Foo},
  data(){
    return {
      msgTrue: {
        isShow: true,
        name: 'msgTrue'
      },
      msgFalse: {
        isShow: false,
        name: 'msgFalse'
      }
    }
  }
}
</script>

# 生命周期选项

页面及组件生命周期流程图

# 页面生命周期

# 兼容性

页面生命周期

示例 详情

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1" :bounces="false">
    <!-- #endif -->
    <view class="page container">
      <text>page lifecycle 选项式 API</text>
      <view class="flex flex-row justify-between mt-10">
        <text>onLoad 触发:</text>
        <text>{{ isOnloadTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onShow 触发:</text>
        <text>{{ isOnShowTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onReady 触发:</text>
        <text>{{ isOnReadyTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onPullDownRefresh 触发:</text>
        <text>{{ isOnPullDownRefreshTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onReachBottom 触发:</text>
        <text>{{ isOnReachBottomTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onBackPress 触发:</text>
        <text>{{ isOnBackPressTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onHide 触发:</text>
        <text>{{ isOnHideTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onResize 触发:</text>
        <text>{{ isOnResizeTriggered }}</text>
      </view>
			<MonitorPageLifecycleOptions />
      <button class="mt-10" @click="scrollToBottom">scrollToBottom</button>
      <button class="mt-10" @click="pullDownRefresh">
        trigger pullDownRefresh
      </button>
			<button class="mt-10" @click="goOnBackPress">
        跳转 onBackPress 示例
      </button>
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
import { state, setLifeCycleNum } from '@/store/index.uts'
import MonitorPageLifecycleOptions from './monitor-page-lifecycle-options.uvue'
 type DataInfo = {
 	isScrolled: boolean
 }
export default {
	components: { MonitorPageLifecycleOptions },
	data() {
		return {
			isOnloadTriggered: false,
			isOnShowTriggered: false,
			isOnReadyTriggered: false,
			isOnPullDownRefreshTriggered: false,
			isOnPageScrollTriggered: false,
			isOnReachBottomTriggered: false,
			isOnBackPressTriggered: false,
			isOnHideTriggered: false,
			isOnResizeTriggered: false,
			dataInfo: {
         isScrolled: false,
       } as DataInfo
		}
	},
	onLoad(options : OnLoadOptions) {
		console.log('onLoad', options)
		this.isOnloadTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum + 100)
	},
	onShow() {
		this.isOnShowTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum + 10)
	},
	onReady() {
		this.isOnReadyTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum + 10)
	},
	onPullDownRefresh() {
		this.isOnPullDownRefreshTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum + 10)
	},
	onPageScroll(e: OnPageScrollOptions) {
		console.log('onPageScroll', e)
  	this.isOnPageScrollTriggered = true
		// 自动化测试
		this.dataInfo.isScrolled = true
	},
	onReachBottom() {
		this.isOnReachBottomTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum + 10)
	},
	onBackPress(options : OnBackPressOptions) : boolean | null {
		console.log('onBackPress', options)
		this.isOnBackPressTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum - 10)
		return null
	},
	onHide() {
		this.isOnHideTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum - 10)
	},
	onUnload() {
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum - 100)
	},
	onResize(options: OnResizeOptions) {
		console.log('onBackPress', options)
		this.isOnResizeTriggered = true
		// 自动化测试
		setLifeCycleNum(state.lifeCycleNum + 10)
	},
	methods: {
		// 自动化测试
		pageGetLifeCycleNum() : number {
			return state.lifeCycleNum
		},
		// 自动化测试
		pageSetLifeCycleNum(num : number) {
			setLifeCycleNum(num)
		},
		// 自动化测试
		pullDownRefresh() {
			uni.startPullDownRefresh({
				success() {
					setTimeout(() => {
						uni.stopPullDownRefresh()
						// 一秒后立即停止下拉刷新不会触发 onPullDownRefresh,因为下拉动画时间大概需要1.1~1.2秒
					}, 1500)
				},
			})
		},
		scrollToBottom() {
			uni.pageScrollTo({
				scrollTop: 2000,
			})
		},
		goOnBackPress() {
			uni.navigateTo({url: '/pages/lifecycle/page/onBackPress/on-back-press-options'})
		}
	},
}
</script>

<style>
.container {
  height: 1200px;
}
</style>

# 组件生命周期

# 兼容性

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 完成。

# mounted、unmounted 使用注意事项

目前 mounted、unmounted 可以保证当前数据已经同步到 DOM,但是由于排版和渲染是异步的的,所以 mounted、unmounted 不能保证 DOM 排版以及渲染完毕。
如果需要获取排版后的节点信息推荐使用 uni.createSelectorQuery 不推荐直接使用 Element 对象。
在修改 DOM 后,立刻使用 Element 对象的同步接口获取 DOM 状态可能获取到的是排版之前的,而 uni.createSelectorQuery 可以保障获取到的节点信息是排版之后的。

# activated、deactivated 使用注意事项

当 A 页面存在 keepAlive 组件,A 页面 navigateTo B 页面时

  • Web 端 A 页面中 keepAlive 的组件会触发 deactivated 生命周期
  • App 端 A 页面中 keepAlive 的组件不会触发 deactivated 生命周期

当 B 页面 back 返回 A 页面时

  • Web 端 A 页面中 keepAlive 的组件会触发 activated 生命周期
  • App 端 A 页面中 keepAlive 的组件不会触发 activated 生命周期

示例 详情

<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>

# 组合选项

Web Android iOS
provide 4.0 3.99 4.11
inject 4.0 3.99 4.11
mixins 4.0 3.99 4.11
extends - - -

# inject

当使用 inject 声明从上层提供方注入的属性时,支持两种写法:字符串数组和对象。推荐使用对象写法,因为当使用数组方法时,类型会被推导为 any | null 类型。
使用对象写法时,额外增加 type 属性用于标记类型。如果注入的属性类型不是基础数据类型,需要通过 PropType 来标记类型:

示例 详情

inject 1

inject 2

<template>
  <view>
    <text class="mt-10 bold">component for inject 1</text>
    <text class="mt-10 alias-provide-page-title"
      >aliasProvidePageTitle: {{ aliasProvidePageTitle }}</text
    >
    <text class="mt-10 provide-page-str"
      >providePageStr: {{ providePageStr }}</text
    >
    <text class="mt-10 provide-page-num"
      >providePageNum: {{ providePageNum }}</text
    >
    <text class="mt-10 provide-page-bool"
      >providePageBool: {{ providePageBool }}</text
    >
    <text class="mt-10 provide-page-object-title"
      >providePageObject.title: {{ providePageObject['title'] }}</text
    >
    <text class="mt-10 provide-page-object-content"
      >providePageObject.content: {{ providePageObject['content'] }}</text
    >
    <text class="mt-10 provide-page-arr"
      >providePageArr: {{ JSON.stringify(providePageArr) }}</text
    >
   <text class="mt-10 provide-page-map"
      >providePageMap: {{ JSON.stringify(providePageMapObj) }}</text
    >
    <text class="mt-10 provide-page-set"
      >providePageSet: {{ JSON.stringify(providePageSetArr) }}</text
    >
    <text class="mt-10 test-inject-string-default-value"
      >testInjectStringDefaultValue: {{ testInjectStringDefaultValue }}</text
    >
    <text class="mt-10 test-inject-object-default-value-title"
      >testInjectObjectDefaultValue.title:
      {{ testInjectObjectDefaultValue['title'] }}</text
    >
    <text class="mt-10 test-inject-object-default-value-content"
      >testInjectObjectDefaultValue.content:
      {{ testInjectObjectDefaultValue['content'] }}</text
    >
  </view>
</template>

<script lang="uts">
export default {
  inject: {
    aliasProvidePageTitle: {
      type: String,
      from: 'providePageTitle',
      default: 'default alias provide page title'
    },
    providePageStr: {
      type: String,
      default: 'default provide page str'
    },
    providePageNum: {
      type: Number,
      default: 0
    },
    providePageBool: {
      type: Boolean,
      default: false
    },
    providePageObject: {
      type: UTSJSONObject,
      default: (): UTSJSONObject => {
        return {
          title: 'default provide page object title',
          content: 'default provide page object content'
        }
      }
    },
    providePageArr: {
      type: Array as PropType<String[]>,
      default: (): String[] => {
        return ['default provide page arr']
      }
    },
    providePageMap: {
      type: Object as PropType<Map<string, string>>,
      default: (): Map<string, string> => {
        return new Map<string, string>([['key', 'default provide page map']])
      }
    },
    providePageSet: {
      type: Object as PropType<Set<string>>,
      default: (): Set<string> => {
        return new Set<string>(['default provide page set'])
      }
    },
    testInjectStringDefaultValue: {
      type: String,
      default: 'test inject string default value'
    },
    testInjectObjectDefaultValue: {
      type: UTSJSONObject,
      default(): UTSJSONObject {
        return {
          title: 'test inject object default value title',
          content: 'test inject object default value content'
        }
      }
    }
  },
	computed: {
		providePageMapObj(): UTSJSONObject {
			const obj: UTSJSONObject = {}
			this.providePageMap.forEach((value, key) => {
				obj[key] = value
			})
			return obj
		},
		providePageSetArr(): string[] {
			const arr: string[] = []
			this.providePageSet.forEach((value) => {
				arr.push(value)
			})
			return arr
		}
	}
}
</script>

# mixins

一个包含组件选项对象的数组,这些选项都将被混入到当前组件的实例中。

mixins 选项接受一个 mixin 对象数组。这些 mixin 对象可以像普通的实例对象一样包含实例选项,它们将使用一定的选项合并逻辑与最终的选项进行合并。举例来说,如果你的 mixin 包含了一个 created 钩子,而组件自身也有一个,那么这两个函数都会被调用。

  • mixins 仅支持通过字面量对象方式和 defineMixin 函数方式定义。
  • 在 app-Android 平台, App.uvue 不支持 mixins, 全局 mixins 也不会对 App.uvue 生效。
    const mixin1 = defineMixin({
      onLoad() {
        console.log('mixin1 onLoad')
      }
    })
    export default {
      mixins: [
        mixin1,
        {
          data() {
            return {
              mixin2: 'mixin2'
            }
          }
        }
      ]
    }
    
  • 同名属性会被覆盖,同名生命周期会依次执行。同名属性的优先级如下:
    • app.mixin 内嵌入的 mixin <app.mixin 中声明的 mixin <page.mixin 内嵌入的 mixin <page.mixin 中声明的 mixin <component.mixin 内嵌入的 mixin <component.mixin 中声明的 mixin
    • 同名生命周期的执行顺序如下:
      1. app.mixin 内嵌入的 mixin
      2. app.mixin 中声明的 mixin
      3. page.mixin 内嵌入的 mixin
      4. page.mixin 中声明的 mixin
      5. component.mixin 内嵌入的 mixin
      6. component.mixin 中声明的 mixin

mixins-web

mixins-app-page-namesake

mixins-app

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1">
    <!-- #endif -->
    <view class="page">
      <text id="mixin-prop" class="mb-10">mixinProp: {{mixinProp}}</text>
      <text id="mixin-data-msg" class="mb-10">mixinDataMsg: {{mixinDataMsg}}</text>
      <text id="mixin-onload-msg" class="mb-10">mixinOnloadMsg: {{mixinOnloadMsg}}</text>
      <text id="mixin-computed" class="mb-10">mixinComputed: {{mixinComputed}}</text>

      <Comp1 title="title" @globalMixinEmit1="(arg: string) => handleMixinEmitter('globalMixinEmit1', arg)"
        @globalChildMixinEmit1="(arg: string) => handleMixinEmitter('globalChildMixinEmit1', arg)"
        @globalMixinEmit2="(arg: string) => handleMixinEmitter('globalMixinEmit2', arg)"
        @globalChildMixinEmit2="(arg: string) => handleMixinEmitter('globalChildMixinEmit2', arg)"
        @mixinEmit="(arg: string) => handleMixinEmitter('mixinEmit', arg)"
        @childMixinEmit="(arg: string) => handleMixinEmitter('childMixinEmit', arg)" />
      <text v-if="handleMixinEmitterMsg" class="mt-10 handle-mixin-emitter-msg">
        handleMixinEmitterMsg: {{ handleMixinEmitterMsg }}
      </text>
      <Comp2 class='comp2' title="title" />
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
  import Comp1 from './components/Comp1.uvue'
  import Comp2 from './components/Comp2.uvue'
  export default {
    components: {
      Comp1,
      Comp2,
    },
    mixins: [defineMixin({
      props: {
        mixinProp: {
          type: String,
          default: '通过字面量定义非全局 mixin props'
        }
      },
      data() {
        return {
          handleMixinEmitterMsg: '',
          mixinDataMsg: '通过字面量定义非全局 mixin data',
          mixinOnloadMsg: ''
        }
      },
      computed: {
        mixinComputed(): string {
          const res = `通过字面量定义非全局 mixin computed, 更新后的 mixinOnloadMsg: ${this.mixinOnloadMsg}`
          console.log(res)
          return res
        }
      },
      onLoad() {
        this.mixinOnloadMsg = 'mixin onLoad msg in onLoad'
      },
      methods: {
        mixinMethod(): string {
          const res = '通过字面量定义非全局 mixin method'
          console.log(res)
          return res
        },
        handleMixinEmitter(emit: string, arg: string) {
          this.handleMixinEmitterMsg = `触发 ${emit}, 参数为 ${arg}`
          console.log(this.handleMixinEmitterMsg)
        }
      },
    })]
  }
</script>

# 其他杂项

Web Android iOS
name 4.0 3.9 4.11
inheritAttrs 4.0 3.9 4.11
components 4.0 3.9 4.11
directives - - -

# 示例代码

# name

用于显式声明组件展示时的名称。

组件的名字有以下用途:

  • 在组件自己的模板中递归引用自己时
  • 在 Vue 开发者工具中的组件树显示时
  • 在组件抛出的警告追踪栈信息中显示时

示例 详情

<template>
  <view class="page">
    <!-- #ifdef APP-ANDROID -->
    <!-- TODO: ios & web 不支持 a b 互相引用 -->
    <child-a :limit="5" />
    <!-- #endif -->
    <child-c :limit="5" />
  </view>
</template>

<script lang='uts'>
  // #ifdef APP-ANDROID
  import ChildA from './childA-options.uvue'
  // #endif
  import childC from './childC-options.uvue'

  export default {
    components: {
      // #ifdef APP-ANDROID
      ChildA,
      // #endif
      childC
    }
  }
</script>

# inheritAttrs

用于控制是否启用默认的组件 attribute 透传行为。

默认情况下,父组件传递的,但没有被子组件解析为 props 的 attributes 绑定会被“透传”。这意味着当我们有一个单根节点的子组件时,这些绑定会被作为一个常规的 attribute 应用在子组件的根节点元素上。当你编写的组件想要在一个目标元素或其他组件外面包一层时,可能并不期望这样的行为。我们可以通过设置 inheritAttrsfalse 来禁用这个默认行为。这些 attributes 可以通过 $attrs 这个实例属性来访问,并且可以通过 v-bind 来显式绑定在一个非根节点的元素上。

示例 详情

inheritAttrs: true

inheritAttrs: false

<template>
  <view class="mt-10" ref="mixin-comp-root">
    <text class="bold">Comp1: inheritAttrs: false</text>
    <text class="mt-10" style="color:#ccc;"
      >rootElementTitle should be null</text
    >
    <text class="mt-10 root-element-title-1">rootElementTitle: {{ rootElementTitle }}</text>
    <!-- #ifdef APP -->
    <text class="mt-10 bold">trigger emitter:</text>
    <button class="mt-10 global-mixin-emit-1" @click="triggerEmitter('globalMixinEmit1')">
      trigger globalMixinEmit1
    </button>
    <button
      class="mt-10 global-child-mixin-emit-1"
      @click="triggerEmitter('globalChildMixinEmit1')">
      trigger globalChildMixinEmit1
    </button>
    <button class="mt-10 global-mixin-emit-2" @click="triggerEmitter('globalMixinEmit2')">
      trigger globalMixinEmit2
    </button>
    <button
      class="mt-10 global-child-mixin-emit-2"
      @click="triggerEmitter('globalChildMixinEmit2')">
      trigger globalChildMixinEmit2
    </button>
    <button class="mt-10 mixin-emit" @click="triggerEmitter('mixinEmit')">
      trigger mixinEmit
    </button>
    <button class="mt-10 child-mixin-emit" @click="triggerEmitter('childMixinEmit')">
      trigger childMixinEmit
    </button>
    <MixinComp />
    <!-- #endif -->
  </view>
</template>

<script lang="uts">
export default {
  mixins:[{
    mixins: [{
      emits: ['childMixinEmit']
    }],
    inheritAttrs: false,
    emits:['mixinEmit']
  }],
  data(){
    return {
      rootElementTitle: '' as string | null
    }
  },
  mounted(){
    const rootElement = this.$refs['mixin-comp-root'] as UniElement
    this.rootElementTitle = JSON.stringify(rootElement.getAttribute('title'))
  },
  methods: {
    triggerEmitter(emit: string){
      this.$emit(emit, emit)
    },
  }
}
</script>

# components

一个对象,用于注册对当前组件实例可用的组件。

示例 详情

<template>
  <view class="page">
    <child
      class="child-class"
      str="str from parent"
      @childClick="() => {}" />
  </view>
</template>

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

export default {
  components: {
    child,
  },
}
</script>

# 组件实例

Web Android iOS
$data 4.0 4.11
$props 4.0 4.11
$el 4.0 4.11
$options 4.0 4.11
$parent 4.0 4.11
$root 4.0 4.11
$slots 4.0 4.11
$refs 4.0 4.11
$attrs 4.0 4.11
$watch() 4.0 4.11
$emit 4.0 4.11
$forceUpdate 4.0 4.11
$nextTick 4.0 4.11
$callMethod 4.0 4.11

# 示例代码

# $data

data 选项函数中返回的对象,会被组件赋为响应式。组件实例将会代理对其数据对象的属性访问。

# 使用注意事项

data内 $ 开头的属性不可直接使用 this.$xxx访问,需要使用 this.$data['$xxx'] ,这是vue的规范

目前安卓端可以使用 this.$xxx 访问是Bug而非特性,请勿使用此特性。

示例

<template>
  <view></view>
</template>
<script>
export default {
  data() {
    return {
      $a: 1
    }
  },
  onReady() {
    console.log(this.$data['$a'] as number) // 1
  }
}
</script>

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>str: </text>
      <text id="str">{{ str }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>num: </text>
      <text id="num">{{ num }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>arr: </text>
      <text id="arr">{{ arr.join(',') }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>obj.str: </text>
      <text id="obj-str">{{ obj.str }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>obj.num: </text>
      <text id="obj-num">{{ obj.num }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>obj.arr: </text>
      <text id="obj-arr">{{ obj.arr.join(',') }}</text>
    </view>
    <view ref='htmlRef' id="idRef" class="flex justify-between flex-row mb-10">
      <text>data 存储 element不需要被包装</text>
      <text id="isSameRefText">{{ refElementIsSame }}</text>
    </view>

    <button @click="updateData">update data</button>
  </view>
</template>

<script lang="uts">
  type Obj = {
    str : string,
    num : number,
    arr : number[]
  }
  export default {
    data() {
      return {
        str: 'default str',
        num: 0,
        arr: [1, 2, 3],
        // 特殊类型需要通过 as 指定类型
        obj: {
          str: 'default obj.str',
          num: 10,
          arr: [4, 5, 6]
        } as Obj,
        refElement: null as UniElement | null,
        refElementIsSame: false
      }
    },
    methods: {
      refTest() {
        const queryElementById1 = uni.getElementById('idRef')
        const queryElementById2 = uni.getElementById('idRef')
        const htmlRefElement = this.$refs['htmlRef'] as UniElement;
        this.refElement = htmlRefElement
        if (queryElementById1 === queryElementById2
          && queryElementById1 === htmlRefElement
          && queryElementById1 === this.refElement) {
          this.refElementIsSame = true
        }
      },
      updateData() {
        this.str = 'new str'
        this.num = 1
        this.arr = [4, 5, 6]

        this.obj.str = 'new obj.str'
        this.obj.num = 100
        this.obj.arr = [7, 8, 9]

        this.refTest()


      },
    },
  }
</script>

# $props

表示组件当前已解析的 props 对象。

示例 详情

<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>

# $el

该组件实例管理的 DOM 根节点。

示例 详情

<template>
  <view class="page flex justify-between flex-row">
    <text class="child">root node tagName:</text>
    <text class="tag-name">{{ el }}</text>
  </view>
</template>

<script lang='uts'>
export default {
  data() {
    return {
      el: '',
    }
  },
  mounted() {
    this.el = this.$el?.nodeName ?? ''
  },
}
</script>

# $options

已解析的用于实例化当前组件的组件选项。

示例 详情

<template>
  <view class="page">
    <view class="mb-10 flex justify-between flex-row">
      <text>component name: </text>
      <text id="component-name">{{ dataInfo.name }}</text>
    </view>
    <!-- #ifndef APP-ANDROID -->
    <view class="mb-10 flex justify-between flex-row">
      <text>custom key: </text>
      <text id="custom-key">{{ dataInfo.customKey }}</text>
    </view>
    <view class="mb-10 flex justify-between flex-row">
      <text>mixin data str: </text>
      <text id="mixin-data-str">{{ dataInfo.mixinDataStr }}</text>
    </view>
    <!-- #endif -->
  </view>
</template>

<script lang="uts">
import mixins from "./mixins.uts"

type DataInfo = {
  name: string
  customKey: string
  mixinDataStr: string
}

export default {
  mixins: [mixins],
  name: "$options",
  _customKey: "custom key",
  data() {
    return {
      dataInfo: {
        name: "",
        customKey: "",
        mixinDataStr: "",
      } as DataInfo
    }
  },
  mounted() {
    this.dataInfo.name = this.$options.name!
    // #ifndef APP-ANDROID
    this.dataInfo.customKey = this.$options._customKey
    // @ts-ignore
    this.dataInfo.mixinDataStr = this.$options.data({})['str']
    // #endif
  },
}
</script>

# $parent

当前组件可能存在的父组件实例,如果当前组件是顶层组件,则为 null

示例 详情

<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>

# $root

当前组件树的根组件实例。如果当前实例没有父组件,那么这个值就是它自己。

示例 详情

<template>
  <view class="page">
    <view class="mb-10 flex justify-between flex-row">
      <text>root str in parent component: </text>
      <text id="root-str-parent">{{ rootStr }}</text>
    </view>
    <child />
  </view>
</template>

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

export default {
  components: {Child},
  data () {
    return {
      str: 'root component str',
      rootStr: ''
    }
  },
  onReady() {
    this.rootStr = this.$root!.$data['str'] as string
  }
}
</script>

# $slots

一个表示父组件所传入插槽的对象。

示例 详情

<template>
  <view class="page">
    <slot-comp class="slot-comp">
      <template v-slot:header>header</template>
      <template v-slot:default>default</template>
      <template v-slot:footer>footer</template>
    </slot-comp>
  </view>
</template>

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

  export default {
    components: {
      slotComp: slot
    }
  }
</script>

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

# $refs

一个包含 DOM 元素和组件实例的对象,通过模板引用注册。

示例 详情

<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>

# $attrs

一个包含了组件所有透传 attributes 的对象。

示例 详情

<template>
  <view class="page">
    <child
      class="child-class"
      str="str from parent"
      @childClick="() => {}" />
  </view>
</template>

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

export default {
  components: {
    child,
  },
}
</script>

# $watch()

用于命令式地创建侦听器的 API。

示例 详情

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1; padding-bottom: 20px">
  <!-- #endif -->
    <view class="page">
      <view class="flex justify-between flex-row mb-10">
        <text>count:</text>
        <text id="count" ref="countRef">{{ count }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch count result:</text>
        <text id="watch-count-res">{{ watchCountRes }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>watch count track number:</text>
        <text id="watch-count-track-num">{{ watchCountTrackNum }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch count cleanup number:</text>
        <text id="watch-count-cleanup-res">{{ watchCountCleanupRes }}</text>
      </view>

      <button class="increment-btn mb-10" @click="increment">increment</button>
      <button class="stop-watch-count-btn mb-10" @click="triggerStopWatchCount">
        stop watch count
      </button>

      <view class="flex justify-between flex-row mb-10">
        <text>obj.str:</text>
        <text id="obj-str" ref="objStrRef">{{ obj.str }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>watch obj.str trigger number:</text>
        <text id="watch-obj-str-trigger-num">{{ watchObjStrTriggerNum }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>obj.num:</text>
        <text id="obj-num">{{ obj.num }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>obj.bool:</text>
        <text id="obj-bool" ref="objBoolRef">{{ obj.bool }}</text>
      </view>
      <view class="flex justify-between flex-row mb-10">
        <text>obj.arr:</text>
        <text id="obj-arr" ref="objArrRef">{{ JSON.stringify(obj.arr) }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj result:</text>
        <text id="watch-obj-res">{{ watchObjRes }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj.str result:</text>
        <text id="watch-obj-str-res">{{ watchObjStrRes }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj.bool result:</text>
        <text id="watch-obj-bool-res">{{ watchObjBoolRes }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text>watch obj.arr result:</text>
        <text id="watch-obj-arr-res">{{ watchObjArrRes }}</text>
      </view>

      <button class="update-obj-btn mb-10" @click="updateObj">
        update obj
      </button>
    </view>
  <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
  type Obj = {
    num : number,
    str : string,
    bool : boolean,
    arr : number[]
  }

  export default {
    data() {
      return {
        countRef: null as UniTextElement | null,
        count: 0,
        watchCountRes: '',
        watchCountCleanupRes: '',
        watchCountTrackNum: 0,
        stopWatchCount: () => { },
        obj: {
          num: 0,
          str: 'num: 0',
          bool: false,
          arr: [0]
        } as Obj,
        watchObjRes: '',
        objStrRef: null as UniTextElement | null,
        watchObjStrRes: '',
        watchObjStrTriggerNum: 0,
        objBoolRef: null as UniTextElement | null,
        watchObjBoolRes: '',
        watchObjArrRes: '',
      }
    },
    onReady() {
      // TODO: app-android this.$watch 返回类型不对
      // watchCountTrackNum 各端表现也不一致
      const self = this
      // #ifdef APP
      this.$watch('count',
        (count : number, prevCount : number, onCleanup : OnCleanup) => {
          this.watchCountRes = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${(this.$refs['countRef'] as UniTextElement).value}`
          const cancel = () => {
            this.watchCountCleanupRes = `watch count cleanup: ${count}`
          }
          onCleanup(cancel)
        },
        {
          // 侦听器在响应式依赖改变时立即触发
          flush: 'sync',
          // 响应属性或引用作为依赖项被跟踪时调用
          onTrack(event : DebuggerEvent) {
            if (event.type === 'get') {
              self.watchCountTrackNum++
            }
          }
          // TODO: vue>3.4.15 开始 监听函数、onTrack、onTrigger 同时存在修改响应式数据时,会报错 Maximum call stack size exceeded
          // 所以将 onTrack 与 onTrigger 调整到两个 watch 里
        })
      // #endif
      // #ifdef WEB
      this.stopWatchCount = this.$watch(
        'count',
        (count : number, prevCount : number, onCleanup : OnCleanup) => {
          this.watchCountRes = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${(this.$refs['countRef'] as UniTextElement).childNodes[0].getAttribute('value')}`
          const cancel = () => {
            this.watchCountCleanupRes = `watch count cleanup: ${count}`
          }
          onCleanup(cancel)
        },
        {
          // 侦听器在响应式依赖改变时立即触发
          flush: 'sync',
          // 响应属性或引用作为依赖项被跟踪时调用
          onTrack(event : DebuggerEvent) {
            if (event.type === 'get') {
              self.watchCountTrackNum++
            }
          }
          // TODO: vue>3.4.15 开始 监听函数、onTrack、onTrigger 同时存在修改响应式数据时,会报错 Maximum call stack size exceeded
          // 所以将 onTrack 与 onTrigger 调整到两个 watch 里
        })
      // #endif
    },
    watch: {
      obj: {
        handler(obj : Obj, prevObj ?: Obj) {
          if (prevObj == null) {
            this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: ${JSON.stringify(prevObj)}`
          } else {
            // #ifdef WEB
            this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: {"num":${prevObj?.num},"str":"${prevObj?.str}","bool":${prevObj?.bool},"arr":${JSON.stringify(prevObj?.arr)}}`
            // #endif
            // #ifndef WEB
            this.watchObjRes = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}, prevObj: {"num":${prevObj.num},"str":"${prevObj.str}","bool":${prevObj.bool},"arr":${JSON.stringify(prevObj.arr)}}`
            // #endif
          }
        },
        // immediate: true 第一次触发, 旧值应该是 undefined, 现在 app 是初始值
        immediate: true,
        deep: true
      },
      'obj.str': function (str : string, prevStr : string) {
        // #ifdef APP
        this.watchObjStrRes = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${(this.$refs['objStrRef'] as UniTextElement).value}`
        // #endif
        // #ifdef WEB
        this.watchObjStrRes = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${(this.$refs.objStrRef as UniTextElement).childNodes[0].getAttribute('value')}`
        // #endif
      },
      'obj.bool': {
        handler: function (bool : boolean, prevBool : boolean) {
          // #ifdef APP
          this.watchObjBoolRes = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${(this.$refs['objBoolRef'] as UniTextElement).value}`
          // #endif
          // #ifdef WEB
          this.watchObjBoolRes = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${(this.$refs.objBoolRef as UniTextElement).childNodes[0].getAttribute('value')}`
          // #endif
        },
        // 侦听器延迟到组件渲染之后触发
        flush: 'post',
        deep: true
      },
      'obj.arr': {
        handler: function (arr : number[], prevArr : number[]) {
          this.watchObjArrRes = `arr: ${JSON.stringify(arr)}, prevArr: ${JSON.stringify(prevArr)}`
        },
        deep: true
      }
    },
    methods: {
      triggerStopWatchCount() {
        // #ifdef WEB
        this.stopWatchCount()
        // #endif
      },
      increment() {
        this.count++
      },
      updateObj() {
        this.obj.num++
        this.obj.str = `num: ${this.obj.num}`
        this.obj.bool = !this.obj.bool
        this.obj.arr.push(this.obj.num)
      }
    }
  }
</script>

# $emit()

在当前组件触发一个自定义事件。任何额外的参数都会传递给事件监听器的回调函数。

示例 详情

<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>

# $forceUpdate()

强制该组件重新渲染。

示例 详情

<template>
  <view class="page">
    <view class="split-title">$forceUpdate</view>
    <text class="uni-common-mt time">Date.now(): {{ Date.now() }}</text>
    <button
      class="uni-common-mt trigger-force-update-btn"
      type="primary"
      @click="triggerForceUpdate">
      trigger $forceUpdate
    </button>
  </view>
</template>

<script lang="uts">
export default {
  methods: {
    triggerForceUpdate(){
      this.$forceUpdate()
    }
  }
}
</script>

# $nextTick()

绑定在实例上的 nextTick() 函数。

# 使用注意事项

目前 $nextTick 可以保证当前数据已经同步到 DOM,但是由于排版和渲染是异步的,所以 $nextTick 不能保证 DOM 排版以及渲染完毕。
如果需要获取排版后的节点信息推荐使用 uni.createSelectorQuery 不推荐直接使用 Element 对象。
在修改 DOM 后,立刻使用 Element 对象的同步接口获取 DOM 状态可能获取到的是排版之前的,而 uni.createSelectorQuery 可以保障获取到的节点信息是排版之后的。

示例 详情

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1">
  <!-- #endif -->
    <view class="page">
      <view class="flex justify-between mb-10">
        <text ref="text">title for callback:</text>
        <text id="page-text-callback">{{ dataInfo.titleForCallback }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text ref="text">before $nextTick callback title:</text>
        <text>{{ dataInfo.beforeNextTickCallbackTitle }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text ref="text">after $nextTick callback title:</text>
        <text>{{ dataInfo.afterNextTickCallbackTitle }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text ref="text">title for promise:</text>
        <text id="page-text-promise">{{ dataInfo.titleForPromise }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text ref="text">before $nextTick promise title:</text>
        <text>{{ dataInfo.beforeNextTickPromiseTitle }}</text>
      </view>
      <view class="flex justify-between mb-10">
        <text ref="text">after $nextTick promise title:</text>
        <text>{{ dataInfo.afterNextTickPromiseTitle }}</text>
      </view>
      <button id="page-test-next-tick-btn" @click="pageTestNextTick">page test $nextTick</button>
      <Child id="child-component" />
    </view>
  <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
  import Child from './child-options.uvue'
  
  type DataInfo = {
    titleForCallback : string
    beforeNextTickCallbackTitle : string
    afterNextTickCallbackTitle : string
    titleForPromise : string
    beforeNextTickPromiseTitle : string
    afterNextTickPromiseTitle : string
  }

  export default {
    components: {
      Child
    },
    data() {
      return {
        dataInfo: {
          titleForCallback: 'default title for callback',
          beforeNextTickCallbackTitle: '',
          afterNextTickCallbackTitle: '',
          titleForPromise: 'default title for promise',
          beforeNextTickPromiseTitle: '',
          afterNextTickPromiseTitle: '',
        } as DataInfo
      }
    },
    methods: {
      pageTestNextTick() {
        this.nextTickCallback()
        this.nextTickPromise()
      },
      nextTickCallback() {
        const pageText = uni.getElementById('page-text-callback')!
        this.dataInfo.titleForCallback = 'new title for callback'

        // #ifdef APP
        this.dataInfo.beforeNextTickCallbackTitle = pageText.getAttribute('value')!
        // #endif
        // #ifndef APP
        // @ts-ignore
        this.dataInfo.beforeNextTickCallbackTitle = pageText.textContent
        // #endif
        
        this.$nextTick(() => {
          // #ifdef APP
          this.dataInfo.afterNextTickCallbackTitle = pageText.getAttribute('value')!
          // #endif
          // #ifndef APP
          // @ts-ignore
          this.dataInfo.afterNextTickCallbackTitle = pageText.textContent
          // #endif
        })
      },
      nextTickPromise() {
        const pageText = uni.getElementById('page-text-promise')!
        this.dataInfo.titleForPromise = 'new title for promise'

        // #ifdef APP
        this.dataInfo.beforeNextTickPromiseTitle = pageText.getAttribute('value')!
        // #endif
        // #ifndef APP
        // @ts-ignore
        this.dataInfo.beforeNextTickPromiseTitle = pageText.textContent
        // #endif
        
        this.$nextTick().then(() => {
          // #ifdef APP
          this.dataInfo.afterNextTickPromiseTitle = pageText.getAttribute('value')!
          // #endif
          // #ifndef APP
          // @ts-ignore
          this.dataInfo.afterNextTickPromiseTitle = pageText.textContent
          // #endif
        })
      }
    }
  }
</script>