Soy nuevo en TypeScript y trato de migrar lentamente un proyecto JS (reaccionar) existente a TypeScript.
Tengo una función ejecutando una consulta:
/** * Run a Query against the currently connected database * @param {String} sql Query to run * @param {Object[]} Array of objects to be used in the prepared statement * @returns {Promise<Object[]>} Promise resolving after query is done. Returns an array of arrays with the result. */ const query = (sql, params) => { //Database stuff }Y estoy llamando a esto en numerosos lugares:
const result = await query('SELECT count(*) as cnt from Table id=?',[2]') return result[0].cntLa última línea da error, porque el campo 'cnt' es desconocido. ¿Cómo resolver mejor esto? El tipo de devolución siempre es Object[], pero el contenido del objeto depende de la consulta.
¿Alguna sugerencia sobre cómo mover mejor este código a TS?
Este es uno de esos lugares donde el sistema de tipo tiene que saber lo que está esperando. Naturalmente, debe tener mucho cuidado al hacer eso para asegurarse de que le está dando al sistema de tipos información precisa. Por lo general, desea hacer esto lo antes posible para que el código más complejo creado pueda confiar en los tipos.
Así que una respuesta en dos partes:
Cómo escribir query , y
Una sugerencia para evitar usar query directamente
Primero, puede hacer query sea una función genérica dándole un parámetro de tipo genérico para decirle qué tipo de objetos devuelve:
/** * Run a Query against the currently connected database * @param {string} sql Query to run * @param {any[]} Array of objects to be used in the prepared statement * @returns {Promise<ResultType[]>} Promise resolving after query is done. Returns an array of arrays with the result. */ const query = <ResultType extends object>(sql: string, params: any[]): Promise<ResultType[]> => { // −−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^ //Database stuff };Entonces, por ejemplo, podrías usarlo así:
const result = await query<{cnt: number}>('SELECT count(*) as cnt from Table id=?', [2]); // −−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^ return result[0].cnt;Pero aquí es donde entra el n.° 2: no haría eso excepto en una función que defina una vez y cuyo trabajo sea decirle cuántas filas coinciden:
async function getCountFromExampleTable(id: number): Promise<number> { const result = await query<{cnt: number}>('SELECT count(*) as cnt from Table id=?', [id]); return result[0].cnt; } Es realmente fácil ver getCountFromExampleTable y saber que el argumento de tipo que estamos dando ( {cnt: number} ) es correcto. Luego, cualquier código que necesite hacer esto puede reutilizar getCountFromExampleTable en lugar de tener que proporcionar ese tipo de argumento (y posiblemente hacerlo mal).
Probablemente desee que getCountFromExampleTable sea más genérico, tal vez aceptando el nombre de la tabla y los parámetros, pero entiende la idea. (Si es así, asegúrese de probar el nombre de la tabla para asegurarse de que no se pueda usar como parte de un ataque de inyección SQL y continúe usando una declaración preparada como ya lo está haciendo).
Tienes que usar genéricos
const query = <T>(sql: string, params: Object[]): T[] => { return [] } Pero la función puede devolver una matriz vacía. En este caso, puede verificar la propiedad cnt como esta
const result = await query('SELECT count(*) as cnt from Table id=?',[2]) return result[0]?.cnt