предисловие
React — один из самых популярных фреймворков во фронтенде, есть много статей, интерпретирующих его исходный код, но я хочу интерпретировать React под другим углом: реализовать React с нуля и реализовать большинство функций React из API. В этом процессе Узнайте, почему существуют виртуальные DOM, diff и почему setState разработан таким образом.
Когда дело доходит до React, всегда неизбежно сравнивать его с Vue.
Дизайн API Vue очень прост, но метод его реализации кажется «волшебным».
Напротив, дизайн философии реагирования очень проста. Хотя вам часто нужно решать различные детали самостоятельно, она заставляет людей чувствовать, что это очень «реально», и вы можете явно чувствовать, что вы все еще пишете JS.
О jsx
Прежде чем мы начнем, необходимо уточнить некоторые понятия.
Давайте посмотрим на этот фрагмент кода:
const title = <h1 className="title">Hello, world!</h1>;
Этот код не является допустимым кодом js, это расширение синтаксиса под названием jsx, с помощью которого мы можем легко писать фрагменты html в коде js.
По сути, jsx — это синтаксический сахар, и приведенный выше код будет преобразован в следующий код с помощью babel.
const title = React.createElement(
'h1',
{ className: 'title' },
'Hello, world!'
);
Вы можете протестировать преобразованный код jsx в онлайн-переводе, предоставленном официальным сайтом babel, вот один из них.Чуть более сложный пример
Готов к работе
Чтобы сконцентрироваться на написании логики, я выбрал популярный в последнее время пакет инструмента упаковки с нулевой конфигурацией на инструменте упаковки кода.Сначала нам нужно установить пакет:
npm install -g parcel-bundler
Далее создайте новыйindex.js
а такжеindex.html
,существуетindex.html
введен вindex.js
.
Конечно, есть более простой способ, вы можете напрямую скачать код этого репозитория:
https://github.com/hujiulong/simple-react/tree/chapter-1
Обратите внимание на конфигурацию babel.babelrc
{
"presets": ["env"],
"plugins": [
["transform-react-jsx", {
"pragma": "React.createElement"
}]
]
}
этоtransform-react-jsx
Это плагин Babel, который конвертирует jsx в js, у него естьpragma
элемент, вы можете определить имя метода преобразования jsx, вы также можете изменить его наh
(это имя используется многими React-подобными фреймворками) или что-то еще.
После завершения подготовки мы можем использовать командуparcel index.html
Поставил его и бежать, конечно, сейчас ничего.
Rect.Createelement и Virtual Dom
Как упоминалось ранее, фрагменты jsx будут переведены вReact.createElement
код упаковки метода. Итак, первый шаг, давайте реализуем этоReact.createElement
метод
Из результата перевода jsx параметры метода createElement следующие:
createElement( tag, attrs, child1, child2, child3 );
Первый параметр — это имя метки DOM-узла, его значение может бытьdiv
,h1
,span
так далее
Второй параметр — это объект, который содержит все свойства, включая, возможно,className
,id
так далее
Начиная с третьего параметра, это его дочерний узел
Наша реализация createElement очень проста, нам просто нужно вернуть объект для хранения его информации.
function createElement( tag, attrs, ...children ) {
return {
tag,
attrs,
children
}
}
аргументы функции...children
используя ES6остаточный параметр, его роль заключается в объединении следующих параметров, таких как child1, child2, в дочерний массив.
Теперь попробуем назвать его
// 将上文定义的createElement方法放到对象React中
const React = {
createElement
}
const element = (
<div>
hello<span>world!</span>
</div>
);
console.log( element );
Откройте инструмент отладки, мы увидим, что выходной объект соответствует тому, что мы ожидали.
Объект, возвращаемый нашим методом createElement, записывает всю информацию об этом узле DOM, другими словами, мы можем сгенерировать настоящий DOM через него, вызывая объект, который записывает информацию.виртуальный DOM.
ReactDOM.render
Далее идет метод ReactDOM.render, еще раз посмотрим на этот код
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
После преобразования этот код становится таким
ReactDOM.render(
React.createElement( 'h1', null, 'Hello, world!' ),
document.getElementById('root')
);
такrender
Первый параметр фактически принимает объект, возвращаемый createElement, который является виртуальным DOM.
Второй параметр — целевой DOM для монтирования.
В общем, то, что делает метод рендеринга,Преобразование виртуального DOM в реальный DOM, вот его реализация:
function render( vnode, container ) {
// 当vnode为字符串时,渲染结果是一段文本
if ( typeof vnode === 'string' ) {
const textNode = document.createTextNode( vnode );
return container.appendChild( textNode );
}
const dom = document.createElement( vnode.tag );
if ( vnode.attrs ) {
Object.keys( vnode.attrs ).forEach( key => {
if ( key === 'className' ) key = 'class'; // 当属性名为className时,改回class
dom.setAttribute( key, vnode.attrs[ key ] )
} );
}
vnode.children.forEach( child => render( child, dom ) ); // 递归渲染子节点
return container.appendChild( dom ); // 将渲染结果挂载到真正的DOM上
}
Обратите внимание, что React избегает имен классов.class
и ключевое слово jsclass
Конфликт, измените имя класса на className, при рендеринге в настоящий DOM вам нужно изменить его обратно.
Здесь на самом деле есть небольшая проблема: когда несколько вызововrender
функция, исходное содержимое не очищается. Поэтому, когда мы присоединяем его к объекту ReactDOM, сначала очистим содержимое целевого DOM монтирования:
const ReactDOM = {
render: ( vnode, container ) => {
container.innerHTML = '';
return render( vnode, container );
}
}
Рендеринг и обновление
На данный момент мы реализовали самые основные функции React и можем использовать его для некоторых вещей.
Сначала мы добавляем корневой узел в index.html.
<div id="root"></div>
Давайте сначала попробуем официальную документациюHello,World
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
Вы можете увидеть результат:
Попробуйте отрендерить кусок динамического кода, этот пример также взят изофициальная документация
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById( 'root' )
);
}
setInterval( tick, 1000 );
Вы можете увидеть результат:
позже
В этой статье мы реализовали самые основные функции React, а также узнали о jsx и виртуальном DOM.В следующей статье мы реализуем очень важныекомпонентыФункция.
Последний маленький вопросПри определении компонентов React или написании кода, связанного с React, независимо от того, используется ли объект React в коде, мы должны его импортировать.
Например:
import React from 'react'; // 下面的代码没有用到React对象,为什么也要将其import进来
import ReactDOM from 'react-dom';
ReactDOM.render( <App />, document.getElementById( 'editor' ) );
Если вы не знаете ответа, внимательно прочитайте эту статью.
Внедрение серии React с нуля
React — один из самых популярных фреймворков во фронтенде, есть много статей, интерпретирующих его исходный код, но я хочу интерпретировать React под другим углом: реализовать React с нуля и реализовать большинство функций React из API. В этом процессе Узнайте, почему существуют виртуальные DOM, diff и почему setState разработан таким образом.
Во всей серии будет около шести статей. Я буду обновлять одну или две статьи каждую неделю. Я обновлю ее на github как можно скорее. Если у вас есть какие-либо вопросы для обсуждения, пожалуйста, ответьте мне на github~
адрес блога:GitHub.com/Ху Цзюлун/Но…Обратите внимание на звездочку, подпишитесь, чтобы смотреть