Programming Languages
TypeScript Narrowing Function Return Type Part 1
Narrowing return type of function is a little bit strange in TypeScript. So I share use cases below from bad to good 🙂
Use Cases
Usage 1 – Bad usage
Notice that even I call foo(0)
in line 8, when I hover mouse on foo
in line 8, popup shows val parameter type as string | number
union type. But it should show as number. So it is not narrowing parameter and return type of function.
Usage 2 – Bad usage
This usage narrows parameter and return type of function as literal type. But we need to narrow to type of parameter not value of parameter as literal type. Popup shows generic parameter type as 0
, that's why, parameter and return type becomes 0
.
Usage 3 – Good usage (function overloads)
Continue readingTypeScript Function Parameter Inference
TypeScript Conditional Types Good Usage Part 2
The aim of this code is simply to show related fields in data
field dependent on eventType
.
When ChangePassword
is selected in eventType
filed, then userName
, oldPassword
and newPassword
are becoming as required fields.
When ForgotPassword
is selected in eventType
field, then userName
is just becoming required.
const msg1 = publishMessage?.({
eventType: 'ChangePassword',
data: {
userName: '',
oldPassword: '',
newPassword: '',
},
subject: '',
topic: '',
});
const msg2 = publishMessage?.({
eventType: 'ChangePassword_Success',
data: {
emailSubject: 'Password Verification code is ######',
emailMessage: '',
},
subject: '',
topic: '',
});
const msg3 = publishMessage?.({
eventType: 'ForgotPassword',
data: {
userName: ''
},
subject: '',
topic: '',
});
const msg4 = publishMessage?.({
eventType: 'ForgotPassword_Success',
data: {
resetPasswordVerificationCode: '',
emailSubject: '',
emailMessage: ''
},
subject: '',
topic: '',
});
Continue reading TypeScript return Type inference issue in function type
Notice that addTodoV2
and addTodoV3
error for extra field WRONG_FIELD
, but addTodoV1
doesn't error for extra field in line 45.
TypeScript Creating Union Type from a Union Type
type TodoAction =
| { type: 'INIT' }
| { type: 'ADD_TODO', text: string }
| { type: 'REMOVE_TODO', id: string }
| { type: 'SET_COMPLETED', id: string };
type KeysOfUnion<T extends { type: any }> =
T extends { type: infer K } ? {} extends Omit<Extract<T, { type: K }>, 'type'> ? never : K : never;
function dispatch(actionType: KeysOfUnion<TodoAction>) { throw new Error('Not implemented') };
dispatch('ADD_TODO');
Continue reading TypeScript Separate Union Type Fields
My aim is to create a function which has two parameters. First one is union value of type
field in Action
and second parameter should force developer to use specific object in Action
.
For example, assume that we have this function function dispatch(actionType, payload){}
dispatch('SIGN_OUT', { type: 'SIGN_OUT', userName: ''
});
or
dispatch('SIGN_IN_FAILURE', { type: 'SIGN_IN_FAILURE', error: '' });
type Action =
| { type: 'INIT' }
| { type: 'SYNC' }
| { type: 'SIGN_UP', userName: string, password: string, attributeList: Array<any> }
| { type: 'SIGN_IN', userName: string, pasword: string }
| { type: 'SIGN_IN_SUCCESS', accessToken: string }
| { type: 'SIGN_IN_FAILURE', error: string }
| { type: 'SIGN_OUT', userName: string }
| { type: 'FORGOT_PASSWORD', userName: string }
| { type: 'FORGOT_PASSWORD_SUCCESS', verificationCode: string }
| { type: 'CHANGE_PASSWORD', userName: string, oldPassword: string, newPassword: string }
| { type: 'CONFIRM_REGISTRATION', userName: string, confirmationCode: string }
| { type: 'RESEND_CONFIRMATION_CODE', userName: string }
| { type: 'DELETE_USER', userName: string }
| { type: 'GET_SESSION', userName: string }
| { type: 'GET_SESSION_SUCCESS', session: string };
type TodoAction =
| { type: 'INIT' }
| { type: 'ADD_TODO', text: string }
| { type: 'REMOVE_TODO', id: string }
| { type: 'SET_COMPLETED', id: string };
Continue reading TypeScript listen property value changes
Creating makeWatchedObject function with overloading
type PropEventSource<T> = {
on<K extends string & keyof T>(eventName: `${K}Changed`, callback: (newValue: T[K]) => void): void;
};
type ReturnType<T1> = T1 & PropEventSource<T1>;
function makeWatchedObject<T1>(obj1: T1): ReturnType<T1>
function makeWatchedObject<T1, T2>(obj1: T1, obj2: T2): ReturnType<T1 & T2>
function makeWatchedObject<T1, T2, T3>(obj1: T1, obj2: T2, obj3: T3): ReturnType<T1 & T2 & T3>
function makeWatchedObject<T1, T2, T3>(obj1: T1, obj2?: T2, obj3?: T3): any {
const events = new Map<string, (a: any) => void>();
const on_: PropEventSource<T1 & T2 & T3> = {
on(eventName: string, callback: (newValue: any) => void): void {
events.set(eventName, callback);
}
}
const obj = { ...obj1, ...obj2, ...obj3, ...on_ };
const proxyObj = new Proxy(obj, {
set: function (target, property, value, receiver) {
const event = events.get(`${property as string}Changed`);
if (event) {
event(value);
return true;
}
return Reflect.set(target, property, value, receiver);
}
});
return proxyObj;
}
Usage
const personInfo = { id: 1, firstName: 'kenan', lastName: 'hancer', age: 36 };
const personAddress = { country: 'UK', city: 'London' };
const personContact = { email: 'kh@kh.com', phone: '077111-33-33' };
let person = makeWatchedObject(personInfo, personAddress, personContact);
// works! 'newName' is typed as 'string'
person.on("firstNameChanged", (newName: string) => {
console.log(`new name is ${newName.toUpperCase()}`);
});
// works! 'newAge' is typed as 'number'
person.on("ageChanged", (newAge: number) => {
if (newAge < 0) {
console.log("warning! negative age");
}
});
person.age = 34;
TypeScript Function Conditional Type Good Usage
Example 1
type MessageOf<T> = T extends { message: infer MessageType } ? MessageType : never;
const getMessage = <T extends { message: any }>(obj: T): MessageOf<T> => obj.message;
const sms = { phone: '07733838', message: ['Hello World', 'Merhaba Dunya'] };
const smsMessage = getMessage(sms); // string[]
const email = { from: 'kh@kh.com', to: 'kkkk@kkkk.com', message: 'Hello World' };
const emailMessage = getMessage(email); // string
Example 1.1
Notice that line 3 in below code, T extends { message: unknown }
message field data type is unknown so returning value from function should be casted to any like obj.message as any
type MessageOf<T> = T extends { message: infer MessageType } ? MessageType : never;
const getMessage = <T extends { message: unknown }>(obj: T): MessageOf<T> => obj.message as any;
const sms = { phone: '07733838', message: ['Hello World', 'Merhaba Dunya'] };
const smsMessage = getMessage(sms); // string[]
const email = { from: 'kh@kh.com', to: 'kkkk@kkkk.com', message: 'Hello World' };
const emailMessage = getMessage(email); // string