必备-14.vue3
vue3和vue2的版本
npm i vue@lasted
:下载的是vue2
npm i vue@next
:下载的是vue3
- 老版本:
- vue2
- vuex3
- vue-router3
- PC端组件库:element-ui 2
- 移动端组件库:vant2;
https://vant-contrib.gitee.io/vant/v2/#/zh-CN/quickstart
- 新版本:
- vue3
- vuex4
- vue-router4
- PC端组件库:element-plus 1
- 移动端组件库:vant3:
https://vant-contrib.gitee.io/vant/v3/#/zh-CN/quickstart
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中解构出各种各样的函数来使用
-
区别二:vue3重写了虚拟DOM的实现:(
Performance
)(跳过静态节点,只处理动态节点)- update性能提高1.2-2倍
- SSR(视图渲染)速度提高2-3倍
-
区别三:vue3可以将无用模块"裁掉",仅打包需要的:(
Tree shaking
)【webpack新版本的】 -
区别四:vue3支持多个根节点(
Fragment
)【文档碎片】 -
区别五:vue3有传送门:(
<Teleport to="#fotter">
),可以把组件内部的部分内容挂载到除#app
的容器中 -
区别六:vue3中可以创建异步组件:(
<Suspense>
)嵌套一个异步组件,可以在获取到数据之前加载loading效果 -
区别七:TypeScript:可以更好的支持TypeScript语法
-
区别八:vue3支持把组件中的某些内容以图形的方式绘制到canvas画布上😦
Custom Renderer API
自定义渲染器API) -
区别九:vue3使用的Composition API:(
Composition API
聚合API):vue2遵循options API
,vue3遵循Composition API
options API
:数据根据功能划分到data、methods、computed、watch
区域(分散式API)Composition API
:vue3中所有的数据方法都放到了setup()
方法中,全部组合到了一起,没有细分(聚合式API)
-
区别十:vue3中的响应式数据劫持,基于Proxy实现的:vue3中的响应式数据劫持,不再基于vue2的Object.defineProperty(),而是基于ES6内置的Proxy:但是不兼容IE
defineProperty&Proxy
-
vue2中的响应式原理
-
基于
Object.defineProperty()
方法做get和set劫持的- 依次迭代对象中的每一项,给每一项分别做数据劫持
- 对深层级别的对象,我们需要基于递归的方式,再次进行劫持
- 对于数组的每一个索引项不做劫持,但是修改其原型指向,指向自己重构的原型对象【包含7个方法,执行这7个方法除了修改数据之外,还会通知视图重新渲染
arr->自己重构的原型对象->Array.prototype->Object.prototype
】
//一:创建重写了七个数组方法的对象,并让所有的数组的原型链都指向这个对象:主要实现的目的:通知视图渲染 const proto = { push(...params) { Array.prototype.push.call(this, ...params); // 通知视图渲染 }, pop() { }, shift() { }, unshift() { }, splice() { }, sort() { }, reverse() { } }; //二、让重写六个方法的对象的原型链指向Array的原型 Object.setPrototypeOf(proto, Array.prototype); //------------------------------创建监听函数observe const observe = function observe(obj) { // 如果是一个数组,每一个索引项无需做劫持,但需要改变这个数组的原型->proto if (Array.isArray(obj)) { Object.setPrototypeOf(obj, proto); return; } //如果不是对象直接返回 if (!isPlainObject(obj)) return; // 把obj进行深拷贝 let proxyObj = JSON.parse(JSON.stringify(obj)); // 迭代对象中的每一项,给每一项做劫持 let keys = Reflect.ownKeys(obj); keys.forEach(key => { //对对象中各属性做get、set拦截 Object.defineProperty(obj, key, { get() { return Reflect.get(proxyObj, key); }, set(newValue) { if (newValue === 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 proxyObj=new Proxy(obj,{ get(target,key){ return target[key]; }, set(target,key,value){ if(target[key]===value) render; target[key]=value; //控制视图重新渲染 } })
-
vue2生命周期顺序
- 初始化props
- beforeCreate[实例还没创建好,也就是不用this]
- 挂载数据/方法
- created[可以用this了]
- beforeMount
- 渲染
- mounted[可以获取DOM了]
setup()钩子函数
- setup(props):钩子函数是Composition API的入口点,组件内的大部分内容都需要在这处理
- 发生在初始化props之后,以及beforeCreate之前,函数中没有this(this是undefined)
- 可以基于形参
props
接收初始化之后的属性值- 属性值是一个对象,而且是基于Proxy代理后的响应式对象
- 并且属性是只读的(
readonly
),当我们去修改某个属性值的时候,控制台会提示警告
使用setup中的数据
-
函数执行返回一个对象,对象中包含啥,那么这些东西就可以直接在视图中进行渲染和使用了
-
<div>{{name}}</div>//-》<div>hahaha</div> setup(props){ //这里的supNum和change不是响应式的 supNum:0; const change=()=>{ supNum++; } return { name:"hahaha", supNum, change } }
vue3中的10+大响应式API
- vue3中创建响应式数据不能直接在setup中创建,需要两个API
ref/reactive
- vue3中创建的响应式数据分为两大类:
- ref、computed:是基于RefImpl类创建的实例,它的底层是由**Object.defineProperty()**方法做的劫持
- props、reactive:是基于Proxy类创建的实例,它的底层是由Proxy类做的劫持:默认会有
get/set/has/ownkeys/defineProperty
五个的劫持
ref:创建状态值
-
创建一个状态值就得执行一次ref
-
<script> import {ref,reactive,computed} from "vue" setup(props){ /*响应式系统API之一:ref let xxx=ref([initialValue]);创建一个响应式状态 xxx是RefImpl类的实例 */ let supNum=ref(100), oppNum=ref(50); const change=(type)=>{ type==="sup"?supNum.value++:oppNum.value++; } /* 计算属性 let xxx=computed([getter函数]) 它是ComputedRefImpl类的一个实例对象,和RefImpl类似,也是对其value属性做的数据劫持,我们操作的也是xxx.value属性 默认情况下创建的计算属性是只读的,所以:xxx.value=100会抛出警告 computed({get(){},set(newValue){}}),基于这种方式可以设置computed计算属性的可写性 */ let ratio=computed(()=>{ let total=supNum.value+oppNum.value; return total===0?'----':(supNum.value/total*100).toFixed(2)+"%"; }) return{ supNum, oppNum, change, ratio } </script>
-
语法:
let xxx=ref([initialValue]);
创建一个响应式状态xxx是RefImpl类的实例 -
ref实现的响应式数据是基于
defineProperty()
做劫持的(不是基于Proxy), -
并且是给RefImpl实例对象中的
xxx.value
属性处理的数据劫持,所以我们后期操作的是xxx的value属性;- 获取:
xxx.value
- 赋值:
xxx.value=100
- 获取:
-
在视图渲染的时候,我们用
{{xxx}}
调用数据时,无需自己写.value,模板编译的时候会自动取RefImpl对象的value值进行渲染!!
-
reactive:创建状态值
-
可以同时生成多个响应式状态值
-
<script> import { ref, reactive, computed,toRefs} from "vue"; export default { name: "Vote", props: ["title"], setup(props) { let state = reactive({ supNum: 100, oppNum: 50, }); const change = (type) => { //不能对state进行解构赋值,因为我们获取的supNum和oppNum是原始值 type ==="sup"?state.supNum++:state.oppNum++; }; let ratio = computed(() => { let total = state.supNum+ state.oppNum; return total === 0? "----": ((state.supNum / total) * 100).toFixed(2) + "%"; }); return { ...toRefs(state),//调用toRefs()将Proxy对象转为refs change, ratio, }; }, }; </script>
-
基于ES6中的Proxy实现响应式代理,而且也进行了深层次的劫持和代理
-
语法:
let xxx= reactive({x:100,y:100})
-
获取:
state.xxx
-
赋值:
state.xxx=100
-
处理了Proxy的这些劫持函数:
get/set/has/ownkeys/deleteProperty
-
如果我们在js中把state状态解构赋值处理,一定要了解下面这几点:
- 如果只是获取使用,则没有任何问题
- 但是如果用解构后的变量去进行修改,不会触发代理对象的set函数,实现不了通知视图重新渲染
- 所以轻易不要解构
toRefs:转多个状态值类型
-
将reactive创建的响应式状态值,转为基于ref管理的refImpl对象
-
目的:在视图中渲染RefImpl对象比较简单【返回一个对象,里面包含,把state中每一项都变为单独的
ObjectRefImpl
的实例对象】 -
语法:
-
let state=reactive({x:100,y:200}) let refs=toRefs(state);//将state中的x和y转为refImpl对象
-
toRef:转单个状态值类型
-
与toRefs相同,转单个reactive创建的响应式状态值
-
语法:
-
let state=reactive({x:100,y:200}) let ref=toRefs(state.x);//将state中的x和y转为refImpl对象
-
unRef
- 把响应式数据变为非响应式的
computed:计算属性
-
vue3推崇函数式编程,我们想做计算属性,需要在vue中解构出
computed
函数 -
语法:
let xxx=computed([getter函数])
-
它是
ComputedRefImpl
类的一个实例对象,和RefImpl类似,也是对其value属性做的数据劫持,我们操作的也是xxx.value
属性 -
默认情况下创建的计算属性是只读的,所以:
xxx.value=100
会抛出警告 -
computed({get(){},set(newValue){}})
,基于这种方式可以设置computed计算属性的可写性
/* 计算属性
let xxx=computed([getter函数])
它是ComputedRefImpl类的一个实例对象,和RefImpl类似,也是对其value属性做的数据劫持,我们操作的也是 xxx.value属性
默认情况下创建的计算属性是只读的,所以:xxx.value=100会抛出警告
computed({get(){},set(newValue){}}),基于这种方式可以设置computed计算属性的可写性
*/
let ratio=computed(()=>{
let total=supNum.value+oppNum.value;
return total===0?'----':(supNum.value/total*100).toFixed(2)+"%";
})
watch:监听器
-
watch可以指定监听哪一个状态、属性,在状态更改后触发指定的函数执行
- 默认情况下,第一次渲染组件,并不会执行监听函数【可以配置】
- 默认不是进行深层次监听【可以配置】
-
语法:
-
watch(RefImpl对象实例,函数)
:监听某一个Ref对象 -
watch(state,函数)
:监听所有基于reactive
创建的状态对象 -
watch(()=>{state.xxx},函数)
:如果监听state的某一个状态,则必须携程函数 -
如果想配置状态值:
watch(监听的对象,执行的函数,配置项)
//配置watch的状态值, watch(state,()=>{}, { immdiate:true, deep:true })
-
watchEffect:监听器
-
语法:
wathEffect([callback])
-
第一次渲染组件,[callback]触发执行一次
-
自动根据函数中的代码建立相关状态值的依赖(用到啥就依赖啥),当依赖的值发生改变,[callback]还会重新执行
-
用处:setup()函数只会在组件第一次渲染的时候执行一次,当组件更新的时候,setup()并不会执行,但是无论是计算属性还是监听器都会根据依赖的信息是否发生变化,重新执行,获取最新的状态值
-
watchEffect(()=>{ state.supNum })
-
readonly:只读
- 把一个状态值变为只读的
let copy=readonly(value)
:copy就是只读的
isRef
isReactive
- 判断状态值是不是使用
reactive()
创建的
isReadonly
- 判断状态值是不是Readonly类型的实例
isProxy
- 判断状态值是不是Proxy类型的实例:
reactive、props生成的数据都是Proxy的实例
周期函数
- vue2中的beforeCreate和created由==setup()==函数代替了
beforeMount
->onbeforeMount
Mounted
->onMounted
beforeUpdate
->onBeforeUpdate
updated
->onUpdated
beforeDestory
->onBeforeUmount
destoryed
->onUnMounted
DOM元素操作
-
vue3中的ref将创建状态值与绑定ref的DOM元素组合在了一起
-
用法:
//1、创建一个ref状态值,值为null let myDom=ref(null); //2、导出该状态值 return{ myDom } //3、在标签的ref中引用 <h1 ref="myDom">aaa</h1> //4、myDom绑定的value值就是这个标签 console.log(myDom.value)
小案例
知乎日报
-
技术栈:
-
@vue/cli、vue3、vuex4、vue-router4、vant3、axios(qs)...
-
postcss-pxtorem、amfe-flexible
:移动端适配postcss-pxtorem
:我们以后写样式都写px,这个插件会把我们写的px转换为remamfe-flexible
:自动设置根元素字体大小
-
less & less-loader
:样式编码规范 -
babel-plugin-import
:按需导入UI组件库的
-
-
需要完成的页面:
- 首页:新闻列表、头部、轮播图… /
- 详情页:文章内容、底部操作栏 /detail/:id
- 个人中心 /person
- 收藏列表 /store
- 登录页面 /login
- 修改个人信息页 /update
- …
-
开始搭建项目的架子:
- 基于@vue/cli创建项目,&配置vue.config.js & 处理浏览器兼容 & 安装需要的模块
- 在SRC目录中:
- 配置vuex
- 配置vue-router
- 配置api
- 配置响应式布局方案&导入UI组件库
vant
vant中的响应式布局
- vant UI组件库本身是按照 375px的设计稿设计的
- 所以 vant中写的所有样式尺寸(单位是px),按照375的设计稿量出来的
- amfe-flexible 使用插件 ,会按照375的设计稿,把html font-size设置为 37.5px
- 我们的目的是把 写死的px单位 除以 37.5 都转换为rem
- 我们想用amfe-flexible,需要在main.js中导入这个插件
- postcss-pxtorem:将所有的px除以37.5再转为rem
- amfe-flexible 使用插件 ,会按照375的设计稿,把html font-size设置为 37.5px
- 真实项目中,设计师给我们的设计稿是750px的,可以缩小一倍,按照vant375px的设计稿设计
- 其他设计稿,可以在
postcss.config.js
中的设置缩小的比例
- 其他设计稿,可以在
// postcss.config.js
module.exports = {
plugins: {
// postcss-pxtorem 插件的版本需要 >= 5.0.0
'postcss-pxtorem': {
rootValue({ file }) {
// 如果是750的设计稿,就把根节点的像素设为75px,
//之后设计稿上是多大px,我们就写多大px,postcss就会除以75给我们缩小
return file.indexOf('vant') !== -1 ? 37.5 : 75;
},
propList: ['*'],
},
},
};
vue3的优化
-
Object.freeze()
:对不需要做深劫持的数据做冻结,使浏览器不需要再花时间去做迭代监听,提升性能 -
v-lazy
:将img的src改为v-lazy,实现图片懒加载 -
IntersectionObserver
:做滚动监听,延迟加载列表中的数据-
//第一步:创建监听器:监听器就像一个事件池,可以同时监听多个DOM元素 //changes里面存放的是监控的DOM对象的数组 le ob=new IntersectionObserver(async (changes)=>{ //我们只监听了一个Dom节点 let item=changes[0]; //当监听的对象出现在页面中时,isIntersecting会改为true if(item.isIntersecting){ //自己想做的事 } }) let loadMore=ref(null); //只有当页面完成挂载时,才去监听这个DOM节点 onMounted(()=>{ //判断是否拿到了loadMore,如果没拿到,监听器监听不了 if(!loadMore.value) return; //监听DOM元素 ob.observe(loadMore.value) }) //当组件触发销毁时,内存中的监听器并不会销毁,需要手动销毁 onbeforeUnMounted(()=>{ if(!loadMore.value) return; //销毁DOM元素,优化性能 ob.unobserve(loadMore.value) })
-