首页 > 解决方案 > 为什么直接来自父组件的 onChange func 与通过组件的实例方法代理会导致不同的状态值?

问题描述

目标:我试图理解为什么捕获的状态值在以下两种方法之间不同。

我有一个包含输入过滤器属性数组的父状态。对于每个过滤器,都有一个onChange回调函数,它对当前状态进行深度复制,修改value相关元素的,并更新整个数组的状态。子组件是<FilterWrapper>,如果相同则阻止渲染value。这个组件最终渲染了 antdesign 库的<InputNumber>组件。

我尝试的两种方法是:

  1. 只需将 onChange 函数按原样传递给<InputNumber>. 这不知何故导致了一个问题,因为如果输入 A 发生更改,输入 B 不知道状态是否已更改。如果稍后输入 B 发生更改,它将对旧状态值进行深度复制,因此在状态更新期间它会意外恢复输入 A 的值。
  2. 或者,我将 onChange 定义为的实例方法<InputNumber>并将其传递给<InputNumber>. 这个函数只是onChange从父组件运行。这会产生所需的行为。

问题是,为什么它们会产生不同的行为?在这两种情况下,如果一个输入发生更改,则在 shouldComponentUpdate 条件下另一个输入将不会呈现(因此转到的 onChange 属性<InputNumber>不会更新)。

下面是代码。也可以在我的repo中找到。我只是使用 create-react-app 来说明这一点。

应用程序.js:

import React from 'react';
import InputWrapper from './InputWrapper';
import _ from 'lodash';
import 'antd/dist/antd.css';

class App extends React.Component {
  state = {
    metricValues: [
      {
        metric: 'METRIC_A',
        value: 50,
        someOtherAttribute1: true,
        someOtherAttribute2: 'boo',
      },
      {
        metric: 'METRIC_B',
        value: 10,
        someOtherAttribute1: true,
        someOtherAttribute2: 'bla',
      },
    ],
  };
  minMaxRanges = {
    METRIC_A: [0, 100000],
    METRIC_B: [0, 1000000],
  };
  render() {
    const { metricValues } = this.state;

    const inputFilters = ['METRIC_A', 'METRIC_B'].map((m) => {
      const mv = metricValues.find((f) => f.metric === m);

      const onChange = (value) => {
        const newMetricValueFilters = _.cloneDeep(metricValues);
        const mvCopy = newMetricValueFilters.find((f) => f.metric === m);
        mvCopy.value = value;
        this.setState({ metricValues: newMetricValueFilters });
      };

      const props = {
        range: this.minMaxRanges[m],
        value: mv.value,
        onChange,
      };

      return (
        <div style={{ margin: 12, display: 'flex' }} key={m}>
          <div style={{ marginRight: 12 }}>{m}</div>
          <InputWrapper {...props} />
        </div>
      );
    });

    return <div className="App">{inputFilters}</div>;
  }
}

export default App;

InputWrapper.js:

import React from 'react';
import { InputNumber } from 'antd';

class InputWrapper extends React.Component {
  shouldComponentUpdate(np, ns) {
    const { value } = this.props;

    // this prevents any component to render if the value hasn't changed
    // I know per React's official documentation that this is not a guarantee,
    // but in this case I added logs when the render function is triggered and confirmed
    // render didn't execute in either cases
    return np.value !== value;
  }

  onChange = (v) => {
    const { onChange } = this.props;
    onChange(v);
  };

  render() {
    const { range, value, onChange } = this.props;
    const min = range[0];
    const max = range[1];

    return (
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'space-around',
        }}
      >
        <div>
          <InputNumber
            min={min}
            max={max}
            style={{ width: 80 }}
            value={value}
            onChange={(v) => {
              // if onChange from the parent is used directly, the last state
              // when this component is last rendered is used
              // use this for approach #1
              // comment this out if we want to try approach #2
              onChange(v);

              // if we use this.onChange(),which calls the parent's onChange, the most recent state is used
              // uncomment below for approach #2
              // this.onChange(v);
            }}
            size="small"
          />
        </div>
      </div>
    );
  }
}

export default InputWrapper;

一些截图:

初始状态:

在此处输入图像描述

使用方法 1: 更改输入 A:

在此处输入图像描述

然后更改输入 B(输入 A 也将被还原):

在此处输入图像描述

使用方法2:

更改 A 然后 B 产生了预期的结果:

在此处输入图像描述

提前致谢!

标签: javascriptreactjsantd

解决方案


推荐阅读