mcaz
10/10/2019 - 9:01 AM

JS: TypeScript: Base type

/**
 * any型
 * 何でもありな型
 * どんな型とも相互変換可能
 * どんな型の値としても利用可能
 * 実質TypeScriptの型システムを無視することに相当
 * できるだけ使わないほうが良さそう
 */


function exampleAny1(): void {
  // どんな型の値としても利用可能
  // any型の値をもつaをstring型の変数bに代入することが可能
  const a: any = 3
  const b: string = a

  // number型になっってしまう
  console.log(b) // 1
  console.log(typeof b) // "number"
}

exampleAny1()
function arrayExample1(): void {
  const foo: number[] = [0,1,2,3]
  foo.push(4)
}

function arrayExample2(): void {
  const foo: string[] = ['a', 'b', 'c']
  // foo.push(1)
}

function arrayExample3(): void {
  class Student {
    private _name: string = ''

    constructor(name: string) {
      this.name = name
    }

    set name(name: string) {
      this._name = name
    }

    get name(): string {
      return this._name
    }
  }

  const student: Student = new Student('arrayExample3.student')
  console.log(student.name)
}

function arrayExample4(): Array<number> {
  const list: Array<number> = [1,2,3]

  list.forEach(item => console.log(item))

  return list
}

/**
 * read only
 */
function arrayExample5(): void {
  const arr: readonly number[] = [1, 2, 3, 4]

  // arr[0] = 10
  // error: Index signature in type 'readonly number[]' only permits reading.

  // arr.push(5)
  // error: Property 'push' does not exist on type 'readonly number[]'
}


arrayExample1()
arrayExample2()
arrayExample3()
arrayExample4()
arrayExample5()
/**
 * class型
 * TypeScriptでは、クラスを定義すると同時に同名の型も定義される
 */

function exampleClass1(): void {
  // クラスFooを定義したことで、Fooという型も同時に定義
  class Foo {
    method(): void {
      console.log('Hello world!')
    }
  }

  const obj: Foo = new Foo()
  obj.method()
}

function exampleClass2(): void {
  class Foo {
    method(): void {
      console.log('Foo!')
    }
  }

  class Bar {
    method(): void {
      console.log('Bar!')
    }
    message() {
      console.log('Bar.message')
    }
  }

  class MyFoo {
    method(): void {
      console.log('MyFoo!')
    }
  }

  /**
   * JavaScriptの実行時にはあるオブジェクトがあるクラスのインスタンスか否かということは
   * プロトタイプチェーンによって特徴づけられるがTSでは異なる
   * ここで定義された型Fooというのは次のようなオブジェクト型で代替可能
   */
  const myFooObj: MyFoo = new Foo()
  // どちらもmethodを持っているため、Foo型にMyFoo型を代入可能
  const fooObj: Foo = myFooObj

  // 代入された実態がMyFoo型であるためmyFooObjの中身はFoo.methodになっている
  myFooObj.method() // Foo!
  fooObj.method() // Foo!

  const bar: Bar = new Bar()
  const a: Foo = bar

  a.method()
  // a.message() // error: 実行自体は可能

  // const b2: Foo = new Foo()
  // const bar2: Bar = b2 // error

  // bar2.method()
  // bar2.message() // error: 実行されない
}

/**
 * newシグネチャ
 */
function exampleClass3(): void {
  interface Ctor<T> {
    new(): T
  }

  class Foo {
    public foo: string = 'Foo.foo value'
    public bar: number | undefined
  }

  const f: Ctor<Foo> = Foo

  console.log('exampleClass3 f', new f().foo)
  // "exampleClass3 f" "Foo.foo value"
}

exampleClass1()
exampleClass2()
exampleClass3()
function functionExample1(): void {
  const f: (foo: string) => number = fn

  function fn(arg: string): number {
    return Number(arg)
  }
}

function functionExample2(): void {
  // こちらに定義する引数名は型の一致等の判定には関係ない
  // 下で定義している関数で指定している引数名と違っていても問題ない
  const f1: (foo: string, arg2: number) => Boolean = fn
  const f2: (foo: string, arg2: number) => Boolean = fn
  const f3 = f1
  const f4: (foo: string, bar: number)  => Boolean = fn

  /**
   * @param foo {string}
   * @param bar {number}
   * @return {boolean}
   */
  function fn(foo: string, bar: number): boolean {
    return true
  }

  console.log('f1', f1('a', 1))
  console.log('f2', f2('a', 1))
  console.log('f3', f3('a', 1))
  console.log('f4', f4('a', 1))
}

function functionExample3(): void {
  interface Obj1 {
    foo: string
    bar: number
  }

  interface Obj2 {
    foo: string
  }

  // const o1: <(obj: Obj1) => Object> = <(o: Obj1) => { return o }>
  const o1: (obj: Obj1) => Object = (o: Obj1) => { return o }
  // const o2: (obj: Obj2) => Object = o1
  // Type '(obj: Obj1) => Object' is not assignable to type '(obj: Obj2) => Object'.
  // Types of parameters 'obj' and 'obj' are incompatible.
  // Property 'bar' is missing in type 'Obj2' but required in type 'Obj1'.

  console.log('o1', o1({foo: 'foo', bar: 1}))
  console.log('o2', o1({foo: 'aaa', bar: 999}))
}

/**
 * 部分型関係
 */
function functionExample4(): void {
  interface MyObj {
    foo: string
    bar: number
  }

  interface MyObj2 {
    foo: string
  }

  const a: (obj: MyObj2) => void = () => {}
  const b: (obj: MyObj)  => void = a
}

/**
 * 可変長引数
 */
function functionExample5(): void {
  const func1 = (foo: string, ...bar: number[]) => bar

  console.log('functionExample5.foo', func1('foo', 1, 2, 3, 4))
  console.log('functionExample5.bar', func1('bar', 1, 2))

  const func2 = (...foo: any[]) => foo

  console.log('functionExample5.bar', func2('foo', 1, 2, 'aaa', {aaa: 'aaa'}))
}

functionExample1()
functionExample2()
functionExample3()
functionExample4()
/**
 * intersection型
 */

function exampleIntesection1(): void {
  interface Hoge {
    foo: string
    bar: number
  }

  interface Piyo {
    foo: string
    baz: boolean
  }

  const obj: Hoge & Piyo = {
    foo: 'fooooooo',
    bar: 3,
    baz: true
  }

  console.log('exampleIntesection1 obj', obj)

  // "exampleIntesection1 obj" Object {
  //   bar: 3,
  //   baz: true,
  //   foo: "fooooooo"
  // }
}

function exampleIntesection2(): void {
  interface Hoge {
    type: 'hoge'
    foo: string
  }

  interface Piyo {
    type: 'piyo'
    bar: number
  }

  interface Fuga {
    baz: boolean
  }

  type Obj = (Hoge | Piyo) & Fuga
  // type obj = (Hoge & Fuga) | (Piyo & Fuga)

  function fn(obj: Obj) {
    console.log('exampleIntesection2 obj.baz', obj.baz)
    // "exampleIntesection2 obj.baz" true

    if (obj.type === 'hoge') {
      console.log('exampleIntesection2 obj', obj)
      // "exampleIntesection2 obj" Object {
      //   baz: true,
      //   foo: "obj foo",
      //   type: "hoge"
      // }
    } else {
      console.log('exampleIntesection2 obj.bar', obj)
    }
  }

  const obj: Obj = {
    type: 'hoge',
    foo: 'obj foo',
    baz: true
  }

  fn(obj)
}

function exampleIntesection3(): void {
  type Fn = (arg: number) => number

  interface MyObj {
    prop: string
  }

  // const obj: Fn | MyObj1 = { prop: '' }
  // Type '{ prop: string; }' is not assignable to type 'Fn | MyObj1'.
  // Object literal may only specify known properties, and 'prop' does not exist in type 'Fn | MyObj1'.

  // obj(1)
  // This expression is not callable.
  // Not all constituents of type 'Fn | MyObj1' are callable.
  //   Type 'MyObj1' has no call signatures.
}

function exampleIntesection4(): void {
  type StrFn = (arg: string) => string
  type NumFn = (arg: number) => string

  const obj: StrFn | NumFn = function(str: string): string {
    return str
  }

  console.log('exampleIntesection4 obj', obj('aaa')) // 'aaa'
}

function exampleIntesection5(): void {
  // interface Hoge {
  //   foo: string;
  //   bar: number;
  // }
  // interface Piyo {
  //     foo: string;
  //     baz: boolean;
  // }

  // type HogeFn = (arg: Hoge) => number
  // type PiyoFn = (arg: Piyo) => boolean

  // declare const fn: HogeFn | PiyoFn

  // const res = fn({
  //   foo: 'foo',
  //   bar: 123,
  //   baz: false
  // })
}

function exampleIntesection6(): void {
  const arr: string[] | number[] = [1, 2]

  arr.forEach(x => console.log(x))

  const arr2 = arr.map(x => x)

  console.log(arr2)
}

function exampleIntesection7(): void {
}

exampleIntesection1()
exampleIntesection2()
exampleIntesection3()
exampleIntesection4()
exampleIntesection5()
exampleIntesection6()
/**
 * ジェネリクス型・総称型
 *「総称型」とも呼ばれ、型をパラメータ化できる
 * 総称型は、パラメータ化された型を外から与えられて、初めて実際の型が決まる
 */

function jeneriskExample1(): void {
  class MyArray<T> {
    private arr: T[] = []

    public push(value: T) {
      this.arr.push(value)
    }
  }

  const numberArr = new MyArray<number>()
  console.log(numberArr.push(10))
}

function jeneriskExample2() {
  interface Foo<S, T> {
    foo: S
    bar: T
  }

  const obj: Foo<number, string> = {
    foo: 10,
    bar: 'Hello!'
  }
  console.log(obj)

  // const obj2: Foo<number, string> = {
    // foo: 'Hello!', // error TS2322: Type 'string' is not assignable to type 'number'.
    // bar: 1 // error TS2322: Type 'number' is not assignable to type 'string'.
  // }
  // console.log(obj2)
}

function jeneriskExample3(): void {
  class Foo<T> {
    private _name
    constructor(name: T) {
      this._name = name
    }
    showName() {
      console.log(this._name)
    }
  }

  const obj1 = new Foo<string>('foo')
  obj1.showName()

  function fn<T>(obj: T): void {
    console.log('fn', obj)
  }

  fn<number>(3)
  // <number>は省略可。省略時は引数から型推論される
  // 複雑な処理中では型推論が機能しない場合がある
  fn(3)
}

jeneriskExample1()
jeneriskExample2()
jeneriskExample3()


const str1: 'foo' = 'foo' // ok
// const str2: 'bar' = 'foo' // error

// const定義時、型推論で const str3: 'foo' = 'foo'と同じ意味になる
const str3 = 'foo'
// const str4: 'bar' = str3
// error: Type '"foo"' is not assignable to type '"bar"'.

// let/var定義時、str5は後々変更する意図があると解釈され
// 'foo'型としてではなくプリミティブ型へ型推論される
let str5 = 'foo'
const str6: string = str5
// const str7: number = str5
// error
// const str8: 'foo' = str5
// error: Type 'string' is not assignable to type '"foo"'.

let str9: 'foo' = 'foo'
// str9 = 'bar'
// error: Type '"bar"' is not assignable to type '"foo"'.
/**
 * never型
 * never型に当てはまる値は存在しない
 * never型の値を実際に作ることはできない
 * よって、(TypeScriptの型システムを欺かない限りは)never型の値を持っているという状況があり得ない
 * never型の値を他の型の変数に入れるということがソースコード上であったとしても、実際には起こりえない
 */

function exampleNever1(): void {
  // どんな値もnever型の変数に入れることはでき無い
  // const n: never = 0 // error TS2322: Type '0' is not assignable to type 'never'.

  // never型の値を作る方法が無いのでdeclareで宣言だけする
  // declare const n: never
  // 逆にどんな方にもnever型の値は代入できる
  // const foo: string = n
}

function exampleNever2(): void {
  interface Some<T> {
    type: 'Some'
    value: T
  }

  interface None {
    type: 'None'
  }

  type Option<T> = Some<T> | None

  function map<T, U>(obj: Option<T>, f: (obj: T) => U): Option<U> {
    switch (obj.type) {
      case 'Some':
        return {
          type: 'Some',
          value: f(obj.value)
        }
      case 'None':
        return {
          type: 'None'
        }
      default:
        // ここでobjはnever型になっている
        return obj
    }
  }

  const some: Option<string> = {
    type: 'Some',
    value: 'some value'
  }
  console.log('exampleNever2 map(some)',
    map(some, (str: string) => str)
  )
  // "exampleNever2 map(someOpt)" Object {
  //   type: "Some",
  //   value: 1
  // }

  const none: Option<None> = {
    type: 'None'
  }
  console.log('exampleNever2 map(opt)',
    map(none, (_none: None) => _none)
  )
  // "exampleNever2 map(someOpt)" Object {
  //   type: "None"
  // }
}

function exampleNever3(): void {
  /**
   * 関数の返り値の型がnever型となるのは、関数が値を返す可能性が無いとき
   * 返り値が無いことを表すvoid型とは異なり、
   * そもそも関数が正常に終了して値が返ってくるということがあり得ない場合を表す
   *
   * この関数は必ずthrowする、
   * 関数の実行は中断され、値を返すことなく関数を脱出する
   */
  function throwError(): never {
    throw new Error('Hi')
  }

  /**
   * 返り値をresultに代入しているが、resultに何かが代入されることはありえない
   * そのため、resultにnever型をつけることができる
   */
  const result: never = throwError()
}

exampleNever1()
exampleNever2()
exampleNever3()
function exampleObject1(): void {

  interface MyObj1 {
    foo: string;
    bar: number
  }

  interface MyObj2 {
    foo: string;
  }

  const obj1: MyObj1 = {
    foo: 'foo',
    bar: 3
  }

  // const obj2: MyObj1 = {
  //   foo: 'foo',
  //   bar: 'barbar'
  // }
  // error
  // Type '{ foo: string; bar: string; }' is not assignable to type 'MyObj1'.
  // Types of property 'bar' are incompatible.
  // Type 'string' is not assignable to type 'number'.

  // const myObj3: MyObj1 = {
  //   foo: 'foo'
  // }
  // error
  // Type '{ foo: string; }' is not assignable to type 'MyObj1'.
  // Property 'bar' is missing in type '{ foo: string; }'.

  // TypeScriptでは構造的部分型を採用しているため下記が可能
  // MyObj1はMyObj2の部分型
  const obj3: MyObj1 = {foo: 'foo', bar: 3}
  const obj4: MyObj2 = obj3

  // const obj5: MyObj2 = {foo: 'foo', bar: 3}
  // error
  // Type '{ foo: string; bar: number; }' is not assignable to type 'MyObj2'.
  // Object literal may only specify known properties, and 'bar' does not exist in type 'MyObj2

  function func(obj: MyObj2): void {
    console.log('func')
  }

  // func({foo: 'foo', bar: 2})
  // error
  // Argument of type '{ foo: string; bar: number; }' is not assignable to parameter of type 'MyObj2'.
  // Object literal may only specify known properties, and 'bar' does not exist in type 'MyObj2'.
}

/**
 * 要素の省略
 */
function exampleObject2(): void {
  interface MyObj {
    foo: string
    // ?をつけることで宣言時に省略可能
    bar?: number
  }

  // barを省略
  // string | undefinedのunion型になる
  let obj: MyObj = {
    foo: 'string'
  }

  console.log('exampleObject2 obj', obj)
  // "exampleObject2 obj" Object {
  //   foo: "string"
  // }

  obj = {
    foo: 'foo',
    bar: 100
  }

  console.log('exampleObject2 obj', obj)
  // "exampleObject2 obj" Object {
  //   bar: 100,
  //   foo: "foo"
  // }

  // function fn(_obj: MyObj): number {
    // return _obj.bar !== null ? _obj.bar * 100 : 0
    // error TS2532: Object is possibly 'undefined'.
    // 怒られる
  // }

  // console.log('exampleObject2 fn(obj)', fn(obj))
  // "exampleObject2 fn(obj)" 10000
}

/**
 * readonly
 */
function exampleObject3(): void {
  interface MyObj {
    readonly foo: string
  }

  interface MyObj2 {
    foo: string
  }

  const obj: MyObj = {
    foo: 'Hey!'
  }
  // obj = {
  //   foo: 'Hi'
  // }
  // error: Cannot assign to 'foo' because it is a constant or a read-only property.

  // 他の型を通して書き換えできてしまうので注意が必要
  const obj2: MyObj2 = obj
  obj2.foo = 'update'

  console.log('exampleObject3 obj2', obj2)
}

/**
 * インデックスシグネチャ
 * 多用は危なそう
 */
function exampleObject4(): void {
  interface MyObj {
    [key: string]: number
  }

  const obj: MyObj   = {}
  const num1: number = obj.foo
  const num2: number = obj.bar

  console.log('exampleObject4 num1', num1) // "exampleObject4 num1" undefined
  console.log('exampleObject4 num2', num2) // "exampleObject4 num2" undefined
}

/**
 * 関数シグネチャ1
 */
function exampleObject5(): void {
  interface Fn {
    (arg: number): void
  }

  const f: Fn = (arg: number) => { console.log(arg) }

  f(1)
}

function exampleObject6(): void {
  const obj1 = { foo: 'foo' }
  const obj2 = {} = obj1

  // const o = Object.create(3)
  // Argument of type '3' is not assignable to parameter of type 'object | null'.
  // プリミティブ型のみに対応している関数のためこのパターンは使えない

  // const obj: {} = 3
  // const o = Object.create(obj)
  /**
   * これもだめ
   * > じつは、{}という型はオブジェクト以外も受け付けてしまうのです。ただし、undefinedとnullはだめです。
   * > これは、JavaScriptの仕様上、プリミティブに対してもプロパティアクセスができることと関係しています。
   * > 例えばプリミティブのひとつである文字列に対してlengthというプロパティを見るとその長さを取得できます。
   * > ということで、次のようなコードが可能になります。
   *
   * ラッパーオブジェクトの作用?
   */
  interface Length {
    length: number;
  }

  const o: Length = 'foobar';

  console.log(o) // 'foobar'

  /**
   * > このことから、{}というのはundefinedとnull以外は何でも受け入れてしまうような
   * > とても弱い型であるということが分かります。
   */
}

/**
 * weak typeな型
 * オプショナルなプロパティ(?付き)を含むオブジェクト型
 */
function exampleObject7(): void {
  interface Options1 { // weak type
    foo?: string
    bar?: number
  }

  const o1 = { hoge: 3 }

  // const o2: Options1 = o1
  // error: Type '{ hoge: number; }' has no properties in common with type 'Options1'

  // const o3: Options1 = 5
  // error: Type '5' has no properties in common with type 'Options1'.

  const o4 = { foo: 'foo string', hoge: 3 }
  const o5: Options1 = o4
  // weak typeは可変なプロパティを1つ以上含むデータを設定する必要がある

  interface Options2 { // weak type
    foo: string
    bar: number
    fizz?: string
    buzz?: string
    hoge?: number
  }

  const o6 = { fizz: 'fizz string' }
  // const o7: Options2 = o6
  // error
  // 通常のプロパティをすべて含んでいない

  const o8 = { foo: 'foo string', bar: 1 }
  const o9: Options2 = o8
  // ok
  // 通常のプロパティをすべて含んでいる
  // オプショナルなプロパティは1つも含まれていなくても問題ない

  const o10 = { foo: 'foo string', fizz: 'fizz string' }
  // const o11: Options2 = o10
  // error
  // 通常のプロパティbarが含まれていない
}


exampleObject1()
exampleObject2()
exampleObject3()
exampleObject4()
exampleObject6()
exampleObject7()
// プリミティブ型

// const num: number = 3
// const str: string = num

// const nil: null = null
// const str: string = nil // error
/**
 * tuple型
 * TypeScriptでは配列をタプルの代わりとして用いる
 * > 関数から複数の値を返したい場合に配列に入れてまとめて返すみたいなユースケースを想定されているのでは
 * 配列として実装されているため、当然配列として操作出来る
 */

function tupleExample1(): void {
  const foo: [string, number] = ['foo', 5]
  const str: string = foo[0]

  // 値の数が違うのでerror
  // const bar: [string, number] = ['foo']

  console.log(str)
}

function tupleExample2(): void {
  /**
   * @param x {string}
   * @param y {number}
   * @return {array<string, number>}
   */
  function makePair(x: string, y: number): [string, number] {
    return [x, y]
  }

  console.log('tupleExample2.makePair', makePair('foo', 1))
}

function tupleExample3(): void {
  const tuple: [string, number] = ['foo', 3]

  /**
   * tuple型は配列を利用した仕組みになっているため普通の配列と同じように操作ができる
   * 一旦number型として定義した後、string型を代入しても普通にコンパイルも処理も通ってしまう。
   * あまり多用しない方が良さそう
   */
  tuple.pop()
  tuple.push('Hey!')
  const num: number = tuple[1]

  console.log(num)
}

/**
 * 要素0
 */
function tupleExample4(): void {
  const unit: [] = []
  console.log(unit)
}

/**
 * 可変長タプル
 */
function tupleExample5(): void {
  type NumAndStrings = [number, ...string[]]

  const a1: NumAndStrings = [3, 'foo', 'bar']
  const a2: NumAndStrings = [5]
  // これはNG
  // const a3: NumAndStrings = ['foo', 'bar']

  console.log('tupleExample5.a1', a1)
  console.log('tupleExample5.a2', a2)
  // console.log('tupleExample5.a3', a3)
}

/**
 * オプショナルに引数の数を変更
 */
function tupleExample6(): void {
  // 2番目の要素はあってもいいしなくてもいい
  // ある場合はnumber型限定
  type T = [string, number?]
  const t2: T = ['foo', 3]
  const t1: T = ['foo']

  // type S = [string, number]
  // const s2: S = ['foo', 3] // ok
  // const s1: S = ['foo'] // error
}

/**
 * タプル型を使って関数の可変長引数の型を指定
 */
function tupleExample7() {
  type  Args = [string, number, boolean]
  const fn   = (...args: Args) => args[1]
  const v    = fn('foo', 3, true)

  console.log('tupleExample7.v', typeof v) // "number"
}

function tupleExample8(): void {
  type Args = [string, ...number[]]
  const fn  = (f: string, ...args: Args) => args

  // 'foo'が上書きされる
  const v1 = fn('foo', 'bar')
  console.log('tupleExample8.v1', v1) // "tupleExample8.v1" ["bar"]

  // 'foo'が上書きされる
  // 数値が可変長で渡される
  const v2 = fn('foo', 'bar', 1, 2, 3)
  console.log('tupleExample8.v2', v2) // "tupleExample8.v2" ["bar", 1, 2, 3]
}

function tupleExample9(): void {
  // 可変長引数として取得
  const fn = (...args: string[]) => args
  const strings: string[] =['foo', 'bar', 'baz']

  // 引数として渡すときに展開
  console.log('tupleExample9.fn', fn(...strings))
}

function tupleExample10(): void {
  const fn = (str: string, num: number, b: boolean) => args
  const args: [string, number, boolean] = ['foo', 3, false]

  console.log('tupleExample10.fn', fn(...args)) // "tupleExample10.fn" ["foo", 3, false]
}

function tupleExample11(): void {
  /**
   * @param func
   * @param value
   * @return {function}
   * 受け取った引数列argsに加えて最初の引数としてvalueをfuncに渡して呼び出した返り値をそのまま返す関数
   */
  function bind<T, U extends any[], R> (
    func: (arg1: T, ...rest: U) => R,
    value: T,
  ): ((...args: U) => R) {
    return (...args: U) => func(value, ...args)
  }

  const add = (x: number, y: number) => x + y
  const add1 = bind(add, 1)

  console.log(add1(5)) // 6

  // add1('foo')
  // Argument of type '"foo"' is not assignable to parameter of type 'number'.
}

/**
 * read only
 */
function tupleExample12(): void {
  const tuple: readonly [string, number] = ['foo', 123]

  // tuple[0] = 'bar'
  // error: Cannot assign to '0' because it is a read-only property.
}

tupleExample1()
tupleExample2()
tupleExample3()
tupleExample4()
tupleExample5()
tupleExample6()
tupleExample7()
tupleExample8()
tupleExample9()
tupleExample10()
tupleExample11()
/**
 * typeof型
 */

function exampleTypeof1(): void {
  let foo = 'str'

  type FooType = typeof foo // FooType = string

  const str: FooType = 'abcdef'
}



exampleTypeof1()
/**
 * union型
 * 複数の型へ対応できる
 */

function exampleUnion1(): void {
  let value: string | number = 'foo'

  value = 100
  value = 'bar'
  // value = true // error Type 'true' is not assignable to type 'string | number'.

  console.log('exampleUnion1 value', value)
}

function exampleUnion2(): void {
  interface Hoge {
    foo: string
    bar: number
  }

  interface Piyo {
    foo: number
    baz: boolean
  }

  type HogePiyo = Hoge | Piyo

  const obj: HogePiyo = {
    foo: 'hello',
    bar: 0
  }

  console.log('exampleUnion2 bar', obj)
}

/**
 * union型の判定
 * in演算子を使用(あまり使わない方が良さそう)
 */
function exampleUnion3(): void {
  interface Hoge {
    foo: string
    bar: number
  }

  interface Piyo {
    foo: number
    baz: boolean
  }

  /**
   * @param obj {Hoge | Piyo}
   * @return void
   */
  function useHogePiyo(obj: Hoge | Piyo): void {
    console.log('exampleUnion3.useHogePiyo obj', 'bar' in obj ? 'Hoge' : 'Piyo')
  }

  const o: Hoge | Piyo = {
    foo: 'hello',
    bar:0
  }

  useHogePiyo(o)
}

/**
 * union型判定
 * typeofを利用
 */
function exampleUnion4(): void {
  function fn(value: string | number): string | number {
    return 'string' === typeof value ? ''+value : value
  }

  console.log('exampleUnion4 fn', fn('value string'))
  console.log('exampleUnion4 fn', fn(999))
}

/**
 * nullチェック
 */
function exampleUnion5(): void {
  function fn(value: string | null): number {
    return value !== null && value.length || 0
  }

  console.log('exampleUnion5 fn', fn('value string'))
  console.log('exampleUnion5 fn', fn(null))
}

function exampleUnion6(): void {
  interface Some<T> {
    type: 'Some'
    value: T
  }

  interface None {
    type: 'None'
  }

  type Option<T> = Some<T> | None

  function map<T, U> (obj: Option<T>, f: (obj: T) => U): Option<U> {
    switch(obj.type)
    {
      case 'Some':
        return {
          type: 'Some',
          value: f(obj.value)
        }
      default:
        return {
          type: 'None'
        }
    }
  }

  const someOpt: Option<string> = {
    type: 'Some',
    value: 'someOpt value'
  }
  console.log('exampleUnion6 map(someOpt)',
    map(someOpt, (str: string) => str)
  )
  // "exampleUnion6 map(someOpt)" Object {
  //   type: "Some",
  //   value: "someOpt value"
  // }

  const noneOpt: Option<None> = {
    type: 'None'
  }
  console.log('exampleUnion6 map(noneOpt)',
    map(noneOpt, (number: None) => number)
  )
  // "exampleUnion6 map(noneOpt)" Object {
  //   type: "None"
  // }
}

function exampleUnion7(): void {
  interface Hoge {
    foo: string
    bar: number
  }

  interface Piyo {
    foo: number
    baz: boolean
  }

  type HogePiyo = Hoge | Piyo

  function getFoo(obj: HogePiyo): string | number {
    return obj.foo
  }

  const hogePiyo: HogePiyo = {
    foo: 'hoge',
    bar: 1
  }

  console.log('exampleUnion7 hogePiyo', getFoo(hogePiyo))


  const arr: string[] | number[] = ['a']
  const elm = arr[0]

  console.log('exampleUnion7 elm', elm)
}

exampleUnion1()
exampleUnion2()
exampleUnion3()
exampleUnion4()
exampleUnion5()
exampleUnion6()
exampleUnion7()
/**
 * unknown型
 * TypeScript 3.0 ~
 *
 * どんな方の値もunknown型として扱えるtop型
 * never型の真逆の性質を持つ、すべての方を部分型として持つ部分関係の頂点にある型
 *
 * 任意の値を取ることができる部分はany型と同じ性質
 * any型と違い中の値を破壊するような処理を行えないためこちらの方が安全
 */

function exampleUnknown1(): void {
    const u1: unknown = 3

    // const sum = u1 + 5
    // error: Object is of type 'unknown'.
    // unknown型はどんな値なのか判断出来ないため、数値型として計算をすることが出来ない

    // const p = u1.prop
    // error: Object is of type 'unknown'.
    // unknown型はどんな値なのか判断出来ないため、文字列型として計算をすることが出来ない

    const u2: unknown = null
    const u3: unknown = (foo: string) => true
}

/**
 * unknown型に代入された値の型判定
 */
function exampleUnknown2(): void {
  const u: unknown = 3

  // typeofを使うと変数の方を取得できる
  // JSではなくTypeScriptの機能
  if (typeof u === 'number') {
    // この中ではuはnumber型として扱える
    const foo = u + 5
  }
}

/**
 * unknown型に代入されたクラス判定
 */
function exampleUnknown3(): void {
  const u: unknown = 3

  class MyClass {
    public prop: number = 10
  }

  if (u instanceof MyClass) {
    // この中ではMyClass型として扱える
    u.prop
  }
}



exampleUnknown1()
exampleUnknown2()
exampleUnknown3()
/**
 * void型
 * 主に関数の返り値の型として使われ、「何も返さない」ことを表す
 * JSの戻値の無い関数はundefinedを返す、void型はundefinedのみを値にとる型
 * void型にundefinedを設定することができるが、逆は不可
 */

function exampleVoid1(): void {
  /**
   * JSの戻値の無い関数はundefinedを返す、void型はundefinedのみを値にとる型
   * void型にundefinedを設定することができるが、逆は不可
   */
  const a: void = undefined
  // void型の値をundefined型に代入するとエラーがでる
  // const b: undefined = a // error : Type 'void' is not assignable to type 'undefined'.

  const b: void = void 0
}

function exampleVoid2(): void {
  console.log('hello')
}

// function exampleVoid3(): undefined {
//   console.log('hello')
// }
// error : A function whose declared type is neither 'void' nor 'any' must return a value.

function exampleVoid4(): void {
  return undefined
  // undefinedをvoid型の値として扱うことができる
  // void型を返す関数にreturn undefined;と書くことが可能
}

function exampleVoid5(): void {
  return void 0
}


exampleVoid1()
exampleVoid2()
exampleVoid4()
exampleVoid5()
/**
 * as
 * ダウンキャスト
 * アップキャスト
 * 危険なので基本的に使わないほうが吉
 */

 /**
  * ダウンキャスト
  * 派生型の値を部分型として扱うためのもの
  */
function exampleAs1(): void {
  function rand(): string | number {
    return Math.random() < 0.5 ? 'hello' : 123
  }

  const value = rand()
  // function rand(): string | number
  // これをやらないとstrをnumber型として扱えない
  const str   = value as number

  console.log('exampleAs1 str * 10', str * 10)
  // "exampleAs1 str * 10" NaN
  // "exampleAs1 str * 10" 1230
  // 安定せずとても危険
}

function exampleAs2(): void {
  const value = 'foo'
  // 全く関係ない値を変換することは出来ない
  // const str   = value as number
  // error: Type 'string' cannot be converted to type 'number'.
}

function exampleAs3(): void {
  const value = 'foo'
  // 全く関係ない値を変換することは出来ない
  // 一旦any型に変換することでnumberに変換することができる
  // 出来るだけ避けるべき
  const str = value as any as number
}

/**
 * as const
 */
function exampleAs4(): void {
  var foo = '123'
  foo = 'foo'

  console.log('exampleAs4 foo', foo)

  // barはstring型ではなく'bar'型として扱われるため、上書きできなくなる
  // var bar = 'bar' as const
  // bar = 'aaa' // error
  // console.log('exampleAs4 bar', bar)
}

/**
 * as const2
 */
function exampleAs5(): void {
  const obj = {
    foo: '123',
    bar: [1, 3, 4]
  } as const

  // これもだめ
  // as constを使用すると中のプロパティは再帰的に型推論が行われ、readonlyにできる
  // すべての値を固定化するのにつかえる
  // obj.foo = '11' // error
  // obj.bar.push('11') // error
}


exampleAs1()
exampleAs2()
exampleAs3()
exampleAs4()
exampleAs5()