mirror of
https://github.com/type-challenges/type-challenges.git
synced 2025-12-08 19:06:13 +00:00
feat: #12
This commit is contained in:
parent
2d2a0a644f
commit
11e4385368
@ -28,7 +28,7 @@ TypeScript 类型体操姿势合集
|
||||
> 点击下方徽章查看题目内容
|
||||
|
||||
<!--challenges-start-->
|
||||
<img src="https://img.shields.io/badge/%E7%AE%80%E5%8D%95-%20-90bb12" alt=" "/><br><a href="./questions/4-easy-pick/README.md" target="_blank"><img src="https://img.shields.io/badge/-%234%E3%83%BB%E5%AE%9E%E7%8E%B0%20Pick%3CT%2C%20K%3E-90bb12" alt="#4・实现 Pick<T, K>"/></a> <a href="./questions/7-easy-readonly/README.md" target="_blank"><img src="https://img.shields.io/badge/-%237%E3%83%BB%E5%AE%9E%E7%8E%B0%20Readonly%3CT%3E-90bb12" alt="#7・实现 Readonly<T>"/></a> <br><br><img src="https://img.shields.io/badge/%E4%B8%AD%E7%AD%89-%20-eaa648" alt=" "/><br><a href="./questions/2-medium-return-type/README.zh-CN.md" target="_blank"><img src="https://img.shields.io/badge/-%232%E3%83%BB%E8%8E%B7%E5%8F%96%E5%87%BD%E6%95%B0%E8%BF%94%E5%9B%9E%E7%B1%BB%E5%9E%8B-eaa648" alt="#2・获取函数返回类型"/></a> <a href="./questions/3-medium-omit/README.md" target="_blank"><img src="https://img.shields.io/badge/-%233%E3%83%BB%E5%AE%9E%E7%8E%B0%20Omit%3CT%2C%20K%3E-eaa648" alt="#3・实现 Omit<T, K>"/></a> <a href="./questions/8-medium-readonly-2/README.md" target="_blank"><img src="https://img.shields.io/badge/-%238%E3%83%BBReadonly%202-eaa648" alt="#8・Readonly 2"/></a> <a href="./questions/9-medium-deep-readonly/README.md" target="_blank"><img src="https://img.shields.io/badge/-%239%E3%83%BB%E6%B7%B1%E5%BA%A6%20Readonly-eaa648" alt="#9・深度 Readonly"/></a> <a href="./questions/10-medium-tuple-to-union/README.md" target="_blank"><img src="https://img.shields.io/badge/-%2310%E3%83%BB%E5%85%83%E7%BB%84%E8%BD%AC%E5%90%88%E9%9B%86-eaa648" alt="#10・元组转合集"/></a> <br><br><img src="https://img.shields.io/badge/%E5%9B%B0%E9%9A%BE-%20-red" alt=" "/><br><a href="./questions/5-hard-readonly-keys/README.md" target="_blank"><img src="https://img.shields.io/badge/-%235%E3%83%BB%E8%8E%B7%E5%8F%96%E5%8F%AA%E8%AF%BB%E5%AD%97%E6%AE%B5-red" alt="#5・获取只读字段"/></a> <br><br><img src="https://img.shields.io/badge/%E5%9C%B0%E7%8B%B1-%20-b11b8d" alt=" "/><br><a href="./questions/6-extreme-simple-vue/README.md" target="_blank"><img src="https://img.shields.io/badge/-%236%E3%83%BB%E7%AE%80%E5%8D%95%E7%9A%84%20Vue%20%E7%B1%BB%E5%9E%8B-b11b8d" alt="#6・简单的 Vue 类型"/></a> <br><details><summary>By Tags</summary><br><table><tbody></tbody></table></details>
|
||||
<img src="https://img.shields.io/badge/%E7%AE%80%E5%8D%95-%20-90bb12" alt=" "/><br><a href="./questions/4-easy-pick/README.md" target="_blank"><img src="https://img.shields.io/badge/-%234%E3%83%BB%E5%AE%9E%E7%8E%B0%20Pick%3CT%2C%20K%3E-90bb12" alt="#4・实现 Pick<T, K>"/></a> <a href="./questions/7-easy-readonly/README.md" target="_blank"><img src="https://img.shields.io/badge/-%237%E3%83%BB%E5%AE%9E%E7%8E%B0%20Readonly%3CT%3E-90bb12" alt="#7・实现 Readonly<T>"/></a> <br><br><img src="https://img.shields.io/badge/%E4%B8%AD%E7%AD%89-%20-eaa648" alt=" "/><br><a href="./questions/2-medium-return-type/README.zh-CN.md" target="_blank"><img src="https://img.shields.io/badge/-%232%E3%83%BB%E8%8E%B7%E5%8F%96%E5%87%BD%E6%95%B0%E8%BF%94%E5%9B%9E%E7%B1%BB%E5%9E%8B-eaa648" alt="#2・获取函数返回类型"/></a> <a href="./questions/3-medium-omit/README.md" target="_blank"><img src="https://img.shields.io/badge/-%233%E3%83%BB%E5%AE%9E%E7%8E%B0%20Omit%3CT%2C%20K%3E-eaa648" alt="#3・实现 Omit<T, K>"/></a> <a href="./questions/8-medium-readonly-2/README.md" target="_blank"><img src="https://img.shields.io/badge/-%238%E3%83%BBReadonly%202-eaa648" alt="#8・Readonly 2"/></a> <a href="./questions/9-medium-deep-readonly/README.md" target="_blank"><img src="https://img.shields.io/badge/-%239%E3%83%BB%E6%B7%B1%E5%BA%A6%20Readonly-eaa648" alt="#9・深度 Readonly"/></a> <a href="./questions/10-medium-tuple-to-union/README.md" target="_blank"><img src="https://img.shields.io/badge/-%2310%E3%83%BB%E5%85%83%E7%BB%84%E8%BD%AC%E5%90%88%E9%9B%86-eaa648" alt="#10・元组转合集"/></a> <a href="./questions/12-medium-chainable-options/README.zh-CN.md" target="_blank"><img src="https://img.shields.io/badge/-%2312%E3%83%BB%E5%8F%AF%E4%B8%B2%E8%81%94%E6%9E%84%E9%80%A0%E5%99%A8-eaa648" alt="#12・可串联构造器"/></a> <br><br><img src="https://img.shields.io/badge/%E5%9B%B0%E9%9A%BE-%20-red" alt=" "/><br><a href="./questions/5-hard-readonly-keys/README.md" target="_blank"><img src="https://img.shields.io/badge/-%235%E3%83%BB%E8%8E%B7%E5%8F%96%E5%8F%AA%E8%AF%BB%E5%AD%97%E6%AE%B5-red" alt="#5・获取只读字段"/></a> <br><br><img src="https://img.shields.io/badge/%E5%9C%B0%E7%8B%B1-%20-b11b8d" alt=" "/><br><a href="./questions/6-extreme-simple-vue/README.md" target="_blank"><img src="https://img.shields.io/badge/-%236%E3%83%BB%E7%AE%80%E5%8D%95%E7%9A%84%20Vue%20%E7%B1%BB%E5%9E%8B-b11b8d" alt="#6・简单的 Vue 类型"/></a> <br><details><summary>By Tags</summary><br><table><tbody></tbody></table></details>
|
||||
<!--challenges-end-->
|
||||
|
||||
## 推荐读物
|
||||
|
||||
32
questions/12-medium-chainable-options/README.md
Normal file
32
questions/12-medium-chainable-options/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
<!--info-header-start--><h1>Chainable Options <img src="https://img.shields.io/badge/-medium-eaa648" alt="medium"/> <img src="https://img.shields.io/badge/-%23application-999" alt="#application"/></h1><blockquote><p>by Anthony Fu <a href="https://github.com/antfu" target="_blank">@antfu</a></p></blockquote><a href="https://type-challenges.netlify.app/case/12/play" target="_blank"><img src="https://img.shields.io/badge/-Take%20the%20Challenge-3178c6?logo=typescript" alt="Take the Challenge"/></a> <br><br><!--info-header-end-->
|
||||
|
||||
Chainable options are commonly used in Javascript. But when we switch to Typescript, can you properly type it?
|
||||
|
||||
In this challenge, you need to type an object or a class - whatever you like - to provide two function `option(key, value)` and `get()`. In `option`, you can extend the the current config type by the given key and value. We should about to access the final result via `get`.
|
||||
|
||||
For example
|
||||
|
||||
```ts
|
||||
declare const config: Chainable
|
||||
|
||||
const result = config
|
||||
.option('foo', 123)
|
||||
.option('name', 'type-challenges')
|
||||
.option('bar', { value: 'Hello World' })
|
||||
.get()
|
||||
|
||||
// expect the type of result to be:
|
||||
interface Result {
|
||||
foo: number
|
||||
name: string
|
||||
bar: {
|
||||
value: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You don't need to write any js/ts logic to handle the problem - just in type level.
|
||||
|
||||
You can assume that `key` only accept `string` and the `value` and be anything - just leave it as-is. Same `key` won't be passed twice.
|
||||
|
||||
<!--info-footer-start--><a href="../../README.md" target="_blank"><img src="https://img.shields.io/badge/-Back-grey" alt="Back"/></a> <a href="https://type-challenges.netlify.app/case/12/solutions" target="_blank"><img src="https://img.shields.io/badge/-Check%20out%20Solutions-de5a77?logo=awesome-lists&logoColor=white" alt="Check out Solutions"/></a> <a href="https://type-challenges.netlify.app/case/12/answer" target="_blank"><img src="https://img.shields.io/badge/-Share%20your%20Solutions-green" alt="Share your Solutions"/></a> <!--info-footer-end-->
|
||||
32
questions/12-medium-chainable-options/README.zh-CN.md
Normal file
32
questions/12-medium-chainable-options/README.zh-CN.md
Normal file
@ -0,0 +1,32 @@
|
||||
<!--info-header-start--><h1>可串联构造器 <img src="https://img.shields.io/badge/-%E4%B8%AD%E7%AD%89-eaa648" alt="中等"/> </h1><blockquote><p>by Anthony Fu <a href="https://github.com/antfu" target="_blank">@antfu</a></p></blockquote><a href="https://type-challenges.netlify.app/case/12/play/zh-CN" target="_blank"><img src="https://img.shields.io/badge/-%E6%8E%A5%E5%8F%97%E6%8C%91%E6%88%98-3178c6?logo=typescript" alt="接受挑战"/></a> <br><br><!--info-header-end-->
|
||||
|
||||
在 JavaScript 中我们很常会使用可串联(Chainable/Pipeline)的函数构造一个对象,但在 TypeScript 中,你能合理的给他附上类型吗?
|
||||
|
||||
在这个挑战中,你可以使用任意你喜欢的方式实现这个类型 - Interface, Type 或 Class 都行。你需要提供两个函数 `option(key, value)` 和 `get()`。在 `option` 中你需要使用提供的 key 和 value 扩展当前的对象类型,通过 `get` 获取最终结果。
|
||||
|
||||
例如
|
||||
|
||||
```ts
|
||||
declare const config: Chainable
|
||||
|
||||
const result = config
|
||||
.option('foo', 123)
|
||||
.option('name', 'type-challenges')
|
||||
.option('bar', { value: 'Hello World' })
|
||||
.get()
|
||||
|
||||
// 期望 result 的类型是:
|
||||
interface Result {
|
||||
foo: number
|
||||
name: string
|
||||
bar: {
|
||||
value: string
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
你只需要在类型层面实现这个功能 - 不需要实现任何 TS/JS 的实际逻辑。
|
||||
|
||||
你可以假设 `key` 只接受字符串而 `value` 接受任何类型,你只需要暴露它传递的类型而不需要进行任何处理。同样的 `key` 只会被使用一次。
|
||||
|
||||
<!--info-footer-start--><a href="../../README.zh-CN.md" target="_blank"><img src="https://img.shields.io/badge/-%E8%BF%94%E5%9B%9E%E9%A6%96%E9%A1%B5-grey" alt="返回首页"/></a> <a href="https://type-challenges.netlify.app/case/12/solutions" target="_blank"><img src="https://img.shields.io/badge/-%E6%9F%A5%E7%9C%8B%E8%A7%A3%E7%AD%94-de5a77?logo=awesome-lists&logoColor=white" alt="查看解答"/></a> <a href="https://type-challenges.netlify.app/case/12/answer/zh-CN" target="_blank"><img src="https://img.shields.io/badge/-%E5%88%86%E4%BA%AB%E4%BD%A0%E7%9A%84%E8%A7%A3%E7%AD%94-green" alt="分享你的解答"/></a> <!--info-footer-end-->
|
||||
8
questions/12-medium-chainable-options/info.yml
Normal file
8
questions/12-medium-chainable-options/info.yml
Normal file
@ -0,0 +1,8 @@
|
||||
title: Chainable Options
|
||||
|
||||
author:
|
||||
name: Anthony Fu
|
||||
email: hi@antfu.me
|
||||
github: antfu
|
||||
|
||||
tags: application
|
||||
1
questions/12-medium-chainable-options/info.zh-CN.yml
Normal file
1
questions/12-medium-chainable-options/info.zh-CN.yml
Normal file
@ -0,0 +1 @@
|
||||
title: 可串联构造器
|
||||
4
questions/12-medium-chainable-options/template.ts
Normal file
4
questions/12-medium-chainable-options/template.ts
Normal file
@ -0,0 +1,4 @@
|
||||
type Chainable = {
|
||||
option(key: string, value: any): any
|
||||
get(): any
|
||||
}
|
||||
21
questions/12-medium-chainable-options/test-cases.ts
Normal file
21
questions/12-medium-chainable-options/test-cases.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Equal, Expect } from '@type-challenges/utils'
|
||||
|
||||
declare const a: Chainable
|
||||
|
||||
const result = a
|
||||
.option('foo', 123)
|
||||
.option('bar', { value: 'Hello World' })
|
||||
.option('name', 'type-challenges')
|
||||
.get()
|
||||
|
||||
type cases = [
|
||||
Expect<Equal<typeof result, Expected>>
|
||||
]
|
||||
|
||||
type Expected = {
|
||||
foo: number
|
||||
bar: {
|
||||
value: string
|
||||
}
|
||||
name: string
|
||||
}
|
||||
2
utils/index.d.ts
vendored
2
utils/index.d.ts
vendored
@ -15,6 +15,8 @@ export type Equal<VALUE extends EXPECTED, EXPECTED> = NotEqual<VALUE, EXPECTED>
|
||||
// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
|
||||
export type IsAny<T> = 0 extends (1 & T) ? true : false
|
||||
|
||||
export type Debug<T> = { [K in keyof T]: T[K] }
|
||||
|
||||
export type ExpectExtends<VALUE, EXPECTED> = EXPECTED extends VALUE ? true : false
|
||||
export type ExpectValidArgs<FUNC extends (...args: any[]) => any, ARGS extends any[]> = ARGS extends Parameters<FUNC>
|
||||
? true
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user