Создайте свою собственную React-подобную библиотеку с нуля
Практически каждый разработчик программного обеспечения более или менее знает React. Даже некоторые серверные разработчики или разработчики машинного обучения - и даже некоторые не разработчики. Да, это хорошая библиотека, элегантная и многообещающая. Каждый фронтенд-разработчик рассказывает о своем API, его оптимизации, своих внутренних механизмах.
Внутренний механизм
Внутренние механизмы - самая важная часть, но как их понять? Чтение исходного кода React, безусловно, является вариантом, но может показаться ошеломляющим. Итак, читаем ли мы дайджесты от кого-то, кто прочитал весь исходный код? Интересно, сколько дайджестов мы можем прочитать, чтобы получить полное представление. Итак, я предлагаю создать React самостоятельно!
Создайте свой собственный React
Звучит безумно? Может, немного. Нам просто нужно закодировать некоторые важные части, которые могут заставить работать крошечную программу - вот и все. Вы можете попробовать собрать интересующие вас детали самостоятельно.
Шаг 1. Рендеринг компонентов
Допустим, у нас есть такой компонент:
<p>hello world
<span style={{color:'red'}}>!</span>
</p>
Он в формате JSX, поэтому в React код будет:
React.createElement('p',null,'hello world',
React.createElement('span',{style:{color:'red'}},'!')
)
React.createElement
мне не удобен. Вместо этого я просто использую класс D, поэтому мне нужно использовать ключевое слово new
. И я изменил дочерние элементы на параметр props с фиксированным ключом children
. Итак, определение и использование компонента должно быть таким:
import { D, render } from '../lib'
let el = document.createElement('div')
document.body.append(el)
render(el, new D('p', {
children: [
'hello world',
new D('span', {
children: '!',
style: { color: 'red' }
})
]
}))
И мы ожидаем, что это будет выглядеть так:
Итак, нам просто нужно рекурсивно преобразовать экземпляры D в элементы и добавить их в дерево DOM.
// lib/d.js
export class D {
...
init(parent) {
this.parent = parent
if (!this.component) {
// text
this.el = document.createTextNode(this.props.children)
} else if (typeof this.component == 'string') {
// html element
this.el = document.createElement(this.component)
let { children, ...rest } = this.props
assignProps(this.el, rest)
this.children = normalizeDArr(children)
} else {
// component
setREACT_LIKE_CUR_COMPONENT(this)
setREACT_LIKE_CUR_COMPONENT_STATEI(0)
this.children = normalizeDArr(this.component(this.props))
}
// append to DOM tree
if (this.el) this.parent.append(this.el)
// init children recursively
this.children.forEach(x => x.init(this.el || this.parent))
}
...
}
Если вы посмотрите мой код и запустите npm run dev1
, вы получите такой результат:
Это каким-то образом представляет виртуальную модель DOM, и каждый элемент HTML имеет привязанный к нему экземпляр D.
Шаг 2. Визуализация с состоянием
Теперь мы визуализируем hello world
буквально или с переменной, содержащей то же значение. Тогда единственное, что нам нужно сделать, это сохранить его. useState
хранит данные в массиве.
Вам нужно будет знать, какой компонент отрисовывается при вызове useState
, и соответствующий индекс массива состояний.
// lib/hooks.js
export function useState(initalValue) {
let d = getREACT_LIKE_CUR_COMPONENT() // get component
let i = getREACT_LIKE_CUR_COMPONENT_STATEI() // get index
if (d.states[i] === undefined) d.states[i] = initalValue
let v = d.states[i]
setREACT_LIKE_CUR_COMPONENT_STATEI(i + 1)
return [v, v1 => {
d.states[i] = v1
d.updateState()
}]
}
useState
вызывается внутри компонента App
. Поэтому перед вызовом компонента App
нам нужно сохранить массив состояний component
и сбросить его index
до 0
.
// lib/d.js
export class D {
constructor(component, props = {}) {
...
this.states = []
...
}
init(parent) {
...
// render component
setREACT_LIKE_CUR_COMPONENT(this)
setREACT_LIKE_CUR_COMPONENT_STATEI(0)
this.children = normalizeDArr(this.component(this.props))
...
}
}
Теперь вы можете использовать состояние useState
при рендеринге.
Шаг 3. Обработка события
Событие React управляется глобально, поэтому мы имитируем его с делегированием:
// lib/event.js
const EVENT_TYPES = ['click']
// delegate from mount point
export function listen(el) {
return EVENT_TYPES.map(eventType => {
let listener = e => {
let key = 'on' + capitalize(eventType)
bubble(e.target, e, key)
}
el.addEventListener(eventType, listener)
})
}
// bubble event until it's handled
function bubble(el, e, key) {
if (!el) return
if (el[key]) return el[key](e)
bubble(el.parentElement, e, key)
}
Затем добавьте опору onClick к кнопке:
// step2/App.js
import { D, useState } from "../lib"
const App = () => {
let [i, setI] = useState(0)
let [str, setStr] = useState('hello counter!')
return [
str,
new D('div', {
children: [
new D('button', {
children: '-',
// onClick assigned to element as prop
onClick: () => setI(i - 1)
}),
i + '',
new D('button', {
children: '+',
// onClick assigned to element as prop
onClick: () => setI(i + 1)
})
]
})
]
}
export default App
React - это скорее планировщик, чем реактор, так как же он может объединять изменения состояния / свойств и обрабатывать их сразу? Здесь я использовал Promise.resolve
.
// lib/d.js
export class D {
...
updateState() {
this.stateChangeCount++
// wait for changes to merge
Promise.resolve().then(() => {
if (!this.stateChangeCount) return
this.stateChangeCount = 0
if (this.el) {
let { children, ...rest } = this.props
assignProps(this.el, rest)
if (!this.component) {
return this.el.data = children
}
}
let children = this.getChildren(this.props)
...
}
...
}
Это не такая уж и сложная проблема, правда? Если вы запустите мой код npm run dev
, вы получите такой результат. Попробуйте нажимать на кнопки.
Важное примечание
React может делать гораздо больше. Например, React может отложить рендеринг до следующего кадра, если рендеринг занимает слишком много времени, и вы не получите сайт, не отвечающий на запросы. Он также может выполнять сравнение списков, переход, приостановку и т. д. Но нельзя ожидать, что 200 строк кода сделают так много. Это просто руководство по созданию React-подобной библиотеки и простое объяснение базового механизма React.