用户定义的类型保护与Object.keys

时间:2017-10-14 22:09:58

标签: typescript types

我遵循TypeScript手册来实现用户定义的类型保护,但我仍然遇到错误,我无法弄清楚原因。我觉得它与我使用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);
};

我的categoryScores常量中出现错误,特别是

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'.

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'.

分别与survey1[category]proficienciesScore函数调用的第一个参数percentageMatch相关。我认为我已经正确实现了用户定义的类型保护(isProficienciesArray),并且我想知道问题出在哪里。

2 个答案:

答案 0 :(得分:1)

我能够通过

让它工作
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];
});

答案 1 :(得分:1)

Aziz解决方案可以解决TypeScript编译器限制问题。

IMO,其他方法意味着更深入地修改代码。下一步可能是删除if (isProficienciesArray...) / else语句(请参阅Object Calisthenics规则#2)。它将帮助TypeScript编译器进行类型推理,并在您喜欢这种代码时改进代码。

在下面的片段中,它使用字典/地图完成,如当前categoryHighestScores变量,但封装了分数/匹配计算:

  • proficienciesScore代表Proficiencies[]
  • matchScore用于其他string[]数组。

地图名为matchFnMap。函数populateMatchFnMapWith()有助于简化其创建。

// 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);

它仍处于功能编程风格。进一步的步骤意味着将模型修改为更多OOP样式以收集数据和计算。