react源码学习(2)

继上一篇react源码学习(1)

创建FiberRoot和rootFiber

//render调用方法
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>, // mount是null
  children: ReactNodeList, // ReactElement
  container: Container,  //容器
  forceHydrate: boolean,  
  callback: ?Function, //回叼
) {


  const maybeRoot = container._reactRootContainer;
  let root: FiberRoot;
  if (!maybeRoot) {
    // Initial mount //初始化mount
    root = legacyCreateRootFromDOMContainer(
      container,
      children,
      parentComponent,
      callback,
      forceHydrate,
    );
  } else {
    // 第二次mount的时候
    root = maybeRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(root);
        originalCallback.call(instance);
      };
    }
    // Update
    updateContainer(children, root, parentComponent, callback);
  }
  return getPublicRootInstance(root);
}

  • legacyRenderSubtreeIntoContainer是render最后调用的方法,他会判断当前容器是否有挂载属性,有的话就直接服用老的FiberRoot,然后调用updateContainer方法开启调度,比如wbepack热更新。
  • 对于第一次,会调用legacyCreateRootFromDOMContainer方法,创建FiberRoot
function legacyCreateRootFromDOMContainer(
  container: Container,
  initialChildren: ReactNodeList,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
  isHydrationContainer: boolean,
): FiberRoot {
  if (isHydrationContainer) {

  } else {
    // First clear any existing content.
    let rootSibling;
    // 创建FiberRoot
    const root = createContainer(
      container,
      LegacyRoot,
      null, // hydrationCallbacks
      false, // isStrictMode
      false, // concurrentUpdatesByDefaultOverride,
      '', // identifierPrefix
      noopOnRecoverableError, // onRecoverableError
      null, // transitionCallbacks
    );
    
    // 容器上挂载root
    container._reactRootContainer = root;

    // Initial mount should not be batched.
    // 初始化渲染优先级比较高,不应该不应分批处理。
    flushSync(() => {
      // 进入调度阶段了
      updateContainer(initialChildren, root, parentComponent, callback);
    });

    return root;
  }
}

可以看到这个方法就是创建FiberRoot然后挂载到容器上,最后调用flushSync调用updateContainer调度。flushSync表示以同步到方式执行updateContainer。因为初始化渲染优先级比较高。
那么createContainer就是用来创建FiberRoot和rootFiber的
createConinter会调用createFiberRoot

export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onRecoverableError: null | ((error: mixed) => void),
  transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
  // FiberRoot
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError,
  ): any);
  
  // RootFiber
  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );

  //FiberRoot和rooFiber联系
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;


    const initialState: RootState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: (null: any), // not enabled yet
      transitions: null,
    };
// rootFiber的初始化state
  uninitializedFiber.memoizedState = initialState;
  

  // 初始化RootFiber的updateQueue
  initializeUpdateQueue(uninitializedFiber);

  return root;
}

createFiberRoot主要做了

  • 1 创建FIberRoot和rootFiber并且联系起来
  • 2 初始化rootFiber的state
  • 3 初始化rootFiber的updateQueue
创建任务对象并存储于任务队列

上面说到updateContainer会开启调度。
它会创建一个update任务,插入到rootFiber上,并且开启调度。

export function updateContainer(
 element: ReactNodeList, // <App/>
  container: OpaqueRoot,  // FiberRoot
  parentComponent: ?React$Component<any, any>, // null
  callback: ?Function,  ){
// current就是RootFiber
  const current = container.current;
  // 获取当前react应用初始化的时间
  const eventTime = requestEventTime();
  // 获取优先级
  const lane = requestUpdateLane(current);
  
// 创建update
  const update = createUpdate(eventTime, lane);
  // 对于HostRoot,update的payload就是element
  update.payload = {element};
  
 // 将update插入fiber.updateQueue上
  enqueueUpdate(current, update, lane);
  // 开启调度
  const root = scheduleUpdateOnFiber(current, lane, eventTime);
  if (root !== null) {
    entangleTransitions(root, current, lane);
  }
  return lane;
}
  • 创建update
  • 插入到fiber.updateQueue.shard.pending上
  • 调用scheduleUpdateOnFiber开启调度
执行任务前的准备工作

上面已经创建了update,放入了任务队列之中,现在应该来执行任务了。updateConitnaer调用了schedlueUpdateOnFiber来开启调度。
这个方法

// 创建Update之后,就需要开启调度更新了。
// 做的事情:
// 1: 通过markUpdateLaneFromFiberToRoot找到rootFiber
// 2: 找到rootFiber之后,调用ensureRootIsScheduled开始调度
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number
){

  /**
   * 判断是否hi无限循环的update,如果是就报错。
   * 比如在componentWillUpdate或者componentDidupdate生命周期中重复调用setState方法,就会发生这种情况。
   * react限制了嵌套更新的数量防止无限制更新,限制的嵌套数量是50
   */
  checkForNestedUpdates();

  // 遍历找到rootFiber
  const root = markUpdateLaneFromFiberToRoot(fiber, lane);
  if (root === null) {
    return null;
  }
   // 开始调度
  ensureRootIsScheduled(root, eventTime);
}

主要做了两件事情

  • 通过fiber找到rootFiber
  • 调用ensureRootIsScheduled开始调度
ensureRootIsScheduled

顾名思义,确保root正在调度。
ensureRootIsScheduled这个方法是关键。他是react自己实现的一个优先级的调度函数。

  • 判断当前调度任务的优先级以及是否有正在调度的任务,有就判断两者优先级,优先级相同,则不做处理,优先级不同,打断当前的调度,开启新的调度,优先执行优先级高的任务
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  //正在工作的任务
  const existingCallbackNode = root.callbackNode;
 //当前调度的任务的优先级
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );
   // 如果当前调度的任务优先级是NoLanes,不需要调度,直接刷新全局变量,并且取消当前的工作的任务
  if (nextLanes === NoLanes) {
    // Special case: There's nothing to work on.
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }
 // 获取此次任务的Priority
  const newCallbackPriority = getHighestPriorityLane(nextLanes);
  // 获取当前正在执行的任务的优先级
  const existingCallbackPriority = root.callbackPriority;
  if ( existingCallbackPriority === newCallbackPriority){
	return;
}
 //如果不一样,并且存在正在工作的任务,取消当前正在工作的任务
  if (existingCallbackNode != null) {
    // Cancel the existing callback. We'll schedule a new one below.
    cancelCallback(existingCallbackNode);
  }
}
   
  • 第二则是通过判断当前调度任务的优先级,同步的话调用performSyncWorkOnRoot,异步的话调用performConcurrentWorkOnRoot。
  // 调度一个新的任务
  let newCallbackNode;

  // 判断当前调度的任务是同步还是异步
  if (newCallbackPriority === SyncLane) {
	scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
	}else {
	 // 异步调度,scheduleCallback的返回值就是当前注册的任务newTask
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root)
    );
}
 root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
	

走到这里表示了当前调度的任务是最高优先级的,所以,他会把当前最新的任务重新挂载在root上,注意,这个跟另一个函数performConcurrentWorkOnRoot配合,实现react自己任务优先级的调度。

  • 我们现在只需要了解ensureRootIsSchedule主要就是用来判断当前是否有更高优先级的任务,有的话就停止当前任务,创建新的调度

构建workInprogress Fiber树中的rootFiber

当面说到执行performConcurrentWorkOnRoot函数,他是render阶段的入口。
performConcurrentWorkOnRoot会根据当前任务是否过期,决定调用renderRootConcurrent或者renderRootSync,这两个函数会返回一个状态,performConcurrentWorkOnRoot根据这个状态来决定是否进入commit阶段。还是开启新的一轮调度。

function performConcurrentWorkOnRoot(root, didTimeout) {
let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);

}

因为performConcurretnWorkOnRoot是通过Scheduler调度的,所以她会接受一个didTimeout参数,表示当前帧是否有剩余时间。
重点看下renderRootConcurrent
renderRootConcurrent会创建workInprogress fiber树的rootFiber,
然后调用workLoopConcurrent真正去执行任务。

function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
// 当wokrInprogress不等于root,就要创建workInprogress
if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
 // 构建workInporgressFiber 树及rootFiber
    prepareFreshStack(root, lanes);
}

  do {
    try {
      // 真正执行任务
      workLoopConcurrent();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
}

prepareFreshStack用于创建workInprogress Fiber的rootFiber。

function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
  root.finishedWork = null; // finishWork标识render阶段完成后构建待提交的对象
  root.finishedLanes = NoLanes; //初始化优先级
  if (workInProgress !== null) {
    let interruptedWork = workInProgress.return;
    while (interruptedWork !== null) {
      const current = interruptedWork.alternate;
      unwindInterruptedWork(
        current,
        interruptedWork,
        workInProgressRootRenderLanes
      );
      interruptedWork = interruptedWork.return;
    }
  }
 
  // 构建workInprogress的FiberRoot
  workInProgressRoot = root;
  // 构建rootFiber
  const rootWorkInProgress = createWorkInProgress(root.current, null);
  workInProgress = rootWorkInProgress;
  workInProgressRootRenderLanes =
    subtreeRenderLanes =
    workInProgressRootIncludedLanes =
      lanes;
  workInProgressRootExitStatus = RootInProgress;
  workInProgressRootFatalError = null;
  workInProgressRootSkippedLanes = NoLanes;
  workInProgressRootInterleavedUpdatedLanes = NoLanes;
  workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
  workInProgressRootPingedLanes = NoLanes;
  workInProgressRootConcurrentErrors = null;
  workInProgressRootRecoverableErrors = null;

  return rootWorkInProgress;
}

可以看到,调用createWorkInProgress就是来创建workInprogress fiber树的rootFiber。

workLoopConcurrent方法解析

上面说到,workLoopConcurrent是真正执行任务的方法,

function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}
  • 我们刚刚才创建了workInprogress fiber的rootFiber赋值给workInProgress ,shouldYild是判断当前帧是否还有多余时间让他执行performUnitOfWork。
  • react16可以中断的最小粒度就是fiber,每一帧至少会执行一个fiebr的调度。当shouldYield返回true的时候,表示该中断了,得把线权交给浏览器了。
  • 然后退出循环。renderRootConcurrent会返回一个退出状态给performConcurrentWorkOnRoot,performConcurrentWorkOnRoot会决定要不要进入commit阶段。
  • 如果任务还没结束,performConcurrentWorkOnRoot不会进入commit阶段,反而会继续调用ensureRootIsScheduled方法,就是我们前面说的可以来判断当前是否有更高优先级的任务,如果没有,那么performConcurrentWorkOnRoot就会继续返回当前的任务。而schedulerCallback执行的时候会以当前任务的返回值决定该任务是否需要继续调度,需要的话就下一帧继续执行。

performUnitOfWork解析

上面说到。workLoopConcurrent会调用performUnitOfWork去调度fiber。那么performUnitOfWork是怎么处理fiber的呢?

function performUnitOfWork(unitOfWork: Fiber): void {
  const current = unitOfWork.alternate;

  let next;
  next = beginWork(current, unitOfWork, subtreeRenderLanes);
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  
  if (next === null) {
    // 进入归阶段
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}
  • 参数就是当前调度的fiber,对于第一次,这就是workInprogress fiber的rootFiber。
  • beginWork,递阶段,处理fiber,创建子fiber。从父到子
  • 如果next === null,调用completeUnitOfWork,归阶段,从子到父。
  • react16通过循环来模拟15的递归处理阶段。beginWork就像是递阶段,completeUnitOfWork就像是归阶段。但他们是可以中断的。

当所有fiber处理完成之后,表示render阶段完成,要进入commit阶段了。

render阶段进入commit阶段。

workLoopConcurrent执行完毕之后,会判断workInProgress是否等于null。如果等于null,表示Reconciler工作完成,要开启commit阶段了。

function renderRootConcurrent(root: FiberRoot, lanes: Lanes){
  // 构建workInporgressFiber 树及rootFiber
    prepareFreshStack(root, lanes);

	 do {
    try {
      // 真正执行任务
      workLoopConcurrent();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  
// Check if the tree has completed.
// 判断是否render完毕
  if (workInProgress !== null){
    return RootInProgress;
  } else {
    // Set this to null to indicate there's no in-progress render.
    workInProgressRoot = null;
    workInProgressRootRenderLanes = NoLanes;

    // Return the final exit status.
    return workInProgressRootExitStatus;
  }
}

如上,renderRootConcurrent会返回一个状态,RooInprogress就表示当前还在调度之中,而当workInprogress为null,就返回workInProgressRootExitStatus退出的状态。
而我们知道performConcurrentWorkOnRoot会根据renderRootConcurrent返回的状态决定是否进入commit阶段

 function performConcurrentWorkOnRoot(root, didTimeout){

	let exitStatus = shouldTimeSlice
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);
 if (exitStatus !== RootInProgress) {
	...
	 // 将构建好的rootfiber存储到FiberRoot上
      root.finishedWork = finishedWork;
      root.finishedLanes = lanes;
	 //完成了render阶段之后,开启commit阶段
      finishConcurrentRender(root, exitStatus, lanes);
}


  // 每次执行完performConcurrentWorkOnRoot都会调用ensureRootIsScheduled来判断当前是否有更高优先级的任务需要调度
  ensureRootIsScheduled(root, now());
  //如果没有更高优先级或者当前任务就是最高优先级的,继续返回该任务
  if (root.callbackNode === originalCallbackNode) {
    // performConcurrentWorkOnRoot是ScheduleCallback注册的函数,而ScheduleCallback执行的时候,需要通过返回来确定该任务是否继续执行
    // 这里通过ensureRootIsScheduled调度之后,发现root上面挂载的任务还是当前这个任务,表示当前的任务依然是最高优先级的。
    // 所以,需要返回当前的任务给ScheduleCallback,以表示当前任务依然是最高优先级,需要执行。
    return performConcurrentWorkOnRoot.bind(null, root);
  }
  //当调用ensureRootIsScheduled调度之后,如果有更高优先级的,或者任务都执行完毕了,那么这里返回null给scheduleCallback
  // 表示当前任务已经结束,当Schedule执行注册的函数performConcurrentWorkOnRoot,结果是Null的时候,他会认为该任务已经结束。
  // 会将该任务从最小堆中取出,然后继续调度,看有没有更高优先级的任务,注意,Schedule和React里面有各自的调度系统
  return null;
}

如上,performConcurrentWorkOnRoot,在确定退出状态是结束状态的时候,就会调用finishConcurrentRender,顾名思义,已经完成了并发的render工作。
而finishConcuurentRender的工作就是

function finishConcurrentRender(root, exitStatus, lanes) {
  switch (exitStatus) {
    case RootInProgress:
    case RootFatalErrored: {
      throw new Error("Root did not complete. This is a bug in React.");
    }
    ....
    case RootCompleted: {
      // The work completed. Ready to commit.
      commitRoot(root, workInProgressRootRecoverableErrors);
      break;
    }
    default: {
      throw new Error("Unknown root exit status.");
    }
  }
}

根据传入的状态,调用commitRoot函数,开启commit阶段,commit阶段是不可中断的。

commitRoot

commitRoot是commit阶段的入口

function commitRoot(root: FiberRoot, recoverableErrors: null | Array<mixed>) {
  const previousUpdateLanePriority = getCurrentUpdatePriority(); // 获取当前优先级保存
  const prevTransition = ReactCurrentBatchConfig.transition;

  try {
    ReactCurrentBatchConfig.transition = null;
    setCurrentUpdatePriority(DiscreteEventPriority); // 修改当前优先级
    // 因为commit阶段是不可被打断的,所以优先级绝对是最高的
    commitRootImpl(root, recoverableErrors, previousUpdateLanePriority);
  } finally {
    ReactCurrentBatchConfig.transition = prevTransition;
    setCurrentUpdatePriority(previousUpdateLanePriority); //恢复之前的优先级
  }

  return null;
}

因为commit阶段是不可被打断的,所以执行commit阶段之前,优先级要设为最高优先级。

commitRootImpl

commitRootImpl是commit阶段主要执行的函数,来看看他做了什么

function commitRootImpl(
  root: FiberRoot,
  recoverableErrors: null | Array<mixed>,
  renderPriorityLevel: EventPriority
) {

 // --------before-mutation-之前的阶段-start-------
 const finishedWork = root.finishedWork; // rootFiber
  const lanes = root.finishedLanes; //优先级
   // 重置FiberRoot的属性
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
    // 重置变量
  root.callbackNode = null;
  root.callbackPriority = NoLane;

//调度useEffect
 if (
    (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
    (finishedWork.flags & PassiveMask) !== NoFlags
  ) {
    if (!rootDoesHavePassiveEffects) {
      rootDoesHavePassiveEffects = true;
      pendingPassiveEffectsRemainingLanes = remainingLanes;
      scheduleCallback(NormalSchedulerPriority, () => { // 以普通优先级调度useEffect
        flushPassiveEffects();
        // This render triggered passive effects: release the root cache pool
        // *after* passive effects fire to avoid freeing a cache pool that may
        // be referenced by a node in the tree (HostRoot, Cache boundary etc)
        return null;
      });
    }
  }


 // -----------------beforeMutation阶段------------------
    const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
      root,
      finishedWork
    );

// ------------mutation阶段------------------
    // The next phase is the mutation phase, where we mutate the host tree.
    commitMutationEffects(root, finishedWork, lanes);
}

//  ------------layout阶段----------------
    commitLayoutEffects(finishedWork, root, lanes);

 // -------------------layout之后--start-------------------
  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
 if (rootDoesHavePassiveEffects) { // 有useEffect的effects
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root; // 赋值全局变量
    pendingPassiveEffectsLanes = lanes;
  }
...

  ensureRootIsScheduled(root, now());
  
  // If layout work was scheduled, flush it now.
  flushSyncCallbacks();

..

主要做了6件事情

  • 2 before-mutation之前的阶段,开始执行dom操作之前,将所有effects执行完毕。 全局变量重置,调度useEffect
  • 3 before-mutation阶段,调用commitBeforeMutationEffects 执行dom操作之前
  • 4 mutation阶段,调用commitMutationEffects 执行dom操作
  • 5 mutation阶段之后,layout阶段之前,切换fiber树,root.current = finishWork
  • 5 layout阶段, 调用commitLayoutEffects 执行dom操作之后
  • 6 layout之后阶段,如果有useEffect的effects,就赋值给全局变量rootWithPendingPassiveEffects,useEffect的调度函数通过上面 去获取effectLists,执行对应的useEffects函数。调用ensureRootIsScheduled最后判断还有没有更新没执行

before-mutation阶段

commit阶段的第一个子阶段,此时dom还没更新,主要调用类组件的getSnapshotBeforeUpdate生命周期函数。

// finishWork表示当前执行的fiber
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
 const current = finishedWork.alternate; //获取rooFiber的alternate也就是current rootFiber
  const flags = finishedWork.flags; //获取rootFiber的effectTag
  if ((flags & Snapshot) !== NoFlags) {
    // falgs有事情要做
	 switch (finishedWork.tag) {    
	 case ClassComponent: {
        if (current !== null) {
          const prevProps = current.memoizedProps; //旧的props
          const prevState = current.memoizedState; //旧的state
          const instance = finishedWork.stateNode; //获取类组件的实例   
          // 调用类组件的getSnapshotBeforeUpdate函数
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
 // 将返回的快照值保存在实例的__reactInternalSnapshotBeforeUpdate上,到时候赋值给componentDidUpdate
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
        break;
      }
}
}
}

主要就调用了getSnapshotBeforeUpdate函数,并且将返回值放在了instance上的__reactInternalSnapshotBeforeUpdate

mutation阶段

mutation阶段主要是执行dom操作

  • 获取effectTag,如果有ref相关的effectTag,处理ref
  • 判断effectTag类型,如果是新增获取非组件父节点和兄弟节点,判断调用insert或者是append方法。对于组件,则直接循环将儿子都插入到dom上。
  • 对于update,函数组建的话,直接执行useLayoutEffect和useEffect的销毁函数。类组件的话,调用componentWillUnMount

layout阶段

layout阶段主要是dom操作完成之后的事情。
主要就是调用函数组件的hooks和类组件的生命周期函数

  • 类组件,调用componentDidMoutn或者componentDidUpdate,如果render有第三个参数回调函数,也会在这里执行,表示react已经挂载完毕了。
  • 函数组件,调用useLayoutEffect的create函数,这里注意,useLayoutEffect是同步执行的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderlin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值