В интернете есть много статей про анализ принципа реакции, но часто я ничего не получаю после прочтения таких статей, потому что написание идей происходит слишком быстро, большинство из них просто пишут несколько предложений, чтобы вкратце представить, что это за код используется для Да, а затем вставьте исходный код, чтобы вы сами могли убедиться.Возможно, что автор сам действительно это понял, но для большинства людей, прочитавших эту статью, это действительно в темноте.
Лучший способ объяснить исходный код фреймворка — реализовать упрощенную версию, чтобы в процессе реализации читатели могли понять ваше общее мышление, а также могли получить общее представление о фреймворке на более высоком уровне. чем увязнуть в некоторых конкретных технических деталях.
Эта статья — отличная реализация простого фреймворка React, а следующая часть принадлежиторигинальныйПеревод плюс некоторое собственное понимание в процессе использования.
Прежде всего, мы представим то, что вы можете узнать из этой статьи в целом — мы реализуем простой React, включая простой API на уровне компонентов и виртуальный дом, а также статья будет разделена на следующие четыре части.
- Элементы: в этой главе мы узнаем, как JSX обрабатывается как виртуальный DOM.
- Рендеринг: в этом разделе мы покажем вам, как превратить виртуальный DOM в настоящий DOM.
- Исправление: в этой главе мы покажем вам, почему ключи так важны и как использовать виртуальный DOM для пакетного обновления существующего DOM.
- Компоненты: последний подраздел расскажет вам о компонентах React и их жизненном цикле.
Element
Элементы несут много важной информации, такой как тип узла, пропсы, список дочерних элементов.По этим свойствам нужные нам элементы могут быть отрисованы.Его древовидная структура выглядит следующим образом
{
"type": "ul",
"props": {
"className": "some-list"
},
"children": [
{
"type": "li",
"props": {
"className": "some-list__item"
},
"children": [
"One"
]
},
{
"type": "li",
"props": {
"className": "some-list__item"
},
"children": [
"Two"
]
}
]
}
Но если мы будем писать такой код каждый день, то мы должны сойти с ума, поэтому обычно мы будем писать синтаксис jsx.
/** @jsx createElement */
const list = <ul className="some-list">
<li className="some-list__item">One</li>
<li className="some-list__item">Two</li>
</ul>;
Чтобы его можно было скомпилировать в обычный метод, нам нужно добавить комментарий, чтобы определить, какую функцию использовать, окончательная определенная функция выполняется и, наконец, возвращается в виртуальный DOM.
const createElement = (type, props, ...children) => {
props = props != null ? props : {};
return {type, props, children};
};
Зачем мне комментировать это место, потому что, когда я использовал babel для упаковки синтаксиса jsx, казалось, что CreateElement, предоставленный React, использовался по умолчанию, поэтому я настроил .babelrc в то время.
Я обнаружил, что он сообщает об ошибке React is notdefined, но я установил простой пакет, похожий на React, от автора.Позже я знал, что комментарий должен быть добавлен перед jsx, чтобы сообщить Babel, какую функцию использовать при компиляции.
/** @jsx Gooact.createElement */
Rendering
Этот раздел предназначен для преобразования vdom в реальный дом.
В предыдущем разделе мы получили древовидную структуру виртуального дома в соответствии с синтаксисом jsx, теперь пришло время преобразовать эту структуру виртуального дома в настоящий дом.
Итак, когда мы получаем древовидную структуру, как мы определяем, какой этот узел должен быть преобразован в настоящий дом? Здесь есть три ситуации. Первая — вернуть строку напрямую, а затем мы сгенерируем ее напрямую. Текстовый узел, если он возвращает пользовательский компонент, то мы вызываем этот метод, если это обычный компонент dom, мы создаем такой элемент dom, а затем продолжаем обход его дочерних узлов.
setAttribute — установить атрибуты, которые мы установили в виртуальном доме, в реальном доме.
const render = (vdom, parent=null) => {
if (parent) parent.textContent = '';
const mount = parent ? (el => parent.appendChild(el)) : (el => el);
if (typeof vdom == 'string' || typeof vdom == 'number') {
return mount(document.createTextNode(vdom));
} else if (typeof vdom == 'boolean' || vdom === null) {
return mount(document.createTextNode(''));
} else if (typeof vdom == 'object' && typeof vdom.type == 'function') {
return mount(Component.render(vdom));
} else if (typeof vdom == 'object' && typeof vdom.type == 'string') {
const dom = document.createElement(vdom.type);
for (const child of [].concat(...vdom.children)) // flatten
dom.appendChild(render(child));
for (const prop in vdom.props)
setAttribute(dom, prop, vdom.props[prop]);
return mount(dom);
} else {
throw new Error(`Invalid VDOM: ${vdom}.`);
}
};
const setAttribute = (dom, key, value) => {
if (typeof value == 'function' && key.startsWith('on')) {
const eventType = key.slice(2).toLowerCase();
dom.__gooactHandlers = dom.__gooactHandlers || {};
dom.removeEventListener(eventType, dom.__gooactHandlers[eventType]);
dom.__gooactHandlers[eventType] = value;
dom.addEventListener(eventType, dom.__gooactHandlers[eventType]);
} else if (key == 'checked' || key == 'value' || key == 'id') {
dom[key] = value;
} else if (key == 'key') {
dom.__gooactKey = value;
} else if (typeof value != 'object' && typeof value != 'function') {
dom.setAttribute(key, value);
}
};
Patching
Представьте, что у вас есть глубокая структура, и вам также нужно часто обновлять свой виртуальный дом, если вы что-то меняете, то все это нужно рендерить, что, несомненно, займет много времени.
Но если у нас есть алгоритм, который может сравнить разницу между новым виртуальным DOM и существующим DOM, а затем обновить только те измененные места, об этом месте часто говорят, что команда React приняла некоторые отработанные соглашения, которые будут o(n ) ^ 3 временная сложность снижается до o (n), в основном из-за следующих двух основных соглашений.
- Два элемента будут генерировать два разных дерева, если они разных типов.
- Когда мы даем ключевой атрибут, он будет судить по нему
const patch = (dom, vdom, parent=dom.parentNode) => {
const replace = parent ? el => (parent.replaceChild(el, dom) && el) : (el => el);
if (typeof vdom == 'object' && typeof vdom.type == 'function') {
return Component.patch(dom, vdom, parent);
} else if (typeof vdom != 'object' && dom instanceof Text) {
return dom.textContent != vdom ? replace(render(vdom)) : dom;
} else if (typeof vdom == 'object' && dom instanceof Text) {
return replace(render(vdom));
} else if (typeof vdom == 'object' && dom.nodeName != vdom.type.toUpperCase()) {
return replace(render(vdom));
} else if (typeof vdom == 'object' && dom.nodeName == vdom.type.toUpperCase()) {
const pool = {};
const active = document.activeElement;
for (const index in Array.from(dom.childNodes)) {
const child = dom.childNodes[index];
const key = child.__gooactKey || index;
pool[key] = child;
}
const vchildren = [].concat(...vdom.children); // flatten
for (const index in vchildren) {
const child = vchildren[index];
const key = child.props && child.props.key || index;
dom.appendChild(pool[key] ? patch(pool[key], child) : render(child));
delete pool[key];
}
for (const key in pool) {
if (pool[key].__gooactInstance)
pool[key].__gooactInstance.componentWillUnmount();
pool[key].remove();
}
for (const attr of dom.attributes) dom.removeAttribute(attr.name);
for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]);
active.focus();
return dom;
}
};
Component
组件是最像js中函数的概念了,我们通过它能够展示出什么应该展示在屏幕上,它可以被定义成一个无状态的函数,或者是一个有生命周期的组件。
class Component {
constructor(props) {
this.props = props || {};
this.state = null;
}
static render(vdom, parent=null) {
const props = Object.assign({}, vdom.props, {children: vdom.children});
if (Component.isPrototypeOf(vdom.type)) {
const instance = new (vdom.type)(props);
instance.componentWillMount();
instance.base = render(instance.render(), parent);
instance.base.__gooactInstance = instance;
instance.base.__gooactKey = vdom.props.key;
instance.componentDidMount();
return instance.base;
} else {
return render(vdom.type(props), parent);
}
}
static patch(dom, vdom, parent=dom.parentNode) {
const props = Object.assign({}, vdom.props, {children: vdom.children});
if (dom.__gooactInstance && dom.__gooactInstance.constructor == vdom.type) {
dom.__gooactInstance.componentWillReceiveProps(props);
dom.__gooactInstance.props = props;
return patch(dom, dom.__gooactInstance.render());
} else if (Component.isPrototypeOf(vdom.type)) {
const ndom = Component.render(vdom);
return parent ? (parent.replaceChild(ndom, dom) && ndom) : (ndom);
} else if (!Component.isPrototypeOf(vdom.type)) {
return patch(dom, vdom.type(props));
}
}
setState(nextState) {
if (this.base && this.shouldComponentUpdate(this.props, nextState)) {
const prevState = this.state;
this.componentWillUpdate(this.props, nextState);
this.state = nextState;
patch(this.base, this.render());
this.componentDidUpdate(this.props, prevState);
} else {
this.state = nextState;
}
}
shouldComponentUpdate(nextProps, nextState) {
return nextProps != this.props || nextState != this.state;
}
componentWillReceiveProps(nextProps) {
return undefined;
}
componentWillUpdate(nextProps, nextState) {
return undefined;
}
componentDidUpdate(prevProps, prevState) {
return undefined;
}
componentWillMount() {
return undefined;
}
componentDidMount() {
return undefined;
}
componentWillUnmount() {
return undefined;
}
}
Недавно разработанное колесо gooact в этой статье закончилось, давайте посмотрим, какие функции у него есть.
- Он может эффективно обновлять сложные структуры DOM.
- Поддерживаются как функциональные компоненты, так и компоненты с отслеживанием состояния.
Итак, насколько далеко это от полноценного приложения React?
- Он пока не поддерживает функции новой версии, такие как фрагменты, порталы
- Поскольку React Fiber слишком сложен, в настоящее время нет поддержки
- Если писать дубликаты ключей, могут быть баги
- Для некоторых методов также меньше функций обратного вызова.
Но дала ли эта статья вам свежий взгляд на фреймворк React и дала ли вам общую картину того, что делает фреймворк?
В любом случае, автор имеет более четкое представление о фреймворке React после прочтения исходного текста и, наконец, представляет вариант использования этого фреймворка.demo