'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
(大概是此时的普通对象,而不是代理)。
我最近重构了我的 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
(大概是此时的普通对象,而不是代理)。