在上一篇博客React虚拟DOM原理中,我们探索了如何创建虚拟DOM
,那么创建了虚拟DOM
之后,又是怎么转换成真实DOM
,渲染在页面中的呢,下面我们继续探索。
在React项目的入口处,经常有ReactDOM.render(<App />, document.getElementById('root'))
这样的写法,那么这是不是将所有的虚拟DOM
转换成真实DOM
插入到root
中的操作呢。
render方法
按照上面的猜想,那么render方法的作用就是
- 把
虚拟DOM
转换成了真实DOM
- 将
真实DOM
插入到root
中
render方法接收两个参数,第二个参数没什么好说的, 固定写法,第一个参数有以下几种情况:
- 类组件
- 函数组件
- 文字、数字
首先处理类组件,我们先回忆一下类组件的写法,我们想要获得的DOM在render
方法中,那么使用new App().render()
方法不就能得到DOM
了吗。
class App extends Component {
render() {
return (
<div>
App
</div>
)
}
}
然后再来处理函数组件,函数组件的写法是,直接就返回了DOM
function App() {
return <div>App</div>
}
对于文字、数字,直接添加到根节点。
这样就处理了各种组件类型,还有个问题就是这些组件的属性怎么处理。
- 属性是
className
,直接添加到标签上 - 属性是style,将样式添加到标签上
- 属性是children,如果是文字、数字,直接插入节点,如果是DOM元素,则循环递归处理
- 其他情况,当做普通属性直接
setAttribute
在src
下创建react-dom.js
,代码如下
function render(element, parentNode) {
// 如果是字符串或者数字,直接添加到根节点
if(typeof element === 'string ' || typeof element === 'number') {
return parentNode.appendChild(document.createTextNode(element));
}
let type, props;
type = element.type;
props = element.props;
if(type && type.isReactComponent) {
// 如果是类组件,拿到render方法中的内容,也就是React.createElement()的返回值
let returnElement = new type(props).render();
type = returnElement.type;
props = returnElement.props;
} else if(typeof type === 'function') {
// 如果是函数组件,直接拿到React.createElement()的返回值
let returnElement = type(props);
type = returnElement.type;
props = returnElement.props;
}
let domElement = document.createElement(type); // 创建一个标签<div></div>、<span></span>之类的
for(let propName in props) {
if(propName === 'className') {
// 如果属性是className,不做处理,直接加到标签上
domElement.className = props[propName];
} else if(propName === 'style') {
// 如果是style,直接设置样式
let styleObj = props[propName];
domElement.style.cssText = styleObj;
} else if(propName === 'children') {
console.log(props[propName])
// 如果是children
if(typeof props[propName] === 'string' || typeof props[propName] === 'number') {
// 如果内容是文字、数字之类的,把文字、数字放到标签里面
domElement.appendChild(document.createTextNode(props[propName]));
} else {
// 如果里面还有标签,先循环再递归
props.children.forEach(child => render(child, domElement));
}
} else {
// 其余情况,当做普通属性处理
domElement.setAttribute(propName,props[propName]);
}
}
parentNode.appendChild(domElement); // 将所有标签处理完毕后插入跟节点
}
export default { render };
这样就实现了一个简单的render
方法,当然还有很多情况需要做兼容,有兴趣的读者可以自行尝试。