题目描述
HTML可以被当作某种意义上的 序列化→浏览器解释(反序列化)HTML文本→ 然后构建DOM tree。
除了基于XML的方案,我们也可以试试JSON。如果打印出React中对element的表示,像这样:
const el = <div>
<h1> this is </h1>
<p className="paragraph"> a <button> button </button> from <a href="https://bfe.dev"><b>BFE</b>.dev</a>
</p>
</div>;
console.log(el)
得到如下数据结构:
{
type: 'div',
props: {
children: [
{
type: 'h1',
props: {
children: ' this is '
}
},
{
type: 'p',
props: {
className: 'paragraph',
children: [
' a ',
{
type: 'button',
props: {
children: ' button '
}
},
' from',
{
type: 'a',
props: {
href: 'https://bfe.dev',
children: [
{
type: 'b',
props: {
children: 'BFE'
}
},
'.dev'
]
}
}
]
}
}
]
}
}
思路分析
要实现jsx <=> virtual dom,上来不太好判断难度,从处理数据生成html的流程更符合平时开发逻辑,就从 vdom -> jsx 开始吧!
最直观的规律,virtual dom 是一棵树,我们需要确定父子关系
- 一个
type对应 *个children children包含字符串节点和带标签的节点- 一个
type对应 一个props props中除了children其余都是元素上的属性
以上4条理顺了,就可以开始写代码了
function render(json) {
// textNode
if (typeof json === 'string') {
return document.createTextNode(json);
}
// element
const {type, props: {children, ...attrs}} = json;
// 出现 type 就创建一个节点
const element = document.createElement(type);
// 给节点加上属性
Object.entries(attrs).forEach(([attr, value]) => element[attr] = value);
// 把 children 添加到节点内
const childrenArr = Array.isArray(children) ? children : [children];
childrenArr.forEach(child => element.append(render(child)));
return element;
}
然后需要完成jsx => vdom,第一反应是 vdom 数据结构(长相)和 jsx 是一样的,是对数据结构进行扩展
大致应该是这样
function virtualize(element) {
const result = {
type: element.tagName,
props: {}
};
// 先添加属性
element.attributes.forEach... props[k] = v;
// 再添加 children,循环节点递归(添加属性和children)
element.childNodes.forEach... props.children = newChildren
}
顺着这个思路把代码补全:
function virtualize(element) {
const result = {
type: element.tagName.toLowerCase(),
props: {}
}
// attrs
element.attributes.forEach(attr => {
const name = attr.name === 'class' ? 'className' : attr.name;
result.props[name] = attr.value;
});
// children
const children = [];
element.childNodes.forEach(node => {
if (node.nodeType === 3) {
children.push(node.textContent);
} else {
children.push(virtualize(node));
}
});
result.props.children = children.length === 1 ? children[0] : children;
return result;
}
AC代码
function render(json) {
// textNode
if (typeof json === 'string') {
return document.createTextNode(json);
}
// element
const {type, props: {children, ...attrs}} = json;
// 出现 type 就创建一个节点
const element = document.createElement(type);
// 给节点加上属性
Object.entries(attrs).forEach(([attr, value]) => element[attr] = value);
// 把 children 添加到节点内
const childrenArr = Array.isArray(children) ? children : [children];
childrenArr.forEach(child => element.append(render(child)));
return element;
}
function virtualize(element) {
const result = {
type: element.tagName.toLowerCase(),
props: {}
}
// attrs
element.attributes.forEach(attr => {
const name = attr.name === 'class' ? 'className' : attr.name;
result.props[name] = attr.value;
});
// children
const children = [];
element.childNodes.forEach(node => {
if (node.nodeType === 3) {
children.push(node.textContent);
} else {
children.push(virtualize(node));
}
});
result.props.children = children.length === 1 ? children[0] : children;
return result;
}
总结
完成了 vdom 和 jsx 的简单转换,在此基础上我们可以尝试写一个简易版的React.createElement 题目链接
写一个 render 和 h 函数,分别负责生成 jsx 和 vdom
render(h(
'div',
{},
h('h1', {}, ' this is '),
h(
'p',
{ className: 'paragraph' },
' a ',
h('button', {}, ' button '),
' from ',
h('a',
{ href: 'https://bfe.dev' },
h('b', {}, 'BFE'),
'.dev')
)
))
h 函数对应上文的 vdom
function h(type, props, ...children) {
return {
type,
props: { ...props, children }
};
}
render 函数如上文
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情