'TypeError: illegal operation attempted on a revoked proxy' when using Immer with setState

'TypeError: illegal operation attempted on a revoked proxy' when using Immer with setState

我最近重构了我的 React 应用程序以使用 Immer。但是,在 onFormValueChange 中使用 produce 给我错误 TypeError: illegal operation attempted on a revoked proxy 而没有 produce 编写的版本工作正常。

这是我可以将相关代码减少到的最小值:


test.tsx

import { produce } from 'immer';
import { IFormValues, TestForm } from './test_form';

interface ITestProps {}

interface ITestState {
  type: string;
}

export class Test extends React.Component<ITestProps, ITestState> {
  constructor(props: ITestProps) {
    super(props);

    this.state = {
      type: '',
    };
  }

  handleSubmit = (values: IFormValues) => {
    console.log.log(values);
  };

  onFormValueChange = (values: IFormValues) => {
    this.setState(
      produce((draft: ITestState) => {
        draft.type = values.type;
      }),
    );
  };

  // The Following version of the function works perfectly fine and as expected:
  //
  // onFormValueChange = (values: IFormValues) => {
  //   this.setState({
  //     type: values.type,
  //   });
  // };

  render() {
    let showField = true;
    if (this.state.type === 'test') {
      showField = false;
    }
    return (
      <div>
        <TestForm
          submit={this.handleSubmit}
          onValueChange={this.onFormValueChange}
        >
          <input name="type" />
        </TestForm>

        {showField && this.state.type}
      </div>
    );
  }
}

test_form.tsx

import produce from 'immer';

export interface IFormValues {
  [key: string]: any;
}

interface IFormProps {
  submit: (values: IFormValues) => void;
  onValueChange?: (values: IFormValues) => void;
}

export interface IFormState {
  values: IFormValues;
}

interface IFieldProps {
  value: any;
  name: string;
  onChange: (event: any) => void;
}

export class TestForm extends React.Component<IFormProps, IFormState> {
  constructor(props: IFormProps) {
    super(props);

    const values: IFormValues = {};

    this.state = {
      values,
    };
  }

  private handleSubmit = (event: any) => {
    event.preventDefault();

    const { submit } = this.props;
    const { values } = this.state;

    submit(values);
  };

  handleChange = (event: any) => {
    const { name, value } = event.target;

    this.setState(
      produce((draft: IFormState) => {
        draft.values[name] = value;

        this.props.onValueChange && this.props.onValueChange(draft.values);
      }),
    );
  };

  public render() {
    const { values } = this.state;
    return (
      <form onSubmit={this.handleSubmit} noValidate={true}>
        <div>
          {React.Children.map(
            this.props.children,
            (child: React.ReactElement<IFieldProps>) => (
              <div>
                {React.cloneElement(child, {
                  value: values[child.props.name],
                  onChange: this.handleChange,
                })}
              </div>
            ),
          )}
          <div>
            <button type="submit">Submit</button>
          </div>
        </div>
      </form>
    );
  }
}

警告:我从未使用过 Immer。但错误很明显:您正在尝试使用已撤销的代理。我的猜测是根本问题在这里:

this.setState(
  produce((draft: IFormState) => {
    draft.values[name] = value;

    this.props.onValueChange && this.props.onValueChange(draft.values); // <=======
  }),
);

produce 中,您将 draft.values 传递给一个函数,该函数将第二次调用 produce 并将 values.type 放在 不同的 草稿状态。我的猜测是您不允许以这种方式从原始 produce 调用中传递数据。 (The documentation"Warning: please note that the draft shouldn't be 'leaked' from the async process and stored else where. The draft will still be revoked as soon as the async process completes." 但该警告与异步生产者有关,而您的不是异步的。不过,这可能是一个更普遍的警告,它恰好在异步生产者部分。)

如果是这样,TestForm 中对 handleChange 的更改将修复它:

this.setState(
  produce((draft: IFormState) => {
    draft.values[name] = value;
  }),
  () => {
    this.props.onValueChange && this.props.onValueChange(this.state.values);
  }
);

这确保它在 设置状态后使用值 调用 onValueChange(大概是此时的普通对象,而不是代理)。