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

Joh*_*ohn 2 javascript reactjs

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

\n

父组件:

\n
import React from \'react\'\nimport { connect } from \'react-redux\'\nimport styles from \'../styles\'\nimport ExpressionsForm from \'./expressionsForm\'\n\nclass EditCondition extends React.Component {\n  constructor (props) {\n    super(props)\n    this.state = {\n      condition: null\n    }\n\n    this.updateExpression = this.updateExpression.bind(this)\n\n    this.changes = false\n  }\n\n  componentWillMount () {\n    let conditionid = this.props.data.id\n    let condition = this.props.conditions.find(c => {\n      return (c.id = conditionid)\n    })\n    this.setState({ condition })\n  }\n\n  updateExpression (e) {\n    let expressionid = e.currentTarget.dataset.expressionid\n    let field = e.currentTarget.dataset.field\n    let value = e.target.value\n    let condition = this.state.condition\n    let expression = condition.expressions[expressionid]\n    expression[field] = value\n    condition.expressions[expressionid] = expression\n    this.changes = true\n    this.setState({ condition })\n    console.log(\'updateExpression condition: \', condition)\n  }\n\n  render () {\n    let condition = this.state.condition\n    if (!this.state.condition) {\n      return (\n        <div>\n          The selected condition with ID "{this.props.data.id}" did not load. It\n          may not exist. Refresh and try again.\n        </div>\n      )\n    }\n\n    let groupOptions = this.props.gambitGroups.map(g => {\n      return (\n        <option value={g.id} key={\'group\' + g.id}>\n          {g.name}\n        </option>\n      )\n    })\n\n    console.log(\'RENDER editCondition: \', condition) // <-- Note: This always logs as expected\n\n    let expressionsJSX = condition.expressions.map((expression, i) => {\n      expression.id = i\n      console.log(\'expression: \', expression) // <-- Note: This always logs as expected\n      return (\n        <ExpressionsForm\n          key={\'expressionsForm_\' + i}\n          expression={expression}\n          deleteExpression={this.deleteExpression}\n          updateExpression={this.updateExpression}\n          updateExpressionData={this.updateExpressionData}\n        />\n      )\n    })\n\n    return (\n           <table>\n             <thead>\n               <tr>\n                 <th {...styles.modal.tableHeaderLeftAlign}>\n                   Device &amp; Data Point\n                 </th>\n                 <th {...styles.modal.tableHeaderLeftAlign}>Operator</th>\n                 <th {...styles.modal.tableHeaderLeftAlign}>Value</th>\n                 <th {...styles.modal.tableHeaderLeftAlign}>PlateValue</th>\n                 <th {...styles.modal.tableHeaderLeftAlign}>&nbsp;</th>\n               </tr>\n             </thead>\n             <tbody>{expressionsJSX}</tbody>\n           </table>\n            \n    )\n  }\n}\n\nexport default connect(\n  (state, ownProps) => ({\n    user: state.user,\n    users: state.users,\n    gambitGroups: state.gambitGroups,\n    // deviceGroups: state.deviceGroups,\n    conditions: state.conditions,\n    reactions: state.reactions,\n    setEditMode: ownProps.setEditMode,\n    navByName: ownProps.navByName\n  }),\n  dispatch => ({\n    addImage: file => dispatch({ type: \'UPDATE_CONDITION_LOGO\', file }),\n    updateCondition: condition =>\n      dispatch({ type: \'UPDATE_CONDITION\', condition })\n  })\n)(EditCondition)\n
Run Code Online (Sandbox Code Playgroud)\n

和子组件:

\n
import React from \'react\'\nimport { connect } from \'react-redux\'\nimport styles from \'../styles\'\n\nclass ExpressionsForm extends React.Component {\n  constructor (props) {\n    super(props)\n    this.state = {}\n\n    this.updateExpression = this.updateExpression.bind(this)\n  }\n\n  updateExpression (e) {\n    this.props.updateExpression(e)\n  }\n\n  render () {\n    let expression = this.props.expression\n    console.log(\'expression: \', expression) // Note: logs initial render only.\n    let data = expression.data\n    let deviceId = data.deviceId\n    let dataPointIndex = data.dataPointIndex\n    let operator = expression.operator\n    let plateValue = expression.plateValue\n    let value = expression.value\n\n    console.log(\'RENDER expressionForm: \', expression) // Note: logs initial render only\n\n    let deviceOptions = this.props.devices.map((device, i) => {\n      return (\n        <option value={device.id} key={\'device_\' + i}>\n          {device.userAssignedName}\n        </option>\n      )\n    })\n\n    let dataPointOptions = this.props.devices[0].inputs.map((input, i) => {\n      return (\n        <option value={input.id} key={\'input_\' + i}>\n          {input.name} currentValue: {input.value}\n        </option>\n      )\n    })\n\n    let operatorOptions = [\'==\', \'!=\', \'<=\', \'>=\', \'<\', \'>\'].map(\n      (operator, i) => {\n        return (\n          <option value={operator} key={\'operator_\' + i}>\n            {operator}\n          </option>\n        )\n      }\n    )\n\n    return (\n      <tr>\n        <td>\n          <select\n            {...styles.modal.inputSexy}\n            style={{ marginBottom: \'20px\' }}\n            data-field=\'deviceid\'\n            data-expressionid={expression.id}\n            value={deviceId}\n            onChange={this.updateExpressionData}\n          >\n            <option value=\'\'></option>\n            {deviceOptions}\n          </select>\n          <select\n            {...styles.modal.inputSexy}\n            data-field=\'dataPointIndex\'\n            data-expressionid={expression.id}\n            value={dataPointIndex}\n            onChange={this.updateExpressionData}\n          >\n            <option value=\'\'></option>\n            {dataPointOptions}\n          </select>\n        </td>\n        <td>\n          <select\n            {...styles.modal.inputSexy}\n            style={{ width: \'75px\' }}\n            data-field=\'operator\'\n            data-expressionid={expression.id}\n            value={operator}\n            onChange={this.updateExpression}\n          >\n            <option value=\'\'></option>\n            {operatorOptions}\n          </select>\n        </td>\n        <td>\n          <input\n            {...styles.modal.inputSexy}\n            style={{ width: \'50px\' }}\n            data-field=\'value\'\n            data-expressionid={expression.id}\n            value={value}\n            onChange={this.updateExpression}\n          />\n        </td>\n        <td>\n          <input\n            {...styles.modal.inputSexy}\n            style={{ width: \'88px\' }}\n            data-expressionid={expression.id}\n            data-field=\'plateValue\'\n            value={plateValue}\n            onChange={this.updateExpression}\n          />\n        </td>\n        <td>\n          <i className=\'fa fa-close\'\n            data-expressionid={expression.id}\n            onClick={this.deleteExpression}\n          ></i>\n          &nbsp;\n        </td>\n      </tr>\n    )\n  }\n}\n\nexport default connect(\n  (state, ownProps) => ({\n    user: state.user,\n    users: state.users,\n    devices: state.devices,\n    gambitGroups: state.gambitGroups,\n    // deviceGroups: state.deviceGroups,\n    conditions: state.conditions,\n    reactions: state.reactions,\n    setEditMode: ownProps.setEditMode,\n    navByName: ownProps.navByName\n  }),\n  dispatch => ({\n    addImage: file => dispatch({ type: \'UPDATE_XXX\', file })\n  })\n)(ExpressionsForm)\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

因此,我们通过 map 函数循环表达式,并将生成的 JSX 作为表达式JSX 返回。

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

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

\n

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

\n
    let expression = this.props.expression\n    console.log(\'expression: \', expression) // Note: logs initial render only.\n
Run Code Online (Sandbox Code Playgroud)\n

由于这是一个 prop,无论它是被 console.log 还是渲染到某些 JSX 中都无关紧要 - 当 prop 更改时,更改也应该重新渲染。但在这种情况下它没有这样做。为什么?

\n

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

\n

这是一个 console.log 示例

\n
\n

初始渲染:

\n

RENDER editCondition: {id: "1", group: 1, name: "Temperature >= 75F",\nmeta: "如果温室中 >= 75F 打开空调直到比 \n75F 低 5 度", 表达式: Array(1 )} editCondition.jsx:191 表达式:{数据:\n{\xe2\x80\xa6},运算符:">=,值:“75”,plateValue:“5”,id:0}\nexpressionsForm.jsx :39 RENDER 表达式形式:{数据:{\xe2\x80\xa6},运算符:\n">=,值:“75”,plateValue:“5”,id:0}

\n

单击进入 PlateValue 字段并添加“6”,父级重新渲染...并且:

\n

editCondition.jsx:188 RENDER editCondition: {id: "1", group: 1, name:\n"Temperature >= 75F", meta: "如果温室中 >= 75F 打开空调直到\n比 75F 低 5 度" ,表达式:Array(1)}\neditCondition.jsx:191 表达式:{数据:{\xe2\x80\xa6},运算符:">=,值:\n"75",plateValue:"56",id : 0} editCondition.jsx:153 STATE SET!\nupdateExpression 条件: {id: "1", group: 1, name: "Temperature >=\n75F", meta: "如果温室中 >= 75F 打开空调直到 5比 75F 冷\n度”,表达式:Array(1)}

\n

我在那里看到一个“plateValue:“56””。那么为什么它不在子组件中重新渲染呢?如此迷茫。

\n
\n

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

\n

发生了一些我无法弄清楚的事情。我已经使用 React 很长时间了,但我被难住了。这种情况不再经常发生了。

\n

在此先感谢您的帮助

\n

PS 我确实查看了 getDerivedStateFromProps - 文档提供了示例,这很好,但它们没有解释 props 和 state 参数实际上是什么。文档很糟糕。他们的解释很糟糕。他们的例子并没有说明它的实际作用。我只使用 componentWillReceiveProps 来了解 prop 何时发生更改,然后更新状态或其他内容。getDerivedStateFromProps 只是让我困惑。尽管如此,我还是玩弄了它,但也无法让它工作。

\n

视觉示例

\n

Pet*_*r B 6

看起来好像一直在传递同一个expression 对象。

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

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

您可以像已经做的那样避免这种情况,方法是制作一个副本,或者将expression对象分解为其属性,然后将每个属性单独输入props到子对象中。

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