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'
// 虚拟dom
const Element = (
<div className="title">
Hello
<span style={{fontSize:"20px",color:"#f00"}}>World!</span>
</div>
)
Element 经过babel转换后如下:
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 中标签属性的行为一致。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。