响应式:这是一个比较模糊的概念。通常可以理解成对某些操作有所反应。
无法检测到对象属性的新增或删除(vue2提供了vue.set方法来解决)
直接通过下标修改数组,无法监听数组的变化,
深度监听,层层处理,影响性能,性能不好,需要对每一个key循环递归处理,特别是处理大数据尤为明显
Object.defineproperty()每调用一次都只能对对象的某一个属性进行数据劫持,所以要采用循环遍历,代码写起来比较麻烦。
Vue2时代Option Api ,data、methos、watch.....分开写,这种是碎片化的分散的,代码一多就容易高耦合,维护时来回切换代码是繁琐的!
Vue3时代Composition Api,通过利用各种Hooks和自定义Hooks将碎片化的响应式变量和方法按功能分块写,实现高内聚低耦合
形象的讲法:Vue3自定义Hooks是组件下的函数作用域的,而Vue2时代的Mixin是组件下的全局作用域。全局作用域有时候是不可控的,就像var和let这些变量声明关键字一样,const和let是var的修正。Composition Api正是对Vue2时代Option Api 高耦合和随处可见this的黑盒的修正,Vue3自定义Hooks是一种进步。
Vue3 提供了两种方式构建响应式数据:ref 和 reactive
ref 用于构建简单值的响应式数据,比如String,Number,基于 Object.defineProperty 监听 value 值,原理是将普通的值转化为对象,并且在获取和设置值时可以增加依赖收集和触发更新功能
// ref 源码部分functionref(value){
returncreateRef(value)
}
functionconvert(rawValue){
returnisObject(rawValue) ? reactive(rawValue) : rawValue
}
// shallwfunctioncreateRef(value) {
const refImpl = newRefImpl(value);
return refImpl;
}
exportclassRefImpl {
private _rawValue: any;
private _value: any;
public dep;
public __v_isRef = true;
constructor(value) {
this._rawValue = value;
// 看看value 是不是一个对象,如果是一个对象的话// 那么需要用 reactive 包裹一下this._value = convert(value);
this.dep = createDep();
}
getvalue() {
// 收集依赖trackRefValue(this);
returnthis._value;
}
setvalue(newValue) {
// 当新的值不等于老的值的话,// 那么才需要触发依赖if (hasChanged(newValue, this._rawValue)) {
// 更新值this._value = convert(newValue);
this._rawValue = newValue;
// 触发依赖triggerRefValue(this);
}
}
}
let num1 = ref(111)
// Vue 3.0 内部将 ref 悄悄的转化为 reactivelet num1 = reactive({
value: 111
})
可以看到,ref方法将这个字符串进行了一层包裹,返回的是一个RefImpl类型的对象,译为引用的实现(reference implement),在该对象上设置了一个不可枚举的属性value,所以使用name.value来读取值。
ref通常用于定义一个简单类型,那么是否可以定义一个对象或者数组?
const param = ref({
name: 'lili',
age: 25
})
console.log(param, param.value.name)
控制台可以看到,对于复杂的对象,值是一个被proxy拦截处理过的对象,但是里面的属性name和age不是RefImpl类型的对象,proxy代理的对象同样被挂载到value上,所以可以通过obj.value.name来读取属性,这些属性同样也是响应式的,更改时可以触发视图的更新
通过上面ref的使用案例,起始不管是复杂引用类型,如array,object等,亦或者是简单的值类型string,number都可以使用ref来进行定义,但是,定义对象的话,通常还是用reactive来实现
reactive 用于构建复杂的响应式数据,不能定义普通类型,基于 Proxy 对数据进行深度监听
reactive 参数必须是对象(json 或 Array),不能定义普通类型
【ref 与reactive的区别与联系】
一般来说,ref被用来定义基本数据类型,reactive定义引用数据类型
ref定义对象时,value返回的是proxy,reactive定义对象时返回的也是proxy,而这确实存在一些联系,ref来定义数据时,会对里面的数据类型进行一层判断,当遇到复杂的引用类型时,还是会使用reactive来进行处理
接收两个参数target和attr,target是一般是reactive的响应式对象,attr是对象的属性,返回响应式变量(采用引用的方式,修改响应式数据,会影响原始数据,并且数据发生改变)
{{ name }}:{{ data.age }}:{{ pq }}
作用将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象,提供给外部使用
批量处理只能处理一层数据,深层的单独取出,在setup()中可以用return { ...toRefs(object)}的方式,将整个响应式对象object的所有属性提供给外部使用。
watch 的功能和之前的 Vue 2.0 的 watch 是一样的。和 watchEffect 相比较,区别在 watch 必须指定一个特定的变量,并且不会默认执行回调函数,而是等到监听的变量改变了,才会执行。并且你可以拿到改变前和改变后的值
watch有三个参数:
参数1:监听的参数
参数2:监听的回调函数
参数3:监听的配置(immediate)
我是TestA组件
当前求和为:{{sum}}
现在页面展示信息:{{info}}
姓名:{{obj.name}}
年龄:{{obj.age}}
监视reactive所定义的响应式数据中的某一个值和监视reactive所定义的响应式数据中的一些数据的改变区别于直接对reactive所定义的响应数据中所有数据进行监视,在默认情况下,监视reactive所定义的响应数据中所有数据是开启深度监视的,也就是说,无论数据在第几层,都能监视到,但是最后情况,是针对其中某一个值或某一些值进行监视的,如果还要监视其属性下的更深层的值,是要开启深度监视的,否则无法监视得到
它是立即执行的,在页面加载时会主动执行一次,来收集依赖
不需要传递需要侦听的内容,它可以自动感知代码依赖,只需要传递一个回调函数
它不能获取之前数据的值
它的返回值用来停止此监听
{{state.search}}
watchEffect 函数返回一个新的函数,我们可以通过执行这个函数或者当组件被卸载的时候,来停止监听行为
setup() {
let timer = nulllet state = reactive({
search: Date.now()
})
// 返回停止函数const stop = watchEffect((onInvalidate) => {
console.log(`监听查询字段${state.search}`)
})
consthandleSearch = () => {
state.search = Date.now()
}
setTimeout(() => {
console.log('执行 stop 停止监听')
stop() // 2 秒后停止监听行为
}, 2000)
return {
state,
handleSearch
}
}
watchEffect 的回调方法内有一个很重要的方法,用于清除副作用。它接受的回调函数也接受一个函数 onInvalidate。重要的是它将会在 watchEffect 监听的变量改变之前被调用一次
export default {
setup () {
const state = reactive({
search: Date.now()
})
// 返回停止函数
const stop = watchEffect((onInvalidate) => {
console.log(`监听查询字段${state.search}`)
onInvalidate(
() => {
console.log('执行 onInvalidate')
})
})
const handleSearch = () => {
state.search = Date.now()
}
return {
state,
handleSearch
}
}
}
只监听.value属性的值的变化,对象内部的某一个属性改变时并不会触发更新,只有当更改value为对象重新赋值时才会触发更新
const foo = shallowRef({
c: 1,
})
const change = () => {
foo.value.c = 2// 视图不更新
foo.value={a:1} // 视图更新
}
只监听对象的第一层属性,对嵌套的对象不做响应式处理
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
const change = () => {
state.foo = 2// 视图更新
state.nested={count:2}// 视图更新
state.nested.bar =3// 视图不更新
}
当构建前端应用时,我们常常需要复用公共任务的逻辑。例如为了在不同地方格式化时间而抽取一个可复用的函数。这个格式化函数封装了无状态的逻辑:它在接收一些输入后立刻返回所期望的输出。
本质是一个函数,将setup函数中的composition API进行了封装
类似vue2的mixin
复用代码,是setup中的逻辑更清楚易懂
需求:鼠标经过打印出当前经过点的坐标
Mouse position is at: {{ x }}, {{ y }}
import { ref, onMounted, onUnmounted } from'vue'
// 按照惯例,组合式函数名以“use”开头exportfunctionuseMouse() {
// 被组合式函数封装和管理的状态
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
functionupdate(event) {
x.value = event.pageX
y.value = event.pageY
}
// 一个组合式函数也可以挂靠在所属组件的生命周期上
// 来启动和卸载副作用
onMounted(() =>window.addEventListener('mousemove', update))
onUnmounted(() =>window.removeEventListener('mousemove', update))
// 通过返回值暴露所管理的状态
return { x, y }
}
Mouse position is at: {{ x }}, {{ y }}
还可以嵌套多个组合式函数:一个组合式函数可以调用一个或多个其他的组合式函数。这使得我们可以像使用多个组件组合成整个应用一样,用多个较小且逻辑独立的单元来组合形成复杂的逻辑。实际上,这正是我们决定将实现了这一设计模式的 API 集合命名为组合式 API 的原因。
import { onMounted, onUnmounted } from'vue'
export function useEventListener(target, event, callback) {
// 如果你想的话,
// 也可以用字符串形式的 CSS 选择器来寻找目标 DOM 元素
onMounted(() => target.addEventListener(event, callback))
onUnmounted(() => target.removeEventListener(event, callback))
}
import { ref } from'vue'import { useEventListener } from'./event'
export functionuseMouse() {
const x = ref(0)
const y = ref(0)
// 组合式函数可以随时更改其状态。
functionupdate(event) {
x.value = event.pageX
y.value = event.pageY
}
useEventListener(window, 'mousemove', update)
return { x, y }
}
综上所诉,核心逻辑一点都没有被改变,我们做的只是把它移到一个外部函数中去,并返回需要暴露的状态。和在组件中一样,你也可以在组合式函数中使用所有的组合式API函数。现在,在任何组件中都可以使用 useMouse() 功能了。
mixin向外暴露出的是一个对象,hooks则是一个可传参数的方法
Mixin 命名容易发生冲突:因为每个 mixin 的变量和方法都被合并到同一个组件中,所以为了避免变量名和方法名冲突,仍然需要了解其他每个特性;Mixin同名变量会被覆盖,Vue3自定义Hook可以在引入的时候对同名变量重命名
可重用性是有限的:我们不能向 mixin 传递任何参数来改变它的逻辑,这降低了它们在抽象逻辑方面的灵活性。
Mixin不明的混淆,我们根本无法获知属性来自于哪个Mixin文件,给后期维护带来困难
export default {
mixins: [ a, b, c, d, e, f, g ], //一个组件内可以混入各种功能的Mixinmounted() {
console.log(this.name) //问题来了,这个name是来自于哪个mixin?
}
}
一个组件选项,在组件被创建之前,props 被解析之后执行。它是composition API(组合式 API) 的入口
setup的执行周期在beforeCreate之前,因此this为undefined
setup 是一个新的配置项,值是一个函数
所有的composition Api都放在setup里面
必须要有返回值,值是一个对象,在模板中直接使用
export default {
props: {
title: String
},
setup(props,context) {
console.log(props.title)
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数) ****** 查询下二是否有
console.log(context.expose)
}
}
- **添加响应性**
为了给 provide/inject 添加响应性,使用 ref 或 reactive 。
完整实例2:provide/inject 响应式
```vue
//父组件代码
info:{{info}}
// InjectCom 子组件代码
{{info}}
import { readonly } from "vue"
let info = readonly('只读info值')
setTimout(()=>{
info="更新info"//两秒后更新info的值
},2000)
provide('info', readonly(info))
在Vue2 中:组件必须有一个根标签
在Vue3 中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
好处:减少标签层架,减少内存占用,并且该标签不会出现在dom树中。
Teleport是一种能将我们的组件html结构移动到指定位置的技术。
好处:当我们使用组件时,不用担心展开模态框会破坏页面结构
父组件App.vue:
这是祖先组件
子组件ChildComponent.vue:
这是孩子组件
孙组件GrandsonAssembly.vue:(用到了A模块框组件)
这是孙子组件
A模块框组件ModalBox.vue:
这是一些内容
这是一些内容
这是一些内容
这是一些内容
这是一些内容
类似于 keep-alive 的形式不需要任何的引入,可以直接进行使用。
自带两个 slot 分别为 default、fallback。顾名思义,当要加载的组件不满足状态时,Suspense 将回退到 fallback状态一直到加载的组件满足条件,才会进行渲染。
等待异步组件时渲染一些额外内容,有更好的用户体验
使用步骤
异步引入组件:
import { defineAsyncComponent } from"vue";
constChild = defineAsyncComponent(()=>import('./compoments/Child.vue'))
使用Suspense包裹组件,并配置好 default 与 fallback
我是App组件
加载中。。。
在 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。
11111
是为Vue 2和3服务的一套Vue Composition API的常用工具集
通俗的来说,这就是一个工具函数包,它可以帮助你快速实现一些常见的功能,免得你自己去写,解决重复的工作内容。以及进行了基于 Composition API 的封装
//isFullscreen 当前是否是全屏//toggle 是函数直接调用即可const { isFullscreen, toggle } = useFullscreen();
//text 粘贴的内容//copy 是粘贴函数const { text, copy, isSupported }
= useClipboard({ copiedDuring: 1500 });
const title = useTitle()
console.log(title.value) // print current title
title.value = 'Hello'// change current title
VueUse将所有方法按照功能性进行了分类,包含:Animation、Browser、Component、Formatters、Misc、Sensors、State、Utilities、Watch,详见vueuse.functions。其中较为常用的有:
useClipboard 复制到剪贴板
useFetch fetch 请求
useFullscreen 全屏
useLocalStorage localStorage 存储
useDebounceFn 防抖/节流
useThrottleFn