你们都很清楚知道React是一个用于构建用户界面的JavaScript库,能够书写jsx编译成真正的DOM插入到要显示的页面上。下面咱们作一些准备工做了解jsx变成DOM的过程,进而本身去实现一遍。javascript
npm install create-react-app -g
create-react-app react-demo
cd react-demo
npm start
复制代码
这时候咱们已经新建好一个react项目了,接下来在index.js中写入console.log(<h1 style={{color: 'red'}}>hello world</h1>)
,打印出来的结果css
{
props: {
children: 'hello world',
style: {color: ''red'} ... }, type: 'h1' } 复制代码
这就是一个React对象,也就是虚拟DOM。接下来咱们打开babel官网,输入<h1 style={{color: 'red'}}>hello world</h1>
,结果以下图所示 html
咱们在项目中实现一个简单的例子java
// index.js
import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(<h1 style={{color: 'red'}}>hello world</h1>, document.getElementById('root'));
复制代码
结果以下图 react
到这一步你们应该清楚知道写jsx到咱们看到的页面效果的实现过程了吧?经过babel转化jsx成React中的createElement函数并执行,获得React对象传入到 ReactDom.render 生成真是的DOM,而且插入到指定的DOM节点上。npm
下面来实现一下createElement函数,返回一个React对象,建立react.js数组
// react.js
function ReactElement(type, props) { // 生成react对象 虚拟DOM
const element = {type, props}
return element;
}
function createElement(type, config, children){
let propName;
const props = {};
for (propName in config) {
props[propName] = config[propName]; // 拷贝config
}
const childrenLength = arguments.length - 2; // 获取children个数
if (childrenLength === 1) {
props.children = children;
} else {
props.children = Array.prototype.slice.call(arguments, 2) // 传入了多个children
}
return ReactElement(type, props) // react对象,虚拟DOM
}
export default { createElement }
复制代码
修改一下index.js,验证一下写的createElement是否正确bash
import React from './react.js'; // 引入本身写的
...
console.log( React.createElement('h1', {style: {color: 'red'}}, 'hello world') );
// 或
// console.log(<h1 style={{color: 'red'}}>hello world</h1>)
// 打印结果以下, 则正确了
//{
// type: 'h1',
// props: {
// style: {color: 'red'},
// children: 'hello world',
// ...
// }
}
复制代码
下面来实现render函数,建立react-dom.jsbabel
function render(element, parentNode) {
if (typeof element == 'string' || typeof element == 'number') { // 单独处理
return parentNode.appendChild(document.createTextNode(element));
}
let type, props;
type = element.type;
props = element.props;
let domElement = document.createElement(type);
for (let propName in props) {
if (propName === 'children') {
let children = props[propName];
children = Array.isArray(children)? children : [children];
children.forEach(child => {
render(child, domElement); // 递归
})
} else if (propName === 'className') { // 生成类名
domElement.className = props[propName];
} else if (propName === 'style') { // 生成样式
let styleObj = props[propName];
let cssText = Object.keys(styleObj).map(attr => {
return `${attr.replace(/([A-Z])/g, function() { return "-" + arguments[1].toLocaleLowerCase() })} : ${styleObj[attr]}`
}).join(';');
domElement.style.cssText = cssText;
}else { // 生成其余属性,还有像‘htmlFor’等等这些须要单独处理的,这里就不一一处理了
if(propName.substring(0, 2) !== '__'){
domElement.setAttribute(propName, props[propName]);
}
}
}
return parentNode.appendChild(domElement); // 插入生成的真是DOM
}
export default { render }
复制代码
接下来修改index.js进行验证app
import React from './react.js';
import ReactDOM from './react-dom.js';
let element = React.createElement('h1', {style: {color: 'red'}}, 'hello world');
ReactDOM.render(element, document.getElementById('root'));
复制代码
结果以下,代码运行成功
这里的代码,当咱们使用函数组件或类组件时,不能正确生成DOM,继续拓展下
// index.js
import React from './react.js';
import ReactDOM from './react-dom.js';
// 函数组件
function Welcome(props) {
return <h1 style={props.style}>{props.name}</h1>
}
let element = React.createElement(Welcome, {name: 'hello world', style: {color: 'red'}});
ReactDOM.render(element, document.getElementById('root'));
复制代码
这时候createElement返回的React对象的type是function,应该在建立DOM以前执行这个函数,拿到Welcome的返回值,再进行解析,那么就在react-dom.js
中加多个判断
...
let type, props;
type = element.type;
props = element.props;
if (typeof type === 'function') { // 若是是函数组件,先执行
element = type(props);
type = element.type;
props = element.props;
}
let domElement = document.createElement(type);
...
复制代码
这时候就能正常使用函数组件了
// index.js
import React from './react.js';
import ReactDOM from './react-dom.js';
// 类组件
class Welcome extends React.Component{
render() {
return React.createElement('h1', {style: this.props.style}, this.props.name, this.props.age)
}
}
let element = React.createElement(Welcome, {name: 'hello world', style: {color: 'red'}});
ReactDOM.render(element, document.getElementById('root'));
复制代码
类组件须要React中的父类Component,那么就在react.js加上
// react.js
class Component {
static isClassComponent = true // 用于区分类组件
constructor(props) {
this.props = props
}
}
...
export default {
createElement,
Component
}
复制代码
因为使用typeof
判断类返回的也是'function',那么就跟函数组件的判断有冲突了,并且类是须要new进行实例化的,所以在父类上加多了一个静态属性 isClassComponent 继承给子类进行区分。下面就继续修改react-dom.js
的代码
// react-dom.js
...
if(type.isClassComponent) { // 类组件
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;
}
...
复制代码
这样也把类组件拓展成功了。
以上过程就简单实现了react中的createElement和react-dom中的render。若有错误,请指出,感谢阅读。