强烈推荐:15道TypeScript练习题 [中篇]

微信扫一扫,分享到朋友圈

强烈推荐:15道TypeScript练习题 [中篇]

写在开头

  • 本次 TypeScript
    一共有15道题,由易道难,可以说是涵盖了所有的使用场景,入门容易,精通难
  • 之前的上集有8道题,没有看过的小伙伴,可以看这之前的文章:

  • 最近技术团队会马上入驻公众号,后期原创文章会不断增多,广告也有,但是大家请理解,这也是创作的动力,大部分收入是会用来发福利,上次就发了一百本书.有的广告还是可以白嫖的课程,点击进去看看也挺好

  • 强烈推荐:15道优秀的TypeScript练习题   (上集)

正式开始

  • 第八题,模拟动态返回数据,我使用的是泛型解题,此时不是最优解法, AdminsApiResponse
    DatesApiResponse
    可以进一步封装抽象成一个接口.有兴趣的可以继续优化

interface User {
type: 'user';
name: string;
age: number;
occupation: string;
}

interface Admin {
type: 'admin';
name: string;
age: number;
role: string;
}

type responseData = Date;

type Person = User | Admin;

const admins: Admin[] = [
{ type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
{ type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' },
];

const users: User[] = [
{
type: 'user',
name: 'Max Mustermann',
age: 25,
occupation: 'Chimney sweep',
},
{ type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
];

type AdminsApiResponse<T> =
| {
status: 'success';
data: T[];
}
| {
status: 'error';
error: string;
};


type DatesApiResponse<T> =
| {
status: 'success';
data: T;
}
| {
status: 'error';
error: string;
};

function requestAdmins(callback: (response: AdminsApiResponse<Admin>) => void) {
callback({
status: 'success',
data: admins,
});
}

function requestUsers(callback: (response: AdminsApiResponse<User>) => void) {
callback({
status: 'success',
data: users,
});
}

function requestCurrentServerTime(
callback: (response: DatesApiResponse<number>) => void
) {
callback({
status: 'success',
data: Date.now(),
});
}

function requestCoffeeMachineQueueLength(
callback: (response: AdminsApiResponse<User>) => void
) {
callback({
status: 'error',
error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.',
});
}

function logPerson(person: Person) {
console.log(
` - ${chalk.green(person.name)}, ${person.age}, ${
person.type === 'admin' ? person.role : person.occupation
}`
);
}

function startTheApp(callback: (error: Error | null) => void) {
requestAdmins((adminsResponse) => {
console.log(chalk.yellow('Admins:'));
if (adminsResponse.status === 'success') {
adminsResponse.data.forEach(logPerson);
} else {
return callback(new Error(adminsResponse.error));
}

console.log();

requestUsers((usersResponse) => {
console.log(chalk.yellow('Users:'));
if (usersResponse.status === 'success') {
usersResponse.data.forEach(logPerson);
} else {
return callback(new Error(usersResponse.error));
}

console.log();

requestCurrentServerTime((serverTimeResponse) => {
console.log(chalk.yellow('Server time:'));
if (serverTimeResponse.status === 'success') {
console.log(
` ${new Date(serverTimeResponse.data).toLocaleString()}`
);
} else {
return callback(new Error(serverTimeResponse.error));
}

console.log();

requestCoffeeMachineQueueLength((coffeeMachineQueueLengthResponse) => {
console.log(chalk.yellow('Coffee machine queue length:'));
if (coffeeMachineQueueLengthResponse.status === 'success') {
console.log(` ${coffeeMachineQueueLengthResponse.data}`);
} else {
return callback(new Error(coffeeMachineQueueLengthResponse.error));
}

callback(null);
});
});
});
});
}

startTheApp((e: Error | null) => {
console.log();
if (e) {
console.log(
`Error: "${e.message}", but it's fine, sometimes errors are inevitable.`
);
} else {
console.log('
Success!');
}
});
  • promisify
    promise
    void
    void
    

interface User {
type: 'user';
name: string;
age: number;
occupation: string;
}

interface Admin {
type: 'admin';
name: string;
age: number;
role: string;
}

type Person = User | Admin;

const admins: Admin[] = [
{ type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
{ type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' }
];

const users: User[] = [
{ type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
{ type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' }
];

type ApiResponse<T> = (
{
status: 'success';
data: T;
} |
{
status: 'error';
error: string;
}
);

function promisify<T>(fn: (callback: (arg: ApiResponse<T>) => void) => void): () => Promise<T>

function promisify(arg: unknown): unknown {
return null;
}

const oldApi = {
requestAdmins(callback: (response: ApiResponse<Admin[]>) => void) {
callback({
status: 'success',
data: admins
});
},
requestUsers(callback: (response: ApiResponse<User[]>) => void) {
callback({
status: 'success',
data: users
});
},
requestCurrentServerTime(callback: (response: ApiResponse<number>) => void) {
callback({
status: 'success',
data: Date.now()
});
},
requestCoffeeMachineQueueLength(callback: (response: ApiResponse<number>) => void) {
callback({
status: 'error',
error: 'Numeric value has exceeded Number.MAX_SAFE_INTEGER.'
});
}
};

const api = {
requestAdmins: promisify(oldApi.requestAdmins),
requestUsers: promisify(oldApi.requestUsers),
requestCurrentServerTime: promisify(oldApi.requestCurrentServerTime),
requestCoffeeMachineQueueLength: promisify(oldApi.requestCoffeeMachineQueueLength)
};

function logPerson(person: Person) {
console.log(
` - ${chalk.green(person.name)}, ${person.age}, ${person.type === 'admin' ? person.role : person.occupation}`
);
}

async function startTheApp() {
console.log(chalk.yellow('Admins:'));
(await api.requestAdmins()).forEach(logPerson);
console.log();

console.log(chalk.yellow('Users:'));
(await api.requestUsers()).forEach(logPerson);
console.log();

console.log(chalk.yellow('Server time:'));
console.log(` ${new Date(await api.requestCurrentServerTime()).toLocaleString()}`);
console.log();

console.log(chalk.yellow('Coffee machine queue length:'));
console.log(` ${await api.requestCoffeeMachineQueueLength()}`);
}

startTheApp().then(
() => {
console.log('Success!');
},
(e: Error) => {
console.log(`Error: "${e.message}", but it's fine, sometimes errors are inevitable.`);
}
);

  • 第十题,考察 declaer module
declare module 'str-utils' {
export function strReverse(arg:string): string;
export function strToLower(arg:string): string;
export function strToUpper(arg:string): string;
export function strRandomize(arg:string): string;
export function strInvertCase(arg:string): string;
}

import {
strReverse,
strToLower,
strToUpper,
strRandomize,
strInvertCase
} from 'str-utils';

interface User {
type: 'user';
name: string;
age: number;
occupation: string;
}

interface Admin {
type: 'admin';
name: string;
age: number;
role: string;
}

type Person = User | Admin;

const admins: Admin[] = [
{ type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
{ type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' },
{ type: 'admin', name: 'Steve', age: 40, role: 'Steve' },
{ type: 'admin', name: 'Will Bruces', age: 30, role: 'Overseer' },
{ type: 'admin', name: 'Superwoman', age: 28, role: 'Customer support' }
];

const users: User[] = [
{ type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
{ type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
{ type: 'user', name: 'Moses', age: 70, occupation: 'Desert guide' },
{ type: 'user', name: 'Superman', age: 28, occupation: 'Ordinary person' },
{ type: 'user', name: 'Inspector Gadget', age: 31, occupation: 'Undercover' }
];

const isAdmin = (person: Person): person is Admin => person.type === 'admin';
const isUser = (person: Person): person is User => person.type === 'user';

const nameDecorators = [
strReverse,
strToLower,
strToUpper,
strRandomize,
strInvertCase
];

function logPerson(person: Person) {
let additionalInformation: string = '';
if (isAdmin(person)) {
additionalInformation = person.role;
}
if (isUser(person)) {
additionalInformation = person.occupation;
}
const randomNameDecorator = nameDecorators[
Math.round(Math.random() * (nameDecorators.length - 1))
];
const name = randomNameDecorator(person.name);
console.log(
` - ${chalk.green(name)}, ${person.age}, ${additionalInformation}`
);
}

([] as Person[])
.concat(users, admins)
.forEach(logPerson);
  • 第十一题,我使用了泛型和 declare module
    解题,如果你有更优解法,可以跟我聊聊,自认为这题没毛病.上面那道题我说两个 interface
    可以进一步优化,也可以参考这个思路
type func = <T>(input: T[], comparator: (a: T, b: T) => number) => T | null;
type index<L> = <T>(input: T[], comparator: (a: T, b: T) => number) => L;
declare module 'stats' {
export const getMaxIndex: index<number>;
export const getMaxElement: func;
export const getMinIndex: index<number>;
export const getMinElement: func;
export const getMedianIndex: index<number>;
export const getMedianElement: func;
export const getAverageValue: func;
}

import {
getMaxIndex,
getMaxElement,
getMinIndex,
getMinElement,
getMedianIndex,
getMedianElement,
getAverageValue
} from 'stats';


interface User {
type: 'user';
name: string;
age: number;
occupation: string;
}

interface Admin {
type: 'admin';
name: string;
age: number;
role: string;
}

const admins: Admin[] = [
{ type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator' },
{ type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver' },
{ type: 'admin', name: 'Steve', age: 40, role: 'Steve' },
{ type: 'admin', name: 'Will Bruces', age: 30, role: 'Overseer' },
{ type: 'admin', name: 'Superwoman', age: 28, role: 'Customer support' }
];

const users: User[] = [
{ type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep' },
{ type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut' },
{ type: 'user', name: 'Moses', age: 70, occupation: 'Desert guide' },
{ type: 'user', name: 'Superman', age: 28, occupation: 'Ordinary person' },
{ type: 'user', name: 'Inspector Gadget', age: 31, occupation: 'Undercover' }
];

function logUser(user: User | null) {
if (!user) {
console.log(' - none');
return;
}
const pos = users.indexOf(user) + 1;
console.log(` - #${pos} User: ${chalk.green(user.name)}, ${user.age}, ${user.occupation}`);
}

function logAdmin(admin: Admin | null) {
if (!admin) {
console.log(' - none');
return;
}
const pos = admins.indexOf(admin) + 1;
console.log(` - #${pos} Admin: ${chalk.green(admin.name)}, ${admin.age}, ${admin.role}`);
}

const compareUsers = (a: User, b: User) => a.age - b.age;
const compareAdmins = (a: Admin, b: Admin) => a.age - b.age;
const colorizeIndex = (value: number) => chalk.red(String(value + 1));

console.log(chalk.yellow('Youngest user:'));
logUser(getMinElement(users, compareUsers));
console.log(` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register`);

console.log();

console.log(chalk.yellow('Median user:'));
logUser(getMedianElement(users, compareUsers));
console.log(` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register`);

console.log();

console.log(chalk.yellow('Oldest user:'));
logUser(getMaxElement(users, compareUsers));
console.log(` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register`);

console.log();

console.log(chalk.yellow('Average user age:'));
console.log(` - ${chalk.red(String(getAverageValue(users, ({age}: User) => age)))} years`);

console.log();

console.log(chalk.yellow('Youngest admin:'));
logAdmin(getMinElement(admins, compareAdmins));
console.log(` - was ${colorizeIndex(getMinIndex(users, compareUsers))}th to register`);

console.log();

console.log(chalk.yellow('Median admin:'));
logAdmin(getMedianElement(admins, compareAdmins));
console.log(` - was ${colorizeIndex(getMedianIndex(users, compareUsers))}th to register`);

console.log();

console.log(chalk.yellow('Oldest admin:'));
logAdmin(getMaxElement(admins, compareAdmins));
console.log(` - was ${colorizeIndex(getMaxIndex(users, compareUsers))}th to register`);

console.log();

console.log(chalk.yellow('Average admin age:'));
console.log(` - ${chalk.red(String(getAverageValue(admins, ({age}: Admin) => age)))} years`);

  • 第十二题,考察模块扩充模式和泛型

import 'date-wizard';

declare module 'date-wizard' {
// Add your module extensions here.
export function pad<T>(name: T): T;
export function dateDetails<T>(name: T): { hours: number };
}
//..
import * as dateWizard from 'date-wizard';
import './module-augmentations/date-wizard';


interface User {
type: 'user';
name: string;
age: number;
occupation: string;
registered: Date;
}

interface Admin {
type: 'admin';
name: string;
age: number;
role: string;
registered: Date;
}

type Person = User | Admin;

const admins: Admin[] = [
{ type: 'admin', name: 'Jane Doe', age: 32, role: 'Administrator', registered: new Date('2016-06-01T16:23:13') },
{ type: 'admin', name: 'Bruce Willis', age: 64, role: 'World saver', registered: new Date('2017-02-11T12:12:11') },
{ type: 'admin', name: 'Steve', age: 40, role: 'Steve', registered: new Date('2018-01-05T11:02:30') },
{ type: 'admin', name: 'Will Bruces', age: 30, role: 'Overseer', registered: new Date('2018-08-12T10:01:24') },
{ type: 'admin', name: 'Superwoman', age: 28, role: 'Customer support', registered: new Date('2019-03-25T07:51:05') }
];

const users: User[] = [
{ type: 'user', name: 'Max Mustermann', age: 25, occupation: 'Chimney sweep', registered: new Date('2016-02-15T09:25:13') },
{ type: 'user', name: 'Kate Müller', age: 23, occupation: 'Astronaut', registered: new Date('2016-03-23T12:47:03') },
{ type: 'user', name: 'Moses', age: 70, occupation: 'Desert guide', registered: new Date('2017-02-19T17:22:56') },
{ type: 'user', name: 'Superman', age: 28, occupation: 'Ordinary person', registered: new Date('2018-02-25T19:44:28') },
{ type: 'user', name: 'Inspector Gadget', age: 31, occupation: 'Undercover', registered: new Date('2019-03-25T09:29:12') }
];

const isAdmin = (person: Person): person is Admin => person.type === 'admin';
const isUser = (person: Person): person is User => person.type === 'user';

function logPerson(person: Person, index: number) {
let additionalInformation: string = '';
if (isAdmin(person)) {
additionalInformation = person.role;
}
if (isUser(person)) {
additionalInformation = person.occupation;
}
let registeredAt = dateWizard(person.registered, '{date}.{month}.{year} {hours}:{minutes}');
let num = `#${dateWizard.pad(index + 1)}`;
console.log(
` - ${num}: ${chalk.green(person.name)}, ${person.age}, ${additionalInformation}, ${registeredAt}`
);
}

console.log(chalk.yellow('All users:'));

([] as Person[])
.concat(users, admins)
.forEach(logPerson);

console.log();

console.log(chalk.yellow('Early birds:'));

([] as Person[])
.concat(users, admins)
.filter((person) => dateWizard.dateDetails(person.registered).hours < 10)
.forEach(logPerson);

  • find
    find
    Database
    

interface User {
_id: number;
name: string;
age: number;
occupation: string;
registered: string;
}

interface Admin {
_id: number;
name: string;
age: number;
role: string;
registered: string;
}


async function testUsersDatabase() {
const usersDatabase = new Database<User>(path.join(__dirname, 'users.txt'), [
'name',
'occupation',
]);

// $eq operator means "===", syntax {fieldName: {$gt: value}}
// see more https://docs.mongodb.com/manual/reference/operator/query/eq/
expect(
(await usersDatabase.find({ occupation: { $eq: 'Magical entity' } })).map(
({ _id }) => _id
)
).to.have.same.members([6, 8]);
expect(
(
await usersDatabase.find({
age: { $eq: 31 },
name: { $eq: 'Inspector Gadget' },
})
)[0]._id
).to.equal(5);

// $gt operator means ">", syntax {fieldName: {$gt: value}}
// see more https://docs.mongodb.com/manual/reference/operator/query/gt/
expect(
(await usersDatabase.find({ age: { $gt: 30 } })).map(({ _id }) => _id)
).to.have.same.members([3, 5, 6, 8]);

// $lt operator means "<", syntax {fieldName: {$lt: value}}
// see more https://docs.mongodb.com/manual/reference/operator/query/lt/
expect(
(await usersDatabase.find({ age: { $lt: 30 } })).map(({ _id }) => _id)
).to.have.same.members([0, 2, 4, 7]);

// $and condition is satisfied when all the nested conditions are satisfied: {$and: [condition1, condition2, ...]}
// see more https://docs.mongodb.com/manual/reference/operator/query/and/
// These examples return the same result:
// usersDatabase.find({age: {$eq: 31}, name: {$eq: 'Inspector Gadget'}});
// usersDatabase.find({$and: [{age: {$eq: 31}}, {name: {$eq: 'Inspector Gadget'}}]});
expect(
(
await usersDatabase.find({
$and: [{ age: { $gt: 30 } }, { age: { $lt: 40 } }],
})
).map(({ _id }) => _id)
).to.have.same.members([5, 6]);

// $or condition is satisfied when at least one nested condition is satisfied: {$or: [condition1, condition2, ...]}
// see more https://docs.mongodb.com/manual/reference/operator/query/or/
expect(
(
await usersDatabase.find({
$or: [{ age: { $gt: 90 } }, { age: { $lt: 30 } }],
})
).map(({ _id }) => _id)
).to.have.same.members([0, 2, 4, 7, 8]);

// $text operator means full text search. For simplicity this means finding words from the full-text search
// fields which are specified in the Database constructor. No stemming or language processing other than
// being case insensitive is not required.
// Syntax {$text: 'Hello World'} - this return all records having both words in its full-text search fields.
// It is also possible that queried words are spread among different full-text search fields.
expect(
(await usersDatabase.find({ $text: 'max' })).map(({ _id }) => _id)
).to.have.same.members([0, 7]);
expect(
(await usersDatabase.find({ $text: 'Hey' })).map(({ _id }) => _id)
).to.have.same.members([]);

// $in operator checks if entry field value is within the specified list of accepted values.
// Syntax {fieldName: {$in: [value1, value2, value3]}}
// Equivalent to {$or: [{fieldName: {$eq: value1}}, {fieldName: {$eq: value2}}, {fieldName: {$eq: value3}}]}
// see more https://docs.mongodb.com/manual/reference/operator/query/in/
expect(
(await usersDatabase.find({ _id: { $in: [0, 1, 2] } })).map(
({ _id }) => _id
)
).to.have.same.members([0, 2]);
expect(
(await usersDatabase.find({ age: { $in: [31, 99] } })).map(({ _id }) => _id)
).to.have.same.members([5, 8]);
}

async function testAdminsDatabase() {
const adminsDatabase = new Database<Admin>(
path.join(__dirname, 'admins.txt'),
['name', 'role']
);

expect(
(await adminsDatabase.find({ role: { $eq: 'Administrator' } })).map(
({ _id }) => _id
)
).to.have.same.members([0, 6]);
expect(
(
await adminsDatabase.find({
age: { $eq: 51 },
name: { $eq: 'Bill Gates' },
})
)[0]._id
).to.equal(7);

expect(
(await adminsDatabase.find({ age: { $gt: 30 } })).map(({ _id }) => _id)
).to.have.same.members([0, 2, 3, 6, 7, 8]);

expect(
(await adminsDatabase.find({ age: { $lt: 30 } })).map(({ _id }) => _id)
).to.have.same.members([5]);

expect(
(
await adminsDatabase.find({
$and: [{ age: { $gt: 30 } }, { age: { $lt: 40 } }],
})
).map(({ _id }) => _id)
).to.have.same.members([0, 8]);

expect(
(
await adminsDatabase.find({
$or: [{ age: { $lt: 30 } }, { age: { $gt: 60 } }],
})
).map(({ _id }) => _id)
).to.have.same.members([2, 5]);

expect(
(await adminsDatabase.find({ $text: 'WILL' })).map(({ _id }) => _id)
).to.have.same.members([4, 6]);
expect(
(await adminsDatabase.find({ $text: 'Administrator' })).map(
({ _id }) => _id
)
).to.have.same.members([0, 6]);
expect(
(await adminsDatabase.find({ $text: 'Br' })).map(({ _id }) => _id)
).to.have.same.members([]);

expect(
(await adminsDatabase.find({ _id: { $in: [0, 1, 2, 3] } })).map(
({ _id }) => _id
)
).to.have.same.members([0, 2, 3]);
expect(
(await adminsDatabase.find({ age: { $in: [30, 28] } })).map(
({ _id }) => _id
)
).to.have.same.members([4, 5]);
}

Promise.all([testUsersDatabase(), testAdminsDatabase()]).then(
() => console.log('All tests have succeeded, congratulations!'),
(e) => console.error(e.stack)
);


//Database

import * as fs from 'fs';

export type FieldOp =
| { $eq: string | number }
| { $gt: string | number }
| { $lt: string | number }
| { $in: (string | number)[] };

export type Query =
| { $and: Query[] }
| { $or: Query[] }
| { $text: string }
| ({ [field: string]: FieldOp } & {
$and?: never;
$or?: never;
$text?: never;
});

function matchOp(op: FieldOp, v: any) {
if ('$eq' in op) {
return v === op['$eq'];
} else if ('$gt' in op) {
return v > op['$gt'];
} else if ('$lt' in op) {
return v < op['$lt'];
} else if ('$in' in op) {
return op['$in'].includes(v);
}
throw new Error(`Unrecognized op: ${op}`);
}

function matches(q: Query, r: unknown): boolean {
if ('$and' in q) {
return q.$and!.every((subq) => matches(subq, r));
} else if ('$or' in q) {
return q.$or!.some((subq) => matches(subq, r));
} else if ('$text' in q) {
const words = q.$text!.toLowerCase().split(' ');
return words.every((w) => (r as any).$index[w]);
}
return Object.entries(q).every(([k, v]) => matchOp(v, (r as any)[k]));
}

export class Database<T> {
protected filename: string;
protected fullTextSearchFieldNames: string[];
protected records: T[];

constructor(filename: string, fullTextSearchFieldNames: string[]) {
this.filename = filename;
this.filename = filename;
this.fullTextSearchFieldNames = fullTextSearchFieldNames;
this.fullTextSearchFieldNames = fullTextSearchFieldNames;

const text = fs.readFileSync(filename, 'utf8');
const lines = text.split('\n');
this.records = lines
.filter((line) => line.startsWith('E'))
.map((line) => JSON.parse(line.slice(1)))
.map((obj) => {
obj.$index = {};
for (const f of fullTextSearchFieldNames) {
const text = obj[f];
for (const word of text.split(' ')) {
obj.$index[word.toLowerCase()] = true;
}
}
return obj;
});
}
async find(query: Query): Promise<T[]> {
return this.records.filter((r) => matches(query, r));
}
}

写在最后

  • 我是 Peter酱
    ,前端架构师 擅长跨平台和极限场景性能优化 不定期会给你们推送高质量原创文章,公众号回复: 加群
    即可加入群聊
  • vue React webpack weboscket redis 静态资源服务器
    http
    redux promise react-redux 微前端框架 ssr
    
  • 前端巅峰
    关注
    在看
    

Telegram现在可以让用户发送单个2GB文件 个人资料可用视频展现

上一篇

全球第一枚“央行数字货币”诞生:立陶宛你能想到吗?

下一篇

你也可能喜欢

强烈推荐:15道TypeScript练习题 [中篇]

长按储存图像,分享给朋友