-
-
Save Jinjiang/f9b6f968af980cfd21cfc713e59db91b to your computer and use it in GitHub Desktop.
All code samples in "Understanding Reactivity in Vue 3.0"
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ npx create-vite-app hello-world | |
$ cd hello-world | |
$ npm install | |
$ npm run dev |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
new Vue({ | |
el: '#app', | |
template: '<div @click="x++">{{x}} + {{y}} = {{z}}</div>', | |
data() { | |
return { x: 1, y: 2 } | |
}, | |
computed: { | |
z() { return this.x + this.y } | |
}, | |
watch: { | |
x(newValue, oldValue) { | |
console.log(`x is changed from ${oldValue} to ${newValue}`) | |
} | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// data | |
const data = { x: 1, y: 2 } | |
// real data and deps behind | |
let realX = data.x | |
let realY = data.y | |
const realDepsX = [] | |
const realDepsY = [] | |
// make it reactive | |
Object.defineProperty(data, 'x', { | |
get() { | |
trackX() | |
return realX | |
}, | |
set(v) { | |
realX = v | |
triggerX() | |
} | |
}) | |
Object.defineProperty(data, 'y', { | |
get() { | |
trackY() | |
return realY | |
}, | |
set(v) { | |
realY = v | |
triggerY() | |
} | |
}) | |
// track and trigger a property | |
const trackX = () => { | |
if (isDryRun && currentDep) { | |
realDepsX.push(currentDep) | |
} | |
} | |
const trackY = () => { | |
if (isDryRun && currentDep) { | |
realDepsY.push(currentDep) | |
} | |
} | |
const triggerX = () => { | |
realDepsX.forEach(dep => dep()) | |
} | |
const triggerY = () => { | |
realDepsY.forEach(dep => dep()) | |
} | |
// observe a function | |
let isDryRun = false | |
let currentDep = null | |
const observe = fn => { | |
isDryRun = true | |
currentDep = fn | |
fn() | |
currentDep = null | |
isDryRun = false | |
} | |
// define 3 functions | |
const depA = () => console.log(`x = ${data.x}`) | |
const depB = () => console.log(`y = ${data.y}`) | |
const depC = () => console.log(`x + y = ${data.x + data.y}`) | |
// dry-run all dependents | |
observe(depA) | |
observe(depB) | |
observe(depC) | |
// output: x = 1, y = 2, x + y = 3 | |
// mutate data | |
data.x = 3 | |
// output: x = 3, x + y = 5 | |
data.y = 4 | |
// output: y = 4, x + y = 7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<p> | |
<span>Count is: {{ count }}</span> | |
<button @click="count++">increment</button> | |
is positive: {{ isPositive }} | |
</p> | |
</template> | |
<script> | |
export default { | |
data: () => ({ count: 0 }), | |
computed: { | |
isPositive() { return this.count > 0 } | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<p> | |
<span>My name is {{ name.given }} {{ name.family }}</span> | |
<button @click="update">update name</button> | |
</p> | |
</template> | |
<script> | |
export default { | |
data: () => ({ | |
name: { | |
given: 'Jinjiang' | |
} | |
}), | |
methods: { | |
update() { | |
this.name.family = 'Zhao' | |
} | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<ul> | |
<li v-for="item, index in list" :key="index"> | |
{{ item }} | |
<button @click="edit(index)">edit</button> | |
</li> | |
</ul> | |
</template> | |
<script> | |
export default { | |
data() { | |
return { | |
list: [ | |
'Client meeting', | |
'Plan webinar', | |
'Email newsletter' | |
] | |
} | |
}, | |
methods: { | |
edit(index) { | |
const newItem = prompt('Input a new item') | |
if (newItem) { | |
this.list[index] = newItem | |
} | |
} | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<ul>...</ul> | |
<!-- btw Vue 3.0 supports multi-root template like this --> | |
<button @click="clean">clean</button> | |
</template> | |
<script> | |
export default { | |
data: ..., | |
methods: { | |
..., | |
clean() { this.list.length = 0 } | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div> | |
<ul> | |
<li v-for="item, index in list" :key="index"> | |
{{ item }} | |
<button @click="remove(item)">remove</button> | |
</li> | |
</ul> | |
<button @click="add">add</button> | |
<button @click="clean">clean</button> | |
</div> | |
</template> | |
<script> | |
export default { | |
data: () => ({ | |
list: new Set([ | |
'Client meeting', | |
'Plan webinar', | |
'Email newsletter' | |
]) | |
}), | |
created() { | |
console.log(this.list) | |
}, | |
methods: { | |
remove(item) { | |
this.list.delete(item) | |
}, | |
add() { | |
const newItem = prompt('Input a new item') | |
if (newItem) { | |
this.list.add(newItem) | |
} | |
}, | |
clean() { | |
this.list.clear() | |
} | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { readonly } from 'vue' | |
export default { | |
data: () => ({ | |
test: readonly({ name: 'Vue' }) | |
}), | |
methods: { | |
update(){ | |
this.test.name = 'Jinjiang' | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div> | |
Hello {{ test.name }} | |
<button @click="update">should not update</button> | |
</div> | |
</template> | |
<script> | |
import { markRaw } from 'vue' | |
export default { | |
data: () => ({ | |
test: markRaw({ name: 'Vue' }) | |
}), | |
methods: { | |
update(){ | |
this.test.name = 'Jinjiang' | |
console.log(this.test) | |
} | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { ref, reactive, readonly, markRaw, computed, toRefs } from 'vue' | |
export default { | |
setup(props) { | |
const counter = ref(0) | |
const increment = () => counter.value++ | |
const proxy = reactive({ x: 1, y: 2 }) | |
const frozen = readonly({ x: 1, y: 2 }) | |
const oneTimeLargeData = markRaw({ ... }) | |
const isZero = computed(() => counter.value === 0) | |
const propRefs = toRefs(props) | |
// could use a,b,c,d,e,f in template and `this` | |
return { | |
a: counter, | |
b: increment, | |
c: proxy, | |
d: frozen, | |
e: oneTimeLargeData, | |
f: isZero, | |
...propRefs | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// store.js | |
import { ref, computed } from 'vue' | |
export const firstName = ref('Jinjiang') | |
export const lastName = ref('Zhao') | |
// getter only version | |
export const fullName = computed(() => `${firstName.value} ${lastName.value}`) | |
// getter + setter version | |
export const fullName2 = computed({ | |
get: () => `${firstName.value} ${lastName.value}`, | |
set: (v) => { | |
const names = v.split(' ') | |
if (names.length > 0) { | |
firstName.value = names[0] | |
} | |
if (names.length > 1) { | |
lastName.value = names[names.length - 1] | |
} | |
} | |
}) | |
// another-file.js | |
import { firstName, lastName, fullName, fullName2 } from './store.js' | |
console.log(fullName.value) // Jinjiang Zhao | |
firstName.value = 'Evan' | |
lastName.value = 'You' | |
console.log(fullName.value) // Evan You | |
fullName2.value = 'Jinjiang Zhao' | |
console.log(firstName.value) // Jinjiang | |
console.log(lastName.value) // Zhao |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<input v-model="email" /> | |
</template> | |
<script> | |
import { customRef } from 'vue' | |
import { validate } from 'isemail' | |
export default { | |
data() { | |
return { | |
email: customRef((track, trigger) => { | |
const value = '' | |
return { | |
get() { | |
track() | |
return value | |
}, | |
set(v) { | |
if (validate(v)) { | |
value = v | |
trigger() | |
} | |
} | |
} | |
}) | |
} | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// track, trigger, reactive handlers | |
const track = (...arguments) => console.log('track', ...arguments) | |
const trigger = (...arguments) => console.log('trigger', ...arguments) | |
const reactiveHandlers = { ... } | |
// set an invisible skip flag to raw data | |
const markRaw = data => Object.defineProperty(data, '__v_skip', { value: true }) | |
// create a proxy only when there is no skip flag on the data | |
const reactive = data => { | |
if (data.__v_skip) { | |
return data | |
} | |
return new Proxy(data, reactiveHandlers) | |
} | |
// create a proxy object for the data | |
const data = { x: 1, y: 2 } | |
const rawData = markRaw(data) | |
const reactiveData = readonly(data) | |
console.log(rawData === data) // true | |
console.log(reactiveData === data) // true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { reactive, readonly, markRaw } from 'vue' | |
const ComponentFoo = { | |
data() { | |
return { | |
reactiveX: { x: 1 }, | |
reactiveXInAnotherWay: reactive({ x: 1 }), | |
immutableY: readonly({ y: 2 }), | |
needntChangeReactivelyZ: markRaw({ z: 3 }) | |
} | |
}, | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { markRaw } from 'vue' | |
const obj = { x: 1 } | |
const result = markRaw(obj) | |
console.log(obj === result) // true | |
const ComponentFoo = { | |
data() { | |
return { | |
obj, | |
result | |
} | |
}, | |
// ... | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { shallowReactive, shallowReadonly } from 'vue' | |
const ComponentFoo = { | |
data() { | |
return { | |
x: shallowReactive({ a: { b: 1 } }), | |
y: shallowReadonly({ a: { b: 1 } }) | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const map = new Map() | |
map.has('x') | |
map.get('x') | |
map.set('x', 1) | |
map.delete('x') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// a Map to record dependets | |
const dependentMap = new Map() | |
// track and trigger a property | |
const track = (type, data, propName) => { | |
if (isDryRun && currentFn) { | |
if (!dependentMap.has(data)) { | |
dependentMap.set(data, new Map()) | |
} | |
if (!dependentMap.get(data).has(propName)) { | |
dependentMap.get(data).set(propName, new Set()) | |
} | |
dependentMap.get(data).get(propName).add(currentFn) | |
} | |
} | |
const trigger = (type, data, propName) => { | |
dependentMap.get(data).get(propName).forEach(fn => fn()) | |
} | |
// observe | |
let isDryRun = false | |
let currentFn = null | |
const observe = fn => { | |
isDryRun = true | |
currentFn = fn | |
fn() | |
currentFn = null | |
isDryRun = false | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const track = (...arguments) => console.log('track', ...arguments) | |
const trigger = (...arguments) => console.log('trigger', ...arguments) | |
// all behaviors of a proxy by operation types | |
const handlers = { | |
get(...arguments) { track('get', ...arguments); return Reflect.get(...arguments) }, | |
has(...arguments) { track('has', ...arguments); return Reflect.set(...arguments) }, | |
set(...arguments) { Reflect.set(...arguments); trigger('set', ...arguments) }, | |
deleteProperty(...arguments) { | |
Reflect.set(...arguments); | |
trigger('delete', ...arguments) | |
}, | |
// ... | |
} | |
// create a proxy object for the data | |
const data = { x: 1, y: 2 } | |
const proxy = new Proxy(data, handlers) | |
// will call `trigger()` in `set()` | |
proxy.z = 3 | |
// create a proxy object for an array | |
const arr = [1,2,3] | |
const arrProxy = new Proxy(arr, handlers) | |
// will call `track()` & `trigger()` when get/set by index | |
arrProxy[0] | |
arrProxy[1] = 4 | |
// will call `trigger()` when set `length` | |
arrProxy.length = 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const data = { x: 1, y: 2 } | |
// all behaviors of a proxy by operation types | |
const handlers = { | |
get(data, propName, proxy) { | |
console.log(`Get ${propName}: ${data[propName]}!`) | |
return data[propName] | |
}, | |
has(data, propName) { ... }, | |
set(data, propName, value, proxy) { ... }, | |
deleteProperty(data, propName) { ... }, | |
... | |
} | |
// create a proxy object for the data | |
const proxy = new Proxy(data, handlers) | |
// print: 'Get x: 1' and return `1` | |
proxy.x |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const data = { x: 1, y: 2 } | |
// all behaviors of a proxy by operation types | |
const handlers = { | |
get(data, propName, proxy) { | |
console.log(`Get ${propName}: ${data[propName]}!`) | |
// same behavior as before | |
return Reflect.get(data, propName, proxy) | |
}, | |
has(...arguments) { return Reflect.set(...arguments) }, | |
set(...arguments) { return Reflect.set(...arguments) }, | |
deleteProperty(...arguments) { return Reflect.set(...arguments) }, | |
// ... | |
} | |
// create a proxy object for the data | |
const proxy = new Proxy(data, handlers) | |
// print: 'Get x: 1' and return `1` | |
proxy.x |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { reactive, computed, effect } from '@vue/reactivity' | |
const data = { x: 1, y: 2 } | |
const proxy = reactive(data) | |
const z = computed(() => proxy.x + proxy.y) | |
// print 'sum: 3' | |
effect(() => console.log(`sum: ${z.value}`)) | |
console.log(proxy.x, proxy.y, z.value) // 1, 2, 3 | |
proxy.x = 11 // print 'sum: 13' | |
console.log(proxy.x, proxy.y, z.value) // 11, 2, 13 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// … handlers | |
// … observe | |
// make data and arr reactive | |
const data = { x: 1, y: 2 } | |
const proxy = new Proxy(data, handlers) | |
const arr = [1, 2, 3] | |
const arrProxy = new Proxy(arr, handlers) | |
// observe functions | |
const depA = () => console.log(`x = ${proxy.x}`) | |
const depB = () => console.log(`y = ${proxy.y}`) | |
const depC = () => console.log(`x + y = ${proxy.x + proxy.y}`) | |
const depD = () => { | |
let sum = 0 | |
for (let i = 0; i < arrProxy.length; i++) { | |
sum += arrProxy[i] | |
} | |
console.log(`sum = ${sum}`) | |
} | |
// dry-run all dependents | |
observe(depA) | |
observe(depB) | |
observe(depC) | |
observe(depD) | |
// output: x = 1, y = 2, x + y = 3, sum = 6 | |
// mutate data | |
proxy.x = 3 | |
// output: x = 3, x + y = 5 | |
arrProxy[1] = 4 | |
// output: sum = 8 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const track = (...arguments) => console.log('track', ...arguments) | |
const trigger = (...arguments) => console.log('trigger', ...arguments) | |
// all behaviors of a proxy by operation types | |
const handlers = { | |
get(...arguments) { track('get', ...arguments); return Reflect.get(...arguments) }, | |
has(...arguments) { track('has', ...arguments); return Reflect.set(...arguments) }, | |
set(...arguments) { | |
console.warn('This is a readonly proxy, you couldn\'t modify it.') | |
}, | |
deleteProperty(...arguments) { | |
console.warn('This is a readonly proxy, you couldn\'t modify it.') | |
}, | |
// ... | |
} | |
// create a proxy object for the data | |
const data = { x: 1, y: 2 } | |
const readonly = new Proxy(data, handlers) | |
// will warn that you couldn't modify it | |
readonly.z = 3 | |
// will warn that you couldn't modify it | |
delete readonly.x |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// store.js | |
import { ref } from 'vue' | |
export const counter = ref(0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// bar.vue | |
<template> | |
<button @click="increment">increment</button> | |
</template> | |
<script> | |
import { counter } from './store.js' | |
export { | |
methods: { | |
increment() { counter.value++ } | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { reactive, toRef, toRefs } from 'vue' | |
const proxy = reactive({ x: 1, y: 2 }) | |
const refX = toRef(proxy, 'x') | |
proxy.x = 3 | |
console.log(refX.value) // 3 | |
const refs = toRefs(proxy) | |
proxy.y = 4 | |
console.log(refs.x.value) // 3 | |
console.log(refs.y.value) // 4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { shallowRef } from 'vue' | |
const data = { x: 1, y: 2 } | |
const ref = shallowRef(data) | |
// won't trigger update | |
ref.value.x = 3 | |
// will trigger update | |
ref.value = { x: 3, y: 2 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// store.js | |
// This won't work. | |
export const counter = 0; | |
// This won't works neither. | |
// import { reactive } from 'vue' | |
// export const counter = reactive(0) | |
// foo.vue | |
<template> | |
<div> | |
{{ counter }} | |
</div> | |
</template> | |
<script> | |
import { counter } from './store.js' | |
export { | |
data() { | |
return { counter } | |
} | |
} | |
</script> | |
// bar.vue | |
<template> | |
<button @click="counter++">increment</button> | |
</template> | |
<script> | |
import { counter } from './store.js' | |
export { | |
data() { | |
return { counter } | |
} | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// store.js | |
import { ref, watch, watchEffect } from 'vue' | |
export const counter = ref(0) | |
// Will print the counter every time it's mutated. | |
watchEffect(() => console.log(`The counter is ${counter.value}`)) | |
// Do the similar thing with more options | |
watch(counter, (newValue, oldValue) => | |
console.log(`The counter: from ${oldValue} to ${newValue}`) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from "react"; | |
import { effect, reactive } from "@vue/reactivity"; | |
const Vue = ({ setup, render }) => { | |
const Comp = props => { | |
const [renderResult, setRenderResult] = React.useState(null); | |
const [reactiveProps] = React.useState(reactive({})); | |
Object.assign(reactiveProps, props); | |
React.useEffect(() => { | |
const data = { ...setup(reactiveProps) }; | |
effect(() => setRenderResult(render(data))); | |
}, []); | |
return renderResult; | |
}; | |
return Comp; | |
}; | |
const Foo = Vue({ | |
setup: () => { | |
const counter = ref(0); | |
const increment = () => { | |
counter.value++; | |
}; | |
return { x: counter, y: increment }; | |
}, | |
render: ({ x, y }) => <h1 onClick={y}>Hello World {x.value}</h1> | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment