原文連結地址:github.com/Nealyang 轉載請註明出處
前言
戰戰兢兢寫下開篇...也感謝小蘑菇大神以及網上各路大神的部落格資料參考~
閱讀原始碼的方式有很多種,廣度優先法、呼叫棧除錯法等等,此係列文章,採用基線法,顧名思義,就是以低版本為基線,逐漸瞭解原始碼的演進過程和思路。
react最初的設計靈感來源於遊戲渲染的機制:當資料變化時,介面僅僅更新變化的部分而形成新的一幀渲染。所以設計react的核心就是認為UI只是把資料通過對映關係變換成另一種形式的資料,也就是展示方式。傳統上,web架構使用模板或者HTML指令構造頁面。react則處理構建使用者介面通過將他們份極為virtual dom,當然這也是react的核心,整個react架構的設計理念也是為此展開的。
準備工作
我們採用基線法去學習react原始碼,所以目前基於的版本為stable-0.3,後面我們在逐步分析學習演變的版本。
clone程式碼
git clone https://github.com/facebook/react.git
git checkout 0.3-stable
複製程式碼
React原始碼都在src目錄中,src包含了8個目錄,其主要內容描述見下表。
目 錄 | 內容 |
---|---|
core | React 核心類 |
domUtil | Dom操作和CSS操作的相關工具類 |
environment | 當前JS執行環境的基本資訊 |
event | React事件機制的核心類 |
eventPlugins | React事件機制的事件繫結外掛類 |
test | 測試目錄 |
utils | 各種工具類 |
vendor | 可替換模組存放目錄 |
我們將該版本編譯後的程式碼放到example下,引入到basic/index.html中執行除錯。
元件初始化
使用
這裡還是以basic.html中的程式碼為例
<script>
var ExampleApplication = React.createClass({
render: function() {
var elapsed = Math.round(this.props.elapsed / 100);
var seconds = elapsed / 10 + (elapsed % 10 ? '' : '.0' );
var message =
'React has been successfully running for ' + seconds + ' seconds.';
return React.DOM.p(null, message);
}
});
var start = new Date().getTime();
setInterval(function() {
React.renderComponent(
ExampleApplication({elapsed: new Date().getTime() - start}),
document.getElementById('container')
);
}, 50);
</script>
複製程式碼
回到我們說的元件初始化,抽離下上面的程式碼就是:
var ExampleApplication = React.createClass({render:function(){ return <div>Nealyang</div> }})
複製程式碼
熟悉react使用的人都知道,render方法不能為空,當然,createClass中我們也可以去寫一寫生命週期的鉤子函式,這裡我們暫且省略,畢竟目前我們更加的關注react組建的初始化過程。
同樣,熟悉react使用方法的人也會有疑惑了,怎麼例項程式碼中的render最後return的是React.DOM.p(null,message)
所以到這裡,就不得不說一下react的編譯階段了
編譯階段
我們都知道,在js中直接編寫html程式碼,或者。。。jsx語法這樣的AST,在js詞法分析階段就會丟擲異常的。
對的,所以我們在編寫react程式碼的時候都會藉助babel去轉碼
從babel官網上寫個例子即可看出:
對呀!明明人家用的是react.createElement方法,我們怎麼出現個React.DOM.p...
OK,歷史原因:
- react現在版本中,使用babel-preset-react來編譯jsx,這個preset又包含了4個外掛,其中transform-react-jsx負責編譯jsx,呼叫了React.createElement函式生成虛擬元件
- 在react-0.3裡,編譯結果稍稍有些不同,官方給出的示例檔案,使用JSXTransformer.js編譯jsx(也就是
<script src="../source/JSXTransformer.js"></script>
),對於native元件和composite元件編譯的方式也不一致。也就是我們看到的React.DOM.p or ReactComponsiteComponent- native元件:編譯成React.DOM.xxx(xxx如div),函式執行返回一個ReactNativeComponent例項。
- composite元件:編譯成createClass返回的函式呼叫,函式執行返回一個ReactCompositeComponent例項
題外話,不管用什麼框架,到瀏覽器這部分的,什麼花裡胡哨的都不復存在。我這就是js、css、html。所以我們這裡的ReactCompositeComponent最終其實還是需要轉成原生元素的 。\
元件建立
從React.js中我們可以找到createClass的出處:
"use strict";
var ReactCompositeComponent = require('ReactCompositeComponent');
...
var React = {
...
createClass: ReactCompositeComponent.createClass,
...
};
module.exports = React;
複製程式碼
- createClass 程式碼
var ReactCompositeComponentBase = function() {};
function mixSpecIntoComponent(Constructor, spec) {
var proto = Constructor.prototype;
for (var name in spec) {
if (!spec.hasOwnProperty(name)) {
continue;
}
var property = spec[name];
var specPolicy = ReactCompositeComponentInterface[name];
if (RESERVED_SPEC_KEYS.hasOwnProperty(name)) {
RESERVED_SPEC_KEYS[name](Constructor, property);
} else if (property && property.__reactAutoBind) {
if (!proto.__reactAutoBindMap) {
proto.__reactAutoBindMap = {};
}
proto.__reactAutoBindMap[name] = property.__reactAutoBind;
} else if (proto.hasOwnProperty(name)) {
// For methods which are defined more than once, call the existing methods
// before calling the new property.
proto[name] = createChainedFunction(proto[name], property);
} else {
proto[name] = property;
}
}
}
createClass: function (spec) {
var Constructor = function (initialProps, children) {
this.construct(initialProps, children);
};
// ReactCompositeComponentBase是React複合元件的原型函式
Constructor.prototype = new ReactCompositeComponentBase();
Constructor.prototype.constructor = Constructor;
// 把消費者宣告配置spec合併到Constructor.prototype中
mixSpecIntoComponent(Constructor, spec);
// 判斷合併後的結果有沒有render,如果沒有 render,丟擲一個異常
invariant(
Constructor.prototype.render,
'createClass(...): Class specification must implement a `render` method.'
);
//工廠
var ConvenienceConstructor = function (props, children) {
return new Constructor(props, children);
};
ConvenienceConstructor.componentConstructor = Constructor;
ConvenienceConstructor.originalSpec = spec;
return ConvenienceConstructor;
},
複製程式碼
- mixSpecIntoComponent 方法就是講spec的屬性賦值給Constructor的原型上
- createClass返回一個ConvenienceConstructor建構函式,建構函式接受props、children 建構函式的靜態方法componentConstructor和originalSpec分別指向Constructor和spec。
- 有種類似於寄生組合式繼承的寫法,Constructor為每一個元件例項的原型(
var instance = new Constructor(); instance.construct.apply(instance, arguments);
)。Constructor原型指向ReactCompositeComponentBase,又把構造器指向Constructor自己。然後把傳入的spec合併到Constructor.prototype中。判斷合併後的結果有沒有render,如果沒有 render,丟擲一個異常
其實很多人看到這估計都會很疑惑,為毛這樣搞???直接返回個建構函式不就可以了嘛。
其實react在後面做diff演算法的時候,是採用元件的Constructor來判斷元件是否相同的。如此可以保證每個createClass建立出來的元件都是一個新的Constructor。
ok,那麼我直接用寄生繼承呀
// 寫法1
const createClass = function(spec) {
var Constructor = function (initialProps, children) {
this.construct(initialProps, children);
};
Constructor.prototype = new ReactCompositeComponentBase();
Constructor.prototype.constructor = Constructor;
mixSpecIntoComponent(ReactCompositeComponentBase, spec)
return Constructor
}
const Table1 = new createClass(spec)(props, children);
//console.log(Table1.constructor)
複製程式碼
為什麼還需要ConvenienceConstructor呢?說實話,我也不知道,然後看了在網上查到相關資訊說道:
上面寫法在大多數情況下並不會產生什麼問題,但是,當團隊裡的人無意中修改錯點什麼,比如:
Table1.prototype.onClick = null
複製程式碼
這樣,所有Table1例項化的元件,onClick全部為修改後的空值
<Table1 />
<Table1 />
複製程式碼
我們知道,js是動態解釋型語言,函式可以執行時被隨意篡改。而靜態編譯語言在執行時期間,函式不可修改(某些靜態語言也可以修改)。所以採用這種方式防禦使用者對程式碼的篡改。
元件例項化
既然createClass返回的是一個建構函式,那麼我們就來看看他的例項化吧
/**
* Base constructor for all React component.
*
* Subclasses that override this method should make sure to invoke
* `ReactComponent.Mixin.construct.call(this, ...)`.
*
* @param {?object} initialProps
* @param {*} children
* @internal
*/
construct: function (initialProps, children) {
this.props = initialProps || {};
if (typeof children !== 'undefined') {
this.props.children = children;
}
// Record the component responsible for creating this component.
this.props[OWNER] = ReactCurrentOwner.current;
// All components start unmounted.
this._lifeCycleState = ComponentLifeCycle.UNMOUNTED;
},
複製程式碼
其實也就是將props、children掛載到this.props上 以及生命週期的設定。這裡暫且不說,因為我也正在看。。。哇咔咔
這裡的
this.props[OWNER] = ReactCurrentOwner.current;
複製程式碼
this.props[OWNER]指的是當前元件的容器(父)元件例項
如果我們直接在basic.html中列印就直接出來的是null,但是如果像如下的方式書寫:
const Children = React.createClass({
componentDidMount = () => console.log(this.props["{owner}"]),
render = () => null
})
const Parent = React.createClass({
render: () => <Children />
})
複製程式碼
這裡輸出的就是Parent元件例項。
再看看ReactCurrentOwner.current的賦值就明白了
_renderValidatedComponent: function () {
ReactCurrentOwner.current = this;
var renderedComponent = this.render();
ReactCurrentOwner.current = null;
invariant(
ReactComponent.isValidComponent(renderedComponent),
'%s.render(): A valid ReactComponent must be returned.',
this.constructor.displayName || 'ReactCompositeComponent'
);
return renderedComponent;
}
複製程式碼
可以看出來,在執行render前後,分別設定了ReactCurrentOwner.current的值,這樣就能保證render函式內的子元件能賦上當前元件的例項,也就是this。
元件渲染
我們先撇開事務、事件池、生命週期、diff當然也包括fiber 等,先不談,其實渲染就是將經過babel編譯後的,當然這裡是JSXTransformer.js編譯後的Ojb給寫入到HTML中而已。
export default function render(vnode, parent) {
let dom;
if (typeof vnode === 'string') {
dom = document.createTextNode(vnode);
// let span_dom = document.createElement('span')
// span_dom.appendChild(dom);
// parent.appendChild(span_dom);
parent.appendChild(dom);
} else if (typeof vnode.nodeName === 'string') {
dom = document.createElement(vnode.nodeName);
setAttrs(dom, vnode.props);
parent.appendChild(dom)
for(let i = 0; i < vnode.children.length; i++) {
render(vnode.children[i], dom)
}
}else if(typeof vnode.nodeName === 'function'){
let innerVnode = vnode.nodeName.prototype.render();
render(innerVnode,parent)
}
}
function setAttrs(dom, props) {
const ALL_KEYS = Object.keys(props);
ALL_KEYS.forEach(k =>{
const v = props[k];
// className
if(k === 'className'){
dom.setAttribute('class',v);
return;
}
if(k == "style") {
if(typeof v == "string") {
dom.style.cssText = v
}
if(typeof v == "object") {
for (let i in v) {
dom.style[i] = v[i]
}
}
return
}
if(k[0] == "o" && k[1] == "n") {
const capture = (k.indexOf("Capture") != -1)
dom.addEventListener(k.substring(2).toLowerCase(),v,capture)
return
}
dom.setAttribute(k, v)
})
}
複製程式碼
是的,就這樣
OK,回到原始碼~
在我們目前使用的react版本中,渲染呼叫的是ReactDOM.render方法,這裡ReactMount.renderComponent為我們的入口方法。
ReactMount.renderComponent在react初探章節講過。如果元件渲染過,就更新元件屬性,如果元件沒有渲染過,掛載元件事件,並把虛擬元件渲染成真實元件插入container內。通常,我們很少去呼叫兩次renderComponent,所以大多數情況下不會更新元件屬性而是新建立dom節點並插入到container中。
ReactComponent.mountComponentIntoNode之內開啟了一個事務,事務保證渲染階段不會有任何事件觸發,並阻斷的componentDidMount事件,待執行後執行等,事務在功能一章我們會詳細講解,這裡不細討論。 ReactComponent._mountComponentIntoNode這個函式呼叫mountComponent獲得要渲染的innerHTML,然後更新container的innerHTML。 ReactCompositeComponent.mountComponent是最主要的邏輯方法。這個函式內處理了react的生命週期以及componentWillComponent和componentDidMount生命週期鉤子函式,呼叫render返回實際要渲染的內容,如果內容是複合元件,仍然會呼叫mountComponent,複合元件最終一定會返回原生元件, 並且最終呼叫ReactNativeComponent的mountComponent函式生成要渲染的innerHTML。
renderComponent: function(nextComponent, container) {
var prevComponent = instanceByReactRootID[getReactRootID(container)];
if (prevComponent) {
var nextProps = nextComponent.props;
ReactMount.scrollMonitor(container, function() {
prevComponent.replaceProps(nextProps);
});
return prevComponent;
}
ReactMount.prepareTopLevelEvents(ReactEventTopLevelCallback);
var reactRootID = ReactMount.registerContainer(container);
instanceByReactRootID[reactRootID] = nextComponent;
nextComponent.mountComponentIntoNode(reactRootID, container);
return nextComponent;
},
複製程式碼
這段程式碼邏輯大概就是上面的流程圖,這裡不再贅述。
- mountComponentIntoNode 從debugger中,可以看出mountComponentIntoNode第一個引數其實傳入的是react分配給元件的一個唯一標識
mountComponentIntoNode: function(rootID, container) {
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._mountComponentIntoNode,
this,
rootID,
container,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
},
複製程式碼
原始碼中,這裡跟事務扯到了關係,其實我們只要關注渲染本身,所以這裡我們直接看this._mountComponentIntoNode的方法實現
- _mountComponentIntoNode
_mountComponentIntoNode: function(rootID, container, transaction) {
var renderStart = Date.now();
var markup = this.mountComponent(rootID, transaction);
ReactMount.totalInstantiationTime += (Date.now() - renderStart);
var injectionStart = Date.now();
// Asynchronously inject markup by ensuring that the container is not in
// the document when settings its `innerHTML`.
var parent = container.parentNode;
if (parent) {
var next = container.nextSibling;
parent.removeChild(container);
container.innerHTML = markup;
if (next) {
parent.insertBefore(container, next);
} else {
parent.appendChild(container);
}
} else {
container.innerHTML = markup;
}
ReactMount.totalInjectionTime += (Date.now() - injectionStart);
},
複製程式碼
上述程式碼流程大概如下:
流程的確如上,作為一個初探原始碼者,我當然不關心你到底是在哪innerHTML的,我想知道你是腫麼把jsx編譯後的Obj轉成HTML的哇~
- ReactCompositeComponent.mountComponent
這裡類變成了ReactCompositeComponent(debugger可以跟蹤每一個函式)
原始碼中的this.mountComponent,為什麼不是呼叫ReactComponent.mountComponent呢?這裡主要使用了多重繼承機制(Mixin,後續講解)。
mountComponent: function(rootID, transaction) {
// 掛在元件ref(等於當前元件例項)到this.refs上
ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
// Unset `this._lifeCycleState` until after this method is finished.
// 這是生命週期
this._lifeCycleState = ReactComponent.LifeCycle.UNMOUNTED;
this._compositeLifeCycleState = CompositeLifeCycle.MOUNTING;
// 元件宣告有props,執行校驗
if (this.constructor.propDeclarations) {
this._assertValidProps(this.props);
}
// 為元件宣告時間繫結this
if (this.__reactAutoBindMap) {
this._bindAutoBindMethods();
}
//獲取state
this.state = this.getInitialState ? this.getInitialState() : null;
this._pendingState = null;
// 如果元件宣告componentWillMount函式,執行並把setState的結果更新到this.state上
if (this.componentWillMount) {
this.componentWillMount();
// When mounting, calls to `setState` by `componentWillMount` will set
// `this._pendingState` without triggering a re-render.
if (this._pendingState) {
this.state = this._pendingState;
this._pendingState = null;
}
}
// 如果宣告瞭componentDidMount,則把其加入到ReactOnDOMReady佇列中
if (this.componentDidMount) {
transaction.getReactOnDOMReady().enqueue(this, this.componentDidMount);
}
// 呼叫元件宣告的render函式,並返回ReactComponent抽象類例項(ReactComponsiteComponent或
// ReactNativeComponent),呼叫相應的mountComponent函式
this._renderedComponent = this._renderValidatedComponent();
// Done with mounting, `setState` will now trigger UI changes.
this._compositeLifeCycleState = null;
this._lifeCycleState = ReactComponent.LifeCycle.MOUNTED;
return this._renderedComponent.mountComponent(rootID, transaction);
},
複製程式碼
這個函式式VDom中最為重要的函式,操作也最為複雜,執行操作大概如下:
如上,很多內容跟我們這part有點超綱。當然,後面都會說道,關於react的渲染,其實我們的工作很簡單,不關於任何,在拿到render的東西后,如何解析,其實就是最後一行程式碼:this._renderedComponent.mountComponent(rootID, transaction);
mountComponent: function(rootID, transaction) {
ReactComponent.Mixin.mountComponent.call(this, rootID, transaction);
assertValidProps(this.props);
return (
this._createOpenTagMarkup() +
this._createContentMarkup(transaction) +
this._tagClose
);
},
_createOpenTagMarkup: function() {
var props = this.props;
var ret = this._tagOpen;
for (var propKey in props) {
if (!props.hasOwnProperty(propKey)) {
continue;
}
var propValue = props[propKey];
if (propValue == null) {
continue;
}
if (registrationNames[propKey]) {
putListener(this._rootNodeID, propKey, propValue);
} else {
if (propKey === STYLE) {
if (propValue) {
propValue = props.style = merge(props.style);
}
propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
}
var markup =
DOMPropertyOperations.createMarkupForProperty(propKey, propValue);
if (markup) {
ret += ' ' + markup;
}
}
}
return ret + ' id="' + this._rootNodeID + '">';
},
/**
* Creates markup for the content between the tags.
*
* @private
* @param {ReactReconcileTransaction} transaction
* @return {string} Content markup.
*/
_createContentMarkup: function(transaction) {
// Intentional use of != to avoid catching zero/false.
var innerHTML = this.props.dangerouslySetInnerHTML;
if (innerHTML != null) {
if (innerHTML.__html != null) {
return innerHTML.__html;
}
} else {
var contentToUse = this.props.content != null ? this.props.content :
CONTENT_TYPES[typeof this.props.children] ? this.props.children : null;
var childrenToUse = contentToUse != null ? null : this.props.children;
if (contentToUse != null) {
return escapeTextForBrowser(contentToUse);
} else if (childrenToUse != null) {
return this.mountMultiChild(
flattenChildren(childrenToUse),
transaction
);
}
}
return '';
},
function ReactNativeComponent(tag, omitClose) {
this._tagOpen = '<' + tag + ' ';
this._tagClose = omitClose ? '' : '</' + tag + '>';
this.tagName = tag.toUpperCase();
}
複製程式碼
程式碼稍微多一點,但是工作目標很單一,就是為了將描述jsx的obj解析成HTML string。其實可以參照我上面直接亮出來的自己寫的程式碼部分。
如上,其實我們已經完成了元件的初始化、渲染~
好吧,我們一直說的渲染的核心部分還沒有細說~~~
掛載元件ref到this.refs上,設定生命週期、狀態和rootID
mountComponent: function(rootID, transaction) {
invariant(
this._lifeCycleState === ComponentLifeCycle.UNMOUNTED,
'mountComponent(%s, ...): Can only mount an unmounted component.',
rootID
);
var props = this.props;
if (props.ref != null) {
ReactOwner.addComponentAsRefTo(this, props.ref, props[OWNER]);
}
this._rootNodeID = rootID;
this._lifeCycleState = ComponentLifeCycle.MOUNTED;
// Effectively: return '';
},
複製程式碼
如果元件ref屬性為空,則為元件的this.refs上掛在當前元件,也就是this,實現如下:
addComponentAsRefTo: function(component, ref, owner) {
owner.attachRef(ref, component);
}
複製程式碼
attachRef: function(ref, component) {
var refs = this.refs || (this.refs = {});
refs[ref] = component;
},
複製程式碼
上述程式碼我刪除了相關的判斷警告。
設定元件生命狀態
元件的生命狀態和生命週期鉤子函式是react的兩個概念,在react中存在兩種生命週期
- 主:元件生命週期:_lifeCycleState,用來校驗react元件在執行函式時狀態值是否正確
- 輔:複合元件生命週期:_componsiteLifeCycleState,用來保證setState流程不受其他行為影響
_lifeCycleState
var ComponentLifeCycle = keyMirror({
/**
* Mounted components have a DOM node representation and are capable of
* receiving new props.
*/
MOUNTED: null,
/**
* Unmounted components are inactive and cannot receive new props.
*/
UNMOUNTED: null
});
複製程式碼
元件生命週期非常簡單,就列舉了兩種,MOUNTED and UNMOUNTED
在原始碼中使用其只是為了在相應的階段觸發時候校驗,並且給出錯誤提示
getDOMNode: function() {
invariant(
ExecutionEnvironment.canUseDOM,
'getDOMNode(): The DOM is not supported in the current environment.'
);
invariant(
this._lifeCycleState === ComponentLifeCycle.MOUNTED,
'getDOMNode(): A component must be mounted to have a DOM node.'
);
var rootNode = this._rootNode;
if (!rootNode) {
rootNode = document.getElementById(this._rootNodeID);
if (!rootNode) {
// TODO: Log the frequency that we reach this path.
rootNode = ReactMount.findReactRenderedDOMNodeSlow(this._rootNodeID);
}
this._rootNode = rootNode;
}
return rootNode;
},
複製程式碼
_compositeLifeCycleState
複合元件的生命週期只在一個地方使用:setState
var CompositeLifeCycle = keyMirror({
/**
* Components in the process of being mounted respond to state changes
* differently.
*/
MOUNTING: null,
/**
* Components in the process of being unmounted are guarded against state
* changes.
*/
UNMOUNTING: null,
/**
* Components that are mounted and receiving new props respond to state
* changes differently.
*/
RECEIVING_PROPS: null,
/**
* Components that are mounted and receiving new state are guarded against
* additional state changes.
*/
RECEIVING_STATE: null
});
複製程式碼
replaceState: function(completeState) {
var compositeLifeCycleState = this._compositeLifeCycleState;
invariant(
this._lifeCycleState === ReactComponent.LifeCycle.MOUNTED ||
compositeLifeCycleState === CompositeLifeCycle.MOUNTING,
'replaceState(...): Can only update a mounted (or mounting) component.'
);
invariant(
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_STATE &&
compositeLifeCycleState !== CompositeLifeCycle.UNMOUNTING,
'replaceState(...): Cannot update while unmounting component or during ' +
'an existing state transition (such as within `render`).'
);
this._pendingState = completeState;
// Do not trigger a state transition if we are in the middle of mounting or
// receiving props because both of those will already be doing this.
if (compositeLifeCycleState !== CompositeLifeCycle.MOUNTING &&
compositeLifeCycleState !== CompositeLifeCycle.RECEIVING_PROPS) {
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState;
this._pendingState = null;
var transaction = ReactComponent.ReactReconcileTransaction.getPooled();
transaction.perform(
this._receivePropsAndState,
this,
this.props,
nextState,
transaction
);
ReactComponent.ReactReconcileTransaction.release(transaction);
this._compositeLifeCycleState = null;
}
},
複製程式碼
setState會呼叫replaceState ,然後呼叫_receivePropsAndState來更新介面
如果元件正處在mounting的過程或者接受props的過程中,那麼將state快取在_pendingState中,並不會更新介面的值。
校驗props
_assertValidProps: function(props) {
var propDeclarations = this.constructor.propDeclarations;
var componentName = this.constructor.displayName;
for (var propName in propDeclarations) {
var checkProp = propDeclarations[propName];
if (checkProp) {
checkProp(props, propName, componentName);
}
}
}
複製程式碼
this.constructor.propDeclarations 就是元件宣告的props屬性,由於props是執行時傳入的屬性。我們可以看到宣告props的屬性值即為checkProp
結束語
其實至此,關於本篇元件的初始化、渲染已經介紹完畢,由於程式碼中關於太多後續章節,生命週期、props、state、物件緩衝池、事務等,所以暫時都先略過,後續學習到的時候再回頭查閱。