對一個框架源碼的解讀,既有利於更深刻地瞭解框架,使用上更駕輕就熟,又能夠學習到其中代碼組織的思路,吸取其精華簡潔的寫法以便於平常工做上使用。下面我就挑選近年大熱門react(15.3.1),從中剖析框架的設計思路,由淺入深地學習。
咱們從這個文件開始看起,這是react的主入口(./lib/react.js)。html
/** * Copyright 2013-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule React */ 'use strict'; var _assign = require('object-assign'); var ReactChildren = require('./ReactChildren'); var ReactComponent = require('./ReactComponent'); var ReactPureComponent = require('./ReactPureComponent'); var ReactClass = require('./ReactClass'); var ReactDOMFactories = require('./ReactDOMFactories'); var ReactElement = require('./ReactElement'); var ReactPropTypes = require('./ReactPropTypes'); var ReactVersion = require('./ReactVersion'); var onlyChild = require('./onlyChild'); var warning = require('fbjs/lib/warning'); var createElement = ReactElement.createElement; var createFactory = ReactElement.createFactory; var cloneElement = ReactElement.cloneElement; if (process.env.NODE_ENV !== 'production') { var ReactElementValidator = require('./ReactElementValidator'); createElement = ReactElementValidator.createElement; createFactory = ReactElementValidator.createFactory; cloneElement = ReactElementValidator.cloneElement; } var __spread = _assign; if (process.env.NODE_ENV !== 'production') { var warned = false; __spread = function () { process.env.NODE_ENV !== 'production' ? warning(warned, 'React.__spread is deprecated and should not be used. Use ' + 'Object.assign directly or another helper function with similar ' + 'semantics. You may be seeing this warning due to your compiler. ' + 'See https://fb.me/react-spread-deprecation for more details.') : void 0; warned = true; return _assign.apply(null, arguments); }; } var React = { // Modern Children: { map: ReactChildren.map, forEach: ReactChildren.forEach, count: ReactChildren.count, toArray: ReactChildren.toArray, only: onlyChild }, Component: ReactComponent, PureComponent: ReactPureComponent, createElement: createElement, cloneElement: cloneElement, isValidElement: ReactElement.isValidElement, // Classic PropTypes: ReactPropTypes, createClass: ReactClass.createClass, createFactory: createFactory, createMixin: function (mixin) { // Currently a noop. Will be used to validate and trace mixins. return mixin; }, // This looks DOM specific but these are actually isomorphic helpers // since they are just generating DOM strings. DOM: ReactDOMFactories, version: ReactVersion, // Deprecated hook for JSX spread, don't use this for anything. __spread: __spread }; module.exports = React;
咱們直接跳過前面的環境判斷以及模塊引入,能夠看到從50行起就是React的關鍵代碼。而且咱們能夠清晰的從上面看到React所提供的方法。這是離咱們使用者最近的一層,看到信息量很少。咱們就按照開發的思路,一步一步地深刻源碼。
編寫一個組件,固然是從建立開始,咱們使用的是 React.createClass,不難發現,React.createClass實際上引用的是ReactClass.createClass。固然咱們也能夠用ES6的寫法直接繼承至React.Component.這兩種寫法有什麼差別存在,咱們先把懸念放在後面。
先從createClass的源碼看起(./lib/ReactClass)。react
var ReactClass = { /** * Creates a composite component class given a class specification. * See https://facebook.github.io/react/docs/top-level-api.html#react.createclass * * @param {object} spec Class specification (which must define `render`). * @return {function} Component constructor function. * @public */ createClass: function (spec) { var Constructor = function (props, context, updater) { // This constructor gets overridden by mocks. The argument is used // by mocks to assert on what gets mounted. if (process.env.NODE_ENV !== 'production') { process.env.NODE_ENV !== 'production' ? warning(this instanceof Constructor, 'Something is calling a React component directly. Use a factory or ' + 'JSX instead. See: https://fb.me/react-legacyfactory') : void 0; } // Wire up auto-binding if (this.__reactAutoBindPairs.length) { bindAutoBindMethods(this); } this.props = props; this.context = context; this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; this.state = null; // ReactClasses doesn't have constructors. Instead, they use the // getInitialState and componentWillMount methods for initialization. var initialState = this.getInitialState ? this.getInitialState() : null; if (process.env.NODE_ENV !== 'production') { // We allow auto-mocks to proceed as if they're returning null. if (initialState === undefined && this.getInitialState._isMockFunction) { // This is probably bad practice. Consider warning here and // deprecating this convenience. initialState = null; } } !(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent') : _prodInvariant('82', Constructor.displayName || 'ReactCompositeComponent') : void 0; this.state = initialState; }; Constructor.prototype = new ReactClassComponent(); Constructor.prototype.constructor = Constructor; Constructor.prototype.__reactAutoBindPairs = []; injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); mixSpecIntoComponent(Constructor, spec); // Initialize the defaultProps property after all mixins have been merged. if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); } if (process.env.NODE_ENV !== 'production') { // This is a tag to indicate that the use of these method names is ok, // since it's used with createClass. If it's not, then it's likely a // mistake so we'll warn you to use the static property, property // initializer or constructor respectively. if (Constructor.getDefaultProps) { Constructor.getDefaultProps.isReactClassApproved = {}; } if (Constructor.prototype.getInitialState) { Constructor.prototype.getInitialState.isReactClassApproved = {}; } } !Constructor.prototype.render ? process.env.NODE_ENV !== 'production' ? invariant(false, 'createClass(...): Class specification must implement a `render` method.') : _prodInvariant('83') : void 0; if (process.env.NODE_ENV !== 'production') { process.env.NODE_ENV !== 'production' ? warning(!Constructor.prototype.componentShouldUpdate, '%s has a method called ' + 'componentShouldUpdate(). Did you mean shouldComponentUpdate()? ' + 'The name is phrased as a question because the function is ' + 'expected to return a value.', spec.displayName || 'A component') : void 0; process.env.NODE_ENV !== 'production' ? warning(!Constructor.prototype.componentWillRecieveProps, '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', spec.displayName || 'A component') : void 0; } // Reduce time spent doing lookups by setting these on the prototype. for (var methodName in ReactClassInterface) { if (!Constructor.prototype[methodName]) { Constructor.prototype[methodName] = null; } } return Constructor; }, injection: { injectMixin: function (mixin) { injectedMixins.push(mixin); } } };
644行起,createClass方法首先定義了一個Constructor構造函數,摺疊內部,咱們看看這個方法在返回一個構造函數前作了什麼,
直接跳到681行,構造函數的prototype指向一個ReactClassComponent的實例。git
Constructor.prototype = new ReactClassComponent();
往上翻咱們能夠發現,ReactClassComponent的prototype屬性,拷貝了ReactComponent.prototype 和 ReactClassMixin,所以咱們的組件可使用ReactComponent原型上的方法。github
var ReactClassComponent = function () {}; _assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);
683行到687行。
定義了 __reactAutoBindPairs 爲一個空數組。
先將mixin裏面的方法按照key,function內容的順序成對存入 __reactAutoBindPairs ,
接着就是spec對象裏的方法用一樣的方式存入。api
Constructor.prototype.__reactAutoBindPairs = []; injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)); mixSpecIntoComponent(Constructor, spec);
690行咱們能夠看到Constructor.defaultProps 就是咱們開發中 getDefaultProps()所返回的對象。數組
if (Constructor.getDefaultProps) { Constructor.defaultProps = Constructor.getDefaultProps(); }
694行 -- 712行 是在開發環境中對開發者的建議,以及規範使用的警示。
715行 -- 719行 能夠知道咱們建立一個組件須要定義的方法都在ReactClassInterface上有,當前未定義的方法設置爲空,咱們就能夠經過打印組件的prototype屬性清楚地在日誌上知道咱們有哪些api是未定義的。經過設置未定義的屬性爲空,能夠減小程序查找的時間。
721行 最終返回了這個封裝好的構造函數。app
for (var methodName in ReactClassInterface) { if (!Constructor.prototype[methodName]) { Constructor.prototype[methodName] = null; } } return Constructor;
看到這裏咱們能夠明白一點,組件實質上是一個構造函數,而咱們自定義的方法,既存在了prototype裏,也按照[key,content,key,content...]的方式概括到了Constructor.prototype.__reactAutoBindPairs 裏。這是爲了組件實例化時能夠將這些方法直接遍歷綁定在實例上,而且避免了React官方指定的方法也被綁定在實例上。框架
接下來咱們展開645行的Constructor,能夠看到實例化的時候主要作了兩件事。
654行
第一件事就是將上文提到的存在Constructor.prototype.__reactAutoBindPairs 的內容成對取出,綁定在實例上。ide
if (this.__reactAutoBindPairs.length) { bindAutoBindMethods(this); }
668行 ——679行
第二件事就是判斷組件是否有定義getInitialState,若是有,則將state設置爲該方法返回的值,若是沒有設置state爲null。函數
var initialState = this.getInitialState ? this.getInitialState() : null; if (process.env.NODE_ENV !== 'production') { // We allow auto-mocks to proceed as if they're returning null. if (initialState === undefined && this.getInitialState._isMockFunction) { // This is probably bad practice. Consider warning here and // deprecating this convenience. initialState = null; } } !(typeof initialState === 'object' && !Array.isArray(initialState)) ? process.env.NODE_ENV !== 'production' ? invariant(false, '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent') : _prodInvariant('82', Constructor.displayName || 'ReactCompositeComponent') : void 0; this.state = initialState;
到這裏咱們大概地知道了一個組件從建立構造函數到實例化的時候作了什麼事情了。後續咱們繼續解讀更底層的ReactComponent。
但願能對你們有幫助。若是有錯誤的地方,懇請各位大神指正。