前言:使用react也有二年多了,一直停留在使用層次。雖然不少時候這樣是夠了。可是總以爲不深刻理解其背後是的實現邏輯,很難體會框架的精髓。最近會寫一些相關的一些文章,來記錄學習的過程。node
備註:react和react-dom源碼版本爲16.8.6 本文適合使用過React進行開發,並有必定經驗的人閱讀。
好了閒話少說,咱們一塊兒來看源碼吧
寫過react
知道,咱們使用react
編寫代碼都離不開webpack
和babel
,由於React
要求咱們使用的是class
定義組件,而且使用了JSX
語法編寫HTML
。瀏覽器是不支持JSX
而且對於class
的支持也很差,因此咱們都是須要使用webpack
的jsx-loader
對jsx
的語法作一個轉換,而且對於ES6
的語法和react
的語法經過babel
的babel/preset-react
、babel/env
和@babel/plugin-proposal-class-properties
等進行轉義。不熟悉怎麼從頭搭建react
的個人示例代碼就放在這。react
好了,咱們從一個最簡單實例demo
來看react
到底作了什麼webpack
下面是咱們的代碼git
import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render( <h1 style={{color:'red'}} >11111</h1>, document.getElementById("root") );
這是頁面上的效果
github
咱們如今看看在瀏覽器中的代碼是如何實現的:web
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", { style: { color: 'red' } }, "11111"), document.getElementById("root"));
最終通過編譯後的代碼是這樣的,發現本來的<h1>11111</h1>
變成了一個react.createElement
的函數,其中原生標籤的類型,內容都變成了參數傳入這個函數中.這個時候咱們大膽的猜想react.createElement
接受三個參數,分別是元素的類型、元素的屬性、子元素。好了帶着咱們的猜測來看一下源碼。瀏覽器
咱們不難找到,源碼位置在位置 ./node_modules/react/umd/react.development.js:1941
babel
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; if (config != null) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { key = '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object for (propName in config) { if (hasOwnProperty$1.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } // Children can be more than one argument, and those are transferred onto // the newly allocated props object. var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } { if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; } // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } { if (key || ref) { var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); }
首先咱們來看一下它的三個參數
第一個type
:咱們想一下這個type
的可能取值有哪些?app
h1
、div
,span
等);App
class App extends React.Component { static defaultProps = { text: 'DEMO' } render() { return (<h1>222{this.props.text}</h1>) } }
第二個config
:這個就是咱們傳遞的一些屬性
第三個children
:這個就是子元素,最開始咱們猜測就三個參數,其實後面看了源碼就知道這裏其實不止三個。框架
接下來咱們來看看react.createElement
這個函數裏面會幫咱們作什麼事情。
一、首先會初始化一些列的變量,以後會判斷咱們傳入的元素中是否帶有有效的key
和ref
的屬性,這兩個屬性對於react
是有特殊意義的(key是能夠優化React的渲染速度的,ref是能夠獲取到React渲染後的真實DOM節點的),若是檢測到有傳入key
,ref
,__self
和__source
這4個屬性值,會將其保存起來。
二、接着對傳入的config
作處理,遍歷config
對象,而且剔除掉4個內置的保留屬性(key,ref,__self,__source)
,以後從新組裝新的config
爲props
。這個RESERVED_PROPS
是定義保留屬性的地方。
var RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true };
三、以後會檢測傳入的參數的長度,若是childrenLength
等於1的狀況下,那麼就表明着當前createElement
的元素只有一個子元素,那麼將內容賦值到props.children
。那何時childrenLength
會大於1呢?那就是當你的元素裏面涉及到多個子元素的時候,那麼children
將會有多個傳入到createElement
函數中。例如:
ReactDOM.render( <h1 style={{color:'red'}} key='22'> <div>111</div> <div>222</div> </h1>, document.getElementById("root") );
編譯後是什麼樣呢?
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", { style: { color: 'red' }, key: "22" }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, "111"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, "222")), document.getElementById("root") );
這個時候react.createElement拿到的arguments.length
就大於3了。也就是childrenLength
大於1。這個時候咱們就遍歷把這些子元素添加到props.children
中。
四、接着函數將會檢測是否存在defaultProps
這個參數,由於如今的是一個最簡單的demo,並且傳入的只是原生元素,因此沒有defaultProps
這個參數。那麼咱們來看下面的例子:
import React, { Component } from "react"; import ReactDOM from "react-dom"; class App extends Component { static defaultProps = { text: '33333' } render() { return (<h1>222{this.props.text}</h1>) } } ReactDOM.render( <App/>, document.getElementById("root") );
編譯後的
var App = /*#__PURE__*/ function (_Component) { _inherits(App, _Component); function App() { _classCallCheck(this, App); return _possibleConstructorReturn(this, _getPrototypeOf(App).apply(this, arguments)); } _createClass(App, [{ key: "render", value: function render() { return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", null, "222", this.props.text); } }]); return App; }(react__WEBPACK_IMPORTED_MODULE_0__["Component"]); _defineProperty(App, "defaultProps", { text: '33333' }); react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(App, null), document.getElementById("root") );
發現傳入react.createElement的是一個App的函數,class通過babel轉換後會變成一個構造函數。有興趣能夠本身去看babel對於class的轉換,這裏就不解析轉換過程,總得來講就是返回一個App的構造函數傳入到react.createElement中.若是type
傳的東西是個對象,且type
有defaultProps
這個東西而且props
中對應的值是undefined
,那就defaultProps
的值也塞props
裏面。這就是咱們組價默認屬性的由來。
五、 檢測key
和ref
是否有賦值,若是有將會執行defineKeyPropWarningGetter
和defineRefPropWarningGetter
兩個函數。
function defineKeyPropWarningGetter(props, displayName) { var warnAboutAccessingKey = function () { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true; warningWithoutStack$1(false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName); } }; warnAboutAccessingKey.isReactWarning = true; Object.defineProperty(props, 'key', { get: warnAboutAccessingKey, configurable: true }); } function defineRefPropWarningGetter(props, displayName) { var warnAboutAccessingRef = function () { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; warningWithoutStack$1(false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName); } }; warnAboutAccessingRef.isReactWarning = true; Object.defineProperty(props, 'ref', { get: warnAboutAccessingRef, configurable: true }); }
我麼能夠看出這個二個方法就是給key
和ref
添加了警告。這個應該只是在開發環境纔有其中isReactWarning
就是上面判斷key
與ref
是否有效的一個標記。
六、最後將一系列組裝好的數據傳入ReactElement
函數中。
var ReactElement = function (type, key, ref, self, source, owner, props) { var element = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner }; { element._store = {}; Object.defineProperty(element._store, 'validated', { configurable: false, enumerable: false, writable: true, value: false }); Object.defineProperty(element, '_self', { configurable: false, enumerable: false, writable: false, value: self }); Object.defineProperty(element, '_source', { configurable: false, enumerable: false, writable: false, value: source }); if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; };
其實裏面很是簡單,就是將傳進來的值都包裝在一個element對象中
var hasSymbol = typeof Symbol === 'function' && Symbol.for; var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7;
從代碼上看若是支持Symbol
就會用Symbol.for
方法建立一個key
爲react.element
的symbol
,不然就會返回一個0xeac7
_store
中添加了一個新的對象validated
(可寫入),element
對象中添加了_self
和_source
屬性(只讀),最後凍結了element.props
和element
。
這樣就解釋了爲何咱們在子組件內修改props
是沒有效果的,只有在父級修改了props
後子組件纔會生效
最後就將組裝好的element
對象返回了出來,提供給ReactDOM.render
使用。到這有關的主要內容咱們看完了。下面咱們來補充一下知識點
Object.freeze
方法能夠凍結一個對象,凍結指的是不能向這個對象添加新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該對象已有屬性的可枚舉性、可配置性、可寫性。該方法返回被凍結的對象。
const obj = { a: 1, b: 2 }; Object.freeze(obj); obj.a = 3; // 修改無效
須要注意的是凍結中能凍結當前對象的屬性,若是obj中有一個另外的對象,那麼該對象仍是能夠修改的。因此React纔會須要凍結element和element.props。
if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); }
後續更多文章將在個人 github第一時間發佈,歡迎關注。