3.实践部分-vue3响应式原理分析

104 阅读2分钟

vue3核心流程

1.vue3核心过程:编译时=>运行时=>响应式数据=虚拟dom(vnode)=>dom渲染

2.vue3中关键包:compiler-core,compiler-dom,compiler-sfc,reactivity,runtime-core,runtime-dom

3.其中响应式和模版模版编译-关联的过程如下: image.png 那么总结起来就是三步:

  • 1.第一步获取我们写的template模版,通过complie编译成AST抽象语法树【】
  • 2.第二步通过createElement方法把ast转换为vnode
  • 3.第三步就是通过render方法把vnode变为真实dom

无奖问答:vue3为什么要先转换成vnode?目的是解决什么问题?有什么好处?

下面开始vue3 响应式模块的原理部分

1.为什么要使用响应式 reactive/ref?

目标:达到自动更新视图,当数据发生变化时,无需关心视图的更新操作逻辑;使得状态和视图操作解耦合

2.具体实现原理

核心思想:1.获取属性时 收集依赖; 2.数据变动,派发更新;【类比服务端能够劫持或代理更新和获取操作】

前置条件:数据劫持,数据代理。 使用代理对象方式,监听属性的获取和变更。 在vue2中使用object.definePropty 实现的,无法监听对象属性的增删或数组操作,在vue3 则使用更为高效的 proxy实现。

3.响应式系统定义和使用

vue playgroud组件定义

<script setup>
import { ref,reactive } from 'vue'

//const msg = ref('Hello World!')
//定义状态
const state =reactive({count:123})
//定义操作
const add=()=>state.count++;
</script>

<!-- 定义视图 -->
<template>
  <div>
<!-- 
   <h1>{{ msg }}</h1>

   <input v-model="msg" /> -->

   <h2>count:{{ state.count }}</h2>
   <button @click='add'>增加</button>

  </div>
  
</template>

<style scoped>
  div{
    color:red
  }
</style>

4.动手实践-响应式 [解决了一个问题:状态如何驱动和解耦视图] 1.原始方式==面向dom操作

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>


  <script type="module">
        //定义状态
        let state={count:1};

        //定义状态操作
        const add=()=>{
            state.count++;
        }

        const render = ()=>{
           document.body.innerHTML = `<div>${state.count}</div>`
        }
        //定义渲染方法 vue react 中都有vnode->dom
        render();

        //耦合操作
       window.addEventListener('click',()=>{
            //状态操作
            add();
            //重新渲染
            render();
        })
  </script>
</body>

</html>

2.响应式操作方式 index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>

  <div id="app"></div>
  <button id="btn">点击</button>
  <script type="module">
      import {effect,reactive,ref} from './reactive.js';
      const btn = document.getElementById('btn');
      const app = document.getElementById('app');
      const state = reactive({count:1})
      // let state = ref(1);

      //
      const render = ()=>{
          app.innerHTML = `nums=>${state.count}`
      }
      //用来跟踪和响应数据的变化指入口
      effect(render)
      //事件触发和视图更新解耦
      btn.addEventListener('click',()=>{
          state.count++;
      })
  </script>
</body>
</html>

reactive.js


//依赖map
/**
 * {counter:[fn1,fn2],counter2:[fn3,fn4]}
 */
//当前effect
let __activeEffect=null;
//副作用收集
export const effect= (fn) => {
    console.log('Effect');
    __activeEffect = fn;
    fn();
    __activeEffect = null;
}
//响应式对象 监听obj变化
export const reactive = (obj) => {
    const triggerMap = new Map();
    console.log('Reactive');
    //依赖收集
    const handler= {
        get(target, key) {
            console.log('Get');
            const result = Reflect.get(target, key);
            if (__activeEffect) {
                // track(target, key);  //依赖收集 可单独抽离成api
                if(!triggerMap.has(key)) {
                    triggerMap.set(key, []);
                }
                triggerMap.get(key).push(__activeEffect);
            }
            return result;
        },
        //派发更新
        set(target, key, value) {
            console.log('Set');
            const oldValue = target[key];
            const result = Reflect.set(target, key, value);
            if (oldValue !== value) {
                //派发更新 //trigger(target, key);
                triggerMap.get(key).forEach((effect) => {
                    effect();
                })
            }
            return true;
        }}
    return new Proxy(obj,handler)}
export const ref = (value) => {
   return reactive({value});
}

5.响应式流程总结

image.png

源码大致位置

image.png vue reactive 源码关键位置:

image.png

github.com/vuejs/core/…