もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

リモートワーク環境の改善のためにFLEXISPOTの電動昇降デスクを買った

前段

しばらくリモートワークが続きそうな雰囲気なので、仕事の効率を上げるためにある程度の投資は必要かなと思い色々買い揃えることにしました。新コロ騒ぎ以前はリビングで仕事してたんですが、その前提条件は "子供が保育園に行っていること" であり、緊急事態宣言下で保育園が使えなくなって常時子供が家にいる状態ではさすがに同じクオリティでアウトプットをだせないかなーと。

幸い住んでいるところは1軒家で、将来は子供部屋にする予定の部屋が空いていたので、そこを作業部屋に作り変えるプランにして、まずは机を買おうということで色々検討しました。要件としてはスタンディングでの作業ができるのが必須で後は見た目がごちゃごちゃしていないくらいです。

買ったもの

公式はこちら https://flexispot.jp/flexispot-e6.html

前職で一緒だったwadapさんのnoteで紹介されてたものが一番イメージに近かったので、FLEXISPOTのE6というのにしました。信頼できる人がおすすめしてるという安心感もあります。

note.com

pros

  • イメージ通りのシンプルな見た目でよい
  • 組み立てが簡単
  • 操作が簡単。高さは任意の位置を登録できて(3パターン)、登録すればワンタッチで高さを切り替えることができる

cons

  • かなり重い。配置予定の部屋は玄関近くの部屋で搬入は苦労しなかったのですが、もし2階に置くとかだと僕の体格ではかなりきつかったと思います。
  • あまり本品のレビューではないですが、公式サイトの「特典新規会員登録で「5%offクーポン」プレゼント」につられて公式で注文したのですが、注文から発送まで2週間近くかかりました。その経緯も注文から2週間たってもステータスが変わらなかったので、いつごろ発送されるかくらい教えてくれない?と問い合わせてみたら、なぜか問い合わせの返事ではなく発送のお知らせが来てそのあとすぐにモノが届きました。

おしまい

コロナ禍を機に自宅の作業環境を整えようかなーという人がいたらおすすめの机です。

Material DesignのDark themeの章読んだめも

dark-themeのページを流し読みしたメモ。

material.io

めも

  • 完全な黒ではなくdark gray(おすすめは#121212らしい)を使用する。
  • UI Componentにはaccent colorを使う。
  • 暗い色を基調とし、明るいピクセルを減らす(OLEDスクリーンを使ったデバイスなどにおいては端末のバッテリー消費が抑えられる)
  • 背景色とテキストの色のコントラストは指定のガイドラインを満たすようにする。ガイドラインは↓

www.w3.org

  • dark themeはアプリ内のコントロールでdefaultのthemeと切り替えることができる。実装のパターンは固定ではない
  • elevationが高いほど明るめのdark grayをsurfaceに使う。明るさはelevationに応じた半透明のoverlay(0 〜 16%)を被せて調整する。
  • surfaceにaccent colorを使った場合はshadowでelevationを表現する。

2019年振り返り

最近は職場以外の人とあまり会ってないので生存報告がてら振り返りなど

仕事の開発

  • 淡々とスタートアップぽい開発してB2Bサービス作ってます。構成もバックエンドはRoR、フロントエンドはReact/TypeScriptでスタートアップっぽい。ReactはFunctionComponent + Hooksの構成に書き換えていてまあまあ見通しのいいコードになったかなと感じてます。
  • バックエンドもフロントエンドも触ってますが、フロントについてはJoinしてからアーキテクチャの整理をして、こつこつリファクタをした結果、Sentryに飛んでくるUnhandledなエラーがほぼ無くなったので、そのへんは成果を出せたかなと思ってます。テストも無い状態から結構頑張った。

個人の開発

  • Firebaseを使ったサービスを作りました。銀の弾丸というわけじゃないのですが、せっかくなので仕事で使えそうな機会があったら取り入れてみたいお気持ちです。
  • 3年前にリリースした百人一首のアプリが20万DLに届いたので純粋にうれしいですね。
  • 今年はWeb成分多めだったので来年はAndroidのアプリをなにか作りたいということで、復習がてらGoogleのCodelabをもくもくとやってました。

その他

  • 来年で現職について3年になるので一旦どこかで振り返りしようかなと思ってます。LAPRAS経由でたまにスカウトが来ますが、転職志望は直近でどうのこうのというのはないけれどそれでもよければという体で話を聞いて、市場の雰囲気や自分のスキルセットの市場価値みたいなところを意識するようにしてます。
  • 去年から引き続き子供優先の生活をしているので、勉強する時間や個人サービスを作る時間がほぼ深夜になるのがまあまあ厳しいですね。先達の方々はどう折り合いつけてるのか。。。

React(Gatsby)+ FirebaseでWebサービス作った

作ったやつ

川柳投稿するだけのサービス

senryu.app

目的

毎年なにかしら新しい言語とか技術を覚えてアウトプットするというのを続けていて、今年はあまり使ったことがなかったFirebaseメインでなにか作ってみるかということで、認証+コンテンツのCRUDがあるスタンダードなWebサービスを作りました。今回はFirebaseを使うというのが目的なので、使ってもらえそうかどうかというのはあまり考えずに作ってます。よく新しい技術覚えるときにTODOリスト作ったりしますが、簡単なサンプル作っただけだと出来た気になるだけであんまりよくないと思っているので、ちゃんとそれっぽいサービスを作るようにしてます。

技術的な話

Firebaseで○○を作ってみた系の記事はたくさんあるので、あまり言及することもないのですが、設計的なところだけ少し。Presentation層はできるだけFirebaseの存在を意識しないように作っています(ただFirebaseのコンテキストにおいてはこの方針がベターかはわからないです。FirebaseUIを使っている関係上、認証部分にはFirebaseが漏れていたりするので)。具体的なコードのイメージは以下のとおりです。

ドメイン層を作ってリポジトリのIFを配置

src/domain/repositories/index.ts

export interface SenryuRepository {
  findById(id: SenryuId): Promise<Senryu>;
  findByUserPerPage(
    userId: UserId,
    pageNo: number,
    base?: Senryu
  ): Promise<Page<Senryu>>;
  findAllPerPage(pageNo: number, base?: Senryu): Promise<Page<Senryu>>;
  add(senryu: SenryuDraft): Promise<SenryuId>;
  delete(senryuId: SenryuId): Promise<void>;
}

インフラ層でリポジトリを実装

src/infrastructure/repositories/SenryuRepositoryData.ts

export class SenryuRepositoryData implements SenryuRepository {
  async findById(id: SenryuId) {
    try {
      const snap = await senryuCollection()
        .doc(id)
        .get();
      const data = snap.data();
      if (data !== undefined) {
        return dataToModel(id, data);
      } else {
        throw reasonToAppError({ code: 'not-found' }, '川柳');
      }
    } catch (reason) {
      throw reasonToAppError(reason, '川柳');
    }
  }

  // 中略
}

ReactのContextを使ってRepositoryの実装をDIできるようにする

src/contexts/DiContainerContextProvider.tsx

export const defalutValue = {
  senryuRepository: new SenryuRepositoryData(), // ここで実体を作成
};

export const DiContainerContext = React.createContext<{
  senryuRepository: SenryuRepository;
}>(defalutValue);

const DiContainerContextProvider: React.FC<{}> = ({ children }) => (
  <DiContainerContext.Provider value={defalutValue}>
    {children}
  </DiContainerContext.Provider>
);

src/hooks/useDiContainer.ts

import { useContext } from 'react';
import { DiContainerContext } from '@src/contexts/DiContainerContextProvider';

export const useDiContainer = () => useContext(DiContainerContext);

custom hookでContextからRepositoryをDIする

src/hooks/useSenryuList.ts

type Deps = {
  senryuRepository: SenryuRepository;
};

export const useSenryuList = (
  { senryuRepository }: Deps = useDiContainer()
): Return => {
  // 中略
}

hookのテストはRepositoryのモックで差し替えることができる

const genMockSenryuRepository = () =>
  jest.genMockFromModule<SenryuRepository>('@src/domain/repositories');

const senryuRepository = genMockSenryuRepository();
senryuRepository.findAllPerPage = jest.fn(pageNo => {
   // テスト用のデータをPromiseで返す
});

const { result, waitForNextUpdate } = renderHook(() =>
    useSenryuList({ senryuRepository })
);

という感じでFirebaseはRepositoryの実装に封印する形で作っています。これは普段RESTなAPIを使うときにやっている実装で、そこをFirebaseに置き換えた体ですね。とはいえ先にもコメントしたとおり、Firebase使う場合はこの形がよいのかはまだわかってないので、Firebaseと一蓮托生する形で設計した方がいいかもわからんです。

おしまい

  • Gatsbyである必要全く無いのですが、最近遊んでたというだけの理由で使ってます。
  • 大きな残件としてFirebaseにからむところのテストの書き方を調べきれてないので、そのあたりまでカバーして、デザインとかSEOまわりを見直したらいったんこれの開発は終わりにする予定
  • ドメインを初めてGoogle Domainsで取ったのですが、普段使ってるお名前.comよりだいぶ使いやすかったです。
  • いつも通りコードはポートフォリオ代わりに公開してます。

静岡の用宗というところで開発合宿したら最の高だった

定期的に個人でもくもく開発するのが好きなメンバーで開発合宿に行ってるのですが、今回使ったところはなかなか良い体験でしたので紹介など。

場所

静岡は用宗という港町にある「日本色」という一棟貸しの宿を使いました。部屋は定員7人の撫子を借りてます。小さいチームの開発合宿としては丁度いいサイズですね。

nihoniro.jp

部屋はここ nihoniro.jp

開発合宿は何回か開催しているのですが、選ぶポイントは以下の条件を満たせるかで選んでいます。

  • 1軒家を借りられる。周りに気を使わなくて良いのでおすすめ。
  • 2泊3日のスケジュールを抑えられる。経験上1泊だと足りない。
  • wifiが使える。必須
  • 食事に不自由しなさそう。開発外の楽しみですね。

今回は全部合致したのでここを選びました。料金は定員の7名Maxで借りれば一人あたり1泊1万ちょいというところです。今回は7人揃わず6人だったのでちょっと割高。

おすすめポイント

東京から近い

  • 東京駅から新幹線で1時間ちょいでいける距離感です。僕は途中の熱海に寄りたかったので鈍行で海を見ながらのんびり行きました。

食べ物が美味しい

  • 港町なので基本的に魚介類が安くてすごい美味しいです。名物は生しらすで漁が行われていれば新鮮なやつをいただけます。↓は実際に食したやつ。

f:id:Rei19:20190908222757j:plain
海鮮丼

f:id:Rei19:20190908222911j:plain
生しらす

温泉と食事するポイントがまとまっている

  • 宿の近くに用宗みなと温泉という温泉と食事処が複数入っている用宗みなと横丁という施設があります。この温泉は中にも食事処とビアバーが揃っているので戦闘力が高いです。なお、この2つの施設の運営は宿の管理会社と同じなので、宿を予約すると温泉のチケットが付いてきて無料で入浴できます。

minato-onsen.jp

宿のサービスが良い

  • 駅の送迎だけでなく↑の温泉や食事処までの移動も気軽に受けてくれました。移動はトゥクトゥクを使うのですが、夏は風がすごい気持ちよくて良い体験でした。コンビニで買い出ししてから戻りたいですみたいなオーダーも気軽に受けてくれて助かりました。

f:id:Rei19:20190908224330j:plain
トゥクトゥク

おしまい

  • 人もあまり多くなく昔ながらの漁師町という印象で静かで作業に集中できてよかったです。宿も温泉もまだ新しい施設で快適に過ごせました。当日はカンカンに晴れていて富士山も望めて爽快でした。(最終日は台風15号から逃げるように帰りましたが)
  • 一応ちゃんと開発してたぞってことで、githubリポジトリなど(Firebaseちゃんと使ったことなかったのでFirebase使ったSPA作ってました。未完なので継続して開発)

GitHub - rei-m/web-senryu: 川柳投稿するサイト(を作っている)

Material-uiのstyled-components-apiを使う

今までstyled-componentsmaterial-uiそれぞれ入れて使ってたのですが、material-uiにstyled-componentっぽいAPIが生えてたのに気づいたので使ってみたメモ(キャッチアップできてないだけですね。。。)。@material-ui/coreのバージョンは4.1.0です。

リファレンス

material-ui.com

使ってみる

基本

公式に載ってるコードだとこんな感じですね。本家の方はTemplateStringsArrayでスタイル定義しますが、こちらはデフォルトだとCSSPropertiesのObjectを食わすのでよりJSっぽいスタイルです。

import React from 'react';
import { styled } from '@material-ui/styles';
import Button from '@material-ui/core/Button';

const MyButton = styled(Button)({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  border: 0,
  borderRadius: 3,
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  color: 'white',
  height: 48,
  padding: '0 30px',
});

MUIのComponentではなく普通のHTMLのタグを指定したいときはstringを指定します。

import React from 'react';
import { styled } from '@material-ui/styles';

const MyButton = styled('button')({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  border: 0,
  borderRadius: 3,
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  color: 'white',
  height: 48,
  padding: '0 30px',
});

Propsに応じてスタイルを変える

Propsに応じてスタイルを変えたい時はこんな感じです。Componentは必要なPropsを渡せる形にして、スタイルは値ではなくpropsを受け取って値を返す関数を設定します。

import React from 'react';
import { styled } from '@material-ui/styles';

type MyButtonProps = {
  size: 'small' | 'normal';
};

// lintで怒られるの不本意なんですけど・・・
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const MyButton = styled(({ size, ...other }: MyButtonProps) => <button {...other} />)({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  border: 0,
  borderRadius: 3,
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  color: 'white',
  height: 48,
  padding: '0 30px',
  fontSize: ({ size }: MyButtonProps) => (size === 'small' ? '12px' : '16px'),
});

自分で定義したComponentで使う

classNameをPropsで渡してRootのComponentに渡します。

MyComponent.tsx

import React from 'react';
import { styled } from '@material-ui/styles';

type MyComponentProps = {
  className?: string;
};

const MyComponent = ({ className }: MyComponentProps) => (
  <div className={className}>こんぽーねんと</div>
);

export default MyComponent;

CustomComponent.tsx

import React from 'react';
import { styled } from '@material-ui/styles';
import MyComponent from './MyComponent';

const StyledMyComponent = styled(MyComponent)({
 background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
});

const CustomComponent = () => (
  <div><StyledMyComponent /></div>
);

export default CustomComponent;

おしまい

  • 少し試しただけですが最初から作るならMaterialUIに寄せてもいいかなと思いました。

React(Gatsby)+ Firebaseでサーバレス入門した

個人開発でFirebase使ってなんか作ろうかなということで、素振りで作ったものを公開してみました。Reduxのチュートリアルで作るTodoアプリをStoreをFirebaseにした体で作り変えたやつになります。Firebaseは古の時代に触ったときは単なるPub/SubできるDBだったのにいろいろ出来るようになっててビビりますね。触る前に公式のドキュメントをざっと読んでcodelabを試したくらいの事前知識で作りました。

作ったもの

サイトはこちら。単に最近遊んでるという理由だけでGatsby.jsで作ってます。Netlifyでホスティングしてますが、特にFirebase hostingを使ってない理由はありません。AuthとFirestoreだけ試したかったので。

gatsby-firebase-todo.netlify.com

コードはこちら

GitHub - rei-m/gatsby-firebase: Sample of Gatsby.js with Firebase

実装

Firebaseの設定

何はともあれエントリーポイントでfirebase.initializeAppをしなければいけないのですが、Gatsbygatsby-browser.jsにonClientEntryというAPIが生えているのでそこで行いました。これだけですね。

export const onClientEntry = () => {
  const config = {
    // your config
  };
  firebase.initializeApp(config);
};

Firebase.authentication

firebase.auth().onAuthStateChangedにObserverを登録します。これはアプリケーション全体に影響するのでReact.Contextに認証状態を保持してアプリケーション全体を囲むようにしました。こんな感じのcustom hookを作って取れたuserオブジェクトをcontextで持つようにしてます。

useFirebaseAuth.ts

export const useFirebaseAuth = () => {
  const [user, setUser] = useState<User | null>();

  useEffect(() => {
    const unsubscribe = firebase.auth().onAuthStateChanged(user => {
      if (user) {
        console.info(`firebase: authorized (uid: ${user.uid})`);
        const userName = user.displayName ? user.displayName : '名無し';
        setUser({ uid: user.uid, name: userName });
      } else {
        console.info(`firebase: unauthorized`);
        setUser(null);
      }
    });

    return () => {
      console.info(`firebase: unsubscribe onAuthStateChanged`);
      unsubscribe();
    };
  }, []);

  return user;
};

useEffectを使ってonAuthStateChangedのsubscribeを開始、componentが破棄されるときにunsbscribeするという感じになりますね。contextはこれだけになります。Gatsbygatsby-browserとgatsby-ssrのwrapRootElementでこのProbiderで包んであげればOKです。

FirebaseAuthProvider.tsx

export const FirebaseAuthContext = React.createContext<{
  user?: User | null;
}>({});

const FirebaseAuthProvider: React.FC<{}> = ({ children }) => (
  <FirebaseAuthContext.Provider value={{ user: useFirebaseAuth() }}>
    {children}
  </FirebaseAuthContext.Provider>
);

これで認証状態が変わったタイミングでcontextのuserが更新されて再描画が走るようになりました。contextの情報はuseContextを使えば参照できます。

Firebase.firestore

今回はよくあるTodoアプリを作るのでユーザーごとのTodoリストをFirestoreに保存します。構成は素朴にUsersというCorrectionの下にUser単位でdocmentを作り、その下にSubCorrectionでtodosを保存します。 FirestoreもonSnapshotでObserverを登録する実装になる(リアルタイム同期が必要なければいらないけど)ので、同様にuseEffectを使ったcustom hookを作ります。

useFirestoreTodos.ts

export const useFirestoreTodos = (uid: string, filter: VisibilityFilter) => {
  const [todos, setTodos] = useState<Todo[]>();

  useEffect(() => {
    const collection = todosCollection(uid);

    let query: firebase.firestore.Query;
    switch (filter) {
      case SHOW_ACTIVE:
        query = collection
          .where(`completed`, `==`, false)
          .orderBy(`createdAt`, `desc`);
        break;
      case SHOW_COMPLETED:
        query = collection
          .where(`completed`, `==`, true)
          .orderBy(`createdAt`, `desc`);
        break;
      default:
        query = collection.orderBy(`createdAt`, `desc`);
        break;
    }

    const unsubscribe = query.onSnapshot(snapshot => {
      console.info(`firestore: receive todos: size=${snapshot.docs.length}`);

      const todos = snapshot.docs.map(doc => toModel(doc.id, doc.data()));
      setTodos(todos);
    });

    return () => {
      console.info(`firestore: unsubscribe onSnapshot:todos`);
      unsubscribe();
    };
  }, [filter]);

  return todos;
};

authと同様ですが、リストの検索条件は変更出来るので、useEffectの第2引数にfilterを指定してfilterが変更されたらobserverを登録し直すようにします。実際にこのhookを使ったcomponentはこんな形になります。

TodoContents.tsc

const TodoContents = ({ user }: Props) => {
  const [filter, setFilter] = useState<VisibilityFilter>(SHOW_ALL);
  const todos = useFirestoreTodos(user.uid, filter);

  const handleAddTodoSubmit = async (todoName: string) => {
    await addTodoAction(user.uid, todoName);
  };

  const handleClickTodo = async (todo: Todo) => {
    await updateTodoAction(user.uid, todo.id, !todo.completed);
  };

  const handleClickDeleteTodo = async (todo: Todo) => {
    await deleteTodoAction(user.uid, todo.id);
  };

  return (
    <section>
      {todos ? (
        <>
          <AddTodoForm onSubmit={handleAddTodoSubmit} />
          <TodoList
            todos={todos}
            onClickTodo={handleClickTodo}
            onClickDeleteTodo={handleClickDeleteTodo}
          />
          <TodoFilter currentFilter={filter} onClick={setFilter} />
        </>
      ) : (
        <div>ろーでいんぐ</div>
      )}
    </section>
  );
};

動かしてみるとこんな感じ

f:id:Rei19:20190608212724g:plain

おしまい

  • 簡単にしか触っていないけどFirestoreまわりの設計がキモになるなーという印象です。RDBでは○○できるのにとか思わずに(そもそも全然別物だけど)、Firestoreに最適化した設計を考えてかないといかんですね。
  • あとはCloudFunctionあたりを素振りすれば、だいたいサービス作るのに必要なものは事足りそう。
  • docment更新したときにonSnapshotで2回通知が流れてくるのがちょっと謎挙動でした。serverTimestampの仕様っぽいけど要調査。
  • テスト書くのはどうやるのかわかってないので後ほど

関連

というようなことを試してたらエンジニアHUBで気合の入った入門記事が流れてきたので紹介など

employment.en-japan.com