предисловие
В этой статье в основном написан исходный код Vue2.0 от руки.Принцип обновления рендеринга
В предыдущей статье мы в основном представили VueПринцип начального рендерингаПроцесс сопоставления данных со слоем представления завершен, но когда мы меняем данные, мы обнаруживаем, что страница не будет обновляться автоматически. Мы знаем, что одна из функций Vue управляется данными. Когда данные изменяются, нам не нужно вручную управлять представлением dom, и оно будет обновляться автоматически.Прочитайте первую статьюПринципы адаптивных данныхВ этой статье в основном используется шаблон наблюдателя для определения Watcher и Dep для завершения сбора зависимостей и отправки обновлений для достижения обновлений рендеринга.
Для людей:Учащиеся, у которых нет времени смотреть официальный исходный код или которые запутались и не хотят смотреть исходный код
Совет: эта статья немного сложнее, потому что основное содержание всего исходного кода Vue.Последующие свойства расчета и реализация пользовательского наблюдателя и API, таких как $set $delete, должны понимать идеи этой статьи.Xiaobian также читал исходный код несколько раз.Я только понимаю это все время.Я надеюсь, что все смогут преодолеть трудности и реализовать это вместе!
текст
<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>
В предыдущем коде мы вызываем vm._update(vm._render()) в setTimeout для реализации функции обновления, потому что из принципа начального рендеринга в предыдущей статье мы можем видеть, что этот метод является ядром рендеринга. Но мы не можем просить пользователя вызывать метод рендеринга для обновления представления каждый раз при изменении данных Нам нужен механизм для автоматического обновления данных при изменении данных.
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Шаблон наблюдателяМы можем рассматривать Watcher как наблюдателя. Ему нужно подписаться на изменения данных. После изменения данных уведомить его о выполнении некоторых методов. По сути, это конструктор. При инициализации он выполнит метод 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 — это глобальный наблюдатель, указывающий на начальное состояние, равное нулю.
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去更新--派发更新
},
});
}
Код апелляции является основой для сбора и распространения обновлений.Фактически, при доступе к данным определенный нами Watcher рендеринга помещается в массив subs массива dep, а объект экземпляра dep также помещается в рендеринг. Watcher уведомляет об обновлении данных. Обновление watcher для магазина подписок 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();
}
}
Наблюдатель назначает себя 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() для сбора зависимостей массива.Если массив также содержит массивы, вам необходимо рекурсивно пройти по коллекции, потому что только данные доступа вызывают получение будет собирать зависимости. Сначала данные только рекурсивно выполняются. Реактивная обработка не может собирать зависимости. Эти два момента нужно различать
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 Мы написали весь процесс от захвата данных -> разбор шаблона -> рендеринг шаблона -> автоматическое обновление представления изменения данных, особенно знания об обновлении рендеринга, представленные в этой статье. рекомендуется повторно понять принцип и реализовать его самостоятельно, потому что многие основные принципы и API Vue связаны с точками знаний здесь.
На данный момент принцип обновления рендеринга Vue завершен.Если вы столкнулись с чем-то, чего не понимаете, или у вас есть споры, оставьте комментарий.
Наконец, если вы найдете эту статью полезной, помнитеКак СанлианБольшое спасибо!
Ссылка на сериал (будет обновлена позже)
- Написанный от руки исходный код Vue2.0 (1) — принцип адаптивных данных
- Рукописный исходный код Vue2.0 (2) - принцип компиляции шаблона
- Рукописный исходный код Vue2.0 (3) — принцип начального рендеринга
- Рукописный исходный код Vue2.0 (четыре) — принцип обновления рендеринга
- Рукописный исходный код Vue2.0 (5) - принцип асинхронного обновления
- Рукописный исходный код Vue2.0 (6) - принцип алгоритма diff
- Рукописный исходный код Vue2.0 (7) - Принцип микширования
- Рукописный исходный код Vue2.0 (8)-компонентный принцип
- Рукописный исходный код Vue2.0 (9) - принцип свойства прослушивания
- Рукописный исходный код Vue2.0 (10) — Принцип вычисляемых свойств
- Рукописный исходный код Vue2.0 (11) — принцип глобального API
- Самые полные вопросы интервью Vue + подробные ответы
- Рукописный исходный код vue-router
- Рукописный исходный код vuex
- Рукописный исходный код vue3.0
Группа передовых рыболовных технологий Brother Shark
Приветствую всех на технических биржахСвязь