我们在开发 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,而是直接跳过。