查看原文
其他

之前写的JSX的条件语句竟然存在那么多Bug?

脚本之家 2022-09-23

The following article is from 前端印象 Author 零一

 关注脚本之家”,与百万开发者在一起

来源 | 前端印象 (ID:zero2one01)
已获得原公众号的授权转载

大家好,我是零一,今天的主题是:关于 JSX 的条件语句,你不知道3件事

一、&&隐藏大坑

在 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 就出现在了页面中

这一定不是你想要的,下面提出一些解决方案和建议吧:

  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 的强大,举个例子🌰

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>

  )
}

如果本意就是不想让两个组件实例不停卸载和挂载,那么就不需要做额外操作了~

  推荐阅读:

终于!我找到程序员爱穿卫衣的原因了

5分钟教你用nodeJS手写一个mock数据服务器

手写简易前端框架:vdom 渲染和 jsx 编译

深夜里,程序员最喜欢去的网站竟然是 ...

每日打卡赢积分兑换书籍入口

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存