JSX渲染机制

JSX渲染机制

  1. 基于Babel中的语法解析模块(BABEL-PRSET-REACT)把JSX编译为React.createElement()结构,如图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
复杂结构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)
发布了23 篇原创文章 · 获赞 0 · 访问量 986

猜你喜欢

转载自blog.csdn.net/yangling_123/article/details/105059174
JSX