深圳幻海软件技术有限公司 欢迎您!

我老板:你根本不懂 React!

2023-02-28

前言我已经使用React多年,我确信我非常了解它,但最近我的老板对我说,“你根本不知道React,你对它一无所知。”我很生他的气,但他指出了我程序中的三个漏洞。我现在把它记录下来,也分享给还不知道的小伙伴。1、你知道“&&”的用法吗?在React程序中,我经常使用“&&amp

前言

我已经使用 React 多年,我确信我非常了解它,但最近我的老板对我说,“你根本不知道 React,你对它一无所知。”

我很生他的气,但他指出了我程序中的三个漏洞。我现在把它记录下来,也分享给还不知道的小伙伴。

1、你知道“&&”的用法吗?

在React程序中,我经常使用“&&”运算符来决定是否显示内容,具体方式如下:

const App = () => {
  const [ list, setList ] = useState([])

  // Simulation request data
  setTimeout(() => {
    setList([ 'fatfish', 'medium' ])
  }, 2000)

  return (
    <div className="app">{ list.length && <List /> }</div>
  )
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

我老板:“你不知道&&”运算符的特点吗?当请求还没有成功返回时,会直接渲染“0”。

我不服气,因为我一直都是这样写代码,从来没有犯过错误。为了证明老大错了,我写了下面的例子。


const List = ({ list = [] }) => {
  return (
    <div className="name-list-container">
      {
        list.map((name) => {
          return <div className="name-list-item">{ name }</div>
        })
      }
    </div>
  )
}

const App = () => {
  const [ list, setList ] = React.useState([ ])

  // Simulation request data
  setTimeout(() => {
    setList([ 'fatfish', 'medium' ])  
  }, 3000)

  return (
    list.length && <List list={ list }/>
  )
}

ReactDOM.render(<App />, document.getElementById('app'))
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

我的天啊!老大说的对,一开始页面显示0,3秒后显示列表。

为什么?

来自 MDN的提示:“当且仅当所有操作数都为真时,一组布尔操作数的逻辑与 (&&) 运算符(逻辑合取)才为真。否则就是假的。”

更一般地,运算符返回从左到右计算时遇到的第一个假操作数的值,或者如果它们都是真值,则返回最后一个操作数的值。

例子如下:

const x1 = 0
const x2 = 'fatfish'
const x3 = 1
const x4 = 'medium'
console.log(x1 && x2) // 0
console.log(x3 && x4) // medium
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

现在我终于明白为什么写这样的代码会导致错误。原因如下:

list.length && <List list={ list } /> 
0 && <List list={ list } /> // 0
  • 1.
  • 2.

如何解决?

我找到了三种方法来解决这个问题。我希望你不要犯和我一样的错误,祝福你。


// 1. Convert list.length to boolean
!!list.length && <List list={ list }/>

// 2. Use ternary expressions and null
list.length ? <List list={ list }/> : null

// 3. Controlled by specific logic
list.length >= 1 && <List list={ list }/>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

2.“props.children”的奇怪行为

我猜你写过类似的代码。当向 <Container /> 组件传递内容时,会显示“children”。如果没有,将显示一个空的工具提示。像下面这样:

const Container = ({ children }) => {
  if (children) {
    return (
      <div className="children-container">
        <p>The content of children is:</p>
        { children }
      </div>
    ) 
  } else {
    return (
      <div className="empty">empty</div>
    )
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

我的老板:“你要小心使用‘children’属性,它会导致逻辑异常!就像在以下情况中一样。”

1).清空列表数据

你认为这个例子会显示什么——“空”?

不幸的是,答案是另一个。你是不是也觉得不可思议?朋友们,我们一定要非常小心地使用 props.children。否则,老板可能会扣你的工资。


const Container = ({ children }) => {
  if (children) {
    return (
      <div className="children-container">
        <p>The content of children is:</p>
        { children }
      </div>
    ) 
  } else {
    return (
      <div className="empty">empty</div>
    )
  }
}
const App = () => {
  const [ list, setList ] = React.useState([])

  return (
    <Container>
      {
        list.map((name) => {
          return <div className="name-item">{ name }</div>  
        })
      }
    </Container>
  )
}
ReactDOM.render(<App />, document.getElementById('app'))
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

为什么?

让我们向“Container”组件添加一行代码,并尝试打印children是什么!


const Container = ({ children }) => {
  console.log(children, 'children')
  // ...
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

是的,你是对的。此时“children”为空数组,所以显示“children的内容为:”而不是“empty”。

如何解决?

使用 React.Children.toArray 解决这个问题会很容易,然后你会看到显示“empty”。所以如果你真的需要用children作为条件判断,我建议你使用这个方法!

const Container = ({ children }) => {
  // if (children) {
  // Pay attention here
  if (React.Children.toArray(children).length) {  
    return (
      <div className="children-container">
        <p>The content of children is:</p>
        { children }
      </div>
    ) 
  } else {
    return (
      <div className="empty">empty</div>
    )
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

3.关于挂载和更新的问题

在 React 中通过状态来切换组件是很常见的,但是,这个小东西也会让你感到困惑。

在下面的代码中,你认为当你切换name的值时,一个Demo组件会被卸载,另一个会被挂载吗?


class Demo extends React.Component {
  componentDidMount() {
    console.log('componentDidMount', this.props.name);
  }
  componentDidUpdate() {
    console.log('componentDidUpdate', this.props.name);
  }

  render () {
    return (
      <div>
        { this.props.name }
      </div>
    )
  }
}
const App = () => {
  const [ name, setName ] = React.useState('fatfish')
  const onClick = () => {
    setName(name === 'fatfish' ? 'medium' : 'fatfish')
  }
  return (
    <div className="app">
      {
        name === 'fatfish' ? 
          <Demo name={ name } /> : 
          <Demo name={ name } />
      }
      <button onClick={ onClick }>click</button>
    </div>
  )
}
ReactDOM.render(<App />, document.getElementById('app'))
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.

我录制了一个简短的 gif 给你真相。

你也可以通过 CodePen 试试,https://codepen.io/qianlong/pen/NWywodV

为什么?

虽然,我们写了两个 Demo 组件,假设它们会分别挂载和更新,但 React 认为它们是同一个组件,所以 componentDidMount 只会执行一次。

如何解决?

但是当我们要写两个相同的组件但是传递不同的参数时,我们应该怎么办呢?

是的,你应该为这两个组件添加不同的键,这样 React 就会认为它们是不同的组件。componentDidMount 也会单独执行。

我们试试看:

//...
// Pay attention here
name === 'fatfish' ? <Demo key="1" name={ name } /> : <Demo key="2" name={ name } />
//...
  • 1.
  • 2.
  • 3.
  • 4.

你也可以通过 CodePen 试试,https://codepen.io/qianlong/pen/NWywodV。