JSX渲染机制
- 基于Babel中的语法解析模块(BABEL-PRSET-REACT)把JSX编译为React.createElement()结构,如图
2.React.createElement()执行,创建一个对象(虚拟DOM),返回的对象如下格式:
{
$$typeof: Symbol(react.element)
type: "h1"
key: "12"
ref: "aa"
props:
id: "titleBox"
className: "title"
style: {color: "red"}
children: "啦啦啦"
__proto__: Object
_owner: null
__proto__: Object
}
3.使用ReactDOM.render(JSX语法最后生成的对象容器)基于RENDER方法把生成的对象动态创建为DOM元素,插入到指定容器。
createElement()原理
createElement传入三个参数,(type,props,children),返回一个对象
function createElement(type,props,children){
// 1.创建一个对象,默认有四个属性(type,props,ref,key),最后要把这个对象返回
// 2.根据传递的值修改这个对象
/*
*TYPE=>传递的type
*PROPS=>需要做一些处理,大部分传递PROPS中的属性都赋值给对象的PROPS,有一些比较特殊,
* =>如果是REF或KEY,我们需要把传递的PROPS中的这两个属性值,给创建对象的两个属性,而传递的PROPS中把这两个值删除掉
* =>把传递的children作为新创建的对象中的PROPS中的一个属性
*/
// =>创建一个对象,设置一些默认属性值
let obj = {
type:null,
props:{
children:''
},
key:null,
ref:null
}
// =>用传递的type和props覆盖原有的默认值
// obj = {...obj,type,props}
obj = {...obj,type,props:{...props,children}},
// =>把ref和key提取出来(并且删除props中的属性)
'key' in obj.props?(obj.key = obj.props.key,obj.props.key = undefined):null
'ref' in obj.props?(obj.ref = obj.props.ref,obj.props.ref = undefined):null
return obj
}
render原理
render方法接收三个参数,(obj,container,callback),render方法把创建的对象生成DOM元素,最后插入到页面中
function render(obj={},container,callback){
let {type,props} = obj,
newElement = document.createElement(type)
for(let attr in props) {
if(!props.hasOwnProperty(attr)) break //=>不是私有的直接结束(已经遍历到原型上了)
if(!props[attr]) continue //=>如果当前属性没有值,直接不处理即可
let value = props[attr]
// =>className的处理
if(attr === 'className'){
newElement.setAttribute('class',value)
continue
}
if(attr==='style'){
if(value === '') continue
for(let stykey in value){
if(value.hasOwnProperty(stykey)){
newElement['style'][stykey] = value[stykey]
}
}
continue
}
// children的处理
if(attr === 'children'){
if(typeof value === "string"){
let text = document.createTextNode(value)
newElement.appendChild(text)
}
continue
}
//=>基于SET_ATTRIBUTES可以让设置的属性表现在HTML结构上
newElement.setAttribute(attr,value)
}
container.appendChild(newElement);
callback && callback()
}
render(objJSX,root,()=>{
console.log('ok')
})
复杂结构的JSX处理
当JSX为复杂结构时,编译后如图:
复杂结构的JSX经过编译后,不同的是传入childrens 的值,childrens可能没有,可能有一个,也可能有多个,因此在重写createElement方法时,childrens的处理应当是:
- 没有传入childrens时,返回的对象的props中的children应设置为空
- 当传入的childrens为一个时,将这一个赋值给对象的props中的children
- 当传入的childrens为多个时,直接将传入的childrens数组赋值给对象的props中的children
复杂结构的createElement方法
function createElement(type, props, ...childrens) {
let ref, key;
if (props&&'ref' in props) {
ref = props['ref'];
props['ref'] = undefined;
}
if (props&&'key' in props) {
key = props['key'];
props['key'] = undefined;
}
return {
type,
props: {
...props,
children: childrens.length <= 1 ? (childrens[0] || '') : childrens
},
ref,
key
};
}
在render方法重写时,需要修改的依然是对children的处理。children的可能性
- 可能是一个值:可能是字符串也可能是一个JSX对象
- 可能是一个数组,数组中的每一项可能是字符串也可能是JSX对象
处理时,将一个值的也转换为数组,方便后续处理,转换为数组后循环遍历,当类型为字符串时,直接创建节点并渲染即可,当类型是对象时(该对象也是形如createElement返回的对象),再次调用render方法并渲染
复杂结构的render方法
function render(obj={},container,callback){
let {type,props} = obj,
newElement = document.createElement(type)
for(let attr in props) {
if(!props.hasOwnProperty(attr)) break //=>不是私有的直接结束(已经遍历到原型上了)
if(!props[attr]) continue //=>如果当前属性没有值,直接不处理即可
let value = props[attr]
// =>className的处理
if(attr === 'className'){
newElement.setAttribute('class',value)
continue
}
if(attr==='style'){
if(value === '') continue
for(let stykey in value){
if(value.hasOwnProperty(stykey)){
newElement['style'][stykey] = value[stykey]
}
}
continue
}
// children的处理
if(attr === 'children'){
/*
* 可能是一个值:可能是字符串也可能是一个JSX对象
* 可能是一个数组,数组中的每一项可能是字符串也可能是JSX对象
*/
// 首先把一个值变为数组,这样后期统一操作数组即可
!(value instanceof Array)? value = [value] :null
value.forEach((item,index)=>{
// =>验证item类型,如果是文本类型,创建文本节点,如果是对象,我们需要再次执行render方法,把创建的元素放到最开始创建的大盒子中
if(typeof item === 'string'){
let text = document.createTextNode(item)
newElement.appendChild(text)
}else{
render(item,newElement)
}
})
continue
}
//=>基于SET_ATTRIBUTES可以让设置的属性表现在HTML结构上
newElement.setAttribute(attr,value)
}
container.appendChild(newElement);
callback && callback()
}
完整代码
function createElement(type, props, ...childrens) {
let ref, key;
if (props&&'ref' in props) {
ref = props['ref'];
props['ref'] = undefined;
}
if (props&&'key' in props) {
key = props['key'];
props['key'] = undefined;
}
return {
type,
props: {
...props,
children: childrens.length <= 1 ? (childrens[0] || '') : childrens
},
ref,
key
};
}
let objJSX = createElement("div", {
id: "titleBox",
className: "title",
style: {
color: 'red'
},
ref: "aa",
key: "12"
},
"\u6E29\u99A8\u63D0\u793A",
createElement("h1", null, "\u51FA\u9519\u4E86"),
createElement("span", null, "\u6D4B\u8BD5"))
function render(obj={},container,callback){
let {type,props} = obj,
newElement = document.createElement(type)
for(let attr in props) {
if(!props.hasOwnProperty(attr)) break //=>不是私有的直接结束(已经遍历到原型上了)
if(!props[attr]) continue //=>如果当前属性没有值,直接不处理即可
let value = props[attr]
// =>className的处理
if(attr === 'className'){
newElement.setAttribute('class',value)
continue
}
if(attr==='style'){
if(value === '') continue
for(let stykey in value){
if(value.hasOwnProperty(stykey)){
newElement['style'][stykey] = value[stykey]
}
}
continue
}
// children的处理
if(attr === 'children'){
/*
* 可能是一个值:可能是字符串也可能是一个JSX对象
* 可能是一个数组,数组中的每一项可能是字符串也可能是JSX对象
*/
// 首先把一个值变为数组,这样后期统一操作数组即可
!(value instanceof Array)? value = [value] :null
value.forEach((item,index)=>{
// =>验证item类型,如果是文本类型,创建文本节点,如果是对象,我们需要再次执行render方法,把创建的元素放到最开始创建的大盒子中
if(typeof item === 'string'){
let text = document.createTextNode(item)
newElement.appendChild(text)
}else{
render(item,newElement)
}
})
continue
}
//=>基于SET_ATTRIBUTES可以让设置的属性表现在HTML结构上
newElement.setAttribute(attr,value)
}
container.appendChild(newElement);
callback && callback()
}
render(objJSX,root)