React Hooks その2

前回の記事で基本的なReact Hooksについて復習しました。

今回はその続きとなります。

useReducer

useReduceruseStateと同じく状態管理を行うためのHookです。

以下のような構文になっております。

const [state, dispatch] = useReducer(reducer, initialArg, init);

stateが管理したい状態、dispatchreducerを呼び出すための関数です。実際の状態変更はreducerが行います。

stateの初期値はinitialArgとなります。

reducerは以下のような関数です。

const reducer = (state, action) => newState;

stateactionを引数に取り、新しい状態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の引数actiontypeによって処理を切り替えているのがわかります。

これを実際に使ってみると、

hogeボタンを押したらhogeの数値が、fugaボタンを押したらfugaの数値が、hogefugaボタンを押したら両方の数値がインクリメントするコンポーネントができました。

最初に述べた通りuseReduceruseStateと同様に状態管理を行うための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.typeresetの場合は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>
  )
}

superHeavyFunctionuseMemoでラップすると、

hogeを押した時だけコンソールにhogeと表示され、fugaを押した時には何も出なくなりました。

useRef

useRefcurrentプロパティに引数で渡された値を持つオブジェクトを返します。

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でした。

絶賛採用中なのでご興味持っていただいた方はぜひ。

corp.smaregi.jp

Kotlin (Ktor & Docker)でHello Worldしてみる

先日書いた通り、今年はKotlinに挑戦してみます。

その第一歩としてとりあえずHello Worldしてみるところから始めてみました。

今回はKtorを使ってみることにします。

Ktor

KtorはKotlinを開発しているJetBrains社製のWebフレームワークです。

Ktor自体もKotlinで書かれており、軽量で柔軟性に富んだ (Lightweight and Flexible) フレームワークとのことです。

JavaJVM言語のフレームワークと言えばSpring Bootが有名かと思います。

当初Spring Bootを使ってやってみようと進めていたのですが、JetBrains社製のKtorは言わばKotlinの公式フレームワークなのでこちらで再度やり直すことにしました。

Hello Worldしてみる

今回は上記2つをDocker上で動くようにしてみたいと思います。

こちらのKtor公式ドキュメントを参考に進めていきます。

Ktorのプロジェクトを作成

Ktorのプロジェクトの作り方は色々あります。

IntelliJ IDEA Ultimateを使っていればIDE上で作成することもできるようですが、まだ無課金なので今回はKtor Project Generatorを使います。

プロジェクト名やバージョン等を設定したら「Add plugins」をクリックします。

認証やルーティング等、様々なプラグインを追加できます。これらは後からでも追加できるので後回しでも良さそうです。

とりあえず今回はRoutingCORSを追加しました。

「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.net

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時間程でしょうか。フロントだけだったのでサクッと作りました。

年内にリリースできたらいいなと思っていたので無事達成できて一安心です。

年末年始の暇つぶしにぜひ遊んでみてください。あとバグがあったらこっそり教えてください。