在上一篇文章的基础上添加处理函数组件渲染。
1、改造index.js为函数组件
// index
import React from './react';
import ReactDOM from './react-dom';
function FunctionComponent(props) {
return (
<div className="title" style={{ background: 'red' }}>
<span>{props.name}</span>
</div>
)
}
ReactDOM.render(<FunctionComponent name="xiaoming" />,
document.getElementById('root')
);
2、添加处理函数组件
createDom函数增加处理函数组件逻辑:
mountFunctionComponent函数实现:实现逻辑其实很简单,就是调用函数组件,传入props,使其成为原生组件,重复调用原生组件逻辑。createElement
3、实现效果
4、完整代码
// react-dom.js
/**
* createElement编译出来的结果
* {"type":"div","key":null,"ref":null,
* "props":{"className":"title","style":{"color":"red"},
* "children":{"type":"span","key":null,
* "ref":null,"props":{"children":"hello word"}
*/
/**
* 1.把虚拟DOM变为真实DOM;
* 2.把虚拟DOM属性同步到真实DOM上;
* 3.把虚拟DOM上的children也变成真实DOM挂载到自己的DOM上;
* 4.把最终DOM挂载到容器上;
* @param {*} vdom 要渲染的虚拟DOM
* @param {*} container 虚拟DOM转换成真实DOM,并插入到容器中
*/
function render(vdom, container) {
const dom = createDom(vdom)
container.appendChild(dom)
}
/**
* 虚拟dom变为真实dom
* @param {*} vdom 虚拟dom
*/
function createDom(vdom) {
// 如果是字符串或者数字,直接返回真实节点
if (['number', 'string'].includes(typeof vdom)) {
return document.createTextNode(vdom)
}
// 否则它就是一个react元素了
const { type, props } = vdom
let dom
if (typeof type === 'function') {
// 函数组件直接返回
return dom = mountFunctionComponent(vdom)
} else {
// 原生组件
dom = document.createElement(type)
}
// 将虚拟dom属性更新到真实dom上
updateProps(dom, props)
// 如果儿子只有一个儿子,并且儿子是文本节点
if (['number', 'string'].includes(typeof props.children)) {
dom.textContent = props.children
// 如果儿子是一个对象,并且是一个虚拟dom,递归调用render
} else if (typeof props.children === 'object' && props.children.type) {
// 把儿子变成真实dom,插入到自己身上
render(props.children, dom)
// 如果儿子是一个数组,说明儿子不止一个
} else if (Array.isArray) {
reconcileChildren(props.children, dom)
} else {
document.textContent = props.children ? props.children.toString() : ""
}
// // 把真实dom作为一个虚拟属性放在虚拟dom上,为以后更新做准备
// vdom.dom = dom
return dom
}
/**
* @param {*} childrenVdom 儿子们的虚拟dom
* @param {*} parentDom 父的真实dom
*/
function reconcileChildren(childrenVdom, parentDom) {
for (const childVdom of childrenVdom) {
render(childVdom, parentDom)
}
}
/**
* 把一个类型为自定义函数组件的虚拟dom转换为真实dom并返回
* @param {*} vdom 自定义函数虚拟dom
*/
function mountFunctionComponent(vdom) {
const { type:FunctionComponent, props } = vdom
// 直接调用
let renderVdom = FunctionComponent(props)
return createDom(renderVdom)
}
/**
* 将虚拟dom属性更新到真实dom上
* @param {*} dom 真实dom
* @param {*} newProps 新属性
*/
function updateProps(dom, newProps) {
for (const key in newProps) {
if (key === 'children') continue
// style单独处理,因为dom不支持
if (key === 'style') {
const styleObj = newProps.style
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr]
}
} else {
dom[key] = newProps[key]
}
}
}
const ReactDom = { render }
export default ReactDom
5、源代码
本文代码:https://gitee.com/linhexs/react-write/tree/2.react-FunctionComponent-render/