VUE3

187 阅读6分钟

创建vue3项目

1. 使用vue-cli创建

npm i @vue-cli -g 
## 需要4.5.0以上
vue -V
## 创建项目
vue create vue3-project

选择第三个自定义安装 1.jpg

2.jpg

3.jpg

2. 使用vite创建

速度快

npm install -g create-vite-app
npm init vite-app vite_study
cd vite_study
npm i
npm run dev

4.jpg

启动成功测试

5.jpg

部分源码分析

main.ts

//程序主入口文件
//引入createApp函数,创建对应的应用给,产生应用的实例对象
import { createApp } from 'vue'
//引入App组件(所有组件的父级组件)
import App from './App.vue'
//创建App应用返回对应的实例对象,调用mount方法进行挂载
createApp(App).mount('#app')//public/index.js

app.vue

项目做eslint检查,需要在设置列关闭vetur

1.jpg

<template>
<!-- vue2中的html模板中必须要有一对根标签,vue3组件的html模板中可以没有根标签 -->
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</template>

<script lang="ts">
//可以使用ts代码
//defineComponent函数,目的是定义一个组件,内部可以传入一个配置对象
import { defineComponent } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
export default defineComponent({
  name: "App",
  components: {
    HelloWorld
  }
});
</script>

CompositionAPI

(1)setup

  • 新的option,所有的组合API都在此函数中使用,只在初始化时执行一次。
  • 函数如果返回对象,对象中的属性或方法,模板中可以直接使用。

(2)ref

  • 作用:是一个函数,定义一个响应式数据。
  • 返回的是一个Ref对象,调用.value属性操作数据。html模板中不需要使用.value
<template>
  <div>{{count}}</div>
  <div @click="add">add</div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "App",
  setup() {
    const count = ref(0);
    function add() {
    
      count.value++;
    }
    return {
      count,
      add
    };
  }
});
</script>

(3)reactive

  • 作用:把复杂的数据变为响应式。
  • 接受一个普通对象返回一个代理对象。
  • 基于ES6 proxy实现,只能通过代理对象操作源对象内部数据都是响应式。
  • 如果操作代理对象,目标对象中的数据也会随之变化。
  • 操作代理对象实现响应式。
<template>
  <div>{{user}}</div>
  <div @click="add">add</div>
</template>

<script lang="ts">
import { defineComponent, reactive } from "vue";

export default defineComponent({
  name: "App",
  setup() {
    //目标对象
    const obj = {
      name: "aaa",
      age: 15
    };
    //代理对象proxy
    const user = reactive(obj);
    function add() {
      //只能使用代理对象的方式来更新数据
      user.name = "bbb";
    }
    return {
      user,
      add
    };
  }
});
</script>

(4)比较vue2与vue3响应式

vue2响应式
  • 核心:
    • 对象:通过defineProperty对对象的已有属性的读取和修改进行劫持
    • 数组:通过重写数组更新
  • 问题:
    • 对象直接添加的属性或删除已有属性,界面不会自动更新。
    • 直接通过下标替换元素或更新length,界面不会自动更新。
vue3响应式
  • 核心:
    • 通过proxy(代理):拦截对data任意属性的任意操作。
    • 通过reflect(反射):动态对被代理对象的相应属性进行特定的操作。
<script type="text/javascript">
//目标对象
const user = {
  name: "aaa",
  age: 23,
  wife: {
    name: "bbb",
    age: 19
  }
};
//目标对象转成代理对象
// 参数1:user->target目标对象
// 参数2:handler->处理器对象,用来监视数据,及数据操作
const proxyUser = new Proxy(user, {
  get(target, prop) {
    //需要反射出该对象
    return Reflect.get(target, prop);
  },
  //修改,添加目标对象的属性值
  set(target, prop, val) {
    //目标对象,属性,值
    return Reflect.set(target, prop, val);
  },
  //删除目标对象上的某个属性
  deleteProperty(target, prop) {
    return Reflect.deleteProperty(target, prop);
  }
});
//通过代理对象更新目标对象上的某个属性值
proxyUser.name = "ccc";
//通过代理对象向目标对象上添加的某个属性值
proxyUser.gender = "男";
delete proxyUser.name;
//深度监听
proxyUser.wife.name = "ddd";
console.log(proxyUser);
</script>

(5)setup细节

  • setup执行的时机
    1. 在beforeCreated之前调用一次,此时组件对象没有创建。
    2. this是undefined,不能通过this来访问data/computed/methods/ props中的相关内容。
  • setup的返回值
    1. setup中的返回值是一个对象,内部的属性和方法是给html模板使用的。
    2. setup中的对象会与data函数中的对象中的属性会合并为组件对象的属性。
    3. setup中的方法会与data函数中的对象中的方法会合并为组件对象的方法。
    4. 如有重名setup优先。
    5. 尽量不要混合使用:method中可以访问setup提供的属性和方法(可以使用this访问), 在setup中不能访问data和method定义的属性和方法(不可以使用this)
    6. setup返回值是promise对象,模板看不到return对象中的属性数据。
  • setup的参数
    1. props参数:父向子传递的数据
    2. context参数:是一个对象,有attrs,emit,slots

(6)reactive ref的细节

  1. ref可以传入对象或数组,经过reactive的处理,形成了一个proxy对象。
  2. ref内部:通过给value属性调价getter/setter来实现对数据的劫持。
  3. reactive内部:通过使用proxy来实现对对象内部所有数据的劫持,并通过Reflect操作对象内部数据。
  4. ref的数据操作:在js中需要.value调用,模板中直接使用。

(7)计算属性和监视

  1. 计算属性:computed
  2. 监视:watch可以监视多个数据 watchEffect,不需要配置immediate,本身默认就会执行监视。

(8)生命周期钩子函数

  • 与2.X版本生命周期对应的组合式API
  • beforeCreate -> setup()
  • created -> setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdated -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeDestroy
  • destoryed -> onUnmounted

(9)自定义hook

类似于mixins

useMousePosition.ts

import { ref, onMounted, onBeforeUnmount } from "vue";
//自定义hook

export default function () {
  const x = ref(-1);
  const y = ref(-1);
  const clickHandler = (event: MouseEvent) => {
    x.value = event.pageX;
    y.value = event.pageY;
  }
  //页面加载完毕的生命周期组合API
  onMounted(() => {
    window.addEventListener("click", clickHandler);
  })
  //页面卸载之前的生命周期组合API
  onBeforeUnmount(() => {
    window.removeEventListener("click", clickHandler);
  })
  return {
    x,
    y
  }

}

app.vue

<template>
  <h2>x:{{x}},y:{{y}}</h2>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import useMousePosition from "./hooks/useMousePosition";
//自定义hook

export default defineComponent({
  name: "App",
  setup() {
    const { x, y } = useMousePosition();
    return {
      x,
      y
    };
  }
});
</script> 

(10) toRefs的使用

<template>
  <h2>{{name}}</h2>
  <h2>{{age}}</h2>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";

//自定义hook

export default defineComponent({
  name: "App",
  setup() {
    const state = reactive({
      name: "test", 
      age: 34
    });
    //toRefs可以把响应式对象转化成一个普通对象,该普通对象的每个property都是一个ref
    const state2 = toRefs(state);
    setInterval(() => {
      // state.name += "123";
      state2.name.value += "123";
    }, 2000);
    return {
      ...state2
    };
  }
});
</script> 

(11)可以利用ref获取元素

利用ref函数获取组件中的标签元素

<template>
  <input type="text" ref="inputRef" />
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from "vue";

export default defineComponent({
  name: "App",
  setup() {
    //默认是空的,页面加载完毕说明组件已经存在了,获取文本框元素
    const inputRef = ref<HTMLElement | null>(null);
    onMounted(() => {
      inputRef.value && inputRef.value.focus();
    });
    return {
      inputRef
    };
  }
});
</script> 

CompositionAPI(其他部分)

(1)shallowReactive 与 shallowRef

  • shallowReactive:只处理了对象内最外层地 响应式(浅响应式)
  • shallowRef:只处理了vaule的响应式,不进行对象的reactive处理
  • 什么时候使用浅响应式
    • 一般情况下不使用
    • 如果有一个对象数据,只改变对象外层数据使用----->shallowReactive
    • 如果有一个对象数据,后面会产生一个新的对象进行替换----->shallowRef

(2)readOnly 与 shallowReadOnly

  • shallowReadOnly 浅只读 可修改外层
  • readOnly 只读

(3)toRaw 与 markRaw

  • toRaw: 将代理对象变成普通对象,数据变化不会响应式。
  • markRaw:标记的对象数据,再也不会变为代理对象。

(4) toRef的特点及使用

<template>
  <h2>param:{{param}}</h2>
  <h2>nameRef:{{nameRef}}</h2>
  <h2>nameToRef:{{nameToRef}}</h2>
  <button @click="update">更新</button>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, toRef } from "vue";
export default defineComponent({
  name: "App",
  setup() {
    const param = reactive({
      name: "eee",
      age: 1
    });
    //把响应式数据对象param的name属性使用ref包装成ref对象
    const nameRef = ref(param.name);
    // 把响应式数据对象param的name属性包装成ref对象
    const nameToRef = toRef(param, "name");
    const update = () => {
      param.name += "1";
      // nameToRef.value += "1";//param中的name同时会发生变化
      // nameRef.value += "1"; //param中的name不会发生变化
    };
    return {
      param,
      nameRef,
      nameToRef,
      update
    };
  }
});
</script> 

(5) customRef

用于自定义一个ref,可以显式地控制依赖追踪和触发响应,接受一个工厂函数,两个参数分别是用于追踪的track,与用于触发响应的trigger,并返回一个带有getset属性的对象。

  • 使用自定义ref实现带防抖功能的v-model
<template>
  <input v-model="name" />
  <div>{{name}}</div>
</template>

<script lang="ts">
import { defineComponent, ref, customRef } from "vue";
//自定义防抖hook函数
function useCustomRef(delay = 200, value: any) {
  //返回一个工厂函数参数track,trigger
  return customRef((track, trigger) => {
    return {
      //返回数据
      get() {
        //告诉vue追踪数据
        track();
        return value;
      },
      //设置数据
      set(newValue) {
        setTimeout(() => {
          value = newValue;
          //告诉vue更新画面
          trigger();
        }, delay);
      }
    };
  });
}
export default defineComponent({
  name: "App",
  setup() {
    // const name = ref("aaa");
    const name = useCustomRef(500, "aaa");
    return {
      useCustomRef,
      name,
      ref
    };
  }
});
</script> 

(6) provide与inject

  • provide和inject提供依赖注入,功能类似vue2
  • 实现跨层级组件(祖孙)间通信

(7) 响应数据的判断

  • isRef :检查一个值是否为一个ref对象
  • isReactive:检查一个对象是否由reactive创建的响应式代理
  • isReadonly:检查一个对象是否由readonly创建的只读代理
  • isProxy:检查一个对象是否由reactive或者ref创建的响应式代理

简单举个栗子

setup() {
  const isRefObj = ref("aaa");
   console.log(isRef(isRefObj));//true
}

新组件

(1)Fragment(片段)

  • 在vue2中:组件必须有一个根标签
  • 在vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
  • 好处:减少标签层级,减少内存占用。

(2)Teleport(瞬移)

  • Teleport提供了一种干净的方法,让组件的html在父组件界面外的特定标签(很可能是body)下插入显示

child.vue

<template>
  <botton @click="showDialog = true">点击显示弹窗</botton>
  <div v-if="showDialog">
    <div>弹窗内容。。。。</div>
    <botton @click="showDialog = false">关闭弹窗</botton>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "childDialog",
  setup() {
    const showDialog = ref(false);
    return {
      showDialog
    };
  }
});
</script> 

app.vue

<template>
  <h2>这是父组件</h2>
  <child-dialog></child-dialog>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import childDialog from "@/components/child.vue";

export default defineComponent({
  name: "App",
  components: {
    childDialog
  },
  setup() {
    return {};
  }
});
</script> 

展示效果 注意dom 截图未命名.jpg

使用Teleport后效果 child.vue

<template>
  <botton @click="showDialog = true">点击显示弹窗</botton>
  <Teleport to="body">
    <div v-if="showDialog">
      <div>弹窗内容。。。。</div>
      <botton @click="showDialog = false">关闭弹窗</botton>
    </div>
  </Teleport>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  name: "childDialog",
  setup() {
    const showDialog = ref(false);
    return {
      showDialog
    };
  }
});
</script> 

展示效果 注意dom

截图未命名1.jpg

(3)Suspense(不确定的)

  • 允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验 child.vue
<template>
  <h3>AsyncComponent子组件</h3>
  <h3>{{msg}}</h3>
</template> 

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "AsyncComponent",
  setup() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve({
          msg: "异步显示msg"
        });
      }, 2000);
    });
  }
});
</script> 

app.vue

<template>
  <h2>这是父组件:Suspense组件使用</h2>
  <Suspense>
    <!-- v-slot="default 简写 #default" -->
    <template #default>
      <!-- 异步组件 -->
      <AsyncComponent />
    </template>
    <template #fallback>
      <!-- loading内容 -->
      <h2>Loading内容</h2>
    </template>
  </Suspense>
</template>

<script lang="ts">
import { defineComponent, defineAsyncComponent } from "vue";
//静态引入组件
// import AsyncComponent from "@/components/child.vue";
//vue2动态引入(vue3中这种写法不可以)
// const AsyncComponent = () => import("./components/child.vue");
//vue3动态引入
const AsyncComponent = defineAsyncComponent(() =>
  import("./components/child.vue")
);
export default defineComponent({
  name: "App",
  components: {
    AsyncComponent
  },
  setup() {
    return {};
  }
});
</script> 

显示效果

截图未命名1.jpg

截图未命名2.jpg

感谢

尚硅谷 24kcs.github.io/vue3_study