允许 typescript 编译器仅在一个反应​​状态属性上调用 setState

Posted

技术标签:

【中文标题】允许 typescript 编译器仅在一个反应​​状态属性上调用 setState【英文标题】:allow typescript compiler to call setState on only one react state property 【发布时间】:2016-09-15 00:26:06 【问题描述】:

我在一个项目中使用带有 React 的 Typescript。 Main 组件通过此接口获得传递状态。

interface MainState 
  todos: Todo[];
  hungry: Boolean;
  editorState: EditorState;  //this is from Facebook's draft js

但是,下面的代码(只是摘录)无法编译。

class Main extends React.Component<MainProps, MainState> 
  constructor(props) 
    super(props);
    this.state =  todos: [], hungry: true, editorState: EditorState.createEmpty() ;
  
  onChange(editorState: EditorState) 
    this.setState(
      editorState: editorState
    );
  

编译器抱怨说,在onChange 方法中,我只尝试为一个属性设置状态, editorState: EditorState; 类型中缺少属性todos 和属性hungry。换句话说,我需要在onChange 函数中设置所有三个属性的状态才能使代码编译。为了让它编译,我需要做

onChange(editorState: EditorState)
  this.setState(
    todos: [],
    hungry: false,
    editorState: editorState
  );

但此时没有理由在代码中设置todoshungry 属性。在 typescript/react 中仅对一个属性调用 setState 的正确方法是什么?

【问题讨论】:

【参考方案1】:

编辑

react 的定义已更新,setState 的签名现在是:

setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;

Pick&lt;S, K&gt; 是 Typescript 2.1 中添加的内置类型:

type Pick<T, K extends keyof T> = 
    [P in K]: T[P];

请参阅Mapped Types 了解更多信息。 如果您仍然遇到此错误,那么您可能需要考虑更新您的反应定义。

原答案:

我也面临同样的事情。

我设法解决这个烦人问题的两种方法是:

(1) 转换/断言:

this.setState(
    editorState: editorState
 as MainState);

(2) 将接口字段声明为可选:

interface MainState 
    todos?: Todo[];
    hungry?: Boolean;
    editorState?: EditorState;

如果有人有更好的解决方案,我很乐意知道!


编辑

虽然这仍然是一个问题,但有两个关于将解决此问题的新功能的讨论:Partial Types (Optionalized Properties for Existing Types) 和More accurate typing of Object.assign and React component setState()

【讨论】:

我现在给你一个赞成票。它可以编译,但我无法完全尝试您的解决方案,因为我的应用程序还有其他问题(如果您感到受到启发,这里还有另一个打字稿 Q ***.com/questions/37302382/…)。关于您提供的这个答案,我唯一担心(我确定这可能不是真实的,或者您会提到它)是您的转换解决方案会将其他状态属性设置为 nil 如果它们没有再次明确设置,或者演员只是忽略它并保持原样? 不,react 只会使用您在setState 中提供的属性更新状态,如果您还没有包含属性,那么之前的值将保持不变 实际上,我收到了运行时错误bundle.js:20184 Uncaught TypeError: Cannot read property 'setState' of null。不知道为什么 this 为空。这显然与您的解决方案无关,但在我让它工作之前,我必须等待接受它作为正确答案。 好的,必须绑定onChange 函数(出于某种原因),但您的解决方案可以正常工作。谢谢。 请注意,断言as MainState 仅在所有字段都不是可选的时才有效。 I've explored this problem quite a bit 和 haven't found a complete solution。 TypeScript 需要支持“部分对象”才能正常工作。【参考方案2】:

显式更新状态,以计数器为例

this.setState((current) => ( ...current, counter: current.counter + 1 ))

【讨论】:

嗨,当我以这种方式调用 setstate 时,我的状态会异步更新。如果您正在运行多个方法,每个方法都更新状态并需要查看先前方法设置的状态,您如何处理它?例如,在字段 updatem 上,我可能会调用单独的方法来 1. 更新状态中的字段 2. 验证新值,更新状态中的一些错误消息, 3. 在状态中设置脏标志。这些方法需要知道之前的方法对状态做了什么。最好的做法是在事件处理程序的最开始创建一个新的状态对象并传递它 使用 Lambda setState 将导致所有 setState 按照调用顺序连续运行,只要您依赖的 setState 之前运行过(如语句 this.setState ) 它们将按顺序应用的依赖项,后者将从先前获得已更新的状态,没有陈旧状态。 这个re-render all components会因为你传播...state而引用state的任何属性吗? 当我这样做时,我收到错误TypeScript error: Parameter 'state' implicitly has an 'any' type. TS7006【参考方案3】:

我认为最好的方法是使用Partial

通过以下方式声明你的组件

class Main extends React.Component<MainProps, Partial<MainState>> 

部分自动将所有键更改为可选。

【讨论】:

如果您知道它们已定义怎么办?根据 TS,它们现在可以是未定义的。现在你需要通过property!@ 明确告诉 TS 没关系【参考方案4】:

我们可以告诉setState 它应该期待哪个字段,通过使用&lt;'editorState'&gt; 对其进行参数化:

this.setState<'editorState'>(
  editorState: editorState
);

如果您要更新多个字段,您可以像这样指定它们:

this.setState<'editorState' | 'hungry'>(
  editorState: editorState,
  hungry: true,
);

虽然它实际上会让你只指定其中一个就可以了!

无论如何,对于最新的 TS 和 @types/react,以上两者都是可选的,但它们引导我们...


如果您想使用动态键,那么我们可以告诉setState 不需要特别指定字段:

this.setState<never>(
  [key]: value,
);

如上所述,如果我们传递一个额外的字段,它不会抱怨。

(GitHub issue)

【讨论】:

【参考方案5】:

编辑:不要使用这个解决方案,更喜欢https://***.com/a/41828633/1420794 详情见 cmets。

现在spread算子已经在TS中发货了,我首选的解决方案是

this.setState(...this.state, editorState); // do not use !

【讨论】:

天哪,这是迄今为止最简单的方法。请对此答案投票以给予更多关注。 目前仍有不利之处; TS 不会检测您是否添加了未声明的属性(即拼写错误)。如果我理解了 TS 团队上次的设计讨论应该很快就会解决 =) 警告:不要使用这个解决方案!如果你这样做,那么你可能很难检测到错误,因为 React 会批量更新状态。想象一下这样的事情:state = a: 1, b: 2, c: 3 , coolSetState( a: 2, b: 3 ), coolSetState( c: 4 ) 其中coolSetState = (editorState) =&gt; this.setState(...this.state, editorState) 如果coolSetState( c: 4 ) 在状态更新之前执行,因为之前的操作,你实际上可能最终得到state = a: 1, b: 2, c: 4 我使用了速记符号。如果我有歧义,请告诉我。 你是对的 =s !我认为最好的解决方案是@Alexey-Petrushin's【参考方案6】:

我不喜欢在扩展项目时给你很多地方犯错误的强制转换和变通方法。这是我的解决方案。

    我们必须通过使用泛型类型为键获得适当的智能感知

&lt;K extends keyof MainState&gt;

    然后,我们必须根据输入的键获取正确的值类型

typeof MainState[K]

    为了获得没有错误和不使用强制转换/解决方法等的代码,请将回调传递给this.setState

    结合在一起


onChange = <K extends keyof MainState>(fieldName: K, value: typeof MainState[K]) => 
        this.setState((prevState) => 
            return 
                ...prevState,
                [fieldName]: value,
            ;
        );
    ;

【讨论】:

以上是关于允许 typescript 编译器仅在一个反应​​状态属性上调用 setState的主要内容,如果未能解决你的问题,请参考以下文章

仅在 useEffect 之后使用数据

与 Typescript 反应 - 类型 缺少类型中的以下属性

我可以强制使用Typescript来允许点符号[重复]

访问器仅在针对 ECMAScript 5 及更高版本时可用 - 使用 TypeScript 时出现错误消息

允许 typescript 编译没有未使用的变量规则

为啥 TypeScript 中的类允许使用鸭子类型