React 16.13.1 - 当 props 改变时子组件不会重新渲染

时间:2021-06-16 07:25:56

标签: javascript reactjs

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

父组件:

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 存储中有一个名为 Condition 的对象数组。父组件获取这些条件之一的 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.

因为这是一个 prop,它是否被 console.log'd 或渲染到一些 JSX 并不重要 - 当 prop 更改时,更改也应该重新渲染。但在这种情况下它没有这样做。为什么?

例如,我在 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: ">=", value: "75",plateValue: "5", id: 0}

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

editCondition.jsx:188 RENDER editCondition: {id: "1", group: 1, name: “温度 >= 75F”,元:“如果温室中的温度 >= 75F 打开空调,直到 比 75F 低 5 度”,表达式:Array(1)} editCondition.jsx:191 表达式:{data: {…}, operator: ">=", value: "75", plateValue: "56", id: 0} editCondition.jsx:153 状态设置! 更新表达式条件:{id: "1", group: 1, name: "Temperature >= 75F”,元:“如果温室中的温度 >= 75F 打开空调,直到温度降低 5 度 比 75F",表达式:Array(1)}

我在那里看到一个 'plateValue: "56"'。那么为什么不重新渲染 子组件?好纠结。

我尝试过 componentWillReceiveProps、componentWillUpdate 等。我什至无法让它们触发 console.log。

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

预先感谢您的帮助

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

Visual Example

1 个答案:

答案 0 :(得分:1)

看起来一直在传入相同的 expression object

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

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

您可以像以前一样避免这种情况,方法是制作一个副本,或者将 expression 对象分解为它的属性,然后将每个属性作为单独的 props 提供给子对象。

>

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

相关问题