Open3

TypeScriptとReact/Next.jsでつくる実践Webアプリケーションを読んでみる

なる💡ideee代表なる💡ideee代表

1章

Next.jsはReactの機能を活かしつつ、Reactだけではカバーできない領域を補う、実践的なフレームワーク。
JavaScriptの誕生は1995年。
当初はブラウザに装飾を足す程度で使われていたが、2005年にGoogleが地図サービスをAjaxで非同期で表示をした。このことでJavaScriptへの注目が集まり、再注目された。

jQueryの人気

当初画期的だった点

  • クロスブラウザ互換を実現できる
  • DOMの操作が簡単にできる
  • アニメーションを簡単に実装できる
  • jQuery UIなど周辺ライブラリが充実している

jQueryのよくなかった点

  • グローバルスコープを汚染する
  • DOM操作の実装が複雑になりやすい
  • ルーティングなど、複数ページのWebアプリケーションを実装するしくみがない
  • ブラウザ間の挙動の違いが以前よりも顕著ではなくなり、ブラウザ互換コードが不要になってきた

SPAのメリット

  • ハイパフォーマンスなアプリケーションを提供できる
  • サーバーサイドエンジニアとフロントエンドエンジニアの分業が容易になる
  • JSON APIによる疎結合の設計ができる
  • iOsやAndroidなどネイティブアプリクライアントに対してもAPIによる疎結合なシステム構成で対応可能に

SPAのデメリット

  • 初期表示に少し時間がかかる
  • フロントエンドの学習コストが高い
  • フロントエンドのコードの量が多くなる
  • 経験のある人材の採用が難しい

SPA普及の裏で貢献したHistory API

HTML5で導入された機能。ページ遷移・履歴をJavaScriptで扱えるようにした。

2009年~2015年

Reactの登場

2013年にFacebookが公開したUIライブラリ。

Reactの特徴

  • 仮装DOM
  • 宣言的UI
  • 単一方向のデータの受け渡し
  • コンポーネント指向・関数コンポーネント
  • Fluxアーキテクチャとの親和性
なる💡ideee代表なる💡ideee代表

Node.jsの躍進

2009年に登場。
フロントエンドのエンジニアが利用している言語をそのままサーバサイドの実装を可能にするため、サーバーサイドとフロント間のコードの共通化も可能になった。
Node.jsの功績にはnpmパッケージもある。

npmの恩恵

  • モジュールの読み込みの機構
  • パッケージ管理
  • ビルドシステム
  • エンジニア採用における技術スタックの統一

CommonJsとESモジュールが対立

  • require()を使用するCommonJS
  • import{}を使用するESモジュール
  • ESモジュールが一般的になってきている

AltJSの流行とTypeScriptの定番化

コンパイルすることでJSが生成されるプログラミング言語。

言語名 特徴
TS Microsoftが開発。静的型宣言
CoffeeScript コードを減らす思想のAltJs
Dart Googleが開発をリードしている言語。Flutterで利用

SSRとSSGの必要性

SSR
Server Side Rendering。サーバーサイドJS実行環境でリクエストに応じてページを生成してHTMLを返すので高速化。SPAで難しかったSEOを向上させることが可能。

デメリット

  • Node.jsなので実行環境が必要になる
  • サーバーのCPU負荷が増える
  • ロジックが分散してしまう可能性がある

SSG
Static Site Generation
事前に静的ファイルとして生成し、デプロイする仕組み。
SSRはサーバー負荷がかかることがあるが、SSGではその弱点を補う。

Next.jsの登場

Vercel社が開発。

画期的ポイント

  • SPA/SSR/SSGの切り替えが容易
  • 簡単なページルーティング
  • TSベース
  • デプロイが簡単
  • 学習コストが少ない
  • webpackの設定の隠蔽
  • ディレクトリベースの自動ルーティング機能
  • コードの分割・統合

コンポーネント思考

  • props
    • コンポーネントの外側から受け取ることのできる値。コンポーネントの中で何かトリガーされた時に呼び出される関数を渡すことも可能
  • state
    • コンポーネント内部で保持するデータ。propsと異なりコンポーネントの外側から値を受け取ることはできず、外部からアクセスをしないもの。

Atomic Design

  • Atom
    • UIの最小単位。ボタンなど
  • Molecules
    • 検索フォームなど
  • Organisms
    • ヘッダーなど
  • Template
    • Organismsを組み合わせて1つの画面にするもの
  • Pages
    • Templateにデータが注ぎ込まれたもの
なる💡ideee代表なる💡ideee代表

TypeScriptの基礎

覇権を握った理由

  • 開発生産性の高い静的型付け言語
  • JSの文法をそのまま拡張する上位互換言語

Microsoftが作っているので、VScodeとの相性も良く、型情報表示や型によるコードチェックが容易。

プリミティブ型

最もよく使われる型
string, number, booleanなど

配列

[]表記

const array: string[] = []

オブジェクト型

キーとバリューによるデータ形式のインスタンス

const user: { name: string, age: number } = {
  name: 'Tanaka',
  age: 30
}

any

全ての方を許容する型(tsの意味がなくなるので基本使わない)

関数

引数と戻り値に対して方を定義できる

function sayHello(name: string): string {
  return `Hello ${name}`
}
sayHello('Tanaka')

オプショナルな引数を使用したい場合は引数名の末尾に ? をつける

TSの型機能

型推論

明示的な変数の初期化を行うと、自動的に型が決定される

const age = 10
console.log(age.length) 
// エラー:ageはnumberなのでlengthプロパティはない

型アサーション

TSが具体的な型を知ることができないケースがある

変数 = 値 as 型
const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement
// HTMLElementの中でもHTMLCanvasElementかもしれない

複雑なアサーションを行いたい場合は、まずanyに変換し、目的の型に変換する2段階のアサーションで表現ができる

const result = (response as any) as User

型エイリアス

type 型名 = 型
type Point = {x: number; y:number;}

function printPoint(point: Point) {
  console.log(point.x)
  console.log(point.y)
}

printPoint({x: 100, y: 100})

// 型があっていてもプロパティ名が異なるとエラー
// printPoint({ z:100, t:100 })

インタフェース

より拡張性の高いエイリアス

interface Colorful {
  color: string;
}

interface Circle {
  radius: number;
}

// 複数のインターフェースを継承して新たなインタフェースを定義できる
interface ColorfulCircle extends Colorful, Circle {}

const cc: ColorfulCircle = {
  color: '赤',
  radius: 10
}

クラス

アクセス修飾子
デフォルトはpublic。
private, protectedもある。

class BasePoint3D {
  public x:number;
  private y: number;
  protected z: number;
}

// インスタンス化を行った場合のアクセス制御の例
const basePoint = new BasePoint3D()
basePoint.x // OK
basePoint.y // コンパイルエラー
basePoint.z // コンパイルエラー

class ChildPoint extends BasePoint3D {
  constructor() {
    super()

    this.x // OK
    this.y // コンパイルエラー
    this.z // OK
}

重要な型

Enum型

enum Direction {
  Up = 'UP',
  Down = 'DOWN'
}

const value = 'DOWN'

const enumValue = value as Direction

if (enumValue === Direction.Down) {
  console.log('Down is selected')
}

ジェネリック型

クラスや関数において、その中で使う型を抽象化し外部から具体的な型を指定できる機能

class Queue<T> {
  private array: T[] = []

  push(item: T) {
    this.array.push(item)
  }

  pop(): T | undefined {
    return this.array.shift()
  }
}

const queue = new Queue<number> ()
queue.push(111)
quue.push('hoge') // コンパイルエラー

Union型とIntersection型

|と& を使用して型を定義できる。
例は上述にて

リテラル型

決まった文字列や数値しか入らない型の制御可能

const postStatus: 'draft' | 'published'
postStatus = 'drafts' コンパイルエラー

never型

決して発生しない値の種類を表す
常に例外を発生させる関数などで定義する
(値が返らない時にneverを使用しないとコンパイルエラー)

Non-null Assertion Operator

--strictNullChecksを指定してコンパイル

userがnullの時エラーになる可能性がある時、明示的に指定することでコンパイルエラーを抑制

function processUser(user?: Uesr) {
  const s = user!.name
}

インデックス型

type SupportVersions = {
  [env: number]: boolean;
}

const versions: SupportVersions = {
  103: false,
  100: true
}

readonly

可変されないreadonlyプロパティを指定できる

type User = {
  readonly name: string;
  readonly gender: string;
}

非同期のAsync/Await

非同期処理APIのPromiseの簡易的な構文にあたる

function fetchFromServer(id: string): Promise<{success: boolean}> {
  return new Promise(resolve => {
    setTimeout(() = > {
      resolve({success: true})
    }, 100)
  })
}

// 非同期処理を含むasync function の戻り値の型はPromise
async function asyncFunc(): Promise<string> {
  const result = await fetchFromServer('111')
  return result.success
}

// await構文を使うためにはasync functionの中で呼び出す必要あり
(async () => {
  const result = await asncFunc()
  console.log(result)
})()

// Promiseとして扱う際
asyncFunc().then(result => console.log(result))