javascript - signaturepad - ¿Cómo anotar una función con múltiples posibles firmas de llamada en Flow?
jquery sign pad (3)
De los tres entrenamientos posibles que dio, he descubierto cómo hacerlo funcionar utilizando un solo objeto de opciones, sin embargo, debido a que necesita al menos un objeto para establecerse, debe definir cada posibilidad.
Me gusta esto:
type Arguments =
{|
+name?: string,
+age?: number
|} |
{|
+name: string,
+age?: number
|} |
{|
+name?: string,
+age: number
|};
const foo = (args: Arguments) => {
let name: string = args.name ? args.name : ''someone'';
let age: number = typeof args.age === ''number'' && !isNaN(args.age) ? args.age : 0;
console.log(`${name} is ${age}`);
}
// any of these call signatures are OK:
foo({ name: ''fred'' });
foo({ name: ''fred'', age: 30 });
foo({ age: 30 });
// fails
foo({});
En JavaScript, es común tener una función que se puede llamar de más de una manera, por ejemplo, con un puñado de argumentos posicionales o un único objeto de opciones o alguna combinación de los dos.
He estado tratando de encontrar la manera de anotar esto.
Una forma en que lo intenté fue anotar los argumentos de descanso como una unión de varias posibles tuplas:
type Arguments =
| [string]
| [number]
| [string, number]
;
const foo = (...args: Arguments) => {
let name: string;
let age: number;
// unpack args...
if (args.length > 1) {
name = args[0];
age = args[1];
} else if (typeof args[0] === ''string'') {
name = args[0];
age = 0;
} else {
name = ''someone'';
age = args[1];
}
console.log(`${name} is ${age}`);
};
// any of these call signatures should be OK:
foo(''fred'');
foo(''fred'', 30);
foo(30);
El fragmento de arriba es inventado; Probablemente podría usar (...args: Array<string | number>)
en este ejemplo, pero para firmas más complejas (por ejemplo, involucrar un objeto de opciones tipeado que puede estar solo o con args anteriores) sería útil poder para definir un conjunto preciso y finito de posibles firmas de llamadas.
Pero lo de arriba no hace check-type. Puede ver un montón de errores confusos en tryflow .
También intenté tipear la función en sí misma como una unión de defs separados de funciones completas, pero eso tampoco funcionó :
type FooFunction =
| (string) => void
| (number) => void
| (string, number) => void
;
const foo: FooFunction = (...args) => {
let name: string;
let age: number;
// unpack args...
if (args.length > 1) {
name = args[0];
age = args[1];
} else if (typeof args[0] === ''string'') {
name = args[0];
age = 0;
} else {
name = ''someone'';
age = args[1];
}
console.log(`${name} is ${age}`);
};
// any of these call signatures should be OK:
foo(''fred'');
foo(''fred'', 30);
foo(30);
¿Cómo debo abordar las funciones de anotación de tipo con múltiples posibles firmas de llamadas? (O las firmas múltiples se consideran un antipatrón en Flow, y simplemente no debería hacerlo, en cuyo caso, ¿cuál es el enfoque recomendado para interactuar con bibliotecas de terceros que lo hacen?)
Los errores que está viendo son una combinación de un error en su código y un error en Flow.
Error en tu código
Comencemos por arreglar tu error. En la tercera declaración más, asigna el valor incorrecto a
} else {
name = ''someone'';
age = args[1]; // <-- Should be index 0
}
Cambiar el acceso a la matriz para que sea el índice correcto elimina dos errores. Creo que ambos podemos estar de acuerdo en que esto es exactamente para lo que sirve Flow, encontrar errores en tu código.
Tipo estrecho
Para llegar a la causa raíz del problema, podemos ser más explícitos en el área donde están los errores para que podamos ver más fácilmente cuál es el problema:
if (args.length > 1) {
const args_tuple: [string, number] = args;
name = args_tuple[0];
age = args_tuple[1];
} else if (typeof args[0] === ''string'') {
Esto es efectivamente lo mismo que antes, pero porque tenemos muy claro qué deberían ser args[0]
y args[1]
en este momento. Esto nos deja con un solo error.
Error en el flujo
El error restante es un error en Flow: https://github.com/facebook/flow/issues/3564
error: el tipo de tupla no interactúa con las aserciones de longitud (.length> = 2 y [] | [number] | [number, number] type)
Cómo escribir funciones sobrecargadas
El flujo no es bueno para tratar con variadics con diferentes tipos, como en este caso. Las variables son más para cosas como function sum(...args: Array<number>)
donde todos los tipos son iguales y no hay arity máximo.
En cambio, deberías ser más explícito con tus argumentos, así:
const foo = (name: string | number, age?: number) => {
let real_name: string = ''someone'';
let real_age: number = 0;
// unpack args...
if (typeof name === ''number'') {
real_age = name;
} else {
real_name = name;
real_age = age || 0;
}
console.log(`${real_name} is ${real_age}`);
};
// any of these call signatures should be OK:
foo(''fred'');
foo(''fred'', 30);
foo(30);
Esto no causa errores y creo que también es más fácil de leer para los desarrolladores.
Una mejor manera
En otra respuesta, Pavlo proporcionó otra solución que me gusta más que la mía.
type Foo =
& ((string | number) => void)
& ((string, number) => void)
const foo: Foo = (name, age) => {...};
Resuelve los mismos problemas de una manera mucho más limpia, lo que le permite mucha más flexibilidad. Al crear una intersección de múltiples tipos de funciones, describirá cada forma diferente de llamar a su función, permitiendo que Flow pruebe cada una de ellas en función de cómo se llame a la función.
Puede definir múltiples firmas de funciones uniéndolas con &
:
type Foo =
& ((string | number) => void)
& ((string, number) => void)