【问题标题】:React 16.13.1 - child component does not re-render when props changeReact 16.13.1 - 道具更改时子组件不会重新渲染
【发布时间】:2021-09-01 00:11:10
【问题描述】:

我有一个父组件和一个子组件。子组件最初将数据呈现到表单中,但在更改数据时,子组件不会更新。

父组件:

import React from 'react'
import { connect } from 'react-redux'
import styles from '../styles'
import ExpressionsForm from './expressionsForm'

class EditCondition extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      condition: null
    }

    this.updateExpression = this.updateExpression.bind(this)

    this.changes = false
  }

  componentWillMount () {
    let conditionid = this.props.data.id
    let condition = this.props.conditions.find(c => {
      return (c.id = conditionid)
    })
    this.setState({ condition })
  }

  updateExpression (e) {
    let expressionid = e.currentTarget.dataset.expressionid
    let field = e.currentTarget.dataset.field
    let value = e.target.value
    let condition = this.state.condition
    let expression = condition.expressions[expressionid]
    expression[field] = value
    condition.expressions[expressionid] = expression
    this.changes = true
    this.setState({ condition })
    console.log('updateExpression condition: ', condition)
  }

  render () {
    let condition = this.state.condition
    if (!this.state.condition) {
      return (
        <div>
          The selected condition with ID "{this.props.data.id}" did not load. It
          may not exist. Refresh and try again.
        </div>
      )
    }

    let groupOptions = this.props.gambitGroups.map(g => {
      return (
        <option value={g.id} key={'group' + g.id}>
          {g.name}
        </option>
      )
    })

    console.log('RENDER editCondition: ', condition) // <-- Note: This always logs as expected

    let expressionsJSX = condition.expressions.map((expression, i) => {
      expression.id = i
      console.log('expression: ', expression) // <-- Note: This always logs as expected
      return (
        <ExpressionsForm
          key={'expressionsForm_' + i}
          expression={expression}
          deleteExpression={this.deleteExpression}
          updateExpression={this.updateExpression}
          updateExpressionData={this.updateExpressionData}
        />
      )
    })

    return (
           <table>
             <thead>
               <tr>
                 <th {...styles.modal.tableHeaderLeftAlign}>
                   Device &amp; Data Point
                 </th>
                 <th {...styles.modal.tableHeaderLeftAlign}>Operator</th>
                 <th {...styles.modal.tableHeaderLeftAlign}>Value</th>
                 <th {...styles.modal.tableHeaderLeftAlign}>PlateValue</th>
                 <th {...styles.modal.tableHeaderLeftAlign}>&nbsp;</th>
               </tr>
             </thead>
             <tbody>{expressionsJSX}</tbody>
           </table>
            
    )
  }
}

export default connect(
  (state, ownProps) => ({
    user: state.user,
    users: state.users,
    gambitGroups: state.gambitGroups,
    // deviceGroups: state.deviceGroups,
    conditions: state.conditions,
    reactions: state.reactions,
    setEditMode: ownProps.setEditMode,
    navByName: ownProps.navByName
  }),
  dispatch => ({
    addImage: file => dispatch({ type: 'UPDATE_CONDITION_LOGO', file }),
    updateCondition: condition =>
      dispatch({ type: 'UPDATE_CONDITION', condition })
  })
)(EditCondition)

和子组件:

import React from 'react'
import { connect } from 'react-redux'
import styles from '../styles'

class ExpressionsForm extends React.Component {
  constructor (props) {
    super(props)
    this.state = {}

    this.updateExpression = this.updateExpression.bind(this)
  }

  updateExpression (e) {
    this.props.updateExpression(e)
  }

  render () {
    let expression = this.props.expression
    console.log('expression: ', expression) // Note: logs initial render only.
    let data = expression.data
    let deviceId = data.deviceId
    let dataPointIndex = data.dataPointIndex
    let operator = expression.operator
    let plateValue = expression.plateValue
    let value = expression.value

    console.log('RENDER expressionForm: ', expression) // Note: logs initial render only

    let deviceOptions = this.props.devices.map((device, i) => {
      return (
        <option value={device.id} key={'device_' + i}>
          {device.userAssignedName}
        </option>
      )
    })

    let dataPointOptions = this.props.devices[0].inputs.map((input, i) => {
      return (
        <option value={input.id} key={'input_' + i}>
          {input.name} currentValue: {input.value}
        </option>
      )
    })

    let operatorOptions = ['==', '!=', '<=', '>=', '<', '>'].map(
      (operator, i) => {
        return (
          <option value={operator} key={'operator_' + i}>
            {operator}
          </option>
        )
      }
    )

    return (
      <tr>
        <td>
          <select
            {...styles.modal.inputSexy}
            style={{ marginBottom: '20px' }}
            data-field='deviceid'
            data-expressionid={expression.id}
            value={deviceId}
            onChange={this.updateExpressionData}
          >
            <option value=''></option>
            {deviceOptions}
          </select>
          <select
            {...styles.modal.inputSexy}
            data-field='dataPointIndex'
            data-expressionid={expression.id}
            value={dataPointIndex}
            onChange={this.updateExpressionData}
          >
            <option value=''></option>
            {dataPointOptions}
          </select>
        </td>
        <td>
          <select
            {...styles.modal.inputSexy}
            style={{ width: '75px' }}
            data-field='operator'
            data-expressionid={expression.id}
            value={operator}
            onChange={this.updateExpression}
          >
            <option value=''></option>
            {operatorOptions}
          </select>
        </td>
        <td>
          <input
            {...styles.modal.inputSexy}
            style={{ width: '50px' }}
            data-field='value'
            data-expressionid={expression.id}
            value={value}
            onChange={this.updateExpression}
          />
        </td>
        <td>
          <input
            {...styles.modal.inputSexy}
            style={{ width: '88px' }}
            data-expressionid={expression.id}
            data-field='plateValue'
            value={plateValue}
            onChange={this.updateExpression}
          />
        </td>
        <td>
          <i className='fa fa-close'
            data-expressionid={expression.id}
            onClick={this.deleteExpression}
          ></i>
          &nbsp;
        </td>
      </tr>
    )
  }
}

export default connect(
  (state, ownProps) => ({
    user: state.user,
    users: state.users,
    devices: state.devices,
    gambitGroups: state.gambitGroups,
    // deviceGroups: state.deviceGroups,
    conditions: state.conditions,
    reactions: state.reactions,
    setEditMode: ownProps.setEditMode,
    navByName: ownProps.navByName
  }),
  dispatch => ({
    addImage: file => dispatch({ type: 'UPDATE_XXX', file })
  })
)(ExpressionsForm)

我在 redux 存储中有一个对象数组,称为条件。父组件获取这些条件之一的 ID,找到正确的条件,并通过 componentWillMount 将其加载到状态以供用户修改。条件上有一个对象数组,称为表达式。这些表达式中的每一个都被传递给名为 ExpressionsForm 的子组件。

所以我们通过 map 函数遍历表达式并将生成的 JSX 作为表达式JSX 返回。

let expressionsJSX = condition.expressions.map((expression, i) => {
          expression.id = i
          console.log('expression: ', expression) // <-- Note: This always logs as expected
          return (
            <ExpressionsForm
              key={'expressionsForm_' + i}
              expression={expression}
              deleteExpression={this.deleteExpression}
              updateExpression={this.updateExpression}
              updateExpressionData={this.updateExpressionData}
            />
          )
        })

请注意,已将表达式传递给它 expression={expression}

在子组件的渲染中你会看到

    let expression = this.props.expression
    console.log('expression: ', expression) // Note: logs initial render only.

由于这是一个道具,无论是控制台记录还是渲染到某个 JSX 中都无关紧要 - 当道具更改时,更改也应该重新渲染。但在这种情况下它没有这样做。为什么?

例如,我在 1 个条件下保存了 1 个表达式。它呈现时,我单击表达式的 plateValue 输入字段 - 默认情况下包含 5 - 并尝试在 5 之后添加 6。当父组件更新状态时,我会在 console.log 中看到重新呈现表达式的 plateValue 字段现在包含一个“56”……它只是不在子组件中呈现……!?

这是一个示例 console.log

初始渲染:

RENDER editCondition: {id: "1", group: 1, name: "Temperature >= 75F", 元:“如果温室中> = 75F,则打开交流电,直到温度低于5度 75F",表达式:Array(1)} editCondition.jsx:191 表达式:{data: {…},运算符:>=,值:“75”,plateValue:“5”,id:0} expressionForm.jsx:39 RENDER expressionForm: {data: {…}, operator: ">=",值:"75",板值:"5",id:0}

点击进入plateValue字段并添加一个'6',父级重新渲染......并且:

editCondition.jsx:188 RENDER editCondition: {id: "1", group: 1, name: “温度 >= 75F”,meta:“如果温室中 >= 75F,打开交流电直到 比 75F 低 5 度”,表达式:Array(1)} editCondition.jsx:191 表达式:{数据:{…},运算符:">=",值: "75", plateValue: "56", id: 0} editCondition.jsx:153 状态设置! updateExpression条件:{id:“1”,组:1,名称:“温度> = 75F”,meta:“如果温室 >= 75F,打开空调,直到 5 度冷却 大于 75F",表达式:Array(1)}

我在其中看到一个“plateValue:“56””。那么为什么不重新渲染 子组件?好困惑。

我已经尝试过 componentWillReceiveProps、componentWillUpdate 等。我什至无法让这些触发 console.log。

发生了一些我无法弄清楚的事情。我已经做 React 很长时间了,我很困惑。这种情况不再经常发生了。

提前感谢您的帮助

PS 我确实看过 getDerivedStateFromProps - 文档提供了示例非常好,但它们没有解释 props 和 state 参数实际上是什么。文档很烂。他们的解释很烂。他们的例子并没有说明它实际上做了什么。我只使用 componentWillReceiveProps 来知道道具何时发生变化,然后更新状态或其他什么。 getDerivedStateFromProps 只是让我感到困惑。尽管如此,我还是玩弄了它,也无法让它工作。

【问题讨论】:

  • 唯一可行的方法是执行let ex = Object.assign({},expression) 并将ex 作为值传递给表达式道具。为什么这似乎有效?
  • 你能做一个只关注有问题的代码部分的最小示例吗?

标签: javascript reactjs


【解决方案1】:

看起来同一个expression object 一直在传递。

React 在决定渲染时检查组件接收到的props 是否有更改。它发现props项没有任何变化,它们都是和以前一样的对象,并得出结论,子组件不需要重新渲染。它不会对每个道具的所有属性进行深入检查。

这也解释了为什么可以通过复制表达式对象来强制重新渲染。副本始终是一个新对象,因此会导致重新渲染,无论其内容是否已更改。

您可以通过复制或将expression 对象分解为其属性,然后将其中的每一个作为单独的props 提供给子对象,从而避免这种情况。

作为最后一点,也可以通过将其作为expression={{...expression}} 传递来制作副本。

【讨论】:

  • 这是有道理的——它不是检查深层变化。所以我的“hack”有效,但我想每次父组件重新渲染时它都会重新渲染。 expression={{...expression}} 我想同样的事情。因此,如果数据不太复杂,也许像您提到的那样将数据分成不同的部分可能是理想的。就我而言,它是一个简单的组件,额外的重新渲染没什么大不了的。感谢您的意见!
猜你喜欢
  • 1970-01-01
  • 2021-12-04
  • 2021-07-13
  • 1970-01-01
  • 1970-01-01
  • 2020-05-01
  • 1970-01-01
  • 2020-12-24
  • 2017-07-22
相关资源
最近更新 更多