準備階段
建立項目
- 經過
create-react-app easy_react
腳手架建立一個項目
- 清理
src
目錄,這裏咱們只留下一個index.js
便可
- 修改
index.js
以下
import React from 'react';
import ReactDOM from 'react-dom';
const style = {
color: 'red',
background: 'yellow',
fontSize:'20px'
}
ReactDOM.render(
<h1 id={'gu_yan'} className={'gu-yan'} style={style}> Guyan </h1>, document.getElementById('root'));
複製代碼
啓動項目
- 執行
yarn start
頁面效果以下
分析階段
- 將
index.js
的代碼拷貝到babel
中進行轉換,效果以下
- 分析上圖
‖
‖
‖
↓
React.createElement("h1", {
id: 'gu_yan',
className: 'gu-yan',
style: style
}, "Guyan")
複製代碼
- 將
index.js
文件,修改以下,發現頁面無變化,控制檯打印結果以下圖
import React from 'react';
import ReactDOM from 'react-dom';
const style = {
color: 'red',
background: 'yellow',
fontSize: '20px'
};
const element = React.createElement("h1", {
id: 'gu_yan',
className: 'gu-yan',
style: style
}, "Guyan")
console.log(element)
ReactDOM.render(element, document.getElementById('root'));
複製代碼
- 如上圖可知
React.createElement()
執行返回的結果是一個對象,這個對象咱們就稱之爲虛擬DOM
,到這裏咱們就能夠總結出虛擬DOM
的由來了
- 【1】在
webpack
打包的時候會調用babel-loader
,將JSX
語法轉義爲React.createElement(...)
的形式
- 【2】
React.createElement
執行返回一個對象,這對象就是虛擬DOM
- 解析
React.createElement
的執行過程
- 【1】收集屬性對象,處理特殊的屬性好比
children
,key
,ref
;本文只舉例說明children
- 【1-1】建立一個
props
對象
- 【1-2】將傳進來的第二個參數對象中的每個屬性都掛在
props
上,值爲第二個參數對象中相對於的值
- 【1-3】給
props
掛一個children
屬性,值爲傳進來的第三個參數
- 注:
children
這個值是一個字符串或者是一個數組。若是執行React.createElement
的時候傳入的參數大於3,那麼children
的值就是一個數組,其值爲除前兩個以外的全部屬性
- 【2】將傳入的
type
和收集的屬性對象做爲參數傳入到ReactElement
中執行(reactElement(type,props)
)
- 【3】
ReactElement
執行建立一個newElement
對象
- 【4】給
newElement
掛一個$$typeof
屬性,這裏咱們統一將其值賦爲Symbol(react.element)
- 【5】給
newElement
掛一個type
屬性,值爲傳進來的type
- 【6】給
newElement
掛一個props
屬性,值爲傳進來的props
- 【7】返回
newElement
(虛擬DOM
)
- 解析
ReactDom.render
的執行過程
- 【1】判斷傳入的虛擬
DOM
的類型
- 【1.1】普通文本類型,則第一個參數多是
string
或者number
,則直接建立一個真實文本節點
- 【1.2】普通
html
標籤組件,好比<div></div>
,第一個參數對象的type
屬性是一個string
類型,建立一個真實的DOM
節點並將第一個參數的props
屬性中的一些普通屬性掛載到這個真實的DOM
節點上,並對一些特殊的屬性好比children
的進行處理,詳細見下文的實現階段
- 【1.3】函數組件(
function Component
),第一個參數對象的type
屬性是一個function
類型,type
執行傳入第一個參數的props
- 【1.4】類組件(
class Component
),第一個參數對象的type
屬性上有一個isReactComponent
屬性,new type(props)
建立實例,並讓實例的render
方法執行
- 【2】將建立的真實節點插入到父節點中
實現階段
react.js
class Component {
static isReactComponent = true;
constructor(props){
this.props = props;
}
}
function ReactElement(type,props){
const virtual_dom = {};
virtual_dom.$$typeof = Symbol.for('react.element');
virtual_dom.type = type;
virtual_dom.props = props;
return virtual_dom;
}
function createElement(type,config,children){
const props = {};
for (const propName in config){
if (config.hasOwnProperty(propName)){
props[propName] = config[propName];
}
}
const childrenLength = arguments.length - 2;
if (childrenLength === 1){
props.children = children;
}else if (childrenLength > 2){
props.children = Array.from(arguments).slice(2);
}
return ReactElement(type,props);
}
export default {createElement,Component}
複製代碼
react-dom.js
function render(virtual_dom,parent_node){
if (typeof virtual_dom ==='string' || typeof virtual_dom === 'number'){
return parent_node.appendChild(document.createTextNode(virtual_dom));
}
if (virtual_dom.type.isReactComponent){
virtual_dom = new virtual_dom.type(virtual_dom.props).render();
render(virtual_dom,parent_node);
}
if (typeof virtual_dom.type === 'function'){
virtual_dom = virtual_dom.type(virtual_dom.props);
render(virtual_dom,parent_node);
}
let {type , props} = virtual_dom;
let real_dom = document.createElement(type);
for (const propName in props){
if(props.hasOwnProperty(propName)){
const prop = props[propName];
if(propName === 'className'){
real_dom.className = prop;
}else if (propName === 'style'){
let cssText = Object.keys(prop).map(attr=>(`${attr.replace(/([A-Z])/g,function(){ return `-${arguments[1].toLowerCase()}` })}:${prop[attr]}`)).join(';');
real_dom.style.cssText = cssText;
}else if (propName === 'children'){
let childArr = Array.isArray(prop) ? prop : [prop];
childArr.forEach(child=>render(child,real_dom));
}else {
real_dom.setAttribute(propName,prop);
}
}
}
return parent_node.appendChild(real_dom);
}
複製代碼
寫在最後
- 平常推薦使用函數組件
- 本文中的僅僅在表面層說明原理。並未深度剖析,如何錯誤還望指教。謝謝!