"dev": "node scripts/dev.js --sourcemap"
複製代碼
<div id="app">
<h1>{{message}}</h1>
<comp></comp>
</div>
<script src="../dist/vue.global.js"></script>
<script>
// 建立實例方式變化了
const {createApp} = Vue
const app = createApp({
data() {
return {
'message': 'hello, vue3'
}
},
})
// 之前全局方法,變成實例方法
app.component('comp', {
template: '<div>comp</div>'
})
app.mount('#app')
</script>
複製代碼
Composition API字面意思是組合API,它是爲了實現基於函數的邏輯複用機制而產生的。html
<div id="app">
<h1>{{message}}</h1>
<p @click="add">{{count}}</p>
<p>{{doubleCount}}</p>
<p>{{num}}</p>
</div>
<script src="../dist/vue.global.js"></script>
<script>
// 引入使用的函數方法
const {createApp, reactive, computed, ref, toRefs} = Vue
const app = createApp({
// setup在beforeCreated以前執行
setup() {
// reactivity api
// message相關
const data = reactive({
message: 'hello,vue3',
})
setTimeout(() => {
data.message = 'vue3,hello'
}, 1000);
// count相關
const counter = reactive({
count: 0,
doubleCount: computed(() => counter.count * 2)
})
function add() {
counter.count++
}
// 單值響應式, ref()返回Ref對象,若是要修改它的值,訪問value屬性
const num = ref(0)
setInterval(() => {
num.value++
}, 1000);
return {...toRefs(data), add, ...toRefs(counter), num}
}
})
app.mount('#app')
</script>
複製代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>composition api</title>
<script src="../dist/vue.global.js"></script>
</head>
<body>
<div>
<h1>邏輯組合</h1>
<div id="app"></div>
</div>
<script>
const {createApp, reactive, onMounted, onUnmounted, toRefs} = Vue;
// 鼠標位置偵聽
function useMouse() {
// 數據響應化
const state = reactive({x: 0, y: 0})
const update = e => {
state.x = e.pageX
state.y = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
// 轉換全部key爲響應式數據
return toRefs(state)
}
// 時間監測
function useTime() {
const state = reactive({time: new Date()})
onMounted(() => {
setInterval(() => {
state.time = new Date()
}, 1000)
})
return toRefs(state)
}
// 邏輯組合
const MyComp = {
template: `
<div>x: {{ x }} y: {{ y }}</div>
<p>time: {{time}}</p>
`,
setup() {
// 使用鼠標邏輯
const {x, y} = useMouse()
// 使用時間邏輯
const {time} = useTime()
// 組合返回使用
return {x, y, time}
}
}
createApp(MyComp).mount('#app')
</script>
</body>
</html>
複製代碼
以上可見vue
// 1.對象響應化:遍歷每一個key,定義getter、setter
// 2.數組響應化:覆蓋數組原型方法,額外增長通知邏輯
const originalProto = Array.prototype
const arrayProto = Object.create(originalProto)
['push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort'].forEach(
method => {
arrayProto[method] = function() {
originalProto[method].apply(this, arguments)
notifyUpdate()
}
})
function observe(obj) {
if (typeof obj !== 'object' || obj == null) {
return
}
// 增長數組類型判斷,如果數組則覆蓋其原型
if (Array.isArray(obj)) {
Object.setPrototypeOf(obj, arrayProto)
} else {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
defineReactive(obj, key, obj[key])
}
}
}
function defineReactive(obj, key, val) {
observe(val) // 解決嵌套對象問題
Object.defineProperty(obj, key, {
get() {
return val
},
set(newVal) {
if (newVal !== val) {
observe(newVal) // 新值是對象的狀況 val = newVal
notifyUpdate()
}
}
})
}
function notifyUpdate() {
console.log('頁面更新!')
}
複製代碼
vue2響應式弊端:node
// Proxy
// 提取幫助方法
const isObject = v => v !== null && typeof v === 'object'
function reactive(obj) {
// 判斷是否對象
if (!isObject(obj)) {
return obj
}
return new Proxy(obj, {
get(target, key, receiver) {
const ret = Reflect.get(target, key, receiver)
console.log('獲取', key);
track(target, key)
// 若是是對象須要遞歸
return isObject(ret) ? reactive(ret) : ret
},
set(target, key, value, receiver) {
const ret = Reflect.set(target, key, value, receiver)
console.log('設置', key);
trigger(target, key)
return ret
},
deleteProperty(target, key) {
const ret = Reflect.deleteProperty(target, key)
console.log('刪除', key);
trigger(target, key)
return ret
},
})
}
複製代碼
// 大概結構以下所示
// target | depsMap
// obj | key | Dep
// k1 | effect1,effect2...
// k2 | effect3,effect4...
// {target: {key: [effect1,...]}}
複製代碼
// 保存當前活動響應函數做爲getter和effect之間橋樑
const effectStack = []
// effect任務:執行fn並將其入棧
function effect(fn) {
const rxEffect = function () {
// 1.捕獲異常
// 2.fn入棧出棧
// 3.執行fn
// 4.執行結束,出棧
try {
effectStack.push(rxEffect)
return fn()
} finally {
effectStack.pop()
}
}
// 默認執行一次響應函數
rxEffect()
// 返回響應函數
return rxEffect
}
// 響應函數觸發某個響應式數據,開始作依賴收集(映射過程)
// {target: {key: [fn1,fn2]}}
const targetMap = new WeakMap()
function track(target, key) {
// 從棧中取出響應函數
const effect = effectStack[effectStack.length - 1]
if (effect) {
// 獲取target對應依賴表
let depsMap = targetMap.get(target)
if (!depsMap) {
// 首次訪問不存在,建立一個
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 獲取key對應的響應函數集
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
// 將響應函數加入到對應集合
deps.add(effect)
}
}
// 觸發target.key對應響應函數,根據映射關係執行對應cb
function trigger(target, key) {
// 獲取依賴表
const depsMap = targetMap.get(target)
if (depsMap) {
// 獲取響應函數集合
const deps = depsMap.get(key)
if (deps) {
// 執行全部響應函數
deps.forEach(effect => effect())
}
}
}
複製代碼
// state就是Proxy實例
const state = reactive({foo: 'foo', bar: {a: 1}, arr: [1, 2, 3]})
// 測試代碼
// state.foo
// state.foo = 'foooooo'
// // 設置不存在屬性
// state.bar = 'bar'
// state.bar.a = 10
// state.arr.push(4)
// state.arr.pop()
effect(() => {
console.log('effect', state.foo);
})
state.foo = 'fooooooooo'
複製代碼