Dość ciekawy przypadek, niby prosty, ale często TS w takich właśnie „niby prostych” przypadkach potrafi przyprawić o niezły mętlik w głowie. Zaczynajmy!

Ok, mamy taki interfejsik, bardzo prosty:

interface SimpleInter {
    a: number;
    b: number;
}

Teraz próbujemy go modyfikować:

interface SimpleInter {
    a: number;
    b: number;
}

                     //SimpleInter     //"a"|"b"                //number
function modifySimple(obj: SimpleInter, key: keyof SimpleInter, val: SimpleInter[keyof SimpleInter]){
    obj[key] = val;
}

Na razie żadnych błędów. Ok, teraz dodajmy jakiś inny typ do niego:

interface SimpleInter {
    a: number;
    b: number;
    c: string;
}
                     //SimpleInter     //"a"|"b"|"c"             //string|number
function modifySimple(obj: SimpleInter, key: keyof SimpleInter, val: SimpleInter[keyof SimpleInter]){
    obj[key] = val; //err
}

//Type 'string | number' is not assignable to type 'never'.

Ok, jak z tego wybrnąć? Możemy użyć typu generycznego:

interface SimpleInter {
    a: number;
    b: number;
    c: string;
}
                     
function modifySimple<T extends SimpleInter, K extends keyof T>
(obj: T, key: K, val: T[K]){
    obj[key] = val; 
}

Ok, a teraz nieco inne podejście, czyli niemutowalność. Taki interfejsik:

interface User1 {
    name: string;
    age: number;
    isMarried: boolean;
};

Obiekt tego interfejsu:

let jane1: User1 = {
    name: "Jane",
    age: 30,
    isMarried: true
}

Funkcja zmieniająca ten obiekt:

function changeUser(usrOld: User1, newFields: Partial<User1>): User1  {
    return {...usrOld, ...newFields};
}

Całość:

interface User1 {
    name: string;
    age: number;
    isMarried: boolean;
};

let jane1: User1 = {
    name: "Jane",
    age: 30,
    isMarried: true
}

function changeUser(usrOld: User1, newFields: Partial<User1>): User1  {
    return {...usrOld, ...newFields};
}

jane1 = changeUser(jane1, {isMarried: false});

console.log(jane1);
//{ name: 'Jane', age: 30, isMarried: false }

Ok, teraz generyczny odpowiednik:

interface User1 {
    name: string;
    age: number;
    isMarried: boolean;
};

let jane1: User1 = {
    name: "Jane",
    age: 30,
    isMarried: true
}

function changeUser(usrOld: User1, newFields: Partial<User1>): User1  {
    return {...usrOld, ...newFields};
}

function changeObject<T extends object>(obj: T, newFields: Partial<T>) : T {
    return {...obj, ...newFields}
}

// jane1 = changeUser(jane1, {isMarried: false});
jane1 = changeObject(jane1, {isMarried: false});

console.log(jane1);
//{ name: 'Jane', age: 30, isMarried: false }

Może i nie przesadnie trudne, ale takie ćwiczenia trzeba rozwiązywać. Wtedy dopiero wychodzi, ile wiemy, a ile nam się jeszcze miesza.

Do następnego razu!