簡體   English   中英

React 16.13.1 - 道具更改時子組件不會重新渲染

[英]React 16.13.1 - child component does not re-render when props change

我有一個父組件和一個子組件。 子組件最初將數據呈現到表單中,但在更改數據時,子組件不會更新。

父組件:

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 將其加載到 state 以供用戶修改。 條件上有一個對象數組,稱為表達式。 這些表達式中的每一個都傳遞給名為 ExpressionsForm 的子組件。

所以我們通過 map function 循環表達式並將生成的 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。當父組件更新 state 時重新呈現,我在 console.log 中看到表達式的 plateValue 字段現在包含一個“56”……它只是不在子組件中呈現……??

這是一個示例 console.log

初始渲染:

渲染editCondition:{id:“1”,組:1,名稱:“溫度> = 75F”,元:“如果溫室> = 75F,打開AC直到5度低於75F”,表達式:Array(1)} editCondition.jsx:191 表達式:{數據:{…},運算符:>=",值:"75",plateValue:"5",id:0} 表達式Form.jsx:39 RENDER 表達式:{數據:{… },運算符:>=,值:“75”,plateValue:“5”,id:0}

單擊 plateValue 字段並添加“6”,父級重新渲染...並且:

editCondition.jsx:188 RENDER editCondition: {id: "1", group: 1, name: "Temperature >= 75F", meta: "If >= 75F in溫室打開AC直到5度低於75F",表達式: Array(1)} editCondition.jsx:191 表達式:{data: {…}, operator: ">=", value: "75", plateValue: "56", id: 0} editCondition.jsx:153 STATE SET: updateExpression 條件:{id,“1”:組,1:名稱,“溫度 >= 75F”:元,“如果溫室中 >= 75F,則打開空調直到比 75F 低 5 度”:表達式:Array(1)}

我在那里看到一個'plateValue:“56”'。 那么為什么不在子組件中重新渲染呢? 如此迷茫。

我已經嘗試過 componentWillReceiveProps、componentWillUpdate 等。 我什至無法讓這些觸發console.log。

發生了一些我無法弄清楚的事情。 我已經做 React 很長時間了,我很困惑。 這不再經常發生了。

在此先感謝您的幫助

PS 我確實看過 getDerivedStateFromProps - 文檔提供了示例很好,但他們沒有解釋 props 和 state 參數實際上是什么。 文檔很爛。 他們的解釋很爛。 他們的例子並沒有說明它實際上做了什么。 我只使用 componentWillReceiveProps 來知道道具何時發生變化,然后更新 state 或其他什么。 getDerivedStateFromProps 只是讓我感到困惑。 盡管如此,我還是玩弄了它,也無法讓它工作。

視覺示例

看起來同一個expression object一直在傳遞。

React 在決定渲染時會檢查組件接收到的props是否有變化。 發現所有的props項都沒有改變,都是和之前一樣的對象,得出子組件不需要重新渲染的結論。 它不會對每個道具的所有屬性進行深入檢查。

這也解釋了為什么可以通過復制表達式 object 來強制重新渲染。 副本始終是新的 object,因此會導致重新渲染,無論其內容是否已更改。

您可以通過制作副本或將expression object 分解為其屬性,然后將其中的每一個作為單獨的props提供給孩子來避免這種情況。

最后一點,也可以通過將其作為expression={{...expression}}傳遞來制作副本。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM