快速掌握-vue3

是什么

vue2 的升级版, 使用 ts 重构了代码, 带来了 Composition API RFC。 类似于 react hook 的写法。

  1. ts 重构,代码可读性更强
  2. vue3.x 使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty
  3. 实现了 TreeShaking (当 Javascript 项目达到一定体积时,将代码分成模块会更易于管理。但是,当这样做时,我们最终可能会导入实际上未使用的代码。Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法。)
  4. 支持 hook 写法 CompositionAPI。受 ReactHook 启发
  5. 支持 jsx
  6. Vue 3 的 Template 支持多个根标签,Vue 2 不支持
  7. 对虚拟 DOM 进行了重写、对模板的编译进行了优化操作
  8. 在 Vue2.x 中具名插槽和作用域插槽分别使用 slot 和 slot-scope 来实现, 在 Vue3.0 中将 slot 和 slot-scope 进行了合并 v-slot
  9. 在 Vue 3 中对自定义指令的 API 进行了更加语义化的修改,名称和组件生命周期名称相同
  10. v-model 变更:在自定义组件上使用 v-model 时,同一组件可以同时设置多个 v-model, 开发者可以自定义 v-model 修饰符

学到什么

  1. vue3 与 vue2 的核心区别
  2. Tree-Shaking
  3. 函数 setup()
  4. 函数 ref()
  5. 函数 isRef()
  6. 函数 toRefs()
  7. 函数 reactive()
  8. 函数 computed()、watch()
  9. LifeCycle Hooks(新的生命周期)
  10. Template refs
  11. vue3 的全局配置
  12. vue3 组件模板结构
  13. 实现 自定义 Hook
  14. 组件 teleport 任意门
  15. 组件 异步组件

vue3 与 vue2 的核心区别

  1. vue3.x 将使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty
  2. Object.defineProperty 只能劫持对象的属性, 而 Proxy 是直接代理对象,由于 Object.defineProperty 只能劫持对象属性,需要遍历对象的每一个属性,如果属性值也是对象,就需要递归进行深度遍历。但是 Proxy 直接代理对象,不需要遍历操作
  3. Object.defineProperty 对新增属性需要手动进行 Observe

Tree-Shaking

当 Javascript 项目达到一定体积时,将代码分成模块会更易于管理。但是,当这样做时,我们最终可能会导入实际上未使用的代码。Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法。

为什么:因为 Vue 实例是作为单个对象导出的,打包器无法分辨出代码中使用了对象的哪些属性。所以,我们需要单独引用

抽离了 一部分 vue2 中的公用函数,需要单独引用。以前的全局 API 现在只能通过具名导入,这一更改会对以下 API 有影响:

  • Vue.nextTick
  • Vue.observable(用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile(仅限完整版本时可用)
  • Vue.set(仅在 2.x 兼容版本中可用)
  • Vue.delete(与上同)

setup 函数

组件提供的新属性,为了使用 vue3 CompositionAPI 新特性而启用的函数,它有自己独立的生命周期

vue3 取消了 beforeCreate 、created 两个钩子函数,统一用 setup 代替

  1. props 用来接收 props 数据
  2. context 上下文对象
  3. return 返回模板中需要使用的函数
setup(props, context) {
    context.attrs
    context.slots
    context.emit

    return {

    }
  }

ref() 函数

组件提供的新特性函数

创建一个响应式的数据对象,这个对象是响应式的,只返回一个 { value: ""} 值

import { defineComponent, ref } from "vue";
export default defineComponent({
  setup() {
    const name = ref < string > "hello";
    // 在js 中获取ref 中定义的值, 需要通过value属性
    console.log(name.value);
    return {
      name,
    };
  },
});

isRef() 函数

组件提供的新特性函数

isRef() 用来判断某个值是否为 ref() 创建出来的对象

import { defineComponent, isRef, ref } from "vue";
export default defineComponent({
  setup(props, context) {
    const name: string = "vue";
    const age = ref<number>(18);
    console.log(isRef(age)); // true
    console.log(isRef(name)); // false
    return {
      age,
      name,
    };
  },
});

toRefs() 函数

组件提供的新特性函数

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

import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
  setup(props, context) {
    let state = reactive({
      name: "hello",
    });
    const age = ref(18);
    return {
      ...toRefs(state),
      age,
    };
  },
});

reactive() 函数

组件提供的新特性函数

reactive() 函数接收一个普通对象,返回一个响应式的数据对象, 想要使用创建的响应式数据也很简单,创建出来之后,在 setup 中 return 出去,直接在 template 中调用即可

import { defineComponent, reactive, ref, toRefs } from "vue";
export default defineComponent({
  setup(props, context) {
    let state = reactive({
      name: "hello",
    });

    return state;
  },
});

computed()、watch() 函数

组件提供的新特性函数

computed() 函数 ,用来计算属性,返回的值是一个 ref 对象。

import { computed, defineComponent, ref } from "vue";
export default defineComponent({
  setup(props, context) {
    const age = ref<number>(18);

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

watch() 函数,用来监听属性, 当数据源变化的时候才会被执行。

import { computed, defineComponent, reactive, toRefs, watch } from "vue";
interface Person {
  name: string;
  age: number;
}
export default defineComponent({
  setup(props, context) {
    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";
    return {
      ...toRefs(state),
    };
  },
});

LifeCycle Hooks 生命周期

组件提供的新特性函数

生命周期组件中新的写法

import { set } from "lodash";
import {
  defineComponent,
  onBeforeMount,
  onBeforeUnmount,
  onBeforeUpdate,
  onErrorCaptured,
  onMounted,
  onUnmounted,
  onUpdated,
} from "vue";
export default defineComponent({
  setup(props, context) {
    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!");
    });

    return {};
  },
});

模板 Template refs

组件提供的新特性函数

通过 refs 来回去真实 dom 元素,onMounted 中可以得到 ref 的 RefImpl 的对象, 通过.value 获取真实 dom

<template>
  <div class="mine" ref="elmRefs">
    <span>hello</span>
  </div>
</template>

<script lang="ts">
import { set } from "lodash";
import { defineComponent, onMounted, ref } from "vue";
export default defineComponent({
  setup(props, context) {
    // 获取真实dom
    const elmRefs = ref<null | HTMLElement>(null);
    onMounted(() => {
      console.log(elmRefs.value); // 得到一个 RefImpl 的对象, 通过 .value 访问到数据
    });

    return {
      elmRefs,
    };
  },
});
</script>

vue 的全局配置

Vue3 可以在组件用通过 getCurrentInstance() 来获取全局 globalProperties 中配置的信息

const app = Vue.createApp({});
app.config.globalProperties.$http = "axios";

setup( ) {
  const { ctx } = getCurrentInstance();
  ctx.$http
}

vue3 组件模板结构

<template>
  <div class="mine" ref="elmRefs">
    <span>{{ name }}</span>
    <br />
    <span>{{ count }}</span>
    <div>
      <button @click="handleClick">测试按钮</button>
    </div>

    <ul>
      <li v-for="item in list" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script lang="ts">
import {
  computed,
  defineComponent,
  getCurrentInstance,
  onMounted,
  PropType,
  reactive,
  ref,
  toRefs,
} from "vue";

interface IState {
  count: 0;
  name: string;
  list: Array<object>;
}

export default defineComponent({
  name: "demo",
  // 父组件传子组件参数
  props: {
    name: {
      type: String as PropType<null | "">,
      default: "vue3.x",
    },
    list: {
      type: Array as PropType<object[]>,
      default: () => [],
    },
  },
  components: {
    /// TODO 组件注册
  },
  emits: ["emits-name"], // 为了提示作用
  setup(props, context) {
    console.log(props.name);
    console.log(props.list);

    const state = reactive<IState>({
      name: "vue 3.0 组件",
      count: 0,
      list: [
        {
          name: "vue",
          id: 1,
        },
        {
          name: "vuex",
          id: 2,
        },
      ],
    });

    const a = computed(() => state.name);

    onMounted(() => {});

    function handleClick() {
      state.count++;
      // 调用父组件的方法
      context.emit("emits-name", state.count);
    }

    return {
      ...toRefs(state),
      handleClick,
    };
  },
});
</script>

实现 自定义 Hook

功能性组件可以封装成 hook, 以 use 作为前缀,和普通的函数区分

import { ref, Ref, computed } from "vue";

type CountResultProps = {
  count: Ref<number>;
  multiple: Ref<number>;
  increase: (delta?: number) => void;
  decrease: (delta?: number) => void;
};

export default function useCount(initValue = 1): CountResultProps {
  const count = ref(initValue);

  const increase = (delta?: number): void => {
    if (typeof delta !== "undefined") {
      count.value += delta;
    } else {
      count.value += 1;
    }
  };
  const multiple = computed(() => count.value * 2);

  const decrease = (delta?: number): void => {
    if (typeof delta !== "undefined") {
      count.value -= delta;
    } else {
      count.value -= 1;
    }
  };

  return {
    count,
    multiple,
    increase,
    decrease,
  };
}

使用 hook

<template>
  <p>count: {{ count }}</p>
  <p>倍数: {{ multiple }}</p>
  <div>
    <button @click="increase()">加1</button>
    <button @click="decrease()">减一</button>
  </div>
</template>

<script lang="ts">
import useCount from "../hooks/useCount";
 setup() {
    const { count, multiple, increase, decrease } = useCount(10);
        return {
            count,
            multiple,
            increase,
            decrease,
        };
    },
</script>

组件 teleport 任意门

目的: 即希望继续在组件内部使用 Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中

场景: 弹框

我们可以用<Teleport>包裹 Dialog, 此时就建立了一个传送门,可以将 Dialog 渲染的内容传送到任何指定的地方。使用 teleport 组件,通过 to 属性,指定该组件渲染的位置与 <div id="app"></div> 同级,也就是在 body 下,但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制.

<body>
  <div id="app"></div>
  <div id="dialog"></div>
</body>
// Dialog.vue
<template>
  <teleport to="#dialog">
    <div class="dialog">
      <div class="dialog_wrapper">
        <div class="dialog_header" v-if="title">
          <slot name="header">
            <span>{{ title }}</span>
          </slot>
        </div>
      </div>
      <div class="dialog_content">
        <slot></slot>
      </div>
      <div class="dialog_footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </teleport>
</template>

// Footer.vue 子组件

<div class="footer">
    ...
    <Dialog v-if="dialogVisible"></Dialog>
</div>

组件 异步组件

Vue3 中 使用 defineAsyncComponent 定义异步组件,配置选项 component 替换为 loader ,Loader 函数本身不再接收 resolve 和 reject 参数,且必须返回一个 Promise。

<template>
  <!-- 异步组件的使用 -->
  <AsyncPage />
</template>

<script>
  import { defineAsyncComponent } from "vue";

  export default {
    components: {
      // 无配置项异步组件
      AsyncPage: defineAsyncComponent(() => import("./NextPage.vue")),

      // 有配置项异步组件
      AsyncPageWithOptions: defineAsyncComponent({
        loader: () => import(".NextPage.vue"),
        delay: 200,
        timeout: 3000,
        errorComponent: () => import("./ErrorComponent.vue"),
        loadingComponent: () => import("./LoadingComponent.vue"),
      }),
    },
  };
</script>

参考

  1. vue3 官网 https://v3.vuejs.org/
  2. 构建 vite https://github.com/vitejs/vite
  3. vue3 源码 https://github.com/vuejs/vue-next
  4. vue-cli https://cli.vuejs.org/
  5. vue-router https://github.com/vuejs/vue-router-next
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容

  • 2020 年 9 月,它终于来了,尽管很早就上了 Beta 版本,但是尤大确一直没有推出正式版,如今正式版上线,让...
    橙色流年阅读 1,088评论 1 4
  • 目录 Vue3 的新特性 Vue3 的新特性(二) —— Composition-Api Vue3 的新特性(三)...
    鹤仔z阅读 22,010评论 3 39
  • 为什么要学习vue3呢? vue2.0也是现在比较稳定的一个版本,社区还有周边都比较完善,如果不是非必要其实我们不...
    嘎子同学阅读 8,153评论 1 3
  • 上次将 Composition API 大致梳理了一遍 ,这次主要是想记录一些 vue3 相较 vue2 新增出来...
    橙色流年阅读 3,411评论 0 5
  • 表情是什么,我认为表情就是表现出来的情绪。表情可以传达很多信息。高兴了当然就笑了,难过就哭了。两者是相互影响密不可...
    Persistenc_6aea阅读 124,526评论 2 7