- 投稿日:2022-03-22T22:03:44+09:00
Google アナリティクス 4 プロパティ
memo. 後日また調べる。 結論 「セッション中心の計測」から「イベント中心の計測」へ変更されている。 理由 ウェブとアプリそれぞれ「自動的に収集されるイベント」「測定機能の強化」「推奨イベント」「カスタム イベント」と 4 つの分類となったため。 具体的には フックを利用した実装例。 useEffect を利用するとよさそう。 備考 gtag('config', ...) とは gtag('config', '<TARGET_ID>', {<additional_config_info>}); config という名称だったので、なぜ毎回初期化しているのか分からなかった。 「実はgtag.jsでは、gtag('config')関数がページビューを送信しています。」とのことらしい。 2重計測のヒント。 「GA4 はデフォルトで History の変更を検知して自動的にページビューを送る機能が備わったため、特別な設定はいらない」
- 投稿日:2022-03-22T20:28:00+09:00
rails7(apiモード), react, dockerを使って環境構築してみた
環境 Ruby 3.1.1 Rails 7.0.2.3(apiモード) MySQL8.0 node 17.2 rails→3001port、react→3000portになるように設定する 環境構築 まずは以下のディレクトリとファイルを作成 invest_qa |-api |-Gemfile |-Gemfile.lock |-Dockerfile |-entrypoint.sh |-front |-dockerfile |-docker-compose.yml docker-compose.ymlは以下のように docker-compose.yml version: '3' services: db: platform: linux/x86_64 # M1チップ対応のため追記 image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: password ports: - '3306:3306' command: --default-authentication-plugin=mysql_native_password volumes: - mysql-data:/var/lib/mysql api: build: ./api command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - ./api:/myapp - gem_data:/usr/local/bundle ports: - "3001:3000" depends_on: - db stdin_open: true tty: true front: build: ./front command: yarn start ports: - "3000:3000" volumes: - ./front:/myapp depends_on: - api volumes: mysql-data: gem_data: driver: local dockerの環境構築(バックエンド編) Gemfileは以下のように Gemfile source 'https://rubygems.org' gem 'rails', '~> 7.0.2.3' Gemfile.lockは何も書かなくてOK Gemfile.lock entrypoint.shは以下のように entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" dockerfileは以下のように Dockerfile FROM ruby:3.1.1 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \ && apt-get update -qq \ && apt-get install -y nodejs yarn \ && mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] フロントとまとめdockerにbuildしていくのでひとまずフロントエンドの環境構築へ dockerの環境構築(フロントエンド編) Dockerfileは以下のように Dockerfile FROM node:17.2 RUN mkdir /myapp WORKDIR /myapp イメージの構築 以下を実行しイメージの構築をする docker-compose build railsアプリの構築 まずはrailsアプリをapiモードで作成 docker-compose run api rails new . --force --no-deps --database=mysql --skip-test --webpacker --api Gemfileが更新されてるのでイメージを再構築する docker-compose build database.ymlを修正する api/config/database.yml # # And be sure to use new-style password hashing: # https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html # default: &default adapter: mysql2 encoding: utf8 #ここ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password #ここ docker-compose.ymlで設定したパスワード host: db #ここ docker-compose.ymlでdepend_onしてる development: <<: *default database: myapp_development # Warning: The database defined as "test" will be erased and # re- (以下略) 続いてデータベースの作成のため以下を実行 docker-compose run api rake db:create reactアプリの構築 まずはreactアプリの作成 docker-compose run front npx create-react-app front frontディレクトリに新しいfrontディレクトリが作成されてしまう。。。 元あるfrontディレクトリにコピペしてなんとかしないといけない ともあれこれやればなんとかなる。 それぞれのアプリにアクセス 以下のコマンドを実行してコンテナの構築・起動をする docker-compose up http://localhost:3001 http://localhost:3000 以上で環境構築ができました。
- 投稿日:2022-03-22T19:11:34+09:00
Go + MySQL + React のDocker開発環境を作成する
はじめに GoとMySQLとReact(&TypeScript)を使ったポートフォリオ作成をしようと思い、最初に勉強も兼ねてDocker開発環境を作成しました。 そこでQiitaやZennなどで書かれている記事を参考にしていましたが、最終的に他の方とは違う構成になったので自分も記事を執筆しようと思いました。 作成したもの ディレクトリ構成 . ├── README.md ├── db │ ├── data │ └── my.cnf ├── docker-compose.yml ├── go-app │ ├── Dockerfile │ └── main.go ├── react-ts-app │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── node_modules │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── src │ └── tsconfig.json └── variables.env Dockerfile(go-app) FROM golang:latest WORKDIR /app/go COPY ./ ./ ENTRYPOINT [ "go", "run", "main.go" ] Dockerfile(react-ts-app) FROM node:17.7.2 WORKDIR /app/react-ts COPY package*.json ./ RUN yarn install COPY . . ENTRYPOINT [ "npm", "start" ] dockerignoreファイル(react-ts-app) node_modules npm-debug.log yarn-error.log docker-compose.yml version: '3' services: react-ts: build: ./react-ts-app ports: - '3000:3000' tty: true stdin_open: true go: build: ./go-app ports: - '8000:8000' tty: true stdin_open: true db: image: mysql:8.0 volumes: - './db/data:/var/lib/mysql' - './db/my.cnf:/etc/mysql/conf.d/my.cnf' # MYSQL_ROOT_PASSWORDのみをenvファイルで設定 env_file: - variables.env ports: - '3306:3306' my.cnf [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_unicode_ci [client] default-character-set=utf8mb4 解説 Dockerfileは、docker-composeを使わなくても単体で動作することが可能だよねっていう状態を意識しました。 また、docker-composeはあくまでコンテナを立ち上げるのを楽にするためのものだと意識しました。 Dockerfile ENTRYPOINTを指定していることで、docker-composeをupした瞬間にdb・go・react-tsの3つのコンテナが起動&サーバーも起動するようになっています。 COPY句によってDockerfile自体もイメージ内にコピーされてしまいますが、イメージサイズへの影響が小さい・dockerignoreファイルを作成してもビルドにかかる時間は変わらないと判断し、許容しています。 dockerignoreファイルによるビルド時間への影響は下記のサイトが参考になります。 dockerignore(React用) node_modules、npm-debug.log、yarn-error.log の3つを指定しています。 これによってローカルモジュール・デバッグログ・エラーログがDockerイメージにコピーされないようにします。 node_modulesはパッケージがインストールされるディレクトリです。必要なパッケージはソースコードによって異なるため、node_modulesをイメージ内にコピーする必要はないと言えます。 また、node_modules内に多くのパッケージをインストールしている場合、イメージをビルドするときに多くの時間を要します。そのため、node_modules は dockerignoreファイルに登録することが基本となっています。 node_modules のコピーはしませんがコンテナ内でもパッケージは必要になります。ReactのDockerfileでは package.json と package-lock.json をコピーし、yarn install を行うことで、コンテナ使用者が必要なパッケージのみをインストールするようにしています。 Go FROM でイメージを取得するときに、golangにするか、ubuntuのようなOSイメージにするか、golang:<version>-alpineにするかで迷いました。 この中で一番設定ミスが起こりづらそうだと感じたgolangにしました。 dockerの知識量的にOSイメージからGoの環境を整えるのはハードルが高そう。また、alpineはGo projectに公式サポートされていないとDockerhubに書いてあるといった理由で、この2つは選択肢から外しました。 ENTRYPOINT によって、コンテナが立ち上がるとともに go run main.go でサーバーも立ち上がるようにしています。 以下、main.goのコードです。 main.go package main import ( "fmt" "net/http" ) func main() { http.HandleFunc("/", echoHello) http.ListenAndServe(":8000", nil) } func echoHello(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "<h1>Hello World</h1>") } React nodeイメージのDockerfileを見ると、デフォルトでyarnが入っていることがわかります。 なので、yarnをわざわざインストールする必要はありません。 また、Dockerfileやcomposeにcreat-react-appの記述もされている方がいますが、コンテナを起動するたびに新規のプロジェクト作成するのは時間がかかりそうだと感じたので僕は書きませんでした。 あくまでソースコードはホスト上にあり、コンテナへそれらをコピーすることで正常に動くようにしたいと思いました。 次に最も重要な部分ですが、ReactのDockerfileではビルド時間の短縮のためにCOPY句を2つ使っています。 COPY package*.json ./ RUN yarn install COPY . . dockerにはレイヤー構造とキャッシュという概念があり、これを活用することでイメージのビルド時間を短縮できます。 レイヤーとキャッシュについては解説すると長くなるので、ここでは割愛させていただきます。 Reactの開発では頻繁にソースコードを変えると思います。 もし yarn install の前に COPY . . を行うと、ソースコードが変更されるたびにパッケージをインストールし直すことになります。 パッケージを追加・変更していなくても毎回この処理が行われ、多くの時間が費やされてしまいます。 COPY package*.json ./ と yarn install を先に行なっていればレイヤーキャッシュが使用され、ビルドの時間を大幅に節約できます。 以上の理由から、COPYを2回使うようにしました。 この記述方法は、こちらのサイトを参考にしました。 MySQL MYSQL_ROOT_PASSWORDを設定しなければエラーが出ます。 共有をしないのであればcompose上に直接設定してもいいですが、僕はgithubを通じて共有しようと考えていたのでenvファイルを使いました。 MySQLはローカルホスト上ではmysql.server startを使って起動する必要があるかと思いますが、このコンテナ上ではそれが必要ありません。コンテナが立ち上がるとともにDBサーバーも立ち上がります。 my.cnfファイルを用意していありますが、ここでは文字化けを防ぐための設定しかしていません。 もしかしたら他にももっと設定した方がいいものもあるかもしれませんが、沼にハマりそうなのでやめました。 最後に 人によって書き方が違って正しい情報を入手しづらく、結構時間がかかってしまいました。 もちろん僕が書いたものも正しいとは言い切れないので、これから更に知識をアップデートしていきたいなと思います。
- 投稿日:2022-03-22T15:20:01+09:00
Reactチュートリアルをリファクタリングしてみた!
Reactチュートリアルをリファクタリングしてみた! Reactを触り始めて1週間も経ってないのですが、Reactチュートリアルを勉強していてTypeScriptや関数コンポーネントを使ってリファクタリングできないのかなーと思っていたら、とても参考になる記事があったのでそれを参考にしつつチュートリアルをリファクタしてみました!っていう記事です!アウトプットみたいな感じなのでご指摘があればどんどんして欲しいです! 以前書いたReactチュートリアルのアウトプット記事 参考にさせていただいた記事 Hooks + TypeScriptでReact公式チュートリアルをリファクタ はじめに インターンの方でTypeScriptを使っていて凄さを痛感しているのですが、一度まとめてみたいと思います。 Hooksもまだ知らないことが多いので調べてみます! TypeScriptとは TypeScriptとは、JavaScriptを拡張して実装されたオープンソースのプログラミング言語のことです。2012年頃にマイクロソフト社によって開発され、2017年2月に正式にリリースが行われました。 TypeScriptが開発された理由には、JavaScriptのプログラミング言語を利用して大規模アプリを開発する際に、JavaScriptの複雑なソースコードを扱わなければならないため、独自のツールが必要となったことが挙げられます。そしてTypeScriptは、JavaScriptのデメリットである使いにくさを解消し、エラーやバグなどのトラブルが発生しにくいプログラミング言語として開発されました。 TypeScriptとJavaScriptの違い まずJavaScriptは動的型付け言語であり、TypeScriptは静的型付け言語であるという違いがあります。これは、変数に関するデータ型の違いです。データ型とはプログラムで使用されるデータの種類を指し、JavaScriptのソースコードでは、プログラムを実行する際にデータ型が自動的に定められます。この特徴をもつプログラム言語は動的型付け言語と呼ばれます。ソースコードをシンプルに記述できるというメリットがありますが、プログラムを実行してみないとエラーやバグがわからないというデメリットもあります。他の動的型付け言語の代表例として、RubyやPythonなどが挙げられるでしょう。 自分は大学でC言語を勉強していたのですが、それまでRubyやPythonなどの動的型付け言語しか触れてこなかったので苦労した記憶があります。インターン先での開発は大規模開発とまではいかないまでも、TypeScriotによる型付けの恩恵はすごい感じていますね。 TypeScriptを使うメリット ・ 型宣言で開発効率と安全性を高められる ・ クラスを使ってオブジェクト指向開発ができる ・ コンパイルを通して旧バージョンのJavaScriptを生成できる など色々あります! インターン先で初めてTypeScriptを見たときは、「これを書いて何の意味があるんだ」とまで思っていたのですが、上にもあるようにTypeScriptを使うことで、「どんなデータを渡せばいいか」「エラーチェックができる」などのメリットの岡grでTypeScriptを書く手間以上に得られる恩恵というのはとても大きいと思います。 引用 TypeScriptとは? TypeScriptを使うべき3つのメリットと使い方 クラスコンポーネントと関数コンポーネントの違い Hooksを理解する前にクラスコンポーネントと関数コンポーネントの違いについてまとめます 1. JSXのレンダリング 関数コンポーネント => JSXを返すプレーンなJavaScript関数 例 import React from "react"; const FunctionalComponent = () => { return <h1>Hello, world</h1>; }; クラスコンポーネント => React.Componentを拡張するJavaScriptクラス。renderメソッドを含む。 例 import React from "react"; class ClassComponent extends React.Component { render() { return <h1>Hello, world</h1>; } } 2.propsの受け渡し 関数コンポーネントの場合 // 関数の引数としてpropsを渡す const FunctionalComponent = (props) => { return <h1>Hello, {props.name}</h1>; }; クラスコンポーネントの場合 // thisを使ってpropsを参照する class ClassComponent extends React.Component { render() { const { name } = this.props; return <h1>Hello, { name }</h1>; } } 3.stateの処理 関数コンポーネントの場合 // useStateを使ってstateを管理する const FunctionalComponent = () => { const [count, setCount] = React.useState(0); return ( <div> <p>count: {count}</p> <button onClick={() => setCount(count + 1)}>Click</button> </div> ); }; クラスコンポーネントの場合 // constructorを使ってstateを定義する class ClassComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <p>count: {this.state.count} times</p> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Click </button> </div> ); } 今回、Reactチュートリアルをリファクタして絶対に関数コンポーネントの方が優秀だと思いましたね笑 上記の比較からも分かるように関数コンポーネントの方がコードの記述量が少ないというメリットがあるため、開発や理解やテストがしやすいですしね。 しかし、関数コンポーネントではstateの管理ができないためHooksの理解が不可欠になっているのでしっかり勉強したいと思います! 参考 React:関数コンポーネントとクラスコンポーネントの違い Hooksとは React Hooks は React 16.8(2019 年 2 月リリース)で追加された機能です。 2021 年現在において React でアプリケーションを構築するためには理解が必須の機能といっても過言ではないでしょう。 React Hooks を使うことによって React の関数型コンポーネントで状態(state)を持つことやコンポーネントのライフサイクルに応じた処理を記述できます。 下記のようなHookがあります。 ・useState ・useEffect ・useContext ・useReducer ・useMemo ・useCallback 今回のリファクタではuseStateしか利用しないのですが、他のフックもめちゃめちゃ重要だと思うのでしっかり理解できるように頑張っていきたいと思います!! リファクタしていく!! リファクタ前のコード import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } class Board extends React.Component { renderSquare(i) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); } render() { return ( <div> <div className="board-row"> {this.renderSquare(0)} {this.renderSquare(1)} {this.renderSquare(2)} </div> <div className="board-row"> {this.renderSquare(3)} {this.renderSquare(4)} {this.renderSquare(5)} </div> <div className="board-row"> {this.renderSquare(6)} {this.renderSquare(7)} {this.renderSquare(8)} </div> </div> ); } } class Game extends React.Component { constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null) }], stepNumber: 0, xIsNext: true, }; } handleClick(i) { const history = this.state.history.slice(0, this.state.stepNumber + 1); const current = history[history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = this.state.xIsNext ? 'X' : 'O'; this.setState({ history: history.concat([{ squares: squares }]), stepNumber: history.length, xIsNext: !this.state.xIsNext, }); } jumpTo(step) { this.setState({ stepNumber: step, xIsNext: step % 2 === 0, }); } render() { const history = this.state.history; const current = history[this.state.stepNumber]; const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? 'Go to move #' + move : 'Go to game start'; return ( <li key={move}> <button onClick={() => this.jumpTo(move)}>{desc}</button> </li> ); }); let status; if (winner) { status = 'Winner: ' + winner; } else { status = 'Next player: ' + (this.state.xIsNext ? 'X' : 'O'); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i) => this.handleClick(i)} /> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); } } function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; } // ======================================== ReactDOM.render(<Game />, document.getElementById('root')); JavaScriptからTypeScriptへ // 雛形プロジェクトを作成 npx create-react-app refactor-react-app --typescript // srcフォルダのファイルを削除 cd src cd rm -f * srcフォルダ内にindex.tsxを作成してリファクタ前のコードを貼る コンパイルエラーを解決する こんな感じで関数の引数などに対してエラーが出てくるので(i: number)のように型を定義してエラーを解消します 次に以下のcaluculateWinner関数をリファクタします function calculateWinner(squares) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; for (let i = 0; i < lines.length; i++) { const [a, b, c] = lines[i]; if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { return squares[a]; } } return null; } この関数の引数であるsquaresにはstring型であるOかX、そしてnullのいずれかが入ります。 型定義には二つのアプローチがあります。 一つはGenericsという機能(<>で囲んでいる部分)を使って calculateWinner(squares: Array<string | null>)という指定の仕方がある。 <string | null>によってstringまたはnullの型が入ると表すことができ、このような複数の型のどれかにあてはまるように定義された型をUnion型と呼び、GenericsもUnion型もTypeScriptの機能である もう一つのアプローチがtypeキーワードを使ってtype ISquare = 'X' | 'O' | null;と指定するやり方だ。この方法を用いれば ’X’もしくは’O’もしくはnullのみを許容する独自の型を定義することができます(リテラル型)。こちらの方が厳格に制約できるため、今回はこの方法でISquare型を定義し、引数squaresの型を「ISquare型が入った配列」に制約します。 とのことだ。 なのでcaluculateWinner関数は以下のようになる type ISquare = 'X' | 'O' | null; ... function calculateWinner(squares: Array<ISquare>) { const lines = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6], ]; ... 次にSquareコンポーネントの引数propsにエラーが出ているので、関数の引数と同様に型を制約していく リファクタ前 function Square(props) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } リファクタ後 interface SquareProps { value: ISquare; onClick: () => void; } function Square(props: SquareProps) { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } interfaceを使って型を定義している。 SquareコンポーネントがISquare型のvalue, 何も返さない無名関数のonClickを引数として受け取ることを型注釈で明示することができた。 同様にBoardコンポーネントも型を定義する interface BoardProps { squares: ISquare[]; onClick: (i: number) => void; } class Board extends React.Component<BoardProps> { renderSquare(i: number) { return ( <Square value={this.props.squares[i]} onClick={() => this.props.onClick(i)} /> ); } Gameコンポーネントも型定義をする class Game extends React.Component<{}> { constructor(props: {}) { super(props); this.state = { history: [{ squares: Array(9).fill(null) }], stepNumber: 0, xIsNext: true, }; } 次にGameコンポーネントのstateの型を制約する リファクタ前 class Game extends React.Component { constructor(props) { super(props); this.state = { history: [{ squares: Array(9).fill(null) }], stepNumber: 0, xIsNext: true, }; } ... リファクタ前だとthis.state の型が定義されていないためエラーが起きてしまうのでstateに型を定義してあげる type History = { squares: ISquare[]; }; ... interface GameState { history: History[]; stepNumber: number; xIsNext: boolean; } class Game extends React.Component<{}, GameState> { constructor(props: {}) { super(props); this.state = { history: [{ squares: Array(9).fill(null) }], stepNumber: 0, xIsNext: true, }; } React.Component<{}, GameState>では1つ目の引数にpropsの型定義を、2つ目の引数にはstateの型定義を受け取る ここまで作業をすすめると、TypeScriptのコンパイルエラーはすべて解消される ファイルを分割する ├── README.md ├── node_modules ├── package.json ├── public ├── src │ ├── components │ │ ├── Board.tsx │ │ ├── Game.tsx │ │ └── Square.tsx │ ├── index.css │ ├── index.tsx │ ├── interface.ts │ └── react-app-env.d.ts ├── tsconfig.json └── yarn.lock こんな感じでファイルを分割します それぞれのファイルでimportやexportをします クラスベースのコンポーネントから関数ベースのコンポーネントへ Boardコンポーネント import React from 'react'; import { ISquare } from '../interface'; import Square from './Square' interface BoardProps { squares: ISquare[]; onClick: (i: number) => void; } function Board(props: BoardProps) { const renderSquare = (i: number) => { return ( <Square value={props.squares[i]} onClick={() => props.onClick(i)} /> ); } return ( <div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); } export default Board; クラスのプロパティだったpropsを関数の引数として扱うためthisキーワードを削除、JSXを返すrenderメソッドの中身を関数のreturnとする。 次に関数コンポーネントをReact.FCの形式に書き換える Squareコンポーネント const Square: React.FC<SquareProps> = (props) => { return ( <button className="square" onClick={props.onClick}> {props.value} </button> ); } React.FC型を使うことで、「これはReactの関数コンポーネントです」ということが明確にわかるようになる 以下のようにするとpropsも省くことができる const Square: React.FC<SquareProps> = ({ value, onClick }) => { return ( <button className="square" onClick={onClick}> {value} </button> ); }; Boardコンポーネントも書き換える const Board: React.FC<BoardProps> = ({ squares, onClick }) => { const renderSquare = (i: number) => { return ( <Square value={squares[i]} onClick={() => onClick(i)} /> ); } 次にGameコンポーネントを書き換える import React, { useState } from "react"; import { ISquare, History } from '../interface' import Board from './Board' const Game: React.FC = () => { const [history, setHistory] = useState<History>([{ squares: Array(9).fill(null)}]); const [stepNumber, setStepNumber] = useState<number>(0); const [xIsNext, setXIsNext] = useState<boolean>(true); Hooksを使うことで、関数コンポーネントでもstateを管理できるようにする const [history, setHistory] = useState<History>([{ squares: Array(9).fill(null)}]); 上記のコードの場合、historyがstateの変数となり、setHistoryがstateを更新するための関数、<History>がhistoryの型となる。([{ squares: Array(9).fill(null)}])はhistoryの初期値となる。 同様にstepNumberとxIsNextについてもuseStateを用いて書き直す // stepNumberがstate変数、setStepNumberがstateを更新する関数、<number>がstepNumberの型、(0)がstepNumberの初期値となる const [stepNumber, setStepNumber] = useState<number>(0); // xIsNextがstate変数、setXisNextがstateを更新する関数、<boolean>がhistoryの型、(true)がxIsNextの初期値となる const [xIsNext, setXIsNext] = useState<boolean>(true); handleClickやjumpToを関数のなかの関数として書き直す const handleClick = (i: number) => { const _history = history.slice(0, stepNumber + 1); const current = _history[_history.length - 1]; const squares = current.squares.slice(); if (calculateWinner(squares) || squares[i]) { return; } squares[i] = xIsNext ? "X" : "O"; setHistory(_history.concat([{ squares: squares }])); setStepNumber(_history.length); setXIsNext(!xIsNext); } const jumpTo = (step: number) => { setStepNumber(step); setXIsNext(step % 2 === 0); } setStateを使っていた部分を、setHistoryやsetStepNumber、setXisNextを使う 次にrenderメソッドを書き換える // render() { const current = history[stepNumber]; //const history = this.state.historyだったのをstateのhistoryを使ってcurrentに代入 const winner = calculateWinner(current.squares); const moves = history.map((step, move) => { const desc = move ? "Go to move #" + move : "Go to game start"; return ( <li key={move}> <button onClick={() => jumpTo(move)}>{desc}</button> </li> ); }); let status; if (winner) { status = "Winner: " + winner; } else { status = 'Next player: ' + (xIsNext ? 'X' : 'O'); } return ( <div className="game"> <div className="game-board"> <Board squares={current.squares} onClick={(i: number) => handleClick(i)} // /> </div> <div className="game-info"> <div>{status}</div> <ol>{moves}</ol> </div> </div> ); // } これでとりあえずのリファクタリングは完成です!! 今回作ったReactチュートリアルのコード まとめ 今回、ReactチュートリアルをリファクタリングしてみてTypeScriptや関数コンポーネント、Hooksを勉強したのですが、コードの可読性や保守性、パフォーマンスを考えたコードの記述というのは今までしっかり触れたことがなかったので、これから先新卒エンジニアとして大規模なアプリケーション開発に関わっていくためには必須の能力だと思うので勉強を頑張っていきたいと思います!拙い文章ではありましたが、読んでいただきありがとうございました!
- 投稿日:2022-03-22T13:54:36+09:00
[Docker]Reactの環境構築をコマンド1発で終わらせる
はじめに 「DockerでReactの環境構築をしたい、そして楽をしたい」と思いました。 手順 1. リポジトリをclone git clone https://github.com/4649rixxxz/qiita-react.git 2. Reactアプリケーションの作成 cd qiita-react qiita-react % make create-project 3. サーバの立ち上げ qiita-react % make app /usr/src/react-app # npm start http://localhost:3000 にアクセスしてみると...おめでとうございます!! その他のコマンド コンテナの起動とサーバの立ち上げ make start 上記のコマンドはコンテナの起動に加えて、「npm start」もしてくれます。 コンテナの停止 make stop TODOリスト 新しい言語やライブラリなどを学ぶ時は「TODOリスト作りたい!」となると思います。 こちらおすすめです。 さいごに Makefile最高。
- 投稿日:2022-03-22T13:54:14+09:00
useQuery で取得した変数を 別の useQuery の variables に代入する方法
備忘録。 下のコードのようなことをしようとしても、怒られたり、動かなかったりする。 const {data: data1, loading: loading1, error: error1} = useQuery(SOME_QUERY1) const {data: data2, loading: loading2, error: error2} = useQuery(SOME_QUERY2, { variables: {hoge: data1.someQuery1.hoge} }) そのような場合は、useLazyQuery と useEffect を使う。 const {data: data1, loading: loading1, error: error1} = useQuery(SOME_QUERY1) const [fetchSecondData , {data: data2, loading: loading2, error: error2}] = useLazyQuery(SOME_QUERY2) useEffect(() => { if (data1.someQuery1.hoge) { fetchSecondData({variables: {hoge: data1.someQuery1.hoge}}) } }, [data1]) if (loading1 || loading2) return <div>loading</div> if (error1 || error2) return <div>error</div> return( <>data2.someQuery2.hoge</> )
- 投稿日:2022-03-22T11:36:05+09:00
Reactの超きほん 〜Reactってなに?〜
React(リアクト)ってなに? JavaScriptのフロントエンドのライブラリです。 Facebook社(現在はmeta社)によりオープンソースで開発されました。 元々人気のあるライブラリでしたが、React16.8のReact Hooksという便利な関数群の登場で、人気に拍車がかかりました。 Reactの特徴 jQueryよりもコードがぱっと見で理解しやすく、複雑になりがちな見た目(DOM)の操作もシンプルに設計できる DOM操作の前に「仮想DOM」で検査して差分のみレンダリングするので、パフォーマンスが良い コンポーネント指向で部品の再利用やデバックがしやすく、大規模開発に向く JSX 前述の特徴を実現している要因のひとつは、JSXというJavaScriptの拡張構文です。 ざっくり言うとHTMLとJavaScriptの機能をあわせもち、 JavaScriptでありながらHTMLのようなタグを出力できます。 JSXを含むファイルの拡張子は.jsxとなります。(TypeScriptなら.tsx) 環境構築 Node.jsをインストールします。 Macの方はこちらの記事が参考になります。 MacにNode.jsをインストール(Qiita) プロジェクトの作成 以下のコマンドで簡単に開発環境を構築できます。 shell npx create-react-app APP_NAME # => Happy hacking! と表示されたら完了 npxは、npm5.2以降から利用できるパッケージランナーツールです。 create-react-app よく使うライブラリをまとめてインストールするライブラリ。 ディレクトリ構成 rootディレクトリ内は以下のようになります。 public src App.js index.js App.css node_modules package.json yarn.lock(yarnの場合) README.md publicディレクトリ index.htmlやファビコン画像などが格納されます。 srcディレクトリ 基本的にこの下にコンポーネントやコードを書いていきます。 index.jsによって、index.htmlのid="root"要素がApp.jsで上書きされ、レンダリングされます。 index.js // ここで外部モジュールを読み込み import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; // ここで#rootにApp.jsをマウントします ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals(); import JavaScriptの基本的なインポート・エクスポートについてはこちら React.StrictMode 開発環境でエラーが発生した際に、警告を出してくれる機能 strict モード - React マウント DOMツリーにReactコンポーネント(DOM)を挿入する処理 レンダリング react-domからimportしたReactDOM変数をrender()関数でレンダリングする render関数 ReactDOM.render(reactElement, DOM); サーバ起動 npmもしくはyarnでサーバを起動します shell npm start # もしくは yarn start # ローカルならhttp://localhost:3000 でアクセス # startコマンドはpackage.jsonで定義されている コンポーネントの作成 後日別の記事で紹介します! React Hooks 後日別の記事で紹介します! 補足 さいごまでお読みいただき、ありがとうございます! この記事はプログラミング初心者であるわたしが、毎日の学習ログとして書きました。 ご質問やご意見、間違いのご指摘などがあれば、気軽にコメントください。 また、平日にもくもく作業したり質問しあえる仲間も募集しています。 ご興味がある方はTwitterなどでご連絡くださいー! これからもどうぞよろしくお願いします
- 投稿日:2022-03-22T10:15:50+09:00
React Conf 2021 全体ざっくりまとめ
いよいよ React 18 のリリースが近づいてきています。 いい機会なので今更ながら React Conf 2021 の内容をざっくりとまとめて紹介しようと思います。 この記事では、具体的な内容は書かないので詳細は動画を御覧ください。 早速、各セッションのまとめをざっくり記載していこうと思います。 セッション React 18 Keynote React がどのように活用されているかと React Conf のセッションの紹介。Suspense や新しい API での取り組みについての説明もされています。Suspense など React 18 の機能は、Native アプリ開発 のインスピレーションを得たことについても説明されています。 React 18 for app developers React 18 の新機能の説明・アップグレードについての説明です。新機能では、自動バッチ処理・Suspense・その他新 API の仕組みについて話しています。最後にはアップデートの仕方と実際にデモを行って紹介をしています。 Streaming Server Rendering with Suspense SSR をするときに、Suspense を使うことでレンダリングが最適化される話。一般的にハイドレートが完了しないとユーザーの操作を受け付けないが、Suspense を使うことで作業分割や非同期表示を行うことができる。ユーザー操作によるハイドレートの順序の最適化についても話をしている。 The first React Working Group ワーキンググループがどんな動きをしているかを API の名前の変更の経緯などと合わせて紹介しています。併せて、useSyncExternalStore, useId, useInsertionEffect に関しての変更経緯の話もしています。 React Developer Tooling React の Developer Tools の最新更新情報とロードマップの話。hooks 部分のソースマップから変数名を解析して表示させたり、コンポーネントのプロファイルやタイムラインの解析を紹介しています。 将来的にプロファイルの改善、React Native のデバッグ改善、SSR のデバッグなどを考えていることを話していました。 React without memo 自動メモ化コンパイラの製品化を目指している話。現状は可読性とパフォーマンスがトレードオフになっています。しかし、それらをいいとこ取りする「React Forget」というプロジェクトがあります。まだ複雑なケースには対応できていないようです。 React Docs Keynote React のドキュメントの beta 版の紹介。 ( https://beta.reactjs.org/ ) Hooks のカリキュラムを求める声が多くあったことから行ったアプローチを解説しています。 Things I learnt from the new React docs 新しい React のドキュメントのわかりやすさを紹介。新しいドキュメントを表示しながら、children、 context、 hooks などがどのような挙動なのかわかりやすく解説されていることを説明しています。 Learning in the Browser ブラウザだけで React を学んだ経験の話。scrimba を利用してターミナルもシェルもコンソールもない状態ですぐに React アプリを書いた体験を話していました。また、最終的に Astro という静的サイトジェネレーターでブログを作って OSS への貢献をした話をしていました。 The ROI of Designing with React デザイナーが React を学ぶことによる ROI の話。figma や framer などで props などの概念を理解することでエンジニアとデザイナーの対話がかんたんになる。また、"ノーコード(アートボード上の component や variants)"、"デザインからコードを活用"、"React内でプロトタイプを構築"のいずれであってもエンジニアとのシームレスな連携などの利益を得ることができるという話。 Interactive Playgrounds with React 静的ページにユーザーのインタラクティブが必要な要素を追加するときは mdx がおすすめという話。React Docs beta でも利用されています。Docusaurus、 Nextra、 next/mdx、 next-mdx-remote、 mdx-bundler などを利用して使うことができる。 Re-introducing Relay Relay の紹介。GraphQL のデータを利用するアプリを構築するためのフレームワーク。コンパイルを挟むことによってクエリを最適化した状態にする。動画後半ではSuspenseとの連携などの説明を行っています。 React Native Desktop Messenger Desktop React Native Migration Electron で Messenger Desktop に拡張現実の機能を追加した際に React Native を採用した話。 WebRTC の制限に遭遇したときに "Electronのフォーク"、"WebGL"、"Qt"、"React Native" を検討したが、"労力"、"パフォーマンス"、"スキルセットのアンマッチ" から React Native を採用した。結果としてパフォーマンスと信頼性が大幅に向上した。 React Native at Microsoft Microsoft が React Native への投資を行っている理由の話。クロスプラットフォームのため、 Windows や MacOS で自然に利用することができる。React のコミュニティの力を活かしてプラットフォーム構築を考えている。 On-device Machine Learning for React Native React Native で機械学習できるフレームワーク PyTorch Live を紹介する話。カメラで撮ったキャラクターを文字で表示するデモを披露しています。 React 18 for External Store Libraries useSyncExternalStore についての話。並列描画を行う際に、外部 store の更新が描画中に行われて、整合性が取れなくなる場合があります。React 17 から 18 へ移行する前に、 use-sync-external-store というパッケージを利用して不整合が起きないようにしておきましょうという話でした。 Building accessible components with React 18 ariakit を利用してかんたんに accesible なものを作ることができる話。Suspense でローディング表示を行ったり、 useDeferredValue で古い状態を維持するデモで紹介しています。 Accessible Japanese Form Components with React 行政手続きの煩雑さと多様性を解決するための UI Kit である "SmartHR UI" の紹介。和暦があることによる手間や、エラーの表示の順序によるアクセシビリティの低下などを例に紹介されています。 UI Tools for artists Netflix Studio で利用している React ベースの Hawkins というデザインシステムの話。アーティストの制作時間を最大化するために他の作業を最小化する必要があるため、画像アップロードをコピー&ペーストでかんたんにアップロードすることができるようになったようです。 Hydrogen + React 18 Hydrogen という、 React 18 ベースで shopify のカスタムストアを構築するための framework の話。一般的な SPA と比べて初期ロードされる bundle のサイズが小さいため、高速な FCP (First Contentful Paint) を実現しているようです。まだデベロッパープレビュー版ですが、オープンソース化しているので気軽なコミュニティへの参加を募っています。 さいごに 拙いまとめで解釈が違ったりするかもしれませんが、少しでも動画を見る判断材料になればと思います。
- 投稿日:2022-03-22T02:19:25+09:00
【React hooks】 useReducer って何?
はじめに フックAPIリファレンスでuseReducerの存在は知っていましたが、実際に使ったことがなかったので、使い所を調べて使ってみた話をします。 useReducerとは useStateの代用品 役割は、useStateと大体おんなじ感じってことですかね。 現在の state を dispatch メソッドとペアにして返します(もし Redux に馴染みがあれば、これがどう動作するのかはご存じでしょう)。 useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合 複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化にもなります。 useStateまみれになったりするの嫌だなぁと思ったことあります。 それを1つのオブジェクトというかReduxのStoreのように、管理できるような感じってことですかね。 Todoアプリのサンプルを作成 Todoアプリのサンプルを作成しました。 ファイル構成はこんな感じです。 src/components/pages/Todos/index.tsx Todoの追加フォーム、リストデータの表示 src/stores/TodosStore/index.ts TodosコンポーネントからuseStoreを呼び出している。 useStateを使って、loadingとtodosを管理 Todoの追加用と削除用のメソッドを提供 画面の動きはこんな感じです。 バックエンドは用意していないので、それっぽく見せるために、Todoの追加時、削除時に、ダミーのfetchを使って、3秒間、ロードが発生するようにしています。 Todo追加時の状態変更の流れは以下の3ステップです。 loadingをtrueに更新 todosに1件追加更新 loadingをfalseに更新 頭の中のイメージ的には、ステップが1つ少ないです。 loadingをtrueに更新 todosに1件追加とloadingをfalseに更新 今回は、stateが2つなので、それほど複雑ではありませんが、useReducerを使って、ReduxのStoreのように1つのオブジェクトのように管理してみます。 useReducerでリファクタリング useStateを使っているところを、useReducerに変更 src/stores/TodosStore/index.ts -const [loading, setLoading] = useState(initialState.loading); -const [todos, setTodos] = useState(initialState.todos); +const [state, dispatch] = useReducer(reducer, initialState); reducerはこんな感じで定義しておきます。 reducer const reducer = (state: TodosStore.State, action: TodosStore.Action) => { switch (action.type) { case "fetch": return fetchAction(state); case "add": return addAction(state, action); case "del": return delAction(state, action); default: return { ...initialState }; } }; useReducerで受けったdispatchは、dispatch({ type: "fetch" });という感じで呼び出すと、reducer関数の"fetch"のcaseが実行されるイメージです。 reducerは、元のstateを各処理によって、変更を加えた新しいstateを返すように記述します。 各処理はこんな感じで定義しておきます。 fetchAction const fetchAction = (state: TodosStore.State): TodosStore.State => { return { ...state, loading: true }; }; addAction const addAction = ( state: TodosStore.State, { data: { name } }: TodosStore.AddAction ): TodosStore.State => { return { ...state, loading: false, todos: [ ...state.todos, { id: new Date().getTime(), name, }, ], }; }; delAction const delAction = ( state: TodosStore.State, { data: { id } }: TodosStore.DelAction ): TodosStore.State => { const todos = state.todos.filter((todo) => todo.id !== id); return { ...state, loading: false, todos, }; }; Todosコンポーネント側から呼び出している、Todo追加と削除の関数内で、dispatchを使ったstateの更新方法に修正します。 src/stores/TodosStore/index.ts + const dispatchAddTodo = (name: string) => { + dispatch({ type: "fetch" }); + + return dummyFetch().then(() => dispatch({ type: "add", data: { name } })); + }; + + const dispatchDelTodo = (id: number) => { + dispatch({ type: "fetch" }); + + return dummyFetch().then(() => dispatch({ type: "del", data: { id } })); + }; const store: TodosStore.Store = { state, dispatchAddTodo, dispatchDelTodo, }; return store; } 動きに変化はありませんが、頭の中のイメージ通りのステップ数になったので、レンダリング回数が減りました。 というわけで、リファクタリング完了! おわりに ベストプラクティスは分かっていませんが、自分なりにReduxを意識して、Store、Reducer、Action用にファイルを分けて、簡潔に実装できたのではないかと思います、 今回は、1ファイルに全ての機能を実装していますが、実際は、ファイルが分かれたりして、でも同じstateを参照した状態でアクションを実行したいことがあると思います。 その時は、useContextを使えば、各ファイルから同じstateを参照できるような仕組みにもできると思います。(propsで頑張って渡す方法でも...) stateに直接変更を加えるわけではなく、Store側でアクションを定義して、そちら側でstateを変更する仕組みの方が、安全性があると思いますし、何より、たくさん管理したい状態がある程、1箇所にまとまっていて、そこにアクションを追加していく方が、分かりやすいのかなと思いました。 参考文献 React hooksを基礎から理解する (useReducer編) - Qiita
- 投稿日:2022-03-22T00:44:52+09:00
JavaScript環境構築まわりの用語
Node.js Node.jsは、JavaScriptの実行環境のこと。 Node.jsはサーバーサイドのプログラミング言語だと誤解されることがあるが、 プログラミング言語であるJavaScriptを、サーバーサイドでも実行できるようにするのがNode.js。 また、現在ではフロントエンドの開発環境としても広く使われている。 詳しくは↓の記事様がわかりやすい。 npm npmは、Node.jsのパッケージ管理ツールのこと。 phpで言うところのcomposer、Pythonで言うところのpip。 npmにおけるパッケージとは、JavaScript のライブラリやフレームワークのこと。 ReactやVue.jsもnpmを利用してインストールできる。 詳しくは↓の記事がわかりやすい nvm、nodebrew、nodist... nvm、nodebrew、nodistは、いずれもNode.jsのバージョン管理ツールのこと。 バージョン管理ツールを使用することで、Node.jsのバージョンを新しくしたり、過去のバージョンに戻したり、プロジェクトごとにバージョンを切り替えたり、といったことが可能になる。 nodebrewはMac用。nodistはwindows用。nvmはMac用とWindows用(nvm for windows)。 上記3つの他にも沢山ある。 ※なお、nodistは更新が止まっており不具合が出やすいらしいので、他を使用したほうがよさそう。 そのあたりも含めて詳しくは↓の記事様がわかりやすい。 webpack webpackは、モジュールをまとめるためのツールのこと。 ここで言うモジュールとは、jsファイルやhtmlファイル、sass(css)ファイル、画像などのこと。 webpackを使うことで複雑なプロジェクトの管理・保守・分担などがしやすくなり効率が上がる。 なぜwebpackを使うのかの具体的な話は↓の記事様がわかりやすい。