Wenzi

React 模板中为什么可以用逻辑与运算符

蚊子前端博客
发布于 2024/09/03 17:21
在 React 模板中,若表达式结果为false时,为什么不渲染数据呢?

我们在开发 React 项目时,经常会用到逻辑与&&的运算符。

const App = () => {
  const [isShow, setShow] = useState(false);

  // 当 isShow 为 true 时,才会渲染 <div>show me</div>
  return <div>{isShow && <div>show me</div>}</div>;
};

逻辑与运算符&&,在 JavaScript 中,它的作用是,如果前面的表达式为真,则返回后面的表达式,否则返回前面的表达式。

可是这里有个问题:对于完整的逻辑与表达式,若isShow为 false 时,则返回 false,为什么当前区域展示内容是空的,而不是展示false呢?

这其实跟 React 的渲染机制有关。我们在之前的文章React18 源码解析之 reconcileChildren 生成 fiber 的过程提到过。这里再稍微介绍下。

/**
 * 将returnFiber节点(即当前的workInProgress对应的节点)里的element结构转为fiber结构
 * @param returnFiber 当前的workInProgress对应的fiber节点
 * @param currentFirstChild current 树上对应的当前 Fiber 节点的第一个子 Fiber 节点,可能为null
 * @param newChild returnFiber中的element结构,用来构建returnFiber的子节点
 * @param lanes
 * @returns {Fiber|*}
 */
function reconcileChildFibers(
  returnFiber: Fiber, // 当前 Fiber 节点,即 workInProgress
  currentFirstChild: Fiber | null,
  newChild: any,
  lanes: Lanes // 优先级相关
): Fiber | null {
  // 是否是顶层的没有key的fragment组件
  const isUnkeyedTopLevelFragment =
    typeof newChild === "object" &&
    newChild !== null &&
    newChild.type === REACT_FRAGMENT_TYPE &&
    newChild.key === null;

  // 若是顶层的fragment组件,则直接使用其children
  if (isUnkeyedTopLevelFragment) {
    newChild = newChild.props.children;
  }

  // Handle object types
  // 判断该节点的类型
  if (typeof newChild === "object" && newChild !== null) {
    /**
     * newChild是Object,再具体判断 newChild 的具体类型。
     * 1. 是普通React的函数组件、类组件、html标签等
     * 2. portal类型;
     * 3. lazy类型;
     * 4. newChild 是一个数组,即 workInProgress 节点下有并排多个结构,这时 newChild 就是一个数组
     * 5. 其他迭代类型,我暂时也不确定这哪种?
     */
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
        // 一般的React组件,如<App />或<p></p>等
        return placeSingleChild(
          // 调度单体element结构的元素
          reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes)
        );
      case REACT_PORTAL_TYPE:
        return placeSingleChild(
          reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes)
        );
      case REACT_LAZY_TYPE:
        const payload = newChild._payload;
        const init = newChild._init;
        // TODO: This function is supposed to be non-recursive.
        return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
    }

    if (isArray(newChild)) {
      // 若 newChild 是个数组
      return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
    }

    if (getIteratorFn(newChild)) {
      return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, lanes);
    }

    throwOnInvalidObjectType(returnFiber, newChild);
  }

  if ((typeof newChild === "string" && newChild !== "") || typeof newChild === "number") {
    // 文本节点
    return placeSingleChild(
      reconcileSingleTextNode(returnFiber, currentFirstChild, "" + newChild, lanes)
    );
  }

  // Remaining cases are all treated as empty.
  // 若没有匹配到任何类型,说明当前newChild无法转为fiber节点,如boolean类型,null等是无法转为fiber节点的
  // deleteRemainingChildren()的作用是删除 returnFiber 节点下,第2个参数传入的fiber节点,及后续所有的兄弟节点
  // 如 a->b->c->d-e,假如我们第2个参数传入的是c,则删除c及后续的d、e等兄弟节点,
  // 而这里,第2个参数传入的是 currentFirstChild,则意味着删除returnFiber节点下所有的子节点
  // 为什么要删除呢?这是因为,为了保证前后两棵树是一致的,若jsx在workInProgress所在树中,无法转为fiber节点,
  // 说明 returnFiber 下所有的fiber节点均无法复用
  return deleteRemainingChildren(returnFiber, currentFirstChild);
}

从上面的部分源码中可以看到,在 React 解析 jsx 时,它只会解析一些固定类型的节点,如:

  • 函数组件类型的;
  • 类组件类型;
  • html 标签类型的;
  • 纯文本类型的,如字符串或数字格式;
  • portal 类型的;
  • lazy 类型的;
  • 数组类型(这个会递归解析每一项);

而诸如 null, undefined, boolean 等类型的节点,则会直接跳过。因此,在一些比较复杂的判断中,有些不希望展示在页面中的,则可以返回这些类型的值。

针对开头样例中的逻辑与运算符,当isShow为 false 时,React 会跳过该数据的渲染,因此这里就会展示为空。

因此,若一个表达式的结果 boolean 类型时,不论是 true 还是 false,都不会转为 html,而是直接跳过。

标签:react
阅读(130)
Simple Empty
No data