[JavaScript, Программирование, VueJS] Сделаем худший Vue.js в мире (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Некоторое время назад я опубликовал похожую статью про React, где с помощью пары строк кода мы создали крошечный клон React.js с нуля. Но React — далеко не единственный инструмент в современном фронтенд-мире, Vue.js стремительно набирает популярность. Давайте разберемся, как работает этот фреймворк, и создадим примитивный клон, похожий на Vue.js, в образовательных целях.
Реактивность
Как и React.js, Vue является реактивным, то есть все изменения в состоянии приложения автоматически отражаются в DOM. Но в отличие от React, Vue отслеживает зависимости во время рендеринга и обновляет только связанные части без каких-либо «сравнений».
Ключ к реактивности Vue.js — это метод Object.defineProperty. Он позволяет указывать настраиваемый метод getter / setter для поля объекта и перехватывать каждый доступ к нему:
const obj = {a: 1};
Object.defineProperty(obj, 'a', {
get() { return 42; },
set(val) { console.log('you want to set "a" to', val); }
});
console.log(obj.a); // prints '42'
obj.a = 100; // prints 'you want to set "a" to 100'
С помощью этого мы можем определять, когда происходит доступ к определенному свойству, или когда оно изменяется, а затем повторно оценивать все зависимые выражения после изменения свойства.
Выражения
Vue.js позволяет привязать выражение JavaScript к атрибуту узла DOM с помощью директивы. Например, <div v-text="s.toUpperCase()"></div> установит текст внутри div’а в значение переменной s в верхнем регистре.
Самый простой подход к оценке строк, таких как s.toUpperCase(), использовать eval(). Хотя eval никогда не считался безопасным решением, мы можем попробовать сделать его немного лучше, обернув в функцию и передав специальный глобальный контекст:
const call = (expr, ctx) =>
new Function(`with(this){${`return ${expr}`}}`).bind(ctx)();
call('2+3', null); // returns 5
call('a+1', {a:42}); // returns 43
call('s.toUpperCase()', {s:'hello'}); // returns "HELLO"
Это немного безопаснее, чем нативный eval, и его достаточно для простого фреймворка, который мы создаем.
Прокси
Теперь мы можем использовать Object.defineProperty, чтобы обернуть каждое свойство объекта данных; можно использовать call() для оценки произвольных выражений и для того, чтобы сказать, к каким свойствам прямо или косвенно обращалось выражение. Мы также должны суметь определить, когда выражение должно быть повторно вычислено, потому что одна из его переменных изменилась:
const data = {a: 1, b: 2, c: 3, d: 'foo'}; // Data model
const vars = {}; // List of variables used by expression
// Wrap data fields into a proxy that monitors all access
for (const name in data) {
let prop = data[name];
Object.defineProperty(data, name, {
get() {
vars[name] = true; // variable has been accessed
return prop;
},
set(val) {
prop = val;
if (vars[name]) {
console.log('Re-evaluate:', name, 'changed');
}
}
});
}
// Call our expression
call('(a+c)*2', data);
console.log(vars); // {"a": true, "c": true} -- these two variables have been accessed
data.a = 5; // Prints "Re-evaluate: a changed"
data.b = 7; // Prints nothing, this variable does not affect the expression
data.c = 11; // Prints "Re-evaluate: c changed"
data.d = 13; // Prints nothing.
Директивы
Теперь мы можем оценивать произвольные выражения и отслеживать, какие выражения оценивать при изменении одной конкретной переменной данных. Осталось только назначить выражения определенным свойствам узла DOM и фактически изменить их при изменении данных.
Как и в Vue.js, мы будем использовать специальные атрибуты, такие как q-on:click для связывания обработчиков событий, q-text для привязки textContent, q-bind:style для привязки стиля CSS и так далее. Я использую здесь префикс «q-», потому что «q» похоже на «vue».
Вот неполный список возможных поддерживаемых директив:
const directives = {
// Bind innerText to an expression value
text: (el, _, val, ctx) => (el.innerText = call(val, ctx)),
// Bind event listener
on: (el, name, val, ctx) => (el[`on${name}`] = () => call(val, ctx)),
// Bind node attribute to an expression value
bind: (el, name, value, ctx) => el.setAttribute(name, call(value, ctx)),
};
Каждая директива — это функция, которая принимает узел DOM, имя необязательного параметра для таких случаев, как q-on:click (имя будет «click»). Также требуется строка выражения (value) и объект данных для использования в качестве контекста выражения.
Теперь у нас есть все строительные блоки, пора всё склеить вместе!
Конечный результат
const call = .... // Our "safe" expression evaluator
const directives = .... // Our supported directives
// Currently evaluated directive, proxy uses it as a dependency
// of the individual variables accessed during directive evaluation
let $dep;
// A function to iterate over DOM node and its child nodes, scanning all
// attributes and binding them as directives if needed
const walk = (node, q) => {
// Iterate node attributes
for (const {name, value} of node.attributes) {
if (name.startsWith('q-')) {
const [directive, event] = name.substring(2).split(':');
const d = directives[directive];
// Set $dep to re-evaluate this directive
$dep = () => d(node, event, value, q);
// Evaluate directive for the first time
$dep();
// And clear $dep after we are done
$dep = undefined;
}
}
// Walk through child nodes
for (const child of node.children) {
walk(child, q);
}
};
// Proxy uses Object.defineProperty to intercept access to
// all `q` data object properties.
const proxy = q => {
const deps = {}; // Dependent directives of the given data object
for (const name in q) {
deps[name] = []; // Dependent directives of the given property
let prop = q[name];
Object.defineProperty(q, name, {
get() {
if ($dep) {
// Property has been accessed.
// Add current directive to the dependency list.
deps[name].push($dep);
}
return prop;
},
set(value) { prop = value; },
});
}
return q;
};
// Main entry point: apply data object "q" to the DOM tree at root "el".
const Q = (el, q) => walk(el, proxy(q));
Реактивный, подобный Vue.js фреймворк во всей красе. Насколько он полезен? Вот пример:
<div id="counter">
<button q-on:click="clicks++">Click me</button>
<button q-on:click="clicks=0">Reset</button>
<p q-text="`Clicked ${clicks} times`"></p>
</div>
Q(counter, {clicks: 0});
Нажатие на одну кнопку увеличивает счетчик и автоматически обновляет содержимое <p>. Щелчок по другому устанавливает счетчик на ноль, а также обновляет текст.
Как видите, Vue.js, на первый взгляд, кажется магией, но внутри он очень прост, а основные функции могут быть реализованы всего в нескольких строках кода.
Дальнейшие шаги
Если вам интересно узнать о Vue.js больше — попробуйте реализовать «q-if» для переключения видимости элементов на основе выражения или «q-each» для привязки списков повторяющихся дочерних элементов (это будет хорошим упражнением).
Полные исходники нанофреймворка Q находятся на Github. Не стесняйтесь контрибьютить, если вы заметили проблему или хотите предложить улучшение!
В заключение я должен упомянуть, что Object.defineProperty использовался в Vue 2, а создатели Vue 3 переключились на другой механизм, предоставляемый ES6, а именно Proxy и Reflect. Прокси позволяет передать обработчик для перехвата доступа к свойствам объекта, как и в нашем примере, в то время как Reflect позволяет получить доступ к свойствам объекта изнутри прокси и сохранить this объект нетронутым (в отличие от нашего примера с defineProperty).
Я оставляю оба Proxy / Reflect в качестве упражнения для читателя, так что, кто бы ни сделал пулл-реквест для правильного использования их в Q — я буду рад совместить это. Удачи!
Надеюсь, вам понравилась статья. Вы можете следить за новостями и делиться предложениями в Github, Twitter или подписываться через rss.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Serge Zaitsev
===========Похожие новости:
- [Информационная безопасность, Программирование, Разработка под Android] Уязвимости Android 2020
- [JavaScript, Программирование] Основы JavaScript: почему вы должны знать, как работает JS-движок (перевод)
- [JavaScript, Функциональное программирование] Сочиняя ПО: Введение (перевод)
- [JavaScript, Программирование] 7 вопросов про замыкания в JavaScript (перевод)
- [Программирование, C++, Алгоритмы] Ленивые операции над множествами в C++
- [Open source, Программирование, Геоинформационные сервисы, Визуализация данных, Научно-популярное] Геология XXI века: от реальности к виртуальности
- [Программирование, C++, Алгоритмы] Ленивые итераторы и диапазоны в C++
- [PHP, Программирование] Работа с заказом через админку OpenCart, взгляд изнутри
- [JavaScript, Анализ и проектирование систем, Алгоритмы, Обработка изображений, Машинное обучение] Мы создали Web приложение для определения лиц и масок для Google Chrome (перевод)
- [Разработка веб-сайтов, JavaScript, ReactJS] Реализация архитектуры Redux на MobX. Часть 2: «Пример на MobX»
Теги для поиска: #_javascript, #_programmirovanie (Программирование), #_vuejs, #_vue.js, #_timeweb, #_blog_kompanii_timeweb (
Блог компании Timeweb
), #_javascript, #_programmirovanie (
Программирование
), #_vuejs
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:13
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Некоторое время назад я опубликовал похожую статью про React, где с помощью пары строк кода мы создали крошечный клон React.js с нуля. Но React — далеко не единственный инструмент в современном фронтенд-мире, Vue.js стремительно набирает популярность. Давайте разберемся, как работает этот фреймворк, и создадим примитивный клон, похожий на Vue.js, в образовательных целях. Реактивность Как и React.js, Vue является реактивным, то есть все изменения в состоянии приложения автоматически отражаются в DOM. Но в отличие от React, Vue отслеживает зависимости во время рендеринга и обновляет только связанные части без каких-либо «сравнений». Ключ к реактивности Vue.js — это метод Object.defineProperty. Он позволяет указывать настраиваемый метод getter / setter для поля объекта и перехватывать каждый доступ к нему: const obj = {a: 1};
Object.defineProperty(obj, 'a', { get() { return 42; }, set(val) { console.log('you want to set "a" to', val); } }); console.log(obj.a); // prints '42' obj.a = 100; // prints 'you want to set "a" to 100' С помощью этого мы можем определять, когда происходит доступ к определенному свойству, или когда оно изменяется, а затем повторно оценивать все зависимые выражения после изменения свойства. Выражения Vue.js позволяет привязать выражение JavaScript к атрибуту узла DOM с помощью директивы. Например, <div v-text="s.toUpperCase()"></div> установит текст внутри div’а в значение переменной s в верхнем регистре. Самый простой подход к оценке строк, таких как s.toUpperCase(), использовать eval(). Хотя eval никогда не считался безопасным решением, мы можем попробовать сделать его немного лучше, обернув в функцию и передав специальный глобальный контекст: const call = (expr, ctx) =>
new Function(`with(this){${`return ${expr}`}}`).bind(ctx)(); call('2+3', null); // returns 5 call('a+1', {a:42}); // returns 43 call('s.toUpperCase()', {s:'hello'}); // returns "HELLO" Это немного безопаснее, чем нативный eval, и его достаточно для простого фреймворка, который мы создаем. Прокси Теперь мы можем использовать Object.defineProperty, чтобы обернуть каждое свойство объекта данных; можно использовать call() для оценки произвольных выражений и для того, чтобы сказать, к каким свойствам прямо или косвенно обращалось выражение. Мы также должны суметь определить, когда выражение должно быть повторно вычислено, потому что одна из его переменных изменилась: const data = {a: 1, b: 2, c: 3, d: 'foo'}; // Data model
const vars = {}; // List of variables used by expression // Wrap data fields into a proxy that monitors all access for (const name in data) { let prop = data[name]; Object.defineProperty(data, name, { get() { vars[name] = true; // variable has been accessed return prop; }, set(val) { prop = val; if (vars[name]) { console.log('Re-evaluate:', name, 'changed'); } } }); } // Call our expression call('(a+c)*2', data); console.log(vars); // {"a": true, "c": true} -- these two variables have been accessed data.a = 5; // Prints "Re-evaluate: a changed" data.b = 7; // Prints nothing, this variable does not affect the expression data.c = 11; // Prints "Re-evaluate: c changed" data.d = 13; // Prints nothing. Директивы Теперь мы можем оценивать произвольные выражения и отслеживать, какие выражения оценивать при изменении одной конкретной переменной данных. Осталось только назначить выражения определенным свойствам узла DOM и фактически изменить их при изменении данных. Как и в Vue.js, мы будем использовать специальные атрибуты, такие как q-on:click для связывания обработчиков событий, q-text для привязки textContent, q-bind:style для привязки стиля CSS и так далее. Я использую здесь префикс «q-», потому что «q» похоже на «vue». Вот неполный список возможных поддерживаемых директив: const directives = {
// Bind innerText to an expression value text: (el, _, val, ctx) => (el.innerText = call(val, ctx)), // Bind event listener on: (el, name, val, ctx) => (el[`on${name}`] = () => call(val, ctx)), // Bind node attribute to an expression value bind: (el, name, value, ctx) => el.setAttribute(name, call(value, ctx)), }; Каждая директива — это функция, которая принимает узел DOM, имя необязательного параметра для таких случаев, как q-on:click (имя будет «click»). Также требуется строка выражения (value) и объект данных для использования в качестве контекста выражения. Теперь у нас есть все строительные блоки, пора всё склеить вместе! Конечный результат const call = .... // Our "safe" expression evaluator
const directives = .... // Our supported directives // Currently evaluated directive, proxy uses it as a dependency // of the individual variables accessed during directive evaluation let $dep; // A function to iterate over DOM node and its child nodes, scanning all // attributes and binding them as directives if needed const walk = (node, q) => { // Iterate node attributes for (const {name, value} of node.attributes) { if (name.startsWith('q-')) { const [directive, event] = name.substring(2).split(':'); const d = directives[directive]; // Set $dep to re-evaluate this directive $dep = () => d(node, event, value, q); // Evaluate directive for the first time $dep(); // And clear $dep after we are done $dep = undefined; } } // Walk through child nodes for (const child of node.children) { walk(child, q); } }; // Proxy uses Object.defineProperty to intercept access to // all `q` data object properties. const proxy = q => { const deps = {}; // Dependent directives of the given data object for (const name in q) { deps[name] = []; // Dependent directives of the given property let prop = q[name]; Object.defineProperty(q, name, { get() { if ($dep) { // Property has been accessed. // Add current directive to the dependency list. deps[name].push($dep); } return prop; }, set(value) { prop = value; }, }); } return q; }; // Main entry point: apply data object "q" to the DOM tree at root "el". const Q = (el, q) => walk(el, proxy(q)); Реактивный, подобный Vue.js фреймворк во всей красе. Насколько он полезен? Вот пример: <div id="counter">
<button q-on:click="clicks++">Click me</button> <button q-on:click="clicks=0">Reset</button> <p q-text="`Clicked ${clicks} times`"></p> </div> Q(counter, {clicks: 0}); Нажатие на одну кнопку увеличивает счетчик и автоматически обновляет содержимое <p>. Щелчок по другому устанавливает счетчик на ноль, а также обновляет текст. Как видите, Vue.js, на первый взгляд, кажется магией, но внутри он очень прост, а основные функции могут быть реализованы всего в нескольких строках кода. Дальнейшие шаги Если вам интересно узнать о Vue.js больше — попробуйте реализовать «q-if» для переключения видимости элементов на основе выражения или «q-each» для привязки списков повторяющихся дочерних элементов (это будет хорошим упражнением). Полные исходники нанофреймворка Q находятся на Github. Не стесняйтесь контрибьютить, если вы заметили проблему или хотите предложить улучшение! В заключение я должен упомянуть, что Object.defineProperty использовался в Vue 2, а создатели Vue 3 переключились на другой механизм, предоставляемый ES6, а именно Proxy и Reflect. Прокси позволяет передать обработчик для перехвата доступа к свойствам объекта, как и в нашем примере, в то время как Reflect позволяет получить доступ к свойствам объекта изнутри прокси и сохранить this объект нетронутым (в отличие от нашего примера с defineProperty). Я оставляю оба Proxy / Reflect в качестве упражнения для читателя, так что, кто бы ни сделал пулл-реквест для правильного использования их в Q — я буду рад совместить это. Удачи! Надеюсь, вам понравилась статья. Вы можете следить за новостями и делиться предложениями в Github, Twitter или подписываться через rss. =========== Источник: habr.com =========== =========== Автор оригинала: Serge Zaitsev ===========Похожие новости:
Блог компании Timeweb ), #_javascript, #_programmirovanie ( Программирование ), #_vuejs |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 13:13
Часовой пояс: UTC + 5