文章目录
Vue UI组件库
移动端常用UI组件库
PC端常用UI组件库
element-ui基本使用
首先下载npm i element-ui
(具体可以在官网的组件–快速上手中查看)
main.js缺点,体积过大
import Vue from 'vue'
//引入ElementUI组件库
import ElementUI from 'element-ui';
//引入ElementUI全部样式
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue'
Vue.config.productionTip = false
Vue.use(ElementUI);
const vm = new Vue({
el: '#app',
render: h => h(App),
})
按需引入
npm install babel-plugin-component -D
引入的时候,缺什么,就下载什么npm i xxx
babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
["@babel/preset-env", { "modules": false }]
],
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
main.js
import Vue from 'vue'
import App from './App.vue'
//按需引入
import { Button, Row } from 'element-ui';
Vue.config.productionTip = false
//创建组件(前面的名字可以修改)
Vue.component("el-button", Button);
Vue.component("el-row", Row);
const vm = new Vue({
el: '#app',
render: h => h(App),
})
App.vue
<template>
<div>
<button>原生的组件</button>
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
Vue3快速上手
1.Vue3简介
- 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
- 耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者
- github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0
2.Vue3带来了什么
1.性能的提升
-
打包大小减少41%
-
初次渲染快55%, 更新渲染快133%
-
内存减少54%
2.源码的升级
-
使用Proxy代替defineProperty实现响应式
-
重写虚拟DOM的实现和Tree-Shaking
3.拥抱TypeScript
- Vue3可以更好的支持TypeScript
4.新的特性
-
Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与inject
- …
-
新的内置组件
- Fragment
- Teleport
- Suspense
-
其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
- …
一、创建Vue3.0工程
1.使用 vue-cli 创建
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue -V查看vue版本
vue --version
## 安装或者升级你的@vue/cli
##npm install -g @vue/cli
## 创建
vue create vue3cli
## 启动
cd vue3cli
npm run serve
2.使用 vite 创建
官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
vite官网:网址
- 什么是vite?—— 新一代前端构建工具。
- 优势如下:
- 开发环境中,无需打包操作,可快速的冷启动。
- 轻量快速的热重载(HMR)。
- 真正的按需编译,不再等待整个应用编译完成。
- 传统构建 与 vite构建对比图
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
首次介绍
main.js
//引入的不再是vue构造函数了,引入的是工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象-app(类似于之前vm,但是app更“轻”)
// createApp(App).mount('#app')
const app=createApp(App)
//挂载
app.mount("#app")
/*
setTimeout(()=>{
app.unmount("#app")
},1000)*/
vue.config.js
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave:false//关闭语法检查
})
下载Vue3开发展工具
链接: https://pan.baidu.com/s/1J_iT_uoNqBM2zQzwQxYgCw?pwd=8jwc 提取码: 8jwc 复制这段内容后打开百度网盘手机App,操作更方便哦
二、常用 Composition API
组合式API
官方文档
#region
#endregion
setup
- 理解:Vue3.0中一个新的配置项,值为一个函数。
- setup是所有Composition API(组合API)“ 表演的舞台 ”。
- 组件中所用到的:数据、方法等等,均要配置在setup中。
- setup函数的两种返回值:
- 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
- 若返回一个渲染函数:则可以自定义渲染内容。(了解)
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
- 但在vue3的setup中不能访问到Vue2.x配置(data、methos、computed…)。
- 如果有重名, setup优先。
- setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)
- 尽量不要与Vue2.x配置混用
<template>
<div>
<h1>我是APP组件</h1>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<h2>性别:{{ sex }}</h2>
<button @click="sayHello">vue3配置---点我说话</button>
<button @click="sayWelcome">vue2配置--点我欢迎</button>
<br>
<br>
<button @click="test">测试在vue2 的配置中去读取vue3 的数据和方法</button>
<button @click="test2">测试在vue3 的配置中去读取vue2 的数据和方法</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
sex: "男",
};
},
methods: {
sayWelcome() {
alert("欢迎美女!");
},
test() {
console.log(this.sex);
console.log(this.name);
console.log(this.age);
console.log(this.sayHello);
},
},
//此处只是测试setup,不考虑响应式问题
setup() {
//数据
let name = "张三";
let age = 18;
// 方法;
function sayHello() {
alert(`我叫${name},我${age}岁了,你好啊!`);
}
function test2() {
console.log(name);
console.log(age);
console.log(this.sex);
console.log(this.sayWelcome);
}
return {
// 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用
name,
age,
sayHello,
test2
};
/*
需要在上面引入
import {h}from 'vue'
return ()=> h('h1','liuyue')
*/
},
};
</script>
2.ref函数
- 作用: 定义一个响应式的数据
- 语法:
const/let xxx = ref(initValue)
const定义的对象是可以修改里面的属性的,比如,const obj={}, obj.a=1允许,但是不允许obj={},不允许等于一个新对象- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- JS中操作数据:
xxx.value
- 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()
的get
与set
完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive
函数。
<template>
<div>
<h1>我是APP组件</h1>
<h2>姓名:{{ name }}</h2>
<h2>年龄:{{ age }}</h2>
<button @click="changeInfo">修改人的信息</button>
<h3>工作种类:{{job.type}}</h3>
<h3>工作薪水:{{job.salary}}</h3>
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "App",
setup() {
//数据
let name = ref("张三");
let age = ref(18); //得到引用实现的实例(引用对象)
let job = ref({
type: "前端工程师",
salary: "30k",
})
function changeInfo() {
name.value = "liuyue";
age.value = 20;
job.value.type="前端";
console.log(job); //查看job是什么
}
return {
name,
age,
changeInfo,
job,
};
},
};
</script>
3.reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref
函数) - 语法:
const 代理对象= reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) - reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
4.Vue3.0中的响应式原理
vue2.x的响应式
-
实现原理:
-
对象类型:通过
Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。 -
数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', { get () {}, set () {} })
-
-
存在问题:
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
- 数据改了,但是vue2没有监测到
this.$set this.$delete
Vue3.0的响应式
- 实现原理:
- 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
- 通过Reflect(反射): 对源对象的属性进行操作。
- MDN文档中描述的Proxy与Reflect:
-
Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
-
Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
new Proxy(data, { // 拦截读取属性值 get (target, prop) { return Reflect.get(target, prop) }, // 拦截设置属性值或添加新属性 set (target, prop, value) { return Reflect.set(target, prop, value) }, // 拦截删除属性 deleteProperty (target, prop) { return Reflect.deleteProperty(target, prop) } }) proxy.name = 'tom'
-
新建一个html文件,在网页中修改删除都可以
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
//原数据
let person={
name:'张三',
age:18
}
//模拟vue3中的响应式,第二个对象可以占位,但是不能不写
//proxy代理对象 reflect反射对象
const p=new Proxy(person,{
//读取时调用
get(target,propName){
console.log(`有人读取了p身上的${propName}属性`);
return Reflect.get(target,propName)
},
//修改或者追加属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`);
Reflect.set(target,propName,value)
},
//删除时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性`);
return Reflect.deleteProperty(target,propName)
}
})
</script>
</body>
</html>
5.reactive对比ref
- 从定义数据角度对比:
- ref用来定义:基本类型数据。
- reactive用来定义:对象(或数组)类型数据。
- 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过
reactive
转为代理对象。
- 从原理角度对比:
- ref通过
Object.defineProperty()
的get
与set
来实现响应式(数据劫持)。 - reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
- ref通过
- 从使用角度对比:
- ref定义的数据:操作数据需要
.value
,读取数据时模板中直接读取不需要.value
。 - reactive定义的数据:操作数据与读取数据:均不需要
.value
。
- ref定义的数据:操作数据需要
“数据劫持: 指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果。 比较典型的是
Object.defineProperty() 和 ES2015 中新增的 Proxy 对象。
6.setup的两个注意点
-
setup执行的时机
在beforeCreate之前执行一次,this是undefined。
-
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
- context:上下文对象
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 就会在attrs中,相当于vue2中的
this.$attrs
。 - slots: 收到的插槽内容, 相当于
this.$slots
。 - emit: 分发自定义事件的函数, 相当于
this.$emit
。
- attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 就会在attrs中,相当于vue2中的
App.vue
<template>
<div>
<demo @hello="showHello" msg="你好啊" school="qut" >
<template v-slot:qwe>
<span>qut</span>
</template>
</demo>
</div>
</template>
<script>
import { ref, reactive } from "vue";
import demo from "./components/demo.vue";
export default {
name: "App",
components: { demo },
setup() {
function showHello(value) {
alert(`你触发了hello事件,我收到的参数是${value}!`);
}
return {
showHello,
};
},
};
</script>
demo.vue
<template>
<h2>姓名 {{ person.name }}</h2>
<h2>年龄{{ person.age }}</h2>
<button @click="test">测试触发一下demo组件的hello事件</button>
</template>
<script>
import { reactive } from "vue";
export default {
name: "demo",
/*beforeCreate(){
console.log("---beforeCreate----");
},*/
props: ["msg", "school"],
emits: ["hello"], //不写有警告
setup(props, context) {
// console.log("---setup----", props);
console.log("---setup----",context);
// console.log("---setup----",context.attrs);//相当于vue2中的$attrs
// console.log("---setup----",context.emit);//触发自定义事件
console.log("---setup----",context.slots);//插槽
let person = reactive({
name: "zhangsan",
age: 18,
});
function test() {
context.emit("hello", 666);
}
return {
person,
test,
};
},
};
</script>
<style>
</style>
7.计算属性与监视
1.computed函数
- 与Vue2.x中computed配置功能一致
<template>
<h2>姓:</h2>
<input type="text" v-model="person.firstName" />
<h2>名:</h2>
<input type="text" v-model="person.lastName" /> <br /><br />
<span>全名:{{ person.fullname }}</span>
<input type="text" v-model="person.fullname" />
</template>
<script>
import { computed, reactive } from "vue";
export default {
name: "demo",
setup() {
let person = reactive({
firstName: "zhang",
lastName: "san",
});
/*计算属性(没有考虑计算属性被修改的情况)
person.fullname=computed(()=>{
return person.firstName+"-"+person.lastName
})*/
person.fullname = computed({
get() {
return person.firstName + "-" + person.lastName;
},
set(value) {
const nameArr = value.split("-");
person.firstName = nameArr[0];
person.lastName = nameArr[1];
},
});
return {
person,
};
},
};
</script>
<style>
</style>
2.watch函数
-
与Vue2.x中watch配置功能一致
-
两个小“坑”:
- 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
- 监视reactive定义的响应式数据中某个属性时:deep配置有效。
<template>
<h2>当前求和为:{{ sum }}</h2>
<button @click="sum++">点我加1</button>
<hr />
<h2>当前的信息是:{{ msg }}</h2>
<button @click="msg += '赵哲'">修改信息</button>
<hr />
<h2>当前的姓名是:{{ person.name }}</h2>
<h2>当前的年龄是:{{ person.age }}</h2>
<h2>当前的薪水是:{{ person.job.j1.salary }}k</h2>
<button @click="person.name += '~'">更改姓名</button>
<button @click="person.age++">更改年龄</button>
<button @click="person.job.j1.salary++">涨薪</button>
</template>
<script>
import { ref,reactive, watch } from "vue";
export default {
name: "demo",
setup() {
let sum = ref(0);
let msg = ref('你好啊,')
let person=reactive({
name:"张三",
age:18,
job:{
j1:{salary:20}
}
})
//情况一:监视ref定义的一个响应式数据(可以传递三个参数)
watch(sum, (newValue, oldValue) => {
console.log("sum变了", newValue, oldValue);
},{immediate:true});
//情况二:监视ref定义的多个响应式数据
watch([sum,msg], (newValue, oldValue) => {
console.log("sum或者msg变了", newValue, oldValue);
},{immediate:true});
/* 情况三:若watch监视的是reactive定义的全部响应式数据,则无法正确获得oldValue!
ref没有这个情况(面试聊天!)
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视(无法关闭)
*/
watch(person,(newValue, oldValue) => {
console.log("person被修改了",newValue, oldValue);
},{immediate:true,deep:false}), //此处的deep配置不再奏效
//情况四:监视reactive定义的响应式数据中的某个属性,需要把监视的函数写成返回值
watch(()=>person.age,(newValue, oldValue)=>{
console.log("person中的age发生变化了",newValue, oldValue);
})
//情况五:监视reactive定义的响应式数据中的某些属性
watch([()=>person.age,()=>person.name],(newValue, oldValue)=>{
console.log("person中的age/name发生变化了",newValue, oldValue);
})
//特殊情况
watch(()=>person.job,(newValue, oldValue)=>{
console.log("person中的job发生变化了",newValue, oldValue);
},{deep:true})//此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
return {
sum,
msg,
person,
};
},
};
</script>
3.watchEffect函数
-
watch的套路是:既要指明监视的属性,也要指明监视的回调。
-
watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
- watchEffect有点像computed:
- 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
8.生命周期
1
-
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy
改名为beforeUnmount
destroyed
改名为unmounted
-
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
App.vue
<template>
<div>
<button @click="isShowDemo=!isShowDemo">切换隐藏/显示</button>
<demo v-if="isShowDemo"/>
</div>
</template>
<script>
import { ref } from "vue";
import demo from "./components/demo.vue";
export default {
name: "App",
components: { demo },
setup(){
let isShowDemo=ref(true)
return {
isShowDemo,
}
},
};
</script>
demo.vue(在components文件夹中)
<template>
<h2>当前求和为:{{ sum }}</h2>
<button @click="sum++">点我加1</button>
<hr />
</template>
<script>
import { ref,onBeforeMount,onMounted } from "vue";
export default {
name: "demo",
setup() {
let sum = ref(0);
//通过组合式API形式使用生命周期钩子(还用和Vue2中的钩子形式一样,但是不要忘记引入)
onMounted(()=>{
console.log("---onMounted---");
})
return {
sum,
};
},
};
</script>
9.自定义hook函数
-
什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API(包括数据,方法钩子…)进行了封装。
-
类似于vue2.x中的mixin。
-
自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。
App.vue使用上面的
Demo.vue
<template>
<h2>当前求和为:{{ sum }}</h2>
<button @click="sum++">点我加1</button>
<hr />
<h2>当前点击鼠标坐标:x:{{ pointer.x }},y:{{ pointer.y }}</h2>
</template>
<script>
import { ref} from "vue";
import usePoint from "../hooks/usePoint"
export default {
name: "demo",
setup() {
let sum = ref(0);
//调用函数
let pointer=usePoint()
return {
sum,
pointer
};
},
};
</script>
<style>
</style>
usePoint.js(新建hooks文件夹)
import { onMounted, onBeforeUnmount, reactive } from "vue";
export default function (){
//实现鼠标"打点" 的数据
let pointer = reactive({
x: 0,
y: 0,
});
//实现鼠标"打点" 的方法 用作下面的回调函数
function savePointer(event) {
pointer.x = event.pageX;
pointer.y = event.pageY;
console.log(event.pageX, event.pageY);
}
//实现鼠标"打点" 的生命周期钩子
onMounted(() => {
window.addEventListener("click",savePointer );
onBeforeUnmount(() => {
window.removeEventListener("click", savePointer);
});
});
return pointer
}
10.toRef
把…变成ref
-
作用:创建一个 ref 对象,其value值指向另一个对象(person)中的某个属性。(把属性拆散了使用)
-
语法:
const name = toRef(person,'name')
-
应用: 要将响应式对象中的某个属性单独提供给外部使用时。
-
扩展:
toRefs
与toRef
功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)
App.vue
<template>
<div>
<button @click="isShowDemo=!isShowDemo">切换隐藏/显示</button>
<demo v-if="isShowDemo"/>
</div>
</template>
<script>
import { ref } from "vue";
import demo from "./components/demo.vue";
export default {
name: "App",
components: { demo },
setup(){
let isShowDemo=ref(true)
return {
isShowDemo,
}
},
};
</script>
demo.vue
<template>
<!-- 通过输出判断,修改的是不是person中的属性 -->
<h2>总体:{{person}}</h2>
<h2>当前的姓名是:{{ name }}</h2>
<h2>当前的年龄是:{{ age }}</h2>
<h2>当前的薪水是:{{ salary }}k</h2>
<button @click="name += '~'">更改姓名</button>
<button @click="age++">更改年龄</button>
<button @click="salary++">涨薪</button>
</template>
<script>
import { ref, reactive, toRef, toRefs } from "vue";
export default {
name: "demo",
setup() {
let person = reactive({
name: "张三",
age: 18,
job: {
j1: { salary: 20 },
},
});
/*
let name2=toRef(person,'name')
console.log(name);
let x=toRefs(person,)
console.log(x);
*/
let x=toRefs(person)
console.log(x);
return {
person,//完全可以交出去,因为person中可能有100个属性,常用的三个用下面这种方法
//第一个参数传一个对象就行
name: toRef(person, "name"),
age: toRef(person, "age"),
salary: toRef(person.job.j1, "salary"),
// name: ref(person.name),这样是不对的,相当于读取出来了name,并且打包成ref,后面修改的内容根本不是person中的内容
...toRefs(person)
};
},
};
</script>
11.其他例子
App.vue
<template>
<div class="app">
<h3>我是APP组件</h3>
<Suspense>
<template v-slot:default>
<Child />
</template>
<template v-slot:fallback>
<!-- 还未加载出来的时候闪现的 -->
<h3>稍等,加载中...</h3>
</template>
</Suspense>
</div>
</template>
<script>
import { reactive } from "vue";
//(静态引入)
// import Child from "./components/Child.vue";
//动态引入(异步引入)
import {defineAsyncComponent} from 'vue'
const Child=defineAsyncComponent(()=>{
return import('./components/Child.vue')
})
export default {
name: "App",
components:{Child}
};
</script>
<style>
.app {
background-color: aqua;
}
</style>
main.js
//引入的不再是vue构造函数了,引入的是工厂函数
import { createApp } from 'vue'
import App from './App.vue'
//创建应用实例对象-app(类似于之前vm,但是app更“轻”)
// createApp(App).mount('#app')
const app=createApp(App)
//挂载
app.mount("#app")
/*
setTimeout(()=>{
app.unmount("#app")
},1000)*/
Child.vue
<template>
<div class="child">
<h3>我是Child组件(子组件)</h3>
{{ sum }}
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "Child",
/*setup(){
let sum=ref(0)
// return {sum}
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve({sum})
}, 1000)
})
},*/
//或者这样写
async setup() {
let sum = ref(0);
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ sum });
}, 1000);
});
return await p;
},
};
</script>
<style>
.child {
background-color: rgb(236, 122, 122);
}
</style>
demo.html其实没啥关系
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
let person={
name:"Zhangsan",
age:18
}
let p=new Proxy(person,{
set(target,propName,value){
console.log(`${propName}被修改了,我要去更新界面了`);
console.log(target,propName,value);
Reflect.set(target,propName,value)
}
})
</script>
</body>
</html>
三、其它 Composition API
1.shallowReactive 与 shallowRef
-
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
-
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
-
什么时候使用?
- 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。
demo.vue
<template>
<h2>当前的x.y是:{{ x.y }}</h2>
<button @click="x.y++">更改X的值</button>
<h2>总体:{{ person }}</h2>
<h2>当前的姓名是:{{ name }}</h2>
<h2>当前的年龄是:{{ age }}</h2>
<h2>当前的薪水是:{{ job.j1.salary }}k</h2>
<button @click="name += '~'">更改姓名</button>
<button @click="age++">更改年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
import { ref, reactive, toRef, toRefs, shallowReactive, shallowRef } from "vue";
export default {
name: "demo",
setup() {
// let person = shallowReactive({
let person = reactive({
name: "张三",
age: 18,
job: {
j1: { salary: 20 },
},
});
let x = ref({//这个地方如果是shallowRef,就没办法更改了
y: 0,
});
//ref的时候,输出的话,如果value是proxy,一定是响应式
return {
x,
person,
...toRefs(person),
};
},
};
</script>
2.readonly 与 shallowReadonly
- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。
<template>
<h2>当前求和是:{{sum }}</h2>
<button @click="sum++">更改X的值</button>
<h2>总体:{{ person }}</h2>
<h2>当前的姓名是:{{ name }}</h2>
<h2>当前的年龄是:{{ age }}</h2>
<h2>当前的薪水是:{{ job.j1.salary }}k</h2>
<button @click="name += '~'">更改姓名</button>
<button @click="age++">更改年龄</button>
<button @click="job.j1.salary++">涨薪</button>
</template>
<script>
import { ref, reactive, toRef, toRefs, shallowReactive, shallowRef, readonly, shallowReadonly } from "vue";
export default {
name: "demo",
setup() {
let person = reactive({
name: "张三",
age: 18,
job: {
j1: { salary: 20 },
},
});
let sum = ref(0)
// person=readonly(person)
sum=readonly(sum)
person=shallowReadonly(person)//浅层次不能改,深层次可以
return {
sum,
person,
...toRefs(person),
};
},
};
</script>
3.toRaw 与 markRaw
- toRaw:
- 作用:将一个由
reactive
生成的响应式对象转为普通对象,ref创作的不行。 - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- 作用:将一个由
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象。
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
demo.vue
<template>
<h2>当前求和是:{{ sum }}</h2>
<button @click="sum++">更改X的值</button>
<h2>总体:{{ person }}</h2>
<h2>当前的姓名是:{{ name }}</h2>
<h2>当前的年龄是:{{ age }}</h2>
<h2>当前的薪水是:{{ job.j1.salary }}k</h2>
<button @click="name += '~'">更改姓名</button>
<button @click="age++">更改年龄</button>
<button @click="job.j1.salary++">涨薪</button>
<button @click="showRawPerson">输出最原始的person</button>
<hr />
<button @click="addCar">给一个人添加车</button>
<h2 v-show="person.car">座驾信息:{{ person.car }}</h2>
<button v-show="person.car" @click="person.car.name += '?'">换车名</button>
<!-- 有车才显示这两个按钮 -->
<button v-show="person.car" @click="person.car.price++">换价格</button>
</template>
<script>
import { ref, reactive, toRefs, toRaw, markRaw } from "vue";
export default {
name: "demo",
setup() {
let sum = ref(0);
let person = reactive({
name: "张三",
age: 18,
job: {
j1: { salary: 20 },
},
// car:{}//解决方法一
});
function showRawPerson() {
const p = toRaw(person);
p.age++; //页面不会发生变化
console.log(p);
}
function addCar() {
let car = { name: "奔驰", price: "40" };
person.car = car;
// person.car = markRaw(car);//加上之后,数据就不能变化了
console.log(person);
}
return {
sum,
person, //把整个响应式对象交出去(所以必须写这句)
...toRefs(person),
showRawPerson,
addCar,
};
},
};
</script>
4.customRef
ref(精装房)customRef(毛坯房)
想要实现,上面的输入框输入的文字发生变化时,下面的输入框间隔一段时间后发生响应的变化
-
作用:创建一个自定义 的 ref,并对其依赖项跟踪和更新触发进行显式控制。
-
实现防抖效果:
App.vue
<template>
<div>
<!-- 上面输入框输入,下面等一秒钟在输入 -->
<input type="text" v-model="keyWord" />
<h3>{{ keyWord }}</h3>
</div>
</template>
<script>
import { ref, customRef } from "vue";
import demo from "./components/demo.vue";
export default {
name: "App",
components: { demo },
setup() {
// 自定义的ref(本质就是一个ref)
function myRef(value, delay) {//value代表初始值,delay延迟的秒数
let timer;
return customRef((track, tragger) => {
return {
get() {
console.log(`有人从myRef中读取数据了,我把${value}给她了`);
track(); //通知vue追踪数据的变化(提前和getter商量一下,让他认为value是有用的)
return value;
},
set(newValue) {
//newValue代表更改后的值
console.log(`有人把myRef中数据更改了:${newValue}`);
clearTimeout(timer); //timer是在全局内定义的,每一次清掉以前的timer(防抖动)
timer = setTimeout(() => {
value = newValue; //改掉之后,get中读取的value就是正确的了
tragger(); //通知vue去重新解析模板
}, delay);
},
};
});
}
// let keyWord = ref("hello");//使用vue提供的ref
let keyWord = myRef("hello",1000); //使用程序员自定义的ref
return {
keyWord,
};
},
};
</script>
5.provide 与 inject
-
作用:实现祖与后代组件间通信
-
套路:父组件有一个
provide
选项来提供数据,后代组件有一个inject
选项来开始使用这些数据
父子之间一般不用
-
具体写法:
-
祖组件中:
setup(){ ...... let car = reactive({name:'奔驰',price:'40万'}) provide('car',car) ...... }
-
后代组件中:
setup(props,context){ ...... const car = inject('car') return {car} ...... }
-
App.vue
<template>
<div class="app">
<h3>我是APP组件(祖先){{name}}---{{price}}</h3>
<Child/>
</div>
</template>
<script>
import { reactive,toRefs,provide } from 'vue'
import Child from './components/Child.vue'
export default {
name:"App",
components:{
Child,
},
setup(){
let car=reactive({
name:"奔驰",
price:"40"
});
//App 给自己的后代组件传递数据
provide('car',car);
return{...toRefs(car)}
}
}
</script>
<style>
.app{
background-color: aqua;
}
</style>
其实没啥用,但是不写的话,就没办法生成孙组件
Child.vue
<template>
<div class="child">
<h3>我是Child组件(子组件)</h3>
<Son/>
</div>
</template>
<script>
import Son from "../components/Son.vue"
export default {
name:"Child",
components:{
Son,
}
}
</script>
<style>
.child{
background-color: rgb(236, 122, 122);
}
</style>
son.vue
<template>
<div class="son">
<h3>我是Son组件(孙){{car.name}}-----{{car.price}}</h3>
</div>
</template>
<script>
import {inject} from'vue'
export default {
name:"Son",
setup(){
let car=inject('car')
// console.log(car,'*******');
return{car}
}
}
</script>
<style>
.son{
background-color: rgb(233, 111, 70);
}
</style>
6.响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
只需APP.vue
<template>
<div class="app">
<h3>我是APP组件{{ name }}---{{ price }}</h3>
</div>
</template>
<script>
import {
reactive,
toRefs,
ref,
readonly,
isRef,
isReactive,
isReadonly,
isProxy,
} from "vue";
export default {
name: "App",
setup() {
let car = reactive({
name: "奔驰",
price: "40",
});
let sum = ref(0);
let car2 = readonly(car);
console.log(isRef(sum));
console.log(isReactive(car));
console.log(isReadonly(car2));
console.log(isProxy(car));
console.log(isProxy(car2)); //确实是修饰了 car,但是返回的依旧是代理的对象,没有变成Object
console.log(isProxy(sum)); //ref底层用的是defineProperty,没有用proxy,所以是false
return { ...toRefs(car) };
},
};
</script>
<style>
.app {
background-color: aqua;
}
</style>
四、Composition API 的优势
1.Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。
2.Composition API 的优势
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
五、新的组件
1.Fragment
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
2.Teleport
- 什么是Teleport?——
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术。
使用provide目录中的APP和Child
son.vue
<template>
<div class="son">
<h3>我是Son组件(孙)</h3>
<Dialog/>
</div>
</template>
<script>
import Dialog from './Dialog.vue'
export default {
name:"Son",
components:{
Dialog,
}
}
</script>
<style>
.son{
background-color: rgb(233, 111, 70);
}
</style>
Dialog.vue
<template>
<div>
<button @click="isShow=true">点我弹个窗</button>
<teleport to="body"> <!-- 在elements控制台可以发现,这个元素是被添加到了和app平级 -->
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<h4>一些内容</h4>
<h4>一些内容</h4>
<h4>一些内容</h4>
<h4>一些内容</h4>
<button @click="isShow=false">关闭弹窗</button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import {ref} from "vue"
export default {
name:"Dialog",
setup(){
let isShow=ref(false)
return{
isShow,
}
}
}
</script>
<style>
.mask{
position: absolute;
top:0;
bottom:0;
left:0;
right:0;
background-color: rgba(0, 0, 0, 0.5);
}
.dialog{
text-align: center;
position: absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%); /* 推回自身的一半 */
background-color: rgb(221, 196, 85);
width: 400px;
}
</style>
3.Suspense
-
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
-
使用步骤:
-
异步引入组件
import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
-
使用
Suspense
包裹组件,并配置好default
与fallback
(如果因为某种原因,展示慢了,显示的内容放在这里面)<template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
-
App.vue
<template>
<div class="app">
<h3>我是APP组件</h3>
<Suspense>
<template v-slot:default>
<Child />
</template>
<template v-slot:fallback>
<!-- 还未加载出来的时候闪现的 -->
<h3>稍等,加载中...</h3>
</template>
</Suspense>
</div>
</template>
<script>
import { reactive } from "vue";
//(静态引入)
// import Child from "./components/Child.vue";
//动态引入(异步引入)
import {defineAsyncComponent} from 'vue'
const Child=defineAsyncComponent(()=>{
return import('./components/Child.vue')
})
export default {
name: "App",
components:{Child}
};
</script>
<style>
.app {
background-color: aqua;
}
</style>
Child.vue
<template>
<div class="child">
<h3>我是Child组件(子组件)</h3>
{{ sum }}
</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "Child",
/*setup(){
let sum=ref(0)
// return {sum}
return new Promise((resolve,reject)=>{
setTimeout(() => {
resolve({sum})
}, 1000)
})
},*/
//或者这样写
async setup() {
let sum = ref(0);
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ sum });
}, 1000);
});
return await p;
},
};
</script>
<style>
.child {
background-color: rgb(236, 122, 122);
}
</style>
六、其他
1.全局API的转移
-
Vue 2.x 有许多全局 API 和配置。
-
例如:注册全局组件、注册全局指令等。
//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
-
-
Vue3.0中对这些API做出了调整:
-
将全局的API,即:
Vue.xxx
调整到应用实例(app
)上2.x 全局 API( Vue
)3.x 实例 API ( app
)Vue.config.xxxx app.config.xxxx Vue.config.productionTip 移除 Vue.component app.component Vue.directive app.directive Vue.mixin app.mixin Vue.use app.use Vue.prototype app.config.globalProperties
-
2.其他改变
-
data选项应始终被声明为一个函数。
-
过度类名的更改:
-
Vue2.x写法
.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
-
Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
-
-
移除keyCode作为 v-on 的修饰符,同时也不再支持
config.keyCodes
-
移除
v-on.native
修饰符-
父组件中绑定事件
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
-
子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
-
-
移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
-
…