手書きのVue2.0ソースコードレンダリングの更新の原則

序文

この記事は主に手書きのVue2.0ソースコードレンダリングの更新原理

オブザーバーモードは、ウォッチャーとDepを定義して依存関係の収集を完了し、更新をディスパッチしてレンダリングの更新を実現するために使用されます。

対象者:公式のソースコードを見る時間がない学生、またはかなり混乱していてソースコードを見たくない学生

ヒント:Vueソースコード全体が非常にコアコンテンツであるため、この記事は少し難しいです。フォローアップ計算プロパティ、カスタムウォッチャー、$ set $ deleteおよびその他のAPI実装は、この記事のアイデアを理解する必要があります。私はそれを理解するだけです。いつも、みんなが困難を乗り越えて一緒に実現してくれることを願っています!


テキスト

    <script>
      // Vue实例化
      let vm = new Vue({
        el: "#app",
        data() {
          return {
            a: 123,
          };
        },
        // render(h) {
        //   return h('div',{id:'a'},'hello')
        // },
        template: `<div id="a">hello {
   
   {a}}</div>`,
      });

        // 我们在这里模拟更新
      setTimeout(() => {
        vm.a = 456;
        // 此方法是刷新视图的核心
        vm._update(vm._render());
      }, 1000);
    </script>

前のコードでは、setTimeoutでvm._update(vm._render())を呼び出して、更新関数を実装しています。前の記事の初期レンダリングの原則から、このメソッドがレンダリングのコアであることがわかっているためですが、データが変更されるたびにユーザーに要求することはできません。レンダリングメソッドを呼び出してビューを更新します。データが変更されたときに自動的に更新するメカニズムが必要です。

1.ウォッチャーを定義する

// src/observer/watcher.js

// 全局变量id  每次new Watcher都会自增
let id = 0;

export default class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.cb = cb; //回调函数 比如在watcher更新之前可以执行beforeUpdate方法
    this.options = options; //额外的选项 true代表渲染watcher
    this.id = id++; // watcher的唯一标识
    // 如果表达式是一个函数
    if (typeof exprOrFn === "function") {
      this.getter = exprOrFn;
    }
    // 实例化就会默认调用get方法
    this.get();
  }
  get() {
    this.getter();
  }
}

オブザーバーフォルダーの下に新しいwatcher.jsを作成します。代表者はオブザーバーに関連しています。ここでは、最初にVueで使用されるオブザーバーモードを紹介しますウォッチャーをオブザーバーと見なすことができます。データ変更をサブスクライブする必要があります。データが変更すると、特定のメソッドを実行するように通知されます。本質的には、コンストラクターは初期化時にgetメソッドを実行します。

2.レンダリングウォッチャーを作成します

// src/lifecycle.js
export function mountComponent(vm, el) {
  //   _update和._render方法都是挂载在Vue原型的方法  类似_init

  // 引入watcher的概念 这里注册一个渲染watcher 执行vm._update(vm._render())方法渲染视图

  let updateComponent = () => {
    console.log("刷新页面");
    vm._update(vm._render());
  };
  new Watcher(vm, updateComponent, null, true);
}

コンポーネントのマウント方法でレンダリングウォッチャーを定義します。主な機能は、コアレンダリングページメソッドを実行することです。

3.深さを定義する

// src/observer/dep.js

// dep和watcher是多对多的关系

// 每个属性都有自己的dep

let id = 0; //dep实例的唯一标识
export default class Dep {
  constructor() {
    this.id = id++;
    this.subs = []; // 这个是存放watcher的容器
  }
}
// 默认Dep.target为null
Dep.target = null;

Depはコンストラクターでもあり、オブザーバーモードのオブザーバーとして理解できます。サブでウォッチャーを収集します。データが変更されると、すべてのウォッチャーの更新をサブに通知します。

Dep.targetは、初期状態がnullであることを示すグローバルウォッチャーです。

4.オブジェクトの依存関係のコレクション

// src/observer/index.js

// Object.defineProperty数据劫持核心 兼容性在ie9以及以上
function defineReactive(data, key, value) {
  observe(value);

  let dep = new Dep(); // 为每个属性实例化一个Dep

  Object.defineProperty(data, key, {
    get() {
      // 页面取值的时候 可以把watcher收集到dep里面--依赖收集
      if (Dep.target) {
        // 如果有watcher dep就会保存watcher 同时watcher也会保存dep
        dep.depend();
      }
      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      // 如果赋值的新值也是一个对象  需要观测
      observe(newValue);
      value = newValue;
      dep.notify(); // 通知渲染watcher去更新--派发更新
    },
  });
}

アピールコードは、収集と配布に依存することです。更新のコアは、実際には、データにアクセスしたときに定義済みのレンダリングウォッチャーをdepのsubs配列に配置し、レンダリングウォッチャーにdepインスタンスオブジェクトを配置して通知することです。データが更新されます。ウォッチャーの更新は、depのサブによって保存されます。

5.ウォッチャーを改善する

// src/observer/watcher.js

import { pushTarget, popTarget } from "./dep";

// 全局变量id  每次new Watcher都会自增
let id = 0;

export default class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    this.vm = vm;
    this.exprOrFn = exprOrFn;
    this.cb = cb; //回调函数 比如在watcher更新之前可以执行beforeUpdate方法
    this.options = options; //额外的选项 true代表渲染watcher
    this.id = id++; // watcher的唯一标识
    this.deps = []; //存放dep的容器
    this.depsId = new Set(); //用来去重dep
    // 如果表达式是一个函数
    if (typeof exprOrFn === "function") {
      this.getter = exprOrFn;
    }
    // 实例化就会默认调用get方法
    this.get();
  }
  get() {
    pushTarget(this); // 在调用方法之前先把当前watcher实例推到全局Dep.target上
    this.getter(); //如果watcher是渲染watcher 那么就相当于执行  vm._update(vm._render()) 这个方法在render函数执行的时候会取值 从而实现依赖收集
    popTarget(); // 在调用方法之后把当前watcher实例从全局Dep.target移除
  }
  //   把dep放到deps里面 同时保证同一个dep只被保存到watcher一次  同样的  同一个watcher也只会保存在dep一次
  addDep(dep) {
    let id = dep.id;
    if (!this.depsId.has(id)) {
      this.depsId.add(id);
      this.deps.push(dep);
      //   直接调用dep的addSub方法  把自己--watcher实例添加到dep的subs容器里面
      dep.addSub(this);
    }
  }
  //   这里简单的就执行以下get方法  之后涉及到计算属性就不一样了
  update() {
    this.get();
  }
}

ウォッチャーは、getterメソッドを呼び出す前後に、Dep.targetに自分自身を割り当てて、依存関係の収集と更新のためのメソッドの更新を容易にします。

6.完璧な深さ

// src/observer/dep.js

// dep和watcher是多对多的关系
// 每个属性都有自己的dep
let id = 0; //dep实例的唯一标识
export default class Dep {
  constructor() {
    this.id = id++;
    this.subs = []; // 这个是存放watcher的容器
  }
  depend() {
    //   如果当前存在watcher
    if (Dep.target) {
      Dep.target.addDep(this); // 把自身-dep实例存放在watcher里面
    }
  }
  notify() {
    //   依次执行subs里面的watcher更新方法
    this.subs.forEach((watcher) => watcher.update());
  }
  addSub(watcher) {
    //   把watcher加入到自身的subs容器
    this.subs.push(watcher);
  }
}
// 默认Dep.target为null
Dep.target = null;
// 栈结构用来存watcher
const targetStack = [];

export function pushTarget(watcher) {
  targetStack.push(watcher);
  Dep.target = watcher; // Dep.target指向当前watcher
}
export function popTarget() {
  targetStack.pop(); // 当前watcher出栈 拿到上一个watcher
  Dep.target = targetStack[targetStack.length - 1];
}

依存関係を収集し、ウォッチャーのdepsコンテナーに自分自身を配置するための関連メソッドを定義します

考え?この時点で、オブジェクトの更新は満たされますが、{a:[1,2,3]}のような配列の場合、配列が依存関係を収集しないため、a.push(4)は自動更新をトリガーしません。

7.配列の依存関係のコレクション

// src/observer/index.js

// Object.defineProperty数据劫持核心 兼容性在ie9以及以上
function defineReactive(data, key, value) {
  let childOb = observe(value); // childOb就是Observer实例

  let dep = new Dep(); // 为每个属性实例化一个Dep

  Object.defineProperty(data, key, {
    get() {
      // 页面取值的时候 可以把watcher收集到dep里面--依赖收集
      if (Dep.target) {
        // 如果有watcher dep就会保存watcher 同时watcher也会保存dep
        dep.depend();
        if (childOb) {
          // 这里表示 属性的值依然是一个对象 包含数组和对象 childOb指代的就是Observer实例对象  里面的dep进行依赖收集
          // 比如{a:[1,2,3]} 属性a对应的值是一个数组 观测数组的返回值就是对应数组的Observer实例对象
          childOb.dep.depend();
          if (Array.isArray(value)) {
            // 如果数据结构类似 {a:[1,2,[3,4,[5,6]]]} 这种数组多层嵌套  数组包含数组的情况  那么我们访问a的时候 只是对第一层的数组进行了依赖收集 里面的数组因为没访问到  所以五大收集依赖  但是如果我们改变了a里面的第二层数组的值  是需要更新页面的  所以需要对数组递归进行依赖收集
            if (Array.isArray(value)) {
              // 如果内部还是数组
              dependArray(value); // 不停的进行依赖收集
            }
          }
        }
      }
      return value;
    },
    set(newValue) {
      if (newValue === value) return;
      // 如果赋值的新值也是一个对象  需要观测
      observe(newValue);
      value = newValue;
      dep.notify(); // 通知渲染watcher去更新--派发更新
    },
  });
}
// 递归收集数组依赖
function dependArray(value) {
  for (let e, i = 0, l = value.length; i < l; i++) {
    e = value[i];
    // e.__ob__代表e已经被响应式观测了 但是没有收集依赖 所以把他们收集到自己的Observer实例的dep里面
    e && e.__ob__ && e.__ob__.dep.depend();
    if (Array.isArray(e)) {
      // 如果数组里面还有数组  就递归去收集依赖
      dependArray(e);
    }
  }
}

オブジェクトプロパティの値が配列の場合は、childOb.dep.depend()を実行して配列の依存関係を収集します。配列に配列も含まれている場合は、アクセス時にのみ依存関係が収集されるため、再帰的なトラバーサル収集が必要です。データはgetをトリガーします。最初は、データは再帰的にのみ実行されます。応答処理は収集できず、これら2つのポイントに依存する必要があります。

8.アレイ配布の更新

// src/observer/array.js

methodsToPatch.forEach((method) => {
  arrayMethods[method] = function (...args) {
    //   这里保留原型方法的执行结果
    const result = arrayProto[method].apply(this, args);
    // 这句话是关键
    // this代表的就是数据本身 比如数据是{a:[1,2,3]} 那么我们使用a.push(4)  this就是a  ob就是a.__ob__ 这个属性代表的是该数据已经被响应式观察过了 __ob__对象指的就是Observer实例
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
      default:
        break;
    }
    if (inserted) ob.observeArray(inserted); // 对新增的每一项进行观测
    ob.dep.notify(); //数组派发更新 ob指的就是数组对应的Observer实例 我们在get的时候判断如果属性的值还是对象那么就在Observer实例的dep收集依赖 所以这里是一一对应的  可以直接更新
    return result;
  };
});

キーコードはob.dep.notify()です

9.更新されたマインドマップをレンダリングします

レンダリングの更新

概要

全体的なプロセス

これがVueレスポンシブ原則全体の写真です。データハイジャック->テンプレート分析->テンプレートレンダリング->データ変更ビューの自動更新から始めましょう。プロセス全体、特にで紹介されたレンダリング更新に関連する知識ポイントは手書きされています。この記事を繰り返すことをお勧めします。Vueの多くのコア原則とAPIはここの知識ポイントに関連しているため、原則を理解した後、自分でそれを行うことをお勧めします。

これまでのところ、Vueのレンダリング更新の原則は終了しています。理解できない場合や論争がある場合は、コメントしてメッセージを残してください。

最後に、この記事が役に立ったと思ったら、忘れずに3回気に入ってくださいありがとうございます。

おすすめ

転載: blog.csdn.net/hugo233/article/details/114946296