Vue3 반응형 핵심 반응형 소스 코드 분석
1. 반응형 소스 코드
1. 반응형
소스 코드 경로: packages/reactivity/src/reactive.ts
packages/reactivity/src/reactive.ts
export function reactive(target: object) { // if trying to observe a readonly proxy, return the readonly version. // 是否是只读响应式对象 if (isReadonly(target)) { return target } return createReactiveObject( target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap ) }
当我们执行reactive({})
的时候,会执行createReactiveObject
这个工厂方法,返回一个响应式对象。
2、接着看工厂方法createReactiveObject
源码路径:packages/reactivity/src/reactive.ts
function createReactiveObject( target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>, proxyMap: WeakMap<Target, any> ) { // 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。 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 if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target } // target already has corresponding Proxy const existingProxy = proxyMap.get(target) if (existingProxy) { return existingProxy } // only specific value types can be observed. const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target } const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers ) proxyMap.set(target, proxy) return proxy }
仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
if (!isObject(target)) { if (__DEV__) { console.warn(`value cannot be made reactive: ${String(target)}`) } return target }
如果 target 已经是一个代理对象了,那么直接返回 target
if ( target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE]) ) { return target }
如果 target 已经有对应的代理对象了,那么直接返回代理对象
const existingProxy = proxyMap.get(target) // 存储响应式对象 if (existingProxy) { return existingProxy }
对于不能被观察的类型,直接返回 target
const targetType = getTargetType(target) if (targetType === TargetType.INVALID) { return target }
// getTargetType源码 function getTargetType(value: Target) { return value[ReactiveFlags.SKIP] || !Object.isExtensible(value) // 不可扩展 ? TargetType.INVALID : targetTypeMap(toRawType(value)) } // ReactiveFlags枚举 export const enum ReactiveFlags { // 用于标识一个对象是否不可被转为代理对象,对应的值是 __v_skip SKIP = '__v_skip', // 用于标识一个对象是否是响应式的代理,对应的值是 __v_isReactive IS_REACTIVE = '__v_isReactive', // 用于标识一个对象是否是只读的代理,对应的值是 __v_isReadonly IS_READONLY = '__v_isReadonly', // 用于标识一个对象是否是浅层代理,对应的值是 __v_isShallow IS_SHALLOW = '__v_isShallow', // 用于保存原始对象的 key,对应的值是 __v_raw RAW = '__v_raw' } // targetTypeMap 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 } } // toRawType export const toRawType = (value: unknown): string => { // extract "RawType" from strings like "[object RawType]" return toTypeString(value).slice(8, -1) }
创建响应式对象(核心代码)
const proxy = new Proxy( target, targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers )
接下来将重点讲解baseHandlers
这个回调函数。
二、baseHandlers
1、baseHandlers
baseHandlers
是mutableHandlers
, 来自于 baseHandlers
export const mutableHandlers: ProxyHandler<object> = { get, set, deleteProperty, has, ownKeys }
reactive({})</code를 실행할 때 > 시간이 지나면 팩토리 메소드 <code>createReactiveObject
가 실행되고 반응형 객체가 반환됩니다. 2. 다음으로, 팩토리 메소드 createReactiveObject를 살펴보세요
소스 코드 경로: packages/reactivity/src/reactive.ts
const get = /*#__PURE__*/ createGetter() function createGetter(isReadonly = false, shallow = false) { // 闭包返回 get 拦截器方法 return function get(target: Target, key: string | symbol, receiver: object) { // 如果访问的是 __v_isReactive 属性,那么返回 isReadonly 的取反值 if (key === ReactiveFlags.IS_REACTIVE) { return !isReadonly // 如果访问的是 __v_isReadonly 属性,那么返回 isReadonly 的值 } else if (key === ReactiveFlags.IS_READONLY) { return isReadonly // 如果访问的是 __v_isShallow 属性,那么返回 shallow 的值 } else if (key === ReactiveFlags.IS_SHALLOW) { return shallow // 如果访问的是 __v_raw 属性,那么返回 target } else if ( key === ReactiveFlags.RAW && receiver === (isReadonly ? shallow ? shallowReadonlyMap : readonlyMap : shallow ? shallowReactiveMap : reactiveMap ).get(target) ) { return target } // target是否是数组 const targetIsArray = isArray(target) if (!isReadonly) { // 可读 // 如果是数组,并且访问的是数组的一些方法,那么返回对应的方法 /** * Vue3中使用 arrayInstrumentations对数组的部分方法做了处理,为什么要这么做呢? * 对于 push、pop、 shift、 unshift、 splice 这些方法, * 写入和删除时底层会获取当前数组的length属性,如果我们在effect中使用的话, * 会收集length属性的依赖,当使用这些api是也会更改length,就会造成死循环: * */ if (targetIsArray && hasOwn(arrayInstrumentations, key)) { // 返回重写的push、pop、 shift、 unshift、 splice 'includes', 'indexOf', 'lastIndexOf' return Reflect.get(arrayInstrumentations, key, receiver) } // 如果访问的是 hasOwnProperty 方法,那么返回 hasOwnProperty 方法 if (key === 'hasOwnProperty') { return hasOwnProperty } } // 获取 target 的 key 属性值 const res = Reflect.get(target, key, receiver) // 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res } // 如果不是只读的,那么进行依赖收集 if (!isReadonly) { track(target, TrackOpTypes.GET, key) } // 如果是浅的,那么直接返回 res if (shallow) { return res } // 如果 res 是 ref,对返回的值进行解包 if (isRef(res)) { // ref unwrapping - skip unwrap for Array + integer key. return targetIsArray && isIntegerKey(key) ? res : res.value } // 如果 res 是对象,递归代理 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 } }
객체 유형(객체, 배열 및 컬렉션 유형)에만 유효합니다. Map 및 Set )이며 문자열, 숫자 및 부울과 같은 기본 유형에는 유효하지 않습니다.
if (targetIsArray && hasOwn(arrayInstrumentations, key)) { // 返回重写的push、pop、 shift、 unshift、 splice 'includes', 'indexOf', 'lastIndexOf' return Reflect.get(arrayInstrumentations, key, receiver) }
대상이 이미 프록시 객체인 경우 대상을 직접 반환const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()
function createArrayInstrumentations() {
const instrumentations: Record<string, Function> = {}
// instrument length-altering mutation methods to avoid length being tracked
// which leads to infinite loops in some cases (#2137)
;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
// 由于上面的方法会改变数组长度,因此暂停收集依赖,不然会导致无限递归
console.log('----自定义push等入口:this, args, key');
pauseTracking()
console.log('----自定义push等暂停收集依赖&执行开始')
// 调用原始方法
const res = (toRaw(this) as any)[key].apply(this, args)
console.log('----自定义push等暂停收集依赖&执行结束')
//复原依赖收集
resetTracking()
return res
}
})
return instrumentations
}
let arr = [1,2,3] let obj = { 'push': function(...args) { // 暂停收集依赖逻辑 return Array.prototype.push.apply(this, [...args]) // 启动收集依赖逻辑 } } let proxy = new Proxy(arr, { get: function (target, key, receiver) { console.log('get的key为 ===>' + key); let res = ''; if(key === 'push') { //重写push res = Reflect.get(obj, key, receiver) } else { res = Reflect.get(target, key, receiver) } return res }, set(target, key, value, receiver){ console.log('set的key为 ===>' + key, value); return Reflect.set(target, key, value, receiver); } }) proxy.push('99')
관찰할 수 없는 유형의 경우 대상을 직접 반환
// 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { return res; }
// 如果不是只读的,那么进行依赖收集 if (!isReadonly) { track(target, "get" /* TrackOpTypes.GET */, key); }
반응형 생성 객체(핵심 코드)
if (shallow) { return res; }
다음으로 콜백 함수 baseHandlers
에 집중하겠습니다.
2.baseHandlers
baseHandlers
는 baseHandlers
파일에서 오는 mutableHandlers
입니다. mutableHandlers의 소스 코드는 다음과 같으며 각각 get, set, deleteProperty, has 및 ownKeys를 프록시합니다. // 如果 res 是 ref,对返回的值进行解包
if (isRef(res)) {
// 对于数组和整数类型的 key,不进行解包
return targetIsArray && isIntegerKey(key) ? res : res.value;
}
// 如果 res 是对象,那么对返回的值进行递归代理 if (isObject(res)) { return isReadonly ? readonly(res) : reactive(res); }
의 프록시 대상이 배열인 경우 'push', 'pop', 'shift', 'unshift', 'splice' 메소드는 배열의 길이를 변경합니다. 그러면 무한 재귀가 발생하므로 먼저 종속성 수집을 일시 중지해야 하므로 위의 배열 메서드를 가로채서 다시 작성했습니다
const set = /*#__PURE__*/ createSetter() function createSetter(shallow = false) { // 返回一个set方法 return function set( target: object, key: string | symbol, value: unknown, receiver: object ): boolean { let oldValue = (target as any)[key] // 获取旧值 // 如果旧值是只读的,并且是 ref,并且新值不是 ref if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) { return false } if (!shallow) { // 非shallow // 新值非shallow && 非只读 if (!isShallow(value) && !isReadonly(value)) { // 获取新旧值的原始值 oldValue = toRaw(oldValue) value = toRaw(value) } // 代理对象非数组 & 旧值是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 } console.log('----set', target, key, value) // 是数组 & key是整型数字 ? // 如果 key 小于数组的长度,那么就是有这个 key : // 如果不是数组,那么就是普通对象,直接判断是否有这个 key // 数组会触发两次set: index和新增的值 和 'length'和新增之后的数组长度 const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key) // 设置key-value const result = Reflect.set(target, key, value, receiver) // don't trigger if target is something up in the prototype chain of original // 如果目标对象是原始数据的原型链中的某个元素,则不会触发依赖收集 if (target === toRaw(receiver)) { if (!hadKey) {// 如果没有这个 key,那么就是新增了一个属性,触发 add 事件 trigger(target, TriggerOpTypes.ADD, key, value) } else if (hasChanged(value, oldValue)) { // // 如果有这个 key,那么就是修改了一个属性,触发 set 事件 trigger(target, TriggerOpTypes.SET, key, value, oldValue) } } // 返回结果,这个结果为 boolean 类型,代表是否设置成功 return result } }
다시 작성된 코드:
let oldValue = target[key];
다음 그림은 실행 결과입니다.
다음 코드로 이해할 수 있습니다.
// 如果旧值是只读的,并且是 ref,并且新值不是 ref,那么直接返回 false,代表设置失败 if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) { return false; }
rrreee
이 단계는 Symbol.iterator, Symbol.toStringTag와 같은 기본 기호 유형 속성과 같은 일부 특수 속성을 필터링하는 것입니다. 이러한 속성은 내장되어 있고 변경되지 않으므로 종속성 수집이 필요하지 않습니다.proto
, __v_isRef, __isVue와 같은 추적할 수 없는 속성도 있습니다. 🎜종속성 수집🎜rrreee🎜Shallow는 수행되지 않습니다. 재귀 에이전트는 🎜rrreee🎜 반환 값을 언팩합니다. 🎜rrreee🎜 이 단계는 ref 상황을 처리하는 것입니다. 배열이고 키가 정수 유형이면 압축을 풀 필요가 없습니다. 반응형은 반응성이 뛰어나므로 ref🎜🎜객체의 재귀 프록시🎜rrreee🎜🎜(2) 속성을 사용하여 압축을 풀어야 합니다. set의 프록시🎜🎜rrreee🎜기본 논리: 🎜🎜이전 값 가져오기🎜rrreee🎜이전 값이 읽기 전용인지 확인🎜rrreee위 내용은 Vue3 반응형 핵심 반응형 소스 코드 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











vue3+vite:src는 require를 사용하여 이미지를 동적으로 가져오고 vue3+vite는 여러 이미지를 동적으로 가져옵니다. vue3을 사용하는 경우 require는 이미지를 사용할 수 없습니다. imgUrl:require(' .../assets/test.png') 와 같은 vue2는 typescript가 require를 지원하지 않기 때문에 가져오므로 이를 해결하는 방법은 다음과 같습니다. waitimport를 사용합니다.

tinymce는 완전한 기능을 갖춘 리치 텍스트 편집기 플러그인이지만,tinymce를 vue에 도입하는 것은 다른 Vue 리치 텍스트 플러그인만큼 원활하지 않습니다.tinymce 자체는 Vue에 적합하지 않으며 @tinymce/tinymce-vue를 도입해야 합니다. 외국 서식 있는 텍스트 플러그인이며 중국어 버전을 통과하지 못했습니다. 공식 웹사이트에서 번역 패키지를 다운로드해야 합니다(방화벽을 우회해야 할 수도 있음). 1. 관련 종속성을 설치합니다. npminstalltinymce-Snpminstall@tinymce/tinymce-vue-S2. 중국어 패키지를 다운로드합니다. 3. 프로젝트 공용 폴더에 스킨과 중국어 패키지를 새로 만들고 다운로드합니다.

페이지를 부분적으로 새로 고치려면 로컬 구성 요소(dom)의 다시 렌더링만 구현하면 됩니다. Vue에서 이 효과를 얻는 가장 쉬운 방법은 v-if 지시어를 사용하는 것입니다. Vue2에서는 v-if 명령을 사용하여 로컬 DOM을 다시 렌더링하는 것 외에도 새 빈 구성 요소를 만들 수도 있습니다. 로컬 페이지를 새로 고쳐야 할 경우 이 빈 구성 요소 페이지로 점프한 다음 다시 돌아올 수 있습니다. 빈 원본 페이지의 beforeRouteEnter 가드. 아래 그림과 같이 Vue3.X에서 새로 고침 버튼을 클릭하여 빨간색 상자 안에 DOM을 다시 로드하고 해당 로딩 상태를 표시하는 방법입니다. Vue3.X의 scriptsetup 구문에 있는 구성 요소의 가드에는

Vue로 블로그 프론트엔드를 구현하려면 마크다운 파싱을 구현해야 합니다. 코드가 있는 경우 코드 하이라이팅을 구현해야 합니다. markdown-it, vue-markdown-loader,marked,vue-markdown 등과 같은 Vue용 마크다운 구문 분석 라이브러리가 많이 있습니다. 이 라이브러리는 모두 매우 유사합니다. 여기서는 Marked가 사용되었고, 코드 하이라이팅 라이브러리로 하이라이트.js가 사용되었습니다. 구체적인 구현 단계는 다음과 같습니다. 1. 종속 라이브러리를 설치합니다. vue 프로젝트에서 명령 창을 열고 다음 명령 npminstallmarked-save//marked를 입력하여 markdown을 htmlnpmins로 변환합니다.

vue3 프로젝트가 패키징되어 서버에 게시되면 액세스 페이지에 공백 1이 표시됩니다. vue.config.js 파일의 publicPath는 다음과 같이 처리됩니다. const{defineConfig}=require('@vue/cli-service') module.exports=defineConfig({publicPath :process.env.NODE_ENV==='생산'?'./':'/&

최종 효과는 VueCropper 컴포넌트 Yarnaddvue-cropper@next를 설치하는 것입니다. 위의 설치 값은 Vue2이거나 다른 방법을 사용하여 참조하려는 경우 공식 npm 주소: 공식 튜토리얼을 방문하세요. 컴포넌트에서 참조하고 사용하는 것도 매우 간단합니다. 여기서는 해당 컴포넌트와 해당 스타일 파일을 소개하기만 하면 됩니다. 여기서는 import{userInfoByRequest}from'../js/api만 소개하면 됩니다. 내 구성 요소 파일에서 import{VueCropper}from'vue-cropper&

vue3+ts+axios+pinia는 무의미한 새로 고침을 실현합니다. 1. 먼저 프로젝트에서 aiXos 및 pinianpmipinia를 다운로드합니다--savenpminstallaxios--save2. AxiosResponse}from"axios";importaxiosfrom'axios';import{ElMess

머리말 Vue든 React든, 여러 개의 반복되는 코드를 접하게 되면, 파일을 중복된 코드 덩어리로 채우는 대신, 이러한 코드를 어떻게 재사용할 수 있을지 고민해 보겠습니다. 실제로 vue와 React 모두 컴포넌트를 추출하여 재사용할 수 있지만, 작은 코드 조각이 발견되어 다른 파일을 추출하고 싶지 않은 경우, 이에 비해 React는 동일한에서 사용할 수 있습니다. 파일에서 해당 위젯을 선언합니다. 또는 다음과 같은 renderfunction을 통해 구현합니다. constDemo:FC=({msg})=>{returndemomsgis{msg}}constApp:FC=()=>{return(
