jsx
import React from 'react'
console.log(<div>hello <span className='className' style={{
color: 'red'
}}>baby</span></div>)
运行以上代码段,可以观察一下console打印出来的结果
这里其实是babel将jsx转换成了React.createElement。
jsx
React.createElement('div', {}, 'hello', React.createElement('span', {
className: 'className',
style: {
color: 'red'
}
}, 'baby'))
最后这段代码返回的则是以上图打印的一个vnode
对象
现在开始实现一个createElement
函数
js
const REACT_ELEMENT = Symbol('react.element')
function createElement(type, properties, ...children) {
let ref = properties.ref || null;
let key = properties.key || null;
// 这里则是将一些特殊的属性去除掉。
["key", "ref"].forEach((key) => {
delete properties[key];
});
let props = { ...properties };
// 这里由于vnode的children有两种状态,一种是一个子vnode的时候则直接是对象
// 如果是多个子vnode时候则是一个数组
// 其实我觉得可以都统一为对象会更好统一。
if (children.length <= 1) {
// 对象或者undefined
props.children = children[0];
} else {
// 数组
props.children = Array.prototype.slice.call(arguments, 2);
}
return {
type,
$$typeof: REACT_ELEMENT,
ref,
key,
props,
};
}
接下来开始实现render
函数
js
// VNode则是createElement生成的虚拟dom对象
// containerDOM则是需要挂载的dom元素对象
function render(VNode, containerDOM) {
// 将虚拟dom转化成真实Dom
// 将得到的真实dom挂载到containerDOM中
mount(VNode, containerDOM);
}
function mount(VNode, containerDOM) {
let newDOM = createDOM(VNode);
newDOM && containerDOM.appendChild(newDOM);
}
function createDOM(VNode) {
// 1. 创建元素 2. 处理子元素 3. 处理属性值
const { type, props } = VNode;
let dom;
if (type && VNode.$$typeof === REACT_ELEMENT) {
dom = document.createElement(type);
}
if (props) {
if (typeof props.children === "object" && props.children.type) {
mount(props.children, dom);
} else if (Array.isArray(props.children)) {
mounArray(props.children, dom);
} else if (typeof props.children === "string") {
dom.appendChild(document.createTextNode(props.children));
}
}
console.log(VNode)
// 处理属性
setPropsForDOM(dom, props);
return dom;
}
function setPropsForDOM(dom, VNodeProps = {}) {
if (!dom) return;
for (let key in VNodeProps) {
if (key === "children") {
continue;
}
// onClick
if (/^on[A-Z].*/.test(key)) {
// TODO 暂不处理
} else if (key === 'style') {
Object.keys(VNodeProps[key]).forEach(styleName => {
dom.style[styleName] = VNodeProps[key][styleName]
})
} else {
// dom[key] = VNodeProps[key]
dom.setAttribute(key, VNodeProps[key])
}
}
}
function mounArray(children, parent) {
if (!Array.isArray(children)) return;
for (let i = 0; i < children.length; i++) {
if (typeof children[i] === "string") {
parent.appendChild(document.createTextNode(children[i]));
} else {
mount(children[i], parent);
}
}
}
我们可以把render
函数分为两个步骤
- 将虚拟dom转化成真实Dom
- 将得到的真实dom挂载到containerDOM中
在createDOM
函数中主要逻辑则是通过type对应元素具体类型,通过document.createElement
方法创建对应的dom兑现 接下来则是需要考虑有子元素存在的情况
- 单个子元素
- 多个子元素
- 元素是否是字符串类型 如果是字符类型则直接映射为文本对象,通过
document.createTextNode
创建
最后则是处理props
属性部分 首先需要去除一些特殊的key关键字的属性比如children
onXXX
style
之类 其实应该还有className
需要转化成class
之类,这里不具体深入,逻辑大致细节处理 现阶段主要考虑两个特殊问题
style
因为react要求的style
传入的是只能是对象类型,则需要根据对象属性进行dom.style[key] = 'xxx'
设置- 另外一些属性则通过
dom.setAttribute(key, value)
设置