type p1 = { a: number, b: string }
type p3 = { a: string }
type p4 = p1 | p3

let demo: p4 = { a: '123', b: '123' }

function isP3(obj: p4): obj is p3 { 
    return typeof (<p3>obj).a === 'string'
}

function func(obj: p4) {
    if ('b' in obj) {
        // Uncaught TypeError: obj.a.toFixed is not a function
        obj.a.toFixed() //<- Now, no error is given
    } else { 

    }
}
func(demo)

¿Por qué la demostración no informó un error al inicializar? Protectores de tipo definidos por el usuario

0
linhao li 26 dic. 2019 a las 17:29

2 respuestas

La mejor respuesta

Este es un problema abierto (microsoft / TypeScript # 20863) en TypeScript. Su tipo de unión no es una unión discriminada, por lo que el compilador no divide la unión en miembros antes de realizar exceso comprobación de propiedad. La mayoría de las personas (incluido yo mismo) esperaría que se produjeran cheques de propiedad en exceso para cada miembro del sindicato, ya sea que el sindicato sea discriminado o no. Sin embargo, por ahora, así es: el compilador ve que "b" es una propiedad aceptable en al menos uno de los miembros del sindicato y decide no quejarse.

Tenga en cuenta que el control excesivo de la propiedad es una conveniencia, y no una cuestión de tipo de seguridad. Los tipos de objetos en TypeScript son abiertos , y siempre puede agregarles más propiedades sobre lo que está en la definición sin violar el tipo. Un valor {x: 1, y: 2} es un {x: number} válido a pesar de tener esa propiedad y. Otra forma de decir esto es que los tipos de objetos en TypeScript no son exacto. Por lo tanto, es técnicamente cierto que { a: '123', b: '123' } es un p3 válido y, por lo tanto, un p4 válido. Y así, técnicamente, no puede simplemente verificar la presencia o ausencia de b para distinguir entre p1 y p3. Sí, si solo trata de decir const demo: p3 = {a: '123', b: '123'}, recibirá una advertencia de exceso de propiedad el "b", pero esto, como dije, es solo una conveniencia. Es fácilmente derrotado:

const demo1 = { a: '123', b: '123' };
const demo2: p3 = demo1; // no error

En este punto puede que se pregunte: "espere, si "b" no distingue correctamente p1 de p3, ¿por qué el compilador cree que lo hace dentro de func()?". Buena pregunta:

if ('b' in obj) { // why does the compiler think this narrows obj to p1?
    obj.a.toFixed() // no error, but blows up at runtime
}

Bueno, resulta que la protección de tipo in es intencionalmente poco sólida . Técnicamente no es seguro usarlo, pero las personas lo hacen y generalmente no es un problema. Pero no te ayuda aquí. Oh bien.


Entonces, ¿qué deberías hacer aquí? Si su intención es hacer una prueba para b distinguir entre p1 y p3, entonces su tipo p3 debe dejar eso claro:

type p3 = { a: string, b?: undefined }; // p3 cannot have a defined "b" property    

Ahora el tipo p4 es, a partir de TypeScript 3.2+, una verdadera unión discriminada. Y entonces esto es un error:

let demo: p4 = { a: '123', b: '123' } // error now

Y hace que la prueba incorrecta 'b' aparezca como un error. Si desea hacer una prueba "buena" b, ahora puede hacer una prueba para obj.b !== undefined, que definitivamente distinguirá entre un p1 y p3 con el nuevo {{X5 }} definición:

function func(obj: p4) {
    if ('b' in obj) {
        obj.a.toFixed() // error now
    }

    if (obj.b !== undefined) {
        obj.a.toFixed(); // okay
    }
}

Bien, espero que eso ayude; ¡buena suerte!

Enlace al código

1
jcalz 26 dic. 2019 a las 17:17

Use su función de protección de tipo personalizada para reducir los tipos en lugar de 'b' in obj

    if (!isP3(obj)) {
        obj.a.toFixed() // Error
    } else { 

    }
}

Acerca de la tarea let demo: p4 = { a: '123', b: '123' } también me molesta que no se dé ningún error. Como descubrí, funcionará correctamente (lo que significa dar un error) si definimos a como un booleano en lugar de un número. Parece que la asignación falla solo si el tipo discriminador contiene el tipo de unión en sí. Puede suscribirse a este problema para obtener detalles https://github.com/microsoft/TypeScript/issues/ 35861

La sección de la especificación sobre el asunto no explica completamente el comportamiento actual. Parece una explicación, uno tiene que echar un vistazo al compilador. https://github.com/Microsoft/ TypeScript / blob / master / doc / spec.md # 34-union-types

0
Evgeniy Malyutin 26 dic. 2019 a las 17:10