提交 2b860713 编写于 作者: DCloud-WZF's avatar DCloud-WZF 💬

refactor(reactivity): watch

上级 389ac221
......@@ -492,9 +492,15 @@
}
},
{
"path": "pages/reactivity/core/watch/watch",
"path": "pages/reactivity/core/watch/watch-options",
"style": {
"navigationBarTitleText": "watch"
"navigationBarTitleText": "watch 选项式 API"
}
},
{
"path": "pages/reactivity/core/watch/watch-composition",
"style": {
"navigationBarTitleText": "watch 组合式 API"
}
},
{
......
......@@ -491,6 +491,22 @@ export default {
name: 'readonly',
url: 'readonly/readonly'
},
{
id: 'watch',
name: 'watch',
children: [
{
id: 'watch-options',
name: 'watch 选项式 API',
url: 'watch-options'
},
{
id: 'watch-composition',
name: 'watch 组合式 API',
url: 'watch-composition'
},
]
},
]
}
] as Page[]
......
const PAGE_PATH = '/pages/composition-api/reactivity/watch/watch'
const COMPOSITION_PAGE_PATH = '/pages/reactivity/core/watch/watch-composition'
describe('watch', () => {
let page = null
beforeAll(async () => {
page = await program.reLaunch(PAGE_PATH)
await page.waitFor('view')
page = await program.reLaunch(COMPOSITION_PAGE_PATH)
await page.waitFor(1000)
})
it('count', async () => {
const count = await page.$('#count')
expect(await count.text()).toBe('count: 0')
expect(await count.text()).toBe('0')
// watch
const watchCountRes = await page.$('#watch-count-res')
expect((await watchCountRes.text()).trim()).toBe('watch count result:')
expect(((await watchCountRes.text()) || '').trim()).toBe('')
// track
const watchCountTrackNum = await page.$('#watch-count-track-num')
expect(await watchCountTrackNum.text()).toBe('watch count track number: 2')
expect(await watchCountTrackNum.text()).toBe('2')
const watchCountCleanupRes = await page.$('#watch-count-cleanup-res')
expect((await watchCountCleanupRes.text()).trim()).toBe('watch count cleanup result:')
expect(((await watchCountCleanupRes.text()) || '').trim()).toBe('')
// watch count and obj.num
const watchCountAndObjNumRes = await page.$('#watch-count-obj-num-res')
expect((await watchCountAndObjNumRes.text()).trim()).toBe('watch count and obj.num result:')
expect(((await watchCountAndObjNumRes.text()) || '').trim()).toBe('')
const incrementBtn = await page.$('.increment-btn')
await incrementBtn.tap()
expect(await count.text()).toBe('count: 1')
expect(await count.text()).toBe('1')
expect(await watchCountRes.text()).toBe(
'watch count result: count: 1, prevCount: 0, count ref text (flush sync): count: 0')
'count: 1, prevCount: 0, count ref text (flush sync): 0')
if (process.env.uniTestPlatformInfo.startsWith('android')) {
expect(await watchCountTrackNum.text()).toBe('watch count track number: 2')
expect(await watchCountTrackNum.text()).toBe('2')
} else {
expect(await watchCountTrackNum.text()).toBe('watch count track number: 4')
expect(await watchCountTrackNum.text()).toBe('4')
}
expect((await watchCountCleanupRes.text()).trim()).toBe('watch count cleanup result:')
expect(((await watchCountCleanupRes.text()) || '').trim()).toBe('')
expect(await watchCountAndObjNumRes.text()).toBe(
'watch count and obj.num result: state: [1,0], preState: [0,0]')
expect(await watchCountAndObjNumRes.text()).toBe('state: [1,0], preState: [0,0]')
await incrementBtn.tap()
expect(await count.text()).toBe('count: 2')
expect(await watchCountRes.text()).toBe(
'watch count result: count: 2, prevCount: 1, count ref text (flush sync): count: 1')
expect(await count.text()).toBe('2')
expect(await watchCountRes.text()).toBe('count: 2, prevCount: 1, count ref text (flush sync): 1')
if (process.env.uniTestPlatformInfo.startsWith('android')) {
expect(await watchCountTrackNum.text()).toBe('watch count track number: 2')
expect(await watchCountTrackNum.text()).toBe('2')
} else {
expect(await watchCountTrackNum.text()).toBe('watch count track number: 6')
expect(await watchCountTrackNum.text()).toBe('6')
}
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup result: watch count cleanup: 1')
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup: 1')
expect(await watchCountAndObjNumRes.text()).toBe(
'watch count and obj.num result: state: [2,0], preState: [1,0]')
expect(await watchCountAndObjNumRes.text()).toBe('state: [2,0], preState: [1,0]')
// stop watch
const stopWatchCountBtn = await page.$('.stop-watch-count-btn')
await stopWatchCountBtn.tap()
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup result: watch count cleanup: 2')
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup: 2')
await incrementBtn.tap()
expect(await count.text()).toBe('count: 3')
expect(await watchCountRes.text()).toBe(
'watch count result: count: 2, prevCount: 1, count ref text (flush sync): count: 1')
expect(await count.text()).toBe('3')
expect(await watchCountRes.text()).toBe('count: 2, prevCount: 1, count ref text (flush sync): 1')
if (process.env.uniTestPlatformInfo.startsWith('android')) {
expect(await watchCountTrackNum.text()).toBe('watch count track number: 2')
expect(await watchCountTrackNum.text()).toBe('2')
} else {
expect(await watchCountTrackNum.text()).toBe('watch count track number: 6')
expect(await watchCountTrackNum.text()).toBe('6')
}
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup result: watch count cleanup: 2')
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup: 2')
expect(await watchCountAndObjNumRes.text()).toBe(
'watch count and obj.num result: state: [3,0], preState: [2,0]')
expect(await watchCountAndObjNumRes.text()).toBe('state: [3,0], preState: [2,0]')
})
it('obj', async () => {
const objStr = await page.$('#obj-str')
expect(await objStr.text()).toBe('obj.str: num: 0')
expect(await objStr.text()).toBe('num: 0')
const objNum = await page.$('#obj-num')
expect(await objNum.text()).toBe('obj.num: 0')
expect(await objNum.text()).toBe('0')
const objBool = await page.$('#obj-bool')
expect(await objBool.text()).toBe('obj.bool: false')
expect(await objBool.text()).toBe('false')
const objArr = await page.$('#obj-arr')
expect(await objArr.text()).toBe('obj.arr: [0]')
expect(await objArr.text()).toBe('[0]')
const watchObjRes = await page.$('#watch-obj-res')
if (process.env.uniTestPlatformInfo.startsWith('android')) {
expect(await watchObjRes.text()).toBe(
'watch obj result: obj: {"arr":[0],"bool":false,"num":0,"str":"num: 0"}, prevObj: {"arr":[0],"bool":false,"num":0,"str":"num: 0"}'
'obj: {"arr":[0],"bool":false,"num":0,"str":"num: 0"}, prevObj: {"arr":[0],"bool":false,"num":0,"str":"num: 0"}'
)
}
if (process.env.uniTestPlatformInfo.startsWith('web')) {
expect(await watchObjRes.text()).toBe(
'watch obj result: obj: {"num":0,"str":"num: 0","bool":false,"arr":[0]}, prevObj: null'
'obj: {"num":0,"str":"num: 0","bool":false,"arr":[0]}, prevObj: null'
)
}
const watchObjStrRes = await page.$('#watch-obj-str-res')
expect((await watchObjStrRes.text()).trim()).toBe('watch obj.str result:')
expect(((await watchObjStrRes.text()) || '').trim()).toBe('')
// trigger
const watchObjStrTriggerNum = await page.$('#watch-obj-str-trigger-num')
expect(await watchObjStrTriggerNum.text()).toBe('watch obj.str trigger number: 0')
expect(await watchObjStrTriggerNum.text()).toBe('0')
const watchObjBoolRes = await page.$('#watch-obj-bool-res')
expect((await watchObjBoolRes.text()).trim()).toBe('watch obj.bool result:')
expect(((await watchObjBoolRes.text()) || '').trim()).toBe('')
const watchObjArrRes = await page.$('#watch-obj-arr-res')
expect((await watchObjArrRes.text()).trim()).toBe('watch obj.arr result:')
expect(((await watchObjArrRes.text()) || '').trim()).toBe('')
const updateObjBtn = await page.$('.update-obj-btn')
await updateObjBtn.tap()
expect(await objStr.text()).toBe('obj.str: num: 1')
expect(await objNum.text()).toBe('obj.num: 1')
expect(await objBool.text()).toBe('obj.bool: true')
expect(await objArr.text()).toBe('obj.arr: [0,1]')
expect(await objStr.text()).toBe('num: 1')
expect(await objNum.text()).toBe('1')
expect(await objBool.text()).toBe('true')
expect(await objArr.text()).toBe('[0,1]')
if (process.env.uniTestPlatformInfo.startsWith('android')) {
expect(await watchObjRes.text()).toBe(
'watch obj result: obj: {"arr":[0,1],"bool":true,"num":1,"str":"num: 1"}, prevObj: {"arr":[0,1],"bool":true,"num":1,"str":"num: 1"}'
'obj: {"arr":[0,1],"bool":true,"num":1,"str":"num: 1"}, prevObj: {"arr":[0,1],"bool":true,"num":1,"str":"num: 1"}'
)
}
if (process.env.uniTestPlatformInfo.startsWith('web')) {
expect(await watchObjRes.text()).toBe(
'watch obj result: obj: {"num":1,"str":"num: 1","bool":true,"arr":[0,1]}, prevObj: {"num":1,"str":"num: 1","bool":true,"arr":[0,1]}'
'obj: {"num":1,"str":"num: 1","bool":true,"arr":[0,1]}, prevObj: {"num":1,"str":"num: 1","bool":true,"arr":[0,1]}'
)
}
expect(await watchObjStrRes.text()).toBe(
'watch obj.str result: str: num: 1, prevStr: num: 0, obj.str ref text (flush pre): obj.str: num: 0')
'str: num: 1, prevStr: num: 0, obj.str ref text (flush pre): num: 0')
expect(await watchObjStrTriggerNum.text()).toBe('watch obj.str trigger number: 1')
expect(await watchObjStrTriggerNum.text()).toBe('1')
expect(await watchObjBoolRes.text()).toBe(
'watch obj.bool result: bool: true, prevBool: false, obj.bool ref text (flush post): obj.bool: true'
'bool: true, prevBool: false, obj.bool ref text (flush post): true'
)
expect(await watchObjArrRes.text()).toBe('watch obj.arr result: arr: [0,1], prevArr: [0,1]')
expect(await watchObjArrRes.text()).toBe('arr: [0,1], prevArr: [0,1]')
const watchCountAndObjNumRes = await page.$('#watch-count-obj-num-res')
expect(await watchCountAndObjNumRes.text()).toBe(
'watch count and obj.num result: state: [3,1], preState: [3,0]')
if (watchCountAndObjNumRes) {
expect(await watchCountAndObjNumRes.text()).toBe('state: [3,1], preState: [3,0]')
}
})
})
\ No newline at end of file
<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',
// 响应属性或引用作为依赖项被跟踪时调用
// TODO: vue 3.4 开始, 数据更新,也会触发 onTrack, 待升级 vue 版本时,需要确认该变化是否合理及跟进
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>
// TODO: 确认各端差异
const OPTIONS_PAGE_PATH = '/pages/reactivity/core/watch/watch-options'
describe('watch', () => {
let page = null
const platformInfo = process.env.uniTestPlatformInfo.toLowerCase()
const isAndroid = platformInfo.startsWith('android')
const isIos = platformInfo.startsWith('ios')
const isApp = isAndroid || isIos
const isWeb = platformInfo.startsWith('web')
beforeAll(async () => {
page = await program.reLaunch(OPTIONS_PAGE_PATH)
await page.waitFor(1000)
})
it('count', async () => {
const count = await page.$('#count')
expect(await count.text()).toBe('0')
// watch
const watchCountRes = await page.$('#watch-count-res')
expect(((await watchCountRes.text()) || '').trim()).toBe('')
// track
const watchCountTrackNum = await page.$('#watch-count-track-num')
expect(await watchCountTrackNum.text()).toBe('1')
const watchCountCleanupRes = await page.$('#watch-count-cleanup-res')
expect(((await watchCountCleanupRes.text()) || '').trim()).toBe('')
const incrementBtn = await page.$('.increment-btn')
await incrementBtn.tap()
expect(await count.text()).toBe('1')
expect(await watchCountRes.text()).toBe(
'count: 1, prevCount: 0, count ref text (flush sync): 0')
if (isAndroid) {
expect(await watchCountTrackNum.text()).toBe('1')
}
if (isIos || isWeb) {
expect(await watchCountTrackNum.text()).toBe('2')
}
expect(((await watchCountCleanupRes.text()) || '').trim()).toBe('')
await incrementBtn.tap()
expect(await count.text()).toBe('2')
expect(await watchCountRes.text()).toBe('count: 2, prevCount: 1, count ref text (flush sync): 1')
if (isAndroid) {
expect(await watchCountTrackNum.text()).toBe('1')
}
if (isIos || isWeb) {
expect(await watchCountTrackNum.text()).toBe('3')
}
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup: 1')
// stop watch
const stopWatchCountBtn = await page.$('.stop-watch-count-btn')
await stopWatchCountBtn.tap()
if (isApp) {
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup: 1')
}
if (isWeb) {
expect(await watchCountTrackNum.text()).toBe('3')
}
await incrementBtn.tap()
expect(await count.text()).toBe('3')
if (isApp) {
expect(await watchCountRes.text()).toBe('count: 3, prevCount: 2, count ref text (flush sync): 2')
}
if (isWeb) {
expect(await watchCountRes.text()).toBe('count: 2, prevCount: 1, count ref text (flush sync): 1')
}
if (isAndroid) {
expect(await watchCountTrackNum.text()).toBe('1')
}
if (isIos) {
expect(await watchCountTrackNum.text()).toBe('4')
}
if (isWeb) {
expect(await watchCountTrackNum.text()).toBe('3')
}
expect(await watchCountCleanupRes.text()).toBe('watch count cleanup: 2')
})
it('obj', async () => {
const objStr = await page.$('#obj-str')
expect(await objStr.text()).toBe('num: 0')
const objNum = await page.$('#obj-num')
expect(await objNum.text()).toBe('0')
const objBool = await page.$('#obj-bool')
expect(await objBool.text()).toBe('false')
const objArr = await page.$('#obj-arr')
expect(await objArr.text()).toBe('[0]')
const watchObjRes = await page.$('#watch-obj-res')
if (isAndroid) {
expect(await watchObjRes.text()).toBe(
'obj: {"arr":[0],"bool":false,"num":0,"str":"num: 0"}, prevObj: {"arr":[0],"bool":false,"num":0,"str":"num: 0"}'
)
}
if (isIos || isWeb) {
expect(await watchObjRes.text()).toBe(
'obj: {"num":0,"str":"num: 0","bool":false,"arr":[0]}, prevObj: null'
)
}
const watchObjStrRes = await page.$('#watch-obj-str-res')
expect(((await watchObjStrRes.text()) || '').trim()).toBe('')
// trigger
const watchObjStrTriggerNum = await page.$('#watch-obj-str-trigger-num')
expect(await watchObjStrTriggerNum.text()).toBe('0')
const watchObjBoolRes = await page.$('#watch-obj-bool-res')
expect(((await watchObjBoolRes.text()) || '').trim()).toBe('')
const watchObjArrRes = await page.$('#watch-obj-arr-res')
expect(((await watchObjArrRes.text()) || '').trim()).toBe('')
const updateObjBtn = await page.$('.update-obj-btn')
await updateObjBtn.tap()
expect(await objStr.text()).toBe('num: 1')
expect(await objNum.text()).toBe('1')
expect(await objBool.text()).toBe('true')
expect(await objArr.text()).toBe('[0,1]')
if (isAndroid) {
expect(await watchObjRes.text()).toBe(
'obj: {"arr":[0,1],"bool":true,"num":1,"str":"num: 1"}, prevObj: {"arr":[0,1],"bool":true,"num":1,"str":"num: 1"}'
)
}
if (isIos || isWeb) {
expect(await watchObjRes.text()).toBe(
'obj: {"num":1,"str":"num: 1","bool":true,"arr":[0,1]}, prevObj: {"num":1,"str":"num: 1","bool":true,"arr":[0,1]}'
)
}
expect(await watchObjStrRes.text()).toBe(
'str: num: 1, prevStr: num: 0, obj.str ref text (flush pre): num: 0')
expect(await watchObjStrTriggerNum.text()).toBe('0')
expect(await watchObjBoolRes.text()).toBe(
'bool: true, prevBool: false, obj.bool ref text (flush post): true'
)
expect(await watchObjArrRes.text()).toBe('arr: [0,1], prevArr: [0,1]')
})
})
\ No newline at end of file
<template>
<!-- #ifdef APP -->
<scroll-view style="flex: 1; padding-bottom: 20px">
<!-- #endif -->
<view class="page">
<view class="flex justify-between flex-row mb-10">
<text>count:</text>
<text id="count" ref="countRef">{{ count }}</text>
</view>
<view class="flex justify-between mb-10">
<text>watch count result:</text>
<text id="watch-count-res">{{ watchCountRes }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>watch count track number:</text>
<text id="watch-count-track-num">{{ watchCountTrackNum }}</text>
</view>
<view class="flex justify-between mb-10">
<text>watch count cleanup number:</text>
<text id="watch-count-cleanup-res">{{ watchCountCleanupRes }}</text>
</view>
<button class="increment-btn mb-10" @click="increment">increment</button>
<button class="stop-watch-count-btn mb-10" @click="triggerStopWatchCount">
stop watch count
</button>
<view class="flex justify-between flex-row mb-10">
<text>obj.str:</text>
<text id="obj-str" ref="objStrRef">{{ obj.str }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>watch obj.str trigger number:</text>
<text id="watch-obj-str-trigger-num">{{ watchObjStrTriggerNum }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.num:</text>
<text id="obj-num">{{ obj.num }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.bool:</text>
<text id="obj-bool" ref="objBoolRef">{{ obj.bool }}</text>
</view>
<view class="flex justify-between flex-row mb-10">
<text>obj.arr:</text>
<text id="obj-arr" ref="objArrRef">{{ JSON.stringify(obj.arr) }}</text>
</view>
<view class="flex justify-between mb-10">
<text>watch obj result:</text>
<text id="watch-obj-res">{{ watchObjRes }}</text>
</view>
<view class="flex justify-between mb-10">
<text>watch obj.str result:</text>
<text id="watch-obj-str-res">{{ watchObjStrRes }}</text>
</view>
<view class="flex justify-between mb-10">
<text>watch obj.bool result:</text>
<text id="watch-obj-bool-res">{{ watchObjBoolRes }}</text>
</view>
<view class="flex justify-between mb-10">
<text>watch obj.arr result:</text>
<text id="watch-obj-arr-res">{{ watchObjArrRes }}</text>
</view>
<button class="update-obj-btn mb-10" @click="updateObj">
update obj
</button>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script lang="uts">
type Obj = {
num : number,
str : string,
bool : boolean,
arr : number[]
}
export default {
data() {
return {
countRef: null as UniTextElement | null,
count: 0,
watchCountRes: '',
watchCountCleanupRes: '',
watchCountTrackNum: 0,
stopWatchCount: () => { },
obj: {
num: 0,
str: 'num: 0',
bool: false,
arr: [0]
} as Obj,
watchObjRes: '',
objStrRef: null as UniTextElement | null,
watchObjStrRes: '',
watchObjStrTriggerNum: 0,
objBoolRef: null as UniTextElement | null,
watchObjBoolRes: '',
watchObjArrRes: '',
}
},
onReady() {
// TODO: app-android this.$watch 返回类型不对
// watchCountTrackNum 各端表现也不一致
const self = this
// #ifdef APP
this.$watch('count',
function (count : number, prevCount : number, onCleanup : OnCleanup) {
this.watchCountRes = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${(this.$refs['countRef'] as UniTextElement).value}`
const cancel = () => {
this.watchCountCleanupRes = `watch count cleanup: ${count}`
}
onCleanup(cancel)
},
{
// 侦听器在响应式依赖改变时立即触发
flush: 'sync',
// 响应属性或引用作为依赖项被跟踪时调用
// TODO: vue 3.4 开始, 数据更新,也会触发 onTrack, 待升级 vue 版本时,需要确认该变化是否合理及跟进
onTrack(event : DebuggerEvent) {
if (event.type === 'get') {
self.watchCountTrackNum++
}
}
// TODO: vue>3.4.15 开始 监听函数、onTrack、onTrigger 同时存在修改响应式数据时,会报错 Maximum call stack size exceeded
// 所以将 onTrack 与 onTrigger 调整到两个 watch 里
})
// #endif
// #ifdef WEB
this.stopWatchCount = this.$watch(
'count',
function (count : number, prevCount : number, onCleanup : OnCleanup) {
this.watchCountRes = `count: ${count}, prevCount: ${prevCount}, count ref text (flush sync): ${this.$refs['countRef'].childNodes[0].innerText}`
const cancel = () => {
this.watchCountCleanupRes = `watch count cleanup: ${count}`
}
onCleanup(cancel)
},
{
// 侦听器在响应式依赖改变时立即触发
flush: 'sync',
// 响应属性或引用作为依赖项被跟踪时调用
// TODO: vue 3.4 开始, 数据更新,也会触发 onTrack, 待升级 vue 版本时,需要确认该变化是否合理及跟进
onTrack(event : DebuggerEvent) {
if (event.type === 'get') {
self.watchCountTrackNum++
}
}
// TODO: vue>3.4.15 开始 监听函数、onTrack、onTrigger 同时存在修改响应式数据时,会报错 Maximum call stack size exceeded
// 所以将 onTrack 与 onTrigger 调整到两个 watch 里
})
// #endif
},
watch: {
obj: {
handler(obj : Obj, prevObj ?: Obj) {
this.watchObjRes = `obj: ${JSON.stringify(obj)}, prevObj: ${JSON.stringify(prevObj)}`
},
// immediate: true 第一次触发, 旧值应该是 undefined, 现在 app 是初始值
immediate: true,
deep: true
},
'obj.str': function (str : string, prevStr : string) {
// #ifdef APP
this.watchObjStrRes = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${(this.$refs['objStrRef'] as UniTextElement).value}`
// #endif
// #ifdef WEB
this.watchObjStrRes = `str: ${str}, prevStr: ${prevStr}, obj.str ref text (flush pre): ${this.$refs.objStrRef.childNodes[0].innerText}`
// #endif
},
'obj.bool': {
handler: function (bool : boolean, prevBool : boolean) {
// #ifdef APP
this.watchObjBoolRes = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${(this.$refs['objBoolRef'] as UniTextElement).value}`
// #endif
// #ifdef WEB
this.watchObjBoolRes = `bool: ${bool}, prevBool: ${prevBool}, obj.bool ref text (flush post): ${this.$refs.objBoolRef.childNodes[0].innerText}`
// #endif
},
// 侦听器延迟到组件渲染之后触发
flush: 'post',
deep: true
},
'obj.arr': {
handler: function (arr : number[], prevArr : number[]) {
this.watchObjArrRes = `arr: ${JSON.stringify(arr)}, prevArr: ${JSON.stringify(prevArr)}`
},
deep: true
}
},
methods: {
triggerStopWatchCount() {
// #ifdef WEB
this.stopWatchCount()
// #endif
},
increment() {
this.count++
},
updateObj() {
this.obj.num++
this.obj.str = `num: ${this.obj.num}`
this.obj.bool = !this.obj.bool
this.obj.arr.push(this.obj.num)
}
}
}
</script>
\ No newline at end of file
<template>
<!-- #ifdef APP -->
<scroll-view style="flex: 1; padding-bottom: 20px">
<!-- #endif -->
<view class="page">
<text id="count" ref="countRef" class="mb-10">count: {{ count }}</text>
<text id="watch-count-res" class="mb-10">watch count result: {{ watchCountRes }}</text>
<text id="watch-count-track-num" class="mb-10">watch count track number: {{ watchCountTrackNum }}</text>
<text id="watch-count-cleanup-res" class="mb-10">watch count cleanup result:
{{ watchCountCleanupRes }}</text>
<button class="increment-btn mb-10" @click="increment">
increment
</button>
<button class="stop-watch-count-btn mb-10" @click="triggerStopWatchCount">
stop watch count
</button>
<text id="obj-str" ref="objStrRef" class="mb-10">obj.str: {{ obj.str }}</text>
<text id="watch-obj-str-trigger-num" class="mb-10">watch obj.str trigger number:
{{ watchObjStrTriggerNum }}</text>
<text id="obj-num" class="mb-10">obj.num: {{ obj.num }}</text>
<text id="obj-bool" ref="objBoolRef" class="mb-10">obj.bool: {{ obj.bool }}</text>
<text id="obj-arr" ref="objArrRef" class="mb-10">obj.arr: {{ JSON.stringify(obj.arr) }}</text>
<text id="watch-obj-res" class="mb-10">watch obj result: {{ watchObjRes }}</text>
<text id="watch-obj-str-res" class="mb-10">watch obj.str result: {{ watchObjStrRes }}</text>
<text id="watch-obj-bool-res" class="mb-10">watch obj.bool result: {{ watchObjBoolRes }}</text>
<text id="watch-obj-arr-res" class="mb-10">watch obj.arr result: {{ watchObjArrRes }}</text>
<button class="update-obj-btn mb-10" @click="updateObj">
update obj
</button>
<text id="watch-count-obj-num-res" class="mb-10">watch count and obj.num result:
{{ watchCountAndObjNumRes }}</text>
</view>
<!-- #ifdef APP -->
</scroll-view>
<!-- #endif -->
</template>
<script setup>
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',
// 响应属性或引用作为依赖项被跟踪时调用
// TODO: vue 3.4 开始, 数据更新,也会触发 onTrack, 待升级 vue 版本时,需要确认该变化是否合理及跟进
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>
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册