vue3学习笔记
1.创建一个vue3项目
1.创建vue cli
npm install -g @vue/cli2.创建项目
npm create <项目名称>
开始敲代码啦!!!
1.引用组件
只需要import就可以了,因为使用了setup之后引用了就会被自动成为子组件了。
2.声明数据
ref |
用于声明基本数据类型 |
reactive |
用于声明对象和数组 |
shallowReactive |
声明一个浅响应式数据,只有第一层跟属性是响应式的,其他的内部属性是非响应式的 |
readonly |
只读(适合放非响应式数据) |
shallowReadonly | 只有跟属性是只读的,其他的属性是响应式的 |
ref和reactive
ref reactive 内部工作原理 ref
是一个函数,它接受一个内部值并返回一个响应式且可变的引用对象。这个引用对象有一个.value
属性,该属性指向内部值。reactive
是一个函数,它接受一个对象并返回该对象的响应式代理,也就是Proxy
。核心 返回的是响应式且可变的引用对象
返回的是响应式代理
由于两者的本质上的核心区别,导致ref优于reactive
声明数据的响应式状态上,底层原理
ref
采用RefImpl
对象实例
reactive
采用Proxy
代理对象。代码实现 import { ref } from 'vue' const state = ref({ count: 0 }) state.value.count++ import { reactive } from 'vue' const state = reactive({ count: 0 }) state.count++源码 // 深响应式 export function ref(value?: unknown) { return createRef(value, false) } // 浅响应式 export function shallowRef(value?: unknown) { return createRef(value, true) } function createRef(rawValue: unknown, shallow: boolean) { // 如果传入的值已经是一个 ref,则直接返回它 if (isRef(rawValue)) { return rawValue } // 否则,创建一个新的 RefImpl 实例 return new RefImpl(rawValue, shallow) } class RefImpl<T> { // 存储响应式的值。我们追踪和更新的就是_value。(这个是重点) private _value: T // 用于存储原始值,即未经任何响应式处理的值。(用于对比的,这块的内容可以不看) private _rawValue: T // 用于依赖跟踪的 Dep 类实例 public dep?: Dep = undefined // 一个标记,表示这是一个 ref 实例 public readonly __v_isRef = true constructor( value: T, public readonly __v_isShallow: boolean, ) { // 如果是浅响应式,直接使用原始值,否则转换为非响应式原始值 this._rawValue = __v_isShallow ? value : toRaw(value) // 如果是浅响应式,直接使用原始值,否则转换为响应式值 this._value = __v_isShallow ? value : toReactive(value) // toRaw 用于将响应式引用转换回原始值 // toReactive 函数用于将传入的值转换为响应式对象。对于基本数据类型,toReactive 直接返回原始值。 // 对于对象和数组,toReactive 内部会调用 reactive 来创建一个响应式代理。 // 因此,对于 ref 来说,基本数据类型的值会被 RefImpl 直接包装,而对象和数组 // 会被 reactive 转换为响应式代理,最后也会被 RefImpl 包装。 // 这样,无论是哪种类型的数据,ref 都可以提供响应式的 value 属性, // 使得数据变化可以被 Vue 正确追踪和更新。 // export const toReactive = (value) => isObject(value) ? reactive(value) : value } get value() { // 追踪依赖,这样当 ref 的值发生变化时,依赖这个 ref 的组件或副作用函数可以重新运行。 trackRefValue(this) // 返回存储的响应式值 return this._value } set value(newVal) { // 判断是否应该使用新值的直接形式(浅响应式或只读) const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal) // 如果需要,将新值转换为非响应式原始值 newVal = useDirectValue ? newVal : toRaw(newVal) // 如果新值与旧值不同,更新 _rawValue 和 _value if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) // 触发依赖更新 triggerRefValue(this, DirtyLevels.Dirty, newVal) } } }
function reactive(target) { if (target && target.__v_isReactive) { return target } return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) } function createReactiveObject( target, isReadonly, baseHandlers, collectionHandlers, proxyMap ) { if (!isObject(target)) { return target } const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } const proxy = new Proxy(target, baseHandlers) proxyMap.set(target, proxy) return proxy }
源码解释 在上述代码中, ref
函数通过new RefImpl(value)
创建了一个新的RefImpl
实例。这个实例包含 getter 和 setter,分别用于追踪依赖和触发更新。使用ref
可以声明任何数据类型的响应式状态,包括对象和数组。reactive
通过new Proxy(target, baseHandlers)
创建了一个代理。这个代理会拦截对目标对象的操作,从而实现响应式。对比 推荐使用ref的优势
统一性
深层响应性
灵活性
统一性
ref提供了一致的方式处理所有类型的数据,无论是数字、字符串、对象还是数组,这种统一性极大的简化了开发者的代码,减少了在不同数据类型之前切换时的复杂性。 import { ref } from 'vue'; const num = ref(0); const str = ref('Hello'); const obj = ref({ count: 0 }); // 修改基本数据类型 num.value++; str.value += ' World'; // 修改对象 obj.value.count++;
深层响应性
ref支持深层响应性,所以ref可以追踪和更新嵌套对象和数组的变化。 这种特性使得ref非常适合处理复杂的数据结构,比如数组和对象。 import { ref } from 'vue'; const obj = ref({ user: { name: 'xiaoming', details: { age: 18 } } }); // 修改嵌套对象 obj.value.user.details.age++;
//为了减少大型不可变数据的响应式开销,也可以通过使用
shallowRef
来放弃深层响应性。const shallowObj = shallowRef({ details: { age: 18, }, });
灵活性
ref高度的灵活性在开发中的普通赋值和解构赋值时更方便尤其是对复杂的数据操作时。 import { ref } from 'vue'; const state = ref({ count: 0, name: 'Vue' }); // 解构赋值 const { count, name } = state.value; // 直接修改解构后的变量 count++; name = 'Vue3'; // 替换整个对象 state.value = { count: 10, name: 'Vue4' };
有局限性:
仅对引用数据类型有效
使用不当会失去响应
仅对引用数据类型有效
reactive 主要适用于对象,包括数组和一些集合类型(如 Map 和 Set)。对于基础数据类型(如 string、number 和 boolean),reactive 是无效的。
这意味着如果你尝试使用 reactive 来处理这些基础数据类型,将会得到一个非响应式的对象。 import { reactive } from 'vue'; const state = reactive({ count: 0 });使用不当会失去响应
1.直接赋值对象:如果直接将一个响应式对象赋值给另一个变量,将会失去响应性。这是因为 reactive 返回的是对象本身,而不仅仅是代理。 import { reactive } from 'vue'; const state = reactive({ count: 0 }); state = { count: 1 }; // 失去响应性 ``` 2.直接替换响应式对象:同样,直接替换一个响应式对象也会导致失去响应性。 import { reactive } from 'vue'; const state = reactive({ count: 0 }); state = reactive({ count: 1 }); // 失去响应性 ``` 3.直接解构对象:在解构响应式对象时,如果直接解构对象属性,将会得到一个非响应式的变量。 const state = reactive({ count: 0 }); let { count } = state; count++; // count 仍然是 0 ``` 好家伙!常用的解构赋值不能用。为了解决这个问题,需要使用 toRefs 函数来将响应式对象转换为 ref 对象。 import { toRefs } from 'vue'; const state = reactive({ count: 0 }); let { count } = toRefs(state); count++; // count 现在是 1 ``` 首先来说,太不方便了!而且使用toRefs(),将响应式变量换成 ref 的形式,那我还不如直接使用ref()了,大家说是不是? 4.将响应式对象的属性赋值给变量:如果将响应式对象的属性赋值给一个变量,这个变量的值将不会是响应式的。 const state = reactive({ count: 0 }) let count = state.count
count++ // count 仍然是 0 ```使用
reactive
声明响应式变量的确存在一些不便之处,尤其是对于喜欢使用解构赋值的开发者而言。这些局限性可能会导致意外的行为,因此在使用reactive
时需要格外注意。相比之下,ref
API 提供了一种更灵活和统一的方式来处理响应式数据。
7.computed:计算属性
8.watch:侦听器
import { reactive } from 'vue'
const state = reactive({ count: 0 })
state.count++