jsx是一种语法糖,通过babel 编译后生成 React.createElement(component, props, ...children)
函数。
例如
const Element = ( <div className="title"> Hello <span style={{fontSize:"20px",color:"#f00"}}>World!</span> </div>)
通过babel编译后:
const Element = React.createElement( 'div', { className: 'title', }, 'Hello', React.createElement( 'span', { style: { fontSize: '20px', color: '#f00', }, }, 'World!' ));
jsx语法通过babel编译后生成一种对象,即虚构dom,在react中,通过render函数将虚构dom渲染成实在的dom绑定到对应节点中
import React from 'react'import ReactDOM from 'react-dom'// 虚构domconst Element = ( <div className="title"> Hello <span style={{fontSize:"20px",color:"#f00"}}>World!</span> </div>)/*等同于const Element = React.createElement( 'div', { className: 'title', }, 'Hello', React.createElement( 'span', { style: { fontSize: '20px', color: '#f00', }, }, 'World!' ))*///绑定节点const el = document.getElementById('root')// render办法渲染到页面ReactDOM.render(Element, el)
这里须要两个函数实现页面的渲染:createElement 和 render办法
createElement函数
createElement函数将babel转换过后的参数简化,返回对象type和props
/** * 生成虚构DOM对象 * @param {string} type dom节点类型 * @param {object} config 属性对象 * @param {*} [children] 子数组 * * @return {object} {type,props} */function createElement(type,config,children) { let props = {}; for(let propsName in config){ props[propsName] = config[propsName] }; let childsLen = arguments.length - 2; if(childsLen===1){ props.children = [children] }else if(childsLen>1){ props.children = Array.prototype.slice.call(arguments, 2) } return { type, props }}
render函数
render函数负责将虚构dom转化为实在dom,咱们将createElement
生成的虚构dom对象传入render函数的第一个参数,构造出type值和props对象
/** * @param {createElement} element 虚构DOM对象 * @param {HTMLElement}} container 绑定的dom节点 */function render(element, container) { if (typeof element === 'string') { return container.appendChild(document.createTextNode(element)) } let type = element.type; let props = element.props; if (type.isReactComponent) { // 如果是类组件 element = new type(props).render() type = element.type props = element.props } else if (typeof type === 'function') { // 如果是函数组件 element = type(props) type = element.type props = element.props } const el = document.createElement(type) for (let propName in props) { let value = props[propName] if (propName === 'className') { el.className = value } else if (propName === 'style') { let cssText = Object.keys(value).map((attr) => { let _attr = attr.replace(/([A-Z])/g, (a) => `-${a.toLocaleLowerCase()}`); return `${_attr}:${value[attr]}` }).join(';'); el.style.cssText = cssText } else if (propName === 'children') { value.forEach((item) => render(item, el)) } else { el.setAttribute(propName, value) } } return container.appendChild(el)}
这里咱们还须要判断type值是类组件还是函数组件
类组件
对于如何判断类组件,咱们能够在组件继承的Component类中增加动态属性isReactComponent
供render函数判断
class Component { static isReactComponent = true; constructor(props){ this.props = props; }}
类组件
let Element = React.createElement(fnElemen, { name: 'Hello', fontSize: '28px' })class clsComp extends React.Component { render() { return React.createElement( 'h1', { className: 'title' }, this.props.name, React.createElement( 'span', { style: { color: '#0f0', fontSize: this.props.fontSize } }, 'World' ) ) }};let Element = React.createElement(clsComp, { name: 'Hello', fontSize: '28px' })ReactDOM.render(Element, document.getElementById('root'))
函数组件
function fnElemen(props){ return React.createElement( 'h1', { className: 'title' }, props.name, React.createElement( 'span', { style: { color: '#0f0', fontSize: props.fontSize } }, 'World' ) )}let Element = React.createElement(fnElemen, { name: 'Hello', fontSize: '28px' })ReactDOM.render(Element, document.getElementById('root'))
开发中的常见问题
为何必须援用React
作用域内必须引入react,否则会导致编译失败,这是因为jsx会编译为React.createElement
的模式调用,所以react在jsx的作用域内必须要引入。
用户定义的组件以大写字母结尾
babel在编译时会把小写字母结尾的元素断定为原生DOM标签,createElement 会把它编译成字符串,例如<div>
或者 <span>
,所以在编写组件的时候,须要以大写字母结尾,这样createElement才会把第一个变量被编译为对象
import React from 'react';// 谬误!组件应该以大写字母结尾:function hello(props) { // 正确!这种 <div> 的应用是非法的,因为 div 是一个无效的 HTML 标签 return <div>Hello {props.toWhat}</div>;}function HelloWorld() { // 谬误!React 会认为 <hello /> 是一个 HTML 标签,因为它没有以大写字母结尾: return <hello toWhat="World" />;}
Props 默认值为 “True”
如果你没给 prop 赋值,它的默认值是 true。以下两个 JSX 表达式是等价的:
<MyTextBox autocomplete /><MyTextBox autocomplete={true} />
通常,咱们不倡议不传递 value 给 prop,因为这可能与 ES6 对象简写混同,{foo}
是 {foo: foo}
的简写,而不是 {foo: true}
。这样实现只是为了放弃和 HTML 中标签属性的行为统一。