feat(runtime-core): add watchEffect API
BREAKING CHANGE: replae `watch(fn, options?)` with `watchEffect` The `watch(fn, options?)` signature has been replaced by the new `watchEffect` API, which has the same usage and behavior. `watch` now only supports the `watch(source, cb, options?)` signautre.
This commit is contained in:
parent
b36a76fe23
commit
99a2e18c97
@ -6,7 +6,7 @@ import {
|
|||||||
render,
|
render,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
nextTick,
|
nextTick,
|
||||||
watch,
|
watchEffect,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
triggerEvent,
|
triggerEvent,
|
||||||
TestElement
|
TestElement
|
||||||
@ -55,7 +55,7 @@ describe('api: setup context', () => {
|
|||||||
|
|
||||||
const Child = defineComponent({
|
const Child = defineComponent({
|
||||||
setup(props: { count: number }) {
|
setup(props: { count: number }) {
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
dummy = props.count
|
dummy = props.count
|
||||||
})
|
})
|
||||||
return () => h('div', props.count)
|
return () => h('div', props.count)
|
||||||
@ -88,7 +88,7 @@ describe('api: setup context', () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
dummy = props.count
|
dummy = props.count
|
||||||
})
|
})
|
||||||
return () => h('div', props.count)
|
return () => h('div', props.count)
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { watch, reactive, computed, nextTick, ref, h } from '../src/index'
|
import {
|
||||||
|
watch,
|
||||||
|
watchEffect,
|
||||||
|
reactive,
|
||||||
|
computed,
|
||||||
|
nextTick,
|
||||||
|
ref,
|
||||||
|
h
|
||||||
|
} from '../src/index'
|
||||||
import { render, nodeOps, serializeInner } from '@vue/runtime-test'
|
import { render, nodeOps, serializeInner } from '@vue/runtime-test'
|
||||||
import {
|
import {
|
||||||
ITERATE_KEY,
|
ITERATE_KEY,
|
||||||
@ -13,10 +21,10 @@ import { mockWarn } from '@vue/shared'
|
|||||||
describe('api: watch', () => {
|
describe('api: watch', () => {
|
||||||
mockWarn()
|
mockWarn()
|
||||||
|
|
||||||
it('watch(effect)', async () => {
|
it('effect', async () => {
|
||||||
const state = reactive({ count: 0 })
|
const state = reactive({ count: 0 })
|
||||||
let dummy
|
let dummy
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
dummy = state.count
|
dummy = state.count
|
||||||
})
|
})
|
||||||
expect(dummy).toBe(0)
|
expect(dummy).toBe(0)
|
||||||
@ -117,10 +125,10 @@ describe('api: watch', () => {
|
|||||||
expect(dummy).toMatchObject([[2, true], [1, false]])
|
expect(dummy).toMatchObject([[2, true], [1, false]])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('stopping the watcher', async () => {
|
it('stopping the watcher (effect)', async () => {
|
||||||
const state = reactive({ count: 0 })
|
const state = reactive({ count: 0 })
|
||||||
let dummy
|
let dummy
|
||||||
const stop = watch(() => {
|
const stop = watchEffect(() => {
|
||||||
dummy = state.count
|
dummy = state.count
|
||||||
})
|
})
|
||||||
expect(dummy).toBe(0)
|
expect(dummy).toBe(0)
|
||||||
@ -132,11 +140,32 @@ describe('api: watch', () => {
|
|||||||
expect(dummy).toBe(0)
|
expect(dummy).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('stopping the watcher (with source)', async () => {
|
||||||
|
const state = reactive({ count: 0 })
|
||||||
|
let dummy
|
||||||
|
const stop = watch(
|
||||||
|
() => state.count,
|
||||||
|
count => {
|
||||||
|
dummy = count
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
state.count++
|
||||||
|
await nextTick()
|
||||||
|
expect(dummy).toBe(1)
|
||||||
|
|
||||||
|
stop()
|
||||||
|
state.count++
|
||||||
|
await nextTick()
|
||||||
|
// should not update
|
||||||
|
expect(dummy).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
it('cleanup registration (effect)', async () => {
|
it('cleanup registration (effect)', async () => {
|
||||||
const state = reactive({ count: 0 })
|
const state = reactive({ count: 0 })
|
||||||
const cleanup = jest.fn()
|
const cleanup = jest.fn()
|
||||||
let dummy
|
let dummy
|
||||||
const stop = watch(onCleanup => {
|
const stop = watchEffect(onCleanup => {
|
||||||
onCleanup(cleanup)
|
onCleanup(cleanup)
|
||||||
dummy = state.count
|
dummy = state.count
|
||||||
})
|
})
|
||||||
@ -187,7 +216,7 @@ describe('api: watch', () => {
|
|||||||
|
|
||||||
const Comp = {
|
const Comp = {
|
||||||
setup() {
|
setup() {
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
assertion(count.value)
|
assertion(count.value)
|
||||||
})
|
})
|
||||||
return () => count.value
|
return () => count.value
|
||||||
@ -221,7 +250,7 @@ describe('api: watch', () => {
|
|||||||
|
|
||||||
const Comp = {
|
const Comp = {
|
||||||
setup() {
|
setup() {
|
||||||
watch(
|
watchEffect(
|
||||||
() => {
|
() => {
|
||||||
assertion(count.value, count2.value)
|
assertion(count.value, count2.value)
|
||||||
},
|
},
|
||||||
@ -263,7 +292,7 @@ describe('api: watch', () => {
|
|||||||
|
|
||||||
const Comp = {
|
const Comp = {
|
||||||
setup() {
|
setup() {
|
||||||
watch(
|
watchEffect(
|
||||||
() => {
|
() => {
|
||||||
assertion(count.value)
|
assertion(count.value)
|
||||||
},
|
},
|
||||||
@ -363,14 +392,14 @@ describe('api: watch', () => {
|
|||||||
expect(spy).toHaveBeenCalledTimes(3)
|
expect(spy).toHaveBeenCalledTimes(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('warn immediate option when using effect signature', async () => {
|
it('warn immediate option when using effect', async () => {
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
let dummy
|
let dummy
|
||||||
// @ts-ignore
|
watchEffect(
|
||||||
watch(
|
|
||||||
() => {
|
() => {
|
||||||
dummy = count.value
|
dummy = count.value
|
||||||
},
|
},
|
||||||
|
// @ts-ignore
|
||||||
{ immediate: false }
|
{ immediate: false }
|
||||||
)
|
)
|
||||||
expect(dummy).toBe(0)
|
expect(dummy).toBe(0)
|
||||||
@ -388,7 +417,7 @@ describe('api: watch', () => {
|
|||||||
events.push(e)
|
events.push(e)
|
||||||
})
|
})
|
||||||
const obj = reactive({ foo: 1, bar: 2 })
|
const obj = reactive({ foo: 1, bar: 2 })
|
||||||
watch(
|
watchEffect(
|
||||||
() => {
|
() => {
|
||||||
dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
|
dummy = [obj.foo, 'bar' in obj, Object.keys(obj)]
|
||||||
},
|
},
|
||||||
@ -423,7 +452,7 @@ describe('api: watch', () => {
|
|||||||
events.push(e)
|
events.push(e)
|
||||||
})
|
})
|
||||||
const obj = reactive({ foo: 1 })
|
const obj = reactive({ foo: 1 })
|
||||||
watch(
|
watchEffect(
|
||||||
() => {
|
() => {
|
||||||
dummy = obj.foo
|
dummy = obj.foo
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
watch,
|
watch,
|
||||||
|
watchEffect,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
onErrorCaptured
|
onErrorCaptured
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
@ -163,7 +164,7 @@ describe('Suspense', () => {
|
|||||||
// extra tick needed for Node 12+
|
// extra tick needed for Node 12+
|
||||||
deps.push(p.then(() => Promise.resolve()))
|
deps.push(p.then(() => Promise.resolve()))
|
||||||
|
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
calls.push('immediate effect')
|
calls.push('immediate effect')
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -265,7 +266,7 @@ describe('Suspense', () => {
|
|||||||
const p = new Promise(r => setTimeout(r, 1))
|
const p = new Promise(r => setTimeout(r, 1))
|
||||||
deps.push(p)
|
deps.push(p)
|
||||||
|
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
calls.push('immediate effect')
|
calls.push('immediate effect')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ import {
|
|||||||
watch,
|
watch,
|
||||||
ref,
|
ref,
|
||||||
nextTick,
|
nextTick,
|
||||||
defineComponent
|
defineComponent,
|
||||||
|
watchEffect
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { setErrorRecovery } from '../src/errorHandling'
|
import { setErrorRecovery } from '../src/errorHandling'
|
||||||
import { mockWarn } from '@vue/shared'
|
import { mockWarn } from '@vue/shared'
|
||||||
@ -241,7 +242,7 @@ describe('error handling', () => {
|
|||||||
expect(fn).toHaveBeenCalledWith(err, 'ref function')
|
expect(fn).toHaveBeenCalledWith(err, 'ref function')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('in watch (effect)', () => {
|
test('in effect', () => {
|
||||||
const err = new Error('foo')
|
const err = new Error('foo')
|
||||||
const fn = jest.fn()
|
const fn = jest.fn()
|
||||||
|
|
||||||
@ -257,7 +258,7 @@ describe('error handling', () => {
|
|||||||
|
|
||||||
const Child = {
|
const Child = {
|
||||||
setup() {
|
setup() {
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
return () => null
|
return () => null
|
||||||
@ -268,7 +269,7 @@ describe('error handling', () => {
|
|||||||
expect(fn).toHaveBeenCalledWith(err, 'watcher callback')
|
expect(fn).toHaveBeenCalledWith(err, 'watcher callback')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('in watch (getter)', () => {
|
test('in watch getter', () => {
|
||||||
const err = new Error('foo')
|
const err = new Error('foo')
|
||||||
const fn = jest.fn()
|
const fn = jest.fn()
|
||||||
|
|
||||||
@ -298,7 +299,7 @@ describe('error handling', () => {
|
|||||||
expect(fn).toHaveBeenCalledWith(err, 'watcher getter')
|
expect(fn).toHaveBeenCalledWith(err, 'watcher getter')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('in watch (callback)', async () => {
|
test('in watch callback', async () => {
|
||||||
const err = new Error('foo')
|
const err = new Error('foo')
|
||||||
const fn = jest.fn()
|
const fn = jest.fn()
|
||||||
|
|
||||||
@ -332,7 +333,7 @@ describe('error handling', () => {
|
|||||||
expect(fn).toHaveBeenCalledWith(err, 'watcher callback')
|
expect(fn).toHaveBeenCalledWith(err, 'watcher callback')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('in watch cleanup', async () => {
|
test('in effect cleanup', async () => {
|
||||||
const err = new Error('foo')
|
const err = new Error('foo')
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
const fn = jest.fn()
|
const fn = jest.fn()
|
||||||
@ -349,7 +350,7 @@ describe('error handling', () => {
|
|||||||
|
|
||||||
const Child = {
|
const Child = {
|
||||||
setup() {
|
setup() {
|
||||||
watch(onCleanup => {
|
watchEffect(onCleanup => {
|
||||||
count.value
|
count.value
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
throw err
|
throw err
|
||||||
|
@ -71,6 +71,14 @@ export type StopHandle = () => void
|
|||||||
|
|
||||||
const invoke = (fn: Function) => fn()
|
const invoke = (fn: Function) => fn()
|
||||||
|
|
||||||
|
// Simple effect.
|
||||||
|
export function watchEffect(
|
||||||
|
effect: WatchEffect,
|
||||||
|
options?: BaseWatchOptions
|
||||||
|
): StopHandle {
|
||||||
|
return doWatch(effect, null, options)
|
||||||
|
}
|
||||||
|
|
||||||
// initial value for watchers to trigger on undefined initial values
|
// initial value for watchers to trigger on undefined initial values
|
||||||
const INITIAL_WATCHER_VALUE = {}
|
const INITIAL_WATCHER_VALUE = {}
|
||||||
|
|
||||||
@ -110,6 +118,13 @@ export function watch<T = any>(
|
|||||||
// watch(source, cb)
|
// watch(source, cb)
|
||||||
return doWatch(effectOrSource, cbOrOptions, options)
|
return doWatch(effectOrSource, cbOrOptions, options)
|
||||||
} else {
|
} else {
|
||||||
|
// TODO remove this in the next release
|
||||||
|
__DEV__ &&
|
||||||
|
warn(
|
||||||
|
`\`watch(fn, options?)\` signature has been moved to a separate API. ` +
|
||||||
|
`Use \`watchEffect(fn, options?)\` instead. \`watch\` will only ` +
|
||||||
|
`support \`watch(source, cb, options?) signature in the next release.`
|
||||||
|
)
|
||||||
// watch(effect)
|
// watch(effect)
|
||||||
return doWatch(effectOrSource, null, cbOrOptions)
|
return doWatch(effectOrSource, null, cbOrOptions)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ export {
|
|||||||
markNonReactive
|
markNonReactive
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
export { computed } from './apiComputed'
|
export { computed } from './apiComputed'
|
||||||
export { watch } from './apiWatch'
|
export { watch, watchEffect } from './apiWatch'
|
||||||
export {
|
export {
|
||||||
onBeforeMount,
|
onBeforeMount,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const { createApp, ref, watch } = Vue
|
const { createApp, ref, watchEffect } = Vue
|
||||||
const API_URL = `https://api.github.com/repos/vuejs/vue-next/commits?per_page=3&sha=`
|
const API_URL = `https://api.github.com/repos/vuejs/vue-next/commits?per_page=3&sha=`
|
||||||
|
|
||||||
const truncate = v => {
|
const truncate = v => {
|
||||||
@ -37,7 +37,7 @@ createApp({
|
|||||||
const currentBranch = ref('master')
|
const currentBranch = ref('master')
|
||||||
const commits = ref(null)
|
const commits = ref(null)
|
||||||
|
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
fetch(`${API_URL}${currentBranch.value}`)
|
fetch(`${API_URL}${currentBranch.value}`)
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
@ -53,7 +53,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const { createApp, reactive, computed, watch, onMounted, onUnmounted } = Vue
|
const { createApp, reactive, computed, watchEffect, onMounted, onUnmounted } = Vue
|
||||||
|
|
||||||
const STORAGE_KEY = 'todos-vuejs-3.x'
|
const STORAGE_KEY = 'todos-vuejs-3.x'
|
||||||
const todoStorage = {
|
const todoStorage = {
|
||||||
@ -119,7 +119,7 @@ createApp({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => {
|
watchEffect(() => {
|
||||||
todoStorage.save(state.todos)
|
todoStorage.save(state.todos)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user