首先,webpack会接住react相关的loader对源代码进行编译,将jsx语法转成js。比如下面的例子。不过我们不会写这些编译后的对象,太不直观了。
const element = <h1 id='h1' className='h1'><span>哈哈哈</span></h1>
会被转成
const element = React.createElement(
"h1",
{
id: "h1",
className: "h1"
},
React.createElement("span", null, "\u54C8\u54C8\u54C8")
);
ReactDOM.render(element, document.getElementById('root'));
这是函数组件
import React from './VirtualDom/React';
import ReactDOM from './VirtualDom/ReactDom';
function Element2 () {
return React.createElement("h1", {
id: "h1",
style: {
color: 'red'
},
lassName: "h1"
}, React.createElement("span", null, "\u54C8\u54C8\u54C8"), "我是函数组件,返回一个用来渲染的对象", React.createElement("div", null, React.createElement("ul", null, React.createElement("li", null, "1"), React.createElement("li", null, "2"), React.createElement("li", null, "3"), React.createElement("li", null, "4"))));
}
const Element22 = React.createElement(Element2, {name:'我', age: 100})
ReactDOM.render(Element33, document.getElementById('root'));
这是类组件
import React from './VirtualDom/React';
import ReactDOM from './VirtualDom/ReactDom';
class Element3 extends React.Component {
render () {
console.log(this.props)
return React.createElement("h1", {
id: "h1",
style: {
color: 'red'
},
lassName: "h1"
}, React.createElement("span", null, "\u54C8\u54C8\u54C8"), "我是类组件,通过render返回一个用来渲染节点的对象", React.createElement("div", null, React.createElement("ul", null, React.createElement("li", null, "1"), React.createElement("li", null, "2"), React.createElement("li", null, "3"), React.createElement("li", null, "4"))));
}
}
const Element33 = React.createElement(Element3, {name:'我', age: 100})
ReactDOM.render(Element33, document.getElementById('root'));
其实可以看到,基础的就以下几个方法
react.js
export default {
Component,
createElement
}
react-dom.js
// React.js
export default {
render
}
我们将React.createElement方法执行生成的虚拟dom节点打印出来可以看到,就是一个对象,有几个重要的属性
{
props: {},
type:
}
props会保存我们传入的数据,还有children属性作为它的子节点,children属性的值分为三种:基础类型(比如数字、字符串、null)、react节点(就是个对象)、数组。
我们来看下实现。
react.js
const createElement = function (type, options, child) {
let props = {}
if (options && typeof options === 'object') {
// for (let key in options) { // for in 会遍历到可枚举的属性,Object.keys不会。
// props[key] = options[key]
// }
// 这个存在的问题就是没有深拷贝,会影响原来的属性。
Object.keys(options).forEach(key => {
props[key] = options[key]
})
}
if (child && arguments.length === 3) {
props.children = child
}
if (arguments.length > 3) {
props.children = Array.from(arguments).slice(2)
}
return {
props,
type
}
}
class Component {
static isReactComponent = true
constructor (props) {
this.props = props
}
}
export default {
createElement,
Component
}
react-dom.js
const render = (virtualDom, parentNode) => {
let childNode
const props = virtualDom.props
const type = virtualDom.type
if (type && type.isReactComponent) {
return render(new type(props).render(), parentNode)
}
if (typeof type === 'function') {
return render(type(props), parentNode)
}
if (typeof virtualDom === 'string' || typeof virtualDom === 'number') {
childNode = document.createTextNode(virtualDom)
}
if (typeof virtualDom.type === 'string') {
childNode = document.createElement(virtualDom.type)
}
if (virtualDom && virtualDom.props && typeof virtualDom.props === 'object') {
const props = virtualDom.props
Object.keys(props).forEach((key) => {
if (key === 'style') {
const styleObj = props[key]
const cssText = Object.keys(styleObj).map(styleName => {
return `${styleName.replace(/[A-Z]/g, (childStr) => {
return '-' + childStr.toLowerCase()
})}:${styleObj[styleName]}`
}).join(';');
childNode.setAttribute(key, cssText) // 或者childNode.style.cssText = cssText
} else if (key === 'children') {
const children = Array.isArray(props.children) ? props.children : [props.children]
children.forEach(child => {
render(child, childNode)
})
} else if (key === 'className') {
childNode.className = props.className
} else {
childNode.setAttribute(key, props[key])
}
})
}
parentNode.appendChild(childNode)
}
export default {
render
}
做的事情其实就是将数据渲染到节点上面。符合react的介绍:构建用户界面的 JavaScript 库。
注意点
- 处理style属性的时候记得将驼峰转换成横杠,str.replace的用法
- 为什么会将其他属性挂载在dom节点上呢?感觉没什么意义。真实项目中貌似没有看到,区别在哪里?