# 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:侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

相比如watchEffectwatch使我们可以

  • 懒执行副作用;
  • 更加明确应该由哪个状态触发侦听器重新执行;
  • 可以访问所侦听状态的前一个值和当前值。
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,但是绑定事件需要加ononUpdate:modelValue

上次更新: 1/22/2025, 9:39:13 AM