7

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

 2 years ago
source link: https://segmentfault.com/a/1190000041355015
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

开门见山,先来看一张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)。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK