# 组合式 API

注意

  • 暂不支持 <script setup><script> 同时使用,如果需要配置 options 内容,比如 name,可以使用 defineOptions
  • 暂不支持顶层 await
  • 暂不支持 <script setup> 配置 generic 泛型类型参数。
  • App.uvue 暂不支持组合式 API。

# 响应式: 核心

Web Android iOS
ref() 4.0 4.11
computed() 4.0 4.11
reactive() 4.0 4.11
readonly() 4.0 4.0 4.11
watchEffect() 4.0 4.0 4.11
watchPostEffect() 4.0 4.0 4.11
watchSyncEffect() 4.0 4.0 4.11
watch() 4.0 4.0 4.11

# 示例代码

# ref

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

示例 详情

<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>str:</text>
      <text id="str">{{ str }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>bool:</text>
      <text id="bool">{{ bool }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>arr:</text>
      <text id="arr">{{ JSON.stringify(arr) }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>counter.count:</text>
      <text id="counter-count">{{ counter.count }}</text>
    </view>
    <button class='mb-10' id="change-count-btn" @click="changeCount">change count</button>
    <button class='mb-10' id='change-str-btn' @click='changeStr'>change str</button>
    <button class='mb-10' id='change-bool-btn' @click='changeBool'>change bool</button>
    <button class='mb-10' id='change-arr-btn' @click='changeArr'>change arr</button>
    <button class='mb-10' id='change-counter-btn' @click='changeCounter'>change counter</button>
  </view>
</template>

<script setup lang="uts">
  // 基础数据类型可自动推导类型
  const count = ref(0)
  const str = ref('default str')
  const bool = ref(false)

  // 可通过泛型指定类型
  const arr = ref<number[]>([1, 2, 3])
  type Counter = {
    count : number
  }
  // 可通过泛型指定类型
  const counter = ref<Counter>({
    count: 0
  })

  const changeCount = () => {
    count.value++
  }
  const changeStr = () => {
    str.value = 'new str'
  }
  const changeBool = () => {
    bool.value = !bool.value
  }
  const changeArr = () => {
    arr.value.push(arr.value.length + 1)
  }
  const changeCounter = () => {
    counter.value.count++
  }
</script>
  • 使用 <template ref>

示例 详情

<template>
  <view class="page">
    <view class="row">
      <text>NodeRef: </text>
      <text ref="nodeRef">{{ dataInfo.existRef }}</text>
    </view>
    <view class="row">
      <text>childRef:</text>
      <text>{{ dataInfo.existChildRef }}</text>
    </view>
    <view class="row">
      <text>existTextItems:</text>
      <text>{{ dataInfo.existTextItems }}</text>
    </view>
    <view>
      <text v-for="item in dataInfo.textItemsExpectNum" ref="textItems" :key="item">{{
        item
      }}</text>
    </view>
    <child class="mt-10" ref="childRef">ComponentRef</child>
  </view>
</template>

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

type DataInfo = {
  existRef: boolean
  existChildRef: boolean
  textItemsExpectNum: number
  existTextItems: boolean
}

const dataInfo = reactive<DataInfo>({
  existRef: false,
  existChildRef: false,
  textItemsExpectNum: 3,
  existTextItems: false
})

const nodeRef = ref<UniElement | null>(null)
const childRef = ref<UniElement | null>(null)
const textItems = ref<UniElement[] | null>(null)

onReady(() => {
  dataInfo.existRef = nodeRef.value !== null
  dataInfo.existChildRef = childRef.value !== null
  dataInfo.existTextItems = textItems.value?.length === dataInfo.textItemsExpectNum
})

defineExpose({
  dataInfo
})
</script>

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

# watch

侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

示例 详情

<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 class="flex justify-between mb-10">
        <text>watch count and obj.num result:</text>
        <text id="watch-count-obj-num-res">{{ watchCountAndObjNumRes }}</text>
      </view>
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

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

const countRef = ref<UniTextElement | null>(null)
const count = ref(0)
const watchCountRes = ref('')
const watchCountCleanupRes = ref('')
const watchCountTrackNum = ref(0)

const stopWatchCount = watch(count, (count : number, prevCount : number, onCleanup : OnCleanup) => {
  // #ifdef APP
  watchCountRes.value = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${countRef.value!.value}`
  // #endif
  // #ifdef WEB
  watchCountRes.value = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${(countRef.value!.childNodes[0] as HTMLElement).innerText}`
  // #endif
  const cancel = () => {
    watchCountCleanupRes.value = `watch count cleanup: ${count}`
  }
  onCleanup(cancel)
}, {
  // 侦听器在响应式依赖改变时立即触发
  flush: 'sync',
  // 响应属性或引用作为依赖项被跟踪时调用
  onTrack(event : DebuggerEvent) {
    if (event.type === 'get') {
      watchCountTrackNum.value++
    }
  }
  // TODO: vue>3.4.15 开始 监听函数、onTrack、onTrigger 同时存在修改响应式数据时,会报错 Maximum call stack size exceeded
  // 所以将 onTrack 与 onTrigger 调整到两个 watch 里
})

const triggerStopWatchCount = () => stopWatchCount()

const increment = () => {
  count.value++
}

const obj = reactive({
  num: 0,
  str: 'num: 0',
  bool: false,
  arr: [0]
} as Obj)

// immediate: true 第一次触发, 旧值应该是 undefined, 现在 app 是初始值
const watchObjRes = ref('')
watch(obj, (obj : Obj, prevObj ?: Obj) => {
  watchObjRes.value = `obj: ${JSON.stringify(obj)}, prevObj: ${JSON.stringify(prevObj)}`
}, { immediate: true })

const objStrRef = ref<UniTextElement | null>(null)
const watchObjStrRes = ref('')
const watchObjStrTriggerNum = ref(0)
watch(() : string => obj.str, (str : string, prevStr : string) => {
  // #ifdef APP
  watchObjStrRes.value = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${objStrRef.value!.value}`
  // #endif
  // #ifdef WEB
  watchObjStrRes.value = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${(objStrRef.value!.childNodes[0] as HTMLElement).innerText}`
  // #endif
}, {
  // 侦听器在组件渲染之前触发
  flush: 'pre',
  // 侦听器回调被依赖项的变更触发时调用
  onTrigger(event : DebuggerEvent) {
    if (event.type === 'set') {
      watchObjStrTriggerNum.value++
    }
  }
})

const objBoolRef = ref<UniTextElement | null>(null)
const watchObjBoolRes = ref('')
watch(() : boolean => obj.bool, (bool : boolean, prevBool : boolean) => {
  // #ifdef APP
  watchObjBoolRes.value = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${objBoolRef.value!.value}`
  // #endif
  // #ifdef WEB
  watchObjBoolRes.value = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${(objBoolRef.value!.childNodes[0] as HTMLElement).innerText}`
  // #endif
}, {
  // 侦听器延迟到组件渲染之后触发
  flush: 'post'
})


const watchObjArrRes = ref('')
watch(() : number[] => obj.arr, (arr : number[], prevArr : number[]) => {
  watchObjArrRes.value = `arr: ${JSON.stringify(arr)}, prevArr: ${JSON.stringify(prevArr)}`
}, { deep: true })

const watchCountAndObjNumRes = ref('')
watch([count, () : number => obj.num], (state : number[], preState : number[]) => {
  watchCountAndObjNumRes.value = `state: ${JSON.stringify(state)}, preState: ${JSON.stringify(preState)}`
})

const updateObj = () => {
  obj.num++
  obj.str = `num: ${obj.num}`
  obj.bool = !obj.bool
  obj.arr.push(obj.num)
}
</script>

# computed

接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

注意

  • computed() 需通过泛型指定返回值类型。
    const count = ref(0)
    const doubleCount = computed<number>(() : number => {
      return count.value * 2
    })
    

示例 详情

<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 setup lang="uts">
const count = ref(0)

const doubleCount = computed(() : number => {
  return count.value * 2
})

type Obj = {
  arr : number[]
}

const obj = reactive({
  arr: [1, 2, 3]
} as Obj)


const objArrLen = computed<number>(() : number => {
  return obj.arr.length
})

const update = () => {
  count.value++
  obj.arr.push(obj.arr.length + 1)
}
</script>

# reactive

返回一个对象的响应式代理。

  • 详细信息

    响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。

    若要避免深层响应式转换,只想保留对这个对象顶层次访问的响应性,请使用 shallowReactive() 作替代。

  • 示例 详情

<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>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">{{ JSON.stringify(obj['arr']) }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>count1:</text>
            <text id="count1">{{ count1 }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>obj1.a.b.c:</text>
            <text id="obj1-a-b-c">{{ obj1.getString('a.b.c') }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>arr1(spread):</text>
            <text id="arr1">{{ JSON.stringify(arr1) }}</text>
        </view>
        <button class='mb-10' id="update-count-btn" @click="updateCount">update count</button>
        <button class='mb-10' id="update-obj-str-btn" @click="updateObjStr">update obj.str</button>
        <button class='mb-10' id="update-obj-num-btn" @click="updateObjNum">update obj.num</button>
        <button class='mb-10' id="update-obj-arr-btn" @click="updateObjArr">update obj.arr</button>
        <button class='mb-10' id="update-obj1-a-b-c-btn" @click="updateObj1_A_B_C">update obj1.a.b.c</button>
        <button class='mb-10' id="update-arr1-btn" @click="updateArr1(false)">update arr1 without reactive</button>
        <button class='mb-10' id="update-arr1-reactive-btn" @click="updateArr1(true)">update arr1 with reactive</button>
    </view>
</template>

<script setup lang="uts">
    const count = ref(0)

    // TODO: 待支持后补充泛型示例
    const obj = reactive({
        str: 'default str',
        num: count,
        arr: ['a', 'b', 'c']
    })

    const updateObjStr = () => {
        obj['str'] = 'new str';
    }

    const updateObjNum = () => {
        obj['num'] = (obj['num'] as number) + 1
    }

    const updateCount = () => {
        count.value++
    }

    const updateObjArr = () => {
        (obj['arr'] as string[]).push('d')
    }

    const obj1 = reactive({
        a: { b: { c: 'c' } }
    })

    const count1 = ref(0)
    watchEffect(() => {
        count1.value++
        // 测试getString等keyPath触发依赖收集
        obj1.getString("a.b.c")
    })
    function updateObj1_A_B_C() {
        ((obj1["a"] as UTSJSONObject)["b"] as UTSJSONObject)["c"] = "c1-" + Date.now()
    }
    const arr1 = ref<number[]>([])
    function test(...args : number[]) {
        arr1.value = args
    }
    function updateArr1(isReactive : boolean) {
        if (isReactive) {
            test(...reactive([4, 5, 6]))
        } else {
            test(...[1, 2, 3])
        }
    }
</script>

# readonly

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

  • 详细信息

    只读代理是深层的:对任何嵌套属性的访问都将是只读的。它的 ref 解包行为与 reactive() 相同,但解包得到的值是只读的。

    要避免深层级的转换行为,请使用 shallowReadonly() 作替代。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>data.str:</text>
      <text id="data-str">{{ data.str }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>data.num:</text>
      <text id="data-num">{{ data.num }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>data.arr:</text>
      <text id="data-arr">{{ JSON.stringify(data.arr) }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>readonly data.str:</text>
      <text id="readonly-data-str">{{ readonlyData.str }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>readonly data.num:</text>
      <text id="readonly-data-num">{{ readonlyData.num }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>readonly data.arr:</text>
      <text id="readonly-data-arr">{{ JSON.stringify(readonlyData.arr) }}</text>
    </view>

    <button id="update-data-btn" class="mb-10" @click="updateData">
      update data
    </button>
    <button id="update-readonly-data-btn" @click="updateReadonlyData">
      update readonly data
    </button>
  </view>
</template>

<script setup lang="uts">
type Data = {
  str : string,
  num : number,
  arr : string[]
}
// 可通过泛型指定类型
const data = reactive<Data>({
  str: 'default str',
  num: 0,
  arr: ['a', 'b', 'c']
})
// 可通过泛型指定类型
const readonlyData = readonly<Data>(data)

const updateData = () => {
  data.str = 'new str'
  data.num++
  data.arr.push('d')
}

const updateReadonlyData = () => {
  readonlyData.str = 'new readonly str'
  readonlyData.num++
  readonlyData.arr.push('e')
}
</script>

# watchEffect

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

  • 详细信息

    第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。清理回调会在该副作用下一次执行前被调用,可以用来清理无效的副作用,例如等待中的异步请求 (参见下面的示例)。

    第二个参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。

    默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

    返回值是一个用来停止该副作用的函数。

示例 详情

<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 class="flex justify-between mb-10">
        <text>watch count and obj.num result:</text>
        <text id="watch-count-obj-num-res">{{ watchCountAndObjNumRes }}</text>
      </view>
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

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

  const countRef = ref<UniTextElement | null>(null)
  const count = ref(0)
  const watchCountRes = ref('')
  const watchCountCleanupRes = ref('')
  const watchCountTrackNum = ref(0)

  const stopWatchCount = watchEffect((onCleanup : OnCleanup) => {
    if (countRef.value !== null) {
      // #ifdef APP
      watchCountRes.value = `count: ${count.value}, count ref text (flush sync): ${countRef.value!.value}`
      // #endif
      // #ifdef WEB
      watchCountRes.value = `count: ${count.value}, count ref text (flush sync): ${(countRef.value!.childNodes[0] as HTMLElement).innerText}`
      // #endif 
    } else {
      watchCountRes.value = `count: ${count.value}, count ref text (flush sync): `
    }
    const cancel = () => {
      watchCountCleanupRes.value = `watch count cleanup: ${count.value}`
    }
    onCleanup(cancel)
  }, {
    // 侦听器在响应式依赖改变时立即触发
    flush: 'sync',
    // 响应属性或引用作为依赖项被跟踪时调用
    onTrack(event : DebuggerEvent) {
      if (event.type === 'get') {
        watchCountTrackNum.value++
      }
    },
  })

  const triggerStopWatchCount = () => stopWatchCount()

  const increment = () => {
    count.value++
  }

  const obj = reactive({
    num: 0,
    str: 'num: 0',
    bool: false,
    arr: [0]
  } as Obj)

  const watchObjRes = ref('')
  watchEffect(() => {
    watchObjRes.value = `obj: ${JSON.stringify(obj)}`
  })

  const objStrRef = ref<UniTextElement | null>(null)
  const watchObjStrRes = ref('')
  const watchObjStrTriggerNum = ref(0)
  
  watchEffect(() => {
    if (objStrRef.value !== null) {
      // #ifdef APP
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text (flush pre): ${objStrRef.value!.value}`
      // #endif
      // #ifdef WEB
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text (flush pre): ${(objStrRef.value!.childNodes[0] as HTMLElement).innerText}`
      // #endif 
    } else {
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text (flush pre): `
    }
  }, {
    // 侦听器在组件渲染之前触发
    flush: 'pre',
    // 侦听器回调被依赖项的变更触发时调用
    onTrigger(event : DebuggerEvent) {
      if (event.type === 'set') {
        watchObjStrTriggerNum.value++
      }
    }
  })

  const objBoolRef = ref<UniTextElement | null>(null)
  const watchObjBoolRes = ref('')
  watchEffect(() => {
    if (objBoolRef.value !== null) {
      // #ifdef APP
      watchObjBoolRes.value = `bool: ${obj.bool}, obj.bool ref text (flush post): ${objBoolRef.value!.value}`
      // #endif
      // #ifdef WEB
      watchObjBoolRes.value = `bool: ${obj.bool}, obj.bool ref text (flush post): ${(objBoolRef.value!.childNodes[0] as HTMLElement).innerText}`
      // #endif 
    } else {
      watchObjBoolRes.value = `bool: ${obj.bool}, obj.bool ref text (flush post): `
    }
  }, {
    // 侦听器延迟到组件渲染之后触发
    flush: 'post'
  })


  const watchObjArrRes = ref('')
  watchEffect(() => {
    watchObjArrRes.value = `arr: ${JSON.stringify(obj.arr)}`
  })

  const watchCountAndObjNumRes = ref('')
  watchEffect(() => {
    watchCountAndObjNumRes.value = `count: ${count.value}, obj.num: ${obj.num}`
  })

  const updateObj = () => {
    obj.num++
    obj.str = `num: ${obj.num}`
    obj.bool = !obj.bool
    obj.arr.push(obj.num)
  }
</script>

# watchPostEffect

watchEffect() 使用 flush: 'post' 选项时的别名。

示例 详情

<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.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 class="flex justify-between mb-10">
        <text>watch count and obj.num result:</text>
        <text id="watch-count-obj-num-res">{{ watchCountAndObjNumRes }}</text>
      </view>
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

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

  const countRef = ref<UniTextElement | null>(null)
  const count = ref<number>(0)
  const watchCountRes = ref('')
  const watchCountCleanupRes = ref('')
  const watchCountTrackNum = ref(0)

  const stopWatchCount = watchPostEffect((onCleanup : OnCleanup) => {
    if (countRef.value !== null) {
      // #ifdef APP
      watchCountRes.value = `count: ${count.value}, count ref text: ${countRef.value!.value}`
      // #endif
      // #ifdef WEB
      watchCountRes.value = `count: ${count.value}, count ref text: ${(countRef.value!.childNodes[0] as HTMLElement).innerText}`
      // #endif 
    } else {
      watchCountRes.value = `count: ${count.value}, count ref text: `
    }
    const cancel = () => {
      watchCountCleanupRes.value = `watch count cleanup: ${count.value}`
    }
    onCleanup(cancel)
  },
    {
      // 响应属性或引用作为依赖项被跟踪时调用
      onTrack(event : DebuggerEvent) {
        if (event.type === 'get') {
          watchCountTrackNum.value++
        }
      }
    },
  )

  const triggerStopWatchCount = () => stopWatchCount()

  const increment = () => {
    count.value++
  }

  const obj = reactive({
    num: 0,
    str: 'num: 0',
    bool: false,
    arr: [0]
  } as Obj)

  const watchObjRes = ref('')
  watchPostEffect(() => {
    watchObjRes.value = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}`
  })

  const objStrRef = ref<UniTextElement | null>(null)
  const watchObjStrRes = ref('')
  const watchObjStrTriggerNum = ref(0)
  
  watchPostEffect(() => {
    if (objStrRef.value !== null) {
      // #ifdef APP
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text: ${objStrRef.value!.value}`
      // #endif
      // #ifdef WEB
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text: ${(objStrRef.value!.childNodes[0] as HTMLElement).innerText}`
      // #endif 
    } else {
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text: `
    }
  },{
    // 侦听器回调被依赖项的变更触发时调用
    onTrigger(event : DebuggerEvent) {
      if (event.type === 'set') {
        watchObjStrTriggerNum.value++
      }
    }
  })

  const watchObjArrRes = ref('')
  watchPostEffect(() => {
    watchObjArrRes.value = `arr: ${JSON.stringify(obj.arr)}`
  })

  const watchCountAndObjNumRes = ref('')
  watchPostEffect(() => {
    watchCountAndObjNumRes.value = `count: ${count.value}, obj.num: ${obj.num}`
  })

  const updateObj = () => {
    obj.num++
    obj.str = `num: ${obj.num}`
    obj.bool = !obj.bool
    obj.arr.push(obj.num)
  }
</script>

# watchSyncEffect

watchEffect() 使用 flush: 'sync' 选项时的别名。

示例 详情

<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.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 class="flex justify-between mb-10">
        <text>watch count and obj.num result:</text>
        <text id="watch-count-obj-num-res">{{ watchCountAndObjNumRes }}</text>
      </view>
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

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

  const countRef = ref<UniTextElement | null>(null)
  const count = ref(0)
  const watchCountRes = ref('')
  const watchCountCleanupRes = ref('')
  const watchCountTrackNum = ref(0)

  const stopWatchCount = watchSyncEffect((onCleanup : OnCleanup) => {
    if (countRef.value !== null) {
      // #ifdef APP
      watchCountRes.value = `count: ${count.value}, count ref text: ${countRef.value!.value}`
      // #endif
      // #ifdef WEB
      watchCountRes.value = `count: ${count.value}, count ref text: ${(countRef.value!.childNodes[0] as HTMLElement).innerText}`
      // #endif 
    } else {
      watchCountRes.value = `count: ${count.value}, count ref text: `
    }
    const cancel = () => {
      watchCountCleanupRes.value = `watch count cleanup: ${count.value}`
    }
    onCleanup(cancel)
  },
    {
      // 响应属性或引用作为依赖项被跟踪时调用
      onTrack(event : DebuggerEvent) {
        if (event.type === 'get') {
          watchCountTrackNum.value++
        }
      }
    },
  )

  const triggerStopWatchCount = () => stopWatchCount()

  const increment = () => {
    count.value++
  }

  const obj = reactive({
    num: 0,
    str: 'num: 0',
    bool: false,
    arr: [0]
  } as Obj)

  const watchObjRes = ref('')
  watchSyncEffect(() => {
    watchObjRes.value = `obj: {"num":${obj.num},"str":"${obj.str}","bool":${obj.bool},"arr":${JSON.stringify(obj.arr)}}`
  })

  const objStrRef = ref<UniTextElement | null>(null)
  const watchObjStrRes = ref('')
  const watchObjStrTriggerNum = ref(0)
  
  watchSyncEffect(() => {
    if (objStrRef.value !== null) {
      // #ifdef APP
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text: ${objStrRef.value!.value}`
      // #endif
      // #ifdef WEB
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text: ${(objStrRef.value!.childNodes[0] as HTMLElement).innerText}`
      // #endif 
    } else {
      watchObjStrRes.value = `str: ${obj.str}, obj.str ref text: `
    }
  }, {
    // 侦听器回调被依赖项的变更触发时调用
    onTrigger(event : DebuggerEvent) {
      if (event.type === 'set') {
        watchObjStrTriggerNum.value++
      }
    }
  })

  const watchObjArrRes = ref('')
  watchSyncEffect(() => {
    watchObjArrRes.value = `arr: ${JSON.stringify(obj.arr)}`
  })

  const watchCountAndObjNumRes = ref('')
  watchSyncEffect(() => {
    watchCountAndObjNumRes.value = `count: ${count.value}, obj.num: ${obj.num}`
  })

  const updateObj = () => {
    obj.num++
    obj.str = `num: ${obj.num}`
    obj.bool = !obj.bool
    obj.arr.push(obj.num)
  }
</script>

# 响应式: 工具

Web Android iOS
isRef() 4.0 4.0 4.11
unref() 4.0 4.0 4.11
toRef() 4.11 4.0 4.11
toValue() 4.11 4.0 4.11
toRefs() 4.11 4.0 4.11
isProxy() 4.0 4.0 4.11
isReactive() 4.0 4.0 4.11
isReadonly() 4.0 4.0 4.11

注意

  • toRefs() 仅支持 ArrayUTSJSONObject, 不支持自定义类型。

# 示例代码

# isRef

检查某个值是否为 ref。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>ref count:</text>
      <text id="ref-count">{{ refCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isRef ref count:</text>
      <text id="is-ref-ref-count">{{ isRefRefCount }}</text>
    </view>
    <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>isRef count:</text>
      <text id="is-ref-count">{{ isRefCount }}</text>
    </view>
  </view>
</template>

<script setup lang="uts">
const refCount = ref(0);
const count = 0;
const isRefRefCount = isRef(refCount);
const isRefCount = isRef(count);
</script>

# unref

如果参数是 ref,则返回内部值,否则返回参数本身。这是 val = isRef(val) ? val.value : val 计算的一个语法糖。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>ref count:</text>
      <text id="ref-count">{{ refCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>ref count type:</text>
      <text id="ref-count-type">{{ refCountType }}</text>
    </view>
    <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>count type:</text>
      <text id="count-type">{{ countType }}</text>
    </view>
  </view>
</template>

<script setup lang="uts">
const refCount = ref<number>(0);
const refCountType = typeof refCount;
const count = unref(refCount);
const countType = typeof count;
</script>

# toRef

可以将值、refs 或 getters 规范化为 refs。

也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

示例 详情

<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>isRef count:</text>
      <text id="is-ref-count">{{ isRefCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>ref count:</text>
      <text id="ref-count">{{ refCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isRef ref count:</text>
      <text id="is-ref-ref-count">{{ isRefRefCount }}</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>toRef(obj, "num"):</text>
      <text id="to-ref-obj-num">{{ objNum }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>toRef(() => obj.num):</text>
      <text id="to-ref-fn-obj-num">{{ readonlyObjNum }}</text>
    </view>

    <button class="mt-10" id="increment-btn" @click="increment">
      increment obj.num
    </button>
  </view>
</template>

<script setup lang="uts">
const count = 0;
const isRefCount = isRef(count);
const refCount = toRef<number>(count);
const isRefRefCount = isRef(refCount);

type Obj = {
  num : number
}
const obj = reactive({
  num: 0
} as Obj)

const objNum = toRef<number>(obj, 'num')

const readonlyObjNum = toRef<number>(() : number => obj.num)

const increment = () => {
  obj.num++;
  objNum.value++;
  readonlyObjNum.value++;
}
</script>

# toValue

将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>ref count:</text>
      <text id="ref-count">{{ refCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isRef ref count:</text>
      <text id="is-ref-ref-count">{{ isRefRefCount }}</text>
    </view>
    <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>isRef count:</text>
      <text id="is-ref-count">{{ isRefCount }}</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>toValue(() => obj.num):</text>
      <text id="to-value-obj-num">{{ toValueObjNum }}</text>
    </view>
    <button class="mt-10" id="increment-btn" @click="increment">
      increment obj.num
    </button>
  </view>
</template>

<script setup lang="uts">
const refCount = ref<number>(0);;
const isRefRefCount = isRef(refCount);
const count = toValue(refCount);
const isRefCount = isRef(count);

type Obj = {
  num : number
}
const obj = reactive({
  num: 0
} as Obj)

let toValueObjNum = toValue(() : number => 0)

const increment = () => {
  obj.num++;
  toValueObjNum++;
}
</script>

# toRefs

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>state.num:</text>
      <text id="state-num">{{ state['num'] }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>state.str:</text>
      <text id="state-str">{{ state['str'] }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>stateAsRefs.num:</text>
      <text id="state-as-refs-num">{{ (stateAsRefs['num'] as Ref<number>).value }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>stateAsRefs.str:</text>
      <text id="state-as-refs-str">{{ (stateAsRefs['str'] as Ref<string>).value }}</text>
    </view>
    <button class="mt-10" id="update-state-btn" @click="updateState">update state</button>
  </view>
</template>

<script setup lang='uts'>
  // toRefs 仅支持 array 和 UTSJSONObject, 不支持自定义类型
  const state = reactive({
    num: 0,
    str: 'str-0'
  })

  const stateAsRefs = toRefs(state)

  const updateState = () => {
    state['num'] = (state['num'] as number) + 1;
    (stateAsRefs['str'] as Ref<string>).value = `str-${(stateAsRefs['num'] as Ref<number>).value}`
  }
</script>

# isProxy

检查一个对象是否是由 reactive()readonly()shallowReactive()shallowReadonly() 创建的代理。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>isProxy(count):</text>
      <text id="is-proxy-count">{{ isProxyCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isProxy(refCount):</text>
      <text id="is-proxy-ref-count">{{ isProxyRefCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isProxy(reactiveCount):</text>
      <text id="is-proxy-reactive-count">{{ isProxyReactiveCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isProxy(readonlyCount):</text>
      <text id="is-proxy-readonly-count">{{ isProxyReadonlyCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isProxy(shallowReactiveCount):</text>
      <text id="is-proxy-shallow-reactive-count">{{
        isProxyShallowReactiveCount
      }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isProxy(shallowReadonlyCount):</text>
      <text id="is-proxy-shallow-readonly-count">{{
        isProxyShallowReadonlyCount
      }}</text>
    </view>
  </view>
</template>

<script setup lang="uts">
const count = 0;
const isProxyCount = isProxy(count);

const refCount = ref(0);
const isProxyRefCount = isProxy(refCount);

const reactiveCount = reactive({ count: 0 });
const isProxyReactiveCount = isProxy(reactiveCount);

const readonlyCount = readonly({ count: 0 });
const isProxyReadonlyCount = isProxy(readonlyCount);

const shallowReactiveCount = shallowReactive({ count: 0 });
const isProxyShallowReactiveCount = isProxy(shallowReactiveCount);

const shallowReadonlyCount = shallowReadonly({ count: 0 });
const isProxyShallowReadonlyCount = isProxy(shallowReadonlyCount);
</script>

# isReactive

检查一个对象是否是由 reactive()shallowReactive() 创建的代理。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>isReactive(count):</text>
      <text id="is-reactive-count">{{ isReactiveCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReactive(count):</text>
      <text id="is-reactive-count">{{ isReactiveCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReactive(refCount):</text>
      <text id="is-reactive-ref-count">{{ isReactiveRefCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReactive(reactiveCount):</text>
      <text id="is-reactive-reactive-count">{{ isReactiveReactiveCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReactive(readonlyCount):</text>
      <text id="is-reactive-readonly-count">{{ isReactiveReadonlyCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReactive(shallowReactiveCount):</text>
      <text id="is-reactive-shallow-reactive-count">{{
        isReactiveShallowReactiveCount
      }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReactive(shallowReadonlyCount):</text>
      <text id="is-reactive-shallow-readonly-count">{{
        isReactiveShallowReadonlyCount
      }}</text>
    </view>
  </view>
</template>

<script setup lang="uts">
const count = 0;
const isReactiveCount = isReactive(count);

const refCount = ref(0);
const isReactiveRefCount = isReactive(refCount);

const reactiveCount = reactive({ count: 0 });
const isReactiveReactiveCount = isReactive(reactiveCount);

const readonlyCount = readonly({ count: 0 });
const isReactiveReadonlyCount = isReactive(readonlyCount);

const shallowReactiveCount = shallowReactive({ count: 0 });
const isReactiveShallowReactiveCount = isReactive(shallowReactiveCount);

const shallowReadonlyCount = shallowReadonly({ count: 0 });
const isReactiveShallowReadonlyCount = isReactive(shallowReadonlyCount);
</script>

# isReadonly

检查传入的值是否为只读对象。只读对象的属性可以更改,但他们不能通过传入的对象直接赋值。

通过 readonly()shallowReadonly() 创建的代理都是只读的,因为他们是没有 set 函数的 computed() ref。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>isReadonly(count):</text>
      <text id="is-readonly-count">{{ isReadonlyCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReadonly(refCount):</text>
      <text id="is-readonly-ref-count">{{ isReadonlyRefCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReadonly(reactiveCount):</text>
      <text id="is-readonly-reactive-count">{{ isReadonlyReactiveCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReadonly(readonlyCount):</text>
      <text id="is-readonly-readonly-count">{{ isReadonlyReadonlyCount }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReadonly(shallowReactiveCount):</text>
      <text id="is-readonly-shallow-reactive-count">{{
        isReadonlyShallowReactiveCount
      }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>isReadonly(shallowReadonlyCount):</text>
      <text id="is-readonly-shallow-readonly-count">{{
        isReadonlyShallowReadonlyCount
      }}</text>
    </view>
  </view>
</template>

<script setup lang='uts'>
  const count = 0;
  const isReadonlyCount = isReadonly(count);

  const refCount = ref(0);
  const isReadonlyRefCount = isReadonly(refCount);

  const reactiveCount = reactive({ count: 0 });
  const isReadonlyReactiveCount = isReadonly(reactiveCount);

  const readonlyCount = readonly({ count: 0 });
  const isReadonlyReadonlyCount = isReadonly(readonlyCount);

  const shallowReactiveCount = shallowReactive({ count: 0 });
  const isReadonlyShallowReactiveCount = isReadonly(shallowReactiveCount);

  const shallowReadonlyCount = shallowReadonly({ count: 0 });
  const isReadonlyShallowReadonlyCount = isReadonly(shallowReadonlyCount);
</script>

# 响应式: 进阶

Web Android iOS
shallowRef() 4.0 4.0 4.11
triggerRef() x 4.0 4.11
customRef() 4.0 4.0 4.11
shallowReactive() 4.0 4.0 4.11
shallowReadonly() 4.0 4.0 4.11
toRaw() 4.0 4.0 4.11
markRaw() - - -
effectScope() 4.0 4.0 4.11
getCurrentScope() 4.0 4.0 4.11
onScopeDispose() 4.0 4.0 4.11

# 示例代码

# customRef

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

  • 详细信息

    customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 tracktrigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。

    一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>state.count:</text>
      <text id="state-count">{{ state['count'] }}</text>
    </view>
    <button class="mb-10 increment-btn" @click="increment">
      increment state.count
    </button>
    <button class="mb-10 trigger-ref-btn" @click="triggerRefState">
      triggerRef state
    </button>
  </view>
</template>

<script setup lang="uts">
const useCustomRef = (value : UTSJSONObject) : Ref<UTSJSONObject> => {
  // @ts-ignore
  return customRef<UTSJSONObject>((track, trigger) => {
    return {
      get() : UTSJSONObject {
        track()
        return value
      },
      set(newValue : UTSJSONObject) {
        value = newValue
        trigger()
      }
    }
  })
}

const state = useCustomRef({ count: 0 })


const increment = () => {
  state.value['count'] = (state.value['count'] as number) + 1
}
const triggerRefState = () => {
  triggerRef(state)
}
</script>

# effectScope

创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。对于该 API 的使用细节

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>counter:</text>
      <text id="counter">{{ counter }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>watch counter result:</text>
      <text id="watch-counter-res">{{ watchCounterRes }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>watchEffect counter result:</text>
      <text id="watch-effect-counter-res">{{ watchEffectCounterRes }}</text>
    </view>
    <button
      id="increment-counter-btn"
      class="mt-10"
      @click="
        () => {
          counter++;
        }
      ">
      increment counter
    </button>
    <button id="stop-effect-scope-btn" class="mt-10" @click="stopEffectScope">
      stop effect scope
    </button>
  </view>
</template>

<script setup lang="uts">
const scope = effectScope()

const counter = ref(0)

const watchCounterRes = ref('')

const watchEffectCounterRes = ref('')

scope.run(() => {
  watch(counter, (newVal : number, oldVal : number) => {
    watchCounterRes.value = `newVal: ${newVal}, oldVal: ${oldVal}`
  })

  watchEffect(() => {
    watchEffectCounterRes.value = `counter: ${counter.value}`
  })
})

const stopEffectScope = () => scope.stop()
</script>

# getCurrentScope

如果有的话,返回当前活跃的 effect 作用域。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>hasCurrentScope:</text>
      <text id="has-current-scope">{{ hasCurrentScope }}</text>
    </view>
    <button id="create-scope-btn" class="mt-10" @click="createScope">
      create scope
    </button>
  </view>
</template>

<script setup lang="uts">
const hasCurrentScope = ref(false);

const createScope = () => {
  const scope = effectScope();
  scope.run(() => {
    hasCurrentScope.value = getCurrentScope() !== null;
  });
};
</script>

# onScopeDispose

在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。

这个方法可以作为可复用的组合式函数中 onUnmounted 的替代品,它并不与组件耦合,因为每一个 Vue 组件的 setup() 函数也是在一个 effect 作用域中调用的。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>hasCurrentScope:</text>
      <text id="has-current-scope">{{ hasCurrentScope }}</text>
    </view>
    <button id="create-scope-btn" class="mt-10" @click="createScope">
      create scope
    </button>
    <button id="stop-scope-btn" class="mt-10" @click="stopScope">
      stop scope
    </button>
  </view>
</template>

<script setup lang="uts">
const hasCurrentScope = ref(false)

let scope = null as EffectScope | null

const createScope = () => {
  scope = effectScope();
  (scope as EffectScope).run(() => {
    hasCurrentScope.value = getCurrentScope() != null
    onScopeDispose(() => {
      hasCurrentScope.value = getCurrentScope() != null
    })
  })
}

const stopScope = () => {
  if (scope !== null) {
    (scope as EffectScope).stop()
  }
}
</script>

# shallowReactive

reactive() 的浅层作用形式。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>state.count:</text>
      <text id="state-count" :data-count="state.count">{{ state.count }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>state.nested.count:</text>
      <text id="state-nested-count">{{ state.nested.count }}</text>
    </view>
    <button
      id="increment-state-count-btn"
      class="mb-10"
      @click="incrementStateCount">
      increment state.count
    </button>
    <button
      id="increment-state-nested-count-btn"
      @click="incrementStateNestedCount">
      increment state.nested.count
    </button>
  </view>
</template>

<script setup lang="uts">
type StateNested = {
  count : number
}
type State = {
  count : number,
  nested : StateNested
}
// 可通过泛型指定类型
const state = shallowReactive<State>({
  count: 0,
  nested: {
    count: 0
  }
})

const incrementStateCount = () => {
  state.count++
}

const incrementStateNestedCount = () => {
  state.nested.count++
}

defineExpose({
  state
})
</script>

# shallowReadonly

readonly() 的浅层作用形式

示例 详情

<template>
  <view :key="pageKey" class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>state.count:</text>
      <text id="state-count">{{ state.count }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>state.nested.count:</text>
      <text id="state-nested-count">{{ state.nested.count }}</text>
    </view>
    <button
      id="increment-state-count-btn"
      class="mb-10"
      @click="incrementStateCount">
      increment state.count
    </button>
    <button
      id="increment-state-nested-count-btn"
      class="mb-10"
      @click="incrementStateNestedCount">
      increment state.nested.count
    </button>
    <button id="update-page-render-btn" @click="updatePageRender">
      update page render
    </button>
  </view>
</template>

<script setup lang="uts">
let pageKey = ref<number>(0)

type StateNested = {
  count : number
}
type State = {
  count : number,
  nested : StateNested
}
// 可通过泛型指定类型
const state = shallowReadonly<State>({
  count: 0,
  nested: {
    count: 0
  }
})

// #ifdef APP
const incrementStateCount = () => {
  state.count++
}

const incrementStateNestedCount = () => {
  state.nested.count++
}
// #endif

const updatePageRender = () => {
  pageKey.value = Date.now()
}
</script>

# shallowRef

ref() 的浅层作用形式。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>state.count:</text>
      <text id="state-count">{{ state.count }}</text>
    </view>
    <button
      id="increment-state-count-btn"
      class="mb-10"
      @click="incrementStateCount">
      increment state.count
    </button>
    <button id="update-state-btn" @click="updateState">update state</button>
  </view>
</template>

<script setup lang="uts">
type State = {
  count: number
}
// 可通过泛型指定类型
const state = shallowRef<State>({
  count: 0
})

const incrementStateCount = () => {
  state.value.count++
}

const updateState = () => {
  state.value = { count: state.value.count } as State
}
</script>

# toRaw

根据一个 Vue 创建的代理返回其原始对象。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>check toRaw ref:</text>
      <text id="check-to-raw-ref">{{ checkToRawRef }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>check toRaw reactive:</text>
      <text id="check-to-raw-reactive">{{ checkToRawReactive }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>check toRaw readonly:</text>
      <text id="check-to-raw-readonly">{{ checkToRawReadonly }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>check toRaw shallowReactive:</text>
      <text id="check-to-raw-shallow-reactive">{{
        checkToRawShallowReactive
      }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>check toRaw shallowReadonly:</text>
      <text id="check-to-raw-shallow-readonly">{{
        checkToRawShallowReadonly
      }}</text>
    </view>
  </view>
</template>

<script setup lang="uts">
const obj = {}

const refObj = ref(obj);
const checkToRawRef = toRaw<UTSJSONObject>(refObj) === obj;

const reactiveObj = reactive(obj);
const checkToRawReactive = toRaw<UTSJSONObject>(reactiveObj) === obj;

const readonlyObj = readonly(obj);
const checkToRawReadonly = toRaw<UTSJSONObject>(readonlyObj) === obj;

const shallowReactiveObj = shallowReactive(obj);
const checkToRawShallowReactive = toRaw<UTSJSONObject>(shallowReactiveObj) === obj;

const shallowReadonlyObj = shallowReadonly(obj);
const checkToRawShallowReadonly = toRaw<UTSJSONObject>(shallowReadonlyObj) === obj;
</script>

# triggerRef

强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。

示例 详情

<template>
  <view class="page">
    <view class="flex justify-between flex-row mb-10">
      <text>state.count:</text>
      <text id="state-count">{{ state.count }}</text>
    </view>
    <button
      id="increment-state-count-btn"
      class="mb-10"
      @click="incrementStateCount">
      increment state.count
    </button>
    <button id="trigger-ref-state-btn" @click="triggerRefState">
      trigger state
    </button>
  </view>
</template>

<script setup lang="uts">
type State = {
  count: number
}
const state = shallowRef({
  count: 0
} as State)

const incrementStateCount = () => {
  state.value.count++
}

const triggerRefState = () => {
  triggerRef(state)
}
</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>onPageShow 触发:</text>
        <text>{{ isOnPageShowTriggered }}</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>onPageHide 触发:</text>
        <text>{{ isOnPageHideTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <text>onResize 触发:</text>
        <text>{{ isOnResizeTriggered }}</text>
      </view>
      <view class="flex flex-row justify-between mt-10">
        <MonitorPageLifecycleComposition />
      </view>
      <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 setup lang="uts">
import { state, setLifeCycleNum } from '@/store/index.uts'
import MonitorPageLifecycleComposition from './monitor-page-lifecycle-composition.uvue'

const isOnloadTriggered = ref(false)
const isOnPageShowTriggered = ref(false)
const isOnReadyTriggered = ref(false)
const isOnPullDownRefreshTriggered = ref(false)
const isOnPageScrollTriggered = ref(false)
const isOnReachBottomTriggered = ref(false)
const isOnBackPressTriggered = ref(false)
const isOnPageHideTriggered = ref(false)
const isOnResizeTriggered = ref(false)

type DataInfo = {
  isScrolled : boolean
}
const dataInfo = reactive({
  isScrolled: false,
} as DataInfo)

onLoad((options : OnLoadOptions) => {
  console.log('onLoad', options)
  isOnloadTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 100)
})
onPageShow(() => {
  isOnPageShowTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})
onReady(() => {
  isOnReadyTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})
onPullDownRefresh(() => {
  isOnPullDownRefreshTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})
onPageScroll((e: OnPageScrollOptions) => {
  console.log('onPageScroll', e)
  isOnPageScrollTriggered.value = true
  // 自动化测试
  dataInfo.isScrolled = true
})
onReachBottom(() => {
  isOnReachBottomTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})
onBackPress((options : OnBackPressOptions) : boolean | null => {
  console.log('onBackPress', options)
  isOnBackPressTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 10)
  return null
})
onPageHide(() => {
  isOnPageHideTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 10)
})
onUnload(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 100)
})
onResize((options: OnResizeOptions) => {
  console.log('onBackPress', options)
  isOnResizeTriggered.value = true
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})

// 自动化测试
const pageGetLifeCycleNum = () : number => {
  return state.lifeCycleNum
}
// 自动化测试
const pageSetLifeCycleNum = (num : number) => {
  setLifeCycleNum(num)
}

// 自动化测试
const pullDownRefresh = () => {
  uni.startPullDownRefresh({
    success() {
      setTimeout(() => {
        uni.stopPullDownRefresh()
      }, 1500)
    },
  })
}

const scrollToBottom = () => {
  uni.pageScrollTo({
    scrollTop: 2000,
  })
}

const goOnBackPress = () => {
  uni.navigateTo({url: '/pages/lifecycle/page/onBackPress/on-back-press-composition'})
}

defineExpose({
  dataInfo,
  pageGetLifeCycleNum,
  pageSetLifeCycleNum,
  pullDownRefresh,
  scrollToBottom,
})
</script>

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

# 组件生命周期

# 兼容性

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 完成。
这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。

# onMounted、onUnmounted 使用注意事项

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

注:页面的 onReady 生命周期可以获取到排版后的节点信息

# onActivated、onDeactivated 使用注意事项

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

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

当 B 页面 back 返回 A 页面时

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

示例 详情

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

<script setup lang='uts'>
import { state, setLifeCycleNum } from '@/store/index.uts'

const title = ref('component for composition API lifecycle test')

const emit = defineEmits<{
  (e : 'updateIsScroll', val : boolean) : void
}>()

onLoad((_ : OnLoadOptions) => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 100)
})
onPageShow(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})
onReady(() => {
  // 自动化测试
  // TODO: onReady 未触发
  setLifeCycleNum(state.lifeCycleNum + 10)
})

onPullDownRefresh(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})
onPageScroll((_) => {
  // 自动化测试
  emit('updateIsScroll', true)
})
onReachBottom(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 10)
})
onBackPress((_ : OnBackPressOptions) : boolean | null => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 10)
  return null
})
onPageHide(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 10)
})
onUnload(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 100)
})

onBeforeMount(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 1)
  console.log('component for lifecycle test onBeforeMount')
})

onMounted(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 1)
  console.log('component for lifecycle test mounted')
})

onBeforeUpdate(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 1)
  console.log('component for lifecycle test beforeUpdate')
})

onUpdated(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 1)
  console.log('component for lifecycle test updated')
})

onBeforeUnmount(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 1)
  console.log('component for lifecycle test beforeUnmount')
})

onUnmounted(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 1)
  console.log('component for lifecycle test unmounted')
})

onActivated(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum + 1)
  console.log('component for lifecycle test onActivated')
})
onDeactivated(() => {
  // 自动化测试
  setLifeCycleNum(state.lifeCycleNum - 1)
  console.log('component for lifecycle test onDeactivated')
})

const updateTitle = () => {
  title.value = 'component for lifecycle test updated'
}
</script>

# <script setup>

# 基本语法

  • 仅支持 export default {} 方式定义组件。
  • data 仅支持函数返回对象字面量方式。
    <script lang="uts">
      export default {
        data() {
          return {
            // 必须写这里
          }
        }
      }
    </script>
    
# 属性
名称 类型 默认值 兼容性 描述
setup Any -
-
lang Any -
合法值 兼容性 描述
ts
typescript
uts
uts
# 兼容性
Web Android iOS
4.0 3.9 4.11
# 参见

# 示例

示例 详情

<template>
  <!-- #ifdef APP -->
  <scroll-view style="flex: 1">
    <!-- #endif -->
    <view class="page">
      <view class="flex justify-between flex-row mt-10">
        <text>str:</text>
        <text id="str">{{ str }}</text>
      </view>
      <view class="flex justify-between flex-row mt-10">
        <text>num:</text>
        <text id="num">{{ num }}</text>
      </view>
      <view class="flex justify-between flex-row mt-10">
        <text>bool:</text>
        <text id="bool">{{ bool }}</text>
      </view>
      <view class="flex justify-between flex-row mt-10">
        <text>count:</text>
        <text id="count">{{ count }}</text>
      </view>
      <button class="mt-10" id="increment-btn" @click="increment">
        increment count
      </button>
      <view class="flex justify-between flex-row mt-10">
        <text>obj.str:</text>
        <text id="obj-str">{{ obj['str'] }}</text>
      </view>
      <view class="flex justify-between flex-row mt-10">
        <text>obj.num:</text>
        <text id="obj-num">{{ obj['num'] }}</text>
      </view>
      <view class="flex justify-between flex-row mt-10">
        <text>obj.bool:</text>
        <text id="obj-bool">{{ obj['bool'] }}</text>
      </view>
      <button class="mt-10" id="update-obj-btn" @click="updateObj">
        update obj
      </button>
      <!-- #ifdef APP -->
      <RenderFunction
        :str="str"
        :count="count"
        :obj="obj"
        @compUpdateObj="compUpdateObj"
        :isShow="true" />
      <!-- #endif -->
      <Foo>
        <text class="mt-10" id="default-slot-in-foo">default slot in Foo</text>
      </Foo>
    </view>
    <!-- #ifdef APP -->
  </scroll-view>
  <!-- #endif -->
</template>

<script lang="uts">
// #ifdef APP
import RenderFunction from './RenderFunction.uvue'
// #endif
import Foo from './Foo.uvue'
export default {
  components: {
    // #ifdef APP
    RenderFunction,
    // #endif
    Foo
  },
  setup() {
    const count = ref(0)
    // 函数只能通过声明变量,赋值函数的方式,不支持 function xxx(){}
    const increment = () => { count.value++ }
    const obj = reactive({
      str: 'obj default str',
      num: 0,
      bool: false,
    })
    const updateObj = () => {
      obj['str'] = 'obj new str'
      obj['num'] = 100
      obj['bool'] = true
    }
    const compUpdateObj = () => {
      obj['str'] = 'obj new str by comp update'
      obj['num'] = 200
      obj['bool'] = true
    }
    return {
      str: 'default str',
      num: 0,
      bool: false,
      count,
      increment,
      obj,
      updateObj,
      compUpdateObj
    }
  }
}
</script>

# 单文件组件中方法兼容性

Web Android iOS
defineProps() 4.0 4.0 4.11
defineEmits() 4.0 4.0 4.11
defineModel() 4.11 4.0 4.11
defineExpose() 4.0 4.0 4.11
defineOptions() 4.11 4.0 4.11
defineSlots() 4.0 4.0 4.11
useSlots() 4.0 4.0 4.11
useAttrs() 4.0 4.0 4.11

# defineProps()

仅支持数组字面量、对象字面量定义(等同于 options 中的 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 setup lang="uts">
  import ArrayLiteral from './array-literal-composition.uvue'
  import ObjectType from "./object-type-composition.uvue";
  import SameNamePropDefaultValue from "./same-name-prop-default-value-composition.uvue";
  import PropsWithDefaults from "./props-with-defaults.uvue";
  import ReferenceTypes from './reference-types-composition.uvue'

  const str = 'str'
  const num = 10
  const bool = true
  const obj = { age: 18 }
  const arr = ['a', 'b', 'c']
</script>

# defineEmits()

仅支持数组字面量和纯类型参数的方式来声明。

// 数组字面量
const emit = defineEmits(['change'])

// 纯类型参数
const emit = defineEmits<{
  (e : 'change', id : number) : void
}>()
const emit = defineEmits<{
  // 具名元组语法
  change : [id: number]
}>()

# 示例

详情

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

<script setup lang="uts">
const emit = defineEmits(['callback'])

const click = () => {
  emit('callback', `${Date.now()}`)
}

defineExpose({
  click
})
</script>

# defineOptions()

仅支持对象字面量方式定义。

defineOptions({
  data() {
    return {
      count: 0,
      price: 10,
      total: 0
    }
  },
  computed: {
    doubleCount() : number {
      return this.count * 2
    },
  },
  watch: {
    count() {
      this.total = this.price * this.count
    },
  },
  methods: {
    increment() {
      this.count++
    }
  }
})

# 示例

详情

<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 setup lang="uts">
import mixins from "./mixins.uts"

defineOptions({
  mixins: [mixins],
  name: "$options",
  _customKey: "custom key"
})

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

const dataInfo = reactive({
  name: "",
  customKey: "",
  mixinDataStr: ""
} as DataInfo)

onMounted(() => {
  const instance = getCurrentInstance()!.proxy!
  dataInfo.name = instance.$options.name!
  // #ifndef APP-ANDROID
  dataInfo.customKey = instance.$options._customKey
  dataInfo.mixinDataStr = instance.$options.data!({})!['str']
  // #endif
})

defineExpose({
  dataInfo
})
</script>

# defineExpose()

使用 <script setup> 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性,注意:

  • 仅支持对象字面量方式定义 defineExpose 导出的属性, 例如:
defineExpose({
  count
})
  • 导出的变量或方法,必须是 setup 中定义的,暂不支持外部定义

示例 详情

<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 setup lang="uts">
  type Obj = {
    str : string,
    num : number,
    arr : number[]
  }

  const instance = getCurrentInstance()!.proxy!

  const str = ref('default str')
  const num = ref(0)
  // 可通过泛型指定类型
  const arr = ref<number[]>([1, 2, 3])
  const obj = ref<Obj>({
    str: 'default obj.str',
    num: 10,
    arr: [4, 5, 6]
  })

  const refElement = ref<UniElement | null>(null)
  const refElementIsSame = ref(false)

  const refTest = () => {
    const queryElementById1 = uni.getElementById('idRef')
    const queryElementById2 = uni.getElementById('idRef')
    const htmlRefElement = instance.$refs['htmlRef'] as UniElement | null;
    refElement.value = htmlRefElement
    if (queryElementById1 === queryElementById2
      && queryElementById1 === htmlRefElement
      && queryElementById1 === refElement.value
    ) {
      refElementIsSame.value = true
    }
  }
  const updateData = () => {
    str.value = 'new str'
    num.value = 1
    arr.value = [4, 5, 6]

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

    refTest()
  }

  defineExpose({
    updateData
  })
</script>

# defineModel()

这个宏可以用来声明一个双向绑定 prop,通过父组件的 v-model 来使用。组件 v-model 指南中也讨论了示例用法。

在底层,这个宏声明了一个 model prop 和一个相应的值更新事件。如果第一个参数是一个字符串字面量,它将被用作 prop 名称;否则,prop 名称将默认为 "modelValue"。在这两种情况下,你都可以再传递一个额外的对象,它可以包含 prop 的选项和 model ref 的值转换选项。

注意: android 端 defineModel 暂不支持创建 Array 类型 prop

# 示例

详情

<template>
  <view>
    <view class="mb-10 flex justify-between flex-row">
      <text>v-model in Foo:</text>
      <text id="model-value-text">{{ modelValue }}</text>
    </view>
    <view class="mb-10 flex justify-between flex-row">
      <text>v-model:msg in Foo:</text>
      <text id="model-msg-text">{{ msg }}</text>
    </view>
    <view class="mb-10 flex justify-between flex-row">
      <text>defineModel num:</text>
      <text id="model-num-text">{{ num }}</text>
    </view>
    <view class="mb-10 flex justify-between flex-row">
      <text>defineModel strArr:</text>
      <text id="model-str-arr-text">{{ JSON.stringify(strArr) }}</text>
    </view>
    <view class="mb-10 flex justify-between flex-row">
      <text>defineModel numArr:</text>
      <text id="model-num-arr-text">{{ JSON.stringify(numArr) }}</text>
    </view>
    <button class="mb-10" id="update-value-btn" @click="updateValue">
      update value
    </button>
  </view>
</template>

<script setup lang="uts">
// 在被修改时,触发 "update:modelValue" 事件
const modelValue = defineModel({ type: String })

// 在被修改时,触发 "update:msg" 事件
const msg = defineModel('msg', { type: String, default: 'default msg' })

const num = defineModel('num', { type: Number, default: 1 })

const strArr = defineModel<string[]>('strArr', { default: () => [] as string[] })
const numArr = defineModel('numArr', {type: Array as PropType<number[]>, required: true })

const updateValue = () => {
  modelValue.value += '1'
  msg.value += '2'
  num.value++
  strArr.value.push(`${strArr.value.length}`)
  numArr.value.push(numArr.value.length)
}
</script>

# defineSlots()

这个宏可以用于为 IDE 提供插槽名称和 props 类型检查的类型提示。

defineSlots() 只接受类型参数,没有运行时参数。类型参数应该是一个类型字面量,其中属性键是插槽名称,值类型是插槽函数。函数的第一个参数是插槽期望接收的 props,其类型将用于模板中的插槽 props。返回类型目前被忽略,可以是 any,但我们将来可能会利用它来检查插槽内容。

它还返回 slots 对象,该对象等同于在 setup 上下文中暴露或由 useSlots() 返回的 slots 对象。

# 示例

详情

<template>
  <view>
    <slot name="header" :msg="msg"></slot>
    <slot :num="num"></slot>
    <slot name="num1" :num="num"></slot>
    <slot name="num2" :num="num"></slot>
    <slot name="msgTrue" :msg="msg"></slot>
    <slot name="msgFalse" :msg="msg"></slot>
    <slot name="footer" :arr="arr"></slot>
  </view>
</template>

<script setup lang='uts'>
  const msg = ref('foo msg')
  const num = ref<number>(0)
  const arr = ref<string[]>(['a', 'b', 'c'])

  defineSlots<{
    header(props : { msg : string }) : any,
    default(props : { num : number }) : any,
    num1(props : { num : number }) : any,
    num2(props : { num : number }) : any,
    msgTrue(props : { msg : string }) : any,
    msgFalse(props : { msg : string }) : any,
    footer(props : { arr : string[] }) : any
  }>()
</script>

# useSlots() 和 useAttrs()

<script setup> 使用 slotsattrs 的情况应该是相对来说较为罕见的,因为可以在模板中直接通过 $slots$attrs 来访问它们。在你的确需要使用它们的罕见场景中,可以分别用 useSlotsuseAttrs 两个辅助函数:

# useSlots() 示例

详情

<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 setup lang="uts">
  import Foo from './Foo-composition.uvue'

  const msgTrue = ref({
    isShow: true,
    name: 'msgTrue'
  })
  const msgFalse = ref({
    isShow: false,
    name: 'msgFalse'
  })
</script>

# useAttrs() 示例

详情

<template>
  <view>
    <view class="mb-10 flex flex-row justify-between">
      <text>hasPropsAttr</text>
      <text id="has-props-attr">{{ hasPropsAttr }}</text>
    </view>
    <view class="mb-10 flex flex-row justify-between">
      <text>hasEmitsAttr</text>
      <text id="has-emits-attr">{{ hasEmitsAttr }}</text>
    </view>
    <view class="mb-10 flex flex-row justify-between">
      <text>hasClassAttr</text>
      <text id="has-class-attr">{{ hasClassAttr }}</text>
    </view>
  </view>
</template>

<script setup lang="uts">
defineEmits(['childClick'])

defineProps({
  str: {
    type: String,
    required: true
  }
})

const attrs = useAttrs()

const hasPropsAttr = computed(():boolean => {
  return attrs['val'] != null
})

const hasEmitsAttr = computed(():boolean => {
  return attrs['childClick'] != null
})

const hasClassAttr = computed(():boolean => {
  return attrs['class'] != null
})
</script>

# 与渲染函数一起使用

示例 详情

<script setup lang="uts">
import CompForHFunction from '@/components/CompForHFunction.uvue'
import CompForHFunctionWithSlot from '@/components/CompForHFunctionWithSlot.uvue'
import Foo from './Foo.uvue'
const msg = ref('default msg')
// 故意外部声明为UTSJSONObject
const msgProps = { class: 'uni-common-mt msg', style: { color: 'blue' } }
const render = ():VNode => h('view', { class: 'page' }, [
  h(CompForHFunctionWithSlot, {}, () : VNode[] => [h('text', { class: 'comp-slot' }, 'component slot')]),
  h(CompForHFunction, { msg: msg.value }),
  h('text', msgProps, msg.value),
  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: () => {
        msg.value = 'new msg'
      }
    },
    'click'
  )
])
</script>

<template><render /></template>

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