Definitelytyped: [@ types / react] no puede establecer un estado con un nombre de clave dinámica seguro para los tipos

Creado en 18 jun. 2018  ·  12Comentarios  ·  Fuente: DefinitelyTyped/DefinitelyTyped

Si sabe cómo solucionar el problema, realice una solicitud de extracción.

  • [x] Intenté usar el paquete @types/react y tuve problemas.
  • [x] Intenté usar la última versión estable de tsc. https://www.npmjs.com/package/typescript
  • [x] Tengo una pregunta que no es apropiada para StackOverflow . (Haga las preguntas correspondientes allí).
  • [x] [Mencione] (https://github.com/blog/821-mention-somebody-they-re-notified) a los autores (consulte Definitions by: en index.d.ts ) para que puedan responder.

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

Si no menciona a los autores, se ignorará el problema.

No puedo llamar setState con un objeto que se crea a partir del nombre de propiedad calculado con seguridad 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
    });
  }
}

Soy consciente de # 18365 y la solución en https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment -351868649. Sin embargo, al usar la solución alternativa, Typescript no produce errores cuando debería:

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

Comentario más útil

Esta es una limitación del compilador en sí, los tipos inferidos a través de claves de propiedad calculadas no admiten tipos de unión, solo admite string , number , symbol o un solo literal de esos 3, si detecta que es una unión de cualquiera de ellos, simplemente lo forzará al tipo string . La solución alternativa aquí sería coaccionar el objeto:

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

Esto todavía dará un error apropiado si el valor no está en el conjunto de posibles tipos de valores de propiedad, pero no le dará un error si las claves no están dentro del conjunto de posibles claves de propiedad.

Todos 12 comentarios

Esta es una limitación del compilador en sí, los tipos inferidos a través de claves de propiedad calculadas no admiten tipos de unión, solo admite string , number , symbol o un solo literal de esos 3, si detecta que es una unión de cualquiera de ellos, simplemente lo forzará al tipo string . La solución alternativa aquí sería coaccionar el objeto:

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

Esto todavía dará un error apropiado si el valor no está en el conjunto de posibles tipos de valores de propiedad, pero no le dará un error si las claves no están dentro del conjunto de posibles claves de propiedad.

exactamente lo que @ferdaber, aunque en mi humilde opinión, lanzar cosas como esta no es un "buen patrón", en general, debería preferir actualizar el estado a través del patrón de devolución de llamada que nuevamente se adhiere a las mejores prácticas, como extraer esa devolución de llamada fuera de la clase para una función pura y fácil de probar :)

Bien:

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

Mejor:

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'))
  }
}

No he encontrado un problema relacionado con esta limitación en el repositorio de mecanografiado. ¿Alguna posibilidad de que sepa si esto se ha discutido (y dónde) en el repositorio de mecanografiado? Básicamente, me pregunto si hay planes para abordar esto en futuras versiones mecanografiadas.
¡Gracias!

Aquí está la discusión, las notas de diseño del equipo de TS y un intento de solucionarlo (que creo que se retiró para una versión futura):
https://github.com/Microsoft/TypeScript/issues/13948
https://github.com/Microsoft/TypeScript/issues/18155
https://github.com/Microsoft/TypeScript/pull/21070

De hecho, lo que estoy tratando de lograr es hacer algo como esto: https://reactjs.org/docs/forms.html#handling -multiple-inputs
Lo que facilitará el manejo de un formulario con múltiples entradas. Es posible que todavía tenga que asegurarme de que el atributo "nombre" sea lo que esperamos, pero después de eso, la seguridad de tipos debería funcionar.

Tuve que agregar as unknown a la solución de @ferdaber :

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

Eso advirtió si key era un booleano, ¡pero no si era un número!

Así que opté por esta solución más corta:

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

Esto advierte si key es un booleano o un número.

¿Por qué funciona https://github.com/DefinitelyTyped/DefinitelyTyped/issues/26635#issuecomment -400260278? Quiero decir, ¿la clave sigue siendo un tipo de unión?

Puede que esto te ayude

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

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

¿Hay algún avance en este tema?

Pude hacer que esto funcionara

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

Para otros que miran esto y tratan de aplicarlo a sus propios tipos de estado, tenga en cuenta que el tipo de value en la publicación original y las respuestas es solo string porque todos los tipos de propiedad en State son string . Podría ser mejor evitar usar any o conversiones adicionales cuando hay otros tipos involucrados, y en su lugar restringir los tipos de modo que el tipo de value corresponda a su tipo que coincida con el tipo de clave.

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 qué funciona # 26635 (comentario) ? Quiero decir, ¿la clave sigue siendo un tipo de unión?

Funciona porque el tipo de { ...prevState } coincidirá suficientemente con State , básicamente porque tenemos al menos todas las propiedades del estado. Podría ser útil comprender por qué _no funciona_ sin él. La raíz del problema es que el tipo de objeto creado: { [key: K]: State[K] } no es lo suficientemente específico; es { [x: string]: State[K] } que no puede convertir la clave string a keyof State .

Simplemente agregando a @arinwt como otro ejemplo.

TL; DR: Solución final en la parte inferior

Solución ampliada con errores de consola:

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} />

Aunque esto me da el siguiente error en la consola:

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.

Solución alternativa (trampas) con errores de consola:

También da la advertencia también.

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

// ...

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

Solución final sin errores:

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} />
¿Fue útil esta página
0 / 5 - 0 calificaciones