Se você souber como corrigir o problema, faça uma solicitação pull.
@types/react
e tive problemas.Definitions by:
em index.d.ts
) para que eles possam responder.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
}));
}
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} />
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 geralstring
. A solução alternativa aqui seria coagir o objeto: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.