# 组合式 API

注意

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

# 响应式: 核心

Web 微信小程序 Android iOS HarmonyOS
ref() 4.0 4.41 4.11 -
computed() 4.0 4.41 4.11 -
reactive() 4.0 4.41 4.11 -
readonly() 4.0 4.41 4.0 4.11 -
watchEffect() 4.0 4.41 4.0 4.11 -
watchPostEffect() 4.0 4.41 4.0 4.11 -
watchSyncEffect() 4.0 4.41 4.0 4.11 -
watch() 4.0 4.41 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>
    <view class="flex justify-between flex-row mb-10">
      <text>counters.length:</text>
      <text id="counters-count">{{ counters.length }}</text>
    </view>
    <view class="flex justify-between flex-row mb-10">
      <text>issue15557:</text>
      <text id="issue-15557">{{ issue15557?.['a'] }}</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>
    <button class='mb-10' id='change-counters-btn' @click='changeCounters'>change counters</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 counters = ref<Counter[]>([])

  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++
  }
  const changeCounters = () => {
    counters.value.push({ count: 0 })
  }
  
  // issue/15557
  const issue15557 = ref<UTSJSONObject | null>(null)
  issue15557.value = {
    a: 1
  }
  issue15557.value!.set('a', 2)
</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
    })
    
  • 目前需要可传参的计算属性时,需要手动指定返回值类型
    const stateText = computed(() : (state : number) => string => {
      return (state : number) : string => {
        const stateArr = ['未审核', '审核中', '审核通过']
        return stateArr[state]
      }
    })
    stateText.value(1)
    

示例 详情

<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>
    <view class="flex justify-between flex-row mb-10">
        <text>computed stateText(1):</text>
        <text id="computed-with-argument">{{ stateText(1) }}</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)
}

const stateText = computed(() => {
  return (state: number) => {
    const stateArr = ['未审核', '审核中', '审核通过']
    return stateArr[state]
  }
})
</script>

# reactive

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

  • 详细信息

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

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

注意

  • reactive 在 app-android 平台目前不支持对 class 做响应式,推荐使用 type 定义存储数据的对象类型。
<template>
    <scroll-view direction="vertical" style="flex: 1;">
        <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>
        <view class="flex justify-between flex-row mb-10">
            <text>arr2ForEachEffectCount:</text>
            <text id="arr2">{{ arr2ForEachEffectCount }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>arr3(reverse):</text>
            <text id="arr3">{{ JSON.stringify(arr3) }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>map2ForEachEffectCount:</text>
            <text id="map2">{{ map2ForEachEffectCount }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>map3ForOfEffectCount:</text>
            <text id="map3">{{ map3ForOfEffectCount }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>set2ForEachEffectCount:</text>
            <text id="set2">{{ set2ForEachEffectCount }}</text>
        </view>
        <view class="flex justify-between flex-row mb-10">
            <text>set3ForOfEffectCount:</text>
            <text id="set3">{{ set3ForOfEffectCount }}</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>
        <button class='mb-10' id="update-arr2-forEach-effect-btn" @click="updateArr2()">update arr2</button>
        <button class='mb-10' id="update-map2-forEach-effect-btn" @click="updateMap2()">update map2 for each</button>
        <button class='mb-10' id="update-map3-forOf-effect-btn" @click="updateMap3()">update map3 for of</button>
        <button class='mb-10' id="update-set2-forEach-effect-btn" @click="updateSet2()">update set2 for each</button>
        <button class='mb-10' id="update-set3-forOf-effect-btn" @click="updateSet3()">update set3 for of</button>
    </scroll-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])
        }
    }

    const arr2 = reactive<number[]>([1])
    const arr2ForEachEffectCount = ref(0)
    watchEffect(() => {
        arr2ForEachEffectCount.value++
        arr2.forEach((item) => {
            console.log('arr2', item)
        })
    })
    function updateArr2() {
        arr2.push(Date.now())
    }
    
    var arr3 = reactive([1, 2, 3, 4, 5]).reverse()

    const map2 = reactive(new Map<string, number>([["a", 1]]))
    const map2ForEachEffectCount = ref(0)
    watchEffect(() => {
        map2ForEachEffectCount.value++
        map2.forEach((item : number) => {
            console.log('map2', item)
        })
    })
    function updateMap2() {
        map2.set("c-" + Date.now(), Date.now())
    }
    
    const map3 = reactive(new Map<string, number>([["a", 1]]))
    const map3ForOfEffectCount = ref(0)
    watchEffect(() => {
        map3ForOfEffectCount.value++
        for(const item of map3){
            console.log("map3",item)
        }
    })
    function updateMap3() {
        map3.set("c-" + Date.now(), Date.now())
    }
    
    const set2 = reactive(new Set<number>([1]))
    const set2ForEachEffectCount = ref(0)
    watchEffect(() => {
        set2ForEachEffectCount.value++
        set2.forEach((item : number) => {
            console.log('set2', item)
        })
    })
    function updateSet2() {
        set2.add(Date.now())
    }
    
    const set3 = reactive(new Set<number>([1]))
    const set3ForOfEffectCount = ref(0)
    watchEffect(() => {
        set3ForOfEffectCount.value++
        for(const item of set3){
            console.log("set3",item)
        }
    })
    function updateSet3() {
        set3.add(Date.now())
    }
</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 HarmonyOS
isRef() 4.0 4.41 4.0 4.11 -
unref() 4.0 4.41 4.0 4.11 -
toRef() 4.11 4.41 4.0 4.11 -
toValue() 4.11 4.41 4.0 4.11 -
toRefs() 4.11 4.41 4.0 4.11 -
isProxy() 4.0 4.41 4.0 4.11 -
isReactive() 4.0 4.41 4.0 4.11 -
isReadonly() 4.0 4.41 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 HarmonyOS
shallowRef() 4.0 4.41 4.0 4.11 -
triggerRef() x 4.41 4.0 4.11 -
customRef() 4.0 4.41 4.0 4.11 -
shallowReactive() 4.0 4.41 4.0 4.11 -
shallowReadonly() 4.0 4.41 4.0 4.11 -
toRaw() 4.0 4.41 4.0 4.11 -
markRaw() - - - - -
effectScope() 4.0 4.41 4.0 4.11 -
getCurrentScope() 4.0 4.41 4.0 4.11 -
onScopeDispose() 4.0 4.41 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 HarmonyOS 描述
onMounted() 4.0 4.41 4.0 4.11 - el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子。
如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el 也在文档内。
onUpdated() 4.0 4.41 4.0 4.11 - 由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。
onUnmounted() 4.0 4.41 4.0 4.11 - 在一个组件实例被卸载之后调用。
onBeforeMount() 4.0 4.41 4.0 4.11 - 在挂载开始之前被调用:相关的 render 函数首次被调用。
onBeforeUpdate() 4.0 4.41 4.0 4.11 - 数据更新时调用,发生在虚拟 DOM 打补丁之前。
这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。
onBeforeUnmount() 4.0 4.41 4.0 4.11 - 在一个组件实例被卸载之前调用。
onErrorCaptured() x - x x - 注册一个钩子,在捕获了后代组件传递的错误时调用。
onRenderTracked() x - x x - 注册一个调试钩子,当组件渲染过程中追踪到响应式依赖时调用。
onRenderTriggered() x - x x - 注册一个调试钩子,当响应式依赖的变更触发了组件渲染时调用。
onActivated() 4.0 x x x - keep-alive 组件激活时调用。
onDeactivated() 4.0 x x x - keep-alive 组件停用时调用。
onServerPrefetch() x x x x - 注册一个异步函数,在组件实例在服务器上被渲染之前调用。
如果这个钩子返回了一个 Promise,服务端渲染会在渲染该组件前等待该 Promise 完成。
这个钩子仅会在服务端渲染中执行,可以用于执行一些仅存在于服务端的数据抓取过程。

# 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 HarmonyOS
4.0 - 3.9 4.11 4.61
# 参见

# 示例

示例 详情

<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 HarmonyOS
defineProps() 4.0 4.41 4.0 4.11 -
defineEmits() 4.0 4.41 4.0 4.11 -
defineModel() 4.11 4.41 4.0 4.11 -
defineExpose() 4.0 4.41 4.0 4.11 -
defineOptions() 4.11 4.41 4.0 4.11 -
defineSlots() 4.0 4.41 4.0 4.11 -
useSlots() 4.0 4.41 4.0 4.11 -
useAttrs() 4.0 4.41 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>
      <!-- #ifndef MP -->
      <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>
      <!-- #endif -->
      <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')
const list = ref(['a','b'])
// 故意外部声明为UTSJSONObject
const msgProps = { class: 'uni-common-mt msg', style: { color: 'blue' } }
const render = ():VNode => {
  const textList: VNode[] = []
  list.value.forEach((item) => {
    textList.push(h('text', { class: 'text-item' }, item))
  })
  return 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('view', null, textList),
    h(
      'button',
      {
        class: 'uni-common-mt btn',
        type: 'primary',
        onClick: () => {
          msg.value = 'new msg'
          list.value.push('c')
        }
      },
      'click'
    )
  ])
}
</script>

<template><render /></template>

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