深入研究React中setState源碼
React作為一門前端框架,雖然只是focus在MVVM中的View部分,但還是實(shí)現(xiàn)了View和model的綁定。修改數(shù)據(jù)的同時(shí),可以實(shí)現(xiàn)View的刷新。這大大簡(jiǎn)化了我們的邏輯,只用關(guān)心數(shù)據(jù)流的變化,同時(shí)減少了代碼量,使得后期維護(hù)也更加方便。這個(gè)特性則要?dú)w功于setState()方法。React中利用隊(duì)列機(jī)制來(lái)管理state,避免了很多重復(fù)的View刷新。下面我們來(lái)從源碼角度探尋下setState機(jī)制。
1 還是先聲明一個(gè)組件,從最開始一步步來(lái)尋源;
class App extends Component {
//只在組件重新加載的時(shí)候執(zhí)行一次
constructor(props) {
super(props);
//..
}
//other methods
}
//ReactBaseClasses.js中如下:這里就是setState函數(shù)的來(lái)源;
//super其實(shí)就是下面這個(gè)函數(shù)
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.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
所以主要來(lái)看是否傳入了updater參數(shù),也就是說(shuō)何時(shí)進(jìn)行 new 組件;具體的updater參數(shù)是怎么傳遞進(jìn)來(lái)的,以及是那個(gè)對(duì)象,參見
react源碼分析系列文章下面的react中context updater到底是如何傳遞的
這里直接說(shuō)結(jié)果,updater對(duì)象其實(shí)就是ReactUpdateQueue.js 中暴漏出的ReactUpdateQueue對(duì)象;
2 既然找到了setState之后執(zhí)行的動(dòng)作,我們?cè)谝徊讲缴钊脒M(jìn)去
class Root extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
let me = this;
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印出0
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印出0
setTimeout(function(){
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印出2
}, 0);
setTimeout(function(){
me.setState({
count: me.state.count + 1
});
console.log(me.state.count); // 打印出3
}, 0);
}
render() {
return (
<h1>{this.state.count}</h1>
)
}
}
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if (callback) {
this.updater.enqueueCallback(this, callback, 'setState');
}
};
ReactUpdateQueue.js
var ReactUpdates = require('./ReactUpdates');
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
};
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
//在ReactCompositeComponent.js中有這樣一行代碼,這就是其來(lái)源;
// Store a reference from the instance back to the internal representation
//ReactInstanceMap.set(inst, this);
var internalInstance = ReactInstanceMap.get(publicInstance);
//返回的是在ReactCompositeComponent.js中construct函數(shù)返回的對(duì)象;ReactInstance實(shí)例對(duì)象并不是簡(jiǎn)單的new 我們寫的組件的實(shí)例對(duì)象,而是經(jīng)過(guò)instantiateReactComponent.js中ReactCompositeComponentWrapper函數(shù)包裝的對(duì)象;詳見 創(chuàng)建React組件方式以及源碼分析.md
return internalInstance;
};
var ReactUpdateQueue = {
//。。。。省略其他代碼
enqueueCallback: function (publicInstance, callback, callerName) {
ReactUpdateQueue.validateCallback(callback, callerName);
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance);
if (!internalInstance) {
return null;
}
//這里將callback放入組件實(shí)例的_pendingCallbacks數(shù)組中;
if (internalInstance._pendingCallbacks) {
internalInstance._pendingCallbacks.push(callback);
} else {
internalInstance._pendingCallbacks = [callback];
}
// TODO: The callback here is ignored when setState is called from
// componentWillMount. Either fix it or disallow doing so completely in
// favor of getInitialState. Alternatively, we can disallow
// componentWillMount during server-side rendering.
enqueueUpdate(internalInstance);
},
enqueueSetState: function (publicInstance, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
//這里,初始化queue變量,同時(shí)初始化 internalInstance._pendingStateQueue = [ ] ;
//對(duì)于 || 的短路運(yùn)算還是要多梳理下
//queue數(shù)組(模擬隊(duì)列)中存放著setState放進(jìn)來(lái)的對(duì)象;
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
//這里將partialState放入queue數(shù)組中,也就是internalInstance._pendingStateQueue 數(shù)組中,此時(shí),每次setState的partialState,都放進(jìn)了React組件實(shí)例對(duì)象上的_pendingStateQueue屬性中,成為一個(gè)數(shù)組;
queue.push(partialState);
enqueueUpdate(internalInstance);
},
};
module.exports = ReactUpdateQueue;
可以看到enqueueSetState enqueueCallback 最后都會(huì)執(zhí)行enqueueUpdate;
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
ReactUpdates.js
var dirtyComponents = [];
var updateBatchNumber = 0;
var asapCallbackQueue = CallbackQueue.getPooled();
var asapEnqueued = false;
//這里聲明batchingStrategy為null,后期通過(guò)注冊(cè)給其賦值;
var batchingStrategy = null;
//這里的component參數(shù)是js中ReactCompositeComponentWrapper函數(shù)包裝的后的React組件實(shí)例對(duì)象;
function enqueueUpdate(component) {
ensureInjected();
//第一次執(zhí)行setState的時(shí)候,可以進(jìn)入if語(yǔ)句,遇到里面的return語(yǔ)句,終止執(zhí)行
//如果不是正處于創(chuàng)建或更新組件階段,則處理update事務(wù)
if (!batchingStrategy.isBatchingUpdates) {
//batchedUpdates就是ReactDefaultBatchingStrategy.js中聲明的
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
//第二次執(zhí)行setState的時(shí)候,進(jìn)入不了if語(yǔ)句,將組件放入dirtyComponents
//如果正在創(chuàng)建或更新組件,則暫且先不處理update,只是將組件放在dirtyComponents數(shù)組中
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
};
//enqueueUpdate包含了React避免重復(fù)render的邏輯。mountComponent和updateComponent方法在執(zhí)行的最開始,會(huì)調(diào)用到batchedUpdates進(jìn)行批處理更新,此時(shí)會(huì)將isBatchingUpdates設(shè)置為true,也就是將狀態(tài)標(biāo)記為現(xiàn)在正處于更新階段了。之后React以事務(wù)的方式處理組件update,事務(wù)處理完后會(huì)調(diào)用wrapper.close(), 而TRANSACTION_WRAPPERS中包含了RESET_BATCHED_UPDATES這個(gè)wrapper,故最終會(huì)調(diào)用RESET_BATCHED_UPDATES.close(), 它最終會(huì)將isBatchingUpdates設(shè)置為false。
ReactDefaultBatchingStrategy.js
//RESET_BATCHED_UPDATES用來(lái)管理isBatchingUpdates的狀態(tài)
var RESET_BATCHED_UPDATES = {
initialize: emptyFunction,
close: function () {
// 事務(wù)批更新處理結(jié)束時(shí),將isBatchingUpdates設(shè)為了false
ReactDefaultBatchingStrategy.isBatchingUpdates = false;
}
};
//FLUSH_BATCHED_UPDATES會(huì)在一個(gè)transaction的close階段運(yùn)行runBatchedUpdates,從而執(zhí)行update。
//因?yàn)閏lose的執(zhí)行順序是FLUSH_BATCHED_UPDATES.close ==> 然后RESET_BATCHED_UPDATES.close
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
function ReactDefaultBatchingStrategyTransaction() {
this.reinitializeTransaction();
}
_assign(ReactDefaultBatchingStrategyTransaction.prototype, Transaction, {
getTransactionWrappers: function () {
return TRANSACTION_WRAPPERS;
}
});
//這個(gè)transition就是下面ReactDefaultBatchingStrategy對(duì)象中使用的transaction變量
var transaction = new ReactDefaultBatchingStrategyTransaction();
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
/**
* Call the provided function in a context within which calls to `setState`
* and friends are batched such that components aren't updated unnecessarily.
*/
batchedUpdates: function (callback, a, b, c, d, e) {
var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;
// 批處理最開始時(shí),將isBatchingUpdates設(shè)為true,表明正在更新
ReactDefaultBatchingStrategy.isBatchingUpdates = true;
// The code is written this way to avoid extra allocations
if (alreadyBatchingUpdates) {
return callback(a, b, c, d, e);
} else {
//transition在上面已經(jīng)聲明; // 以事務(wù)的方式處理updates,后面詳細(xì)分析transaction
return transaction.perform(callback, null, a, b, c, d, e);
}
}
};
module.exports = ReactDefaultBatchingStrategy;
接下來(lái)我們看下React中的事物處理機(jī)制到底是如何運(yùn)行的;
Transaction.js
var _prodInvariant = require('./reactProdInvariant');
var invariant = require('fbjs/lib/invariant');
var OBSERVED_ERROR = {};
var TransactionImpl = {
reinitializeTransaction: function () {
//getTransactionWrappers這個(gè)函數(shù)ReactDefaultBatchingStrategy.js中聲明的,上面有;返回一個(gè)數(shù)組;
this.transactionWrappers = this.getTransactionWrappers();
if (this.wrapperInitData) {
this.wrapperInitData.length = 0;
} else {
this.wrapperInitData = [];
}
this._isInTransaction = false;
},
_isInTransaction: false,
getTransactionWrappers: null,
isInTransaction: function () {
return !!this._isInTransaction;
},
perform: function (method, scope, a, b, c, d, e, f) {
var errorThrown;
var ret;
try {
this._isInTransaction = true;
errorThrown = true;
//var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
//1 這里會(huì)先執(zhí)行所有的TRANSACTION_WRAPPERS中成員的initialize方法,上面聲明的其都是emptyFunction
this.initializeAll(0);
//2 這里其實(shí)還是執(zhí)行的 enqueueUpdate 函數(shù)
ret = method.call(scope, a, b, c, d, e, f);
errorThrown = false;
} finally {
try {
if (errorThrown) {
// If `method` throws, prefer to show that stack trace over any thrown
// by invoking `closeAll`.
try {
this.closeAll(0);
} catch (err) {}
} else {
// Since `method` didn't throw, we don't want to silence the exception
// here.
//3 執(zhí)行TRANSACTION_WRAPPERS對(duì)象中成員的所有close方法;
this.closeAll(0);
}
} finally {
this._isInTransaction = false;
}
}
return ret;
},
initializeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
try {
this.wrapperInitData[i] = OBSERVED_ERROR;
this.wrapperInitData[i] = wrapper.initialize ? wrapper.initialize.call(this) : null;
} finally {
if (this.wrapperInitData[i] === OBSERVED_ERROR) {
try {
this.initializeAll(i + 1);
} catch (err) {}
}
}
}
},
closeAll: function (startIndex) {
var transactionWrappers = this.transactionWrappers;
for (var i = startIndex; i < transactionWrappers.length; i++) {
var wrapper = transactionWrappers[i];
var initData = this.wrapperInitData[i];
var errorThrown;
try {
errorThrown = true;
if (initData !== OBSERVED_ERROR && wrapper.close) {
wrapper.close.call(this, initData);
}
errorThrown = false;
} finally {
if (errorThrown) {
try {
this.closeAll(i + 1);
} catch (e) {}
}
}
}
this.wrapperInitData.length = 0;
}
};
module.exports = TransactionImpl
//3 執(zhí)行TRANSACTION_WRAPPERS對(duì)象中成員的所有close方法;
var FLUSH_BATCHED_UPDATES = {
initialize: emptyFunction,
close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates)
};
接著會(huì)執(zhí)行ReactUpdates.js中的flushBatchedUpdates方法
ReactUpdates.js中
var flushBatchedUpdates = function () {
while (dirtyComponents.length || asapEnqueued) {
if (dirtyComponents.length) {
var transaction = ReactUpdatesFlushTransaction.getPooled();
//這里執(zhí)行runBatchedUpdates函數(shù);
transaction.perform(runBatchedUpdates, null, transaction);
ReactUpdatesFlushTransaction.release(transaction);
}
if (asapEnqueued) {
asapEnqueued = false;
var queue = asapCallbackQueue;
asapCallbackQueue = CallbackQueue.getPooled();
queue.notifyAll();
CallbackQueue.release(queue);
}
}
};
function runBatchedUpdates(transaction) {
var len = transaction.dirtyComponentsLength;
dirtyComponents.sort(mountOrderComparator);
updateBatchNumber++;
for (var i = 0; i < len; i++) {
var component = dirtyComponents[i];
var callbacks = component._pendingCallbacks;
component._pendingCallbacks = null;
var markerName;
if (ReactFeatureFlags.logTopLevelRenders) {
var namedComponent = component;
// Duck type TopLevelWrapper. This is probably always true.
if (component._currentElement.type.isReactTopLevelWrapper) {
namedComponent = component._renderedComponent;
}
markerName = 'React update: ' + namedComponent.getName();
console.time(markerName);
}
//這里才是真正的開始更新組件
ReactReconciler.performUpdateIfNecessary(component, transaction.reconcileTransaction, updateBatchNumber);
if (markerName) {
console.timeEnd(markerName);
}
if (callbacks) {
for (var j = 0; j < callbacks.length; j++) {
transaction.callbackQueue.enqueue(callbacks[j], component.getPublicInstance());
}
}
}
}
ReactReconciler.js中
performUpdateIfNecessary: function (internalInstance, transaction, updateBatchNumber) {
if (internalInstance._updateBatchNumber !== updateBatchNumber) {
// The component's enqueued batch number should always be the current
// batch or the following one.
return;
}
//這里執(zhí)行React組件實(shí)例對(duì)象的更新;internalInstance上的performUpdateIfNecessary在ReactCompositeComponent.js中的;
internalInstance.performUpdateIfNecessary(transaction);
if (process.env.NODE_ENV !== 'production') {
if (internalInstance._debugID !== 0) {
ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID);
}
}
}
ReactCompositeComponent.js
performUpdateIfNecessary: function (transaction) {
if (this._pendingElement != null) {
// receiveComponent會(huì)最終調(diào)用到updateComponent,從而刷新View
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
} else if (this._pendingStateQueue !== null || this._pendingForceUpdate) {
// 執(zhí)行updateComponent,從而刷新View。
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context);
} else {
this._updateBatchNumber = null;
}
},
//執(zhí)行更新React組件的props. state。context函數(shù)
updateComponent: function (transaction, prevParentElement, nextParentElement, prevUnmaskedContext, nextUnmaskedContext) {
var inst = this._instance;
var willReceive = false;
var nextContext;
// Determine if the context has changed or not
if (this._context === nextUnmaskedContext) {
nextContext = inst.context;
} else {
nextContext = this._processContext(nextUnmaskedContext);
willReceive = true;
}
var prevProps = prevParentElement.props;
var nextProps = nextParentElement.props;
// Not a simple state update but a props update
if (prevParentElement !== nextParentElement) {
willReceive = true;
}
// An update here will schedule an update but immediately set
// _pendingStateQueue which will ensure that any state updates gets
// immediately reconciled instead of waiting for the next batch.
if (willReceive && inst.componentWillReceiveProps) {
if (process.env.NODE_ENV !== 'production') {
measureLifeCyclePerf(function () {
return inst.componentWillReceiveProps(nextProps, nextContext);
}, this._debugID, 'componentWillReceiveProps');
} else {
inst.componentWillReceiveProps(nextProps, nextContext);
}
}
//這里可以知道為什么setState可以接受函數(shù),主要就是_processPendingState函數(shù);
//這里僅僅是將每次setState放入到_pendingStateQueue隊(duì)列中的值,合并到nextState,并沒(méi)有真正的更新state的值;真正更新組件的state的值是在下面;
var nextState = this._processPendingState(nextProps, nextContext);
var shouldUpdate = true;
if (!this._pendingForceUpdate) {
if (inst.shouldComponentUpdate) {
if (process.env.NODE_ENV !== 'production') {
shouldUpdate = measureLifeCyclePerf(function () {
return inst.shouldComponentUpdate(nextProps, nextState, nextContext);
}, this._debugID, 'shouldComponentUpdate');
} else {
shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext);
}
} else {
if (this._compositeType === CompositeTypes.PureClass) {
shouldUpdate = !shallowEqual(prevProps, nextProps) || !shallowEqual(inst.state, nextState);
}
}
}
this._updateBatchNumber = null;
if (shouldUpdate) {
this._pendingForceUpdate = false;
// Will set `this.props`, `this.state` and `this.context`.
this._performComponentUpdate(nextParentElement, nextProps, nextState, nextContext, transaction, nextUnmaskedContext);
} else {
// If it's determined that a component should not update, we still want
// to set props and state but we shortcut the rest of the update.
//諾:在這里更新組件的state. props 等值;
this._currentElement = nextParentElement;
this._context = nextUnmaskedContext;
inst.props = nextProps;
inst.state = nextState;
inst.context = nextContext;
}
},
_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
//如果是setState的參數(shù)是一個(gè)函數(shù),那么該函數(shù)接受三個(gè)參數(shù),分別是state props context
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
},
this.state的更新會(huì)在_processPendingState執(zhí)行完執(zhí)行。所以兩次setState取到的都是this.state.count最初的值0,這就解釋了之前的現(xiàn)象。其實(shí),這也是React為了解決這種前后state依賴但是state又沒(méi)及時(shí)更新的一種方案,因此在使用時(shí)大家要根據(jù)實(shí)際情況來(lái)判斷該用哪種方式傳參。來(lái)看個(gè)小例子直觀感受下
handleClickOnLikeButton () {
this.setState({ count: 0 }) // => this.state.count 還是 undefined
this.setState({ count: this.state.count + 1}) // => undefined + 1 = NaN
this.setState({ count: this.state.count + 2}) // => NaN + 2 = NaN
}
//....VS ....
handleClickOnLikeButton () {
this.setState((prevState) => {
return { count: 0 }
})
this.setState((prevState) => {
return { count: prevState.count + 1 } // 上一個(gè) setState 的返回是 count 為 0,當(dāng)前返回 1
})
this.setState((prevState) => {
return { count: prevState.count + 2 } // 上一個(gè) setState 的返回是 count 為 1,當(dāng)前返回 3
})
// 最后的結(jié)果是 this.state.count 為 3
}
...
setState流程還是很復(fù)雜的,設(shè)計(jì)也很精巧,避免了重復(fù)無(wú)謂的刷新組件。它的主要流程如下
- enqueueSetState將state放入隊(duì)列中,并調(diào)用enqueueUpdate處理要更新的Component
- 如果組件當(dāng)前正處于update事務(wù)中,則先將Component存入dirtyComponent中。否則調(diào)用batchedUpdates處理。
- batchedUpdates發(fā)起一次transaction.perform()事務(wù)
- 開始執(zhí)行事務(wù)初始化,運(yùn)行,結(jié)束三個(gè)階段
- 初始化:事務(wù)初始化階段沒(méi)有注冊(cè)方法,故無(wú)方法要執(zhí)行
- 運(yùn)行:執(zhí)行setSate時(shí)傳入的callback方法,一般不會(huì)傳callback參數(shù)
- 結(jié)束:更新isBatchingUpdates為false,并執(zhí)行FLUSH_BATCHED_UPDATES這個(gè)wrapper中的close方法
- FLUSH_BATCHED_UPDATES在close階段,會(huì)循環(huán)遍歷所有的dirtyComponents,調(diào)用updateComponent刷新組件,并執(zhí)行它的pendingCallbacks, 也就是setState中設(shè)置的callback。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
React?Flux與Redux設(shè)計(jì)及使用原理
這篇文章主要介紹了React?Flux與Redux設(shè)計(jì)及使用,Redux最主要是用作應(yīng)用狀態(tài)的管理。簡(jiǎn)言之,Redux用一個(gè)單獨(dú)的常量狀態(tài)樹(state對(duì)象)保存這一整個(gè)應(yīng)用的狀態(tài),這個(gè)對(duì)象不能直接被改變2023-03-03
基于React.js實(shí)現(xiàn)簡(jiǎn)單的文字跑馬燈效果
剛好手上有一個(gè)要實(shí)現(xiàn)文字跑馬燈的react項(xiàng)目,然后ant-design上面沒(méi)有這個(gè)組件,于是只能自己手?jǐn)]一個(gè),文中的實(shí)現(xiàn)方法講解詳細(xì),希望對(duì)大家有所幫助2023-01-01
react項(xiàng)目中使用react-dnd實(shí)現(xiàn)列表的拖拽排序功能
這篇文章主要介紹了react項(xiàng)目中使用react-dnd實(shí)現(xiàn)列表的拖拽排序,本文結(jié)合實(shí)例代碼講解react-dnd是如何實(shí)現(xiàn),代碼簡(jiǎn)單易懂,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02
react中使用ant組件庫(kù)的modal彈窗報(bào)錯(cuò)問(wèn)題及解決
這篇文章主要介紹了react中使用ant組件庫(kù)的modal彈窗報(bào)錯(cuò)問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
React+CSS?實(shí)現(xiàn)繪制豎狀柱狀圖
這篇文章主要介紹了React+CSS?實(shí)現(xiàn)繪制豎狀柱狀圖,文章圍繞主題展開詳細(xì)的內(nèi)容介紹。具有一定的參考價(jià)值,需要的朋友可以參考一下2022-09-09
解決React報(bào)錯(cuò)Style prop value must be a
這篇文章主要為大家介紹了React報(bào)錯(cuò)Style prop value must be an object解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12

