Definitelytyped: [@ types / react] não pode setState com nome de chave dinâmica seguro para o tipo

Criado em 18 jun. 2018  ·  12Comentários  ·  Fonte: DefinitelyTyped/DefinitelyTyped

Se você souber como corrigir o problema, faça uma solicitação pull.

  • [x] Tentei usar o pacote @types/react e tive problemas.
  • [x] Tentei usar a versão estável mais recente do tsc. https://www.npmjs.com/package/typescript
  • [x] Eu tenho uma pergunta que é inadequada para StackOverflow . (Por favor, faça quaisquer perguntas apropriadas lá).
  • [x] [Mencione] (https://github.com/blog/821-mention-somebody-they-re-notified) os autores (veja Definitions by: em index.d.ts ) para que eles possam responder.

    • Autores: @johnnyreilly , @bbenezech , @pzavolinsky , @digiguru , @ericanderson , @morcerf , @tkrotoff , @DovydasNavickas , @onigoetz , @ theruther4d , @guilhermehubner , @ferrakdaber , @jotoharisoa

Se você não mencionar os autores, o problema será ignorado.

Não posso chamar setState com um objeto sendo criado a partir do nome da propriedade computada com segurança de tipo:

type State = {
  username: string,
  password: string
};

type StateKeys = keyof State;

class A extends React.Component<{}, State> {
  dynSetState(key: StateKeys, value: string) {
    this.setState({
      [key]: value // Error here. Pretty sure key is in StateKeys
    });
  }
}

Estou ciente de # 18365 e da solução alternativa em https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351868649. No entanto, ao usar a solução alternativa, o Typescript não apresenta erros quando deveria:

  dynLooselySetState(key: string, value: string) {
    this.setState(prevState => ({
      ...prevState,
      [key]: value // No error here, but can't ensure that key is in StateKeys
    }));
  }

Comentários muito úteis

Esta é uma limitação do próprio compilador, os tipos inferidos por meio de chaves de propriedade computadas não suportam tipos de união, mas apenas string , number , symbol ou um único literal desses 3, se ele detectar que é uma união de qualquer um deles, ele apenas o forçará para o tipo geral string . A solução alternativa aqui seria coagir o objeto:

dynSetState(key: StateKeys, value: string) {
  this.setState({
    [key]: value
  } as Pick<State, keyof State>)
}

Isso ainda gerará um erro apropriadamente se o valor não estiver no conjunto de tipos de valores de propriedade possíveis, mas não dará a você um erro se as chaves não estiverem dentro do conjunto de chaves de propriedade possíveis.

Todos 12 comentários

Esta é uma limitação do próprio compilador, os tipos inferidos por meio de chaves de propriedade computadas não suportam tipos de união, mas apenas string , number , symbol ou um único literal desses 3, se ele detectar que é uma união de qualquer um deles, ele apenas o forçará para o tipo geral string . A solução alternativa aqui seria coagir o objeto:

dynSetState(key: StateKeys, value: string) {
  this.setState({
    [key]: value
  } as Pick<State, keyof State>)
}

Isso ainda gerará um erro apropriadamente se o valor não estiver no conjunto de tipos de valores de propriedade possíveis, mas não dará a você um erro se as chaves não estiverem dentro do conjunto de chaves de propriedade possíveis.

exatamente o que @ferdaber, embora IMHO casting material como este não seja um "bom padrão", em geral você deve preferir atualizar o estado via padrão de retorno de chamada que, novamente, segue as práticas recomendadas, como extrair esse retorno de chamada de fora da classe para uma função pura e fácil de testar :)

Boa:

class C extends Component<{}, State> {
  updateState(key: StateKeys, value: string) {
    this.setState((prevState) => ({
      ...prevState,
      [key]: value,
    }));
  }
}

Melhor:

const updateState = <T extends string>(key: keyof State, value: T) => (
  prevState: State
): State => ({
  ...prevState,
  [key]: value
})

class C extends Component<{}, State> {
  doSomething(){
    this.setState(updateState('password','123124'))
  }
}

Não encontrei um problema relacionado a essa limitação no repositório de datilografia. Alguma chance de você saber se isso foi discutido (e onde) no repositório de datilografia? Basicamente me perguntando se existem alguns planos para resolver isso em futuras versões do texto datilografado.
Obrigado!

Aqui está a discussão, as notas de design da equipe de TS e uma tentativa de consertá-lo (que acredito ter sido retirado para uma versão futura):
https://github.com/Microsoft/TypeScript/issues/13948
https://github.com/Microsoft/TypeScript/issues/18155
https://github.com/Microsoft/TypeScript/pull/21070

Na verdade, o que estou tentando realizar é algo assim: https://reactjs.org/docs/forms.html#handling -multiple-inputs
O que tornará mais fácil lidar com um formulário com várias entradas. Talvez eu ainda precise ter certeza de que o atributo "name" é o que esperamos, mas depois disso a segurança de tipo deve funcionar.

Tive que adicionar as unknown à solução de @ferdaber :

  this.setState({
    [key]: value
  } as unknown as Pick<State, keyof State>)

Isso avisou se key era um booleano, mas não se fosse um número!

Então, optei por esta solução mais curta:

  this.setState<never>({
    [key]: value
  })

Isso avisa se key é um booleano ou um número.

Por que https://github.com/DefinitelyTyped/DefinitelyTyped/issues/26635#issuecomment -400260278 está funcionando? Quer dizer, a chave ainda é do tipo sindical?

Pode ser isso vai te ajudar

type IAction = {
  [P in keyof IAppSettings]?: IAppSettings[P];
};

function reducer(state: IAppSettings, action: IAction) {
  return {
    ...state,
    ...action,
  };
}

Existe algum progresso nesta questão?

Eu fui capaz de fazer isso funcionar

handleTextChange(name: keyof State, value: any) {
    this.setState({
        ...this.state,
        [name]: value
    });
}

Para outras pessoas olhando para isso e tentando aplicá-lo a seus próprios tipos de estado, observe que o tipo de value na postagem original e nas respostas é apenas string porque todos os tipos de propriedade em State são string . Pode ser melhor evitar o uso de any ou fundição adicional quando outros tipos estiverem envolvidos e, em vez disso, restringir os tipos de forma que o tipo de value corresponda ao seu tipo correspondente ao tipo de chave.

interface State {
  name: string;
  age: number;
}
type StateKeys = keyof State;
function dynSetState<K extends StateKeys>(key: K, value: State[K]) {
  this.setState({ [key]: value }); // fails; if only this worked...
  this.setState({ [key]: value } as Pick<State, K>); // clean cast works
  this.setState((s, _) => ({ ...s, [key]: value })); // avoids cast, but changes js
}
dynSetState("name", "a"); // works as expected
dynSetState("name", 1); // fails as expected
dynSetState("age", "a"); // fails as expected
dynSetState("age", 1); // works as expected

Por que # 26635 (comentário) está funcionando? Quer dizer, a chave ainda é do tipo sindical?

Funciona porque o tipo de { ...prevState } corresponderá suficientemente a State , basicamente porque temos pelo menos todas as propriedades de estado. Pode ajudar a entender por que _não funciona_ sem ele. A raiz do problema é que o tipo do objeto criado: { [key: K]: State[K] } não é específico o suficiente; é { [x: string]: State[K] } que não pode lançar a chave string para um keyof State .

Apenas adicionando @arinwt como outro exemplo.

TL; DR: Solução final na parte inferior

Solução expandida com erros do console:

type RegisterTypes = {
    email: string;
    password: string;
}

// ...

const [state, setState] = useState<RegisterTypes>({
    email: "",
    password: "",
});

// ...

const onChangeInput = (key: keyof RegisterTypes) => (event: React.ChangeEvent<HTMLInputElement>) => {
    setState({
        [key]: event.target.value
    } as Pick<RegisterTypes, typeof key>);
};

// ...

<input type="email" onChange={onChangeInput('email')} value={state.email} />

Embora isso me dê o seguinte erro no console:

Warning: A component is changing a controlled input of type password to be uncontrolled. Input elements should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component.

Solução alternativa (trapaça) com erros de console:

Também dá o aviso também.

type RegisterTypes = {
    [key: string]: string;
}

// ...

const onChangeInput = (key: string) => (event: React.ChangeEvent<HTMLInputElement>) => {
    setState({
        [key]: event.target.value
    });
};

Solução final sem erros:

type RegisterTypes = {
    email: string;
    password: string;
}

// ...

const onChangeInput = (event: React.ChangeEvent<HTMLInputElement>) => {
    const newState = { ...state };
    newState[event.target.name as keyof RegisterTypes] = event.target.value;
    setState(newState);
};

// ...

<input name="email" type="email" onChange={onChangeInput} value={state.email} />
Esta página foi útil?
0 / 5 - 0 avaliações