Eu entendo que Pick
foi usado para o tipo de setState
porque retornar undefined
para uma chave que não deveria ser indefinida resultaria na chave sendo definida como indefinida por React.
No entanto, usar Pick
causa outros problemas. Por um lado, o preenchimento automático do serviço de compilador torna-se inútil, pois usa o resultado de Pick
para preenchimento automático e, quando você solicita conclusões, o resultado de Pick
ainda não contém a chave que você pode deseja preencher automaticamente. Mas os problemas são particularmente graves ao escrever setState
com um argumento de retorno de chamada:
return
; se você não retornar uma chave específica em sua instrução return, também não poderá lê-la no argumento sem forçar a lista de chaves a ser redefinida para never
. Várias instruções de retorno podem ser difíceis de escrever se retornarem chaves diferentes, especialmente se você tiver um retorno indefinido em algum lugar (por exemplo, if (state.busy) { return }
).this.setState(input => ({ ...input, count: +input.count + 1 }))
), mas isso é redundante e uma desotimização, especialmente para estados maiores, pois setState
passará o valor de retorno do retorno de chamada para Object.assign
.Pick
escolherá never
para suas chaves, e a função terá permissão para retornar _qualquer coisa_. Mesmo as chaves que coincidem com uma chave existente permitem efetivamente any
como um valor - se não couber, não é Pick
ed e é tratada como uma propriedade em excesso para {}
, que não está marcado.never
for escolhido como o argumento genérico, por qualquer um dos motivos listados acima, um argumento de retorno de chamada pode, na verdade, ser tratado como um argumento para a forma de objeto de setState
; isso faz com que os argumentos do retorno de chamada sejam digitados any
vez de {}
. Não sei por que isso não é um erro implícito.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
}))
}
}
Resumindo, embora o uso de Pick
, apesar de alguns inconvenientes, ajude a detectar erros de tipo na forma não-callback de setState
, é completamente contraproducente na forma callback; onde não apenas não cumpre a tarefa pretendida de proibir undefined
mas também desativa qualquer tipo de verificação nas entradas ou saídas do callback.
Talvez deva ser alterado, pelo menos para o formulário de retorno de chamada, para Partial
e esperar que os usuários saibam que não devem retornar undefined
valores, como era feito nas definições anteriores.
Obrigado pelo seu feedback! Este é um caso muito interessante que você mencionou.
Preciso pensar um pouco sobre as implicações antes de me comprometer com uma opinião.
A certa altura, @ahejlsberg queria tratar a opcionalidade de maneira diferente de | undefined
. Assim, foo?: string
significaria que foo não está definido ou é uma string.
Ao ler um valor de propriedade, a diferença é irrelevante 99,9% do tempo, mas para gravações, especialmente no caso de Partial<>
, a distinção é extremamente importante.
Infelizmente, esta é uma mudança de linguagem de quebra, então devemos esperar pelo 3.0 ou tê-lo atrás de uma bandeira.
Se tivéssemos dito mudança, Partial<>
torna-se extremamente útil para muitos, em vez do meu dogma atual que é rejeitar seu uso à primeira vista.
@ahejlsberg Eu sei que você é um homem ocupado, seria muito difícil implementá-lo? de forma que indefinido não é um valor atribuível implícito?
Tudo bem, depois de passar algum tempo esta manhã pensando sobre o problema, eu vejo algumas "soluções" para o problema que você propõe, cada uma com alguns efeitos colaterais bastante pesados.
interface State { foo?: string }
) para significar string ou não definida.Exemplo:
interface State {
foo: string;
bar: string;
}
const bleh: Partial<State> = { foo: "hi" }; // OKAY
const bleh: Partial<State> = { foo: undefined; } // BREAK
Isso resolveria tecnicamente o problema e nos permitiria usar Partial<>
, à custa de 98% do caso ser mais difícil. Isso quebra o mundo, então não seria realmente possível fazer até 3.xe geralmente, muito poucas bibliotecas / casos de uso realmente se preocupam com a distinção entre não definido e indefinido.
Exemplo:
interface State {
foo: string;
bar: string;
}
setState(prevState => {foo: "hi"}); // OKAY
setState(prevState => {foo: undefined}); // OKAY BUT BAD!
Exemplo:
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
Agora, quando temos vários caminhos de retorno, o compilador apenas todas as chaves potenciais em um gigante Pick<State, "foo" | "bar">
.
No entanto, uma alteração compatível com versões anteriores seria permitir que os literais fossem agrupados, como Pick<State, ("foo") | ("bar")>
ou em um caso mais complexo: Pick<State, ("foo" | "bar") | ("baz")>
A falta de parênteses sugere que é apenas um único conjunto de valores que é a funcionalidade existente.
Agora, quando tentamos lançar {foo: number}
em Pick<State, ("foo") | ("bar")>
, podemos ter sucesso. O mesmo vale para {bar: number}
.
Pick<State, ("foo") | ("bar")>
também pode ser convertido em Partial<State>
e em {foo: number} | {bar: number}
.
(1) e (2) simplesmente não são viáveis. Um introduz complexidade para quase todos, não importa o que aconteça, e o outro ajuda as pessoas a produzir código que compila, mas é claramente incorreto.
(3) é completamente utilizável hoje e embora eu não me lembre por que adicionei | S
como um valor de retorno potencial para a função em setState
, não consigo imaginar outra razão para fazê-lo.
(4) poderia evitar a solução alternativa em (3), mas fica por conta dos desenvolvedores do TypeScript e talvez se @ahejlsberg se interessasse o suficiente, pudéssemos ver isso mais cedo ou mais tarde.
Isso me faz pensar que os tipos estão mais corretos hoje do que se os mudássemos.
O problema com a abordagem "não fazer nada" é que o compilador não está se comportando da maneira que você está descrevendo, caso você realmente cometa um erro de tipo real. Se eu modificar seu exemplo para, digamos, definir o valor para 0
vez de uma string vazia:
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 };
});
Eu vejo agora. De volta à prancheta. Vou ver se podemos criar uma solução inteligente para isso. Se não conseguirmos encontrar uma, precisamos avaliar a solução Partial<>
que você propõe. A grande coisa a se considerar é se um undefined
inesperado em um valor seria ou não mais comum / irritante do que um tipo incorreto inesperado.
Para esclarecer, temos duas sobrecargas para isso ...
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;
Definir um ou ambos para Partial<S>
vez de Pick<S, K>
não corrige seu problema. Ele ainda passa para a versão do objeto, que aceita o tipo errado por algum motivo:
`` `ts
interface State {
barra: string;
foo: número;
}
classe Foo estende React.Component <{}, State> {
blah public () {
this.setState ((prevState) => ({bar: 1})); // Erro não: /
}
} `` `
Poderíamos de alguma forma forçá-lo a rejeitar uma função usando um extends object
limitação?
Na sexta-feira, 28 de julho de 2017 às 0:00 Eric Anderson [email protected] escreveu:
Para esclarecer, temos duas sobrecargas para isso ...
setState
(f: (prevState: Readonly , props: P) => Pick, callback ?: () => any): void; setState(estado: Pick, callback ?: () => any): void;Definir um ou ambos como Parcial em
vez de Selecionarnão resolve seu problema.interface State {
barra: string;
foo: número;
}
classe Foo estende React.Component <{}, State> {
blah public () {
this.setState ((prevState) => ({bar: 1})); // Erro não: /
}
} `` `-
Você está recebendo isso porque é o autor do tópico.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-318388471 ,
ou silenciar o tópico
https://github.com/notifications/unsubscribe-auth/AAEdfeEJ_Ejfana14fRII1OZuS7qTuyuks5sSKX3gaJpZM4OiDrc
.
Nós tentamos isso. Funções são objetos.
Eric L Anderson
Enviado do meu iPhone
Vim aqui para relatar um problema relacionado, que é que a refatoração do tipo de estado foi interrompida com essa mudança.
Por exemplo, consulte https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx - se você abrir no VS Code e F2 para refatorar / renomear something
na linha 6 (https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx#L6), ele atualizará essa linha e https://github.com/tomduncalf/react-types-issue/ blob / master / Test.tsx # L14 , mas perderá o uso disso na chamada setState
em https://github.com/tomduncalf/react-types-issue/blob/master/Test.tsx # L20
A única maneira que encontrei de fazê-lo funcionar corretamente é digitar explicitamente meu objeto as IState
ao chamar setState
, o que é um pouco inconveniente e fácil de esquecer.
Não estou familiarizado com a justificativa para a mudança exatamente, mas ela parece ter quebrado a segurança de tipo de uma forma bastante significativa ao trabalhar com o estado, então seria ótimo se houvesse uma maneira de resolver isso!
Obrigado,
Tom
Sim, novamente eu acredito que seria melhor usar Partial
porque mantém essa informação de relação melhor.
Indiscutivelmente, Pick
não sendo refatorado corretamente é uma limitação / bug do código de refatoração que poderia ser melhorado, mas ainda não acredito que Pick
esteja fornecendo a segurança de tipo que https: // github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318385945 menções. Enquanto Partial
permitiria undefined
onde undefined
não deveria estar, e o usuário então precisa ter cuidado para não fazer isso, Pick
permite qualquer coisa porque qualquer tipo que não esteja em conformidade com a interface faria com que, em vez de um erro de tipo, Pick
gerasse uma interface vazia, que aceita qualquer coisa.
Quanto a https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -318388471, isso poderia ser um bug no verificador de propriedade em excesso? Ou isso foi testado antes de o verificador de propriedade em excesso se tornar mais rígido no 2.3 ou 2.4 (esqueci)?
Outro problema que acabei de notar aqui é que se um componente não tiver nenhum tipo de estado (ou seja, apenas um parâmetro de tipo para React.Component
) ou o tipo de estado estiver vazio ( {}
), o Typescript ainda permitirá chamadas para setState
sem gerar um erro, o que me parece incorreto - consulte https://github.com/tomduncalf/react-types-issue/blob/master/Test2.tsx
A solução usando Partial parece preferível para mim - posso tentar fazer o patch sozinho e ver como funciona.
Obrigado,
Tom
Olá a todos!
Não sei por que, mas se eu juntar setState
declarações a uma única declaração:
setState<K extends keyof S>(state: ((prevState: Readonly<S>, props: P) => Pick<S, K>) | Pick<S, K>, callback?: () => any): void;
Funciona como esperado para mim:
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!
};
}
Isso pode mudar o suficiente para corrigir o problema original?
@mctep Nice find.
E se eu pegar o que você fez e estendê-lo um pouquinho, para ser Partial<S> & Pick<S, K>
vez de Pick<S, K>
em alguns lugares, o intellisense sugere nomes-chave para você. Infelizmente, os nomes das chaves dizem que o valor da propriedade é "algo | indefinido", mas quando você o compila, ele sabe melhor:
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
})
}
}
Vou colocar essa mudança mais tarde hoje
A "correção" recente está causando problemas para mim agora com várias instruções de retorno em um retorno de chamada setState()
.
Texto datilografado: 2.6.2 com todas as opções "strict" habilitadas, exceto para "strictFunctionTypes"
digita / reage: 16.0.30
Exemplo de código:
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
};
});
}
}
Erro do compilador:
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) => {
~~~~~~~~~~~~~~~~
Também estou vendo o problema descrito por
Tenho quase certeza de que isso sempre foi um problema.
Tenho certeza de que nem sempre foi um problema. Eu tenho um projeto com várias declarações de retorno em setState()
callbacks que foram compilados sem problemas por mais de 2 meses. Tenho acompanhado as atualizações de dependência do NPM a cada 2 semanas ou mais, e este erro do compilador começou a acontecer para mim hoje, depois de atualizar para a versão mais recente de types / react. A versão anterior não produziu este erro do compilador.
Tem sido um problema desde a mudança de parcial para escolha. A solução alternativa seria fornecer a lista de chaves que você pretende retornar para o parâmetro genérico de setState
, mas então você será forçado a sempre retornar todas as chaves ...
O que eu faço nesses casos é sempre retornar todas as chaves, com as chaves não modificadas sendo definidas como key: prevState.key
, ou retornando um spread com prevState ( { ...prevState, newKey: newValue }
).
Talvez eu realmente tenha um caso de borda mais específico em meu código que estava funcionando anteriormente por coincidência? Um exemplo real do meu projeto é mais parecido com este, em que retorna um objeto vazio (para não alterar nenhum estado) ou retorna um objeto não vazio:
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
e return undefined
também são valores de retorno perfeitamente aceitáveis para não alterar o estado (eles passarão por Object.assign
e, portanto, não farão alterações em this.state
).
Me lembra, a assinatura atual não permite nenhum deles. Talvez null
deva ser permitido como um valor de retorno, pelo menos, pois não é algo que pode resultar do esquecimento acidental de return
.
De qualquer forma, nos casos em que a assinatura é ambígua, o TypeScript parece escolher apenas a primeira instrução return
na ordem de origem e usá-la para derivar os parâmetros genéricos. Parece ser capaz de mesclar os tipos de genéricos simples (por exemplo, Array
ou Promise
), mas nunca os mescla se o tipo contextual for um tipo mapeado como Pick
.
Estou vendo algumas regressões com a versão não funcional com os tipos mais recentes. Em particular, o tipo de argumento "state" ao passar um argumento mudou de:
setState
( estado: Pick , callback ?: () => any): void;
para:
state: ((prevState: Readonly \
, props: P) => (Pick& Partial \)) |
https://github.com/DefinitelyTyped/DefinitelyTyped/commit/62c2219a6ed6dc34ea969b8c2c87a41d31002660#diff -96b72df8b13a8a590e4f160cbc51f40c
A adição de & Partial<S>
parece estar quebrando coisas que funcionavam anteriormente.
Aqui está uma reprodução mínima:
export abstract class ComponentBaseClass<P, S = {}> extends React.Component<P, S & { baseProp: string }>
{
foo()
{
this.setState( { baseProp: 'foobar' } );
}
}
isso falha com o erro:
(429,18): Argumento do tipo '{baseProp: "foobar"; } 'não é atribuível ao parâmetro do tipo' ((prevState: Somente leitura
Tipo '{baseProp: "foobar";}' não é atribuível ao tipo 'Escolha&parcialtipo' {baseProp: "foobar";} 'não é atribuível a tipo 'parcial
Alterar o tipo de estado de S &{ baseProp: string }
para apenas { baseProp: string }
também fará com que o erro desapareça (embora isso interrompa as classes reais que especificam o tipo S).
Isso é interessante. Estou feliz em reverter a parte parcial e escolher a parte da mudança
Direi que o que você relata parece um bug no TS, especificamente:
Digite '{baseProp: "foobar"; } 'não pode ser atribuído ao tipo' Parcial
Tudo bem. Talvez o TS não saiba o que está acontecendo porque não há relação entre S e baseProp.
Pode-se passar um S do tipo { baseProp: número }, caso em que é correto que você não pode atribuir uma string a baseProp.
Talvez se S estendesse a versão baseProp?
Eu tinha tentado algo assim antes:
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' } );
}
}
enquanto o acesso está ok, a chamada setState tem o mesmo erro:
Argumento do tipo '{baseProp: "foobar"; } 'não é atribuível ao parâmetro do tipo' ((prevState: Readonly \
, props: P) => Pick& Partial \) |} 'não pode ser atribuído ao tipo' Seleçãoe parcial \'.Digite '{baseProp: "foobar";
Provavelmente não é uma boa maneira de estruturá-lo - afinal, alguma classe filha poderia declarar variáveis de estado que colidiram com variáveis na classe base. Portanto, o texto datilografado pode estar certo em reclamar que não é possível ter certeza do que vai acontecer lá. É estranho que ele não tenha nenhum problema em acessar esses adereços.
Hmm. Isso definitivamente parece um bug no TS agora.
Vou brincar com isso hoje e, possivelmente, tirar o & Partial.
Vou adicionar isso como um caso de teste também e tentar outra ideia para tornar o intellisense feliz
Oi,
Mesmo problema aqui...
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>
);
}
}
Erro em setState:
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>'.
O erro só começou a acontecer após essa mudança. Na versão 16.0.10 funcionou bem.
O texto datilografado tem vários problemas relacionados, eles os fecharam conforme planejado:
https://github.com/Microsoft/TypeScript/issues/19388
Fiz alguns exemplos aqui: https://gist.github.com/afarnsworth-valve/93d1096d1410b0f2efb2c94f86de9c84
Embora o comportamento ainda pareça estranho. Em particular, você pode atribuir a uma variável digitada como a classe base sem conversão e, em seguida, fazer as mesmas chamadas sem problemas. Este problema parece sugerir que atribuição e comparação são duas coisas diferentes.
Não esqueci de vocês. Vai consertar em breve
O PR referenciado deve resolver este problema. Eu também adicionei testes que devem proteger contra quebrar este caso extremo novamente.
@ericanderson Obrigado pela resposta rápida :)
A nova correção tem alguma limitação / desvantagem?
Nenhum que eu conheça atualmente. Consegui manter o intellisense (na verdade melhor do que antes, pois resolve alguns casos extremos).
Para elaborar, a solução & Partial<S>
era enganar o intellisense para revelar possíveis parâmetros, mas o fez afirmando que eles são X | undefined
. Isso, é claro, não conseguiria compilar, mas era um pouco confuso. Obter | S
corrige o intellisense de forma que sugere todos os parâmetros corretos e agora não mostra um tipo falso.
Tentei adicionar null
como um possível valor de retorno do retorno de chamada setState
(o que você pode retornar para indicar que não deseja alterar nenhum estado, sem fazer return;
também válido), mas isso fez com que a inferência de tipo desistisse completamente e pegasse never
como as chaves 😢
Por enquanto, nos retornos de chamada em que realmente desejo pular o estado de atualização, estou usando return null!
. O tipo never
desse retorno faz com que o TypeScript ignore o retorno para a inferência de tipo genérico.
Oi, pessoal...
O último commit corrigiu meu problema.
Obrigado pela resposta rápida :)
Qual versão tem a correção? É publicado no npm? Estou chegando ao caso em que a versão de retorno de chamada de setState está me informando que há uma incompatibilidade de propriedade no valor de retorno, assim como @UselessPickles encontrado.
Deve ser bom nos últimos @ types / react
Consertei para as séries 15 e 16
import produce from 'immer';
interface IComponentState
{
numberList: number[];
}
export class HomeComponent extends React.Component<ComponentProps, IComponentState>
A definição de tipo antigo do 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;
..produz este erro:
Tentei esta sugestão:
setState<K extends keyof S>(
state:
((prevState: Readonly<S>, props: P) => (Partial<S> & Pick<S, K>))
| (Partial<S> & Pick<S, K>),
callback?: () => any
): void;
O Intellisense agora pode detectar a propriedade do estado de React. No entanto, a propriedade detectada agora é percebida como possibly undefined
.
Teria que usar o operador de asserção nulo, embora numberList não seja indefinido nem anulável:
Vou ficar na definição de tipo antigo até que o sensor de tipo seja aprimorado. Por enquanto, apenas declararei explicitamente o tipo no parâmetro genérico do produto immer. produce<IComponentState>
é mais fácil de raciocinar do que list!
.
Seu primeiro erro é porque você não está devolvendo nada. Não é assim que setState funciona.
Funciona mesmo quando não retorna uma variável no produto. Acabei de seguir o exemplo aqui (não TypeScript):
https://github.com/mweststrate/immer
onBirthDayClick2 = () => {
this.setState(
produce(draft => {
draft.user.age += 1
// no need to return draft
})
)
}
A única coisa que impede o TypeScript de executar esse código é que ele inferiu incorretamente o tipo da definição de tipo React. A definição de tipo relata um erro de numberList does not exist on type Pick<IComponentState, never>
. Consegui apenas eliminar o erro de compilação passando explicitamente o tipo no parâmetro genérico do produto, ou seja, produce<IComponentState>
.
Eu até tentei retornar a variável em produzir e ver se isso ajudaria a definição de tipo do React a inferir o tipo (um problema de ovo e galinha), mas ainda não há como a definição de tipo do React detectar o tipo correto do estado. Portanto, o intellisense para o calado não está aparecendo:
Ou talvez eu tenha uma expectativa errada do compilador :) O compilador não pode fazer um tipo para a variável de rascunho com base no tipo de setState enquanto o compilador processa o código de dentro para fora. No entanto, a definição de tipo sugerida de alguma forma me fez pensar que o compilador pode processar o código de fora para dentro, que pode escolher o melhor tipo que pode passar do código externo ( setState
) para o código interno ( 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;
Com a definição de tipo acima, o compilador pode detectar que o rascunho possui uma propriedade numberList
. Ele detecta como possibly undefined
embora:
Após alguns ajustes, tornei o compilador capaz de passar o tipo do estado para o rascunho do produto adicionando S
à definição de tipo de 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;
O código está compilando agora :)
Seu erro não é com setState, seu erro está dentro do produto. Qual é a sua definição de tipo para produto?
Meu PR acima tem um erro no script de teste do DefinitelyTyped, não testei localmente. Então, estou testando localmente agora.
Aqui está a definição do tipo immer / produzir.
/**
* 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, você poderia me indicar a discussão sobre por que Pick
é usado em vez de Partial
? Isso tem me causado horas de pesar (usando apenas setState(obj)
, não a versão de retorno de chamada) e, por enquanto, vou usar this.setState(newState as State)
como solução alternativa. Só quero entender por que mudou, porque devo estar faltando alguma coisa.
Olá @ericanderson ,
Tenho alguns problemas com a definição mais recente.
Meu caso de uso é resumidamente assim:
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
}
A mensagem de erro é assim:
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'.
Parece que Partial<AppState>
não é compatível com a assinatura de setState
. Claro que posso resolver isso por tipo de asserção, como
this.setState(newState as Pick<AppState, 'valueA' | 'valueB'>)
mas não é o ideal, porque:
1) esta sintaxe é muito detalhada
2) mais importante, a asserção de tipo pode violar meus dados reais. Por exemplo, newState as Pick<AppState, 'somethingElse'>
também passa na verificação, embora não se encaixe em meus dados.
Eu acho parcial
Você poderia gentilmente considerar minha sugestão ou apontar meu mal-entendido, se houver? Obrigado!
Esta é uma mudança muito antiga, em primeiro lugar. Portanto, a menos que alguém tenha mudado isso recentemente, você provavelmente está latindo na árvore errada.
Dito isto. Parcial permite valores indefinidos.
const a: Parcial <{foo: string}> = {foo: indefinido}
a é válido, mas claramente o resultado dessa atualização em seu estado coloca seu estado com foo sendo indefinido, embora você tenha declarado que isso é impossível.
Portanto, um parcial não pode ser atribuído a um Pick. E Pick é a resposta certa para garantir que seus tipos não mentem
Eu sinto que não permitindo:
setState((prevState) => {
if (prevState.xyz) {
return { foo: "" };
}
return { bar: "" };
});
é super restritivo.
A solução alternativa de @Kovensky é a única solução sensata que conheço, mas ainda dolorosa de escrever.
Existe algo que pode ser feito para apoiar este (eu diria) padrão bastante comum?
A única coisa que pode ser feita é remover o tipo de segurança
Alguém pode explicar o motivo de 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, eu nem mesmo agora por que a assinatura acima funciona para atualizações de estado parciais como K
é definido como keyof S
então Pick<S, K>
essencialmente recria S
?
Partial<S> | null
não deveria fazer o trabalho também?
setState(
state: ((prevState: Readonly<S>, props: Readonly<P>) => (Partial<S> | null)) | (Partial<S> | null),
callback?: () => void
): void;
Alguém pode explicar ...
Foi explicado claramente algumas respostas acima.
setState((prevState) => { if (prevState.xyz) { return { foo: "" }; } return { bar: "" }; });
é super restritivo.
A solução alternativa de @Kovensky é a única solução sensata que conheço, mas ainda dolorosa de escrever.
Acabei de encontrar exatamente esse problema, mas não consigo ver nenhuma referência a Kovensky no tópico (talvez alguém tenha alterado o nome de usuário?). Alguém pode me indicar a solução alternativa atualmente recomendada
@ timrobinson33 Este comentário explica a solução alternativa https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351884578
E é uma coisa boa não termos que nos preocupar mais com isso com ganchos 🙂
@ timrobinson33 Este comentário explica a solução alternativa # 18365 (comentário)
Muito obrigado por isso. No final, achei que meu código parecia melhor com várias pequenas chamadas setState com instruções
If
fora delas, embora isso signifique que alguns caminhos chamarão setState mais de uma vez.
Eu acho que isso é realmente semelhante a como trabalhamos com ganchos, vendo o estado como várias pequenas coisas que atualizamos independentemente.
Comentários muito úteis
A "correção" recente está causando problemas para mim agora com várias instruções de retorno em um retorno de chamada
setState()
.Texto datilografado: 2.6.2 com todas as opções "strict" habilitadas, exceto para "strictFunctionTypes"
digita / reage: 16.0.30
Exemplo de código:
Erro do compilador: