前言
react使用也有一段時間了,大家對這個框架褒獎有加,但是它究竟好在哪裡呢? 讓我們結合它的原始碼,探究一二!(當前原始碼為react16,讀者要對react有一定的瞭解)
回到最初
根據react官網上的例子,快速構建react專案
npx create-react-app my-app
cd my-app
npm start
複製程式碼
開啟專案並跑起來以後,暫不關心專案結構及語法糖,看到App.js
裡,這是一個基本的react元件 我們console一下,看看有什麼結果。
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
</header>
</div>
);
}
}
export default App;
console.log(<App/>)
複製程式碼
可以看到,<App/>
元件其實是一個JS物件,並不是一個真實的dom。
ES6 引入了一種新的原始資料型別Symbol,表示獨一無二的值。有興趣的同學可以去阮一峰老師的ES6入門詳細瞭解一下
上面有我們很熟悉的props
,ref
,key
,我們稍微修改一下console,看看有什麼變化。
console.log(<App key={1} abc={2}><div>你好,這裡是App元件</div></App>)
複製程式碼
可以看到,props
,key
都發生了變化,值就是我們賦予的值,props
中巢狀了children屬性。可是為什麼我們嵌入的是div,實際上卻是一個物件呢?
開啟原始碼
/node_modules/react
首先開啟index.js
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react.production.min.js');
} else {
module.exports = require('./cjs/react.development.js');
}
複製程式碼
可以知道目前用上的是./cjs/react.development.js
,直接開啟檔案。
根據最初的程式碼,我們元件<App/>
用到了React.Component。找到React暴露的介面:
接著找到Component: Component
方法,
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function (partialState, callback) {
!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0;
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function (callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
複製程式碼
上面就是一些簡單的建構函式,也可以看到,我們常用的setState是定義在原型上的2個方法。
至此,一個<App/>
元件已經有一個大概的雛形:
到此為止了嗎?這看了等於沒看啊,究竟元件是怎麼變成div的?render嗎? 可是全域性搜尋,也沒有一個function是render啊。
原來,我們的jsx語法會被babel
編譯的。
這下清楚了,還用到了React.createElement
createElement: createElementWithValidation,
複製程式碼
通過createElementWithValidation
,
function createElementWithValidation(type, props, children) {
······
var element = createElement.apply(this, arguments);
return element;
}
複製程式碼
可以看到,return了一個element,這個element又是繼承自createElement
,接著往下找:
function createElement(type, config, children) {
var propName = void 0;
// Reserved names are extracted
var props = {};
var key = null;
var ref = null;
var self = null;
var source = null;
······
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
複製程式碼
這裡又返回了一個ReactElement
方法,再順著往下找:
var ReactElement = function (type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
······
return element;
};
複製程式碼
誒,這裡好像返回的就是element
物件,再看我們最初的<App/>
的結構,是不是很像
React.createElement 、 createElementWithValidation 、 createElement 、 ReactElement,通過這些方法,我們用class宣告的React元件在變成真實dom之前都是ReactElement
型別的js物件
createElementWithValidation
:
- 首先校驗type是否是合法的
- 校驗了props是否符合設定的proptypes
- 校驗了子節點的key,確保每個陣列中的元素都有唯一的key
createElement
:
- type是你要建立的元素的型別,可以是html的div或者span,也可以是其他的react元件,注意大小寫
- config中包含了props、key、ref、self、source等
- 向props加入children,如果是一個就放一個物件,如果是多個就放入一個陣列。
- 那如果type.defaultProps有預設的props時,並且對應的props裡面的值是undefined,把預設值賦值到props中
- 也會對key和ref進行校驗
ReactElement
:
ReactElement就比較簡單了,建立一個element物件,引數裡的type、key、ref、props、等放進去,然後return了。最後呼叫Object.freeze使物件不可再改變。
元件的掛載
我們上面只是簡單的探究了<App/>
的結構和原理,那它究竟是怎麼變成真實dom的呢
ReactDOM.render(<App />, document.getElementById('root'));
複製程式碼
我們接著用babel編譯一下:
原來ReactDOM.render
呼叫的是render方法,一樣,找暴露出來的介面。
var ReactDOM = {
······
render: function (element, container, callback) {
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
},
······
};
複製程式碼
它返回的是一個legacyRenderSubtreeIntoContainer
方法,這次我們直接打上console.log
這是列印出來的結果,
legacyRenderSubtreeIntoContainer 這個方法除主要做了兩件事:
- 清除dom容器元素的子元素
while (rootSibling = container.lastChild) {
{
if (!warned && rootSibling.nodeType === ELEMENT_NODE && rootSibling.hasAttribute(ROOT_ATTRIBUTE_NAME)) {
warned = true;
}
}
container.removeChild(rootSibling);
}
複製程式碼
- 建立ReactRoot物件
原始碼暫時只讀到了這裡,關於React16.1~3的新功能,以及新的生命週期的使用和原理、Fiber
究竟是什麼,我們將在後續文章接著介紹。
廣而告之
本文釋出於薄荷前端週刊,歡迎Watch & Star ★,轉載請註明出處。