你們都很清楚知道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。若有錯誤,請指出,感謝閱讀。