本文旨在记录react框架源码的学习历程。手写一个mini版的react,实现主要的功能。 这是第一篇,主要内容包括React.createElement和ReactDOM.render两个函数的实现思路。
关于jsx
可以理解为一个js的语法糖,使用jsx的编写方式,可以像写html标签一样,更加灵活便捷。经过babel的编译后,其实是调用了React.createElement()生成了虚拟DOM
所以jxs就是原生js的语法糖,简化了创建虚拟DOM的操作
关于 React.createElement
React.createElement(type, config, children) 该函数的返回值就是虚拟DOM, 三个参数分别对应 标签名称,标签属性,标签体内容 其中config包含了key、ref、className、style等等以props形式传递给元素的内容。
返回的虚拟DOM整体上和config类似,但有一点区别,比如标签体内容、className、style等被装进了props对象中。对比两幅图,可以实现简单的React.createElement
创建一个 react.js 文件
function createElement(type, config, children){
let key = null, ref = null
if(config){
key = config.key
ref = config.ref
delete config.key
delete config.ref
}
let props = {...config}
if(arguments.length > 3){ //说明children有多个,应该封装成数组
let arr = [...arguments]
props.children = arr.slice(2)
}else if(arguments.length === 3){ //说明只有一个子节点,直接添加
props.children = children
}
return {
$$typeof: REACT_ELEMENT,
props,
key,
ref,
type
}
}
const React = {
createElement,
}
export default React
关于 ReactDOM.render
ReactDOM.render(VDOM, container) 该函数两个作用,一个是接收虚拟DOM, 创建出真实DOM;另外一个是把真实DOM挂载到指定的容器中
创建一个 react-dom.js 文件。
这个过程中用到了一个递归去创建节点
function render(VDOM, container) { //把虚拟DOM转为真实DOM 把真实DOM挂到指定容器
let dom = createDOM(VDOM) //根据虚拟DOM生成真实DOM
container.appendChild(DOM) //将真实DOM挂载到了页面
}
function createDOM(VDOM){ //VDOM: {type, props, ref, key}
if(Object(VDOM) !== VDOM){ //如果是文本类型的,就直接创建文本节点,也是递归的出口
return document.createTextNode(VDOM)
}
let {type, ref, key, props} = VDOM
let dom = document.createElement(type) //根据type创建真正的dom
updateProps(dom, null, props)
let children = props.children
if(children){ // <span></span> 这种情况的话 props:{}
handleChildren(dom, children)
}
return dom
}
function updateProps(dom, oldProps, newProps){ //props:{children, className, style}
for(let key in newProps){
if(key === "children"){
continue
}else if(key === "style"){
let styleObject = newProps[key]
for(let item in styleObject){
dom.style[key] = styleObject[item]
}
}else if(key.startsWith("on")){ //说明是绑定的事件
dom[key.toLocaleLowerCase()] = newProps[key]
}else{
dom[key] = newProps[key]
}
}
if(oldProps){ //更新处理。如果旧的属性在新的属性中不存在,那么就删除
for(let key in oldProps){
if(!newProps[key]) delete oldProps[key]
}
}
}
//形成递归 children:["hello, react", { xxxx,xxxx VDOM }]
function handleChildren(dom, children){
if(children instanceof Array){
children.forEach( child => render(child, dom))
}else{
render(child, dom)
}
}
示例
在React脚手架文件的 index.js 中
import React from "./react-resource/react" //引入刚刚写的react和react-dom
import ReactDOM from "./react-resource/react-dom"
//jsx语法
let element1 = <h1 className="title" style={{ color: "red" }}>
hello, react
<span>this is span</span>
</h1>
ReactDOM.render(element1, document.getElementById("root"))
效果展示