ReactHooksを使ってみる
最近のReactでは関数コンポーネント&ReactHooksを使うのが主流らしい。
キャッチアップしたところ、state管理がコンポーネントからキレイに分離できてよさそうだった。
index.tsx
import * as React from "react"; import * as ReactDOM from "react-dom"; import Omikuji from "./Omikuji"; let element: JSX.Element = <Omikuji name="kinosuke01"/>; let dom: HTMLElement = document.getElementById('root'); ReactDOM.render(element, dom);
Omikuji.tsx
import React from 'react'; import useOmikuji from './useOmikuji'; interface Props { name: string, }; interface State { }; export default function Omikuji(props: Props) { // state管理を行う処理を、関数コンポーネントに組み込む。 let [result, updateResult] = useOmikuji(); let message: string = '今日の運勢を占います!'; let buttonText: string = '占う!'; if (result) { message = `${props.name}さんの今日の運勢は「${result}」です!!`; buttonText = 'もう一度占う!'; } return ( <div> <p>{message}</p> <button onClick={() => updateResult()}>{buttonText}</button> </div> ) };
useOmikuji.ts
import { useState, useEffect } from 'react'; // // 関数コンポーネントにstate管理を組み込む関数。 // state管理をコンポーネントから分離できる。 // 複数のコンポーネントで使い回すことができる。 // setResultやuseEffectを使うことで実現。 // export default function useOmikuji(): [string, Function] { // result -> state.result // setResult -> setState({result: xxx}); // に対応するイメージ // useStateの引数は、対象stateの初期値 const [result, setResult] = useState(null); function predict(): string { let i: number = Math.floor(Math.random() * 10 % 5); let list: Array<string> = ['大吉', '吉', '中吉', '小吉', '凶']; return list[i]; } function updateResult(): void { setResult('占い中...'); setTimeout(() => { let predictResult: string = predict(); setResult(predictResult); }, 1000); } // useEffectには、 // componentDidMount, componentDidUpdate の // タイミングで呼びたい関数を渡す useEffect(() => { console.log(`「${result}」を取得しました。`); // 戻り値には、componentWillUnmountの // タイミングで呼びたい関数を渡す return (): void => { console.log('さよなら'); return; }; }); return [result, updateResult]; }
参考
React+TypeScript+webpackの環境構築
必要なライブラリのインストール
ビルドツールをインストールする
npm install --save-dev webpack webpack-cli webpack-dev-server typescript ts-loader
React関連のライブラリをインストールする。
ビルド後のファイルに含めるので--save
とする。
npm install --save react react-dom @types/react @types/react-dom
- react-dom: ReactオブジェクトをDOMに反映するライブラリ
- @types/react: Reactに関する型定義
- @types/react-dom: react-domに関する型定義
scriptsの追加
${APP_DIR}/package.json
にscriptsを追加
{ "scripts": { "build:prod": "webpack --mode production", "build": "webpack", "watch": "webpack --watch", "start": "webpack serve" }, # 略 }
TypeScriptの設定を行う
${APP_DIR}/tsconfig.json
に以下を書き出す。
{ "compilerOptions": { "sourceMap": true, "target": "ES5", // ECMAScript 5に変換 "module": "ES2015", // ES Modulesとして出力 "jsx": "react", // JSXの書式を有効に設定 // 非相対パスを指定したとき、インポート対象をnode_modulesから探す "moduleResolution": "node", // targetに含まれていないライブラリを組み込む "lib": [ "ES2020", "DOM" ] } }
webpackの設定を行う
${APP_DIR}/webpack.config.js
に以下を書き出す。
const path = require('path') module.exports = { mode: 'development', entry: { index: path.join(__dirname, '/src/index.tsx'), }, output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, module: { rules: [ { test: /\.tsx?$/, // 拡張子 .ts と .tsx のファイルを use: 'ts-loader', // ts-loaderでトランスパイルする exclude: /node_modules/, // ただし外部ライブラリは除く }, ], }, // 拡張子指定なしでimportできるおまじない resolve: { extensions: [".ts", ".tsx", ".js", ".json"] }, // ES5向けの指定(webpack 5以上で必要) target: ["web", "es5"], devServer: { host: '0.0.0.0', port: 8080, }, };
TypeScriptでReactのコードを書いてみる
${APP_DIR}/src/index.tsx
import * as React from "react"; import * as ReactDOM from "react-dom"; import { ShoppingList } from "./ShoppingList"; let element: JSX.Element = <ShoppingList name="weekend"/>; let dom: HTMLElement = document.getElementById('root'); ReactDOM.render(element, dom);
${APP_DIR}/src/ShoppingList.tsx
import * as React from "react"; interface Props { name: string, }; interface State { }; export class ShoppingList extends React.Component<Props, State> { render() { return ( <div className="shopping-list"> <h1>Shopping List for {this.props.name}</h1> <ul> <li>Instagram</li> <li>WhatsApp</li> <li>Oculus</li> </ul> </div> ); } };
${APP_DIR}/index.html
<!DOCTYPE html> <html> <head></head> <body> <div id="root"></div> <script type="text/javascript" src="./dist/index.js"></script> </body> </html>
ビルドなどをする
# ビルド npm run build # 開発サーバ立ち上げる npm run start
補足
いつの間にか、webpackDevServerの起動方法が変わっていた。
webpack-dev-server
→ webpack serve
参考
TypeScriptのコードをJestでテストする
やり方には、ts-jestを使う方法と、babelを使う方法がある。
babelを使うやり方は、https://kinosuke.hatenablog.jp/entry/2020/01/29/115640 と同じアプローチ。preset-envをpreset-typescriptにするだけ。ただし、型チェックはできない。
今回は、型チェックもやってほしいので、ts-jestを使う方法を試してみる。
ライブラリのインストール
npm install --save-dev jest ts-jest @types/jest
- @types/jest
- jestの型を定義
- ts-jest
- Jest用のTypeScriptプリプロセッサ
- JestがTypeScriptをトランスパイルできるようになる
Jestを設定する
# 設定ファイルを作成する npx jest --init
${APP_DIR}/jest.config.js
に以下を書き込む。
module.exports = { // Jestの検索対象となるパス roots: [ "<rootDir>" ], // テストコードを書いたファイルを特定するための条件 "testMatch": [ "**/__tests__/**/*.+(ts|tsx|js)", "**/?(*.)+(spec|test).+(ts|tsx|js)" ], // ts/tsxファイルに対してts-jestを使うよう設定 "transform": { "^.+\\.(ts|tsx)$": "ts-jest" }, }
テストスクリプトを準備する
${APP_DIR}/package.json
に以下を追加
{ "scripts": { # 略 "test": "jest", "test:w": "jest --watch", "test:coverage": "jest --coverage" }, # 略 }
テストコードを書いてみる
src/Animal.ts
にテスト対象コードを書く。
export class Animal { animaltype: string; name: string; constructor(animalType: string, name: string) { this.animaltype = animalType; this.name = name; } say(): string { let content: string = "`Hello. I am ${this.name} of ${this.animaltype}.`"; return content; } }
test/Animal.test.ts
にテストコードを書く。
import { Animal } from '../src/Animal'; describe('say', () => { it("`Hello`を含む文字列を返すこと", () => { let animal = new Animal('cat', 'popuko'); let reg = /Hello/; let result = animal.say(); expect(reg.test(result)).toEqual(true); }) })
テストを実行する
# 全部をテストする npm run test test/. # ファイル名を指定してテストする npm run test test/Animal.test.ts
補足
テスト時に以下の警告が出た。
importで問題がある場合は、esModuleInterop
を使うとよさそうですね、とのこと。
ts-jest[config] (WARN) message TS151001: If you have issues related to imports, you should consider setting `esModuleInterop` to `true` in your TypeScript configuration file (usually `tsconfig.json`). See https://blogs.msdn.microsoft.com/typescript/2018/01/31/announcing-typescript-2-7/#easier-ecmascript-module-interoperability for more information.
毎回警告が出るのは気になるので、tsconfig.json
に以下を追加。
{ "compilerOptions": { # 略 "esModuleInterop": true } }
esModuleInterop
って何がいいの?
// esModuleInterop無効の場合 // CommonJSのモジュールは、requireで読み込まないとだめ。 `const moment = require('moment');` // esModuleInterop有効な場合 // CommonJSのモジュールもES Modulesと同じ書き方で読み込めるようになる! import moment from 'moment'
参考
TypeScript+webpack環境を作る - ts-loader使う版
https://kinosuke.hatenablog.jp/entry/2021/07/23/165039 のやりなおし。 babelはTypeScriptからJavaScriptへの変換だけをやってくれるだけで、肝心の型チェックはやってくれなかったので、ts-loaderを使ってやってみる。
必要なライブラリをインストール
npm install --save-dev webpack webpack-cli typescript ts-loader
- webpack:
- モジュールバンドラー。
- JavaScript等の複数のファイルを1つにまとめる。
- loaderを組み込むことで、まとめる前に変換処理(ES2015→ES5など)を加えることができる。
- webpack-cli:
- webpackのcli
- ts-loader:
- webpackにTypeScriptの変換処理を組み込むloader
${APP_DIR}/package.json
の更新
scriptsにビルドコマンドを追加する
{ "scripts": { "watch": "webpack --watch", "build": "webpack --mode production" }, "devDependencies": { # 略 } }
${APP_DIR}/tsconfig.json
の作成
TypeScriptをどのように変換するかを設定する
{ "compilerOptions": { "sourceMap": true, "target": "ES5", // TSはECMAScript 5に変換 "module": "ES2015" // TSのモジュールはES Modulesとして出力 } }
- CommonJSのモジュール:
- require/module.exportsで外部JSファイルを扱う
- Node.jsが採用している
- tsconfig.jsonのデフォルト
- ES Modules:
- import/exportで外部JSファイルを扱う
- ES2015が採用している
- webpackのTree Shaking(未使用のimportを振り落とす)が効く
${APP_DIR}/webpack.config.js
の作成
webpackのバンドル設定を行う
const path = require('path') module.exports = { // production: 改行やインデントを削除してJSファイル出力 // development: ソースマップ有効でJSファイル出力 // ただし `webpack --mode production` したときは production が有効になる mode: 'development', // 入力元の指定 entry: { index: path.join(__dirname, '/src/index.ts'), }, // 出力先の指定 output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, module: { rules: [ { test: /\.ts$/, // 拡張子 .ts のファイルを use: 'ts-loader', // ts-loaderでトランスパイルする exclude: /node_modules/, // ただし外部ライブラリは除く }, ], }, // import './MyLib.ts' が // import './MyLib' の書き方でOKになる resolve: { extensions: [ '.ts', '.js', ], }, };
tsファイルを書いてみる
${APP_DIR}/src/index.ts
import { Animal } from './Animal'; let animal = new Animal("cat", "pipimi"); animal.say();
${APP_DIR}/src/Animal.ts
export class Animal { animaltype: string; name: string; constructor(animalType: string, name: string) { this.animaltype = animalType; this.name = name; } say() { console.log(`Hello. I am ${this.name} of ${this.animaltype}.`); } }
ビルドする
npm run build
参考
TypeScript+webpack環境を作る - babel使う版
TypeScriptをはじめてみる - コピペコードで快適生活 の続き
必要なライブラリをインストール
npm install --save-dev webpack webpack-cli typescript babel-loader @babel/core @babel/preset-typescript
- webpack:
- モジュールバンドラー。
- JavaScript等の複数のファイルを1つにまとめる。
- loaderを組み込むことで、まとめる前に変換処理(ES2015→ES5など)を加えることができる。
- webpack-cli:
- webpackのcli
- babel-loader:
- webpackにbabelの変換処理を組み込むloader
- babelは古いブラウザで動くJavaScriptに変換するツール群
- @babel/core:
- babelのコア機能
- @babel/preset-typescript
- TypeScript→JavaScriptの変換を行う
- babelでの変換処理本体は、プラグインとして提供される
- @babel/preset-env は最新のJavaScriptからの変換を行う
${APP_DIR}/package.json
の更新
scriptsにビルドコマンドを追加する
{ "scripts": { "watch": "webpack --watch", "build": "webpack --mode production" }, "devDependencies": { "@babel/core": "^7.14.8", "@babel/preset-typescript": "^7.14.5", "babel-loader": "^8.2.2", "typescript": "^4.3.5", "webpack": "^5.45.1", "webpack-cli": "^4.7.2" } }
${APP_DIR}/tsconfig.json
の作成
TypeScriptをどのように変換するかを設定する
{ "compilerOptions": { "sourceMap": true, "target": "ES5", // TSはECMAScript 5に変換 "module": "ES2015" // TSのモジュールはES Modulesとして出力 } }
- CommonJSのモジュール:
- require/module.exportsで外部JSファイルを扱う
- Node.jsが採用している
- tsconfig.jsonのデフォルト
- ES Modules:
- import/exportで外部JSファイルを扱う
- ES2015が採用している
- webpackのTree Shaking(未使用のimportを振り落とす)が効く
${APP_DIR}/webpack.config.js
の作成
webpackのバンドル設定を行う
const path = require('path') module.exports = { // production: 改行やインデントを削除してJSファイル出力 // development: ソースマップ有効でJSファイル出力 // ただし `webpack --mode production` したときは production が有効になる mode: 'development', // 入力元の指定 entry: { index: path.join(__dirname, '/src/index.ts'), }, // 出力先の指定 output: { path: path.join(__dirname, 'dist'), filename: '[name].js', }, module: { rules: [ { test: /\.ts$/, // 拡張子 .ts のファイルを loader: 'babel-loader', // babelでトランスパイルする options: { presets: ['@babel/preset-typescript'] }, exclude: /node_modules/, // ただし外部ライブラリは除く }, ], }, // import './MyLib.ts' が // import './MyLib' の書き方でOKになる resolve: { extensions: [ '.ts', '.js', ], }, };
tsファイルを書いてみる
${APP_DIR}/src/index.ts
import { Animal } from './Animal'; let animal = new Animal("cat", "pipimi"); animal.say();
${APP_DIR}/src/Animal.ts
export class Animal { animaltype: string; name: string; constructor(animalType: string, name: string) { this.animaltype = animalType; this.name = name; } say() { console.log(`Hello. I am ${this.name} of ${this.animaltype}.`); } }
ビルドする
npm run build
参考
TypeScriptをはじめてみる
TypeScriptの雰囲気を掴むために、5分でできるチュートリアルをやってみる。
環境の準備
PC環境は汚したくないので、dockerで環境を作る。
#{APP_DIR}/docker-compose.yml
に以下を書く
version: '3' services: node: image: node volumes: - "./:/var/app" tty: true
コンテナを起動してログインする
docker-compose up docker-compose exec node /bin/bash
TypeScriptをインストール
cd /var/app npm install -g typescript
TypeScriptを書いていく
#{APP_DIR}/greeter.ts
を書いていく
// クラスの定義 class Student { // プロパティの宣言 fullName: string; // コンストラクタ // "引数名: 型" で引数の型指定ができる // 引数にpublicをつけることでプロパティとなる constructor(public firstName: string, public middleInitial: string, public lastName: string) { this.fullName = firstName + " " + middleInitial + " " + lastName; } } // インターフェースの定義 interface Person { firstName: string; lastName: string; } function greeter(person: Person) { return "Hello, " + person.firstName + " " + person.lastName; } // let user = { firstName: "Jane", lastName: "User"}; let user = new Student("Jone", "M.", "User"); document.body.innerHTML = greeter(user);
トランスパイルする
以下のコマンドでトランスパイルできる。 同じディレクトリにjsファイルができる。
tsc greeter.ts
参考:
Kubernetesの設定ファイルの書き方キホン
確認できる環境(minikube)の準備
$ brew install kubectl $ kubectl version --client $ brew install minikube $ minikube version
minikube コマンド
minikube start # 起動(デフォルトはDockerで起動) minikube status # 状態表示 minikube ssh # ホストOSにログイン minikube stop # 停止
※minikubeを起動すると、~/.kube/config
がよしなに変更されて、kubectlコマンドでminikube環境への接続が可能になる。
Pod
- 1個以上のコンテナの集まり
- 1Podに1コンテナ配置することが多い。
- 複数のコンテナでストレージを共有するようなケースでは1Podに複数コンテナを配置する
- Deploymentとセットで定義することが多いが、Pod単体を設定を書くこともできる。
- https://kubernetes.io/ja/docs/concepts/workloads/pods/pod-overview/
Deployment
- Podをどこにいくつ配置するかを管理するもの
- PodはDeploymentと紐付けられてデプロイされる
- Deploymentの設定ファイルの中にPodの設定(Podテンプレート)を含む
- https://kubernetes.io/ja/docs/concepts/workloads/controllers/deployment/
Deploymentの設定ファイル
apiVersion: apps/v1 # 固定値 kind: Deployment # 固定値 metadata: name: nginx-deployment # Deployment名 labels: app: nginx # Podテンプレートと同じ値 spec: # レプリカ数 replicas: 3 # デプロイメントの履歴数 # デフォルト値は10 revisionHistoryLimit: 5 # このDeployamentが管理するPodをどう見つけるかの定義 # 基本はPodテンプレートの`labels`と同じにすれば良い selector: matchLabels: app: nginx # Podテンプレート template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.7.9 ports: - containerPort: 80 # コンテナが開けるポート
Deploymentの作成・確認・削除
$ kubectl apply -f nginx-deployment.yml # 設定をもとにオブジェクトを作成する $ kubectl get deployment # オブジェクトの一覧を表示する $ kubectl describe deployment nginx-deployment # オブジェクトの詳細情報を表示する $ kubectl delete -f nginx-deployment.yml # オブジェクトを削除する
Service
- Podにアクセスするためのゲートウェイ
- Pod自体は壊れたり作成される度にIPアドレスが変わるので、仲立してくれるService経由でアクセスする。
- https://kubernetes.io/ja/docs/concepts/services-networking/service/
Serviceの設定ファイル
kind: Service # 固定値 apiVersion: v1 # 固定値 metadata: # Serviceの名前 # 他のPodから `http://nginx-service` でアクセス可 name: nginx-service spec: # 接続タイプ # - ClusterIP クラスタ内部のみ接続可能(デフォルト値) # - NodePort クラスタ外から接続可能 type: NodePort # どのPodを対象にするか selector: app: nginx # 公開するポート ports: - name: http # 複数ポートを指定するときはname必須 protocol: TCP port: 80 # 受け口ポート targetPort: 80 # 転送先ポート - name: https protocol: TCP port: 443 # 受け口ポート targetPort: 443 # 転送先ポート
Serviceの作成・確認・削除
$ kubectl apply -f nginx-service.yml # 設定をもとにオブジェクトを作成する $ kubectl get service # オブジェクトの一覧を表示する $ kubectl describe service nginx-service # オブジェクトの詳細情報を表示する $ kubectl delete -f nginx-service.yml # オブジェクトを削除する
アクセスできるURLの確認
$ minikube service nginx-service --url http://192.168.64.2:32599
コンテナにログインする
以下のコマンドでPod一覧を表示する。
$ kubectl get pod NAME READY STATUS RESTARTS AGE nginx-deployment-5d59d67564-2gzp2 1/1 Running 2 2d1h nginx-deployment-5d59d67564-4mvjs 1/1 Running 2 2d1h nginx-deployment-5d59d67564-wlglr 1/1 Running 2 2d1h
で、NAME値を指定してkubecltコマンドを実行することで、sshログインができる。
kubectl exec --stdin -tty nginx-deployment-5d59d67564-2gzp2 -- /bin/bas