初步学习VUE3

191 阅读6分钟

VUE3

经过了漫长的迭代,Vue3.0 终于在上 2020-09-18 发布了

  • 对 VUE2 中的主要函数构进行了重构,使用了 Typescript
  • 带来了组合式 API(Composition API)

以往 VUE2

  • 对 TypeScript 支持不友好(所有属性都放在了 this 对象上,难以推倒组件的数据类型)
  • 大量的 API 挂载在 Vue 对象的原型上,难以实现 TreeShaking
  • 架构层面对跨平台 dom 渲染开发支持不友好
  • 组合式 API(CompositionAPI)。受 ReactHook 启发
  • 更方便的支持了 jsx
  • Vue 3 的 Template 支持多个根标签,Vue 2 不支持
  • 对虚拟 DOM 进行了重写、对模板的编译进行了优化操作

组合式 API 的入口 setup

setup() 函数是 vue3 中,专门为组件提供的新属性。 它为我们使用 vue3 的 Composition API 新特性提供了统一的入口, setup 函数会在 beforeCreate 、created 之前执行, vue3 也是取消了这两个钩子,统一用 setup 代替, 该函数相当于一个生命周期函数,vue 中过去的 data,methods,watch 等全部都用对应的新增 api 写在 setup()函数中

响应数据(reactive)

reactive() 函数接收一个普通对象, 返回一个响应式的数据对象, 相当于 Vue 2.x 中的 Vue.observable() API, 响应式转换是深层的——它影响所有嵌套属性(基于 proxy 来实现)。

let state = reactive({
  name: "test",
});

引用数据(ref = reference)

ref() 函数用来根据给定的值创建一个响应式的数据对象, 返回值也是一个对象(proxy),这个对象上只包含一个 value 属性, 只在 setup 函数内部访问 ref 函数需要加.value

const count = ref<number>(10);
// 获取ref 中定义的值, 需要通过value属性
console.log(count.value);

toRefs() 函数

toRefs() 函数可以将 reactive() 创建出来的响应式对象,转换为普通的对象, 只不过,这个对象上的每个属性节点,都是ref() 类型的响应式数据

let state = reactive({
  name: "test",
});
const age = ref<number>(10);
return {
  ...toRefs(state), // =>可以直接使用name不需要state.name
  age,
};

computed()

该函数用来创造计算属性,和过去一样,它返回的值是一个 ref 对象。

创建只读的计算属性

const age = ref<number>(10);
/**
 * @description: 只读
 * 根据 age 的值,创建一个响应式的计算属性 readOnlyAge,
 * 它会根据依赖的 ref 自动计算并返回一个新的 ref
 */
const readOnlyAge = computed(() => age.value++); // 19

通过 get/set 方法创建一个可读可写的计算属性

const age = ref<number>(18);
const computedAge = computed({
  get: () => age.value + 1,
  set: (value) => age.value + value,
});
// 为了计算属性赋值的操作,会触发 set 函数, 触发 set 函数后,age 的值会被更新
age.value = 100;

监听函数(watch)

watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。 默认情况是懒执行的,也就是说仅在侦听的源数据变更时才执行回调。

监听用 reactive 声明的数据源

interface Person {
  name: string;
  age: number;
}
const state = reactive<Person>({ name: "vue", age: 10 });
watch(
  () => state.age,
  (age, preAge) => {
    console.log(age); // 100
    console.log(preAge); // 10
  }
);
// 修改age 时会触发watch 的回调, 打印变更前后的值
state.age = 100;

监听用 ref 声明的数据源

const age = ref<number>(10);
watch(age, () => console.log(age.value)); // 100
// 修改age 时会触发watch 的回调, 打印变更后的值
age.value = 100;

同时监听多个值

interface Person {
  name: string;
  age: number;
}
const state = reactive<Person>({ name: "vue", age: 10 });
watch(
  [() => state.age, () => state.name],
  ([newName, newAge], [oldName, oldAge]) => {
    console.log(newName);
    console.log(newAge);
    console.log(oldName);
    console.log(oldAge);
  }
);
// 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
state.age = 100;
state.name = "vue3";

stop 停止监听

在 setup() 函数内创建的 watch 监视,会在当前组件被销毁的时候自动停止。 如果想要明确地停止某个监视,可以调用 watch() 函数的返回值即可,语法如下:

interface Person {
  name: string;
  age: number;
}
const state = reactive<Person>({ name: "vue", age: 10 });
const stop = watch(
  [() => state.age, () => state.name],
  ([newName, newAge], [oldName, oldAge]) => {
    console.log(newName);
    console.log(newAge);
    console.log(oldName);
    console.log(oldAge);
  }
);
// 修改age 时会触发watch 的回调, 打印变更前后的值, 此时需要注意, 更改其中一个值, 都会执行watch的回调
state.age = 100;
state.name = "vue3";
setTimeout(() => {
  stop();
  // 此时修改时, 不会触发watch 回调
  state.age = 1000;
  state.name = "vue3-";
}, 1000); // 1秒之后讲取消watch的监听

慎用!!!停止后无法再启动

function doWatch(
  source,
  cb,
  { immediate, deep, flush, onTrack, onTrigger } = EMPTY_OBJ
) {
  if (process.env.NODE_ENV !== "production" && !cb) {
    if (immediate !== undefined) {
      warn(
        `watch() "immediate" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      );
    }
    if (deep !== undefined) {
      warn(
        `watch() "deep" option is only respected when using the ` +
          `watch(source, callback, options?) signature.`
      );
    }
  }
  const warnInvalidSource = (s) => {
    warn(
      `Invalid watch source: `,
      s,
      `A watch source can only be a getter/effect function, a ref, ` +
        `a reactive object, or an array of these types.`
    );
  };
  const instance = currentInstance;
  let getter;
  let forceTrigger = false;
  let isMultiSource = false;
  if (isRef(source)) {
    getter = () => source.value;
    forceTrigger = !!source._shallow;
  } else if (isReactive(source)) {
    getter = () => source;
    deep = true;
  } else if (isArray(source)) {
    isMultiSource = true;
    forceTrigger = source.some(isReactive);
    getter = () =>
      source.map((s) => {
        if (isRef(s)) {
          return s.value;
        } else if (isReactive(s)) {
          return traverse(s);
        } else if (isFunction(s)) {
          return callWithErrorHandling(s, instance, 2 /* WATCH_GETTER */);
        } else {
          process.env.NODE_ENV !== "production" && warnInvalidSource(s);
        }
      });
  } else if (isFunction(source)) {
    if (cb) {
      // getter with cb
      getter = () =>
        callWithErrorHandling(source, instance, 2 /* WATCH_GETTER */);
    } else {
      // no cb -> simple effect
      getter = () => {
        if (instance && instance.isUnmounted) {
          return;
        }
        if (cleanup) {
          cleanup();
        }
        return callWithAsyncErrorHandling(
          source,
          instance,
          3 /* WATCH_CALLBACK */,
          [onInvalidate]
        );
      };
    }
  } else {
    getter = NOOP;
    process.env.NODE_ENV !== "production" && warnInvalidSource(source);
  }
  if (cb && deep) {
    const baseGetter = getter;
    getter = () => traverse(baseGetter());
  }
  let cleanup;
  let onInvalidate = (fn) => {
    cleanup = effect.onStop = () => {
      callWithErrorHandling(fn, instance, 4 /* WATCH_CLEANUP */);
    };
  };
  // in SSR there is no need to setup an actual effect, and it should be noop
  // unless it's eager
  if (isInSSRComponentSetup) {
    // we will also not call the invalidate callback (+ runner is not set up)
    onInvalidate = NOOP;
    if (!cb) {
      getter();
    } else if (immediate) {
      callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
        getter(),
        isMultiSource ? [] : undefined,
        onInvalidate,
      ]);
    }
    return NOOP;
  }
  let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE;
  const job = () => {
    if (!effect.active) {
      return;
    }
    if (cb) {
      // watch(source, cb)
      const newValue = effect.run();
      if (
        deep ||
        forceTrigger ||
        (isMultiSource
          ? newValue.some((v, i) => hasChanged(v, oldValue[i]))
          : hasChanged(newValue, oldValue)) ||
        false
      ) {
        // cleanup before running cb again
        if (cleanup) {
          cleanup();
        }
        callWithAsyncErrorHandling(cb, instance, 3 /* WATCH_CALLBACK */, [
          newValue,
          // pass undefined as the old value when it's changed for the first time
          oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue,
          onInvalidate,
        ]);
        oldValue = newValue;
      }
    } else {
      // watchEffect
      effect.run();
    }
  };
  // important: mark the job as a watcher callback so that scheduler knows
  // it is allowed to self-trigger (#1727)
  job.allowRecurse = !!cb;
  let scheduler;
  if (flush === "sync") {
    scheduler = job; // the scheduler function gets called directly
  } else if (flush === "post") {
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense);
  } else {
    // default: 'pre'
    scheduler = () => {
      if (!instance || instance.isMounted) {
        queuePreFlushCb(job);
      } else {
        // with 'pre' option, the first call must happen before
        // the component is mounted so it is called synchronously.
        job();
      }
    };
  }
  const effect = new ReactiveEffect(getter, scheduler);
  if (process.env.NODE_ENV !== "production") {
    effect.onTrack = onTrack;
    effect.onTrigger = onTrigger;
  }
  // initial run
  if (cb) {
    if (immediate) {
      job();
    } else {
      oldValue = effect.run();
    }
  } else if (flush === "post") {
    queuePostRenderEffect(
      effect.run.bind(effect),
      instance && instance.suspense
    );
  } else {
    effect.run();
  }
  return () => {
    effect.stop();
    if (instance && instance.scope) {
      remove(instance.scope.effects, effect);
    }
  };
}

监听响应(watchEffect)

监听器的升级版本,立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const count = ref<number>(0);
watchEffect(() => console.log(count.value));
// -> 打印出 0
setTimeout(() => {
  count.value++;
  // -> 打印出 1
}, 100);

新的生命后期

onBeforeMount(() => {
  console.log("beformounted!");
});
onMounted(() => {
  console.log("mounted!");
});
onBeforeUpdate(() => {
  console.log("beforupdated!");
});
onUpdated(() => {
  console.log("updated!");
});
onBeforeUnmount(() => {
  console.log("beforunmounted!");
});
onUnmounted(() => {
  console.log("unmounted!");
});
onErrorCaptured(() => {
  console.log("errorCaptured!");
});

模板中的 refs

通过 refs 来回去真实 dom 元素, 这个和 react 的用法一样,为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup()中声明一个 ref 并返回它

  • 还是跟往常一样,在 html 中写入 ref 的名称
  • 在 setup 中定义一个 ref
  • setup 中返回 ref 的实例
  • onMounted 中可以得到 ref 的 RefImpl 的对象, 通过.value 获取真实 dom