系统性学习vue-vue3

系统性学习vue-vue3

Vue3简介

代号:One Piece

支持Typescript

创建Vue3.0工程

使用vue-cli创建

  1. 保证目前的vue-cli的版本在4.5.0以上
  2. 在对应目录下创建项目


    创建期间报错了,看输出可能是node版本太低了
    Node下载官网

还是报错,去掉淘宝镜像npm config set registry https://registry.npmjs.org/可以了= =

  1. 运行 √

使用vite创建工程

Vite官网

(上一个项目就是这种)

优势:

  • 开发环境中,无需打包操作。可快速的冷启动
  • 轻量快速的热重载(HMR)
  • 真正的按需编译,不再等待整个应用编译完成
  1. 创建工程
    npm init vite-app <project_name>
  2. 安装依赖
    npm i
  3. 运行
    npm run dev

分析工程结构(cli创建的)

main.js

对比以前写法,可以看到相似性

但是如果使用原来写法,报错,根本引入不来Vue

js 复制代码
// 引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
import { createApp } from "vue";
import App from "./App.vue";

// createApp(App)-创建应用实例对象(类似于vue2中的vm,但比vm更"轻")
// .mount("#app")-挂载
createApp(App).mount("#app");

// vue2的写法
// new Vue({
//   render: (h) => h(App),
// }).$mount("#app");

App.vue

组件中的模板结构可以没有根标签

其他就没有什么区别了

html 复制代码
<template>
  <!-- 组件中的模板结构可以没有根标签 -->
  <img alt="Vue logo" src="./assets/logo.png" />
  <HelloWorld msg="Welcome to Your Vue.js App" />
</template>

安装开发者工具

运行项目到Chrome,看到之前安装的开发者工具没有亮起

因为版本不对,需要重新下载安装支持Vue3的

直接去Chrome商店下载

哎~没梯子

没事还有第二种方法-离线下载

发现一个宝藏网站

安装完,亮起!

初识setup

  • 是Vue3中一个新的配置项,值为一个函数
  • setup是所有Composition API(组合API)"表演的舞台"
  • 组件中所有用到的:数据、方法等等,均要配置在setup中
  • setup函数的两种返回值:
  1. 若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用(常用)
html 复制代码
<template>
  <h1>信息:{{ name }}-{{ age }}</h1>
  <button @click="sayHello">打招呼</button>
</template>

<script>
export default {
  name: "App",
  setup() {
    // 数据
    let name = "Qiu";
    let age = 18;

    // 方法
    function sayHello() {
      alert("Hello! " + name);
    }

    // 返回对象
    return {
      name,
      age,
      sayHello,
    };
  },
};
</script>

2)若返回一个渲染函数,则可以自定义渲染内容

html 复制代码
<script>
import { h } from "vue";
export default {
  name: "App",
  setup() {
    // 返回渲染函数
    return () => h("h1", "Hello World!");
  },
};
</script>

注意点:

  1. 是向下兼容的,也可以用data,methods等Vue2配置,但不要混用vue2和vue3配置,因为在setup中不能读取到data或methods中数据,且setup优先级要高
  2. setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的数据

ref函数

此ref非之前学的ref属性

之前写的代码展示目前没有问题,但是数据并没有响应式

要想实现响应式,需要将数据用ref包装

js 复制代码
import { ref } from "vue";

处理基本类型

js 复制代码
// 数据
let name = ref("Qiu");
let age = ref(18);

在函数中控制台输出name

RefImpl(reference implement)引用实现对象

其中setter和getter放在了原型对象中,可以类似理解为vm中的_data

修改数据就要通过value改

js 复制代码
function changeInfo() {
  name.value = "CAI";
  age.value = "19";
}

而模板中不需要.value因为解析时会自动提取RefImpl对象的value

处理对象类型

js 复制代码
let school = ref({
  name: "TUST",
  add: "TJ",
});

输出school.value,看到并不是RefImpl对象,而是Proxy对象

因此修改对象数据应该school.value.name = "TJKJDX";

reactive函数

作用:定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
const 代理对象 = reactive(源对象)

reactive定义的响应式数据时"深层次的"(无论有几层都能定义到)

内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作

js 复制代码
import { ref, reactive } from "vue";
//......
let school = reactive({ 
  name: "TUST",
  add: "TJ",
});

输出school,直接就是Proxy对象

修改数据就相比要简单school.name = "TJKJDX";

由此可见,ref函数碰到对象时,也会用reactive将对象包装为Proxy

reactive也可以处理数组对象

回顾Vue2的响应式原理

  • 实现原理:
    对象类型:通过Object.definedProperty()对属性的读取、修改进行拦截(数据劫持)
    数组类型:通过重写更新数组的一系列方法来实现拦截
  • 存在问题:
    1)新增属性、删除属性,界面不会更新
    2)直接通过下标修改数组,界面不会自动更新

vue3响应式原理---Proxy

试验一下看看vue3会有上面的问题么

html 复制代码
<h1>信息:{{ school.name }}-{{ school.add }}-{{ school.age }}</h1>
<button @click="changeInfo">修改信息</button>
js 复制代码
setup() {
  //数据
  let school = reactive({
    name: "TUST",
    add: "TJ",
  });
  function changeInfo() {
    school.age = 60;
  }

  // 返回对象
  return {
    school,
    changeInfo,
  };
},

直接看结果

所以vue3新增属性是响应式的

模拟Vue3的响应式

js 复制代码
let person = {
  name:"Qiu",
    age:18
}
// 模拟vue3中实现响应式
const p = new Proxy(person,{
    get(target,propName){
    console.log("捕获到获取了",propName);
        return target[propName];
    },
    set(target,propName,value){
        console.log(`捕获到修改了${propName},修改为${value}`);
        target[propName] = value;
    },
    // 添加了属性删除回调
    deleteProperty(target,propName){
        console.log("捕获到删除了",propName);
        return delete target[propName];
    }
});

对数据进行增删改查,可以看到都是可以进行响应式的

但其实真正vue3中并不是target[propName] = value;直接对源数据进行操作

而是使用window上的一个属性Reflect

js 复制代码
// 模拟vue3中实现响应式
constp = new Proxy(person,{
    // 读取属性回调
    get(target,propName){
    console.log("捕获到获取了",propName);
        // return person[propName];
        return Reflect.get(target,propName);
    },
    // 修改、添加属性回调
    set(target,propName,value){
        console.log(`捕获到修改了${propName},修改为${value}`);
        // person[propName] = value;
        return Reflect.set(target,propName,value);
    },
    // !添加了属性删除回调
    deleteProperty(target,propName){
        console.log("捕获到删除了",propName);
        // return delete target[propName];
        return Reflect.deleteProperty(target,propName);
    }
});

reactive对比ref

从定义数据角度对比:

  • ref用来定义:基本类型数据
  • reactive用来定义:对象或数组数据类型
  • 备注:ref也可以用来定义对象或数组数据类型,它内部会自定通过reactive转为代理对象

从原理角度对比:

  • ref通过Object.definedProperty()的get与set来实现响应式(数据劫持)
  • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据

从使用角度对比:

  • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
  • reactive定义的数据:操作数据与读取数据-均不需要.value

setup的两个注意点

setup的执行时机:在beforeCreate之前 执行一次,this是undefined

setup的参数:

  • props:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于vue2中的this.$atters
  • slots:收到的插槽内容,相当于vue2中的this.$slot
  • emit:分发自定义事件的函数,相当于vue2中的this.$emit

注意:vue3中建议插槽内容template使用v-slot:xxx,因为可能会有兼容性问题

计算属性与监视

computed函数

与vue2中的computed功能配置一致

js 复制代码
import { ref, reactive, computed } from "vue";
//....
// 计算属性
let fullName = computed(() => {
  return person.firstName + person.lastName;
});
// 完整写法(考虑读和写)
let fullName = computed({
  get() {
    return person.firstName + "-" + person.lastName;
  },
  set(value) {
    const nameArr = value.split("-");
    person.firstName = nameArr[0];
    person.lastName = nameArr[1];
  },
});
//....

watch监视

总结一下下面这些情况:

主要是看watch函数的第一个参数类型

  • 如果是RefImpl类型的,即用ref定义的数据,默认不开启深度监视(也没必要开启),监视value值内容
  • 如果是Proxy类型对象,即用reactive定义的数据,就会强制开启深度监视,监视所有属性值(但oldValue没有用)
  • 如果是函数或函数数组,返回某一个数据中的属性,默认不开启深度监视,监视当前返回属性,如为object需开启深度监视

所以当我们监视一个用ref定义的object时,需要.value或者改为深度监视(细品)

有六种情况

js 复制代码
// 情况一:监视ref所定义的一个响应式数据
watch(sum, (newValue, oldValue) => {
  console.log("sum变化了", newValue, oldValue);
});
// 完整写法
// deep无效(看情况三)
watch(
  sum,
  (newValue, oldValue) => {
    console.log("sum变化了", newValue, oldValue);
  },
  { immediate: true, deep: true }
);

// 情况二:监视ref所定义的多个响应式数据
// newValue:[Object]
watch([sum, msg], (newValue, oldValue) => {
  console.log("监视到变化了", newValue, oldValue);
});

// 情况三:监视reactive所定义的一个响应式数据的全部属性
// 强制开启了深度监视,deep配置无效
// 有个坑:此处无法正确获取oldValue
watch(person, (newValue, oldValue) => {
  console.log("person变化了", newValue, oldValue);
});

// 情况四:监视reactive所定义的一个响应式数据的某一个属性
watch(
  () => person.age,
  (newValue, oldValue) => {
    console.log("person.age变化了", newValue, oldValue);
  }
);
// 情况五:监视reactive所定义的一个响应式数据的某些属性
watch([() => person.age, () => person.name], (newValue, oldValue) => {
  console.log("person某些属性变化了", newValue, oldValue);
});

// 特殊情况(person.job:Object)
// 又一坑:监视数据中的Object属性,要加上deep,什么鬼
watch(
  () => person.job,
  (newValue, oldValue) => {
    console.log("person.age变化了", newValue, oldValue);
  },
  { deep: true }
);

watchEffect函数

不用指定监视哪个属性,只要回调函数中使用到的数据发生变化,就直接重新执行回调函数,默认开启immediate(好智能~)

js 复制代码
// school.name发生变化时,回调执行
watchEffect(() => {
  console.log("===watchEffect===", school.name);
});

Vue3生命周期

和Vue2区别的地方:

  1. Vue2中是在created之后检测是否有el配置,而Vue3是在一开始就检测配置是否完整
  2. 将beforeDestory和destoryed更名为beforeUnmount和ummounted

使用组合式API使用生命周期钩子,即写在setup中(需要引入)(麻烦, 一般不用)

优先级要比配置项钩子高
官网文档

  • beforeCreate ======> setup()
  • created ==========> setup()
  • beforeMount =======> onBeforeMount
  • mounted ==========> onMounted
  • beforeUpdate ======> onBeforeUpdate
  • updated ==========> onUpdated
  • beforeUnmount =====> onBeforeUnmount
  • unmounted ========> onUnmounted

自定义hook

本质是一个函数,把setup函数中使用的Composition API进行了封装

类似于混合 mixin

js 复制代码
/**
 * useMousePos.js
 * 鼠标点击位置功能相关
 */
import { reactive, onMounted, onBeforeUnmount } from "vue";
export default function () {
  let pos = reactive({ x: 0, y: 0 });
  function getMousePos(event) {
    pos.x = event.pageX;
    pos.y = event.pageY;
  }

  onMounted(() => {
    window.addEventListener("click", getMousePos);
  });
  onBeforeUnmount(() => {
    window.removeEventListener("click", getMousePos);
  });

  return pos;
}

这样就可以直接引用使用了

js 复制代码
import useMousePos from "./hooks/useMousePos";
setup() {
    // ...
    let pos = useMousePos();
    //...
}

toRef与toRefs

作用:创建一个ref对象,其value值指向另一个对象中的某个属性

语法:const name = toRef(person, name)

应用:要将响应式对象中的某个属性单独提供给外部使用

扩展:toRefstoRef功能一直,但可以批量创建多个ref对象

当我们希望在模板中直接使用某个变量时

html 复制代码
<h1>学校名称:{{ mySchoolName }}</h1>
js 复制代码
setup() {
	let school = reactive({
      name: "TUST",
      add: "TJ",
      teacher: {
        name: "LIU",
        age: "40",
      },
    });
	return {
	    mySchoolName: school.name,
	};
}

可以看到mySchoolName仅仅是一个字符串数据,并没有响应式,修改school中的数据mySchoolName并不会改变

那用ref包装呢

html 复制代码
mySchoolName: ref(school.name),

确实可以变成ref类型,相当于重新定义了一个ref类型对象,与school.name并不是同一个引用,数据还是不互通的

这就要用到toRef 了,这样数据就与school.name为同一引用了
mySchoolName: toRef(school, "name"),

如果有多个这样的需求,就用toRefs

html 复制代码
return {
  mySchoolName: toRef(school, "name"),
  ...toRefs(school),
};

会返回所有属性改为ref后的对象集合

以上记录的组合式都是比较常用的

shallowReactive与shallowRef

shallowReactive:浅层次的响应式

这样teacher内数据不会是响应式的

js 复制代码
let school = shallowReactive({
  name: "TUST",
  add: "TJ",
  teacher: {
    name: "LIU",
    age: "40",
  },
});

shallowRef:只处理基本数据类型的响应式,不进行对象内部的响应式处理

shallowRef传入基本类型与ref没有区别

传入对象类型,就不会在内部使用reactive

用于性能优化(自己悟吧~)

readonly与shallowReadonly

用readonly包装的响应式所有数据都不允许修改(深只读)

用shallowReadonly包装的响应式只会控制浅层的数据不进行修改(浅只读)

toRaw与markRaw

toRaw与reactive是反向操作,会将Proxy响应式对象还原为普通对象(RefImpl不行)

当向Proxy响应式对象追加一个深层次的数据,且并不需要后续修改,如果正常追加,就也会成为响应式,没必要

如下,使用markRow包裹的对象就不会成为响应式,添加的car就不会成为响应式数据

js 复制代码
let car = {name:"大众",price:20}
person.car = markRow(car)

customRef

贴个完整代码,比较难理解,

应该是为了暴露出getter和setter,方便进行自定义操作

修改数据时工作流程:

value = newValue 修改value值为更改后数据

② 走get()获取最新数据(需要添加track())

trigger()重新解析模板

html 复制代码
<template>
  <input type="text" v-model="keyWord" />
  <h4>{{ keyWord }}</h4>
</template>

<script>
import { ref, customRef } from "vue";
export default {
  name: "CustomRefDemo",
  setup() {
    // let keyWord = ref(""); // 精装房
    let keyWord = myRef(""); // 毛坯房 需要自己根据自己需求装修

    // 自定义ref
    function myRef(value) {
      return customRef((track, trigger) => {
        return {
          get() {
            console.log("从myRef中读取了数据");
            track(); // 通知vue追踪value数据改变
            return value;
          },
          set(newValue) {
            console.log("myRef中数据修改了");
            value = newValue; // 修改数据
            trigger(); //通知vue重新解析模板
          },
        };
      });
    }

    return {
      keyWord,
    };
  },
};
</script>

<style></style>

provide与inject

作用:实现祖组件及其后代组件间通信(就不用逐层传了)

祖组件有一个provide选项来提供数据,孙组件有一个inject选项来开始使用这些数据
其实不止孙组件,所有后代组件都可以用inject获取数据

js 复制代码
// 祖组件
provide("data",data);
js 复制代码
// 后代组件
let data = inject("data");

响应式数据的判断

isRef()
isReactive()
isReadonly()
isProxy() -- 检查是否为reactive或readyonly包装的对象

Composition API的优势

  • Vue2中使用的是OpitionsAPI(配置API),即将需求代码放在data,methods等中,那实现一个功能的数据、方法等都要拆分放到这些配置项中,会比较混乱
  • Vue3的组合式API就可以将每个功能的相关放到一起

新组件

Fragment

在Vue2中:组件必须有一个根标签

而在Vue3中组件可以没有根标签,其实内部是将多个标签包含在一个Fragment虚拟元素中

优点:减少标签层级,减小内存占用

Teleport

如图,在Son组件中使用了Dialog组件来创建弹窗,但是希望是按钮显示在Son组件中 而弹窗显示在外部

就需要在Dialog组件中使用teleport传送组件,将需要传送出去的结构包裹在teleport标签中,并指定要传送的位置

html 复制代码
<template>
  <div>
    <button @click="isShow = true">点我弹个窗</button>
    <teleport to="body">
      <div v-show="isShow" class="dialog">
        <h3>我是一个弹窗</h3>
        <h4>balabalabalabalabalabalbala</h4>
        <button @click="isShow = false">关闭弹窗</button>
      </div>
    </teleport>
  </div>
</template>


Suspense

一般我们引入组件import Child from "./components/Child.vue"; 静态引入组件 ,这样引入父组件与子组件是互相等待同时展示出来的

另一种引入是动态/异步引入组件

js 复制代码
import {defineAsyncComponent} from "vue";
const Child = defineAsyncComponent(() => import("./components/Child.vue"));

这样就是谁先准备好了就先展示谁

但是这样也会有一个问题

当页面上只展示出来父组件,而子组件还在加载中时,则完全不知道会有子组件存在,而导致忽略一些东西

这就需要使用Supense组件,将动态引入的组件包裹

html 复制代码
<div class="app">
  <h3>我是APP组件</h3>
  <Suspense>
    <template v-slot:default>
      <Child></Child>
    </template>
    <template v-slot:fallback>
      <h3>稍等。。。加载中。。。</h3>
    </template>
  </Suspense>
</div>



当使用异步引用和suspend组合技时,setup中return的就可以是Promise实例对象

其他

全局API的转移

vue2中我们需要注册全局组件和注册全局指令是使用全局API去配置

js 复制代码
// 全局注册组件
Vue.component("comp",comp);
// 全局注册指令
Vue.directive('focus',{
	inserted: el => el.focus()
})

而Vue3中对这类API做了调整,将Vue.xxx调整为app.xxx

有一个需要注意的是原来的Vue.config.productionTip在Vue3中已经移除

还有一个特殊的Vue.prototype调整为app.config.globalProperties

其他改变

  • data选项必须被声明为一个函数
  • 过渡类型的更改(就是加了个-from)
css 复制代码
// vue2
.trans-enter,
.trans-leave-to {
  transform: translateX(-100%);
}

.trans-enter-to,
.trans-leave {
  transform: translateX(0);
}
css 复制代码
//vue3
.trans-enter-from,
.trans-leave-to {
  transform: translateX(-100%);
}

.trans-enter-to,
.trans-leave-from {
  transform: translateX(0);
}
  • 移除了keyCode作为v-on的修饰符,同时也不再支持config.keyCodes(自定义别名按键)
  • 移除v-on.native修饰符(Vue3中通过不声明事件指定为原生事件)
  • 移除过滤器 filter (建议使用计算属性或方法调用)
  • ...

完结撒花~

相关推荐
四喜花露水1 小时前
Vue 自定义icon组件封装SVG图标
前端·javascript·vue.js
怀旧6661 小时前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
infiniteWei2 小时前
【Lucene】原理学习路线
学习·搜索引擎·全文检索·lucene
follycat2 小时前
[极客大挑战 2019]PHP 1
开发语言·学习·网络安全·php
程序员爱技术5 小时前
Vue 2 + JavaScript + vue-count-to 集成案例
前端·javascript·vue.js
并不会6 小时前
常见 CSS 选择器用法
前端·css·学习·html·前端开发·css选择器
龙鸣丿6 小时前
Linux基础学习笔记
linux·笔记·学习
Nu11PointerException8 小时前
JAVA笔记 | ResponseBodyEmitter等异步流式接口快速学习
笔记·学习
cs_dn_Jie10 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic10 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js