之前寫的JSX的條件語句竟然存在那麼多Bug?

語言: CN / TW / HK

大家好,我是 零一 ,今天的主題是: 關於 JSX 的條件語句,你不知道3件事

一、&&隱藏大坑

JSX 裡寫條件語句, && 應該是用的最多的了,例如:

function Demo () {
// ...省略一些程式碼
return (
<div>
{
isShow && <Child/>
}
</div>

)
}

這樣寫確實非常簡單易懂,但也存在隱藏的踩坑點,那就是 && 邏輯運算子的工作原理

&& 邏輯運算子工作原理: 例如 A && B ,當 A 隱式轉換後為 true 時,則返回 B ;當 A 隱式轉換後為 false 時,則返回 A

舉個例子:chestnut::

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 就出現在了頁面中

這一定不是你想要的,下面提出一些解決方案和建議吧:

  1. 用三元運算子,即 list.length ? <List data={list} /> : null
  2. 兩次取反,即 !!list.length && <List data={list} />
  3. 直接給出具體的判斷邏輯,即 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 傳過來的每個元素都放到一個數組中再返回,並會過濾掉 空陣列Booleanundefined

所以我們剛才的例子中,空陣列直接被過濾掉了。我們再來驗證一下 React.Children.toArray 的強大,舉個例子:chestnut:

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

今天的分享就結束啦~ 希望本文對你們有所幫助,也希望你們不要吝嗇自己的點贊:+1|type_1_2:嗷~

我是 零一 ,分享技術,不止前端!我們下期見!

我是 零一 ,分享技術,不止前端,下期見~

最後,歡迎加我的微信,拉你進 上百人的前端交流群

分享

收藏

點贊

在看