ホームページ >ウェブフロントエンド >Vue.js >vue3のreactiveとrefの違いを詳しく解説(ソースコード解析)
vue の reactive と ref の違いは何ですか?次の記事では、vue3 の reactive と ref の違いを徹底的に理解するために、ソース コードを詳しく説明します。お役に立てば幸いです。
vue3 の日々の開発では、多くの人が自分の習慣に基づいてシャトルを使用していることがわかりましたreactive
orref
これで要件を満たすことはできますが、その場合、すでに reactive
があるのに、なぜ別の ref
を設計する必要があるのでしょうか?実際のアプリケーション シナリオと 2 つの違いは何ですか?
そして、ref
の基礎となるロジックに関して、ref
の基礎となるロジックは依然として reactive
であると言う人もいます。 ref
の最下層は class
であり、value
は class
の単なる属性であると言う人もいます。そうですか?毛織物?これには何か根拠があるのでしょうか?
この質問では、今回はソースコードを深く掘り下げて、vue3のreactive
とref
の違いを徹底的に理解していきます。 (学習ビデオ共有: vue ビデオ チュートリアル )
ソース コードを見たくない場合は、後ろにスクロールして概要を読むことができます
ソースコードアドレス: packages/reactivity/reactive.ts
まず見てみましょうvue3
ReactiveFlags
<pre class="brush:php;toolbar:false">// 标记目标对象 target 类型的 ReactiveFlags
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean // 不做响应式处理的数据
[ReactiveFlags.IS_REACTIVE]?: boolean // target 是否是响应式
[ReactiveFlags.IS_READONLY]?: boolean // target 是否是只读
[ReactiveFlags.RAW]?: any // 表示proxy 对应的源数据, target 已经是 proxy 对象时会有该属性
}</pre>
reactiveexport function reactive<t>(target: T): UnwrapNestedRefs<t>
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
// 如果目标对象是一个只读的响应数据,则直接返回目标对象
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
// 创建 observe
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
)
}</t></t>
# で使用されるターゲットで## タイプ Objecttarget
reactive 関数は
target オブジェクトを受け取ります。
target オブジェクトが読み取り専用の場合は、戻り値が返されます。
を直接渡します。 #createReactiveObject
長くなっても心配しないでください。まず createReactiveObject の完全なコードを投稿してください。セクションに分けて読んでみましょう <pre class="brush:php;toolbar:false">/**
*
* @param target 目标对象
* @param isReadonly 是否只读
* @param baseHandlers 基本类型的 handlers
* @param collectionHandlers 主要针对(set、map、weakSet、weakMap)的 handlers
* @param proxyMap WeakMap数据结构
* @returns
*/
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<target>
) {
// typeof 不是 object 类型的,在开发模式抛出警告,生产环境直接返回目标对象
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
// 已经是响应式的就直接返回(取ReactiveFlags.RAW 属性会返回true,因为进行reactive的过程中会用weakMap进行保存,
// 通过target能判断出是否有ReactiveFlags.RAW属性)
// 例外:对reactive对象进行readonly()
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 对已经Proxy的,则直接从WeakMap数据结构中取出这个Proxy对象
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only a whitelist of value types can be observed.
// 只对targetTypeMap类型白名单中的类型进行响应式处理
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// proxy 代理 target
// (set、map、weakSet、weakMap) collectionHandlers
// (Object、Array) baseHandlers
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}</target></any></any></pre>
まず、createReactiveObject が 5 つのパラメータを受け取ることがわかります。
target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<target></target></any></any>
target ターゲット オブジェクト
isReadonly 読み取られるかどうか-only
#baseHandlers 基本型ハンドラー 配列、オブジェクトの処理
collectionHandlers セット、マップ、weakSet、weakMap# の処理
##proxyMapWeakMap データ構造には副作用関数が格納されます
ここでは主にReactiveFlags を使用します。RAW と ReactiveFlags.IS_REACTIVE
は、応答データである場合、オブジェクトは直接返されますif ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target }すでに Proxy
WeakMap# から Proxy オブジェクトを直接取り出します## データ構造と戻り値 <pre class="brush:php;toolbar:false"> const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}</pre>
ここでは、現在の
target
、Array## であるかどうかを確認します。 #,
Map,
Set
, WeakSet
、何もない場合は、直接応答処理を実行せずにオブジェクトを返します<pre class="brush:php;toolbar:false"> // 只对targetTypeMap类型白名单中的类型进行响应式处理
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}</pre>
検証タイプのロジック<pre class="brush:php;toolbar:false">function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}</pre>
すべての事前検証が完了したら、
proxy Agent
targetObject
A を使用できます。
三項演算子
# を通じて
TargetType.COLLECTION##(set,map,weakSet,weakMap) collectionHandlers#を使用します
(オブジェクト、配列)
baseHandlers
<pre class="brush:php;toolbar:false">// proxy 代理 target
// (set、map、weakSet、weakMap) collectionHandlers
// (Object、Array) baseHandlers
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy</pre>## を使用します
# これで、createReactiveObject
の実行ロジックが非常に明確になりました
の最後の
proxy をプロキシするにはどうすればよいですか?ここでは例として
baseHandlersbaseHandlers# を確認します。
#ソース コード アドレス:
packages/reactivity/baseHandlers.ts
reactive.ts
では、合計 4 つのハンドラーが導入
import { mutableHandlers, readonlyHandlers, shallowReactiveHandlers, shallowReadonlyHandlers } from './baseHandlers'
#mutableHandlers 変数処理##readonlyHandlers 読み取り専用処理
浅い観察 処理 (ターゲット オブジェクトの第 1 レベルのプロパティのみを観察します)
浅い観察 && 読み取り専用
<pre class="brush:php;toolbar:false">// 可变处理
// const get = /*#__PURE__*/ createGetter()
// const set = /*#__PURE__*/ createSetter()
// get、has、ownKeys 会触发依赖收集 track()
// set、deleteProperty 会触发更新 trigger()
export const mutableHandlers: ProxyHandler<object> = {
get, // 用于拦截对象的读取属性操作
set, // 用于拦截对象的设置属性操作
deleteProperty, // 用于拦截对象的删除属性操作
has, // 检查一个对象是否拥有某个属性
ownKeys // 针对 getOwnPropertyNames, getOwnPropertySymbols, keys 的代理方法
}</object></pre>
ここでの set
は createSetter()# に対応します。
最初にコードの完全版に移動します<pre class="brush:php;toolbar:false">/**
* 用于拦截对象的读取属性操作
* @param isReadonly 是否只读
* @param shallow 是否浅观察
* @returns
*/
function createGetter(isReadonly = false, shallow = false) {
/**
* @param target 目标对象
* @param key 需要获取的值的键值
* @param receiver 如果遇到 setter,receiver 则为setter调用时的this值
*/
return function get(target: Target, key: string | symbol, receiver: object) {
// ReactiveFlags 是在reactive中声明的枚举值,如果key是枚举值则直接返回对应的布尔值
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (
// 如果key是raw receiver 指向调用者,则直接返回目标对象。
// 这里判断是为了保证触发拦截 handle 的是 proxy 本身而不是 proxy 的继承者
// 触发拦的两种方式:一是访问 proxy 对象本身的属性,二是访问对象原型链上有 proxy 对象的对象的属性,因为查询会沿着原型链向下找
key === ReactiveFlags.RAW &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {
return target
}
const targetIsArray = isArray(target)
// 如果目标对象 不为只读、是数组、key属于arrayInstrumentations:['includes', 'indexOf', 'lastIndexOf']方法之一,即触发了这三个方法之一
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
// 通过 proxy 调用,arrayInstrumentations[key]的this一定指向 proxy
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
// 如果 key 是 symbol 内置方法,或者访问的是原型对象__proto__,直接返回结果,不收集依赖
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 不是只读类型的 target 就收集依赖。因为只读类型不会变化,无法触发 setter,也就会触发更新
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是浅观察,不做递归转化,就是说对象有属性值还是对象的话不递归调用 reactive()
if (shallow) {
return res
}
// 如果get的结果是ref
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
// 返回 ref.value,数组除外
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 由于 proxy 只能代理一层,如果子元素是对象,需要递归继续代理
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}</pre>
長く見えますが、最終的には
Dependency collectiontrack()
Dependency collection の内容が多すぎます。と
trigger() は一緒に更新をトリガーするため、別の記事を書きます。<pre class="brush:php;toolbar:false">/**
* 拦截对象的设置属性操作
* @param shallow 是否是浅观察
* @returns
*/
function createSetter(shallow = false) {
/**
* @param target 目标对象
* @param key 设置的属性名称
* @param value 要改变的属性值
* @param receiver 如果遇到setter,receiver则为setter调用时的this值
*/
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
let oldValue = (target as any)[key]
// 如果模式不是浅观察模式
if (!shallow) {
// 拿新值和老值的原始值,因为新传入的值可能是响应式数据,如果直接和 target 上原始值比较是没有意义的
value = toRaw(value)
oldValue = toRaw(oldValue)
// 目标对象不是数组,旧值是ref,新值不是ref,则直接赋值,这里提到ref
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
// 检查对象是否有这个属性
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) <p><code>trigger()</code>触发更新</p><h2 data-id="heading-7"><strong>ref</strong></h2><p>源码地址:<code>packages/reactivity/src/ref.ts</code></p><p>接收一个可选<code>unknown</code>,接着直接调用<code>createRef()</code></p><pre class="brush:php;toolbar:false">export function ref(value?: unknown) {
return createRef(value, false)
}</pre><p><img src="https://img.php.cn/upload/image/632/159/341/1661168925472417.png" title="1661168925472417.png" alt="vue3のreactiveとrefの違いを詳しく解説(ソースコード解析)"></p><p>与<code>ref
的区别就是在调用createRef()
时第二个值传的是true
export function shallowRef(value?: unknown) { return createRef(value, true) }
看一下官方文档上对shallowRef
的解释
createRef
通过isRef()
判断是否是ref
数据,是则直接返回该数据,不是则通过new RefImpl
创建ref数据
在创建时会传两个值一个是rawValue
(原始值),一个是shallow
(是否是浅观察),具体使用场景可看上面ref
和shallowRef
的介绍
function createRef(rawValue: unknown, shallow: boolean) { // 是否是 ref 数据 if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) }
isRef()
通过__v_isRef
只读属性判断是否是ref数据,此属性会在RefImpl
创建ref数据时添加
export function isRef(r: any): r is Ref { return Boolean(r && r.__v_isRef === true) }
RefImpl
class RefImpl<t> { private _value: T private _rawValue: T public dep?: Dep = undefined // 只读属性 __v_isRef 判断是否是ref数据的静态标识 public readonly __v_isRef = true constructor(value: T, public readonly _shallow: boolean) { this._rawValue = _shallow ? value : toRaw(value) // 非浅观察用toRaw()包裹原始值 this._value = _shallow ? value : toReactive(value) // 非浅观察用toReactive()处理数据 } get value() { // 依赖收集 trackRefValue(this) return this._value } set value(newVal) { newVal = this._shallow ? newVal : toRaw(newVal) // 非浅观察用toRaw()包裹值 // 两个值不相等 if (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : toReactive(newVal) triggerRefValue(this, newVal) // 触发依赖,派发更新 } } }</t>
根据RefImpl
我们可以看到ref
的底层逻辑,如果是对象确实会使用reactive
进行处理,并且ref
的创建使用的也是RefImpl
class实例,value只是RefImpl
的属性
在我们访问
和设置
ref
的value值时,也分别是通过get
和set
拦截进行依赖收集
和派发更新
的
toReactive
我们来看一下toReactive()
这个方法,在RefImpl
中创建ref
数据时会调用toReactive()
方法,这里会先判断传进来的值是不是对象,如果是就用reactive()
包裹,否则就返回其本身
export const toReactive = <t>(value: T): T => isObject(value) ? reactive(value) : value</t>
trackRefValue
ref的依赖收集方法
export function trackRefValue(ref: RefBase<any>) { if (isTracking()) { ref = toRaw(ref) if (!ref.dep) { ref.dep = createDep() } if (__DEV__) { trackEffects(ref.dep, { target: ref, type: TrackOpTypes.GET, key: 'value' }) } else { trackEffects(ref.dep) } } }</any>
triggerRefValue
ref的派发更新方法
export function triggerRefValue(ref: RefBase<any>, newVal?: any) { ref = toRaw(ref) if (ref.dep) { if (__DEV__) { triggerEffects(ref.dep, { target: ref, type: TriggerOpTypes.SET, key: 'value', newValue: newVal }) } else { triggerEffects(ref.dep) } } }</any>
看完reactive
和ref
源码,相信对本文一开始的几个问题也都有了答案,这里也总结了几个问题:
答:ref底层会通过 new RefImpl()
来创造ref数据,在new RefImpl()
会首先给数据添加__v_isRef
只读属性用来标识ref
数据。而后判断传入的值是否是对象,如果是对象则使用toReactive()
处理成reactive
,并将值赋给RefImpl()
的value
属性上。在访问
和设置
ref数据的value
时会分别触发依赖收集
和派发更新
流程。
reactive
处理数据答:RefImpl中非浅观察会调用toReactive()
方法处理数据,toReactive()
中会先判断传入的值是不是一个对象,如果是对象则使用reactive
进行处理,不是则直接返回值本身。
reactive
还需要在设计一个ref
呢?答: 因为vue3响应式方案使用的是proxy
,而proxy
的代理目标必须是非原始值,没有任何方式能去拦截对原始值
的操作,所以就需要一层对象作为包裹,间接实现原始值的响应式方案。
ref
数据必须要有个value
属性,访问ref数据必须要通过.value
的方式呢?答:这是因为要解决响应式丢失的问题
,举个例子:
// obj是响应式数据 const obj = reactive({ foo: 1, bar: 2 }) // newObj 对象下具有与 obj对象同名的属性,并且每个属性值都是一个对象 // 该对象具有一个访问器属性 value,当读取 value的值时,其实读取的是 obj 对象下相应的属性值 const newObj = { foo: { get value() { return obj.foo } }, bar: { get value() { return obj.bar } } } effect(() => { // 在副作用函数内通过新对象 newObj 读取 foo 的属性值 console.log(newObj.foo) }) // 正常触发响应 obj.foo = 100
可以看到,在现在的newObj
对象下,具有与obj
对象同名的属性,而且每个属性的值都是一个对象,例如foo 属性的值是:
{ get value() { return obj.foo } }
该对象有一个访问器属性value
,当读取value的值时,最终读取的是响应式数据obj下的同名属性值
。也就是说,当在副作用函数内读取newObj.foo
时,等价于间接读取了obj.foo
的值。这样响应式数据就能够与副作用函数建立响应联系
(学習ビデオ共有: Web フロントエンド開発、基本プログラミング ビデオ)
以上がvue3のreactiveとrefの違いを詳しく解説(ソースコード解析)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。