一款框架诞生需要的阶段
查看版本: npm view vue versions
- 开发
- alpha版:内部测试版 α
- beta版:公开测试版 β
- rc版:Release Candidate(候选版本)
- stable版:稳定版
npm i vue@lasted
:下载的是vue2
- vue2
- vuex3
- vue-router3
- element-ui 2
npm i vue@next
:下载的是vue3
- vue3
- vuex4
- vue-router4
- element-plus 1
npm i vuex@next vue-router@next element-plus
vue3的安装使用
- 一、安装vue3:
npm i vue@next
- 二、安装vue3版本的项目
- 三、安装vue-router、vuex
- 四、安装less@3、less-loader@7
- 五、配置vue.config.js,与vue2相同
vue3与vue2的十大区别
- 版本号不同
- 引用的vuex/vue-router版本不同
- vue3的APP.vue的《template》 中支持多个根节点
区别一:==vue3中推崇使用函数式编程==:各种创建实例,都调用函数来创建,我们从vue中解构出各种各样的函数来使用
区别二.Performance [pəˈfɔːməns]
- 重写了虚拟DOM的实现(跳过静态节点,只处理动态节点)
- update性能提高1.3~2倍
- SSR速度提高了2~3倍
区别三:Tree shaking [ˈʃeɪkɪŋ]
可以将无用模块“剪辑”,仅打包需要的)【webpack新版本的】
区别四 Fragment ['frægmənt]
不再限于模板中的单个根节点[文档碎片]
区别五 <Teleport> [ˈtelɪpɔːt]
以前称为<Portal> [ˈpɔːtl]
,译作传送门,可以把组件内部的部分内容挂载到除#app
的容器中(<Teleport to="#fotter">
),
<div id="app"></div>
<div id="footer"></div>
<Teleport to="#footer"> 你好,世界</Teleport>
区别六<Suspense> [səˈspens]
可在嵌套层级中等待嵌套的异步依赖项,嵌套一个异步组件,可以在获取到数据之前加载loading效果
父组件------------------
<template>
<Suspense>
<template #default><AsyncComponent></AsyncComponent></template>
<template #fallback>loading.....</template>
</Suspense>
</template>
<script>
import AsyncComponent from "./AsyncComponent.vue";
export default {
name: "App",
components: {
AsyncComponent,
},
};
</script>
子组件--------------------------
<template>
<div>{{ msg }}</div>
</template>
<script>
import { defineComponent } from "vue";
const query = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve("我是异步组件");
}, 2000);
});
};
export default defineComponent({
async setup() {
let msg = await query();
return {
msg,
};
},
});
</script>
区别七:TypeScript
更好的TypeScript支持
区别八:vue3支持把组件中的某些内容以图形的方式绘制到canvas画布上(Custom Renderer API
自定义渲染器API)Apache ECharts
- 自定义渲染器API
- 用户可以尝试WebGL自定义渲染器
区别九:vue3使用的Composition API:(Composition API
聚合API):vue2遵循options API
,vue3遵循Composition API
options API
:数据根据功能划分到data、methods、computed、watch
区域(分散式API)
Composition API
:vue3中所有的数据方法都放到了setup()
方法中,全部组合到了一起,没有细分(聚合式API)
- 组合式API,替换原有的 Options [ˈɒpʃnz] API
- 根据逻辑相关性组织代码,提高可读性和可维护性
- 更好的重用逻辑代码(避免mixins混入时命名冲突的问题)
- 但是依然可以延用 Options [ˈɒpʃnz] API
区别十:vue3中的响应式数据
劫持,基于Proxy实现的==:vue3中的响应式数据劫持,不再基于vue2的Object.defineProperty(),而是基于ES6内置的Proxy:但是不兼容IE
defineProperty&Proxy
扫盲
深克隆&&浅克隆
空间地址赋值,是空间地址的引用,不是克隆
let arr = [10, 20]
let cloneArr = arr
console.log(arr === cloneArr);//true
浅克隆就是将栈内存中的复制一份,赋给一个新的变量,内容也相同,(只对最对象最外层克隆, 第二层不克隆)
// 数组浅克隆
let arr = [10, 20, [1, 2, 3]]
// let cloneArr = arr.slice();//截取整个数组实现浅克隆
// let cloneArr = arr.concat([]); //拼接一个空数组
// let cloneArr = [...arr]; //es6剩余运算符
let cloneArr = [];
arr.forEach((item, index) => {
cloneArr[index] = item;
});
console.log(arr === cloneArr); //false
console.log(arr[3] === cloneArr[3]);//true,浅克隆,只克隆最外层
// 对象浅克隆
let obj = {
name: 'lisa',
age: 18,
obj2: {
job: 'teacher'
}
}
// let cloneObj = { ...obj };
// let cloneObj = Object.assign({}, obj);
let cloneObj = {}
for (let a in obj) {
cloneObj[a] = obj[a]
}
console.log(obj === cloneObj);//false
深克隆就是创建一个新的空对象,开辟一块内存,然后将原对象中的数据全部复制过去,完全切断两个对象间的联系。
1. Lodash 或者 underscore 这些类库中都提供了深浅拷贝的办法
2. //先把原始对象变为JSON格式字符串,再把字符串变为对象「所涉及的内存,浏览器都会重新开辟一份全新的出来」
- 如果值是bigint则会报错 TypeError: Do not know how to serialize a BigInt
- + 如果值是 undefined/symbol/function 转换为字符串就没有了
- + 如果值是 正则/错误对象实例 直接变为“{}”
- + 如果值是 日期对象 变为字符串就回不来的
let arr = [10, 20, 'lisa', { name: 'lisa' }];
let cloneArr = JSON.parse(JSON.stringify(arr));
console.log(cloneArr === arr); //false
console.log(cloneArr[3] === arr[3]); //false
纯粹对象:通过 "{}" 或者 "new Object" 创建的对象,原型链直接指向Object
vue2中的响应式原理
基于Object.defineProperty()
方法做get和set劫持的
- 依次迭代对象中的每一项,给每一项分别做数据劫持
- 对深层级别的对象,我们需要基于递归的方式,再次进行劫持
- 对于数组的每一个索引项不做劫持,但是修改其原型指向,指向自己重构的原型对象【包含7个方法,执行这7个方法除了修改数据之外,还会通知视图重新渲染
arr->自己重构的原型对象->Array.prototype->Object.prototype
】
//检测是否为纯粹对象
const isPlainObject = function isPlainObject(obj) {
let proto, Ctor;
if (!obj || Object.prototype.toString.call(obj) !== "[object Object]") return false;
proto = Object.getPrototypeOf(obj);
if (!proto) return true;
Ctor = proto.hasOwnProperty('constructor') && proto.constructor;
return typeof Ctor === "function" && Ctor === Object;
};
let obj = {
x: 1,
y: {
n: 2,
m: 3
},
z: [10, 20, 30]
};
//vue2中的响应式原理
//重新数组七大方法
const proto = {
push(...params) {
//this:数组
Array.prototype.push.call(this, ...params)
//通知视图渲染
},
pop() { },
shift() { },
unshift() { },
splice() { },
sort() { },
reverse() { }
};
//保证重新的方法先找到Array原型
Object.setPrototypeOf(proto, Array.prototype);
const observe = function observe(obj) {
if (Array.isArray(obj)) {
//如果是一个数组,每一项无需做劫持,需要改变原型指向
Object.setPrototypeOf(obj, proto);
return
}
if (!isPlainObject(obj)) return;//不是对象就中断
//把obj深拷贝JSON.stringify转换为字符串,JSON.parse在转为对象
let proxyObj = JSON.parse(JSON.stringify(obj))
//Reflect 是一个内置的对象,ownKeys返回一个包含所有自身属性
let keys = Reflect.ownKeys(obj)
keys.forEach(key => {//迭代对象中的每一项,给每一项做劫持
//方法直接在一个对象上定义新的属性或修改现有属性,并返回该对象。
Object.defineProperty(obj, key, {
get() {
//同于proxyObj[ky]
return Reflect.get(proxyObj, key)
},
set(value) {
if (value === Reflect.get(proxyObj, key)) return
Reflect.set(proxyObj, key, value)
//通知视图重新渲染
}
});
//通过递归做深劫持
if (isPlainObject(obj[key]) || Array.isArray(obj[key])) {
observe(obj[key])
}
});
}
observe(obj)//执行方法把要做劫持的对象传进去
vue3中的响应式原理==:基于ES6的Proxy类来实现,为啥这么做
- 因为Proxy各方面表现都比
Object.defineProperty
好一些,【除了不兼容IE浏览器】 - Proxy可以直接代理整个对象,无需依次迭代对象每一项做get/set劫持【性能好】
- 对于数组来讲,直接基于Proxy代理即可,无需像vue2中一样,再自己重写7个方法【对数组友好】
- 基于
defineProperty
只能做get/set劫持,但是基于基于Proxy可以做的劫持方式很多:【功能强大】
- get/set劫持
- getPrototypeOf()劫持 获取要返回其原型的对象
- setProot ypeOf()劫持 设置要返回其原型的对象
- defineProperty()劫持 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
- deleteProperty()劫持 删除对象中的某个属性
- has劫持 指示对象自身属性中是否具有指定的属性
- ownKeys()劫持 返回一个由目标对象自身的属性键组成的数组。
==相同点==:都是需要基于递归的方式,对“对象”深层次进行劫持代理
let obj = {
x: 1,
y: {
n: 2,
m: 3
},
z: [10, 20, 30]
};
obj = [10, 20, 30]//对数组也做了劫持
//vue3中的响应式
//基于ES6中的Proxy来实现的,new Proxy返回obj的代理对象
let proxObj = new Proxy(obj, {//对obj的第一层做劫持
//target代理的对象,key值
get(target, key) {
return target[key]
},
set(target, key, value) {
if (target[key === value]) return;
target[key] = value;
//控制视图重新渲染,操作代理对象时触发渲染
console.log('rendering');
}
})
vue2生命周期顺序
- 初始化props
- beforeCreate[实例还没创建好,也就是不能使用this 挂载数据/方法
- created[可以用this了]
- beforeMount 渲染
- mounted[可以获取DOM了]
setup()钩子函数
- setup 函数是一个新的组件选项,作为在组件内使用 Composition API(聚合式) 的入口点,组件内的大部分内容都要在这处理
- 发生在初始化props之后,和beforeCreate之前,函数中没有this
- this值是undefined,可以基于props接收到初始化的属性值
- +属性值是一个对象,而且基于Proxy代理后的响应式对象
- +并且属性是只读[readonly]的,当我们修改这个属性值的时候控制台会提示警告
- 可以使用watch监听属性的改变,父组件重新调用子组件,传递不同的属性值.重新渲染
-
函数执行会返回一个对象,对象中包含啥,那么这些东西可以直接在视图中渲染和使用
setup(props) {
console.log(this); // undefined
console.log(props); //可以拿到传递过来的数据,Proxy做了代理
props.title = "哈哈"; //只读,只能调用组件的时候传递不同的值
return {
// 函数执行会返回一个对象,对象中包含啥,那么这些东西可以直接在视图中渲染和使用
text: "哈哈",
};
},
};
vue3中的10+ 响应式API
ref:响应式数据
- 响应式系统API之一,ref let xxx =ref(初始值)创建一个响应式数据
- 它是RefImpl类的一个实例,
- 他是基于Object.defineProetry实现数据劫持的(不基于Proxy),并且是给RefImpl实例对象中的value属性处理的数据劫持,所以后期我们操作的是xxx的value属性
- 视图中渲染的时候 我们{{xxx}},无需自己写value,模板编译的时候会自动取RefImpl对象的value值渲染
import { ref } from "vue"; //导入ref方法
setup() {
let supNum = ref(0), //创建响应式数据,RefImpl类的一个实例
oppNum = ref(50);
console.log(supNum.value); //基于Object.defineProetry实现数据劫持的
supNum.value++; //只对value属性做了劫持,操作value属性
return { supNum, oppNum }; 返回出去
},
-------视图中不需要.value----------
<p>支持人数{{ supNum }}人</p>
reactive:响应式数据
- 响应式系统API之reactive, 基于ES6中的Proxy实现响应式代理,而且也进行了深层次的劫持和代理
- 获取 console.log(state.xxx),设置 (state.xxx=100)
- 处理了proxy的这些劫持函数: get/set/deletePropetry/has/ownKeys
- 如果我们在JS中把state的状态进行了解构处理,一定要了解下面这几点
- 如果只是获取使用,则没有任何问题,但是如果用解构后变量去进行修改,不会触发代理对象的set函数,实现不了通知视图的重新渲染
<script>
import { reactive, computed} from "vue";
export default {
name: "Vote",
props: ["title"],
setup() {
let state = reactive({
supNum: 100,
oppNum: 50,
});
const change = (type) => {
// let { supNum, oppNum } = state;
// 等同于 let n=supNum,修改的是n的值,但是只对state的属性做了劫持,不能解构修改
type === "sup" ? state.supNum++ : state.oppNum++;
// state.supNum = 200; //设置属性
// console.log(state.supNum); //获取属性
};
const ratio = computed(() => {
let { supNum, oppNum } = state; //获取使用解构没有问题
let total = supNum + oppNum;
return total === 0 ? "----" : ((supNum / total) * 100).toFixed(2) + "%";
});
console.log(state); //基于ES6中的Proxy实现响应式代理,而且也进行了深层次的劫持和代理
return { change, ratio, state };
},
};
</script>
toRefs && toRef:转换响应式类型
- toRefs(state),把基于 reactive创建的响应式状态,改为基于ref管理的状态,目的是视图中渲染RefImpl对象比较简单(返回一个对象,把state中每一项修变为ObjectRefImpl的实例对象)
- 项目中:一般在JS中用reactive创建状态,导出toRefs变成ref的响应式状态
- toRef把数据中的某一项变成变成ref对象, refs把所有项变成ref对象
return { ...toRefs(state) };
let state=reactive({x:100,y:200})
let refs=toRefs(state);//将state中的x和y转为refImpl对象
let state=reactive({x:100,y:200})
let ref=toRef(state.x);//将state中的x和y转为refImpl对象
computed:计算属性
- vue3推崇函数式编程,我们想做计算属性,需要在vue中解构出
computed
函数 - let xxx=computed([getter函数])
- 它是 ComputedRefImpl类的一个实例对象,也是对其value属性的数据劫持,我们操作的也应该是xxx.value这个属性
- 默认情况下,创造的计算属性是只读的,所以xxx.value=100会抛出警告
- computed({get(){},set(){}}) 基于这种方式设置computed可写性
import { computed } from "vue";
export default {
name: "Vote",
props: ["title"],
setup() {
let supNum = ref(100),
oppNum = ref(50);
//相当于methods中的普通方法
const change = (type) => (type === "sup" ? supNum.value++ : oppNum.value++);
// 计算属性
const ratio = computed(() => {
//ComputedRefImpl的一个实例,
let total = supNum.value + oppNum.value;
return total === 0
? "----"
: ((supNum.value / total) * 100).toFixed(2) + "%";
});
console.log(ratio.value); //也是对其value属性的数据劫持
ratio.value = "100%"; //创造的计算属性是只读的,设置会抛出警告
return { supNum, oppNum, change, ratio };
},
};
--------------------------------------
const ratio = computed({
get() {
let total = supNum.value + oppNum.value;
return total === 0
? "----"
: ((supNum.value / total) * 100).toFixed(2) + "%";
},
set(value) {
console.log(value);
},
});
console.log(ratio.value);
ratio.value = "100%";
监听器 :watch && watchEffect
watchEffect([callback])
- 第一次渲染组件[callback]回调函数触发执行一次
- 自动根据函数中的代码建立相关依赖(用到啥依赖啥),当依赖发送改变,[callback]还会重新执行
- setup函数只会在组件第一次渲染的时候执行一次,当组件更新的时候steup并不会执行,但是无论是计算属性,还是监听器,都会根据依赖的信息是否发生变化,重新执行(获取最新的值)
watchEffect(() => {
console.log(state.supNum);
});
watch
watch API 完全等效于 2.x this.$watch
- watch可以指定监听哪一个状态/属性,在状态更改后触发指定的执行函数
- 默认情况下第一次渲染组件并不会执行监听函数(可以配置)
- 默认也不是进行深层次的监听(可以配置)
watch(Reflmpl对象,函数) 监听某一个ref对象
watch(state,函数) 监听所有基于 reactive创建的状态对象
watch(()=>state.xxx,函数) 如果监听 state中的某一个状态,则必须写成函数的形式
watch(监听的对象,函数,配置项immediate/deep)
watch(
() => state.supNum,
(next, prev) => {
// next之前的值,prev最新的值
console.log(next, prev);
}
);
---------------------
watch(
state,
(next, prev) => {
console.log(next, prev);
},
{//配置项
immediate: true, //第一次执行
deep: true, //深层次监听劫持
}
);
readonly只读
传入一个对象(响应式或普通)或 ref,返回一个原始对象的只读代理
一个只读的代理是“深层的”,对象内部任何嵌套的属性也都是只读的
const original = reactive({ count: 0 })
const copy = readonly(original)
watchEffect(() => {
// 依赖追踪
console.log(copy.count)
});
// original 上的修改会触发 copy 上的侦听
original.count++;
// 无法修改 copy 并会被警告
copy.count++; // warning!
检测的
isReadonly:判断状态值是不是Readonly类型的实例(是不是只读的)
isReactive判断状态值是不是使用reactive()
创建的
isProxy判断状态值是不是Proxy类型的实例(是不是通过Proxy做代理的):reactive、computed、props生成的数据都是Proxy的实例)
isRef:是不是ref创建的响应式对象(ref创建的,computed 通过refs转换的
)
unref:把响应式数据变为非响应式的
周期函数&&DOM元素操作
周期函数
可以直接导入 onXXX 一族的函数来注册生命周期钩子,这些生命周期钩子注册函数只能在 setup() 期间同步使用,在卸载组件时,生命周期钩子内部同步创建的侦听器和计算状态也将删除
- vue2中的beforeCreate和created由==setup()==函数代替了
beforeMount
->onbeforeMount
Mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestory
->onBeforeUmount
destoryed
->onUnMounted
dom元素的操作
vue3中的ref将创建状态值与绑定ref的DOM元素组合在了一起
//创建一个ref实例对象
let titlebox = ref(null);
//导出状态值
return { titlebox };
//在dom元素上使用
<h2 class="title" ref="titlebox">{{ title }}</h2>
获取dom元素
onMounted(() => {
console.log(titlebox);
});
掌握Vue3.0自定义指令处理技巧
export default function directive(app) {
app.directive('xxx', {
// 指令首次绑定到元素且在安装父组件之前...「等同于bind」
beforeMount(el, binding, vnode, prevVnode) {
// binding:数据对象
// + arg:传给指令的参数 v-xxx:n -> arg:"n"
// + modifiers:修饰符对象 v-xxx.stop -> modifiers:{stop:true}
// + value:指令绑定的值 v-xxx="1+1" -> value:2
// + oldValue:之前绑定的值
},
// 安装绑定元素的父组件时...「等同于inserted」
mounted() {},
// 在包含组件的VNode更新之前...
beforeUpdate() {},
// 在包含组件的VNode及其子VNode更新后...「等同于componentUpdated」
updated() {},
// 在卸载绑定元素的父组件之前...
beforeUnmount() {},
// 指令与元素解除绑定且父组件已卸载时...「等同于unbind」
unmounted() {}
});
};
// main.js
import {
createApp
} from 'vue';
import App from './App.vue';
import directive from './directive';
const app = createApp(App);
directive(app);
app.mount('#app');