2017-10-14 3 views
1

J'ai suivi le manuel TypeScript pour implémenter des protections de type définies par l'utilisateur, mais je reçois toujours une erreur et je n'arrive pas à comprendre pourquoi. J'ai le sentiment que cela a quelque chose à voir avec mon utilisation de Object.keys mais je ne suis pas tout à fait sûr.Protections de type définies par l'utilisateur avec Object.keys

types.ts

type devices = 'Web' | 'iOS' | 'Android' | 'Gaming' | 'Mac' | 'PC'; 
type languages = 'Javascript' | 'PHP' | 'Ruby' | 'Python' | 'Java' | 'C#' | 'C++' | 'C' | 'Obj-C' | 'Swift'; 
type frameworks = 'React' | 'Angular' | 'Vue' | 'Ember' | 
    'Laravel' | 'Symfony' | 'CakePHP' | 'Yii' | 'Phalcon' | 
    'Rails' | 'Sinatra' | 'Padrino' | 'Hanami' | 'NYNY' | 'Django' | 'TurboGears' | 'web2py' | 'Pylons' | 
    'SpringMVC' | 'JSF' | 'GWT' | 'Spring Boot' | 'Grails'| 
    'ASP.NET' | 'Nancy'; 
type backends = 'Express' | 'Koa' | 'Mojito' | 'Meteor' | 'Sails'; 
export interface Proficiencies { 
    technology: devices | languages | frameworks | backends; 
    proficiency: 0 | 0.5 | 1 | 1.5 | 2 | 2.5 | 3 | 3.5 | 4 | 4.5 | 5; 
} 

export interface SurveyResponse { 
    [index: string]: string[] | Proficiencies[]; 
    devices: devices[]; 
    languages: languages[]; 
    frameworks: frameworks[]; 
    backends: backends[]; 
    proficiencies: Proficiencies[]; 
} 

main.ts

import { SurveyResponse, Proficiencies } from 'types.ts'; 

export const percentageMatch = (array1: string[], array2: string[]): number => { 
    const numberOfMatches: number = array1.reduce((accumulator, item) => { 
     if (array2.includes(item)) { 
      return accumulator + 1; 
     } 
     return accumulator; 
    }, 0); 
    return (numberOfMatches/array1.length) || 0; 
}; 

export const proficienciesScore = (proficiencies1: Proficiencies[], proficiencies2: Proficiencies[]): number => { 
    return 1; 
}; 

export const matchScore = (survey1: SurveyResponse, survey2: SurveyResponse): number => { 
    const categoryHighestScores: { [index: string]: number } = { 
     devices: 0.15, 
     languages: 0.15, 
     frameworks: 0.15, 
     backends: 0.15, 
     proficiencies: 0.40 
    }; 

    const isProficienciesArray = (array: string[] | Proficiencies[]): array is Proficiencies[] => { 
     return (<Proficiencies[]>array)[0].technology !== undefined; 
    }; 

    const categoryScores: number[] = Object.keys(survey1).map(category => { 
     if (isProficienciesArray(survey1[category])) { 
      return proficienciesScore(survey1[category], survey2[category]) * categoryHighestScores[category]; 
     } 
     return percentageMatch(survey1[category], survey2[category]) * categoryHighestScores[category]; 
    }); 

    return categoryScores.reduce((accumulator, score): number => { 
     return accumulator + score; 
    }, 0); 
}; 

Je reçois des erreurs dans mon categoryScores constant, plus précisément

Argument of type 'string[] | Proficiencies[]' is not assignable to parameter of type 'Proficiencies[]'. 
    Type 'string[]' is not assignable to type 'Proficiencies[]'. 
    Type 'string' is not assignable to type 'Proficiencies'. 

et

Argument of type 'string[] | Proficiencies[]' is not assignable to parameter of type 'string[]'. 
    Type 'Proficiencies[]' is not assignable to type 'string[]'. 
    Type 'Proficiencies' is not assignable to type 'string'. 

la fois lié au premier paramètre survey1[category] de la fonction appelle proficienciesScore et percentageMatch respectivement. Je pense que j'ai mis en place correctement mon garde de type défini par l'utilisateur (isProficienciesArray) et je me demande où est le problème.

Répondre

1

j'ai pu le faire fonctionner en faisant

const categoryScores: number[] = Object.keys(survey1).map(category => { 
    const x = survey1[category]; 
    const y = survey2[category]; 
    if (isProficienciesArray(x) && isProficienciesArray(y)) { 
     return proficienciesScore(x, y) * categoryHighestScores[category]; 
    } 
    else if (!isProficienciesArray(x) && !isProficienciesArray(y)) 
     return percentageMatch(x, y) * categoryHighestScores[category]; 
}); 
+0

Bizarre. Je me demande pourquoi c'est. Pour l'instant c'est une solution de contournement, mais j'aimerais vraiment comprendre pourquoi mon code ne fonctionne pas. Je commence à penser qu'il pourrait s'agir d'un bug TypeScript. Si c'est le cas, je vais le signaler sur leur repo Github. – neoflash

+1

Je pense qu'il y a déjà un problème pour cela à [Microsoft/TypeScript # 17567] (https://github.com/Microsoft/TypeScript/issues/17567). – jcalz

1

solution Aziz fait l'affaire en ce qui concerne la limitation du compilateur dactylographiée. OMI, d'autres approches impliquent de modifier le code plus profondément. L'étape suivante pourrait être de supprimer l'instruction if (isProficienciesArray...)/else (voir la règle Object Calisthenics # 2). Cela aidera l'inférence de type par le compilateur TypeScript et améliorera le code si vous aimez ce genre de code.

Dans le soufflet d'extrait, il est fait à l'aide d'un dictionnaire/carte comme la variable categoryHighestScores actuelle mais encapsulant le calcul partition/match:

  • proficienciesScore pour Proficiencies[],
  • matchScore pour les autres string[] tableaux.

La carte est appelée matchFnMap. La fonction populateMatchFnMapWith() permet de simplifier sa création.

// types.ts 
// [...] 

const emptySurveyResponse: SurveyResponse = { 
    devices: [], 
    languages: [], 
    frameworks: [], 
    backends: [], 
    proficiencies: [] 
}; 

export const surveyResponseCategories = Object.keys(emptySurveyResponse); 

// main.ts 
// [...] 

interface MatchFn<T> { 
    (a: T, b: T): number; 
} 

const matchFnMap: {[category: string]: MatchFn<SurveyResponse>} = {}; 

function populateMatchFnMapWith(category: string, categoryHighestScore: number, match: MatchFn<string[]|Proficiencies[]>) { 
    matchFnMap[category] = 
     (survey1: SurveyResponse, survey2: SurveyResponse) => 
      categoryHighestScore * 
       match(survey1[category], 
         survey2[category]); 
} 

populateMatchFnMapWith('devices',  0.15, percentageMatch); 
populateMatchFnMapWith('languages',  0.15, percentageMatch); 
populateMatchFnMapWith('frameworks', 0.15, percentageMatch); 
populateMatchFnMapWith('backends',  0.15, percentageMatch); 
populateMatchFnMapWith('proficiencies', 0.40, proficienciesScore); 

const matchScore = (survey1: SurveyResponse, survey2: SurveyResponse) => 
    surveyResponseCategories.reduce((total, category) => { 
     const computeScore = matchFnMap[category]; 
     return total + computeScore(survey1, survey2); 
    }, 0); 

Il s'agit toujours d'un style de programmation fonctionnel. Une étape supplémentaire impliquerait de réviser le modèle à un style plus OOP pour rassembler des données et des calculs.