vue引入了react的hook的概念
示例代码:
import { value, computed, watch, onMounted } from 'vue'function useMouse() { const x = value(0) const y = value(0) const update = e => { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y }}// 在组件中使用该函数const Component = { setup() { const { x, y } = useMouse() // 与其它函数配合使用 const { z } = useOtherLogic() return { x, y, z } }, template: `<div>{{ x }} {{ y }} {{ z }}</div>`}from 'vue'
function useMouse() {
const x = value(0)
const y = value(0)
const update = e => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
window.addEventListener('mousemove', update)
})
onUnmounted(() => {
window.removeEventListener('mousemove', update)
})
return { x, y }
}
// 在组件中使用该函数
const Component = {
setup() {
const { x, y } = useMouse()
// 与其它函数配合使用
const { z } = useOtherLogic()
return { x, y, z }
},
template: `<div>{{ x }} {{ y }} {{ z }}</div>`
}
Vue Hooks 与 React Hooks 的差异
- React Hooks 的简单语法:
const [ count, setCount ] = useState(0)
const setToOne = () => setCount(1) - Vue Hooks 的简单语法:
const count = value(0)
const setToOne = () => count.value = 1 - 在 React 中,useMouse 如果修改了 x 的值,那么使用 useMouse 的函数就会被重新执行,以此拿到最新的 x,而在 Vue 中,将 Hooks 与 Immutable 深度结合,通过包装 x.value,使得当 x 变更时,引用保持不变,仅值发生了变化。所以 Vue 利用 Proxy 监听机制,可以做到 setup 函数不重新执行,但 Template 重新渲染的效果。
effect 的实现
// effect的实现
function effect(fn, options = {}) {
var effect = createResponsiveEffect(fn, options);
return options.lazy ? effect : effect();
}
function createResponsiveEffect(fn, options) {
var effect = function(...args) {
return run(effect, fn, args);
};
effect.lazy = options.lazy;
effect.computed = options.computed;
effect.deps = [];
return effect;
}
reactive和effect结合
// run的实现
var activeEffectStack = [];
function run(effect, fn, args) {
if (activeEffectStack.indexOf(effect) === -1) {
try {
// 把effectpush到数组里面
activeEffectStack.push(effect);
return fn(...args);
} finally {
// 清除已经收集到的effect
activeEffectStack.pop();
}
}
}
// 触发依赖收集与派发更新
var func = {
get(target, key, receiver) {
var result = Reflect.get(target, key, receiver);
// 依赖收集
track(target, key);
return isObject(result) ? responsive(result) : result;
},
set(target, key, val, receiver) {
var result = Reflect.set(target, key, val, receiver);
const extra = { oldValue: target[key], newValue: target };
// 派发更新
trigger(target, key, extra);
return result;
}
};
// 依赖收集的实现
// 依赖收集的最终数据结构
// targetMap = {
// target: {
// name: [effect],
// height: [effect]
// }
// }
var targetMap = new WeakMap();
function track(target, key) {
var effect = activeEffectStack(activeEffectStack.length - 1);
if (effect) {
var depsMap = targetMap.get(target);
if (depsMap === void 0) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
var dep = depsMap.get(key);
if (dep === void 0) {
dep = new Set();
depsMap.set(key, dep);
}
if (!dep.has(effect)) {
dep.add(effect);
effect.deps.push(dep);
}
}
}
//派发更新
function trigger(target, key, extra) {
var depsMap = targetMap.get(target);
if (depsMap === void 0 || key === void 0) {
return;
}
var effects = new Set();
var computedEffects = new Set();
let deps = depsMap.get(key);
deps.forEach(effect => {
if (effect.computed) {
computedEffects.add(effect);
} else {
effects.add(effect);
}
});
computedEffects.forEach(effect => {
effect();
});
effects.forEach(effect => {
effect();
});
}
// computed 的实现
function computed(fn) {
var getter = fn;
var func = effect(getter, { computed: true, lazy: true });
return {
effect: func,
get value() {
return func();
}
};
}
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>vue3的响应式原理</title>
</head>
<script>
// 提前定义的一些变量
var isObject = object =>
object && typeof object === "object" ? true : false;
var wm1 = new WeakMap();
var wm2 = new WeakMap();
var activeEffectStack = [];
var targetMap = new WeakMap();
// 依赖收集
function track(target, key) {
var effect = activeEffectStack[activeEffectStack.length - 1];
if (effect) {
var depsMap = targetMap.get(target);
if (depsMap === void 0) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
var dep = depsMap.get(key);
if (dep === void 0) {
dep = new Set();
depsMap.set(key, dep);
}
if (!dep.has(effect)) {
dep.add(effect);
effect.deps.push(dep);
}
}
}
// 派发更新
function trigger(target, key, extra) {
var depsMap = targetMap.get(target);
if (depsMap === void 0 || key === void 0) {
return;
}
var effects = new Set();
var computedEffects = new Set();
let deps = depsMap.get(key);
deps.forEach(effect => {
if (effect.computed) {
computedEffects.add(effect);
} else {
effects.add(effect);
}
});
computedEffects.forEach(effect => {
effect();
});
effects.forEach(effect => {
effect();
});
}
// responsive
var responsiveFunc = {
get(target, key, receiver) {
var result = Reflect.get(target, key, receiver);
track(target, key);
return isObject(result) ? responsive(result) : result;
},
set(target, key, val, receiver) {
var result = Reflect.set(target, key, val, receiver);
const extra = { oldValue: target[key], newValue: target };
trigger(target, key, extra);
return result;
}
};
var responsive = function(target) {
// 是否有observed
var observed = wm1.get(target);
// 是否有target
var value = wm2.has(target);
if (observed) {
return observed;
}
if (value) {
return target;
}
observed = new Proxy(target, responsiveFunc);
// 缓存observed
wm1.set(target, observed);
// 缓存target
wm2.set(observed, target);
return observed;
};
// effect
function effect(fn, options = {}) {
// 需要把fn函数变成响应式的函数
var effect = createResponsiveEffect(fn, options);
return options.lazy ? effect : effect();//默认先执行一次,以后有数据变化就执行fn
}
function createResponsiveEffect(fn, options) {
var effect = function(...args) {//这个就是创建响应式的effect
return run(effect, fn, args);
};
effect.lazy = options.lazy;
effect.computed = options.computed;
effect.deps = [];
return effect;
}
// effect-responsive
function run(effect, fn, args) {//让fn执行,并把effect放到堆栈中
if (activeEffectStack.indexOf(effect) === -1) {
try {
// 把effectpush到数组里面
activeEffectStack.push(effect);
return fn(...args);
} finally {
// 清除已经收集到的effect
activeEffectStack.pop();
}
}
}
// computed
function computed(fn) {
var getter = fn;
var func = effect(getter, { computed: true, lazy: true });
return {
effect: func,
get value() {
return func();
}
};
}
</script>
<body>
<h3>
体重指数 = 18.5 - 25 (中国体质标准:正常范围
18.5~23.9,超重24.0~27.9,肥胖≥28.0)
</h3>
<p class="text" style="font-size: 30px;color:#e93030"></p>
<button style="width: 100px;height: 50px;font-size: 30px;" class="button1">
加1
</button>
<button style="width: 100px;height: 50px;font-size: 30px;" class="button2">
减1
</button>
<script>
const $text = document.querySelector(".text");
const $button1 = document.querySelector(".button1");
const $button2 = document.querySelector(".button2");
var responsivePerson = responsive({
name: "小何",
height: 180,
weight: 74,
girls: {
1: "小尹",
2: "小钱"
}
});
var calacBodyMass = computed(() => {
return (
responsivePerson.weight / Math.pow(responsivePerson.height / 100, 2)
).toFixed(1);
});
effect(() => {
$text.innerHTML = `姓名:${responsivePerson.name},身高:${responsivePerson.height}cm,体重:${responsivePerson.weight}kg,体重指数:${calacBodyMass.value}`;
});
$button1.onclick = () => {
responsivePerson.height += 1;
};
$button2.onclick = () => {
responsivePerson.height -= 1;
};
</script>
</body>
</html>