一、&&隐藏大坑
在 JSX
里写条件语句,&&
应该是用的最多的了,例如:
function Demo () { // ...省略一些代码 return ( <div> { isShow && <Child/> } </div> ) }
这样写确实非常简单易懂,但也存在隐藏的踩坑点,那就是 &&
逻辑运算符的工作原理
&& 逻辑运算符工作原理: 例如 A && B
,当 A
隐式转换后为 true
时,则返回 B
;当 A
隐式转换后为 false
时,则返回 A
举个例子🌰:
const A = 0 const B = 1 const 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.children if (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.props return ( <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> ) }
如果本意就是不想让两个组件实例不停卸载和挂载,那么就不需要做额外操作了~
End
今天的分享就结束啦~ 希望本文对你们有所帮助,也希望你们不要吝啬自己的点赞👍🏻嗷~
我是零一,分享技术,不止前端!我们下期见!