全新数据响应式初体验
<div id="app">
<h3>{{state.title}}</h3>
<p>counter: {{state2.counter}}</p>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const { createApp, reactive, onMounted } = Vue
function useTitle() {
const state = reactive({
title: 'hello vue3!!!!!'
})
onMounted(() => {
state.title = 'vue3, hello!!!'
})
setTimeout(() => {
state.title = 'title 2s'
}, 2000);
return state
}
function useCounter() {
const state2 = reactive({
counter: 0
})
onMounted(() => {
state2.counter = 1
})
return state2
}
const app = createApp({
setup() {
const state = useTitle()
const state2 = useCounter()
return {
state,
state2
}
},
})
app.mount('#app')
</script>
数据响应式革新
- 初始化需要递归,速度慢
- 依赖关系占用资源较多
- 数组支持需要特殊实现
- 动态增加,删除属性需要额外API
- 不支持Map,Set等
手写数据响应式实现
- 基于Proxy的数据响应式
- 依赖收集
- 添加副作用effect()
- 依赖收集track()
- 触发副作用trigger()
<div id="app">
<h3>{{title}}</h3>
</div>
<script>
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
console.log('get', key);
track(target, key)
return target[key]
},
set(target, key, val) {
console.log('set', key);
target[key] = val
trigger(target, key)
},
})
}
const effectStack = []
function effect(fn) {
const eff = function () {
try {
effectStack.push(eff)
fn()
} finally {
effectStack.pop()
}
}
eff()
return eff
}
const targetMap = {}
function track(target, key) {
const effect = effectStack[effectStack.length - 1]
if (effect) {
let map = targetMap[target]
if (!map) {
map = targetMap[target] = {}
}
let deps = map[key]
if (!deps) {
deps = map[key] = []
}
if (deps.indexOf(effect) === -1) {
deps.push(effect)
}
}
}
function trigger(target, key) {
const map = targetMap[target]
if (map) {
const deps = map[key]
if(deps) {
deps.forEach(dep => dep())
}
}
}
const Vue = {
createApp(options) {
const renderer = Vue.createRenderer({
querySelector(selector) {
return document.querySelector(selector)
},
insert(child, parent, anchor) {
parent.insertBefore(child, anchor || null)
}
})
return renderer.createApp(options)
},
createRenderer({ querySelector, insert }) {
return {
createApp(options) {
return {
mount(selector) {
const parent = querySelector(selector)
if (!options.render) {
options.render = this.compile(parent.innerHTML)
}
if (options.setup) {
this.setupState = options.setup()
} else {
this.data = options.data()
}
this.proxy = new Proxy(this, {
get(target, key) {
if (key in target.setupState) {
return target.setupState[key]
} else {
return target.data[key]
}
},
set(target, key, val) {
if (key in target.setupState) {
target.setupState[key] = val
} else {
target.data[key] = val
}
}
})
this.update = effect(() => {
const el = options.render.call(this.proxy)
parent.innerHTML = ''
insert(el, parent)
})
this.update()
},
compile(template) {
return function render() {
const h3 = document.createElement('h3')
h3.textContent = this.title
return h3
}
}
}
}
}
}
}
</script>
<script>
const { createApp } = Vue
const app = createApp({
setup() {
const state = reactive({
title: 'hello vue3!!!!!!'
})
setTimeout(() => {
state.title = 'vue3, hello!!!!!!'
}, 2000);
return state
},
})
app.mount('#app')
</script>