목차
반응형
总结
웹 프론트엔드 View.js Vue3 반응형 시스템을 손으로 쓰는 방법

Vue3 반응형 시스템을 손으로 쓰는 방법

May 14, 2023 am 09:40 AM
vue3

반응형

먼저 반응형이란 무엇일까요?

반응성은 관찰된 데이터가 변경될 때 일련의 연결 처리를 수행하는 것입니다. 화끈한 소셜 이벤트처럼 업데이트가 있으면 각방의 미디어가 후속 조치를 취하고 관련 보도를 할 것입니다. 여기서 사회적으로 핫한 이벤트는 관찰 대상이다. 그렇다면 프론트엔드 프레임워크에서 관찰되는 타겟은 무엇입니까? 분명히 그것은 국가이다. 일반적으로 개체별로 구성되는 여러 상태가 있습니다. 따라서 상태 객체의 각 키의 변화를 관찰하고 일련의 과정을 연동하여 수행할 수 있습니다.

우리는 다음과 같은 데이터 구조를 유지해야 합니다:

Vue3 반응형 시스템을 손으로 쓰는 방법

상태 객체의 각 키에는 연관된 일련의 효과 부작용 기능이 있습니다. 즉, 변경 시 연결된 실행 논리가 Set으로 구성됩니다. .

각 키는 일련의 효과 기능과 연결되어 있으며 여러 키를 맵에서 관리할 수 있습니다.

이 맵은 물체가 존재하면 존재합니다. 물체가 파괴되면 역시 파괴됩니다. (객체가 사라지기 때문에 각 키에 관련된 효과를 유지할 필요가 없습니다.)

그리고 WeakMap에는 바로 이러한 기능이 있습니다. WeakMap의 키는 객체여야 하며, 값은 어떤 데이터라도 될 수 있습니다. 키 객체가 파괴되면 값도 파괴됩니다.

그래서 반응형 맵은 WeakMap을 사용하여 저장되며 키는 원본 객체입니다.

이 데이터 구조는 반응성의 핵심 데이터 구조입니다.

예를 들어 다음 상태 개체는

const obj = {
    a: 1,
    b: 2
}
로그인 후 복사

반응형 데이터 구조는 다음과 같습니다.

const depsMap = new Map();
const aDeps = new Set();
depsMap.set('a', aDeps);
const bDeps = new Set();
depsMap.set('b', bDeps);
const reactiveMap = new WeakMap()
reactiveMap.set(obj, depsMap);
로그인 후 복사

생성된 데이터 구조는 그림과 같습니다.

Vue3 반응형 시스템을 손으로 쓰는 방법

Vue3 반응형 시스템을 손으로 쓰는 방법

그런 다음 추가합니다. 예를 들어, a에 의존하는 함수는 a의 deps 컬렉션에 추가해야 합니다:

effect(() => {
    console.log(obj.a);
});
로그인 후 복사

즉, 다음과 같습니다:

const depsMap = reactiveMap.get(obj);
const aDeps = depsMap.get('a');
aDeps.add(该函数);
로그인 후 복사

에서 deps 함수를 유지하는 데에는 문제가 없습니다. 하지만 사용자가 수동으로 deps를 추가해야 합니까? 이는 비즈니스 코드를 침해할 뿐만 아니라 놓치기 쉽습니다.

따라서 사용자는 deps를 수동으로 유지 관리하라는 요청을 받지 않고 자동 종속성 수집이 수행됩니다. 그렇다면 종속성을 자동으로 수집하는 방법은 무엇입니까? 상태 값을 읽으면 상태와의 종속 관계가 설정되므로 상태를 프록시하는 데 사용할 수 있는 get 메서드를 생각하기 쉽습니다. Object.defineProperty 또는 프록시를 통해:

const data = {
    a: 1,
    b: 2
}
let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}
const reactiveMap = new WeakMap()
const obj = new Proxy(data, {
    get(targetObj, key) {
        let depsMap = reactiveMap.get(targetObj);
        
        if (!depsMap) {
          reactiveMap.set(targetObj, (depsMap = new Map()))
        }
        
        let deps = depsMap.get(key)
        
        if (!deps) {
          depsMap.set(key, (deps = new Set()))
        }
        deps.add(activeEffect)

        return targetObj[key]
   }
})
로그인 후 복사

효과는 전달된 콜백 함수 fn을 실행합니다. fn에서 obj.a를 읽으면 get이 트리거되고 해당 객체의 응답 맵이 획득됩니다. a에 현재 효과 기능을 추가합니다.

이것으로 종속성 수집이 완료됩니다.

obj.a를 수정하면 모든 deps에 알려야 하므로 프록시 설정도 필요합니다:

set(targetObj, key, newVal) {
    targetObj[key] = newVal
    const depsMap = reactiveMap.get(targetObj)
    if (!depsMap) return
    const effects = depsMap.get(key)
    effects && effects.forEach(fn => fn())
}
로그인 후 복사

기본 응답성이 완료되었으므로 테스트해 보겠습니다.

Vue3 반응형 시스템을 손으로 쓰는 방법

두 번 인쇄되었습니다. 첫 번째는 1, 두 번째는 3입니다. 효과는 먼저 수신 콜백 함수를 실행하고 get을 트리거하여 종속성을 수집합니다. 이때 인쇄된 obj.a는 1입니다. 그런 다음 obj.a에 값 3이 할당되면 수집된 종속성을 실행하기 위해 set이 트리거됩니다. . 현재 obj.a 는 3

종속성도 올바르게 수집되었습니다.

Vue3 반응형 시스템을 손으로 쓰는 방법

결과가 정확하여 기본 응답이 완료되었습니다! 물론, 반응성은 단지 이 작은 코드만으로는 충분하지 않습니다. 현재 구현은 완벽하지 않으며 여전히 몇 가지 문제가 있습니다. 예를 들어 코드에 분기 스위치가 있으면 마지막 실행은 obj.b에 의존하지만 다음 실행은 이에 의존하지 않습니다. 이때 잘못된 종속성이 있습니까?

이 코드 조각:

const obj = {
    a: 1,
    b: 2
}
effect(() => {
    console.log(obj.a ? obj.b : 'nothing');
});
obj.a = undefined;
obj.b = 3;
로그인 후 복사

효과 함수가 처음 실행될 때 obj.a는 1입니다. 이때 첫 번째 분기로 이동하여 obj.b에 의존합니다. obj.a를 정의되지 않은 상태로 수정하고 세트를 트리거하고 모든 종속 기능을 실행합니다. 이때 분기 2로 이동하여 더 이상 obj.b에 의존하지 않습니다.

obj.b를 3으로 변경합니다. 현재 b에 의존하는 함수가 없다는 것은 당연합니다.

Vue3 반응형 시스템을 손으로 쓰는 방법

2의 첫 번째 인쇄가 정확합니다. 첫 번째 브랜치에 도달하여 obj.b를 인쇄합니다

두 번째로 아무것도 인쇄하지 않는 것도 맞습니다. 이때 두 번째 브랜치로 이동합니다. 그러나 세 번째로 아무것도 인쇄하지 않는 것은 잘못된 것입니다. 왜냐하면 현재 obj.b에는 더 이상 종속 기능이 없지만 여전히 인쇄되기 때문입니다.

인쇄하여 deps를 보면 obj.b의 deps가 지워지지 않은 것을 알 수 있습니다

Vue3 반응형 시스템을 손으로 쓰는 방법

所以解决方案就是每次添加依赖前清空下上次的 deps。怎么清空某个函数关联的所有 deps 呢?记录下就好了。

我们改造下现有的 effect 函数:

let activeEffect
function effect(fn) {
  activeEffect = fn
  fn()
}
로그인 후 복사

记录下这个 effect 函数被放到了哪些 deps 集合里。也就是:

let activeEffect
function effect(fn) {
  const effectFn = () => {
      activeEffect = effectFn
      fn()
  }
  effectFn.deps = []
  effectFn()
}
로그인 후 복사

对之前的 fn 包一层,在函数上添加个 deps 数组来记录被添加到哪些依赖集合里。

get 收集依赖的时候,也记录一份到这里:

Vue3 반응형 시스템을 손으로 쓰는 방법

这样下次再执行这个 effect 函数的时候,就可以把这个 effect 函数从上次添加到的依赖集合里删掉:

Vue3 반응형 시스템을 손으로 쓰는 방법

cleanup 实现如下:

function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}
로그인 후 복사

effectFn.deps 数组记录了被添加到的 deps 集合,从中删掉自己。全删完之后就把上次记录的 deps 数组置空。

我们再来测试下:

Vue3 반응형 시스템을 손으로 쓰는 방법

无限循环打印了,什么鬼?

问题出现在这里:

Vue3 반응형 시스템을 손으로 쓰는 방법

set 的时候会执行所有的当前 key 的 deps 集合里的 effect 函数。

而我们执行 effect 函数之前会把它从之前的 deps 集合中清掉:

Vue3 반응형 시스템을 손으로 쓰는 방법

执行的时候又被添加到了 deps 集合。这样 delete 又 add,delete 又 add,所以就无限循环了。

解决的方式就是创建第二个 Set,只用于遍历:

Vue3 반응형 시스템을 손으로 쓰는 방법

这样就不会无限循环了。

再测试一次:

Vue3 반응형 시스템을 손으로 쓰는 방법

现在当 obj.a 赋值为 undefined 之后,再次执行 effect 函数,obj.b 的 deps 集合就被清空了,所以需改 obj.b 也不会打印啥。

看下现在的响应式数据结构:

Vue3 반응형 시스템을 손으로 쓰는 방법

确实,b 的 deps 集合被清空了。那现在的响应式实现是完善的了么?也不是,还有一个问题:

如果 effect 嵌套了,那依赖还能正确的收集么?

首先讲下为什么要支持 effect 嵌套,因为组件是可以嵌套的,而且组件里会写 effect,那也就是 effect 嵌套了,所以必须支持嵌套。

我们嵌套下试试:

effect(() => {
    console.log(&#39;effect1&#39;);
    effect(() => {
        console.log(&#39;effect2&#39;);
        obj.b;
    });
    obj.a;
});
obj.a = 3;
로그인 후 복사

按理说会打印一次 effect1、一次 effect2,这是最开始的那次执行。然后 obj.a 修改为 3 后,会触发一次 effect1 的打印,执行内层 effect,又触发一次 effect2 的打印。也就是会打印 effect1、effect2、effect1、effect2。

我们测试下:

Vue3 반응형 시스템을 손으로 쓰는 방법

打印了 effect1、effet2 这是对的,但第三次打印的是 effect2,这说明 obj.a 修改后并没有执行外层函数,而是执行的内层函数。为什么呢?

看下这段代码:

Vue3 반응형 시스템을 손으로 쓰는 방법

我们执行 effect 的时候,会把它赋值给一个全局变量 activeEffect,然后后面收集依赖就用的这个。

当嵌套 effect 的时候,内层函数执行后会修改 activeEffect 这样收集到的依赖就不对了。

怎么办呢?嵌套的话加一个栈来记录 effect 不就行了?

也就是这样:

Vue3 반응형 시스템을 손으로 쓰는 방법

执行 effect 函数前把当前 effectFn 入栈,执行完以后出栈,修改 activeEffect 为栈顶的 effectFn。

这样就保证了收集到的依赖是正确的。

这种思想的应用还是很多的,需要保存和恢复上下文的时候,都是这样加一个栈。

我们再测试一下:

Vue3 반응형 시스템을 손으로 쓰는 방법

现在的打印就对了。至此,我们的响应式系统就算比较完善了。

全部代码如下:

const data = {
    a: 1,
    b: 2
}
let activeEffect
const effectStack = [];
function effect(fn) {
  const effectFn = () => {
      cleanup(effectFn)
      activeEffect = effectFn
      effectStack.push(effectFn);
      fn()
      effectStack.pop();
      activeEffect = effectStack[effectStack.length - 1];
  }
  effectFn.deps = []
  effectFn()
}
function cleanup(effectFn) {
    for (let i = 0; i < effectFn.deps.length; i++) {
        const deps = effectFn.deps[i]
        deps.delete(effectFn)
    }
    effectFn.deps.length = 0
}
const reactiveMap = new WeakMap()
const obj = new Proxy(data, {
    get(targetObj, key) {
        let depsMap = reactiveMap.get(targetObj)
        
        if (!depsMap) {
          reactiveMap.set(targetObj, (depsMap = new Map()))
        }
        let deps = depsMap.get(key)
        if (!deps) {
          depsMap.set(key, (deps = new Set()))
        }
        deps.add(activeEffect)
        activeEffect.deps.push(deps);
        return targetObj[key]
   },
   set(targetObj, key, newVal) {
        targetObj[key] = newVal
        const depsMap = reactiveMap.get(targetObj)
        if (!depsMap) return
        const effects = depsMap.get(key)
        // effects && effects.forEach(fn => fn())
        const effectsToRun = new Set(effects);
        effectsToRun.forEach(effectFn => effectFn());
    }
})
로그인 후 복사

总结

响应式就是数据变化的时候做一系列联动的处理。

核心是这样一个数据结构:

Vue3 반응형 시스템을 손으로 쓰는 방법

最外层是 WeakMap,key 为对象,value 为响应式的 Map。这样当对象销毁时,Map 也会销毁。Map 里保存了每个 key 的依赖集合,用 Set 组织。

我们通过 Proxy 来完成自动的依赖收集,也就是添加 effect 到对应 key 的 deps 的集合里。 set 的时候触发所有的 effect 函数执行。

这就是基本的响应式系统。

但是还不够完善,每次执行 effect 前要从上次添加到的 deps 集合中删掉它,然后重新收集依赖。这样可以避免因为分支切换产生的无效依赖。并且执行 deps 中的 effect 前要创建一个新的 Set 来执行,避免 add、delete 循环起来。此外,为了支持嵌套 effect,需要在执行 effect 之前把它推到栈里,然后执行完出栈。解决了这几个问题之后,就是一个完善的 Vue 响应式系统了。当然,现在虽然功能是完善的,但是没有实现 computed、watch 等功能,之后再实现。

最后,再来看一下这个数据结构,理解了它就理解了 vue 响应式的核心:

Vue3 반응형 시스템을 손으로 쓰는 방법

위 내용은 Vue3 반응형 시스템을 손으로 쓰는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

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

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

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

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

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

vue3+vite: src에서 이미지를 동적으로 가져오기 위해 require를 사용할 때 오류를 해결하는 방법 vue3+vite: src에서 이미지를 동적으로 가져오기 위해 require를 사용할 때 오류를 해결하는 방법 May 21, 2023 pm 03:16 PM

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

vue3 프로젝트에서tinymce를 사용하는 방법 vue3 프로젝트에서tinymce를 사용하는 방법 May 19, 2023 pm 08:40 PM

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

Vue3가 마크다운을 구문 분석하고 코드 강조 표시를 구현하는 방법 Vue3가 마크다운을 구문 분석하고 코드 강조 표시를 구현하는 방법 May 20, 2023 pm 04:16 PM

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

Vue3에서 페이지의 일부 콘텐츠를 새로 고치는 방법 Vue3에서 페이지의 일부 콘텐츠를 새로 고치는 방법 May 26, 2023 pm 05:31 PM

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

vue3 프로젝트가 패키징되어 서버에 게시된 후 액세스 페이지가 공백으로 표시되는 문제를 해결하는 방법 vue3 프로젝트가 패키징되어 서버에 게시된 후 액세스 페이지가 공백으로 표시되는 문제를 해결하는 방법 May 17, 2023 am 08:19 AM

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

Vue3에서 아바타를 선택하고 자르는 방법 Vue3에서 아바타를 선택하고 자르는 방법 May 29, 2023 am 10:22 AM

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

Vue3 재사용 가능한 구성 요소를 사용하는 방법 Vue3 재사용 가능한 구성 요소를 사용하는 방법 May 20, 2023 pm 07:25 PM

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

DefineCustomElement를 사용하여 Vue3에서 구성 요소를 정의하는 방법 DefineCustomElement를 사용하여 Vue3에서 구성 요소를 정의하는 방법 May 28, 2023 am 11:29 AM

Vue를 사용하여 사용자 정의 요소 구축 WebComponents는 개발자가 재사용 가능한 사용자 정의 요소(customelements)를 생성할 수 있는 웹 네이티브 API 세트의 집합적 이름입니다. 사용자 정의 요소의 주요 이점은 프레임워크 없이도 어떤 프레임워크에서도 사용할 수 있다는 것입니다. 다른 프런트 엔드 기술 스택을 사용하는 최종 사용자를 대상으로 하거나 사용하는 구성 요소의 구현 세부 사항에서 최종 애플리케이션을 분리하려는 경우에 이상적입니다. Vue와 WebComponents는 보완적인 기술이며 Vue는 사용자 정의 요소를 사용하고 생성하는 데 탁월한 지원을 제공합니다. 사용자 정의 요소를 기존 Vue 애플리케이션에 통합하거나 Vue를 사용하여 빌드할 수 있습니다.

See all articles