# Vue3响应式基础
记录一些对文档的自己的理解。
# 1、setup 里面获取Dom元素
需要使用ref,声明一个ref(null)
, 就可以在onMounted钩子获取到元素了。
<p ref="CountEl">{{count}}</p>
setup(props: Props) {
const CountEl = ref(null);
onMounted(() => {
console.log(CountEl.value);
});
return { CountEl };
}
# 2、watch 和watchEffect的区别
watchEffect
:立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
watch
:侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
相比如watchEffect
,watch
使我们可以
- 懒执行副作用;
- 更加明确应该由哪个状态触发侦听器重新执行;
- 可以访问所侦听状态的前一个值和当前值。
import { reactive, ref, watch } from 'vue';
interface Props {}
export default {
name: 'Watch',
// props: {},
// components: {},
setup(props: Props) {
const state = reactive({ count: 0 });
// watch getter function
watch(
() => state.count,
(newVal, oldVal) => {},
{
flush: 'post'
}
);
const handleAdd = () => {
state.count++;
};
// watch ref
const count = ref(1);
watch(count, (newVal, oldVal) => {});
// 当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。如果你想让回调在深层级变更时也能触发,你需要使用 { deep: true } 强制侦听器进入深层级模式。
watch(
() => state,
(newVal, oldVal) => {},
{ deep: true }
);
// 当直接侦听一个响应式对象时,侦听器会自动启用深层模式:
watch(state, () => {});
return {
count,
state,
handleAdd
};
}
};
# 3、customRef
自定义ref。主要可以自定义set和get的行为。
如果像下面这样子写, 那么MyRef其实就和ref的效果一样。
function MyRef(value) {
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newVal) {
value = newVal;
trigger();
}
};
});
}
但是我们可以自定义set或者get的行为, 比如下面这个防抖的功能
function useDebounceRef(value, delay = 500) {
let timeout;
return customRef((track, trigger) => {
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, delay);
}
};
});
}
# 4、watchEffect的实现原理
function reactive(obj) {
return new Proxy(obj, {
get(target, key) {
track(target, key);
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key);
}
});
}
let activeEffect = null;
let effectMap = new Map();
function track(target, key) {
if (activeEffect) {
const effects = getSubscribersForProperty(target, key);
effects.add(activeEffect);
// console.log(effectMap);
}
}
function trigger(target, key) {
const effects = getSubscribersForProperty(target, key);
effects.forEach(effect => effect());
}
function getSubscribersForProperty(target, key) {
if (effectMap.has(target)) {
const map = effectMap.get(target);
if (map.has(key)) {
return map.get(key);
} else {
const set = new Set();
map.set(key, set);
return set;
}
} else {
const map = new Map();
const set = new Set();
map.set(key, set);
effectMap.set(target, map);
return set;
}
}
function watchEffect(update) {
const effect = () => {
activeEffect = effect;
update();
activeEffect = null;
};
effect();
}
const p = { name: 'xddd' };
const pp = reactive(p);
// console.log(pp.name);
const a = { name: 'a' };
const ap = reactive(a);
/* watchEffect的参数被立即执行, 读取了pp.name, 会触发 pp的get,在get里面调用track。
track的作用就是保存这个副作用函数到一个数据结构 */
watchEffect(() => {
console.log(pp.name);
console.log(ap.name);
});
setTimeout(() => {
// 设置name会触发pp的set。在设置完之后调用trigger。trigger的作用就是执行副作用
pp.name = '22222';
}, 1000);
setTimeout(() => {
ap.name = 'apapap';
}, 2000);
# 5、渲染函数实现 v-model
子组件
<script lang="ts">
import { defineComponent, h } from 'vue';
export default defineComponent({
name: 'MyInput',
props: {
modelValue: {
type: String,
default: ''
}
},
emits: ['update:modelValue', 'handleBlur'],
// components: {},
setup(props, context) {
return () =>
h('input', {
style: {
color: 'red',
border: '1px solid red'
},
value: props.modelValue,
onInput(event) {
const v = event.target.value;
console.log(v);
context.emit('update:modelValue', v);
},
onBlur() {
console.log('blur');
context.emit('handleBlur');
}
});
}
});
</script>
<style scoped>
</style>
父组件
const inputVal = ref('');
h(MyInput, {
modelValue: inputVal.value,
'onUpdate:modelValue': (val) => (inputVal.value = val),
onHandleBlur: () => {
console.log('onHandleBlur');
}
}),
注意触发emit写的是update:modelValue
,但是绑定事件需要加on
,onUpdate:modelValue
。