Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React的零渲染问题及源码分析 #255

Open
FrankKai opened this issue Jan 28, 2022 · 0 comments
Open

React的零渲染问题及源码分析 #255

FrankKai opened this issue Jan 28, 2022 · 0 comments
Labels

Comments

@FrankKai
Copy link
Owner

FrankKai commented Jan 28, 2022

开门见山,先来看一张bug图(状态下面有个00)。
image
预期是:状态为0时,2个组件不做渲染。
现状:状态为0时,2个组件不做渲染,但是渲染出了00。

  • 零渲染 bug 代码
  • 如何修复零渲染问题
  • 初窥源码
  • 源码疑惑
  • 原因总结
  • 源码实锤

零渲染 bug 代码

什么是React的零渲染问题?
看下下面这段代码,我们会经常这样写:

// bug代码 0
{obj?.count && <span>{obj?.count}</span>}

假如obj?.count为0,渲染结果为0。
这里不就1个0么,上面为什么是00呢。

// bug代码 00 
{obj?.countFoo && <span>{obj?.countFoo}</span>}
{obj?.countBar && <span>{obj?.countBar}</span>}

当obj?.countFoo和obj?.countBar都为0时,渲染结果为00。

如何修复零渲染问题

{!!obj?.count && <span>{obj?.count}</span>}

或者

{obj?.count ? <span>{obj?.count}</span> : null}

或者

{Boolean(obj?.count) && <span>{obj?.count}</span>}

初窥源码

原因(点击类型查看源码):
React组件会渲染string,number。不会渲染nullundefinedboolean

源码疑惑

既然boolean会被处理为null,那为什么true && <FooComponent />可以正常渲染呢?
先说结论,因为进行了&&运算,React最终渲染的是jsx与计算后的结果。

const type = typeof children;
if (type === 'undefined' || type === 'boolean') {
    // All of the above are perceived as null.
    children = null;
}

也就是说 此处的children,是jsx计算后的结果。

举例如下:

// 可渲染值
1 && <FooComponent /> // => jsx计算结果为<FooComponent />,因此渲染<FooComponent/>
"a string" && <FooComponent /> // => jsx计算结果为<FooComponent />,因此渲染<FooComponent />
0 && <FooComponent /> // => jsx计算结果为0,Renders '0'
true && <FooComponent /> // => jsx计算结果为<FooComponent />,因此渲染<FooComponent />

// 不可渲染值
false && <FooComponent /> // => jsx计算结果为false,因此什么都不渲染
null && <FooComponent /> // => jsx计算结果为null,因此什么都不渲染
undefined && <FooComponent /> // => jsx计算结果为undefined,因此什么都不渲染

原因总结

其实,根本不是React渲染什么的问题,而是&&操作符后返回值的问题。
所以,最根本是因为

  • React渲染string,number,正常组件
  • React不渲染undefined,boolean,null
{"1"} // 渲染为"1"
{0} // 渲染为0
{<FooComponent />} // 假设为正常组件,渲染为<FooComponent />

{undefined} // 不渲染
{true} // 不渲染
{false} // 不渲染

{null} // 不渲染

源码实锤

  const type = typeof children;

  // React不渲染undefined,boolean
  if (type === 'undefined' || type === 'boolean') {
    // All of the above are perceived as null.
    children = null;
  }

  let invokeCallback = false;

  if (children === null) {
    invokeCallback = true;
  } else {
    switch (type) {
      case 'string':
      case 'number':
        // React渲染string,number
        invokeCallback = true; 
        break;
      case 'object':
        // React渲染正常组件
        switch ((children: any).$$typeof) {
          case REACT_ELEMENT_TYPE:
          case REACT_PORTAL_TYPE:
            invokeCallback = true;
        }
    }
  }

原始值为null,和undefined以及boolean最终被处理为null,React不渲染null的源码实锤呢

  render(
    child: ReactNode | null,
    context: Object,
    parentNamespace: string,
  ): string {
    if (typeof child === 'string' || typeof child === 'number') {
      const text = '' + child;
      if (text === '') {
        return '';
      }
      this.previousWasTextNode = true;
      return escapeTextForBrowser(text);
    } else {
      let nextChild;
      ({child: nextChild, context} = resolve(child, context, this.threadID));
      // React不渲染null
      if (nextChild === null || nextChild === false) {
        return '';
      }

对于html标签渲染空字符串而言,空字符串会被渲染,例如<div>""</div>会被渲染为<div>""</div>
但对于react而言,完整流程为{null} =>{""} => nothing
例如下面这样:

<div>{''}</div> // => <div></div>
<div>{'    '}</div> // => <div>    </div>

那么,React是如何处理空字符串的呢?

export function pushTextInstance(
  target: Array<Chunk | PrecomputedChunk>,
  text: string,
  responseState: ResponseState,
): void {
  if (text === '') {
    // Empty text doesn't have a DOM node representation and the hydration is aware of this.
    // 这句注释的意思是:空文本节点没有DOM节点表示,它属于textNode
    return;
  }
  // TODO: Avoid adding a text separator in common cases.
  target.push(stringToChunk(encodeHTMLTextNode(text)), textSeparator);
}

从源码我们可以看到,对于空文本节点,React会直接return出去,不会去生成文本实例(TextInstance)。

@FrankKai FrankKai added the React label Jan 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant