作者 | 零一
来源 | 前端印象
今天的主题是:关于 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 射击游戏,好玩到停不下来
点分享
点收藏
点点赞
点在看