简单版(只处理一个jsx语法)
1.基于BABEL中的语法解析模块(babel-preset-react)把JSX语法编译为React.CreateElement(...)结构.
根据babel和babel-preset-react插件中正则解析,得到如图所示
2.执行React.createElement(type,props,children),会创建一个对象,这个对象就是虚拟DOM.
React.createElement("h1", {
ref: "ref1",
key: "key1",
id: "title",
className: "title",
style: {
"font-size": "12px",
"color": "red"
}
}, "\u54C8\u54C8\u54C8")
// 执行大的的结果是一个对象
{
key: 'key1',
props:{
children: "哈哈哈"
clasName: "title"
id: "title"
style: {color: "red"}
},
ref: "ref1",
type: "h1",
__proto__: Object
}
实现React.createElement(type,props,children)方法
步骤:
1.创建一个对象(默认要有四个属性: Type/Props/Key/Ref),然后把对象返回 2.参数处理,按照React.createElement(type,props,children)执行的结果处理
/*
* 函数名: createElement
* 参数:
type: 标签名, 必传
props: 属性值等, 必须
children: 内容 可不传
* 返回: 返回一个对象,如上面React.createElement()执行的结果
*/
createElement(type,props,children){
let obj = {
type: null,
props: {
children: '',
}
key: null,
ref: null
};
// es6解析
obj = {
...obj
type, // => type:type(参数)
props:{
...props, // 把参数props结构了
children // => children: children(参数)
}
}
// 处理key和ref
if('key' in obj.props){
obj.key = obj.props.key;
obj.props.key = undefined
}
if('ref' in obj.props){
obj.ref = obj.props.ref;
obj.props.ref = undefined
}
return obj;
}
ReactDOM.render(JSX语法最后生成的对象,容器),基于RENDER方法把生成的对象动态创建为DOM元素,插入到指定的容器中.
ReactDOM.render(<div>
<h1 key="key1" id="title" className="title" style={{"font-size":"12px", "color": "red"}}>哈哈哈</h1>
</div>, document.getElementById('root'));
render方法的实现
/*
* 函数名: render
* 参数:
obj: (React.createElement执行的结果),
container: document.getElementById('root')),
callback: 回调函数
* 返回: 渲染到页面上
*/
render(obj,container,callback){
let {type,props} = obj || {};
// 根据type创建标签
let newElement = document.createElement(type);
/*
* setAttribute
* 基于setAttribute可以设置的属性可以表现在HTML结构上
*/
// 根据props创建标签上的属性
for(let attr in props){
// 只需要处理props私有属性,原型上的属性不需要去处理
if(!props.hasOwnProperty(attr)) break;
// props[attr]可能是undefined,可能是null
if(!props[attr]) continue;
let value = props[attr];
// 处理attr == 'style'的时候
if(attr === 'style'){
if(!value) continue;
// props['style']是一个对象
for(let stylekey in value){
// 对象只处理自己私有的属性
if(value.hasOwnProperty(stylekey)){
// 把对象的每一个键值对压入到标签的style中
newElement['style'][stylekey] = value[stylekey];
}
}
continue;
}
// 处理className(呈现在页面上的是class)
if(attr === 'className'){
newElement.setAttribute('class') = value;
}
// 处理children(简单的,我们只有文本节点)
if(attr === 'children'){
if(typeof value === 'string'){
// 通过createTextNode创建文本节点,然后压入到标签中
let text = document.createTextNode(value)
newElement.appendChild(text);
}
}
}
// 把新生成的标签压入到容器中
container.appendChild(newElement);
}
处理复杂的JSX语法
基于babel转义
发现,createElement可以传入多个参数,基于React,createElement生成的对象就为createElement方法创建虚拟DOM
方法改造
/*
* 函数名: createElement
* 参数:
type: 标签名, 必传
props: 属性值等, 必须
children:
可能是一个字符串,也有可能是一个数组
* 返回: 返回一个对象,如上面React.createElement()执行的结果
*/
createElement(type,props,...childrens){
props = props ? props : {};
let obj = {
type: null,
props: {
children: '',
}
key: null,
ref: null
};
// es6解析
obj = {
...obj
type, // => type:type(参数)
props:{
...props, // 把参数props结构了
// 如果childrens数组只有一项,或者是一个空数组,
// 如果childrens数组有多项
// 此时我们生成的children有可能是一个字符串(或者为空),也有可能是一个数组,
children: childrens.length<=1?(childrens[0] || ''): childrens // => children: children(参数)
}
}
// 处理key和ref
if('key' in obj.props){
obj.key = obj.props.key;
obj.props.key = undefined
}
if('ref' in obj.props){
obj.ref = obj.props.ref;
obj.props.ref = undefined
}
return obj;
}
根据生成的obj对象(虚拟DOM),render函数渲染的 props[attr]=== children部分,也要做相对应的处理
render函数渲染
render(obj,container,callback){
let {type,props} = obj || {};
// 根据type创建标签
let newElement = document.createElement(type);
/*
* setAttribute
* 基于setAttribute可以设置的属性可以表现在HTML结构上
*/
// 根据props创建标签上的属性
for(let attr in props){
// 只需要处理props私有属性,原型上的属性不需要去处理
if(!props.hasOwnProperty(attr)) break;
// props[attr]可能是undefined,可能是null
if(!props[attr]) continue;
let value = props[attr];
// 处理attr == 'style'的时候
if(attr === 'style'){
if(!value) continue;
// props['style']是一个对象
for(let stylekey in value){
// 对象只处理自己私有的属性
if(value.hasOwnProperty(stylekey)){
// 把对象的每一个键值对压入到标签的style中
newElement['style'][stylekey] = value[stylekey];
}
}
continue;
}
// 处理className(呈现在页面上的是class)
if(attr === 'className'){
newElement.setAttribute('class') = value;
}
// 处理children(复杂的,
// children
// 可能是一个字符串
// 也可能是一个对象
// 还可能是一个数组,
// 数组中有字符串,也要JSX对象
if(attr === 'children'){
// 统一作出数组处理
if(!(value instanceof Array)){
let value = [value]
}
// 变量数组
value.forEach((item,index)=>{
if(typeof value === 'string'){
// 通过createTextNode创建文本节点,然后压入到标签中
// 此时创建的就是当前的item像
let text = document.createTextNode(item)
newElement.appendChild(text);
}else{
// 就是一个jsx对象,重新执行render方法
// item为jsx对象
// newElement为容器
render(item,newElement);
}
})
}
}
// 把新生成的标签压入到容器中
container.appendChild(newElement);
}
总结:
1. 基于babel和babel-preset-react转义
2. 基于React.createElement创建虚拟DOM
3. 根据虚拟DOM生成标签tree.
细节之处,略有疏漏,如有不足或者错误的地方,欢迎大佬批评并加以指正.谢谢.