Reactソースの分析と実現(二):状態、属性の更新->set State
14591 ワード
原文のリンク先:https://github.com/Nealyang%EF%BC%9A%E7%8A%B6%E6%80%81%E3%80%81%E5%B1%9E%E6%80%A7%E6%9B%B4%E6%96%B0%20-%3 E%20 set Start.md)転載は出所を明記してください.
ステータスの更新
今回はset Stateを分析して0.3バージョンに基づいて、比較的簡単なことを実現して、後で現在使用しているバージョンと事務機構を分析します.
フローチャートは大体次の通りです.
set Stateのソースコードは比較的に簡単ですが、更新を実行する過程では複雑です.私たちは直接ソースに従って少しずつ分かります. React Component.js
このコードは非常に簡単です.つまり、私たちが入ってきたstateとthis._pendingStateは一回mergeをして、mergeのコードはutil.jsの下にあります.
最後に、merge後の結果を
デモできます.
16または15のバージョンを使用しています.
なぜですか?ソースコードのこの部分から来ています.
属性の更新
まず、属性の更新は必ずstateの更新のためであることを知っています.だから、実はコンポーネント属性の更新プロセスはset Stateの更新の継続です.つまり、set Stateはコンポーネント属性の更新に出発できます.ソースは私がstateの更新を処理する時、ついでに属性の更新を検出しました.だから、このソースの始まりは、まだset Stateから見てもいいです.
続いてレンデコンポーネントのreceiveProps方法を呼び出します.実はこの場所は最初はとても困惑しました.thisは馬鹿を指して分かりませんでした.その後いろいろな資料を調べて、複合部品ならReactComponent.receive Propsを実行します.ソースは以下の通りです.
実は、compsiteComponentは最終的には分析のもとのコンポーネントに戻ってきます.全体を通して、React NativeComponent.jsコードをご覧ください.
まずreceive Props方法から見ます.
最後にコンポーネントのプロパティを直接更新します.
結尾語
全部読みました.種類がありますか?
reactソースの中には多くの点の知识が含まれています.例えば、私たちが前に言ったVDOM、后ろからdom-diff、事务、キャッシュなどを勉强しに行きます.全部同じ点ですが、一つの点から切り込むとどうしても一部の味気ない卵があります.焦らないでください.
ステータスの更新
今回はset Stateを分析して0.3バージョンに基づいて、比較的簡単なことを実現して、後で現在使用しているバージョンと事務機構を分析します.
フローチャートは大体次の通りです.
set Stateのソースコードは比較的に簡単ですが、更新を実行する過程では複雑です.私たちは直接ソースに従って少しずつ分かります.
/**
* Sets a subset of the state. Always use this or `replaceState` to mutate
* state. You should treat `this.state` as immutable.
*
* There is no guarantee that `this.state` will be immediately updated, so
* accessing `this.state` after calling this method may return the old value.
*
* @param {object} partialState Next partial state to be merged with state.
* @final
* @protected
*/
setState: function(partialState) {
// Merge with `_pendingState` if it exists, otherwise with existing state.
this.replaceState(merge(this._pendingState || this.state, partialState));
},
コメントの部分は明確にしています.セットした値はすぐにもらえません.このコードは非常に簡単です.つまり、私たちが入ってきたstateとthis._pendingStateは一回mergeをして、mergeのコードはutil.jsの下にあります.
var merge = function(one, two) {
var result = {};
mergeInto(result, one);
mergeInto(result, two);
return result;
};
function mergeInto(one, two) {
checkMergeObjectArg(one);
if (two != null) {
checkMergeObjectArg(two);
for (var key in two) {
if (!two.hasOwnProperty(key)) {
continue;
}
one[key] = two[key];
}
}
}
checkMergeObjectArgs: function(one, two) {
mergeHelpers.checkMergeObjectArg(one);
mergeHelpers.checkMergeObjectArg(two);
},
/**
* @param {*} arg
*/
checkMergeObjectArg: function(arg) {
throwIf(isTerminal(arg) || Array.isArray(arg), ERRORS.MERGE_CORE_FAILURE);
},
var isTerminal = function(o) {
return typeof o !== 'object' || o === null;
};
var throwIf = function(condition, err) {
if (condition) {
throw new Error(err);
}
};
診断コードのロジックはとても簡単です.機能はObject.assign()
です.でも、上のコードからreactソースのfunctionの多くは小さくて巧妙な特徴を持っていることが分かります.最後に、merge後の結果を
replaceState
に伝えます.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;
}
},
50%を切り离してwarningコードを判断します.上のコードから见れば、compnsite LifeStateだけがmountingとreceivingに等しくないです.propsの場合は、_を呼び出します.receive PropsAndState関数がコンポーネントを更新します.デモできます.
var ExampleApplication = React.createClass({
getInitialState() {
return {}
},
componentWillMount() {
this.setState({
a: 1,
})
console.log('componentWillMount', this.state.a)
this.setState({
a: 2,
})
console.log('componentWillMount', this.state.a)
this.setState({
a: 3,
})
console.log('componentWillMount', this.state.a)
setTimeout(() => console.log('a5'), 0)
setTimeout(() => console.log(this.state.a,'componentWillMount'))
Promise.resolve('a4').then(console.log)
},
componentDidMount() {
this.setState({
a: 4,
})
console.log('componentDidMount', this.state.a)
this.setState({
a: 5,
})
console.log('componentDidMount', this.state.a)
this.setState({
a: 6,
})
console.log('componentDidMount', this.state.a)
},
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);
}
});
したがって、以上の結果から、component WillMountライフサイクルでset State後のthis.stateは変更されません.component DidMountでは正常です.前の記事でも、mount Componentでは、compsiteLifeCycleStateをMOUNTING状態に設定していますが、この過程では、receive PropsAndStateは実行されませんので、this.IVstateも更新されません.PROPS状態では、state更新やrender実行も行われず、udateComponent中にmount Component関数を実行し、mount Component関数がrender関数を呼び出しました.16または15のバージョンを使用しています.
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 1 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 2 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 3 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 4 log
}, 0);
}
最後に印刷した結果は0,0,2,3です.なぜですか?ソースコードのこの部分から来ています.
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setProps, setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
}
ここでは事務の概念、一括更新、benchUpdateなどに関連していますので、現在分析されているバージョンの中ではまだ反復されていません.バージョンアップに従ってゆっくり話します.属性の更新
まず、属性の更新は必ずstateの更新のためであることを知っています.だから、実はコンポーネント属性の更新プロセスはset Stateの更新の継続です.つまり、set Stateはコンポーネント属性の更新に出発できます.ソースは私がstateの更新を処理する時、ついでに属性の更新を検出しました.だから、このソースの始まりは、まだset Stateから見てもいいです.
_receivePropsAndState: function(nextProps, nextState, transaction) {
if (!this.shouldComponentUpdate ||
this.shouldComponentUpdate(nextProps, nextState)) {
this._performComponentUpdate(nextProps, nextState, transaction);
} else {
this.props = nextProps;
this.state = nextState;
}
},
コードは非常に簡単で、一言で説明します.ショルドComponentUpdateがtrueである場合、更新操作を行います. _performComponentUpdate: function(nextProps, nextState, transaction) {
var prevProps = this.props;
var prevState = this.state;
if (this.componentWillUpdate) {
this.componentWillUpdate(nextProps, nextState, transaction);
}
this.props = nextProps;
this.state = nextState;
this.updateComponent(transaction);
if (this.componentDidUpdate) {
transaction.getReactOnDOMReady().enqueue(
this,
this.componentDidUpdate.bind(this, prevProps, prevState)
);
}
},
このコードの核心はthis.updateComponent
を呼び出して、古い属性と状態を保存して、新しい更新をするだけです.componentWillUpdateがあれば実行して、更新プロセスを実行します.最後に、component DidUpdateをget React OnDOMREAdyのキューに押し込み、コンポーネントの更新を待つ. _renderValidatedComponent: function() {
ReactCurrentOwner.current = this;
var renderedComponent = this.render();
ReactCurrentOwner.current = null;
return renderedComponent;
},
...
...
updateComponent: function(transaction) {
var currentComponent = this._renderedComponent;
var nextComponent = this._renderValidatedComponent();
if (currentComponent.constructor === nextComponent.constructor) {
if (!nextComponent.props.isStatic) {
currentComponent.receiveProps(nextComponent.props, transaction);
}
} else {
var thisID = this._rootNodeID;
var currentComponentID = currentComponent._rootNodeID;
currentComponent.unmountComponent();
var nextMarkup = nextComponent.mountComponent(thisID, transaction);
ReactComponent.DOMIDOperations.dangerouslyReplaceNodeWithMarkupByID(
currentComponentID,
nextMarkup
);
this._renderedComponent = nextComponent;
}
},
ここでは、updateComponent
の更新フローを直接見て、まず現在のrender関数のコンポーネントを取得し、次にrender関数のコンポーネントを取得し、_renderValidatedComponent
は次のrenderコンポーネントを取得することである.コンストラクタによってコンポーネントが同じかどうかを判断し、同じであり、またコンポーネントが非静的であれば、コンポーネントの属性を更新し、そうでなければ、現在のコンポーネントをアンインストールし、次にモジュールを再mountし、直接的に暴力的に更新する.続いてレンデコンポーネントのreceiveProps方法を呼び出します.実はこの場所は最初はとても困惑しました.thisは馬鹿を指して分かりませんでした.その後いろいろな資料を調べて、複合部品ならReactComponent.receive Propsを実行します.ソースは以下の通りです.
receiveProps: function(nextProps, transaction) {
if (this.constructor.propDeclarations) {
this._assertValidProps(nextProps);
}
ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_PROPS;
if (this.componentWillReceiveProps) {
this.componentWillReceiveProps(nextProps, transaction);
}
this._compositeLifeCycleState = CompositeLifeCycle.RECEIVING_STATE;
var nextState = this._pendingState || this.state;
this._pendingState = null;
this._receivePropsAndState(nextProps, nextState, transaction);
this._compositeLifeCycleState = null;
},
ここのthis._.に気づく人がいるかもしれません.receive PropsAndState関数は、先ほど呼び出したのではないですか?どうやってまた呼び出しますか?そうです.これを呼び出すthisはすでにcurrentComponentです.前のthisではありません.currentComponentは現在のコンポーネントのrenderコンポーネント、つまり現在のコンポーネントのサブコンポーネントです.サブアセンブリもまた、複合コンポーネントまたは元のコンポーネントである可能性がある.このような多形の方法により、再帰的な解析は、各段階のネストされたコンポーネントを正式に行う.最終的には、現在のコンポーネントから下のすべての葉っぱノードのツリー更新が完了します.実は、compsiteComponentは最終的には分析のもとのコンポーネントに戻ってきます.全体を通して、React NativeComponent.jsコードをご覧ください.
まずreceive Props方法から見ます.
receiveProps: function(nextProps, transaction) {
assertValidProps(nextProps);
ReactComponent.Mixin.receiveProps.call(this, nextProps, transaction);
this._updateDOMProperties(nextProps);
this._updateDOMChildren(nextProps, transaction);
this.props = nextProps;
},
function assertValidProps(props) {
if (!props) {
return;
}
var hasChildren = props.children != null ? 1 : 0;
var hasContent = props.content != null ? 1 : 0;
var hasInnerHTML = props.dangerouslySetInnerHTML != null ? 1 : 0;
}
セキュリティ警告とコメントを削除します.コードはとてもシンプルです.まず、astertValidPropsはpropsが適法かどうかを確認します.属性を更新する方法は_updateDOMProperties
です._updateDOMProperties: function(nextProps) {
var lastProps = this.props;
for (var propKey in nextProps) {
var nextProp = nextProps[propKey];
var lastProp = lastProps[propKey];
//
if (!nextProps.hasOwnProperty(propKey) || nextProp === lastProp) {
continue;
}
// style , style, style , styleUpdates 。 updateStylesByID dom style 。
if (propKey === STYLE) {
if (nextProp) {
nextProp = nextProps.style = merge(nextProp);
}
var styleUpdates;
for (var styleName in nextProp) {
if (!nextProp.hasOwnProperty(styleName)) {
continue;
}
if (!lastProp || lastProp[styleName] !== nextProp[styleName]) {
if (!styleUpdates) {
styleUpdates = {};
}
styleUpdates[styleName] = nextProp[styleName];
}
}
if (styleUpdates) {
ReactComponent.DOMIDOperations.updateStylesByID(
this._rootNodeID,
styleUpdates
);
}
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
var lastHtml = lastProp && lastProp.__html;
var nextHtml = nextProp && nextProp.__html;
if (lastHtml !== nextHtml) {
ReactComponent.DOMIDOperations.updateInnerHTMLByID(// innerHtml, dangerouslyInnerHTML HTML
this._rootNodeID,
nextProp
);
}
} else if (propKey === CONTENT) {
ReactComponent.DOMIDOperations.updateTextContentByID(// innerText, content children HTML
this._rootNodeID,
'' + nextProp
);
} else if (registrationNames[propKey]) {
putListener(this._rootNodeID, propKey, nextProp);
} else {
ReactComponent.DOMIDOperations.updatePropertyByID(
this._rootNodeID,
propKey,
nextProp
);
}
}
},
この中の方法はあまり多くないhackテクニックです.とても簡単でまっすぐで、単独で絞り出さずに説明します.直接にコメントに書きます.最後にコンポーネントのプロパティを直接更新します.
setValueForProperty: function(node, name, value) {
if (DOMProperty.isStandardName[name]) {
var mutationMethod = DOMProperty.getMutationMethod[name];
if (mutationMethod) {
mutationMethod(node, value);
} else if (DOMProperty.mustUseAttribute[name]) {
if (DOMProperty.hasBooleanValue[name] && !value) {
node.removeAttribute(DOMProperty.getAttributeName[name]);
} else {
node.setAttribute(DOMProperty.getAttributeName[name], value);
}
} else {
var propName = DOMProperty.getPropertyName[name];
if (!DOMProperty.hasSideEffects[name] || node[propName] !== value) {
node[propName] = value;
}
}
} else if (DOMProperty.isCustomAttribute(name)) {
node.setAttribute(name, value);
}
}
全体的な属性更新のフローチャートは以下の通りです.結尾語
全部読みました.種類がありますか?
reactソースの中には多くの点の知识が含まれています.例えば、私たちが前に言ったVDOM、后ろからdom-diff、事务、キャッシュなどを勉强しに行きます.全部同じ点ですが、一つの点から切り込むとどうしても一部の味気ない卵があります.焦らないでください.