vue3的基本使用2
1、hook
Vue3 的 hook函数 相当于 vue2 的 mixin,不同在于 hook 是函数,其使用目的是为了复用代码,让setup中的逻辑更加清楚易懂。
使用示例:
在 src 目录下建立一个 hooks 文件夹,声明一个用于存放需要复用的代码的 js 文件,如下:
文件内容如下:
import {reactive,onMounted,onBeforeUnmount} from 'vue' export default function (){ //实现鼠标“打点”相关的数据 let point = reactive({ x:0, y:0 }) //实现鼠标“打点”相关的方法 function savePoint(event){ point.x = event.pageX point.y = event.pageY console.log(event.pageX,event.pageY) } //实现鼠标“打点”相关的生命周期钩子 onMounted(()=>{ window.addEventListener('click',savePoint) }) onBeforeUnmount(()=>{ window.removeEventListener('click',savePoint) }) return point }
上面的自定义 hook 实际上相当于封装了一个给页面绑定点击事件,并且记录了点击事件的鼠标位置的方法,可复用于各个组件之间。
在组件中引入并使用自定义 hook ,代码如下:
<template> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">点我+1</button> <hr> <h2>当前点击时鼠标的坐标为:x:{{point.x}},y:{{point.y}}</h2> </template> <script> import {ref} from 'vue' import usePoint from '../hooks/usePoint' export default { name: 'Demo', setup(){ //数据 let sum = ref(0) let point = usePoint() //返回一个对象(常用) return {sum,point} } } </script>
由此上面组件即复用了自定义 hook 的方法,给页面绑定了点击事件,并且记录了点击的鼠标。自定义hook的作用类似于vue2中的mixin技术,自定义Hook的优势在于很清楚复用功能代码的来源,更清楚易懂。
2、响应性API之基础api
2.1、toRaw()(将响应式对象转为普通对象)
toRaw() 可以将一个由reactive
生成的响应式对象转为普通对象,该方法返回 reactive
或 readonly
代理的原始对象。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
使用场景:用于读取响应式对象对应的普通对象,该普通对象不具有响应式特性,对这个普通对象的所有操作,不会引起页面更新。
2.2、markRow()(标记对象使其无法成为响应式)
作用:可用于标记一个对象,此时该对象将永远无法成为响应式对象,该方法返回该对象本身。当某个对象我们并不想让其具有响应式特性时,可以使用该方法对该对象进行标记。
应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类库等。
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
示例:
setup(){
//数据
let person = reactive({
name:'张三',
age:18,
job:{
j1:{
salary:20
}
}
})function addCar(){
let car = {name:'奔驰',price:40}
person.car = markRaw(car)
}//返回一个对象(常用)
return {
person,
addCar
}
}
上面 person.car 并不具有响应式特性,修改 person.car 的值页面不会发生改变。
2.3、响应式数据的判断(isRef、isReactive、isReadonly、isProxy)
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理 - isReadonly: 检查一个对象是否是由
readonly
创建的只读代理 - isProxy: 检查一个对象是否是由
reactive
或者readonly
方法创建的代理
3、响应性API之Refs
3.1、toRef() 函数
toRef() 函数用来用来为源响应式对象上的某个 property 创建一个 ref 对象,其 value 值指向该响应式对象中的属性值。新创建的 ref 对象会保持对其源对象的对应 property 值的响应式连接。一般来说,当我们需要将响应式对象中的某个属性单独提供给外部使用时就可以使用 toRef() 函数。
比如下面的 person,如果直接返回 person.name,则该属性并不是响应式的,无法响应式地绑定在页面上。我们可以通过类似于 ref(person, 'name') 的写法来为响应式对象的某个属性创建 ref 对象,页面通过绑定该 ref 对象可以实现双向绑定的效果。
<template> <h4>{{person}}</h4> <h2>姓名:{{name2}}</h2> <h2>年龄:{{age}}</h2> <button @click="name2+='~'">修改姓名</button> <button @click="age++">增长年龄</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 } } }) const name2 = toRef(person,'name')//返回一个对象(常用) return { person, name2:name2, age:toRef(person,'age'), salary:toRef(person.job.j1,'salary') } } } </script>
3.2、toRefs() 函数
toRefs
与toRef
功能一致,不过 roRefs() 是一次性批量创建多个 ref 对象,并且返回的是一个对象。语法:toRefs(person)
代码示例:
<template> <h4>{{person}}</h4> <h2>薪资:{{job.j1.salary}}K</h2> <button @click="job.j1.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 } } })//返回一个对象(常用) return { person, ...toRefs(person) } } } </script>
3.3、customRef()(自定义ref)
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。该方法接收一个工厂函数,该工厂函数接收 track
和 trigger
函数作为参数,并且应该返回一个带有 get
和 set
的对象。
示例:
通过 customRef() 可以自定义一个 ref,在响应式过程中执行一些自定义操作。
<template> <input type="text" v-model="keyWord"> <h3>{{keyWord}}</h3> </template> <script> import {ref,customRef} from 'vue' export default { name: 'App', setup() { //自定义一个ref——名为:myRef function myRef(value, delay){ let timer return customRef((track,trigger)=>{ return { get(){ console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`) track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的) return value }, set(newValue){ console.log(`有人把myRef这个容器中数据改为了:${newValue}`) clearTimeout(timer) timer = setTimeout(()=>{ value = newValue trigger() //通知Vue去重新解析模板 }, delay) }, } }) } // let keyWord = ref('hello') //使用Vue提供的ref let keyWord = myRef('hello',500) //使用程序员自定义的ref return {keyWord} } } </script>
4、其他组合式api
shallowReactive(obj)(浅响应式):只有对象的最顶层属性有响应式。也就是说,如果修改对象的属性不是顶层的属性,则并不会有响应式的效果。
shallowRef():只处理基本数据类型的响应式,不进行对象的响应式处理。也就是说,该方法只接收基本数据类型的对象,如果是参数是对象类型,则返回的对象不具有响应式。
readonly()(深只读):接受一个对象 (可以是响应式对象或纯对象) 或 ref 并返回原始对象的只读代理。返回的只读对象如果对该对象属性进行修改,浏览器会报错。
shallowReadonly()(浅只读):跟 readonly() 一样,接受一个对象 (可以是响应式对象或纯对象) 或 ref 并返回原始对象的只读代理。但是该方法只是控制浅只读,也就是可以修改返回的代理对象的非顶层属性的值,修改后也有响应式的效果。
5、provide/inject
父子组件之间可以通过 props 来传递数据,但是如果是更深层级的,如果仍然将 prop 沿着组件链逐级传递下去会很麻烦。对于这种情况,我们可以使用一对 provide
和 inject
。父组件通过 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,子组件 child.vue,孙组件 son.vue,实现组件之间数据传递如下:
父组件 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:'40W'}) provide('car',car) //给自己的后代组件传递数据 return {...toRefs(car)} } } </script> <style> .app{ background-color: gray; padding: 10px; } </style>
子组件 child.vue 代码如下:
<template> <div class="child"> <h3>我是Child组件(子)</h3> <Son/> </div> </template> <script> import {inject} from 'vue' import Son from './Son.vue' export default { name:'Child', components:{Son}, setup(){ let x = inject('car') console.log(x,'Child-----') } } </script> <style> .child{ background-color: skyblue; padding: 10px; } </style>
孙组件代码如下:
<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') return {car} } } </script> <style> .son{ background-color: orange; padding: 10px; } </style>
6、vue3中的新组件
6.1、Fragment组件
在Vue2中组件必须有一个根标签,但是在 Vue3 中组件是可以没有根标签的,当组件没有根标签时,vue3 内部会自动将多个标签包含在一个Fragment虚拟元素中,该元素并不会渲染在页面上,只是通过 vue 开发者工具可以看到。该组件的作用就是可以让 vue3 的组件省略根标签,减少标签层级,减小内存占用。
示例:
<template> <div> <img alt="Vue logo" src="./assets/logo.png"> <Demo /> </div> </template> <script> import Demo from './components/Demo'; export default { name: 'App', components: { Demo, }, }; </script> <style> </style>
上面代码为 app.vue,在该组件中使用了根标签,而假设我们在引入的 demo 组件中并没有使用根标签,则可以看到效果如下:
6.2、Teleport组件
Teleport 是一种能够将组件的 html 结构移动到指定位置的技术。
比如说我们想要在一个组件里面实现弹窗和遮罩层效果,但是在使用该组件时,可能层级很深,导致很难实现那种需要以 body 或者 html 作为直接父元素来实现的样式,比如想要实现全屏居中,或者遮罩全屏的效果,在使用该组件后,由于该组件可能此时处于很深的层级下,导致很难实现实现全屏居中或者遮罩的效果。此时我们可以使用 Teleport 组件,通过该组件可以指定一些 html 在渲染后直接移动到指定的位置下。
使用语法:
<teleport to="移动位置"> <!-- 比如可以指定body或者指定id,如 to="body"、to="html"、to="#myId" --> <div> <p></p> ... </div> </teleport>
代码示例:
如下,在 app.vue 中使用 dialog 组件,dialog 组件通过 Teleport 组件在渲染后将指定的 html 直接移动至 body 下。
app.vue 代码如下:
<template> <div class="app"> <h3>我是App组件</h3> <Dialog/> </div> </template> <script> import Dialog from './Dialog.vue' export default { name:'App', components:{Dialog}, } </script> <style> .app{ background-color: gray; padding: 10px; } </style>
dialog 组件代码如下:
<template> <div> <button @click="isShow = true">点我弹个窗</button> <teleport to="body"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <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> </style>
在显示弹出框的 mask 元素后,可以看到该元素直接插入到了 body 下,此时要想设置该元素的一些需要基于 body 才好实现的样式就会更加方便。
7、vue3 中的一些改变
7.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 |
7.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
修饰符
vue3中通过该事件是否在子组件中的 emits 选项中来判断该事件是否为自定义事件,是则为自定义事件,否则为原生事件。
父组件中绑定事件:
<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
子组件中声明自定义事件:
<script> export default { emits: ['close'] } </script>
- 移除过滤器(filter)。官方说法:过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。
10、vue3的响应式原理
<script>
//目标对象
let obj = {
name: "张三",
age: 12,
bobby: ['钓鱼', '唱k']
};
//proxy把目标对象转化成代理对象
//参数1:目标对象 参数2:处理器对象,用来监听数据,及操作数据
let proxyObj = new Proxy(obj, {
//监听取值,第一个参数目标对象,第二个参数被获取的属性名
get(target, prop) {
console.log('触发了get操作');
// return target[prop];//不推荐
//使用Reflect为了优化Object的一些操作方法以及合理的返回Object操作返回的结果
return Reflect.get(target, prop);
},
//监听设置值
set(target, prop, val) {
console.log('触发了set操作');
// target[prop]=val;//不推荐
return Reflect.set(target, prop, val);
},
//监听删除delete
deleteProperty(target, prop) {
console.log('触发了delete操作');
// delete target[prop];不推荐
return Reflect.deleteProperty(target, prop);
}
});
console.log('初始代理对象值', proxyObj);
proxyObj.name = "李四"; //修改属性值
console.log('修改属性值后代理对象值', proxyObj, '修改后原对象值', obj);
proxyObj.sex = '男' //增加属性值
console.log('增加属性值后代理对象值', proxyObj, '修改后原对象值', obj);
proxyObj.bobby[0] = '踢球' //直接修改数组索引值
console.log('修改数组索引值后代理对象值', proxyObj.bobby[0], '修改后原对象值', obj.bobby[0]);
delete proxyObj.age; //删除属性值
console.log('删除属性后原对象值', obj);
</script>
上面通过 proxy 对 obj 对象的属性值的读写、添加、删除操作进行劫持,对于原对象的属性的增删改查操作就能监听得到,也就可以进行后面的一系列操作,以此来作为实现响应式的基础。
并且通过 proxy 代理来劫持对象属性,可以避免 vue2 中通过 Object.defineProperty() 无法监听对象属性的添加和删除操作,无法监听数组中直接通过索引来修改数组值的弊端。
执行结果如下:
(上面的代码不用 Reflect 直接通过返回或者设置原对象的属性实际也可以,Reflect 这个 API 在 es6 中被提出,目的主要是为了形成一个未来规范吧,把操作对象相关的方法统一到 Reflect。)
9.1、Proxy
proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。
var proxy = new Proxy(target, handler);