我知道Pick
用于setState
的类型,因为为不应未定义的键返回undefined
会导致键被 React 设置为未定义。
但是,使用Pick
会导致其他问题。 一方面,编译器服务的自动完成变得无用,因为它使用Pick
进行自动完成,并且当您请求完成时, Pick
的结果尚未包含您可能需要的密钥想要自动完成。 但是在使用回调参数编写setState
时问题尤其严重:
return
语句; 如果您不在 return 语句中返回特定键,则也无法在不强制键列表重置为never
情况下在参数中读取它。 如果返回不同的键,则多个 return 语句可能很难编写,尤其是当您在某处有未定义的 return 时(例如if (state.busy) { return }
)。this.setState(input => ({ ...input, count: +input.count + 1 }))
)来解决,但这是多余的和去优化的,特别是对于较大的状态,因为setState
将传递回调的返回值到Object.assign
。Pick
将选择never
作为其键,并且该函数将被允许返回 _anything_。 即使与现有键重合的键有效地允许any
作为值——如果它不适合,它只是不是Pick
ed,并且被视为{}
的多余属性never
作为泛型参数,出于上面列出的任何原因,回调参数实际上可能被视为setState
的对象形式的参数; 这会导致回调的参数被键入any
而不是{}
。 我不确定为什么这不是隐含的任何错误。interface State {
count: string // (for demonstration purposes)
}
class Counter extends React.Component<{}, State> {
readonly state: Readonly<State> = {
count: '0'
}
render () {
return React.createElement('span', { onClick: this.clicked }, this.state.count)
}
private readonly clicked = () => {
this.setState(input => ({
count: +input.count + 1 // not a type error
// the setState<never>(input: Pick<State, never>) overload is being used
}))
}
}
综上所述,虽然Pick
虽然有一些不便,但有助于在setState
的非回调形式中捕获类型错误,但在回调形式中完全适得其反; 它不仅没有执行禁止undefined
的预期任务,而且还完全禁用对回调输入或输出的任何类型检查。
也许应该将其更改为,至少对于回调表单,更改Partial
并希望用户知道不要返回undefined
值,就像在旧定义中所做的那样。
感谢您的反馈意见。 这是你提出的一个非常有趣的案例。
在我发表意见之前,我需要稍微考虑一下其中的含义。
曾几何时,@ahejlsberg希望以不同于| undefined
方式对待可选性。 因此foo?: string
意味着 foo 要么未设置,要么是一个字符串。
在读取属性值时,差异在 99.9% 的情况下无关紧要,但对于写入,尤其是在Partial<>
的情况下,差异非常重要。
不幸的是,这是一个重大的语言更改,因此我们必须等待 3.0 或将其置于标志之后。
如果我们说改变, Partial<>
对许多人来说变得非常有用,而不是我目前的教条是拒绝使用它。
@ahejlsberg我知道你很忙,实施起来有多难? 这样 undefined 不是一个隐式的可分配值?
好的,今天早上花了一些时间思考这个问题之后,我看到了你提出的问题的一些“解决方案”,每个都有一些非常严重的副作用。
interface State { foo?: string }
) 更改为字符串或未设置。例子:
interface State {
foo: string;
bar: string;
}
const bleh: Partial<State> = { foo: "hi" }; // OKAY
const bleh: Partial<State> = { foo: undefined; } // BREAK
这将在技术上解决问题并允许我们使用Partial<>
,代价是 98% 的情况更加困难。 这打破了世界,所以在 3.x 之前真的不可能做到,而且一般来说,很少有库/用例真正关心未设置与未定义之间的区别。
例子:
interface State {
foo: string;
bar: string;
}
setState(prevState => {foo: "hi"}); // OKAY
setState(prevState => {foo: undefined}); // OKAY BUT BAD!
例子:
interface State {
foo: string;
bar: string;
}
// The following errors out because {foo: ""} can't be assigned to Pick<State, "foo" | "bar">
// Same with {bar: "" }
setState((prevState) => {
if (randomThing) {
return { foo: "" };
}
return { bar: "" };
});
// A work around for the few places where people need this type of thing, which works
setState((prevState) => {
if (randomThing) {
return { foo: "" } as State
}
return { bar: "" } as State
});
// This is fine because the following is still an error
const a = {oops: ""} as State
现在,当我们有多个返回路径时,编译器只是将所有潜在的键放入一个巨大的Pick<State, "foo" | "bar">
。
但是,向后兼容的更改是允许对文字进行分组,例如Pick<State, ("foo") | ("bar")>
或更复杂的情况: Pick<State, ("foo" | "bar") | ("baz")>
缺少括号表明它只是一组值,即现有功能。
现在,当我们尝试将{foo: number}
为Pick<State, ("foo") | ("bar")>
,我们可以成功。 与{bar: number}
。
Pick<State, ("foo") | ("bar")>
也可以转换为Partial<State>
和{foo: number} | {bar: number}
。
(1)和(2)只是不可行。 一个为几乎每个人都引入了复杂性,无论如何,另一个帮助人们生成可编译但显然不正确的代码。
(3) 今天完全可用,虽然我不记得为什么我添加| S
作为setState
函数的潜在返回值,但我想不出任何其他原因这样做。
(4) 可以避免 (3) 中的解决方法,但这取决于 TypeScript 开发人员,也许如果我们让@ahejlsberg足够感兴趣,我们迟早会看到它。
这让我觉得今天的类型比我们改变它们更正确。
“什么都不做”方法的问题在于,在您实际犯了真正的类型错误的情况下,编译器的行为与您描述的方式不同。 如果我将您的示例修改为将值设置为0
而不是空字符串:
interface State {
foo: string;
bar: string;
}
// The following does not error at all because the compiler picks `Pick<State, never>`
// The function overload of `setState` is not used -- the object overload is, and it
// accepts the function as it is accepting anything (`{}`).
setState((prevState) => {
if (randomThing) {
return { foo: 0 };
}
return { bar: 0 };
});
我现在明白了。 回到绘图板。 我将看看我们是否可以为此创建一个聪明的解决方案。 如果我们无法想出一个,那么我们需要评估您提出的Partial<>
解决方案。 需要考虑的重要问题是值上意外的undefined
是否比意外的错误类型更常见/更烦人。
为了澄清,我们有两个重载...
setState<K extends keyof S>(f: (prevState: Readonly<S>, props: P) => Pick<S, K>, callback?: () => any): void;
setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;
将其中一个或两个设置为Partial<S>
而不是Pick<S, K>
不能解决您的问题。 它仍然下降到对象版本,由于某种原因它接受错误的类型:
```ts
接口状态{
酒吧:字符串;
富:数字;
}
class Foo 扩展 React.Component<{}, State> {
公共废话(){
this.setState((prevState) => ({bar: 1})); // 没有错误:/
}
}```
我们能否以某种方式强制它通过使用extends object
拒绝一个函数
约束?
2017 年 7 月 28 日星期五 0:00 Eric Anderson [email protected]写道:
为了澄清,我们有两个重载...
设置状态
(f: (prevState: Readonly , props: P) => Pick, callback?: () => any): void;setState(state: Pick, callback?: () => any): void;将其中一个或两个设置为 Partial
而不是 Pick不能解决你的问题。接口状态{
酒吧:字符串;
富:数字;
}
class Foo 扩展 React.Component<{}, State> {
公共废话(){
this.setState((prevState) => ({bar: 1})); // 没有错误:/
}
}```—
您收到此消息是因为您创作了该线程。
直接回复本邮件,在GitHub上查看
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-318388471 ,
或静音线程
https://github.com/notifications/unsubscribe-auth/AAEdfeEJ_Ejfana14fRII1OZuS7qTuyuks5sSKX3gaJpZM4OiDrc
.
我们试过了。 函数是对象。
埃里克·L·安德森
从我的iPhone发送
我来这里报告一个相关问题,即状态类型的重构被此更改破坏了。
例如,请参阅https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx - 如果您在 VS Code 和 F2 中打开它以重构/重命名something
6 行的https://github.com/tomduncalf/react-types-issue/ blob/master/Test.tsx#L14 ,但会错过在https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx的setState
调用中使用它#L20
我发现让它正常工作的唯一方法是在调用setState
时显式键入我的对象as IState
setState
,这有点不方便并且很容易忘记。
我不完全熟悉更改的基本原理,但是在处理状态时它似乎确实以相当重要的方式破坏了类型安全,因此如果有办法解决它会很棒!
谢谢,
汤姆
是的,我再次相信使用Partial
会更好,因为它可以更好地保持关系信息。
可以说,没有正确重构Pick
是重构代码的一个限制/错误,可以改进,但我仍然不相信Pick
提供了https://的类型安全Partial
将允许undefined
而undefined
不应该是,然后用户需要小心不要这样做, Pick
允许任何事情,因为任何不符合接口的类型都会导致Pick
而不是类型错误,而是生成一个空接口,它接受任何东西。
至于https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318388471,这可能是多余属性检查器中的错误吗? 或者这是在 2.3 或 2.4 中对多余属性检查器变得更严格之前进行了测试(我忘记了)?
我刚刚注意到的另一个问题是,如果一个组件没有状态类型(即React.Component
只有一个类型参数),或者状态类型为空( {}
),Typescript 仍然允许调用setState
没有抛出错误,这对我来说似乎不正确 – 请参阅https://github.com/tomduncalf/react-types-issue/blob/master/Test2.tsx
使用 Partial 的解决方案听起来更适合我——我可能会尝试自己修补它并看看它是如何工作的。
谢谢,
汤姆
大家好!
我不知道为什么,但是如果我将setState
声明加入单个声明:
setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => Pick<S, K>) | Pick<S, K>, callback?: () => any): void;
它按我的预期工作:
import * as React from 'react';
export class Comp extends React.Component<{}, { foo: boolean, bar: boolean }> {
public render() {
this.handleSomething();
return null;
}
private handleSomething = () => {
this.setState({ foo: '' }); // Type '""' is not assignable to type 'boolean'.
this.setState({ foo: true }); // ok!
this.setState({ foo: true, bar: true }); // ok!
this.setState({}); // ok!
this.setState({ foo: true, foo2: true }); // Object literal may only specify
// known properties, and 'foo2' does not exist in type
this.setState(() => ({ foo: '' })); // Property 'foo' is missing in type '() => { foo: string; }'.
this.setState(() => ({ foo: true })); // ok!
this.setState(() => ({ foo: true, bar: true })); // ok!
this.setState(() => ({ foo: true, foo2: true })); // Property 'foo' is missing in type
// '() => { foo: true; foo2: boolean; }'
this.setState(() => ({ foo: '', foo2: true })); // Property 'foo' is missing in
// type '() => { foo: string; foo2: boolean; }'.
this.setState(() => ({ })); // ok!
};
}
这是否足以解决原始问题?
@mctep不错的发现。
如果我采用您所做的并将其扩展一点,在某些地方使用Partial<S> & Pick<S, K>
而不是Pick<S, K>
,intellisense 会为您建议关键名称。 不幸的是,键名说属性值是“something | undefined”,但是当你编译它时,它知道得更好:
declare class Component<P, S> {
setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>)) | (Partial<S> & Pick<S, K>), callback?: () => any): void;
}
interface State {
foo: number;
bar: string;
baz?: string;
}
class Foo extends Component<{}, State> {
constructor() {
super();
this.setState(() => { // error
return {
foo: undefined
}
});
this.setState({ // error
foo: undefined
})
this.setState({
foo: 5,
bar: "hi",
baz: undefined
})
}
}
我将在今天晚些时候进行此更改
最近的“修复”现在给我带来了问题,在setState()
回调中使用多个返回语句。
打字稿:2.6.2 启用所有“严格”选项,“strictFunctionTypes”除外
类型/反应:16.0.30
代码示例:
interface TestState {
a: boolean,
b: boolean
}
class TestComponent extends React.Component<{}, TestState> {
private foo(): void {
this.setState((prevState) => {
if (prevState.a) {
return {
b: true
};
}
return {
a: true
};
});
}
}
编译器错误:
error TS2345: Argument of type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to parameter of type '((prevState: Readonly<TestState>, props: {}) => Pick<TestState, "b"> & Partial<TestState>) | (Pick<TestState, "b"> & Partial<TestState>)'.
Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b"> & Partial<TestState>'.
Type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }' is not assignable to type 'Pick<TestState, "b">'.
Property 'b' is missing in type '(prevState: Readonly<TestState>) => { b: true; } | { a: true; }'.
522 this.setState((prevState) => {
~~~~~~~~~~~~~~~~
由于此更改,还看到了@UselessPickles描述的问题。
我很确定这一直是一个问题。
我非常肯定它并不总是一个问题。 我有一个在setState()
回调中包含多个 return 语句的项目,该项目已经编译了 2 个多月没有问题。 我一直在每 2 周左右更新一次 NPM 依赖项升级,在升级到最新版本的类型/反应后,这个编译器错误今天才开始发生在我身上。 以前的版本没有产生这个编译器错误。
自从从 Partial 更改为 Pick 以来,这一直是一个问题。 解决方法是将您打算返回的键列表提供给setState
的通用参数,但是您必须始终返回所有键...
在这些情况下,我所做的是要么始终返回所有键,将未修改的键设置为key: prevState.key
,要么返回带有 prevState ( { ...prevState, newKey: newValue }
) 的价差。
也许我的代码中实际上有一个更具体的边缘情况,它以前是巧合的? 我的项目中的一个实际示例更像是这样,它要么返回一个空对象(不改变任何状态),要么返回一个非空对象:
interface TestState {
a: boolean,
b: boolean
}
class TestComponent extends React.Component<{}, TestState> {
private foo(newValue: boolean): void {
this.setState((prevState) => {
if (prevState.a === newValue) {
// do nothing if there's no change
return { };
}
return {
a: newValue,
// force "b" to false if we're changing "a"
b: false
};
});
}
}
return null
和return undefined
也是完全可以接受的不改变状态的返回值(它们将经历Object.assign
并且因此不要改变this.state
)。
提醒我,当前的签名不允许它们中的任何一个。 也许null
应该被允许作为返回值,至少,因为它根本不会因为意外忘记return
而产生。
无论如何,在签名不明确的情况下,TypeScript 似乎只选择源代码顺序中的第一个return
语句并使用它来派生通用参数。 它似乎能够合并简单泛型的类型(例如Array
或Promise
),但如果上下文类型是像Pick
这样的映射类型,它永远不会合并它们。
我看到最新类型的非函数版本有一些回归。 特别是,传递参数时的“状态”参数类型已从:
设置状态
( state: Pick , callback?: () => any): void;
到:
状态: ((prevState: Readonly\
, props: P) => (Pick& Partial\)) |
https://github.com/DefinitelyTyped/DefinitelyTyped/commit/62c2219a6ed6dc34ea969b8c2c87a41d31002660#diff -96b72df8b13a8a590e4f160cbc51f40c
& Partial<S>
似乎打破了以前有效的东西。
这是一个最小的重现:
export abstract class ComponentBaseClass<P, S = {}> extends React.Component<P, S & { baseProp: string }>
{
foo()
{
this.setState( { baseProp: 'foobar' } );
}
}
这失败并出现错误:
(429,18): '{ baseProp: "foobar"; }' 不可分配给类型 '((prevState: Readonly
Type '{ baseProp: "foobar"; }' 不可分配给类型 'Pick& PartialType '{ baseProp: "foobar"; }' 不可分配给输入“部分
将状态类型从S &{ baseProp: string }
更改为仅{ baseProp: string }
也会使错误消失(尽管这会破坏指定 S 类型的实际类)。
那很有意思。 我很高兴回滚更改的部分和选择部分
我会说您报告的内容听起来像是 TS 中的错误,具体来说:
输入 '{ baseProp: "foobar"; }' 不可分配到类型 'Partial
好吧。 也许 TS 不知道发生了什么,因为 S 和 baseProp 之间没有关系。
可以传递类型为 { baseProp:number } 的 S,在这种情况下,您不能为 baseProp 分配字符串是正确的。
也许如果 S 扩展了 baseProp 版本?
我以前尝试过这样的事情:
interface BaseState_t
{
baseProp: string
}
export abstract class ComponentBaseClass<P, S extends BaseState_t> extends React.Component<P, S >
{
foo()
{
this.state.baseProp;
this.setState( { baseProp: 'foobar' } );
}
}
虽然访问正常,但 setState 调用有相同的错误:
'{ baseProp: "foobar"; 类型的参数 }' 不可分配给类型为 '((prevState: Readonly\
, props: P) => Pick& Partial\) | 的参数}' 不可分配给类型 'Pick& Partial\'。输入 '{ baseProp: "foobar";
这可能不是构造它的好方法 - 毕竟,某些子类可以声明与基类上的变量发生冲突的状态变量。 所以打字稿抱怨它不能确定那里会发生什么可能是正确的。 奇怪的是,访问这些道具没有问题。
唔。 这现在绝对感觉像是 TS 中的一个错误。
我今天会玩这个,可能会去掉 & Partial。
我也会将此添加为测试用例,并尝试另一个想法让智能感知快乐
你好,
同样的问题在这里...
export interface ISomeComponent {
field1: string;
field2: string;
}
interface SomeComponentState {
field: string;
}
export class SomeComponent<
TProps extends ISomeComponent = ISomeComponent,
TState extends SomeComponentState = SomeComponentState> extends React.Component<TProps, TState>{
doSomething() {
this.setState({ field: 'test' });
}
render() {
return (
<div onClick={this.doSomething.bind(this)}>
{this.state.field}
</div>
);
}
}
设置状态错误:
TS2345 (TS) Argument of type '{ field: "test"; }' is not assignable to parameter of type '((prevState: Readonly<TState>, props: TProps) => Pick<TState, "field"> & Partial<TState>) | (Pick...'.
Type '{ field: "test"; }' is not assignable to type 'Pick<TState, "field"> & Partial<TState>'.
Type '{ field: "test"; }' is not assignable to type 'Partial<TState>'.
此更改后才开始发生错误。 在 16.0.10 版本中它运行良好。
Typescript 有几个相关的问题,他们已经按照设计的方式关闭了它们:
https://github.com/Microsoft/TypeScript/issues/19388
我在这里做了一些例子的要点: https :
虽然这种行为看起来仍然很奇怪。 特别是,您可以在没有强制转换的情况下分配给类型为基类的变量,然后毫无问题地进行相同的调用。 这个问题似乎表明分配和比较是两件不同的事情。
没有忘记你们。 很快就会修复
引用的 PR 应该可以解决这个问题。 我还添加了应该防止再次打破这种边缘情况的测试。
@ericanderson感谢您的快速回复:)
新修复是否有任何限制/缺点?
没有我目前知道的。 我能够保持智能感知(实际上比以前更好,因为它解决了一些边缘情况)。
详细地说, & Partial<S>
解决方案是欺骗智能感知来揭示可能的参数,但它通过声明它们是X | undefined
。 这当然会无法编译,但有点令人困惑。 获取| S
修复了智能感知,以便它建议所有正确的参数,现在它不会显示错误类型。
我尝试研究从setState
回调中添加null
作为可能的返回值(您可以返回到不想更改任何状态的状态,而无需使return;
也有效),但这反而使类型推断完全放弃并选择never
作为键 😢
现在,在我确实想跳过更新状态的回调中,我一直在使用return null!
。 此返回的never
类型使 TypeScript 忽略泛型类型推断的返回。
嗨,大家好...
最后一次提交解决了我的问题。
感谢您及时的回复 :)
哪个版本有修复? 发布到 npm 了吗? 我遇到了 setState 的回调版本告诉我返回值中存在属性不匹配的情况,就像发现的@UselessPickles一样。
应该适用于最新的@types/react
我为 15 和 16 系列修复了它
import produce from 'immer';
interface IComponentState
{
numberList: number[];
}
export class HomeComponent extends React.Component<ComponentProps, IComponentState>
React 的旧类型定义..
// We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
// Also, the ` | S` allows intellisense to not be dumbisense
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: P) => (Pick<S, K> | S)) | (Pick<S, K> | S),
callback?: () => void
): void;
..产生这个错误:
我试过这个建议:
setState<K extends keyof S>(
state:
((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
| (Partial<S> & Pick<S, K>),
callback?: () => any
): void;
Intellisense 现在可以从 React 的状态感知属性。 然而,感知的属性现在被视为possibly undefined
。
即使 numberList 不是未定义也不是可空的,也必须使用空断言运算符:
我将继续使用旧的类型定义,直到类型检测得到改进。 同时,我将在 immerproduce 的泛型参数上明确说明类型。 produce<IComponentState>
比list!
更容易推理。
你的第一个错误是因为你没有返回任何东西。 这不是 setState 的工作方式。
即使在生产中没有返回变量,它也能工作。 我只是按照这里的例子(非 TypeScript):
https://github.com/mweststrate/immer
onBirthDayClick2 = () => {
this.setState(
produce(draft => {
draft.user.age += 1
// no need to return draft
})
)
}
阻止 TypeScript 运行该代码的唯一原因是它从 React 类型定义中错误地推断出类型。 类型定义报告numberList does not exist on type Pick<IComponentState, never>
。 能够通过在生产的泛型参数上显式传递类型来消除编译错误,即produce<IComponentState>
。
我什至尝试在 generate 中返回变量,看看它是否有助于 React 类型定义推断类型(尽管这是一个鸡和蛋的问题),但是 React 类型定义仍然无法检测状态的正确类型。 因此草稿的智能感知没有出现:
或者也许我对编译器有错误的期望:) 编译器无法根据 setState 的类型为草稿变量创建类型,因为编译器从内到外处理代码。 然而,建议的类型定义不知何故让我认为编译器可以从外到内处理代码,它可以选择它可以从外部代码( setState
)传递到内部代码( produce
)。
setState<K extends keyof S>(
state:
((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
| (Partial<S> & Pick<S, K>),
callback?: () => any
): void;
使用上述类型定义,编译器可以检测到该草稿具有numberList
属性。 虽然它检测为possibly undefined
:
经过进一步的修改,我通过将S
到 setState 的类型定义,使编译器能够将状态的类型传递给生成的草稿:
setState<K extends keyof S>(
state:
((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K> & S))
| (Partial<S> & Pick<S, K>),
callback?: () => any
): void;
代码现在正在编译:)
您的错误与 setState 无关,您的错误在产品内部。 您对产品的类型定义是什么?
我上面的 PR 在绝对类型的测试脚本上有错误,我没有在本地测试。 所以我现在在本地测试它。
这是 immer/produce 类型定义。
/**
* Immer takes a state, and runs a function against it.
* That function can freely mutate the state, as it will create copies-on-write.
* This means that the original state will stay unchanged, and once the function finishes, the modified state is returned.
*
* If the first argument is a function, this is interpreted as the recipe, and will create a curried function that will execute the recipe
* any time it is called with the current state.
*
* <strong i="7">@param</strong> currentState - the state to start with
* <strong i="8">@param</strong> recipe - function that receives a proxy of the current state as first argument and which can be freely modified
* <strong i="9">@param</strong> initialState - if a curried function is created and this argument was given, it will be used as fallback if the curried function is called with a state of undefined
* <strong i="10">@returns</strong> The next state: a new state, or the current state if nothing was modified
*/
export default function<S = any>(
currentState: S,
recipe: (this: S, draftState: S) => void | S
): S
// curried invocations with default initial state
// 0 additional arguments
export default function<S = any>(
recipe: (this: S, draftState: S) => void | S,
initialState: S
): (currentState: S | undefined) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
recipe: (this: S, draftState: S, a: A) => void | S,
initialState: S
): (currentState: S | undefined, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
recipe: (this: S, draftState: S, a: A, b: B) => void | S,
initialState: S
): (currentState: S | undefined, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S,
initialState: S
): (currentState: S | undefined, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S,
initialState: S
): (currentState: S | undefined, ...extraArgs: any[]) => S
// curried invocations without default initial state
// 0 additional arguments
export default function<S = any>(
recipe: (this: S, draftState: S) => void | S
): (currentState: S) => S
// 1 additional argument of type A
export default function<S = any, A = any>(
recipe: (this: S, draftState: S, a: A) => void | S
): (currentState: S, a: A) => S
// 2 additional arguments of types A and B
export default function<S = any, A = any, B = any>(
recipe: (this: S, draftState: S, a: A, b: B) => void | S
): (currentState: S, a: A, b: B) => S
// 3 additional arguments of types A, B and C
export default function<S = any, A = any, B = any, C = any>(
recipe: (this: S, draftState: S, a: A, b: B, c: C) => void | S
): (currentState: S, a: A, b: B, c: C) => S
// any number of additional arguments, but with loss of type safety
// this may be alleviated if "variadic kinds" makes it into Typescript:
// https://github.com/Microsoft/TypeScript/issues/5453
export default function<S = any>(
recipe: (this: S, draftState: S, ...extraArgs: any[]) => void | S
): (currentState: S, ...extraArgs: any[]) => S
/**
* Automatically freezes any state trees generated by immer.
* This protects against accidental modifications of the state tree outside of an immer function.
* This comes with a performance impact, so it is recommended to disable this option in production.
* It is by default enabled.
*/
export function setAutoFreeze(autoFreeze: boolean): void
/**
* Manually override whether proxies should be used.
* By default done by using feature detection
*/
export function setUseProxies(useProxies: boolean): void
@ericanderson能否请您指出有关为什么使用Pick
而不是Partial
? 这给我带来了数小时的悲伤(使用普通的setState(obj)
,而不是回调版本),现在我将使用this.setState(newState as State)
作为解决方法。 我只是想了解为什么要更改它,因为我一定遗漏了一些东西。
你好@ericanderson ,
我对最新的定义有一些问题。
我的用例大概是这样的:
interface AppState {
valueA: string;
valueB: string;
// ... something else
}
export default class App extends React.Component <{}, AppState> {
onValueAChange (e:React.ChangeEvent<HTMLInputElement>) {
const newState: Partial<AppState> = {valueA: e.target.value}
if (this.shouldUpdateValueB()) {
newState.valueB = e.target.value;
}
this.setState(newState); // <-- this leads to a compiling error
}
// ... other methods
}
错误信息是这样的:
Argument of type 'Partial<AppState>' is not assignable to parameter of type 'AppState | ((prevState: Readonly<AppState>, props: {}) => AppState | Pick<AppState, "valueA" | "v...'.
Type 'Partial<AppState>' is not assignable to type 'Pick<AppState, "valueA" | "valueB" | "somethingElse">'.
Types of property 'valueA' are incompatible.
Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
似乎Partial<AppState>
与setState
的签名不兼容。 当然我可以通过类型断言来解决这个问题
this.setState(newState as Pick<AppState, 'valueA' | 'valueB'>)
但这并不理想,因为:
1) 这种语法非常冗长
2)更重要的是,类型断言可能会违反我的实际数据。 例如, newState as Pick<AppState, 'somethingElse'>
也通过了检查,尽管它不适合我的数据。
我认为部分
您能否考虑一下我的建议或指出我的误解(如果有)? 谢谢!
首先这是一个非常古老的变化。 因此,除非其他人最近更改了这一点,否则您可能会找错树。
那说。 部分允许未定义的值。
const a : Partial<{foo: string}> = { foo: undefined }
a 是有效的,但很明显,更新到您的状态的结果会使您的状态 foo 未定义,即使您声明这是不可能的。
因此部分不能分配给选择。 Pick 是确保您的类型不会说谎的正确答案
我觉得不允许:
setState((prevState) => {
if (prevState.xyz) {
return { foo: "" };
}
return { bar: "" };
});
是超级限制。
@Kovensky的解决方法是我所知道的唯一明智的解决方法,但编写起来仍然很痛苦。
有什么可以做来支持这种(我会说)相当常见的模式吗?
唯一可以做的就是删除类型安全
有人可以解释Pick<S, K> | S | null
吗?
setState<K extends keyof S>(
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
callback?: () => void
): void;
Tbh 我什至不知道为什么上面的签名甚至适用于部分状态更新,因为K
被定义为keyof S
所以Pick<S, K>
基本上重新创建了S
?
Partial<S> | null
不应该也做这项工作吗?
setState(
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Partial<S> | null)) | (Partial<S> | null),
callback?: () => void
): void;
谁能解释一下...
几条回复已经解释清楚了。
setState((prevState) => { if (prevState.xyz) { return { foo: "" }; } return { bar: "" }; });
是超级限制。
@Kovensky的解决方法是我所知道的唯一明智的解决方法,但编写起来仍然很痛苦。
我刚刚遇到了这个确切的问题,但我在线程中看不到任何对 Kovensky 的引用(也许有人更改了他们的用户名?)。 谁能指出我目前推荐的解决方法
@timrobinson33此评论解释了解决方法https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351884578
这是一件好事,我们不必再用钩子担心这个了🙂
@timrobinson33此评论解释了解决方法#18365(评论)
非常感谢。 最后,我认为我的代码看起来更好,因为几个小的 setState 调用在它们之外带有
If
语句,即使这意味着某些路径将多次调用 setState。
我想这实际上类似于我们如何使用钩子,将状态视为我们独立更新的几个小事情。
最有用的评论
最近的“修复”现在给我带来了问题,在
setState()
回调中使用多个返回语句。打字稿:2.6.2 启用所有“严格”选项,“strictFunctionTypes”除外
类型/反应:16.0.30
代码示例:
编译器错误: