行数 https://github.com/a8397550/react-source-share/blob/master/default/react-dom-default.js

  1. function scheduleUpdateOnFiber(fiber, expirationTime) {
  2. // ... 省略
  3. // true
  4. if (expirationTime === Sync) {
  5. if (
  6. // 常量 NoContext = 0
  7. // 初始化时 executionContext = 8 LegacyUnbatchedContext = 8
  8. // 8 & 8 = 8 下条语句初始化时结果为true
  9. (executionContext & LegacyUnbatchedContext) !== NoContext &&
  10. // 初始化 (8 & (16 | 32)) === NoContext 结果为true
  11. (executionContext & (RenderContext | CommitContext)) === NoContext) {
  12. // expirationTime === 1073741823
  13. // 初始化时,啥也没干
  14. schedulePendingInteractions(root, expirationTime);
  15. var callback = renderRoot(root, Sync, true);
  16. while (callback !== null) {
  17. callback = callback(true);
  18. }
  19. } else {
  20. scheduleCallbackForRoot(root, ImmediatePriority, Sync);
  21. if (executionContext === NoContext) {
  22. flushSyncCallbackQueue();
  23. }
  24. }
  25. } else {
  26. scheduleCallbackForRoot(root, priorityLevel, expirationTime);
  27. }
  28. if ((executionContext & DiscreteEventContext) !== NoContext && (
  29. // Only updates at user-blocking priority or greater are considered
  30. // discrete, even inside a discrete event.
  31. priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority)) {
  32. // This is the result of a discrete event. Track the lowest priority
  33. // discrete update per root so we can flush them early, if needed.
  34. if (rootsWithPendingDiscreteUpdates === null) {
  35. rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
  36. } else {
  37. var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
  38. if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
  39. rootsWithPendingDiscreteUpdates.set(root, expirationTime);
  40. }
  41. }
  42. }
  43. }

renderRoot 主要方法

  1. function renderRoot(root, expirationTime, isSync) {
  2. (function () {
  3. // (8 & (16 | 32)) === NoContext 结果为true 取反 false
  4. if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
  5. {
  6. throw ReactError(Error('Should not already be working.'));
  7. }
  8. }
  9. })();
  10. // 初始化时 true && 1073741823 !== 1073741823 结果 false
  11. if (enableUserTimingAPI && expirationTime !== Sync) {
  12. var didExpire = isSync;
  13. stopRequestCallbackTimer(didExpire);
  14. }
  15. // 初始化时 1073741823 < 1073741823 结果 false
  16. if (root.firstPendingTime < expirationTime) {
  17. return null;
  18. }
  19. // isSync = true
  20. // root.finishedExpirationTime = 0
  21. // expirationTime = 0
  22. // 初始化时 结果为false
  23. if (isSync && root.finishedExpirationTime === expirationTime) {
  24. return commitRoot.bind(null, root);
  25. }
  26. // 初始化时,默认return了false,啥也没干
  27. flushPassiveEffects();
  28. // root = FiberRootNode
  29. // workInProgressRoot = null
  30. // expirationTime = 1073741823
  31. // renderExpirationTime = 0
  32. if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
  33. // prepare 准备 [prɪˈpeə(r)]
  34. // Fresh 新的 [freʃ]
  35. // stack 堆栈 [stæk]
  36. prepareFreshStack(root, expirationTime);
  37. startWorkOnPendingInteractions(root, expirationTime);
  38. } else if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
  39. if (workInProgressRootHasPendingPing) {
  40. prepareFreshStack(root, expirationTime);
  41. } else {
  42. var lastPendingTime = root.lastPendingTime;
  43. if (lastPendingTime < expirationTime) {
  44. return renderRoot.bind(null, root, lastPendingTime);
  45. }
  46. }
  47. }
  48. // workInProgress = FiberNode
  49. if (workInProgress !== null) {
  50. // executionContext = 8
  51. var prevExecutionContext = executionContext;
  52. // 8 | 16 = 24
  53. executionContext |= RenderContext;
  54. // ReactCurrentDispatcher.current = null
  55. var prevDispatcher = ReactCurrentDispatcher.current;
  56. if (prevDispatcher === null) {
  57. /*
  58. var ContextOnlyDispatcher = {
  59. readContext: readContext,
  60. useCallback: throwInvalidHookError,
  61. useContext: throwInvalidHookError,
  62. useEffect: throwInvalidHookError,
  63. useImperativeHandle: throwInvalidHookError,
  64. useLayoutEffect: throwInvalidHookError,
  65. useMemo: throwInvalidHookError,
  66. useReducer: throwInvalidHookError,
  67. useRef: throwInvalidHookError,
  68. useState: throwInvalidHookError,
  69. useDebugValue: throwInvalidHookError,
  70. useResponder: throwInvalidHookError
  71. };
  72. */
  73. prevDispatcher = ContextOnlyDispatcher;
  74. }
  75. // ReactCurrentDispatcher.current = null 全局变量
  76. ReactCurrentDispatcher.current = ContextOnlyDispatcher;
  77. var prevInteractions = null;
  78. // enableSchedulerTracing = true
  79. if (enableSchedulerTracing) {
  80. // __interactionsRef.current = Set(0)
  81. prevInteractions = __interactionsRef.current;
  82. // root.memoizedInteractions = Set(0)
  83. __interactionsRef.current = root.memoizedInteractions;
  84. }
  85. startWorkLoopTimer(workInProgress);
  86. // 方法形参 isSync = true
  87. if (isSync) {
  88. // expirationTime = 1073741823 !== Sync = 1073741823 结果为false
  89. if (expirationTime !== Sync) {
  90. var currentTime = requestCurrentTime();
  91. if (currentTime < expirationTime) {
  92. executionContext = prevExecutionContext;
  93. resetContextDependencies();
  94. ReactCurrentDispatcher.current = prevDispatcher;
  95. if (enableSchedulerTracing) {
  96. __interactionsRef.current = prevInteractions;
  97. }
  98. return renderRoot.bind(null, root, currentTime);
  99. }
  100. }
  101. } else {
  102. currentEventTime = NoWork;
  103. }
  104. do {
  105. try {
  106. // isSync = true
  107. if (isSync) {
  108. workLoopSync();
  109. } else {
  110. workLoop();
  111. }
  112. break;
  113. } catch (thrownValue) {
  114. resetContextDependencies();
  115. resetHooks();
  116. var sourceFiber = workInProgress;
  117. if (sourceFiber === null || sourceFiber.return === null) {
  118. prepareFreshStack(root, expirationTime);
  119. executionContext = prevExecutionContext;
  120. throw thrownValue;
  121. }
  122. if (enableProfilerTimer && sourceFiber.mode & ProfileMode) {
  123. stopProfilerTimerIfRunningAndRecordDelta(sourceFiber, true);
  124. }
  125. var returnFiber = sourceFiber.return;
  126. throwException(root, returnFiber, sourceFiber, thrownValue, renderExpirationTime);
  127. workInProgress = completeUnitOfWork(sourceFiber);
  128. }
  129. } while (true);
  130. executionContext = prevExecutionContext;
  131. resetContextDependencies();
  132. ReactCurrentDispatcher.current = prevDispatcher;
  133. if (enableSchedulerTracing) {
  134. __interactionsRef.current = prevInteractions;
  135. }
  136. if (workInProgress !== null) {
  137. // There's still work left over. Return a continuation.
  138. stopInterruptedWorkLoopTimer();
  139. if (expirationTime !== Sync) {
  140. startRequestCallbackTimer();
  141. }
  142. return renderRoot.bind(null, root, expirationTime);
  143. }
  144. }
  145. stopFinishedWorkLoopTimer();
  146. root.finishedWork = root.current.alternate;
  147. root.finishedExpirationTime = expirationTime;
  148. var isLocked = resolveLocksOnRoot(root, expirationTime);
  149. if (isLocked) {
  150. return null;
  151. }
  152. workInProgressRoot = null;
  153. switch (workInProgressRootExitStatus) {
  154. case RootIncomplete:
  155. {
  156. (function () {
  157. {
  158. {
  159. throw ReactError(Error('Should have a work-in-progress.'));
  160. }
  161. }
  162. })();
  163. }
  164. case RootErrored:
  165. {
  166. var _lastPendingTime = root.lastPendingTime;
  167. if (_lastPendingTime < expirationTime) {
  168. return renderRoot.bind(null, root, _lastPendingTime);
  169. }
  170. if (!isSync) {
  171. prepareFreshStack(root, expirationTime);
  172. scheduleSyncCallback(renderRoot.bind(null, root, expirationTime));
  173. return null;
  174. }
  175. return commitRoot.bind(null, root);
  176. }
  177. case RootSuspended:
  178. {
  179. flushSuspensePriorityWarningInDEV();
  180. var hasNotProcessedNewUpdates = workInProgressRootLatestProcessedExpirationTime === Sync;
  181. if (hasNotProcessedNewUpdates && !isSync &&
  182. !(true && flushSuspenseFallbacksInTests && IsThisRendererActing.current)) {
  183. var msUntilTimeout = globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
  184. if (msUntilTimeout > 10) {
  185. if (workInProgressRootHasPendingPing) {
  186. prepareFreshStack(root, expirationTime);
  187. return renderRoot.bind(null, root, expirationTime);
  188. }
  189. var _lastPendingTime2 = root.lastPendingTime;
  190. if (_lastPendingTime2 < expirationTime) {
  191. return renderRoot.bind(null, root, _lastPendingTime2);
  192. }
  193. root.timeoutHandle = scheduleTimeout(commitRoot.bind(null, root), msUntilTimeout);
  194. return null;
  195. }
  196. }
  197. return commitRoot.bind(null, root);
  198. }
  199. case RootSuspendedWithDelay:
  200. {
  201. flushSuspensePriorityWarningInDEV();
  202. if (!isSync &&
  203. !(true && flushSuspenseFallbacksInTests && IsThisRendererActing.current)) {
  204. if (workInProgressRootHasPendingPing) {
  205. prepareFreshStack(root, expirationTime);
  206. return renderRoot.bind(null, root, expirationTime);
  207. }
  208. var _lastPendingTime3 = root.lastPendingTime;
  209. if (_lastPendingTime3 < expirationTime) {
  210. return renderRoot.bind(null, root, _lastPendingTime3);
  211. }
  212. var _msUntilTimeout = void 0;
  213. if (workInProgressRootLatestSuspenseTimeout !== Sync) {
  214. _msUntilTimeout = expirationTimeToMs(workInProgressRootLatestSuspenseTimeout) - now();
  215. } else if (workInProgressRootLatestProcessedExpirationTime === Sync) {
  216. _msUntilTimeout = 0;
  217. } else {
  218. var eventTimeMs = inferTimeFromExpirationTime(workInProgressRootLatestProcessedExpirationTime);
  219. var currentTimeMs = now();
  220. var timeUntilExpirationMs = expirationTimeToMs(expirationTime) - currentTimeMs;
  221. var timeElapsed = currentTimeMs - eventTimeMs;
  222. if (timeElapsed < 0) {
  223. timeElapsed = 0;
  224. }
  225. _msUntilTimeout = jnd(timeElapsed) - timeElapsed;
  226. if (timeUntilExpirationMs < _msUntilTimeout) {
  227. _msUntilTimeout = timeUntilExpirationMs;
  228. }
  229. }
  230. if (_msUntilTimeout > 10) {
  231. root.timeoutHandle = scheduleTimeout(commitRoot.bind(null, root), _msUntilTimeout);
  232. return null;
  233. }
  234. }
  235. return commitRoot.bind(null, root);
  236. }
  237. case RootCompleted:
  238. {
  239. if (!isSync &&
  240. !(true && flushSuspenseFallbacksInTests && IsThisRendererActing.current) && workInProgressRootLatestProcessedExpirationTime !== Sync && workInProgressRootCanSuspendUsingConfig !== null) {
  241. var _msUntilTimeout2 = computeMsUntilSuspenseLoadingDelay(workInProgressRootLatestProcessedExpirationTime, expirationTime, workInProgressRootCanSuspendUsingConfig);
  242. if (_msUntilTimeout2 > 10) {
  243. root.timeoutHandle = scheduleTimeout(commitRoot.bind(null, root), _msUntilTimeout2);
  244. return null;
  245. }
  246. }
  247. return commitRoot.bind(null, root);
  248. }
  249. default:
  250. {
  251. (function () {
  252. {
  253. {
  254. throw ReactError(Error('Unknown root exit status.'));
  255. }
  256. }
  257. })();
  258. }
  259. }
  260. }

flushPassiveEffects

  1. function flushPassiveEffects() {
  2. // 变量rootWithPendingPassiveEffects 默认等于null
  3. if (rootWithPendingPassiveEffects === null) {
  4. return false;
  5. }
  6. var root = rootWithPendingPassiveEffects;
  7. var expirationTime = pendingPassiveEffectsExpirationTime;
  8. var renderPriorityLevel = pendingPassiveEffectsRenderPriority;
  9. rootWithPendingPassiveEffects = null;
  10. pendingPassiveEffectsExpirationTime = NoWork;
  11. pendingPassiveEffectsRenderPriority = NoPriority;
  12. var priorityLevel = renderPriorityLevel > NormalPriority ? NormalPriority : renderPriorityLevel;
  13. return runWithPriority$2(priorityLevel, flushPassiveEffectsImpl.bind(null, root, expirationTime));
  14. }

prepareFreshStack

  1. function prepareFreshStack(root, expirationTime) {
  2. // 初始化时 root.finishedWork = null,root.finishedExpirationTime = 0
  3. // NoWork = 0
  4. root.finishedWork = null;
  5. root.finishedExpirationTime = NoWork;
  6. // root.timeoutHandle 初始化时 = -1
  7. var timeoutHandle = root.timeoutHandle;
  8. // 常量 noTimeout = -1
  9. if (timeoutHandle !== noTimeout) {
  10. root.timeoutHandle = noTimeout;
  11. cancelTimeout(timeoutHandle);
  12. }
  13. // 初始化时 变量 workInProgress = null
  14. if (workInProgress !== null) {
  15. var interruptedWork = workInProgress.return;
  16. while (interruptedWork !== null) {
  17. unwindInterruptedWork(interruptedWork);
  18. interruptedWork = interruptedWork.return;
  19. }
  20. }
  21. // 初始化时 workInProgressRoot = null
  22. // Progress 进程 [ˈprəʊɡres]
  23. workInProgressRoot = root;
  24. workInProgress = createWorkInProgress(root.current, null, expirationTime);
  25. // expirationTime = 1073741823
  26. renderExpirationTime = expirationTime;
  27. // RootIncomplete = 0
  28. workInProgressRootExitStatus = RootIncomplete;
  29. // Sync = 1073741823
  30. workInProgressRootLatestProcessedExpirationTime = Sync;
  31. workInProgressRootLatestSuspenseTimeout = Sync;
  32. workInProgressRootCanSuspendUsingConfig = null;
  33. workInProgressRootHasPendingPing = false;
  34. // enableSchedulerTracing = true
  35. if (enableSchedulerTracing) {
  36. spawnedWorkDuringRender = null;
  37. }
  38. {
  39. /*
  40. ReactStrictModeWarnings = {
  41. discardPendingWarnings: ƒ ()
  42. flushLegacyContextWarning: ƒ ()
  43. flushPendingUnsafeLifecycleWarnings: ƒ ()
  44. recordLegacyContextWarning: ƒ (fiber, instance)
  45. recordUnsafeLifecycleWarnings: ƒ (fiber, instance)
  46. __proto__: Object
  47. }
  48. 11858行
  49. ReactStrictModeWarnings.discardPendingWarnings = function () {
  50. pendingComponentWillMountWarnings = [];
  51. pendingUNSAFE_ComponentWillMountWarnings = [];
  52. pendingComponentWillReceivePropsWarnings = [];
  53. pendingUNSAFE_ComponentWillReceivePropsWarnings = [];
  54. pendingComponentWillUpdateWarnings = [];
  55. pendingUNSAFE_ComponentWillUpdateWarnings = [];
  56. pendingLegacyContextWarning = new Map();
  57. };
  58. */
  59. ReactStrictModeWarnings.discardPendingWarnings();
  60. componentsThatTriggeredHighPriSuspend = null;
  61. }
  62. }

行数 23910 createWorkInProgress [prəˈɡres]

  1. function createWorkInProgress(current, pendingProps, expirationTime) {
  2. // current.alternate 默认为null
  3. var workInProgress = current.alternate;
  4. if (workInProgress === null) {
  5. // current.tag = 3
  6. // pendingProps = null
  7. // current.key = null
  8. // current.mode = 0
  9. workInProgress = createFiber(current.tag, pendingProps,
  10. current.key, current.mode);
  11. // current.elementType = null
  12. workInProgress.elementType = current.elementType;
  13. // current.type = null
  14. workInProgress.type = current.type;
  15. // current.stateNode = FiberRootNode
  16. workInProgress.stateNode = current.stateNode;
  17. {
  18. // DEV-only fields
  19. // current._debugID = 1;
  20. workInProgress._debugID = current._debugID;
  21. // current._debugSource = null;
  22. workInProgress._debugSource = current._debugSource;
  23. // current._debugOwner = null;
  24. workInProgress._debugOwner = current._debugOwner;
  25. // current._debugHookTypes = null;
  26. workInProgress._debugHookTypes = current._debugHookTypes;
  27. }
  28. workInProgress.alternate = current;
  29. current.alternate = workInProgress;
  30. } else {
  31. workInProgress.pendingProps = pendingProps;
  32. workInProgress.effectTag = NoEffect;
  33. workInProgress.nextEffect = null;
  34. workInProgress.firstEffect = null;
  35. workInProgress.lastEffect = null;
  36. if (enableProfilerTimer) {
  37. workInProgress.actualDuration = 0;
  38. workInProgress.actualStartTime = -1;
  39. }
  40. }
  41. // current.childExpirationTime = 0
  42. workInProgress.childExpirationTime = current.childExpirationTime;
  43. // current.expirationTime = 1073741823
  44. workInProgress.expirationTime = current.expirationTime;
  45. // current.child = null
  46. workInProgress.child = current.child;
  47. // current.memoizedProps = null;
  48. workInProgress.memoizedProps = current.memoizedProps;
  49. // current.memoizedState = null;
  50. workInProgress.memoizedState = current.memoizedState;
  51. // 重要 current.updateQueue = {firstUpdate, lastUpdate, ...}
  52. /*
  53. firstUpdate = {
  54. payload: {
  55. element: {$$typeof: Symbol(react.element), ...}
  56. , ...}
  57. , ...}
  58. lastUpdate的结构与firstUpdate一致
  59. */
  60. workInProgress.updateQueue = current.updateQueue;
  61. // current.dependencies = null
  62. var currentDependencies = current.dependencies;
  63. workInProgress.dependencies = currentDependencies === null ? null : {
  64. expirationTime: currentDependencies.expirationTime,
  65. firstContext: currentDependencies.firstContext,
  66. responders: currentDependencies.responders
  67. };
  68. // current.sibling = null;
  69. workInProgress.sibling = current.sibling;
  70. // current.index = 0;
  71. workInProgress.index = current.index;
  72. // current.ref = null;
  73. workInProgress.ref = current.ref;
  74. // 常量 enableProfilerTimer = true
  75. if (enableProfilerTimer) {
  76. // current.selfBaseDuration = 0;
  77. workInProgress.selfBaseDuration = current.selfBaseDuration;
  78. // current.treeBaseDuration = 0;
  79. workInProgress.treeBaseDuration = current.treeBaseDuration;
  80. }
  81. {
  82. // current._debugNeedsRemount = false;
  83. workInProgress._debugNeedsRemount = current._debugNeedsRemount;
  84. switch (workInProgress.tag) {
  85. case IndeterminateComponent: // 2
  86. case FunctionComponent: // 0
  87. case SimpleMemoComponent: // 15
  88. workInProgress.type = resolveFunctionForHotReloading(current.type);
  89. break;
  90. case ClassComponent: // 1
  91. workInProgress.type = resolveClassForHotReloading(current.type);
  92. break;
  93. case ForwardRef: // 11
  94. workInProgress.type = resolveForwardRefForHotReloading(current.type);
  95. break;
  96. default:
  97. break;
  98. }
  99. }
  100. return workInProgress;
  101. }

startWorkOnPendingInteractions

  1. function startWorkOnPendingInteractions(root, expirationTime) {
  2. // enableSchedulerTracing = true
  3. if (!enableSchedulerTracing) {
  4. return;
  5. }
  6. var interactions = new Set();
  7. // root.pendingInteractionMap.size = 0
  8. root.pendingInteractionMap.forEach(function (scheduledInteractions, scheduledExpirationTime) {
  9. if (scheduledExpirationTime >= expirationTime) {
  10. scheduledInteractions.forEach(function (interaction) {
  11. return interactions.add(interaction);
  12. });
  13. }
  14. });
  15. root.memoizedInteractions = interactions;
  16. if (interactions.size > 0) {
  17. var subscriber = __subscriberRef.current;
  18. if (subscriber !== null) {
  19. var threadID = computeThreadID(root, expirationTime);
  20. try {
  21. subscriber.onWorkStarted(interactions, threadID);
  22. } catch (error) {
  23. // If the subscriber throws, rethrow it in a separate task
  24. scheduleCallback(ImmediatePriority, function () {
  25. throw error;
  26. });
  27. }
  28. }
  29. }
  30. }

startWorkLoopTimer

  1. var formatMarkName = function (markName) {
  2. // reactEmoji = "⚛"
  3. return reactEmoji + ' ' + markName;
  4. };
  5. var beginMark = function (markName) {
  6. // performance 需要注意这个玩意是window.performance
  7. // performance 主要用来做性能监控,白屏时间,首屏时间,用户可操作时间节点,总下载时间...
  8. performance.mark(formatMarkName(markName));
  9. };
  10. var resumeTimers = function () {
  11. // currentFiber = FiberNode # renderRoot 传入 workInProgress
  12. if (currentFiber !== null) {
  13. resumeTimersRecursively(currentFiber);
  14. }
  15. };
  16. var resumeTimersRecursively = function (fiber) {
  17. // fiber.return = null
  18. if (fiber.return !== null) {
  19. resumeTimersRecursively(fiber.return);
  20. }
  21. // fiber._debugIsCurrentlyTiming = false
  22. if (fiber._debugIsCurrentlyTiming) {
  23. beginFiberMark(fiber, null);
  24. }
  25. };
  26. function startWorkLoopTimer(nextUnitOfWork) {
  27. // enableUserTimingAPI = true
  28. if (enableUserTimingAPI) {
  29. // nextUnitOfWork = FiberNode # renderRoot 传入 workInProgress
  30. currentFiber = nextUnitOfWork;
  31. // supportsUserTiming = true
  32. if (!supportsUserTiming) {
  33. return;
  34. }
  35. commitCountInCurrentWorkLoop = 0;
  36. beginMark('(React Tree Reconciliation)');
  37. // Resume any measurements that were in progress during the last loop.
  38. resumeTimers();
  39. }
  40. }

行数 22320 workLoopSync

  1. function workLoopSync() {
  2. // workInProgress = FiberNode
  3. while (workInProgress !== null) {
  4. workInProgress = performUnitOfWork(workInProgress);
  5. }
  6. }
  7. function performUnitOfWork(unitOfWork) {
  8. // The current, flushed, state of this fiber is the alternate. Ideally
  9. // nothing should rely on this, but relying on it here means that we don't
  10. // need an additional field on the work in progress.
  11. var current$$1 = unitOfWork.alternate;
  12. startWorkTimer(unitOfWork);
  13. setCurrentFiber(unitOfWork);
  14. var next = void 0;
  15. if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
  16. startProfilerTimer(unitOfWork);
  17. next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
  18. stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  19. } else {
  20. next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
  21. }
  22. resetCurrentFiber();
  23. unitOfWork.memoizedProps = unitOfWork.pendingProps;
  24. if (next === null) {
  25. // If this doesn't spawn new work, complete the current work.
  26. next = completeUnitOfWork(unitOfWork);
  27. }
  28. ReactCurrentOwner$2.current = null;
  29. return next;
  30. }