
作者 | 零一
来源 | 前端印象
今天的主题是:关于 JSX 的条件语句,你不知道3件事
一、&&隐藏大坑
在 JSX 里写条件语句,&& 应该是用的最多的了,例如:
function Demo () {// ...省略一些代码return (<div>{isShow && <Child/>}</div>)
}这样写确实非常简单易懂,但也存在隐藏的踩坑点,那就是 &&逻辑运算符的工作原理
&& 逻辑运算符工作原理: 例如 A && B ,当 A 隐式转换后为 true 时,则返回 B;当 A 隐式转换后为 false 时,则返回 A
举个例子🌰:
const A = 0
const B = 1const C = A && B // 0
const D = B && A // 0所以有一种场景下,我们用 && 符号做条件判断渲染会有问题:有一个列表,当有列表数据时,展示列表里的内容;当没有列表数据时,则什么都不展示
function List () {//...
}function App () {const [list, setList] = useState([])useEffect(() => {// 请求列表数据// ...}, [])return (<div>{list.length && <List data={list} />}</div>)
}代码看起来没什么问题,逻辑也说得通(当 list 有具体数据时,展示 <List/> 组件),但其实是有问题的,此时页面长这个样:

为什么? 这就是刚才提到的 && 的工作原理了,当咱们未请求数据前,list = [] ,即 list.length = 0,那么 list.length && <List data={list} /> 最终返回的就是 0 了,所以自然而然的 0 就出现在了页面中
这一定不是你想要的,下面提出一些解决方案和建议吧:
用三元运算符,即
list.length ? <List data={list} /> : null两次取反,即
!!list.length && <List data={list} />直接给出具体的判断逻辑,即
list.length > 0 && <List data={list} />
当然了,如果判断条件本来就是布尔值的话,那就可以忽略这一条了
二、Children作判断条件
在某些场景下我们可能会写一个组件来处理逻辑,例如:
function Wrap (props) {if (props.children) {return (<div><p>当前内容为:</p><div>{props.children}</div></div>)} else {return (<div>nothing</div>)}
}function App () {return (<Wrap><div>零一</div></Wrap>)
}这段代码看起来也是毫无问题(当有传递给 <Wrap/> 组件 children 属性时,直接展示内容;否则展示 nothing ,表示当前为空),但其实存在很多漏洞情况,例如:
function App () {return (<Wrap>{list.map(item => <span>{item}</span>)}</Wrap>)
}假设此时变量list 为 [] ,那么 Wrap 组件中接收到的 children 则也为 [],那么 if (props.children) 的判断结果也为 true,则页面会这样展示:

这显然不是我们想要的结果。我们想要的效果是:当接收到空数组时,也展示 nothing ,即为空
有什么解决方案呢?
React 提供了现成的用于处理 children的 API:
React.Children.map
React.Children.forEach
React.Children.count
React.Children.only
React.Children.toArray
这里就不一一介绍每个的作用了,想要了解的可以直接去官网看:https://zh-hans.reactjs.org/docs/react-api.html#reactchildren
我们直接挑重点说,可以直接用 React.Children.toArray 来做处理,该方法可以把 children 统一变成数组的形式
还是用刚才的那个例子,我们改造一下看看返回了什么:
import { Children } from 'react'function Wrap (props) {// 用 Children.toArray 来处理 props.childrenif (Children.toArray(props.children).length) {return (<div><p>当前内容为:</p><div>{props.children}</div></div>)} else {return (<div>nothing</div>)}
}function App () {return (<Wrap>{ // 返回空数组[].map(item => <span>{item}</span>)}</Wrap>)
}此时页面展示的是:

为什么会这样呢?打个断点进去看了一下 React.Children.toArray 大致都做了什么处理,这里简单总结一下:将 children 传过来的每个元素都放到一个数组中再返回,并会过滤掉空数组、Boolean、undefined
所以我们刚才的例子中,空数组直接被过滤掉了。我们再来验证一下 React.Children.toArray 的强大,举个例子🌰
function App () {return (<Wrap>{false && <span>作者:零一</span>}{true}{ // 返回空数组[].map(item => <span>{item}</span>)}{{}?.name}</Wrap>)
}这种情况,<Wrap/> 组件接收到的 children 值应为:
[false,true,[],undefined,
]那么页面展示的是什么呢?

是的,还是nothing,因为这四种情况的值全都被 React.Children.toArray 给过滤掉了,最终返回的值为 [] ,这也十分符合我们开发时的预期
所以如果你真的需要把 children 作为条件判断的依据的话,我建议是用这个方法!
三、挂载与更新
三元运算符在 JSX 中经常被我们拿来用于两种不同状态的组件切换,例如:
import { Component, useState } from 'react'class Child extends Component {componentDidMount() {console.log('挂载', this.props.name, this.props.age);}componentDidUpdate() {console.log('更新', this.props.name, this.props.age);}render () {const { name, age } = this.propsreturn (<div><p>{name}</p><p>{age}</p></div>)}
}function App () {const [year, setYear] = useState('1999')return (<div>{ year === '1999' ? <Child name="零一" age={1} />: <Child name="01" age={23} />}<button onClick={() => {setYear(year === '1999' ? '2022' : '1999')}}>切换</button></div>)
}看到这个代码,你是不是觉得当变量 year 切换时,一个组件会卸载,另一个组件会挂载?但其实不是,我们来验证一下:

可以看到,我们在切换了变量 year 时,<Child/> 组件只挂载了一次,而不是不停地挂载、卸载。其实这是React做的处理,虽然写了两个 <Child/> 组件,但React只认为是一个,并直接进行更新,即上述代码等价于:
// ... 省略大部分代码
function App () {// ...return (<div><Child name={year === '1999' ? "零一" : "01"} age={year === '1999' ? 1 : 23} />// ...</div>)
}这种情况需要特别注意,当你真的想写两次同一个组件并传递不同的参数时,你可以给这两个组件赋予不同的 key ,那么React就不会认为它俩是同一个组件实例了,例如:
function App () {// ...return (<div>{ year === '1999' ? <Child name="零一" age={1} key="0"/>: <Child name="01" age={23} key="1"/>}</div>)
}如果本意就是不想让两个组件实例不停卸载和挂载,那么就不需要做额外操作了~

往期推荐
好难啊……一个 try-catch 问出这么多花样
k8s集群居然可以图形化安装了?
用了HTTPS,没想到还是被监控了
将 k8s 制作成 3D 射击游戏,好玩到停不下来

点分享

点收藏

点点赞

点在看