ClassComponent 的更新
1 ) 概述
- 在 react 中 class component,是一個非常重要的角色
- 它承擔了 react 中 更新整個應(yīng)用的API
setState
forceUpdate
- 在react當中,只有更新了state之后,整個應(yīng)用才會重新進行渲染
- 在 class component 中, 它的邏輯相對復(fù)雜
2 )源碼
在 packages/react-reconciler/src/ReactFiberBeginWork.js
// 這個方法就是更新 ClassComponent 組件的一個過程
function updateClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
nextProps,
renderExpirationTime: ExpirationTime,
) {
// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
// 先跳過 context 相關(guān)的邏輯
let hasContext;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}
prepareToReadContext(workInProgress, renderExpirationTime);
// instance 就是我們 class component,通過new這個class獲取的一個對象
const instance = workInProgress.stateNode;
// 在這里面,它聲明了一個 shouldUpdate 的一個屬性
let shouldUpdate;
// 先判斷instance是否存在, 比如說我們第一次通過 ReactDOM.render 進行渲染的過程當中
// 在從上往下第一次渲染的過程當中,第一次更新到 class component 的時候
// 它的instance肯定是不存在的,因為它還沒有被更新過,所以它的節(jié)點肯定是沒有被創(chuàng)建的
if (instance === null) {
// 接下去, 判斷一下current是否等于null
// current等于null的情況是代表在進入第一次渲染,因為current它還不存在
// 如果current不等于null, 代表我們至少已經(jīng)經(jīng)歷過一次渲染了
// 這時候 instance 不存在,而 current 存在,說明第一次渲染的時候沒有創(chuàng)建這個instance
// 同樣說明這個組件是被 suspended 的,一個組件處于suspended的狀態(tài)
// 在這里react認為它相當于是第一次被渲染, 在初次渲染的時候,只是拋出了一個promise
// 并沒有真正的渲染出它的子節(jié)點, 拋出了一個promise之后,接下去的渲染就結(jié)束了
if (current !== null) {
// An class component without an instance only mounts if it suspended
// inside a non- concurrent tree, in an inconsistent state. We want to
// tree it like a new mount, even though an empty version of it already
// committed. Disconnect the alternate pointers.
current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}
// In the initial pass we might need to construct the instance.
// 對于沒有instance的一個情況,需要去創(chuàng)建 class instance
constructClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
// 并且要mount這個 class instance
mountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
// 在第一次渲染的時候,會執(zhí)行上述兩個方法,并且 shouldUpdate = true;
shouldUpdate = true;
} else if (current === null) {
// In a resume, we'll already have an instance we can reuse.
// 在 有instance 和 沒有 current 的情況下,這種是之前被中斷的
// 在執(zhí)行 class component 的 render 方法的時候 報錯,但 instance 已被創(chuàng)建
// 代表著可以復(fù)用 instance
shouldUpdate = resumeMountClassInstance(
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
} else {
// 對于又有 current 又有 instance的情況,說明組件已經(jīng)被重新渲染了
// 這個 updateClassInstance 方法和 上述 resumeMountClassInstance 類似
// 最大的區(qū)別是 內(nèi)部判斷 comonentDidUpdate
shouldUpdate = updateClassInstance(
current,
workInProgress,
Component,
nextProps,
renderExpirationTime,
);
}
// 這里最終調(diào)用 finishClassComponent
return finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime,
);
}
-
進入
constructClassInstance
function constructClassInstance( workInProgress: Fiber, ctor: any, props: any, renderExpirationTime: ExpirationTime, ): any { // 先跳過 context 相關(guān) let isLegacyContextConsumer = false; let unmaskedContext = emptyContextObject; let context = null; const contextType = ctor.contextType; // if (typeof contextType === 'object' && contextType !== null) { // 跳過 DEV if (__DEV__) { if ( contextType.$$typeof !== REACT_CONTEXT_TYPE && !didWarnAboutInvalidateContextType.has(ctor) ) { didWarnAboutInvalidateContextType.add(ctor); warningWithoutStack( false, '%s defines an invalid contextType. ' + 'contextType should point to the Context object returned by React.createContext(). ' + 'Did you accidentally pass the Context.Provider instead?', getComponentName(ctor) || 'Component', ); } } context = readContext((contextType: any)); } else { unmaskedContext = getUnmaskedContext(workInProgress, ctor, true); const contextTypes = ctor.contextTypes; isLegacyContextConsumer = contextTypes !== null && contextTypes !== undefined; context = isLegacyContextConsumer ? getMaskedContext(workInProgress, unmaskedContext) : emptyContextObject; } // Instantiate twice to help detect side-effects. if (__DEV__) { if ( debugRenderPhaseSideEffects || (debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) ) { new ctor(props, context); // eslint-disable-line no-new } } // ctor 是 construct 的一個縮寫 這個 ctor 就是在 ReactElement當中 // 存在的那個type,也就是 class component 定義的那個class // 傳入 props和context,對應(yīng)于 ReactBaseClasses.js 里面,Component 函數(shù) 接收的這兩個參數(shù) // 第三個參數(shù) updater 后面在 adoptClassInstance 里面設(shè)置 const instance = new ctor(props, context); // 獲取 state const state = (workInProgress.memoizedState = instance.state !== null && instance.state !== undefined ? instance.state : null); adoptClassInstance(workInProgress, instance); if (__DEV__) { if (typeof ctor.getDerivedStateFromProps === 'function' && state === null) { const componentName = getComponentName(ctor) || 'Component'; if (!didWarnAboutUninitializedState.has(componentName)) { didWarnAboutUninitializedState.add(componentName); warningWithoutStack( false, '`%s` uses `getDerivedStateFromProps` but its initial state is ' + '%s. This is not recommended. Instead, define the initial state by ' + 'assigning an object to `this.state` in the constructor of `%s`. ' + 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.', componentName, instance.state === null ? 'null' : 'undefined', componentName, ); } } // If new component APIs are defined, "unsafe" lifecycles won't be called. // Warn about these lifecycles if they are present. // Don't warn about react-lifecycles-compat polyfilled methods though. if ( typeof ctor.getDerivedStateFromProps === 'function' || typeof instance.getSnapshotBeforeUpdate === 'function' ) { let foundWillMountName = null; let foundWillReceivePropsName = null; let foundWillUpdateName = null; if ( typeof instance.componentWillMount === 'function' && instance.componentWillMount.__suppressDeprecationWarning !== true ) { foundWillMountName = 'componentWillMount'; } else if (typeof instance.UNSAFE_componentWillMount === 'function') { foundWillMountName = 'UNSAFE_componentWillMount'; } if ( typeof instance.componentWillReceiveProps === 'function' && instance.componentWillReceiveProps.__suppressDeprecationWarning !== true ) { foundWillReceivePropsName = 'componentWillReceiveProps'; } else if ( typeof instance.UNSAFE_componentWillReceiveProps === 'function' ) { foundWillReceivePropsName = 'UNSAFE_componentWillReceiveProps'; } if ( typeof instance.componentWillUpdate === 'function' && instance.componentWillUpdate.__suppressDeprecationWarning !== true ) { foundWillUpdateName = 'componentWillUpdate'; } else if (typeof instance.UNSAFE_componentWillUpdate === 'function') { foundWillUpdateName = 'UNSAFE_componentWillUpdate'; } if ( foundWillMountName !== null || foundWillReceivePropsName !== null || foundWillUpdateName !== null ) { const componentName = getComponentName(ctor) || 'Component'; const newApiName = typeof ctor.getDerivedStateFromProps === 'function' ? 'getDerivedStateFromProps()' : 'getSnapshotBeforeUpdate()'; if (!didWarnAboutLegacyLifecyclesAndDerivedState.has(componentName)) { didWarnAboutLegacyLifecyclesAndDerivedState.add(componentName); warningWithoutStack( false, 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' + '%s uses %s but also contains the following legacy lifecycles:%s%s%s\n\n' + 'The above lifecycles should be removed. Learn more about this warning here:\n' + 'https://fb.me/react-async-component-lifecycle-hooks', componentName, newApiName, foundWillMountName !== null ? `\n ${foundWillMountName}` : '', foundWillReceivePropsName !== null ? `\n ${foundWillReceivePropsName}` : '', foundWillUpdateName !== null ? `\n ${foundWillUpdateName}` : '', ); } } } } // Cache unmasked context so we can avoid recreating masked context unless necessary. // ReactFiberContext usually updates this cache but can't for newly-created instances. if (isLegacyContextConsumer) { cacheContext(workInProgress, unmaskedContext, context); } return instance; }
- 進入
adoptClassInstance
function adoptClassInstance(workInProgress: Fiber, instance: any): void { instance.updater = classComponentUpdater; // 掛載 update 方法 workInProgress.stateNode = instance; // instance 掛載到 stateNode 以供下次進來時使用,下次進來就會存在了 // The instance needs access to the fiber so that it can schedule updates ReactInstanceMap.set(instance, workInProgress); if (__DEV__) { instance._reactInternalInstance = fakeInternalInstance; } }
- 進入
ReactInstanceMap
這個就是對 instance 上_reactInternalFiber
屬性的設(shè)置export function remove(key) { key._reactInternalFiber = undefined; } export function get(key) { return key._reactInternalFiber; } export function has(key) { return key._reactInternalFiber !== undefined; } export function set(key, value) { key._reactInternalFiber = value; }
- 這也意味著,通過
this._reactInternalFiber
就可以拿到當前 fiber 對象
- 這也意味著,通過
- 進入
-
進入
mountClassInstance
// Invokes the mount life-cycles on a previously never rendered instance. function mountClassInstance( workInProgress: Fiber, ctor: any, newProps: any, renderExpirationTime: ExpirationTime, ): void { if (__DEV__) { checkClassInstance(workInProgress, ctor, newProps); } const instance = workInProgress.stateNode; instance.props = newProps; instance.state = workInProgress.memoizedState; instance.refs = emptyRefsObject; const contextType = ctor.contextType; if (typeof contextType === 'object' && contextType !== null) { instance.context = readContext(contextType); } else { const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true); instance.context = getMaskedContext(workInProgress, unmaskedContext); } if (__DEV__) { if (instance.state === newProps) { const componentName = getComponentName(ctor) || 'Component'; if (!didWarnAboutDirectlyAssigningPropsToState.has(componentName)) { didWarnAboutDirectlyAssigningPropsToState.add(componentName); warningWithoutStack( false, '%s: It is not recommended to assign props directly to state ' + "because updates to props won't be reflected in state. " + 'In most cases, it is better to use props directly.', componentName, ); } } if (workInProgress.mode & StrictMode) { ReactStrictModeWarnings.recordUnsafeLifecycleWarnings( workInProgress, instance, ); ReactStrictModeWarnings.recordLegacyContextWarning( workInProgress, instance, ); } if (warnAboutDeprecatedLifecycles) { ReactStrictModeWarnings.recordDeprecationWarnings( workInProgress, instance, ); } } // 獲取 updateQueue,初次渲染這個 updateQueue 是空的 // 對于有 setState 的情況,它的 updateQueen 里面可能有多個update // 這個時候, 需要調(diào)用這個 updateQueen 來去得到一個新的state let updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { // 這里 計算出新的state 并賦值給 workInProgress.memoizedState processUpdateQueue( workInProgress, updateQueue, newProps, instance, renderExpirationTime, ); // 同時更新 instance.state instance.state = workInProgress.memoizedState; } // 判斷是否有這個生命周期方法 const getDerivedStateFromProps = ctor.getDerivedStateFromProps; if (typeof getDerivedStateFromProps === 'function') { // 如果存在該生命周期方法,則計算新的state // 這個生命周期會在組件更新的過程中被調(diào)用 applyDerivedStateFromProps( workInProgress, ctor, getDerivedStateFromProps, newProps, ); instance.state = workInProgress.memoizedState; } // In order to support react-lifecycles-compat polyfilled components, // Unsafe lifecycles should not be invoked for components using the new APIs. // 判斷是否有 componentWillMount 生命周期方法 // 在這個生命周期方法中,會執(zhí)行 setState 方法 if ( typeof ctor.getDerivedStateFromProps !== 'function' && typeof instance.getSnapshotBeforeUpdate !== 'function' && (typeof instance.UNSAFE_componentWillMount === 'function' || typeof instance.componentWillMount === 'function') ) { // callComponentWillMount(workInProgress, instance); // If we had additional state updates during this life-cycle, let's // process them now. // 執(zhí)行了 這個生命周期方法,就需要重新執(zhí)行 updateQueue // 如果在這時執(zhí)行了 setState, 立馬被反映到組件上面 updateQueue = workInProgress.updateQueue; if (updateQueue !== null) { processUpdateQueue( workInProgress, updateQueue, newProps, instance, renderExpirationTime, ); instance.state = workInProgress.memoizedState; } } if (typeof instance.componentDidMount === 'function') { workInProgress.effectTag |= Update; } }
- 進入
processUpdateQueue
export function processUpdateQueue<State>( workInProgress: Fiber, queue: UpdateQueue<State>, props: any, instance: any, renderExpirationTime: ExpirationTime, ): void { hasForceUpdate = false; // 克隆 updateQueue queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue); if (__DEV__) { currentlyProcessingQueue = queue; } // These values may change as we process the queue. let newBaseState = queue.baseState; let newFirstUpdate = null; let newExpirationTime = NoWork; // Iterate through the list of updates to compute the result. let update = queue.firstUpdate; let resultState = newBaseState; while (update !== null) { const updateExpirationTime = update.expirationTime; if (updateExpirationTime < renderExpirationTime) { // This update does not have sufficient priority. Skip it. if (newFirstUpdate === null) { // This is the first skipped update. It will be the first update in // the new list. newFirstUpdate = update; // Since this is the first update that was skipped, the current result // is the new base state. newBaseState = resultState; } // Since this update will remain in the list, update the remaining // expiration time. if (newExpirationTime < updateExpirationTime) { newExpirationTime = updateExpirationTime; } } else { // This update does have sufficient priority. Process it and compute // a new result. resultState = getStateFromUpdate( workInProgress, queue, update, resultState, props, instance, ); const callback = update.callback; // 判斷是否有 callback if (callback !== null) { workInProgress.effectTag |= Callback; // 加上 Callback 這塊 // Set this to null, in case it was mutated during an aborted render. update.nextEffect = null; // 防止在中斷的渲染中被修改 if (queue.lastEffect === null) { queue.firstEffect = queue.lastEffect = update; // 鏈表操作 } else { queue.lastEffect.nextEffect = update; queue.lastEffect = update; } } } // Continue to the next update. update = update.next; } // Separately, iterate though the list of captured updates. let newFirstCapturedUpdate = null; update = queue.firstCapturedUpdate; while (update !== null) { const updateExpirationTime = update.expirationTime; if (updateExpirationTime < renderExpirationTime) { // This update does not have sufficient priority. Skip it. if (newFirstCapturedUpdate === null) { // This is the first skipped captured update. It will be the first // update in the new list. newFirstCapturedUpdate = update; // If this is the first update that was skipped, the current result is // the new base state. if (newFirstUpdate === null) { newBaseState = resultState; } } // Since this update will remain in the list, update the remaining // expiration time. if (newExpirationTime < updateExpirationTime) { newExpirationTime = updateExpirationTime; } } else { // This update does have sufficient priority. Process it and compute // a new result. resultState = getStateFromUpdate( workInProgress, queue, update, resultState, props, instance, ); const callback = update.callback; if (callback !== null) { workInProgress.effectTag |= Callback; // Set this to null, in case it was mutated during an aborted render. update.nextEffect = null; if (queue.lastCapturedEffect === null) { queue.firstCapturedEffect = queue.lastCapturedEffect = update; } else { queue.lastCapturedEffect.nextEffect = update; queue.lastCapturedEffect = update; } } } update = update.next; } if (newFirstUpdate === null) { queue.lastUpdate = null; } if (newFirstCapturedUpdate === null) { queue.lastCapturedUpdate = null; } else { workInProgress.effectTag |= Callback; } if (newFirstUpdate === null && newFirstCapturedUpdate === null) { // We processed every update, without skipping. That means the new base // state is the same as the result state. newBaseState = resultState; } queue.baseState = newBaseState; queue.firstUpdate = newFirstUpdate; queue.firstCapturedUpdate = newFirstCapturedUpdate; // Set the remaining expiration time to be whatever is remaining in the queue. // This should be fine because the only two other things that contribute to // expiration time are props and context. We're already in the middle of the // begin phase by the time we start processing the queue, so we've already // dealt with the props. Context in components that specify // shouldComponentUpdate is tricky; but we'll have to account for // that regardless. workInProgress.expirationTime = newExpirationTime; workInProgress.memoizedState = resultState; if (__DEV__) { currentlyProcessingQueue = null; } }
- 進入
ensureWorkInProgressQueueIsAClone
function ensureWorkInProgressQueueIsAClone<State>( workInProgress: Fiber, queue: UpdateQueue<State>, ): UpdateQueue<State> { const current = workInProgress.alternate; if (current !== null) { // If the work-in-progress queue is equal to the current queue, // we need to clone it first. if (queue === current.updateQueue) { // 要保證 workInProgress.updateQueue 是一個克隆的queue, 而非直接進行修改 queue = workInProgress.updateQueue = cloneUpdateQueue(queue); // 拷貝 queue } } return queue; }
- 進入
- 進入
getStateFromUpdate
function getStateFromUpdate<State>( workInProgress: Fiber, queue: UpdateQueue<State>, update: Update<State>, prevState: State, nextProps: any, instance: any, ): any { switch (update.tag) { case ReplaceState: { const payload = update.payload; if (typeof payload === 'function') { // Updater function if (__DEV__) { if ( debugRenderPhaseSideEffects || (debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) ) { payload.call(instance, prevState, nextProps); } } return payload.call(instance, prevState, nextProps); } // State object return payload; } // 這里 a & ~b 表示: a & 除了b之外的所有屬性 case CaptureUpdate: { workInProgress.effectTag = (workInProgress.effectTag & ~ShouldCapture) | DidCapture; // 這里最終剩下的 只有 DidCapture } // Intentional fallthrough case UpdateState: { const payload = update.payload; let partialState; if (typeof payload === 'function') { // Updater function if (__DEV__) { if ( debugRenderPhaseSideEffects || (debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) ) { payload.call(instance, prevState, nextProps); } } // 如果是 function 則調(diào)用 payload 傳入之前的參數(shù) 計算出 state partialState = payload.call(instance, prevState, nextProps); } else { // Partial state object partialState = payload; } // 處理特殊情況 if (partialState === null || partialState === undefined) { // Null and undefined are treated as no-ops. return prevState; } // Merge the partial state and the previous state. // 合并 return Object.assign({}, prevState, partialState); } case ForceUpdate: { hasForceUpdate = true; return prevState; } } return prevState; }
- 進入
-
進入
finishClassComponent
文章來源:http://www.zghlxwxcb.cn/news/detail-811799.htmlfunction finishClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, shouldUpdate: boolean, hasContext: boolean, renderExpirationTime: ExpirationTime, ) { // Refs should update even if shouldComponentUpdate returns false // 這個先跳過 markRef(current, workInProgress); // 判斷 workInProgress.effectTag 上是否有 DidCapture const didCaptureError = (workInProgress.effectTag & DidCapture) !== NoEffect; // 在沒有更新也沒有錯誤捕獲的情況下 if (!shouldUpdate && !didCaptureError) { // Context providers should defer to sCU for rendering if (hasContext) { invalidateContextProvider(workInProgress, Component, false); } // 跳過更新 return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, ); } const instance = workInProgress.stateNode; // Rerender ReactCurrentOwner.current = workInProgress; let nextChildren; // 有任何錯誤捕獲, 但沒有配置這個api的時候 // 這個 getDerivedStateFromError 生命周期的api和 componentDidCatch 有區(qū)別, 后者在下次更新的時候處理,中間可能instance不存在(因為出錯了) // 就繼續(xù)可能引發(fā)在這個class component上面設(shè)置的 ref拿到一個null, 操作ref的時候就會出現(xiàn)錯誤 // 在前者這個新的生命周期的api中, 在本次渲染中,生成 state, 渲染出屬性, 這樣對于 instance 對象依然存在 對應(yīng) ref 就可以拿到實際的對象 if ( didCaptureError && typeof Component.getDerivedStateFromError !== 'function' ) { // If we captured an error, but getDerivedStateFrom catch is not defined, // unmount all the children. componentDidCatch will schedule an update to // re-render a fallback. This is temporary until we migrate everyone to // the new API. // TODO: Warn in a future release. nextChildren = null; if (enableProfilerTimer) { stopProfilerTimerIfRunning(workInProgress); } } else { if (__DEV__) { ReactCurrentFiber.setCurrentPhase('render'); nextChildren = instance.render(); if ( debugRenderPhaseSideEffects || (debugRenderPhaseSideEffectsForStrictMode && workInProgress.mode & StrictMode) ) { instance.render(); } ReactCurrentFiber.setCurrentPhase(null); } else { // 調(diào)用 render 方法,計算出新的 children nextChildren = instance.render(); } } // React DevTools reads this flag. workInProgress.effectTag |= PerformedWork; // 增加 PerformedWork // 不是第一次渲染,并且有錯誤捕獲 if (current !== null && didCaptureError) { // If we're recovering from an error, reconcile without reusing any of // the existing children. Conceptually, the normal children and the children // that are shown on error are two different sets, so we shouldn't reuse // normal children even if their identities match. forceUnmountCurrentAndReconcile( current, workInProgress, nextChildren, renderExpirationTime, ); } else { // 正常情況調(diào)用 調(diào)和 children reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, ); } // Memoize state using the values we just used to render. // TODO: Restructure so we never read values from the instance. workInProgress.memoizedState = instance.state; // The context might have changed so we need to recalculate it. if (hasContext) { invalidateContextProvider(workInProgress, Component, true); } return workInProgress.child; // 把 render 方法渲染出來的第一個子節(jié)點 返回給 nextUnitOfWork, 就可以在 workLoop 中繼續(xù)進行更新的流程 }
-
簡單總結(jié)文章來源地址http://www.zghlxwxcb.cn/news/detail-811799.html
- 以上是更新 class component 的整體流程
- 一開始要使用
constructClassInstance
創(chuàng)建 class instance - 內(nèi)部會根據(jù)不同情況調(diào)用不同方法
- 并且要調(diào)用
mountClassInstance
來掛載 - 如果是第一次渲染被中斷的情況
- 如果存在 instance 則重復(fù)使用 instance
- 它調(diào)用的生命周期方法仍然是 第一次渲染的生命周期方法
- 也就是
componentDidMount
- 如果不是第一次渲染的情況
- 調(diào)用 updateClassInstance 方法
- 這種會最終調(diào)用
componentDidUpdate
方法
- 它們中間的流程類似
- 判斷各種情況,創(chuàng)建新的 instance 或 復(fù)用之前的
- 通過
processUpdateQueue
來獲取新的 state - 會把它賦值到 instance 和 fiber 上面進行記錄
- 最終都會調(diào)用
finishClassComponent
- 這里面做一些錯誤判斷的處理
- 以及是否可以跳過更新的過程
- 還有重新調(diào)和子節(jié)點 (通用流程)
到了這里,關(guān)于React16源碼: React中的updateClassComponent的源碼實現(xiàn)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!