jsx是一種語法糖,通過babel 編譯後生成 React.createElement(component, props, ...children)
函數。css
例如react
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綁定到對應節點中babel
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> ) /* 等同於 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方法app
createElement函數將babel轉換事後的參數簡化,返回對象type和propsdom
/** * 生成虛擬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函數負責將虛擬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值是類組件仍是函數組件this
關於如何判斷類組件,咱們能夠在組件繼承的Component類中添加靜態屬性isReactComponent
供render函數判斷spa
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,不然會致使編譯失敗,這是由於jsx會編譯爲React.createElement
的形式調用,因此react在jsx的做用域內必需要引入。prototype
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" />; }
若是你沒給 prop 賦值,它的默認值是 true。如下兩個 JSX 表達式是等價的:
<MyTextBox autocomplete /> <MyTextBox autocomplete={true} />
一般,咱們不建議不傳遞 value 給 prop,由於這可能與 ES6 對象簡寫混淆,{foo}
是 {foo: foo}
的簡寫,而不是 {foo: true}
。這樣實現只是爲了保持和 HTML 中標籤屬性的行爲一致。