React源碼學習——ReactClass


前言

之前一直在使用react做開發,但是對其內部的工作機制卻一點兒都不了解,說白了就是一直在套api,毫無成就感。趁最近比較閑,對源碼做了一番研究,並通過博客的方式做一些記錄。

進入正題

通過編寫自定義組件來實現代碼復用是react一個很亮眼的創新點,我們知道react創建組件一般使用兩種方式:

  1. 通過React.createClass API
  2. 運用es6語法 class xx extends React.Component 

雖然后者正在逐漸取代前者,但是去研究一下前者也是很有必要的。我們先來看一看 createClass 方法,然后再去分析一下es6的寫法,通過對比你將發現 createClass 被取代也是有理可循的。我們在源碼中找到createClass方法,如圖,在貼出的代碼中我將作者原來的注釋都刪除了,並在必要的地方加上了自己的理解。

 1 var ReactClass = {
 2 
 3   createClass: function (spec) {
 4 
 5     //warning:createClass API將會在16版本被移除,現在通常是通過es6的語法創建組件,后面會進行說明
 6     if ("development" !== 'production') {
 7       "development" !== 'production' ? warning(didWarnDeprecated, '%s: React.createClass is deprecated and will be removed in version 16. ' + 'Use plain JavaScript classes instead. If you\'re not yet ready to ' + 'migrate, create-react-class is available on npm as a ' + 'drop-in replacement.', spec && spec.displayName || 'A Component') : void 0;
 8       didWarnDeprecated = true;
 9     }
10 
11     //identity返回參數中的匿名函數
12     var Constructor = identity(function (props, context, updater) {
13 
14       //warning: 必須通過new方法創建Constructor實例來使用組件
15       if ("development" !== 'production') {
16         "development" !== '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;
17       }
18 
19       // 處理自動綁定的方法
20       if (this.__reactAutoBindPairs.length) {
21         bindAutoBindMethods(this);
22       }
23 
24       this.props = props;
25       this.context = context;
26       this.refs = emptyObject;
27       this.updater = updater || ReactNoopUpdateQueue;
28       this.state = null;
29 
30       //調用 getInitialState 初始化state
31       var initialState = this.getInitialState ? this.getInitialState() : null;
32       if ("development" !== 'production') {
33         if (initialState === undefined && this.getInitialState._isMockFunction) {
34           initialState = null;
35         }
36       }
37       //當 initialState 返回的不是object或null類型拋出錯誤
38       !(typeof initialState === 'object' && !Array.isArray(initialState)) ? "development" !== 'production' ? invariant(false, '%s.getInitialState(): must return an object or null', Constructor.displayName || 'ReactCompositeComponent') : _prodInvariant('82', Constructor.displayName || 'ReactCompositeComponent') : void 0;
39 
40       this.state = initialState;
41     });
42     //使Constructor繼承ReactClassComponent
43     Constructor.prototype = new ReactClassComponent();
44     Constructor.prototype.constructor = Constructor;
45     Constructor.prototype.__reactAutoBindPairs = [];
46     
47     //將通過 ReactClass.injection.injectMixin(mixin) 方法傳入的mixin注入到Constructor中去
48     injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor));
49 
50     // 將 createClass 參數中定義的屬性或方法混入到 Constructor.prototype 或 Constructor 中,其中分了很多情況,后面詳細說明
51     mixSpecIntoComponent(Constructor, spec);
52 
53     // 調用 getDefaultProps 初始化 defaultProps,掛載組件時會作為參數傳給new Constructor(),注意 getDefaultProps 不在prototype上
54     if (Constructor.getDefaultProps) {
55       Constructor.defaultProps = Constructor.getDefaultProps();
56     }
57 
58     if ("development" !== 'production') {
59       // getDefaultProps 只能在createClass中使用,通過 isReactClassApproved 防止在外部調用
60       if (Constructor.getDefaultProps) {
61         Constructor.getDefaultProps.isReactClassApproved = {};
62       }
63       // 同上
64       if (Constructor.prototype.getInitialState) {
65         Constructor.prototype.getInitialState.isReactClassApproved = {};
66       }
67     }
68     // 必須定義render方法,否則拋出錯誤
69     !Constructor.prototype.render ? "development" !== 'production' ? invariant(false, 'createClass(...): Class specification must implement a `render` method.') : _prodInvariant('83') : void 0;
70     // 一些拼寫錯誤的warning
71     if ("development" !== 'production') {
72       "development" !== '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;
73       "development" !== 'production' ? warning(!Constructor.prototype.componentWillRecieveProps, '%s has a method called ' + 'componentWillRecieveProps(). Did you mean componentWillReceiveProps()?', spec.displayName || 'A component') : void 0;
74     }
75 
76     // 這是一個優化,后面詳細說明
77     for (var methodName in ReactClassInterface) {
78       if (!Constructor.prototype[methodName]) {
79         Constructor.prototype[methodName] = null;
80       }
81     }
82 
83     return Constructor;
84   },
85 
86   injection: {
87     injectMixin: function (mixin) {
88       injectedMixins.push(mixin);
89     }
90   }
91 
92 };

 

首先可以看到,createClass 實際上返回的是一個 Constructor 構造函數,該構造函數擁有5個實例屬性,分別是:props,context,refs,updater和state。props 即為通常我們用於組件之間通信的 props,存儲組件的一些相關屬性。它實際上由3個部分組成:

調用 createElement 時傳入的config,children 和 createClass 中調用 getDefaultProps 方法獲得的 defaultProps。舉個例子,如下列代碼: 

var Root = React.createClass({
    getDefaultProps: function() {
        return {
            name: 'Ray'
        }
    },

    render: function() {
        let me = this;
        console.log(me.props);
        return (
            <div>
                <h1>Hello, {me.props.name}, {me.props.age} years old</h1>
                {this.props.children}
            </div>
        ) 
    }
})

var ele = React.createElement(Root, {age: '18'}, "i'm a child");

ReactDOM.render(
    ele,
    document.getElementById('dom')
);

打印出props,如下圖:

可以看到 props 屬性包含了在 createElement 中傳入的config和children參數,還有在 getDefaultProps 中定義的默認值,這也就是為什么我們可以在組件用 {this.props.children} 這種方式獲取到children。

關於 createElement 的具體實現會在之后相關文章中分析。

第二個實例屬性 context 即保存組件掛載或更新時的上下文環境,react會為你處理好一切,這里就不展開了。

然后是refs,保存着對組件render方法內的dom節點的引用。如果是自定義的元素節點,即我們創建的組件,則會保存着一個我們在掛載組件時用到的 ReactCompositeComponent 的實例。如果是基本元素類型,如div,p等,則直接返回其真實dom。注意,該屬性不能再render中使用,因為此時組件還未掛載。

updater屬性保存着組件的更新隊列,在組件更新時會用到。

state屬性即保存組件的狀態。

在定義了Constructor構造函數后(注意:此時還未執行該函數,僅僅是定義,生成實例是在掛載組件的時候進行的),見41,42行,運用了簡單的原型鏈繼承,是 Costructor 對象繼承了 ReactClassComponent,我們來看看ReactClassComponent具體有哪些屬性和方法。

1 var ReactClassComponent = function () {};
2 _assign(ReactClassComponent.prototype, ReactComponent.prototype, ReactClassMixin);

 

這段代碼將 ReactComponent.prototype 和 ReactClassMixin 混入到ReactClassComponent.prototype上,再看看這兩者代碼。

function ReactComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  this.updater = updater || ReactNoopUpdateQueue;
}

ReactComponent.prototype.isReactComponent = {};

ReactComponent.prototype.setState = function (partialState, callback) {
  !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? "development" !== 'production' ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : _prodInvariant('85') : void 0;
  this.updater.enqueueSetState(this, partialState);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'setState');
  }
};

ReactComponent.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this);
  if (callback) {
    this.updater.enqueueCallback(this, callback, 'forceUpdate');
  }
};

var ReactClassMixin = {

  replaceState: function (newState, callback) {
    this.updater.enqueueReplaceState(this, newState);
    if (callback) {
      this.updater.enqueueCallback(this, callback, 'replaceState');
    }
  },

  isMounted: function () {
    return this.updater.isMounted(this);
  }
};

 

因此通過 Constructor.prototype 一共可以訪問到5個方法,分別為:isReactComponent,setState,forceUpdate,replaceState和isMounted。

然后在48行有 injectedMixins.forEach(mixSpecIntoComponent.bind(null, Constructor)) 這樣一段代碼,在第86行可以看到,ReactClass 還有一個方法 injection,這個方法會把要混入的mixin添加到 injectedMixins 數組中。因此這里的 forEach 也就是要把 injectedMixins中所有的mixin混入到Constructor中去,這和 createClass的功能有些重復,並且 injection 方法並沒有暴露到 React 中去,因此我也不太明白這個方法存在的意義。

接下來在51行執行 mixSpecIntoComponent(Constructor, spec) ,mixSpecIntoComponent 就是把我們傳入的spec中的方法或屬性混入到 Constructor,我們來看看這個方法。

 

 1 function mixSpecIntoComponent(Constructor, spec) {
 2   // 當傳入的spec為null或者undefined時顯示warning信息
 3   if (!spec) {
 4     if ("development" !== 'production') {
 5       var typeofSpec = typeof spec;
 6       var isMixinValid = typeofSpec === 'object' && spec !== null;
 7 
 8       "development" !== 'production' ? warning(isMixinValid, '%s: You\'re attempting to include a mixin that is either null ' + 'or not an object. Check the mixins included by the component, ' + 'as well as any mixins they include themselves. ' + 'Expected object but got %s.', Constructor.displayName || 'ReactClass', spec === null ? null : typeofSpec) : void 0;
 9     }
10 
11     return;
12   }
13 
14   // 當傳入的spec為function類型或者是ReactElement對象,拋出異常
15   !(typeof spec !== 'function') ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to use a component class or function as a mixin. Instead, just use a regular object.') : _prodInvariant('75') : void 0;
16   !!ReactElement.isValidElement(spec) ? "development" !== 'production' ? invariant(false, 'ReactClass: You\'re attempting to use a component as a mixin. Instead, just use a regular object.') : _prodInvariant('76') : void 0;
17 
18   var proto = Constructor.prototype;
19   // __reactAutoBindPairs為一個數組的引用,見createClass方法,對autoBindPairs的修改會影響到__reactAutoBindPairs所引用的數組
20   var autoBindPairs = proto.__reactAutoBindPairs;
21 
22   // 先處理spec中的mixins字段,MIXINS_KEY即為'mixins'
23   if (spec.hasOwnProperty(MIXINS_KEY)) {
24     RESERVED_SPEC_KEYS.mixins(Constructor, spec.mixins);
25   }
26 
27   for (var name in spec) {
28     if (!spec.hasOwnProperty(name)) {
29       continue;
30     }
31 
32     if (name === MIXINS_KEY) {
33       // mixins字段前面已經處理過了
34       continue;
35     }
36 
37     var property = spec[name];
38     var isAlreadyDefined = proto.hasOwnProperty(name);
39     validateMethodOverride(isAlreadyDefined, name);
40 
41     //若是RESERVED_SPEC_KEYS中定義過的屬性,則按照RESERVED_SPEC_KEYS中定義方法執行相應操作
42     if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
43       RESERVED_SPEC_KEYS[name](Constructor, property);
44     } else {
45       // 滿足以下條件的方法不應該被auto bind:
46       // 1. 是ReactClassInterface中定義的方法.
47       // 2. Constructor.prototype上已存在的方法.
48       var isReactClassMethod = ReactClassInterface.hasOwnProperty(name);
49       var isFunction = typeof property === 'function';
50       var shouldAutoBind = isFunction && !isReactClassMethod && !isAlreadyDefined && spec.autobind !== false;
51 
52       if (shouldAutoBind) {
53         // 將應該自動綁定的方法添加到autoBindPairs所引用的數組中
54         autoBindPairs.push(name, property);
55         proto[name] = property;
56       } else {
57         if (isAlreadyDefined) {
58           var specPolicy = ReactClassInterface[name];
59 
60           // 這部分操作已經在 validateMethodOverride 執行過了
61           !(isReactClassMethod && (specPolicy === 'DEFINE_MANY_MERGED' || specPolicy === 'DEFINE_MANY')) ? "development" !== 'production' ? invariant(false, 'ReactClass: Unexpected spec policy %s for key %s when mixing in component specs.', specPolicy, name) : _prodInvariant('77', specPolicy, name) : void 0;
62 
63           // 對於擁有 DEFINE_MANY_MERGED 或者 DEFINE_MANY標識的方法,當定義多次時,分別采取不同的處理
64           if (specPolicy === 'DEFINE_MANY_MERGED') {
65             proto[name] = createMergedResultFunction(proto[name], property);
66           } else if (specPolicy === 'DEFINE_MANY') {
67             proto[name] = createChainedFunction(proto[name], property);
68           }
69         } else {
70           proto[name] = property;
71           if ("development" !== 'production') {
72             // 給function類型的屬性添加displayName屬性,相當添加了個標識符,給使用分析工具提供了幫助
73             if (typeof property === 'function' && spec.displayName) {
74               proto[name].displayName = spec.displayName + '_' + name;
75             }
76           }
77         }
78       }
79     }
80   }
81 }

 

直接從20行開始說,這里創建了一個 autoBindPairs 變量,將 proto.__reactAutoBindPairs 賦給了它。而 proto.__reactAutoBindPairs 是一個對數組的引用,因此在該段代碼54行執行 autoBindPairs.push(name, property) 時,實際上也影響到了 proto.__reactAutoBindPairs,因為他倆指向同一個數組,對其中任何一個的操作將會影響到另一個。

接下來在22行處理spec的注入時,首先會去處理mixins字段,無論它是否是第一個定義的,此時會去調用 RESERVED_SPEC_KEYS 中對應的方法。RESERVED_SPEC_KEYS 顧名思義,就是列出spec的一些保留的關鍵字,做特殊的處理。例如 getDefaultProps,propTypes等,這個對象中定義的屬性是會被添加到Constructor上,而不是Constructor.prototype上,即作為Constructor的靜態屬性存在。這里在處理mixins時,實際上就是遍歷mixins數組,然后對每一項執行 mixSpecIntoComponent 方法,這是一個遞歸的過程。這里就有一個問題,假如mixins和spec包含同名屬性,該如何處理?不着急,繼續往下看。

處理了mixins之后,就會去遍歷spec對象,去處理其它的字段。在處理之前,首先會去調用 validateMethodOverride 方法,這個方法就是用來處理當混入了兩個同名屬性的情況,我們來看一下這段代碼。

 1 function validateMethodOverride(isAlreadyDefined, name) {
 2   var specPolicy = ReactClassInterface.hasOwnProperty(name) ? ReactClassInterface[name] : null;
 3 
 4   // Disallow overriding of base class methods unless explicitly allowed.
 5   if (ReactClassMixin.hasOwnProperty(name)) {
 6     !(specPolicy === 'OVERRIDE_BASE') ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.', name) : _prodInvariant('73', name) : void 0;
 7   }
 8 
 9   // Disallow defining methods more than once unless explicitly allowed.
10   if (isAlreadyDefined) {
11     !(specPolicy === 'DEFINE_MANY' || specPolicy === 'DEFINE_MANY_MERGED') ? "development" !== 'production' ? invariant(false, 'ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.', name) : _prodInvariant('74', name) : void 0;
12   }
13 }

 

ReactClassInterface可以理解為一個對象,對象中包含的key為屬性或者方法名,value為對該屬性或方法的描述,代碼如下(為了簡潔,刪除了注釋)。

 1 var ReactClassInterface = {
 2 
 3   mixins: 'DEFINE_MANY',
 4 
 5   statics: 'DEFINE_MANY',
 6 
 7   propTypes: 'DEFINE_MANY',
 8 
 9   contextTypes: 'DEFINE_MANY',
10 
11   childContextTypes: 'DEFINE_MANY',
12 
13   getDefaultProps: 'DEFINE_MANY_MERGED',
14 
15   getInitialState: 'DEFINE_MANY_MERGED',
16 
17   getChildContext: 'DEFINE_MANY_MERGED',
18 
19   render: 'DEFINE_ONCE',
20 
21   componentWillMount: 'DEFINE_MANY',
22 
23   componentDidMount: 'DEFINE_MANY',
24 
25   componentWillReceiveProps: 'DEFINE_MANY',
26 
27   shouldComponentUpdate: 'DEFINE_ONCE',
28 
29   componentWillUpdate: 'DEFINE_MANY',
30 
31   componentDidUpdate: 'DEFINE_MANY',
32 
33   componentWillUnmount: 'DEFINE_MANY',
34   
35   updateComponent: 'OVERRIDE_BASE'
36 
37 };

例如componentWillMount: 'DEFINE_MANY', 就是規定 componentWillMount 可以定義多次。

回到 validateMethodOverride 方法,首先 ReactClassMixin 對象前面說過,包含replaceState和isMounted方法,而描述為 OVERRIDE_BASE 的只有 updateComponent 方法,所以第一個if語句規定不能重寫 ReactClassMixin 中的方法,否則拋出異常。isAlreadyDefined 為在Constructor.prototype中已存在的方法,如果一個方法已存在且不為 DEFINE_MANY 或者 DEFINE_MANY_MERGED,則拋出異常。如果是這兩個其中一個的話,來看 mixSpecIntoComponent 的64-68行, 如果為 DEFINE_MANY_MERGED,會去調用 createMergedResultFunction 方法;如果為 DEFINE_MANY 會去調用 createChainedFunction 方法。我們來看看這兩個函數的代碼。

 1 function mergeIntoWithNoDuplicateKeys(one, two) {
 2   !(one && two && typeof one === 'object' && typeof two === 'object') ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.') : _prodInvariant('80') : void 0;
 3 
 4   for (var key in two) {
 5     if (two.hasOwnProperty(key)) {
 6       !(one[key] === undefined) ? "development" !== 'production' ? invariant(false, 'mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the same key: `%s`. This conflict may be due to a mixin; in particular, this may be caused by two getInitialState() or getDefaultProps() methods returning objects with clashing keys.', key) : _prodInvariant('81', key) : void 0;
 7       one[key] = two[key];
 8     }
 9   }
10   return one;
11 }
12 
13 function createMergedResultFunction(one, two) {
14   return function mergedResult() {
15     var a = one.apply(this, arguments);
16     var b = two.apply(this, arguments);
17     if (a == null) {
18       return b;
19     } else if (b == null) {
20       return a;
21     }
22     var c = {};
23     mergeIntoWithNoDuplicateKeys(c, a);
24     mergeIntoWithNoDuplicateKeys(c, b);
25     return c;
26   };
27 }
28 
29 function createChainedFunction(one, two) {
30   return function chainedFunction() {
31     one.apply(this, arguments);
32     two.apply(this, arguments);
33   };
34 }

簡單來說,這兩個方法都返回了一個函數,返回的函數主要的作用是按順序調用兩個同名方法。不過在處理 DEFINE_MANY_MERGED 的時候,需要將執行后返回的對象合並,此時對象中若存在同名的屬性,則會拋出異常。通過以上代碼的第6行,我們可以看到這種情況通常是在mixins 和 spec中混入了 getInitialState 或者 getDefaultProps,並且返回的對象中包含同名屬性造成的。因此可以得出一個結論,當我們在mixins或者spec中混入了多個 componentDidMount 方法,結果將會按順序執行;而混入多個自定義的方法,因為缺少了 DEFINE_MANY 的描述,將會拋出異常。

最后幾行是給某些屬性或方法添加displayName字段便於區分,經過那么多if else條件語句篩選,這里所說的某些屬性或方法基本就是指在 ReactClassInterface 中定義的但不存在於 RESERVED_SPEC_KEYS 中的屬性或方法。關於displayName,如果在構建環境時用了webpack並且引入babel,那么其中的 babel-preset-react 會去引入一個叫 babel-plugin-transform-react-display-name 的模塊,這個模塊會自動的為你的組件混入一個displayName屬性,值為你定義的變量的值。如 var foo = React.createClass({}),那么添加的displayName就是foo。

至此,mixSpecIntoComponent 的過程大致的都描述完了,我們可以來理一理這里的處理順序:

  1. 首先處理mixins,如果存在mixins,則遞歸調用 mixSpecIntoComponent 方法。
  2. 遍歷要混入的spec,首先判斷是否是在 RESERVED_SPEC_KEYS 中定義的屬性,如果是,則調用 RESERVED_SPEC_KEYS 中的相應方法處理,否則步驟3。
  3. 如果混入的spec未在 RESERVED_SPEC_KEYS 中定義,則判斷它是否該被自動綁定,判斷條件是:是function類型,未在 ReactClassInterface 中定義,已經存在於prototype中並且spec.autobind!==false。滿足這個條件的基本上也就是我們自定義的一些方法了。如果滿足條件,則將其添加到綁定的隊列,並在prototype上添加,若不滿足條件,則進入步驟4。
  4. 若不滿足自動綁定條件,那么先判斷是否已經存在於prototype中。若是,那么就要根據相應的規則,諸如 DEFINE_MANY 等描述來做相應的操作。我們自定義的方法是不能被多次定義的,只有一些生命周期方法可以,具體看 ReactClassInterface 的定義。如果還未存在於prototype中,見步驟5。
  5. 此時,有兩種情況,可能是自定義的屬性(不是function類型)或者是 ReactClassInterface 中定義的屬性或方法。若是自定義屬性,則將其添加到prototype上;若是 ReactClassInterface 中定義的屬性或方法,則根據相應的判斷來決定是否給該屬性和方法添加 displayName 來便於區分。

接着回到 createClass 方法,繼續往下看。

執行完 mixSpecIntoComponent 之后,54行執行 getDefaultProps 並把結果賦給了 Constructor 的一個defaultProps 靜態屬性。由此可知,getDefaultProps 會先於 getInitialState 執行,后者是在31行創建Constructor實例時才被調用。因此,不要在 getDefaultProps 中使用 this.state 屬性,因為此時 state 還未被賦值。

最后我們來看77行,給 Constructor.prototype 的某些屬性賦了一個null,這些屬性是在 ReactClassInterface 上定義但未在 Constructor.prototype 上定義的。這么做的目的根據我個人的理解,是因為在首次掛在組件的時候,會先去判斷 Constructor.prototype.componentWillMount 是否存在,若存在則調用。如果該屬性在 Constructor.prototype 上未定義,那么我們必須遍歷完整個prototype對象才可以得出結論;然而在prototype上賦了一個null,可以有效地防止不必要的遍歷。這同樣適用於組件更新時執行componentWillUpdate等方法,且效果更為明顯,因為更新組件的操作在React中是相當頻繁的。

createClass的過程到此就執行完了,我們再來回頭看看Constructor構造函數,它在創建實例對象時還會去做一件事,bindAutoBindMethods,見21行。這個方法主要是使用bind方法綁定作用域到Constructor實例上的,因此在使用createClass時不必擔心方法作用域的問題,而使用es6創建組件時,必須自己去bind作用域。那么 this.__reactAutoBindPairs 里面的值是哪里來的呢?不錯,就是在執行 mixSpecIntoComponent 這個方法時,通過對 autoBindPairs 變量進行操作,從而影響了 __reactAutoBindPairs。

對比es6 extends寫法

說了一堆,其實 createClass 主要就做了這么幾件事。

  1. 生成並返回一個Constructor構造函數,該函數有props,context,refs,updater和state5個屬性。
  2. 綁定了作用域。
  3. 將屬性或方法添加到 Constructor.prototype 上。
  4. 調用 getDefaultProps 初始化 Constructor.defaultProps 屬性。

以上3點通過 class xxx extends React.Component 寫法都能辦到。React.Component 即為 ReactComponent 對象,上面已經放過代碼了,它正好是含有props,context,refs,updater這4個屬性的構造函數,並且擁有 setState 和 forceUpdate 方法供我們調用,因此我們在創建組件時(stateless component除外),只需在constructor添加 this.state 用來初始化state屬性即可保持一致。第2點需要注意,es6的寫法並沒有為你綁定作用域,需要自己手動在constructor中綁定,或者使用箭頭函數的特性。第3點,在class中定義的方法都會被添加到prototype上,也可以滿足條件。至於第4點,若需要定義defalutProps,只需手動添加,如:XXX.defalutProps 定義即可。唯一缺少的就是對mixins的支持。其實mixins已經在官方文檔上被定義為一個大坑,主要在於mixins 與 mixins,mixins與組件之間存在着千絲萬縷的依賴關系,代碼量一增加,就變得十分難維護。還有一點,正如上面分析,mixins會造成命名的沖突,當我們定義兩個同名的自定義方法時會拋異常。官方已經給出了一個稱之為‘高階組件’的解決方案來替代mixins了。具體的文檔請參見https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html。既然mixins不建議再使用了,那么 createClass 中 mixSpecIntoComponent 這個方法所做的很多事情都顯得多余了,因此 es6 extends的寫法以其代碼的簡潔性取代createClass應該是理所當然的事。

結語

第一次寫博客,不知道是否已經把該講的東西都講清楚了,希望看過的小伙伴都能給點意見,或者是博主有什么理解誤區的,都歡迎指出。接下來還會陸續對ReactElement,組件的mount和update等進行詳細的說明,感謝各位賞臉閱讀。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM