Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
TypeScript 練習問題集

TypeScript 練習問題集(Practice)

更新情報

- 問題を追加 (8/25)
- 問題を追加 (8/7)
- 問題を追加 50問完成 (7/28)
- 問題を追加 (7/27)
- 問題を追加 (7/20)
- 画像を追加 (7/13)
  • こちらは @bukotsunikki自身が学習するための問題集です。

  • 以前JavaScript問題集を作りましたがそれのTypeScript版です。

  • 随時更新して行きますのでスターつけていただけると励みになります。

TODO

  • まだまだ文中の文言、整っていません。とりあえず50問作ってその後個々に対して内容精査

問1

こちら

interface Foo {
    bar: string;
    baz: number;
}

Fooが持つプロパティ全てoptionalにしてください

type PartialFoo = Partial<Foo>;

問2

こちら

type Foo = {
    name?: string;
    age?: number;
}

Fooが持つプロパティ全てreadOnlyにしてください

type RequireA = Required<Foo>;

問3

こちら

type Foo = {
    name?: string;
    age?: number;
}

Fooからnameだけを取得したtypeを作ってください

type Picked = Pick<Foo, "name">

問4

こちら

type Foo = {
    name?: string;
    age?: number;
}

Fooからageを省略した型を作ってください

type Omited = Omit<Foo, "age">;

// Omited {
//    name?: string | undefined;
// }

問5

こちら

const user = { name: "kenji", age: 98};

のuserに推論される型は何ですか。またその理由を教えてください。

{name: string, age: number}

JavaScriptのオブジェクトはconstであれ(freezeしない限り)書き込みが可能です。
それそれのプロパティ値はあとで書き込めるようにwindeningされ、それぞれのプロパティの型はプリミティブ型になります。
これをそれぞれのプロパティをリテラル型にするには
as constか型注釈をすることです。(下記playground)

問6

T extends U ? X : Y はどのような意味になりますか

// Conditional types

T extends U ? X : Y;
// TがUに代入可能ならXを、そうではない場合Yを返す
// T型がU型と互換性あるならXを、そうでない場合Yを返す
// T型がU型のサブタイプならXを、そうでない場合Yを返す
// T型がU型の部分型ならXを、そうでない場合Yを返す

// ()=> void extends Functionは互換性がある
// "hello!" extends String

問7

下記

interface Part {
  name: string,
  age: number,
  add(): number
}

メソッド名だけ取り出した型を作ってください

interface Part {
  name: string,
  age: number,
  add(): number
}

const obj = {
  name: "kenji",
  age: 99,
  add: () => 1 * 2
}

type FunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? K : never
}[keyof T]

type result = FunctionPropertyNames<Part>

playground

問8 wip

neverとはどんな型ですか

・絶対にreturnされない関数
・常にthrowされる関数

例えば

function foo(x: string | number): boolean {
  if (typeof x === "string") {
    return true;
  } else if (typeof x === "number") {
    return false;
  }
  return fail("Unexhaustive!"); //ここはreturnされないので neverが返っている。boolean型が返る関数なのでError
}

function fail(message: string): never { throw new Error(message); } // 常にthrowされるのでnever型が返る


// 1. 型推論の結果、取り得る型が無い状態になった時の変数・メンバーに付けられる型

const bool = true
if(bool){
    const a = bool; // boolean
} else {
    const b = bool; // never
}

// 2. return文が無く、かつ(無限ループなどで)関数末尾に到達しない関数/アロー関数に対して推論される戻り値の型

問9

これは

(...args: any[]) => any

どういう意味ですか?

// 関数ならなんでもOK

Type inference in Conditional types

問10

これは

type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

なにをする型か説明してください(とくにinfer)

// Tが関数ならその関数の戻り値をRにキャプチャしてそれを返す

問11

非同期の中身を取る型を書いてください

type ResolvedType<T> =
T extends Promise<infer R> ? R :
T extends Observable<infer R> ? R :
T;

問12

Nullableな型を作ってください

type PropNullable<T> = {[P in keyof T]: T[P] | null};

interface User {name: string, age: number, money: null}

const obj:PropNullable<User> = {name: "kenji", age: 99, money: null}

問13

こちら

let createObj = (obj) => {
   let o = {}
   for(const key in obj){
     o[key] = String(obj[key]);
   }
   return o;
}

const anotherFun = createObj;

のcreateObj型を定義してください

 let createObj = <T>(obj:T): { [P in keyof T]: string} => {
    let o = {} as {[P in keyof T]: string}
    for(const key in obj){
       o[key] = String(obj[key]);
    }
    return o;
 }

const anotherFun = createObj;

問14 TODO

neverunion型の中では消えるを問題にする

問15 こちらの

arr(["a", 1]);

どんな要素の配列が渡されてもいいような型を作ってください。

let arr = <T extends any[]>(...rest: T) => {
   return rest
}

arr(["a", 1]);

問16

wideningとはなんですか説明してください。

型推論によってリテラル型を変数に代入した際にプリミティブ型に拡張されること
例えば、

let a = "a" //string

constでは再代入はできないので`a`型というリテラル型になるが、letは再代入可能なので推論は拡張されプリミティブ型になる

let a: "a" = "a" //"a"

このように型注釈をつけることでa型というリテラル型になる(型注釈はwideningより優先される)

問17

下記

let a;

if (Math.random() < 0.5) {
    a = 123;
}

console.log(a); // a は number | undefined 型

aがunion型になるのはなぜか

a宣言時に初期化されていない & 型注釈されていないことでif文がtrueになるまでundefined、その後aを参照すると`number``undefined`の可能性があるから。初期化なし、型注釈なしの変数はコンテキストによって型が変わる(アンチパターン)

問18

letで変数初期化時に型注釈としてunion型(string | null) にする場合の挙動を説明してください。

//WIP

let nullOrString: string | null = "string"

console.log(nullOrString.length) //string。union型が失われるが...

nullOrString = null //代入可能 string | nullになっている

nullOrString = "stringAgein"; // 注釈はstring | nullのまま

nullOrString = 123; // Error

playground

問19

こちら

let a = 1
const num = a && "hoge";

型推論は何ですか

0 | "hoge"

問20

WIP

型の絞り込み (type narrowing)

問21

WIP

return文はあるけどreturnせずに関数が終了する場合がある」 ->  例: string | undefinedreturn文がない」 -> void

問22 contextual typeがある状態というのはどういう状態のことですか

TypeSciriptは引数の型注釈で型を宣言しなくてはいけない、が、

型推論の時点で期待される型があらかじめわかっている状態(contextual tyipingがある状態)なら書かなくても良い

type Func = (arg: number) => number
const double: Func = function (num) { return num * 2}

numは本来型注釈を書かなくてはいけないが、文脈からdoubleに入る関数はFunc型でなくてはいけないことがわかっている。
これがcontextual typingがある状態

・文脈からわかる型。
・関数の引数や戻り値を文脈から推論させること。
・型推論の時点で期待される型があらかじめ分かっている場合を「contextual typeがある」という

これは以下のときに重要になる
・callback関数を別の変数に割り当てると、contextual typeがなくなる
・型引数を明示しない時、型推論が推論される順番を理解するとき

contextual typingが発生する場面
- 変数の型注釈によって型推論中の式の型があらかじめわかっている場合
- 関数引数のcontextual typing
- 関数の返値のcontextual typ

メモ

型引数の推論について 実行時に型引数を省略すると TやRといった型引数も一緒に推論される この場合引数から推論される

function apply<T, R>(value: T, func: (arg: T) => R): R {
  return func(value);
}

// res は string 型
const res = apply(100, num => String(num ** 2));
  • 呼び出し時引数から受け取り側の仮引数の型が決まる
  • 引数の型が決まってから関数の型が判明する

引数の型より型変数の推論ができていないといけない | 型引数の推論と、引数で関数を渡す場合のその引数の型推論はどのような順番で解決されるか

contextual typingが必要な引数だけ後回しにする

apply(100, num => String(num ** 2))

  1. contextual tyipingが不要な引数を先に型推論。100に対して型推論。numberを得る
  2. 得られた情報からTがnumberに推論される
  3. contextual typingが必要な引数を型推論する。 num => String(num ** 2)に対して型推論。 (num: number) => stringを得る このときcontextual tyingは (arg: T) => R型だがTは判明しているので (arg: number) => R
  4. 再び型引数を推論する型引数 Rstringに推論される

型引数TRの推論結果が決まるタイミングが異なる

Tの型引数の推論結果はその型引数が使われた時点で確定する

型推論結果は処理の順番に依存する

オーバーロードシグネチャ

問23

こちらのコード

type MyObj = {
  name?: string;
}

function foo(obj: MyObj): string {
  return obj.name!.slice(0, 5);
}

!の意味、危険性について説明をしてください。

問24

こちらの

function isStringArray(obj: unknown): obj is Array<string> {
  return Array.isArray(obj) && obj.every(value => typeof value === "string");
}

function foo(obj: unknown) {
  if (isStringArray(obj)) {
    obj.push("abcde");
  }
}

obj is Array<string>の説明をしてください

このように返り値をobj is Array<string>のように宣言している関数は
真偽値が返らなくてはならず、
isStringArray関数の返値がtrueならobj`Array<string>型`が返ることを指定しています。

問25

こちらの

(num: number) => { // 'num' is declared but never used.
  return "return";
}

'num' is declared but never used.をdisableしてください

(_num: number) => { // ok, _ pre
  return "return";
}

問26

WIP enumの使い方 1

enum Weekend {
  Friday = 1,
  Saturday,
  Sunday
}

function getDate(Day: string): Weekend {
    if ( Day === 'TGIF') {
        return Weekend.Friday;
    }
    return Weekend.Saturday
 }
let DayType: Weekend = getDate('TGIF');


// string enum
enum Weekend {
  Friday = 'FRIDAY',
  Saturday = 'SATURDAY',
  Sunday ='SUNDAY'
}
const value = someString as Weekend;
if (value === Weekend.Friday || value === Weekend.Sunday){
    console.log('You choose a weekend');
    console.log(value);
}

// enumを使用するのが最適かつ非常に効率的な場所と適切な使用例があります

// 列挙型は、他のTypeScriptデータ型と同じように、配列の初期化内で使用できます。
// これは簡単な例です。

enum NigerianLanguage {
  Igbo,
  Hause,
  Yoruba
}

//can be used in array initialisation
let citizen = {
  Name: 'Ugwunna',
  Age: 75,
  Language: NigerianLanguage.Igbo
}

列挙型は理想的には、週の7日のように、定数と見なすことができる明確な値がある状況で使用されるべきです。
enum Days {
  Sunday = 1,
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday
}

// 列挙型は、文字列または定数を変数で表す必要がある場所でも使用できます。

// TypeScriptの列挙型は、次の場所では使用しないでください。

// 列挙型メンバーの値を再割り当てまたは変更する予定の場合、enumは型保証されているため、再割り当て時にコンパイルエラーが返されます。
// 動的な値を記録したい場合、enumは有限項目に最も適しており、その背後にある一般的な考え方はユーザー定義の定数システムを作成するのを助けることでした
// 列挙型を変数として使用することはできません。そうするとエラーが返されます

問27

WIP unknown type

unknown type

問28

こちらのエラーをnumberとstringに対応できるように修正してください。

function eachItem(val: number, i: number) {
    return val.toExponential(3);
}
const arr = [4, "fafa", 6];
arr.map(eachItem);

問29

こちら

function fa(callback, e){
  return callback(e);
}

const fun = (e) => 1 * e;
const v = fa(fun, 1);

Parameter 'callback' implicitly has an 'any' type.Parameter 'e' implicitly has an 'any' type. に対応してください(callbackに型付けしてください)

interface Fun {(e: number): number;}

function fa(callback:Fun, e: number){
  return callback(e);
}
const fun:Fun = (e) => 1 * e;
const v = fa(fun, 1);

問30

こちら

type YukarinoChi = "tokyo";
type OnlySpecificProperty<T> = Pick<T, {[K in keyof T]: T[K] extends YukariNoChi ? K : never}[keyof T]>;

の型を説明してください

// "tokyo"リテラル型を値としてもつプロパティだけを抜き出した型を定義しています

type YukariNoChi = "tokyo"

const obj = {
    name: "kenji",
    age: 99,
    born: "tokyo",
    live: "tokyo"
}
type Obj = {
    name: string,
    age: number,
    born: YukariNoChi,
    live: YukariNoChi
}

const obj2 = {
    born: "tokyo",
    live: "tokyo"
} as const

type OnlySpecificProperty<T> = Pick<T, {[K in keyof T]: T[K] extends YukariNoChi ? K : never}[keyof T]>;


function fun(onlyYukari: OnlySpecificProperty<Obj>){
    return onlyYukari
}
const result = fun(obj2); // Pick<Obj, "born | live">

playground

問31

stringとnullableな配列の型を作ってください

let arr: (string | null)[] = []

[playground](http://www.typescriptlang.org/play/index.html#code/MYewdgzgLgBAhgJwQLgBTQQSzAcxgHxjAFcAbUgSgG0BdGAXhloFgAoNxBAOgAdiIAFqhLkKAbjahIIUgFMupEDlSdxQA

問32

こちらの

type F = {
    foo: string;
    bar: number;
}
const E:F = { foo: "fafa", bar: "fafa"} //Error

定義元のFを直接編集せずに代入できるように型付けしてください

type F = {
    foo: string;
    bar: number;
}

const E:Record<keyof F, string> = { foo: "fafa", bar: "fafa"}

問33

type Exclude<T, U> の説明をしてください

ExcludeTUに代入可能ならnever、そうでない場合Tを返すconditionalTypeです

use case
type Q = Exclude<string | number, boolean | string | number>
//boolean

type Q = Exclude<string | number | undefined, any>
// never

問34

こちら

export defaut function person({ detail } : Person) {
  return <div>{detail.name}</div>
};

interface Person {
    id: number
    detail: Detail
}

person({detail: {name: "fafa"}, id: 1 });

はdetailが初期化された時 undefinedが渡って来てもいいように対応してください

export defaut function person({ detail = {}} : Person) { // error
  return <div>{detail.name}</div>
};
// このままだと
// Property 'name' is missing in type '{}' but required in type 'Detail'.
// になります

defaultaParameterを設定し、アサーションします

export defaut function person({ detail = {} as Detail} : Person) {
  return <div>{detail.name}</div>
};

問35

reactでsetStateをする際に

interface State {
 name: string
 age: number
}

this.setState({name: "kenji"}) // Error
this.setState({name: "kenji", age: this.state.age}); // ok

このように特定のState.propertyのみを渡すとエラーになる

全てのpropertyを渡さないでもいいようにしてください。

interface State {
  name?: string;
  age?: number;
}

問36

こちらは

interface User {
  id: string
}
interface AppUser {
  appName: "appName"
  appID: string
}
interface ServiceUser {
  serviceName: 'serviceName'
  serviceID: string
}
const user = {id: "1"}
const appUser = { appName: "appName", appID: "appId"} as const;
const serviceUser = { serviceName: "serviceName", serviceID: "serviceID"} as const


function a(o: ServiceUser | User | AppUser){
  return o
}

const result = a(user)

関数 aServiceUser or User or AppUsera に渡してそれを返す関数です。 期待型は ServiceUser | User | AppUser になっています。 これを それぞれ ServiceUserserviceIDUseridAppUserappIdを返す関数に直して、 期待型をstringにしてください

function a(o: ServiceUser | User | AppUser){
  if("serviceID" in o) return o.serviceID;
  if("appID" in o) return o.appID;
  return o.id;
}

const result = a(serviceUser) // string

playground

問37

WIP 問題文。

上の問題の

function a(o: ServiceUser | User | AppUser){
  if("serviceID" in o) return o.serviceID;
  if("appID" in o) return o.appID;
  return o.id;
}

を 独⾃定義 TypeGuardで型定義してください。(それぞれ isServiceisAppUser、任意でisUser関数を作り、ifのコンディション内で実行。返す値がそれぞれのプロパティを持つようにして、型付けされていることを確認してください)

const isService = (o: any): o is ServiceUser => {
    return o.serviceID === "serviceID";
}
const isAppUser = (o: any): o is AppUser => {
    return o.AppUser === "appUser";
}

type O = ServiceUser | User | AppUser;
function a(o: any){
    if(isService(o)) return o.serviceID;
    if(isAppUser(o)) return o.appID;
    return o.id; // User
}
const result = a(serviceUser)

playground

問38

こちら

const o = { name: "hoge" }
function a(o){
    return o
}

a(o)
a();

の defaultValueとany型に対応してください

const getDefaultProps = () => {
    return {name: "hoge"}
}
const defaultProps = getDefaultProps();
const o = {name: "hoge"}
function a(o = defaultProps){
    return o
}

a(o)
a();

playground

問39

こちらは

type Animal = { name: string, run: boolean }
type Bird = { name: string, fly: boolean }
const animal = { name: "tigger", run: true }
const bird = { name: "condol", fly: true }

type NotHumman = Animal | Bird

const a = (b:NotHumman) => {
    b.run
}

なぜコンパイルエラーになるのですか?説明してください

問40

こちら

declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(action: (done: DoneFn) => void,timeout?: number): void;

コールバックに渡す引数の数が違うのでオーバーライドしてあります。修正してください

declare function beforeAll(action: (done: DoneFn) => void, timeout?: number): void;

// コールバックの引数が違うだけでオーバーライドしないようにしましょう。
// コールバックがパラメータを無視することは常に正当です。渡って来なくても無視されるだけです。

問41

こちら

declare function fn(x: any): any;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;

var myElem: HTMLDivElement;
var x = fn(myElem); // x: any, wat?

修正してください。

declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: any): any;

var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)

// TypeScript は関数呼び出し時に最初にマッチしたオーバーライドを選ぶので、any だと最初に必ずマッチしてしまう

問42

こちら

interface Example {
  diff(one: string): number;
  diff(one: string, two: string): number;
  diff(one: string, two: string, three: boolean): number;
}

修正してください

interface Example {
  diff(one: string, two?: string, three?: boolean): number;
}

// 返る型が同じ場合、可能な限りオプショナルを使いましょう。

問43

こちら

let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

// 1
y = x;

// 2
x = y;

12はそれぞれエラーになりますかなりませんか

y = x; // ok!
x = y; // error

//返る型が同じ場合、引数の数は関係ない。代入元が代入先の引数を持っているかどうか。

// example
type F = (name: string, n: number) => void;
let f: F = (value: string) => {
  //実装は使わないでもokだが
  console.log("here");
};
f("how", 2); //渡す際に満たさないといけない,

問44

こちら

let x = () => ({ name: "Alice" });
let y = () => ({ name: "Alice", location: "Seattle" });

// 1
x = y;
// 2
y = x;

1, 2はそれぞれエラーになるかならないか

let x = () => ({ name: "Alice" });
let y = () => ({ name: "Alice", location: "Seattle" });

x = y; // OK
y = x; // エラー。xの戻り値には location プロパティがない

// 代入元の返り型は代入先のプロパティを含んでいないといけない

問46

こちら

let identity = function<T>(x: T): T {
  // ...
};
let reverse = function<U>(y: U): U {
  // ...
};
identity = reverse;

は代入できるか。それぞれTUの型は何か

 // OK。anyになります。
 (x: any)=>any は (y: any)=>any と互換性がある

問47

こちらは呼び出すことができません。

type StrFunc = (arg: string) => string;
type NumFunc = (arg: number) => string;

declare const obj: StrFunc | NumFunc;
obj("fa");

なぜですか

objの型はStrFuncNumFuncの型であり、それぞれの引数の型が違うためどちらの関数が呼び出されてもいいようにどちらの引数にも対応できる型を渡す必要があります

問48

こちらは

interface MyObj {
  name: string;
  age: number | undefined;
}

let obj: MyObj = {
  name: "kenji"
};

Errorになります。なぜですか。また正しく修正してください

interface MyObj {
  name: string;
  age?: number | undefined;
}

let obj: MyObj = {
  name: "kenji"
};

// オプショナルを使わない場合はプロパティは存在はして居ないといけません。

let obj: MyObj = {
  name: "kenji",
  age: undefined
};
// なら可能
存在もして居ない場合は`?`を付与すること。

問49

TypeScriptでconsole.logを呼び出してもコンパイルエラーにならないのはなぜですか?

//https://docs.solab.jp/typescript/ambient/declaration/

TypeScript では console.log などを呼び出してもコンパイルエラーにはなりません。 
これは、TypeScript コンパイラがデフォルトで lib.d.ts という宣言ソースファイルを利用しており、
lib.d.ts には次のようなアンビエント宣言が記述されているためです。

// lib.d.ts
declare var console: Console;

問50

こちらは

interface Foo {
  name: string;
}
let obj: Foo = { name: "kenji", age: 90 };

なぜコンパイルエラーなのですか? { name: "kenji", age: 90 };が代入できるように修正してください

// オブジェクトリテラル型はFooが知っているプロパティのみ代入可能です。ageは知りません。
// これを回避するためにはオブジェクト型にすることです。

interface Foo {
  name: string;
}
const other = { name: "kenji", age: 90 };
// otherで推論が下記のように変わる
const other: {
  name: string;
  age: number;
}
let obj: Foo = other;

// or

//何が入ってくるかわからない場合
interface Foo {
  name: string;
  [other: string]: any; //here
}

let obj: Foo = { name: "kenji", age: 90 };

問51

こちらは

let foo:any = {}
foo["a"] = { message: "some message"};

fooにanyを注釈しています。インデックスにstring、 値に代入しようとしている型を指定してください

let foo:{ [index: string]: { message: string }} = {}
foo["a"] = { message: "some message"};

問52

こちらは

const tupleStrNum = ["X", 2];

型推論で(string|number)[]になります。

[string, number] とするにはどうしたらいいですか

const tupleStrNum = ["x", 2] as [string, number];
//const tupleStrNum: [string, number] = ["X", 2];

53

こちらを

interface SomeObject {
    firstKey:   string;
    secondKey:  string;
    thirdKey:   { id: { name: string} }
}

再帰的に各プロパティをオプショナルにした型を定義してください

type RecursivePartial<T> = {
    [P in keyof T]?: RecursivePartial<T[P]>;
};

const b: RecursivePartial<SomeObject> = {}
b.thirdKey = {}

/// 一部のプロパティをのぞいて再帰的にオプショナルにする型
type PartialExcept<T, K extends keyof T> = RecursivePartial<T> & Pick<T, K>;

//ok
const a: PartialExcept<SomeObject, "thirdKey"> = {
    firstKey:   "",
    secondKey:  "",
    thirdKey:   { id: { name: {}} }
}
// ok
const b: PartialExcept<SomeObject, "thirdKey"> = {
    thirdKey:   { id: { name: {}} }
}
// error
const c: PartialExcept<SomeObject, "thirdKey"> = {}

54

プロパティnameの値型がstring | null、ageの値型がnumber | nullの型Userを定義してください

const d = {name: "kenji", age: 99}

type E = {name: string, age: number}
type User<T> = {[K in keyof T]: T[K] | null }

const e:User<E> = { name: null, age: null};

55

Uextendsしている値Tneverを返し、そうでない値型はTを返すDiffを定義してください

type Diff<T, U> = T extends U ? never : T;

const t1:Diff<"a" | "b", "b" | "c"> = "a";

56

こちらの

const t3 = {name: "kenji", age: 99} as const
type T3 = keyof typeof t3

T3の型をおしえてください

//type T3 = "name" | "age"

57

TODO

enum StatusEnum { RootAdmin = "RootAdmin", Admin = "Admin" }
type T2 =  Partial<Record<StatusEnum, number | null>>
const t2:T2 = { RootAdmin: 0 }

58

こちらの

type User = { name: string, age: number}
const f = (a:User) => a
const a:F<User> = f({name: "kenji", age: 9});

を参照に、

もし関数型である引数を渡したらその引数が返ってくる型、関数型ではないなら関数が返ってくるF<User>を定義してください。

type F<T> = T extends (a: infer P) => any ? P : T;
type User = { name: string, age: number}
const f = (a:User) =>  a

const a:F<User> = f({name: "kenji", age: 9}); // User
const b:F<string> = "hello" //string

playground

58

58

58

58

58

58

playgrond

https://tech-1natsu.hatenablog.com/entry/2019/02/09/014218

参照

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.