React Hooks その2
前回の記事で基本的なReact Hooksについて復習しました。
今回はその続きとなります。
useReducer
useReducer
はuseState
と同じく状態管理を行うためのHookです。
以下のような構文になっております。
const [state, dispatch] = useReducer(reducer, initialArg, init);
state
が管理したい状態、dispatch
がreducer
を呼び出すための関数です。実際の状態変更はreducer
が行います。
state
の初期値はinitialArg
となります。
reducer
は以下のような関数です。
const reducer = (state, action) => newState;
state
とaction
を引数に取り、新しい状態newState
を返すような関数です。
実際に書いてみるとこんな感じです。
import React, { useReducer } from "react"; export const Sample = () => { const defaultValue = { hoge: 0, fuga: 0 } const reducer = (state, action) => { switch (action.type) { case 'hoge': return { ...state, hoge: state.hoge + 1 } case 'fuga': return { ...state, fuga: state.fuga + 1 } case 'hogefuga': return { hoge: state.hoge + 1, fuga: state.fuga + 1 } default: throw new Error() } } const [state, dispatch] = useReducer(reducer, defaultValue) return ( <div> <p>hoge: {state.hoge}</p> <p>fuga: {state.fuga}</p> <div> <button onClick={() => dispatch({ type: 'hoge' })}>hoge</button> <button onClick={() => dispatch({ type: 'fuga' })}>fuga</button> <button onClick={() => dispatch({ type: 'hogefuga' })}>hogefuga</button> </div> </div> ) }
reducer
の引数action
のtype
によって処理を切り替えているのがわかります。
これを実際に使ってみると、
hoge
ボタンを押したらhoge
の数値が、fuga
ボタンを押したらfuga
の数値が、hogefuga
ボタンを押したら両方の数値がインクリメントするコンポーネントができました。
最初に述べた通りuseReducer
はuseState
と同様に状態管理を行うためのHookですが、複雑な状態変更を伴う場合にはuseState
よりも適していると思います。
例えば今回のようにオブジェクト内の複数の値を更新したりする場合や、action.type
がより多い場合にも細かく条件設定することができます。
また、reducer
を切り分けることもできるので関数単体でのテストもできそうです。
ちなみに第3引数のinit
を使うことでstate
の初期値の設定を遅らせることができます。例えばprops
で渡ってきた値を初期値に設定したい場合等です。
import React, { useReducer } from "react"; export const Sample = ({ count }) => { const init = (count) => { return { count: count } } const reducer = (state, action) => { switch (action.type) { case 'increment': return { count: state.count + 1 } case 'decrement': return { count: state.count - 1 } case 'reset': return init(action.payload) default: throw new Error() } } const [state, dispatch] = useReducer(reducer, count, init) return ( <div> <p>count: {state.count}</p> <div> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'reset', payload: 0 })}>reset</button> </div> </div> ) }
こんな感じにして<Sample count={0} />
としてやると、
props
で渡ったcount
の値を元にstate
の初期値が設定され、またaction.type
がreset
の場合はaction.payload
の値で初期化されるコンポーネントになりました。
useMemo
useMemo
はパフォーマンス改善のためのHookです。関数の結果をメモ化しておき、それを返します。依存する変数の値が更新した場合のみ再度関数を実行します。
const memo = useMemo(() => { doSomething() }, [deps])
初回はdoSomething()
が実行されますが、2回目以降はdeps
の値が更新されない限り初回と同じ値を返します。
例として以下のようなコンポーネントを考えます。
import React, { useState } from "react"; export const Sample = () => { const [hoge, setHoge] = useState(0) const [fuga, setFuga] = useState(0) return ( <div> <div> <button onClick={() => setHoge(hoge + 1)}>hoge</button> <button onClick={() => setFuga(fuga + 1)}>fuga</button> </div> <Child count={hoge} /> </div> ) } export const Child = ({ count }) => { const superHeavyFunction = () => { // めっちゃ重い処理 console.log('hoge') return <p>{count} times clicked hoge.</p> } return ( superHeavyFunction() ) }
hoge
をクリックしたら子コンポーネントの文言が変わる、というものです。
これを動かしてみると、
fuga
をクリックしたときもコンソールにhoge
と出てしまう=superHeavyFunction()
が都度実行されてしまいます。
これは親コンポーネントでuseState
を使っているfuga
の値が更新されたため、子コンポーネントも再レンダリングされたからです。
けどその都度superHeavyFunction()
が実行されるとパフォーマンス的にかなり悪くなりそうです。
このような時にuseMemo
を使います。
import React, { useState, useMemo } from "react"; export const Sample = () => { const [hoge, setHoge] = useState(0) const [fuga, setFuga] = useState(0) return ( <div> <div> <button onClick={() => setHoge(hoge + 1)}>hoge</button> <button onClick={() => setFuga(fuga + 1)}>fuga</button> </div> <Child count={hoge} /> </div> ) } export const Child = ({ count }) => { const superHeavyFunction = useMemo(() => { // めっちゃ重い処理 console.log('hoge') return count }, [count]) return ( <p> { superHeavyFunction } times clicked hoge.</p> ) }
superHeavyFunction
をuseMemo
でラップすると、
hoge
を押した時だけコンソールにhoge
と表示され、fuga
を押した時には何も出なくなりました。
useRef
useRef
はcurrent
プロパティに引数で渡された値を持つオブジェクトを返します。
const ref = useRef(0); console.log(ref.current); // 0
よく使われるのはDOMへの参照です。公式にもその例が載っています。
function TextInputWithFocusButton() { const inputEl = useRef(null); const onButtonClick = () => { // `current` points to the mounted text input element inputEl.current.focus(); }; return ( <> <input ref={inputEl} type="text" /> <button onClick={onButtonClick}>Focus the input</button> </> ); }
ボタンをクリックするとinput
タグにフォーカスする、というものです。
しかし、useRef
には以下のような特徴があります。
useRef は中身が変更になってもそのことを通知しないということを覚えておいてください。.current プロパティを書き換えても再レンダーは発生しません。
これを利用して、再レンダリングの回数を減らすことができます。
import React, { useState, useRef } from "react"; export const Sample = () => { const [text, setText] = useState('') return ( <div> <input type="text" value={text} onChange={e => setText(e.target.value)} /> <p>{ text }</p> </div> ) }
上記のようなコンポーネントでは文字が入力される度に再レンダリングが走ります。
import React, { useState, useRef } from "react"; export const Sample = () => { const inputEl = useRef(null) const [text, setText] = useState('') const handleClick = () => { setText(inputEl.current.value) } return ( <div> <input ref={inputEl} type="text" /> <div> <button onClick={handleClick}>set</button> </div> <span>{ text }</span> </div> ) }
こうすることで文字を入力後にボタンを押した時のみ再レンダリングが行われるようになります。
まとめ
前回の続きとしてuseReducer
, useMemo
, useRef
についてまとめました。
Reactは便利ですが使い方を誤るとパフォーマンスに悪影響を及ぼす箇所も多いように思うのでこれらのHooksを使って最適化できるようにしたいと思います。
React Hooks
先日、Next.jsを勉強していきたいと書きましたがそれ以前にReactがちゃんと使いこなせないとそもそもNext.jsの恩恵も受けられないので復習していきます。
Reactそのものをしっかり扱ったことは今までなく、昔React NativeでiOSアプリを作ろうとしていた時に勉強してた以来です。
Reactの中でも状態管理等々に深く関わるHooksを中心に復習したいと思います。
Hooksとは
HooksはReact 16.8で追加された機能です。
Reactのコンポーネントにはクラスコンポーネントと関数コンポーネントの2種類があります。
前者はこんな感じ。
import React from "react"; class Sample extends React.Component { constructor(props) { super(props) this.state = { count: 0 } } increment = () => { this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <span>Count: {this.state.count}</span> <button onClick={this.increment}>Click</button> </div> ) } }
constructor
の中でコンポーネントのstate
に初期値を設定しています。変更する場合はsetState
を使います。
一方関数コンポーネントはこんな感じ。
export const Sample = (props) => { return ( <div> <span>Sample</span> </div> ) }
しかしClassと異なりconstructor
内でstate
の設定ができません。
なので関数コンポーネントにできることは限られていました。
そこで関数コンポーネントでもstate
等を使えるようにと登場したのがHooksです。
先程のクラスコンポーネントの例を関数コンポーネントで書くとこんな感じ。
import React, { useState } from "react"; export const Sample = (props) => { const [count, setCount] = useState(0) const increment = () => { setCount(count + 1) } return ( <div> <span>Count: {count}</span> <button onClick={increment}>Click</button> </div> ) }
useState
というHookを使ってcount
とそれを更新するためのsetCount
を定義しています。初期値はuseState
の引数に入れた0です。
関数コンポーネントなので最終的にreturn
すればOK、ということで直感的でシンプルな感じがします。
こうしてHooksが導入されたことにより、現在では関数コンポーネントが主流となっています。
主なHooks
ここからは主に使われるHooksを見ていきます。
useState
先程挙げたものです。一番基本的なHookでコンポーネントに状態(state)を持たせるために使います。
useEffect
コンポーネントのライフサイクルに伴う副作用を管理するためのHookです。
コンポーネントがマウントされた直後に呼び出されるcomponentDidMountやコンポーネントが更新された直後に呼び出されるcomponentDidUpdate等に該当します。
これを使うとレンダリング後に副作用として関数を実行することができます。
例を挙げるとこんな感じ。
import React, { useState, useEffect } from "react"; export const Sample = (props) => { const [count, setCount] = useState(0) const increment = () => { setCount(count + 1) } useEffect(() => { document.title = `Clicked ${count} times.` }, [count]) return ( <div> <span>Count: {count}</span> <button onClick={increment}>Click</button> </div> ) }
先程のuseState
の例に追加しました。こうするとボタンをクリックする度にタイトルが変わります。
useEffect
の構文は以下の通りです。
useEffect(() => { /* 第1引数: 副作用として実行したい関数 */ }, [ /* 第2引数: 副作用のトリガーとなる変数の配列 */ ])
第1引数にはレンダリング後に実行したい関数を、第2引数にはその副作用のトリガーとなる変数の配列を取ります。
第2引数に渡した配列の中のどれかの値が更新される度に第1引数の関数が実行されます。
注意点としてuseEffect
は使い方を誤ると無限ループに陥ります。
import React, { useState, useEffect } from "react"; export const Sample = (props) => { const [count, setCount] = useState(0) const increment = () => { setCount(count + 1) } useEffect(() => { increment() }, [count]) return ( <div> <span>Count: {count}</span> <button onClick={increment}>Click</button> </div> ) }
かなり極端な例ですが、↑のようにすると
- 初期レンダリング後に
useEffect
内でincrement
が呼ばれる count
が更新されたので再レンダリング時に再度useEffect
が実行useEffect
内でincrement
が呼ばれるcount
が更新されたので(以下略)
となります。初期レンダリング時以外に実行したくない場合は第2引数に空配列を渡せばOKです。
useContext
親コンポーネントから子コンポーネントに値を渡したい場合は通常props
を使います。
しかし、場合によっては何階層にも渡ってprops
をバケツリレーする必要が出てきます。かなり手間ですし、記述量も増えます。
useContext
を使えば直接の親子関係に無いコンポーネントでも共通して使えるグローバルな値にアクセスすることができます。
import React, { useContext } from "react"; const titleContext = React.createContext() export const Parent = () => { return ( <titleContext.Provider value={'hoge'}> <Child /> </titleContext.Provider> ) } export const Child = () => { return ( <GrandChild /> ) } export const GrandChild = () => { return ( <GreatGrandChild /> ) } export const GreatGrandChild = () => { const title = useContext(titleContext) return ( <span>title: { title }</span> ) }
titleContext
の現在の値をどこからでも取得できます。
注意点としてはContext
の値が変わった場合、useContext
を使用してそのContext
の値を使用しているコンポーネントは全て再レンダリングされてしまいます。
まとめ
基本的なReact Hooksについて簡単にまとめてみました。
より詳細に知りたい方はこちらの公式ドキュメントを見てください。
次回は他のHooksについても書いていきたいと思います。
スマレジの良いとこまとめてみた
年始の記事で書いた通り、スマレジに入社して1年が経ちました。
ので、今回は技術的なことから少し離れて、改めてスマレジに入社して良かったと思う点を挙げてみたいと思います。
誰に頼まれたわけでもないですが勝手にPR記事です。
先に断っておくと、以下はチームの状況等によっても変わってくると思うのでその点だけご留意ください。
やりたいことが(ある程度)自由にできる
僕のいるチームはメンバークラスであっても各自の裁量が結構大きいと思います。
どういうスケジュールでタスクを進めるのか、どのバージョンでどの機能を実装するのか等、担当するメンバーが決められます。
「ある程度」と書いたのは他のチームと連携して進めるような一部のタスクについてはスケジュールの擦り合わせがあるからです。
基本的に仕様等については上からこうしろと言われることは無いです。
これは前の会社と一番異なるところであるような気がします。
前の会社では企画や営業から機能の要望が降りてきて、いつまでに実装してほしいというのもほぼ決まっていました。
もちろん現在も他部署からの要望に対応することはありますが、進め方は割と自由です。
ただし、自由とは言え何をしてもいいわけではもちろん無いです。むしろ自由だからこそ成果は求められます。
仕様についても関わるメンバーの了解が得られないような仕様はNGです。
どんな仕様にするか、どういう進め方をするか等、事前に共有・相談した上で適宜進捗報告ができるのが大前提です。
逆に言えばそこがしっかりできていれば他のメンバーや上司等から色々言われることは特にありません。
メンバーとの議論が活発
↑とも関連しますが、今のチームメンバーは皆何か困ったことがあればすぐslackで相談を投げますし、他の人の相談にも答えてくれます。
僕も設計や実装について相談を投げるんですが、誰かは必ず答えてくれるだろうという信頼感があります。
またメンバー間で意見が割れることもありますが、その際の議論は面白いなと思いながらやってます。
メンバーに共通しているのは何か投げられた際にそれに対して何かしら自分の意見を持っている、という点です。
最近チーム内でペアプロをしているのですが、その最中にも「ここはこうした方が良いと思うんですけど何でこうしてるんですか?」みたいなやり取りがしょっちゅうあります。
そうした質問に対して「これは◯◯だから△△で〜」みたいに根拠を元に回答されるので質問した側も納得できる。
双方ともに自分の意見に対して根拠を持っているので「より良い設計・実装にするには」という着地点に向けて建設的な議論ができる環境があります。
これってすごくありがたいなと最近感じています。
また、雑談の中で最近気になっている技術等について情報共有を行うこともよくあります。
そうしたところからまた新たな興味が湧いたりモチベーションにも繋がるので良い環境だなぁと思います。
コミュニケーションがオープン
僕のチームは基本全員フルリモートなので他のチームの人と直接お会いする機会はかなり少ないです。
僕自身の話で言うと入社直後の1週間出社したのと、去年のGW前にチームの飲み会をやった日、タスクの都合で出社しないといけなかった日、昨年末の忘年会の日ぐらいでこれまでに10日ほどしか出社していません。
ですが先日の忘年会の際に初めてお会いした方にも気さくに話しかけていただいた等、コミュニケーションに関してフランクでオープンな方が多い印象です。
そうした環境なので仕事面においてもわからないことがあれば他部署の方にでも気軽に質問できるし、コミュニケーションコストはかなり低いです。
なのでフルリモートでも困ったことはほぼ無いです。仕事がしやすい環境と言えます。
slack上のやり取りも原則パブリックチャンネルなので欲しい情報は大体オープンですし、困ったら誰か助けてくれます。良い人ばかりです。
まとめ
というわけでスマレジの良いとこPRでした。
絶賛採用中なのでご興味持っていただいた方はぜひ。
Kotlin (Ktor & Docker)でHello Worldしてみる
先日書いた通り、今年はKotlinに挑戦してみます。
その第一歩としてとりあえずHello Worldしてみるところから始めてみました。
今回はKtorを使ってみることにします。
Ktor
KtorはKotlinを開発しているJetBrains社製のWebフレームワークです。
Ktor自体もKotlinで書かれており、軽量で柔軟性に富んだ (Lightweight and Flexible) フレームワークとのことです。
JavaやJVM言語のフレームワークと言えばSpring Bootが有名かと思います。
当初Spring Bootを使ってやってみようと進めていたのですが、JetBrains社製のKtorは言わばKotlinの公式フレームワークなのでこちらで再度やり直すことにしました。
Hello Worldしてみる
今回は上記2つをDocker上で動くようにしてみたいと思います。
こちらのKtor公式ドキュメントを参考に進めていきます。
Ktorのプロジェクトを作成
Ktorのプロジェクトの作り方は色々あります。
IntelliJ IDEA Ultimateを使っていればIDE上で作成することもできるようですが、まだ無課金なので今回はKtor Project Generatorを使います。
プロジェクト名やバージョン等を設定したら「Add plugins」をクリックします。
認証やルーティング等、様々なプラグインを追加できます。これらは後からでも追加できるので後回しでも良さそうです。
とりあえず今回はRouting
とCORS
を追加しました。
「Generate project」をクリックするとzipファイルのダウンロードが始まります。解凍するとプロジェクトに必要なファイルが一式揃っています。
Dockerの設定
Dockerの設定を行っていきます。今回はDBも用意したいのでDocker composeを使います。
まずはDockerfile
をプロジェクトルート直下に作成します。
FROM eclipse-temurin:17 EXPOSE 8080:8080 RUN mkdir /app COPY ./build/libs/*-all.jar /app/blog.jar ENTRYPOINT ["java", "-jar", "/app/blog.jar"]
上記公式ではopenjdk
のイメージを使用していますが、OpenJDKのDockerイメージは非推奨となり今後更新されないため、eclipse-temurin
を代わりに使用します。
参考: Docker HubのOpenJDKイメージの利用を更新するためのアドバイス - 赤帽エンジニアブログ
続いてdocker-compose.yml
を作成します。
version: "3" services: app: build: context: . dockerfile: ./Dockerfile ports: - 8080:8080
Dockerの設定は取り急ぎ以上です。
ビルド
先程のDockerfile
から分かる通り、ビルド済みのjarファイルをローカルからコンテナ上の/app/hoge.jar
にコピーしています。
なのでDockerイメージのビルド前にKotlinのビルドが必要です。
./gradlew buildFatJar
完了したら今度はDockerイメージをビルドします。
docker compose build
そして最後にコンテナを立ち上げます。
docker compose up -d
無事起動したらhttp://localhost:8080
にアクセスして以下のように表示されたらOKです。
が、これだと都度Kotlinのビルド⇒Dockerイメージのビルド⇒コンテナ起動しないと変更が反映されないので修正していきます。
Dockerfileを修正
以下のように修正します。
FROM eclipse-temurin:17 EXPOSE 8080:8080 RUN mkdir /app WORKDIR /app/
docker-compose.yml修正
以下のように修正します。
version: "3" services: app: volumes: - .:/app/ build: context: . dockerfile: ./Dockerfile ports: - 8080:8080 tty: true
tty: true
を追加してコンテナの起動状態を継続させています。
Auto-reload
KtorにはAuto-reloadという機能があり、変更を検知して再コンパイルしてくれます。
これを有効にするにはdevelopment mode
をONにして監視対象のパスを追加します。
ktor { deployment { port = 8080 watch = [ classes ] // 追加 } application { modules = [ com.example.ApplicationKt.module ] } development = true // 追加 }
ここまで設定したら再度イメージをビルドして立ち上げます。
docker compose build --no-cache
docker compose up -d
app
コンテナ内でバックグラウンドでビルドします。
docker compose exec app ./gradlew -t build -x test
以下のように表示されたらOK。
BUILD SUCCESSFUL in 2m 16s 11 actionable tasks: 11 up-to-date Waiting for changes to input files... (ctrl-d to exit) <-------------> 0% WAITING
別タスクでアプリケーションを起動します。
docker compose exec app ./gradlew run
しばらく待つと以下のように表示されます。
2023-01-12 13:01:17.457 [main] INFO Application - Application started in 2.62 seconds. 2023-01-12 13:01:18.126 [main] INFO Application - Responding at http://0.0.0.0:8080 <===========--> 85% EXECUTING [51s] > :run
この状態でコードを修正すると再度./gradlew -t build -x test
が走り、変更が反映されます。
まとめ
取り急ぎ開発できる体制は整いました。
が、まだわからないことだらけです。./gradlew -t build -x test
すると毎回zipファイルがダウンロードされるし、その他不要なタスクも大量にありそうです。
そのあたりの設定も少しずつ見ていきたいと思います。
2023年やってみたいこと
あけましておめでとうございます。今年もよろしくお願いいたします。
今日から仕事初めでした。ちょうど1年前が初出社の日でしたので感慨深いです。
新年なので今年チャレンジしてみようと思っていることをつらつら書いてみます。
Kotlinで何か作る
エンジニアになってからバックエンドはずっとPHPで書いてきましたが、何か他の言語もチャレンジしてみたいなと改めて思うようになりました。
未経験からエンジニアになる前にポートフォリオを作る際にはRuby On Railsを使ったり、趣味の範囲でNode.js (Express) を使ってみたりしたことはありますが、
せっかくなので全く触ったことが無い言語にしたいなと。
で、何がいいかなと色々調べたり比較検討してみた結果Kotlinをやってみようと思います。
Kotlinを選んだ理由としては、
- 静的型付け言語であること
- オブジェクト指向言語であること
が挙げられます。
特に後者はこれまで学んだクリーンアーキテクチャの概念を他の言語でも使えるかどうか試すという点で必要と考えました。
また、静的型付け言語なので型が間違ってたらそもそも動かないという点も◎です。
ならJavaでいいじゃんと言われそうですがやっぱりモダンな言語にしたかった
最初Goにしようかなと思ったのですが、例外処理の辺りの考え方がやや掴みにくく、学習コストが高そうだなと感じてしまいました。
あとはオブジェクト指向という観点からも外れるのかなと。
というわけでKotlinを始めようと思うのですが、IntelliJ IDEAを購入するかどうか絶賛迷い中です。
そもそもKotlinの開発元がJetBrainsなので導入したほうが絶対開発効率は上がるんですがもう少し検討します。
Next.jsで何か作る
先程はバックエンドの話でしたが、フロントエンドもNext.jsに挑戦しようと思います。
最近チーム内でもフロントエンドの話をすることがよくあるんですが、やっぱり今主流なのはNext.jsかなぁと思います。
少し前までは「日本ではVue、世界的にはReactが主流」という感じでしたが、最近では日本でもReact、特にNext.jsが勢力を伸ばしているのではないかと。何となくですが。
現状仕事で使っているのはVue (Nuxt) ですが、やはりReact及びNext.jsも押さえておきたいなと思ったのでやってみます。
昔React Nativeでスマホアプリを作ろうとしてReact周りを少し触ってみたことはあるんですがそれ以来になります。
Hooksとかもうほとんど忘れてしまっているのでまずは復習から。
こちらもTypeScriptを併せて導入して型をガチガチに固めてやっていこうと思います。
ブログの移転
上記2つをやる上で手っ取り早く着手できそうなのがこのブログの移転です。
つまりバックエンドをKotlin、フロントエンドをNext.jsで1から全て書き、どこかにデプロイしてそれ以降はそのブログを使用する、というのを今年やりたいと思います。
作業自体はゆっくり進めていければと考えているのでいつまでにというのは特に決めていませんが少なくとも年内に新しいブログで稼働できればと思います。
とりあえずは最小限の構成・機能で実装しようと考えています。またその模様も記事にできればと思います。
2022年のまとめ
昨日で2022年の仕事納めをしました。
最終週にまさかの胃腸炎に罹り、火曜水曜と2日も休んでしまうトラブルはありましたが、無事〆ることができて良かったです。
というわけで今回は2022年の振り返りです。
転職して1年
今年の1月1日付でスマレジに入社したのでちょうど1年が経ちました。
総じて言うと転職して良かったの一言に尽きます。
- 今まで触れたことが無かったフレームワークを使えるようになった(Laravel, Nuxt)
- 設計面で学ぶことが多かった(クリーンアーキテクチャ, ドメイン駆動設計, その他デザインパターン)
- 目の前のタスクだけでなく、プロジェクトやチームのことを考えるようになった
- 困ったらすぐ相談できるチームメンバーに恵まれた
等々。。
特に大きいのは設計面でしょうか。恥ずかしながら前職のときには聞いたこともなかったクリーンアーキテクチャやドメイン駆動設計という手法を学べたのは自分にとってかなり大きな変化でした。
まだまだ完璧には程遠いので引き続き勉強していかないとなという思いです。
またチームメンバーにも適宜相談に乗っていただいたり、あるいはレビューで指摘をいただいたりと毎度毎度非常に助けられています。
かなり重いタスクが重なってキツい時期もありましたが、なんとか乗り越えられたのもチームとしての自信に繋がったような気がします。
あ、あと基本フルリモートなのもかなり助かってます。これも前職からの大きな変化の一つ。
個人開発
今年は個人開発にもチャレンジしました。先日リリースしたnumnamです。
実はその前にも1つ別のものを作ってたんですが色々あって閉じちゃいました。
おかげさまで社内でも遊んでくれた方がチラホラいてありがたい限りです。開発以外の部署の方も遊んでくれて嬉しい...!
難易度上げたバージョンも待ってますとのお声をいただいたので追加してみようかなと思ってます。
他にも何か作りたいなと思うので現在絶賛アイデアこねくり回し中です。
実際やってみると自分で全部決められるのも楽しいし、普段業務で使ってない技術を試すいい機会にもなるし、あとそんなにハードルが高くないという気づきも得ました。
来年も色々楽しみながら作れたらいいなと思います。
その他
プライベートな面でも今年は色々動いた1年だったなと思います。
新しく始めたことで言うとサウナやコーヒー、ウイスキーをボトルで買うようになったのも今年からだったような。
あとはコロナ禍以降あまり遠出していなかったんですが今年は久しぶりに旅行にも行ったり、ここ数年の中では一番フットワーク軽く色んな所に行けました。
まだまだこの先どうなるかはわからない状況ですが、来年も何か楽しいこと始められたらいいなと思う次第です。
まとめ
以上、2022年のまとめでした。
今年1年関わった皆様ありがとうございました。そして2023年もよろしくお願いいたします。
それでは良いお年を。
Svelteを使ってブラウザゲームを作りました
タイトルの通り、ここのところ記事にしていたSvelteを使ってブラウザ上で動く簡単なゲームを作りました。
ので、今日はそれについて書いていきます。
どんなゲーム?
こちらです。
Numnam(ナムナム)という名前です。名前は響きだけで決めました。
5桁の数字を当てるだけのシンプルなゲームです。
正解の数字は以下のような縛りがあります。
- 一の位は0または5
- 全部違う数字
最初は「全部違う数字」だけにしてたんですが、全然当たらないのでもうちょっと絞りました。
1問につき3回まで回答することができ、回答するごとにヒントがもらえます。
それぞれの数字については、
- 場所も数字も合っていたら数字の色はそのまま
- 数字は合っているけど場所が違ったら数字の色は赤
- 含まれない数字だったら黒
また入力した5桁の数字については、
- 正解より大きければピンクの枠
- 正解より小さければ青の枠
になります。これと上述した縛りを元に数字を当ててください。
「WORDLEのパクリ」はNGワードです。インスパイアと言ってください。
ちなみに3回回答しても当たらなかった場合は、
「まだ本気出してないだけでしょ?」とめっちゃ煽られます。
やってみると程良い難易度でそれなりに頭の体操になるかと思います。
技術面
ここからは使った技術について簡単にまとめます。
今回サーバーサイドは何も書いていません。フロントエンドのみの実装になります。
これぐらいのものであればわざわざバックエンド書かなくてもいいかなと思ったのでフロントのみで完結させました。サーバー用意するのも面倒だし
そもそものコンセプト的に「ログイン不要でサッと遊べるもの」だったのでサーバーサイドが必要な複雑なものを作る気がありませんでした。
フロントエンドは冒頭に書いたとおりSvelteです。先日めでたく正式リリースされたSveltekitを使いました。
そこにTypeScriptも合わせて型をがっちり定めて書きました。が、正直改善の余地ありまくりなのでぼちぼちリファクタリングしていきたいと思います。
あとテストも書けてないのでそこも課題です。
デプロイ先は色々検討しましたがCloudflare Pagesを使ってます。
@sveltejs/adapter-staticを使って静的サイトとしてビルドされるように設定しています。
Githubと連携すると指定したブランチにプッシュされるたびに自動でビルドしてデプロイしてくれるのでめっちゃ便利です。
ビルド〜デプロイにかかる時間は約1分です。
注意点として、Cloudflare PagesではデフォルトのNode.jsのバージョンが12.18.0
です。なぜ。
しかも使えるバージョンも17系までなので18系を使っている方はご注意ください。
僕はnodenvで17系を指定して、.node-version
もリポジトリに含めています。
その他、Cloudflare Pagesの環境変数でNODE_VERSION
を指定することでも可能なようです。
詳しくはこちらをご覧ください。
まとめ
というわけで自作ゲームの紹介でした。
今月頭から作り始めて実際にかかった時間でいうと多分5〜60時間程でしょうか。フロントだけだったのでサクッと作りました。
年内にリリースできたらいいなと思っていたので無事達成できて一安心です。
年末年始の暇つぶしにぜひ遊んでみてください。あとバグがあったらこっそり教えてください。