20200726のReactに関する記事は7件です。

Next.jsで、同じ変数なのに属性内とテキスト内とで違う値になる

環境

  • Next.js 9.4.4
  • React 16.13.1

再現手順

Next.jsで、下記のようなコンポーネントを定義します。そしてnext devで開発サーバを立ち上げるか、next build && next exportで静的サイト生成して、サイトを表示します。

pages/index.js
export default function Home() {
  const now = Number(new Date());
  return (
    <>
      <p><a href={now}>{now}</a></p>
    </>
  )
}

このときのnow変数で表示される値が、href属性とaタグ内部テキストとで同じ変数なのに異なる値を取ってしまいます

image.png

ふたつの値は、それぞれ下記のものになっています。

  • href属性内の値は、サーバサイドでのページ生成時の値(静的サイトのビルド時の値)
  • テキストは、クライアントサイドでのページ表示時の値

開発サーバ立ち上げ時はブラウザのコンソールにprop did not match. Server: ..., Client: ...という警告が出るので気づきやすいですが、静的サイト生成時だと表示されません。逆に、静的サイト生成の方で試すとエラーは出ませんが違いが大きいため分かりやすいです。

適当にステートを更新して明示的に再描画すると、それ以降ふたつの値はちゃんと一致するようになります。

pages/index.js
export default function Home() {
  const now = Number(new Date());
  const [_, setHoge] = React.useState();

  return (
    <>
      <p><a href={now}>{now}</a></p>
      <input type="button" value="refresh" onClick={()=>{setHoge(Math.random())}} />
    </>
  )
}

また、Productionモードで動作させたときもこのような現象は発生しないようです。

原因

どうやらNext.jsのAutomatic Static Optimizationという機能が原因のようです。サーバサイドとクライアントサイド両方で生成が可能な変数で、かつサーバとクライアントとで値が変わってしまう場合に、このような現象が発生するようです。

この問題は、getStaticPropsを使うと値が一致するようになります。

pages/index.js
export default function Home({ now2 }) {
  return (
    <>
      <p><a href={now2}>{now2}</a></p>
    </>
  )
}

export async function getStaticProps() {
  const now2 = Number(new Date());
  return { props: { now2 } }
}

またはAutomatic Static OptimizationはgetInitialPropsという機能を使うことで無効になります。

pages/index.js
export default function Home({ now2 }) {
  return (
    <>
      <p><a href={now2}>{now2}</a></p>
    </>
  )
}

Home.getInitialProps = async (_) => {
  return { now2: Number(new Date()) };
}

ただしこれらの方法を使うと、静的サイトのビルド時に(サーバサイドで)生成された値が更新される事はありません。静的サイト生成を使う場合は特に困りますね。

もし静的サイト生成を使わない場合(サーバサイドレンダリングする場合)は、getServerSidePropsを使えば良いようです。

pages/index.js
export default function Home({ now2 }) {
  return (
    <>
      <p><a href={now2}>{now2}</a></p>
    </>
  )
}

export async function getServerSideProps() {
  const now2 = Number(new Date());
  return { props: { now2 } }
}

解決方法

componentDidMountに引っ掛けることで、サーバサイドで描画されないように(クライアントサイドで一度レンダリングが走った後に、実際のDOMが描画されるように)します。useEffect()を使う場合は下記のような感じです。

pages/index.js
export default function Home() {
  const now = Number(new Date());

  const [isMounted, setIsMounted] = React.useState(false);
  React.useEffect(() => setIsMounted(true));
  if (!isMounted) return <>Loading...</>;

  return (
    <>
      <p><a href={now}>{now}</a></p>
    </>
  )
}

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Mac】「Docker」で「Ruby on Rails 6」「React」と「MySQL 8」で環境構築(CRUDのサンプル付)

■ はじめに

SESエンジニアとして、PHPをメインに参画し、現在はJava案件に参画しているTatsuyaです。
そしてこの度転職が決まりまして、9月から新しい職場に!!!!
楽しみもある反面、実は不安要素が。。。

それは、、、
次の職場は!!Ruby on Rails!!やったことない!!

僕「学習しないと転職した瞬間からお荷物確定ダァー」

と焦り散らかし始めて、先日からRuby on Railsの学習に励み始めたわけなのですが。。。

僕「Rubyの公式チュートリアルめちゃくちゃ大変すぎないか!?さらにHerokuとかも使うの!?結構大掛かり!?」

というので、実は挫折しちゃいました。。。
挫折理由としては、チュートリアル自体が骨太ということもありますが、 Dockerを使った環境構築 も重なって折れちゃいました。(最弱)

僕は、直接自分のMacにRuby環境は作りたくなかったんで、なんとしてでもDockerでローカル環境を整えたかったのです。(次の職場でもDocker使うみたいですしお寿司)

ということで、先人の方々が丁寧に書き上げていただいた貴重な文献を元に、実際に動かしながらRubyの動きがわかるような良い感じの環境を作らせていただいて、そこで学習を進めようという方向に舵を切ることになりました。

その過程で、とてもわかりやすく、実際に手を動かしながら理解しやすいと感じた「環境」「サンプルコード」を有り難くミックスさせていただいたので、そちらの結果の方をこの記事でご紹介できればなと思います。

◇ 大変お世話になった貴重な参考文献(無許可です。すいません。)

以下に記載した方々のお力をお借りして、良い感じのサンプルサイトの構築ができました。ありがとうございます。
これから環境構築作業に入っていきますが、丁寧に進めたい!という方はぜひ僕の記事と一緒に偉大なる先人方の記事を見ていただければかなり深まるのではないかと思います。

◉ 環境

Docker + Ruby on Rails + MySQLで開発環境を新規構築する

  • 丁寧な記事で、詳しくソース毎の解説をしてくれています。またDockerのインストールから記載していただいているので、初めてDockerに触るという方にもオススメです。とてもありがたかったです。

◉ サンプルコード

Rails + React + AjaxでCRUDのサンプルプロジェクト

Rails + React + AjaxでCRUDのサンプルプロジェクト(ソース一式)

  • チュートリアル方式で解説を進めてくださっております。大変わかりやすいです。私はまるっとクローンして動かしたのですが、書きながら学びたい方は順に進めていくのも良いと思います。

◉ その他細かい部分でお世話になった記事

Rails on Dockerでcredentialsをeditしたい

◇ 感謝と謝罪

  • ※基本的に参考記事様の情報をコピペで使用しております。私が今回取り組んだのは、 環境とサンプルコードのミックス なので、変にアレンジさせずにほぼ原型のままで使用させていただいております。貴重なお時間を割いて書き上げていただいた記事を丸々コピーするような形になりまして、大変申し訳ないという気持ちと、Rubyの学習コストを下げていただいて誠に有り難うございますという感謝と謝罪を先にさせていただきたいと思います。

■ 実際に環境を構築していきましょう

◇ 前提条件

  • Mac
  • Docker インストール済みであること
    • まだの方はこちらの記事から
  • git インストール済みであること

◇ 最終的なディレクトリ構成

ディレクトリ構成
mpp_react_crud
  /mysql
    /Dockerfile
    /my.cnf
  /rails
    /app
    /bin
    /config
    /db
    /log
    /node_modules
    /public
    /tmp
      .browserslistrc
      .gitignore
      .ruby-version
      .babel.config.js
      .config.ru
      Dockerfile
      Gemfile
      Gemfile.lock
      LICENSE
      package.json
      pastcss.config.js
      Rakefile
      README.md
      yarn.lock
  docker-compose.yml

◇ サンプルプロジェクトをクローン

terminal
# homeへ
$ cd
# mpp_react_crud ディレクトリを作成
$ mkdir mpp_react_crud
# mpp_react_crud ディレクトリに移動
$ cd mpp_react_crud
# サンプルプロジェクトをclone
$ git clone https://github.com/TakeshiOkamoto/mpp_react_crud.git
# mpp_react_crudプロジェクト の名前を railsに変更
$ mv mpp_react_crud rails

◇ docker-compose.yml の作成

terminal
# 『◇ サンプルプロジェクトをクローン』の続きから

# docker-compose.yml を作成する
$ vi docker-compose.yml
# vim画面が開いたら、「iキー」でINSERTモードにし、以下のymlをコピペしてください。
# コピペが完了したタイミングで、「:wq」で保存をして終了してください。
docker-compose.yml
version: "3.7"
services:
  db:
    build: mysql
    image: mpp_react_crud_db
    container_name: mpp_react_crud_db
    ports:
      - 3306:3306
  app:
    build: rails
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    image: mpp_react_crud_app
    container_name: mpp_react_crud_app
    ports:
      - 3000:3000
    volumes:
      - ./rails:/rails
    depends_on:
      - db

◇ MySQL用のDockerfileとmy.cnfを作成

▼ Dockerfile の作成

terminal
# 『◇ docker-compose.yml の作成』の続きから

# mysql ディレクトリを作成
$ mkdir mysql
# mysql ディレクトリに移動
$ cd mysql
# Dockerfile を作成する
$ vi Dockerfile
# vim画面が開いたら、「iキー」でINSERTモードにし、以下のymlをコピペしてください。
# コピペが完了したタイミングで、「:wq」で保存をして終了してください。
Dockerfile
FROM mysql:8.0.18

ENV MYSQL_ROOT_PASSWORD root_pass

COPY ./my.cnf /etc/mysql/conf.d/my.cnf
RUN mkdir /var/log/mysql
RUN chown mysql:mysql /var/log/mysql
RUN mkdir /var/run/mysql
RUN chown mysql:mysql /var/run/mysql

▼ my.cnf の作成

terminal
# my.cnf を作成する
$ vi my.cnf
# vim画面が開いたら、「iキー」でINSERTモードにし、以下のymlをコピペしてください。
# コピペが完了したタイミングで、「:wq」で保存をして終了してください。
my.cnf
[mysql]
default-character-set=utf8mb4
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_bin
datadir=/var/lib/mysql
socket=/var/run/mysql/mysql.sock
log-error=/var/log/mysql/mysqld.log
pid-file=/var/run/mysql/mysqld.pid
port=3306
default_authentication_plugin= mysql_native_password
[client]
default-character-set=utf8mb4
terminal
# 最後に mpp_react_crud ディレクトリに戻る
$ cd ..

◇ Ruby on Rails用のDockerfileとGemfile.lockを作成

▼ Dockerfile の作成

terminal
# 『◇ MySQL用のDockerfileとmy.cnfを作成』の続きから

# rails ディレクトリに移動
$ cd rails
# Dockerfile を作成する
$ vi Dockerfile
# vim画面が開いたら、「iキー」でINSERTモードにし、以下のymlをコピペしてください。
# コピペが完了したタイミングで、「:wq」で保存をして終了してください。
Dockerfile
FROM ruby:2.6.5

ENV LANG C.UTF-8
ENV APP_HOME /rails

RUN apt-get update -qq && apt-get install -y build-essential nodejs

# もしyarnでエラーが発生した場合
RUN apt-get update -qq && apt-get install -y curl && 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 && apt-get install -y yarn && apt-get install -y vim

RUN rm -rf /var/lib/apt/lists/*

RUN mkdir $APP_HOME
WORKDIR $APP_HOME
COPY ./Gemfile $APP_HOME/Gemfile
COPY ./Gemfile.lock $APP_HOME/Gemfile.lock
RUN bundle install
COPY . $APP_HOME

EXPOSE  3000

▼ Gemfile.lock の作成

terminal
# Gemfile.lock を作成
# 空ファイルで良いので、touch コマンドで作成します。
$ touch Gemfile.lock

◇ Rails用データベース設定ファイル"database.yml"を編集

terminal
# 『◇ Ruby on Rails用のDockerfileとGemfile.lockを作成』の続きから

# database.yml を編集
$ vi config/database.yml
# vim画面が開いたら、「iキー」でINSERTモードにし、以下のymlをコピペしてください。
# コピペが完了したタイミングで、「:wq」で保存をして終了してください。
database.yml
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password:  root_pass
  host: db

development:
  <<: *default
  database: mpp_react_crud_development

test:
  <<: *default
  database: mpp_react_crud_test

production:
  <<: *default
  database:
  username:
  password:
terminal
# 最後に mpp_react_crud ディレクトリに戻る
$ cd ..

◇ Dockerイメージをビルド

terminal
# 『◇ Rails用データベース設定ファイル"database.yml"を編集』の続きから
# Docker イメージをビルド
$ docker-compose build

◇ docker-composeでアプリを起動〜プロジェクト設定

terminal
# 『◇ Dockerイメージをビルド』の続きから

# サービスの立ち上げ
$ docker-compose up -d  # -d: バックグラウンドで起動
# bundle インストール
$ docker-compose run app bundle install
# 各種パッケージのインストール
$ docker-compose run app yarn install
# マスターキーの生成
# ファイル生成後、credentials.yml.encの編集画面が表示されるので:q!で終了します。
$ docker-compose run -e EDITOR=vim app rails credentials:edit
# フォルダの生成
$ mkdir rails/app/assets/images

◇ データベース準備

terminal
# 『◇ docker-composeでアプリを起動〜プロジェクト設定』の続きから

# データベースの作成
$ docker-compose run app rails db:create
# 各テーブルの作成
$ docker-compose run app rails db:migrate
# 各テーブルの初期データの作成
$ docker-compose run app rails db:seed

◇ 完成!

最後にコンテナを再起動しましょう。

terminal
$ docker-compose restart

再起動終了後に、http://localhost:3000 にアクセスしてみてください!
すると、以下のようなページが開かれます。

スクリーンショット 2020-07-26 16.49.15.png

以上でサンプルサイトの環境構築終了です!

■ 終わりに

初心者の方だけでなく、経験者の方でもあっても、初めて触る技術の学習にはかなり苦戦すると思います。
そのため、先に「どういうサイトがどのように動いているか」ということを知るだけでも学習効率は上がるんじゃないかなーと思い、今回の作業に取り掛かりました。
まだ私も環境構築をしてすぐにこの記事を書いているので、この環境を使った学習は始めていませんが、実際にログを仕込んでみたりしながら体感的に学ぶ方が飲み込みは早いと考えるので、ガンガンこの環境とこのサイトを元に学習に取り組んでいこうと思います。

この記事は解説部分を完全に端折って、ただただ完成させることだけを目標に書き上げました!
そのため、しっかりと解説を確認したい方は、参考文献様の記事をご確認いただいて、照らし合わせながら進めてほしいと思います。

僕「本当に先人の方々はすげえや。」

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Hooksって結局いつ使うの?

導入

最近、React Hooksについて勉強しているのですが、どんなことができるのかなんとなく分かってもどういうメリットがあるのかイマイチ分からないということがあります

そこで今回はReact Hooksを使うとどんなメリットがあるのかをいろいろと調べてみたので学習記録という意味でもまとめてみようと思います

まず最初に:useStateとレンダリングの仕組み

useStateはstateとそのstateに値を入れるsetStateの組を生成します

const [state, setState] = useState(null);
setState('test'); // stateに'test'が入る

なぜuseStateを使うかを理解するにはレンダリングの流れを理解する必要があります。
以下のコードを例に解説します

import React, { useState } from 'react';

export const Page = () => {
  const [text, setText] = useState(null);
  if(!text) {
    setText('test');
    console.log(text);
  }
  console.log(text);
  return (
    <div className="Page">
       {text}
    </div>
  );
}

こちらのコードを実行すると画面にはtestとだけ表示されます
以下実行の流れを見ていきます

const [text, setText] = useState(null);

まず最初のuseStateでtextとsetTextを定義します。このときtextにはnullが入っています

if(!text) {
  setText('test');
  console.log(text);
}
console.log(text);

textがnullならtextにtestを入れます。textは最初nullなのでここを通り、textには'test'が入ります。
ここで注意しなければならないのはsetTextはすぐに機能しないということです。setTextはtextの値を【次のレンダリングから】testに変更することを意味していますなのでこの時点ではtextの値は変わっておらずconsoleではどちらもnullが表示されます

return (
  <div className="Page">
     {text}
  </div>
);

その状態で一度returnされます。したがって一回目のレンダリングでは何も表示されません。setTextによりtextが変更されたのでそのまますぐに2回目のレンダリングがtext=testとして実行されます

2回目のレンダリングではtextはnullではないのでsetTextは呼ばれず、そのままtext='test'が出力されます

useStateで実現できること

useStateで実現できるのは
画面内でのアクションによる変化を画面に反映させること
です

こちらも例を見てみます

export const Page = () => {
  let text = null
  if(!text) {
    text = 'test';
    console.log(text);
  }
  console.log(text);
  return (
    <div className="Page">
      <button onClick={() => {text = 'clicked'}}>button</button>
      <div>{text}</div>
    </div>
  );
}

ボタンをクリックすると表示される内容を変えるという内容ですが、これだと再レンダリングされないため、いくらボタンを押しても表示されるのはtestのまま変わりません
ボタンを押したことを画面に反映させるにはuseStateを使わなければなりません。

export const Page = () => {
  const [text, setText] = useState(null);
  if(!text) {
    setText('test');
    console.log(text);
  }
  console.log(text);
  return (
    <div className="Page">
      <button onClick={() => {setText('clicked')}}>button</button>
      <div>{text}</div>
    </div>
  );
}

こちらのコードを使うとちゃんと表示されるtextが変化します。

なお、変更内容を画面に表示しなくてよければuseStateは必要ありません。

export const Page = () => {
  let text = null
  if(!text) {
    text = 'test';
    console.log(text);
  }
  console.log(text);
  return (
    <div className="Page">
      <button onClick={() => {text = 'clicked'}}>button</button>
      <button onClick={() => {console.log(text)}}>display</button>
      <div>{text}</div>
    </div>
  );
}

このコードで一度buttonを押してからdisplayを押すとconsoleにはpushedが出力されます

useEffectで実現できること

useEffectによって
一部に依存した処理の不要な呼び出しを防ぐこと
ができます

useEffectは

useEffect(
  (), //関数
  [] //依存する変数
)

という形で依存する変数が変化したときのみ関数処理を行うことができます

useEffectを使わないとこのようなコードになります

export const Page = () => {
  const [clicked, setClicked] = useState(false);
  const [text, setText] = useState(null);
  if(clicked && !text) {
    setText('test');
    console.log(text);
  }
  console.log(text);
  return (
    <div className="Page">
      <button onClick={() => {setClicked(true)}}>button</button>
      <button onClick={() => {setText(null)}}>clear</button>
      <div>{text}</div>
    </div>
  );
}

こちらのコードではbuttonを押すとtextにtestが入ります。
しかし、clearを押してtextをnullにしたときもif文の中に入ってしまい、そこでtextに値が入ってしまうためclearの方は上手く動きません。

useEffectを用いるとこのように書けます

export const Page = () => {
  const [clicked, setClicked] = useState(false);
  const [text, setText] = useState(null);
  useEffect(
    () => {
      if(clicked) {
        setText('test');
        console.log(text);
      }
    },
    [clicked]
  )
  console.log(text);
  return (
    <div className="Page">
      <button onClick={() => {setClicked(true)}}>button</button>
      <button onClick={() => {setText(null)}}>clear</button>
      <div>{text}</div>
    </div>
  );
}

こちらではclearを押してもtextは変わっていないためuseEffectの中は呼ばれず、textの値も変わりません。
値の変化に連動してstateを変化させる処理はuseEffectを使わないと複雑になりがちなので複数の値を連動させて変化されるときはuseEffectを使うのが良いと思われます

memo, useCallbackでできること

memoは
一部に依存したコンポーネントの呼び出しを最小限にすること
useCallbackは
一部に依存したfunctionの呼び出しを最小限にすること
ができます

役割が似ていることもあり、この2つは一緒に使われることが多いです

useCallbackは

const functionA = useCallback(
  (), //関数
  [] //依存する変数
)

という形で書くことができ、このようにするとfunctionAは依存する変数が変化しない限り再生成されず、同じ関数を使い回すことができます。

memoも同様で

const ComponentA = memo((props) => {
  // コンポーネントの内容
})

と書くことができ、propsの内容が変わらない限り再レンダリングされず、同じコンポーネントを使い回すことができます。

以下のコードを例に考えてみます
(例はこちらを参考にさせていただきました)

const Child = ({handleClick}) => {
  console.log('child render')
  return(
    <button onClick={handleClick}>child button</button>
  )
}

export const Page = () => {
  const [count, setCount] = useState(0);
  console.log('render');
  return (
    <div className="Page">
      <button onClick={() => {setCount(count + 1)}}>button</button>
      <Child handleClick={() => console.log('child')} />
      <div>{count}</div>
    </div>
  );
}

こちらのコードではbuttonをクリックするとsetCountが走り、それによってPageだけでなく、変化していないChildも再レンダリングされてしまいます。memoやuseCallbackを使うことでこのような不要な再レンダリングを減らすことができます

const Child = memo((props) => {
  console.log('child render')
  return(
    <button onClick={props.handleClick}>child button</button>
  )
})

export const Page = () => {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(
    () => {
      console.log('child');
    },
    []
  )
  console.log('render');
  return (
    <div className="Page">
      <button onClick={() => {setCount(count + 1)}}>button</button>
      <Child handleClick={handleClick} />
      <div>{count}</div>
    </div>
  );
}

このようにすることで、まずuseCallbackにより、handleClickの再生成を避けることができます。すると、memoにより、ChildはhandleClickが変化しない限り再レンダリングされないようになっているのでChildの再レンダリングも避けることができ、結果的に不要なレンダリングを減らすことができるようになります。

useCallbackはmemoと一緒に扱われることが多いので勘違いしやすいですが、効果はあくまでもfunctionの再生成を防ぐことにあるのでmemoと併用する以外にも使用される場面はあると思われます(うまい例が浮かびませんが...)
関数の再生成を防ぐならuseCallback、コンポーネントの再レンダリングを防ぐならmemoと覚えておくといいです

まとめ

useStateは画面内で発生した変化を画面に反映させるとき
useEffectは一部に依存した処理の不要な呼び出しを防ぐとき
useCallbackは一部に依存したfunctionの再生成を防ぐとき
memoは一部に依存したコンポーネントの再レンダリングを防ぐとき

に効果がある

参考

https://qiita.com/ossan-engineer/items/740425a0df937a47e093
https://qiita.com/uehaj/items/99f7cd014e2c0fa1fc4e
https://qiita.com/soarflat/items/b9d3d17b8ab1f5dbfed2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel + React環境にESlintとPrettierを導入する

始めに

以下はLaravel + Reactの構成で、EslintPrettierを導入する流れをメモした記事となります。

bladeを使わずに、ReactやVueを利用するプロジェクトも増えてきてると聞いて、では、ESlintやPrettierを導入する時の手順はどうするんだろう?という事で、ちょっと試してみました。

Laravelのバージョンは、5.7です。
Laravel + Reactの構成で、Linterを導入したい人は、多少参考になるのではないかと思います。

そもそもESlintって?

ESlintとは、JavaScriptのための静的解析ツールで、シングルクォートやスペースの記法のルールを統一したり、単純な構文エラーを検出してくれたりします。

ルールは細かく設定できるので、各プロジェクトによって固有のコーディング規約を定義することもできるので便利です。

詳しくは公式を...
ESlint

そもそもPrettierって?

こちらは、Node.js上で動作するフォーマッターです。
インデントや、改行コードなどの基本的な設定をして、定義したフォーマットに従って自動的に整形してくれます。
Linterと役割が、重複する部分もありますが、Linterだけで整形できない部分もカバーしてくれたりします。(1行の長い文字列を整形する設定ができるなど)

コードのフォーマットに関しては、Prettierで行い、構文チェックはESlintで行うという形で併用するのが良いかと思います。

こちらも詳しくは公式を...
Prettier

なんで導入するの?

上記のようなLinterとFormatterを導入してない状態で、複数人開発をしている方は経験あるかもしれませんが、さまざまな書き方が混在し、1ファイルの中でも、複数の記法が混在し、統一性のない読みづらいコードとなってしまうことがあります。

あれ? シングルクォート、ダブルクォートが混在してる...
おぉ...パスカルケース、キャメルケースが混在してる...何か意図があるのかなぁ...?
ん? セミコロンつけるの?つけないの?...など

このような状態で開発が続いてしまうと、可読性が低く、メンテナンス性の悪いコードになってしまいます。また、プルリク時に、コーディング規約の議論に時間を奪われてしまうことも度々発生します。

フォーマットや命名規則は、プロジェクトに関わる開発者間で統一し、コードリーディング/レビュー時の可読性をあげることは、開発のスピード感をあげる上で重要です。それによって、開発者はコードスタイルに気をとらわれることなく、本質的なコーディングにフォーカスすることができます。

ESlintやPrettierのような優秀なフォーマッターが用意されてるので、それに頼って、可読性の高い、後から入ってきたメンバーに対しても優しいコードにしましょう。ツールに任せられることは優秀なツールに任せちゃいましょう。

Laravel+ReactのプロジェクトにESlintとPrettierを導入する

では、早速導入してみましょう。
まずはnpmコマンドでパッケージをインストール。
各環境で必要に応じて、パッケージをインストールして下さい。

パッケージをインストール
npm install eslint eslint-loader eslint-plugin-react eslint-plugin-prettier eslint-config-prettier --save-dev

上記インストールが完了したら、.eslintrc.jsonを作成します。
これはESlintの設定ファイルです。

.eslintrc.jsonを作成
$ cd LaravelProject
$ touch .eslintrc.json
eslintrc.json
{
    "env": {
        "es6": true,
        "commonjs": true,
        "browser": true // Globalオブジェクト「window」「document」を許可
    },
    "extends": [
        "eslint:recommended",
        "plugin:react/recommended",
        "plugin:prettier/recommended"
    ],
    "settings": {
        "react": {
            "pragma": "React",
            "version": "detect"
        }
    },
    "parser": "babel-eslint",
    "parserOptions": {
        "sourceType": "module"
    },
    "rules": {
        "prettier/prettier": [
            "error",
            {
                "printWidth": 120,
                "tabWidth": 2,
                "trailingComma": "all",
                "bracketSpacing": true,
                "singleQuote": true,
                "semi": false
            }
        ]
    }
}

LaravelでESlintを使用するよう設定を変更する

Webpackが定義したルールをを元に、ファイル変更を監視してくれるように、webpack.mix.jsを編集します。

以下を参考にしています。
Laravel Mix Eslint Config

webpack.mix.js
const mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

//本番環境ではESLintは使用しません
if (!mix.inProduction()) { 
    mix.webpackConfig({
        module: {
            rules: [
                {
                    enforce: 'pre',
                    exclude: /node_modules/,
                    loader: 'eslint-loader',
                    test: /\.(js|jsx)?$/,
                    options: {
                        fix: true,
                        cache: false,
                    }
                }
            ]
        }
    })
}

これでセットアップは完了です。

実際に試してみる

以下のようなセミコロンのあるファイルがあります。
しかし、このファイルは、セミコロンを許可しないルールが定義されてる場合、コーディング規約に則っていないコーディングをしているということになります。

App.js
import React from 'react';
import ReactDOM from 'react-dom';

import BooksApp from './BooksApp';

const App = () => {
    return (
        <div>
            <BooksApp />
        </div>
    );
};

export default App;

if (document.getElementById('app')) {
    ReactDOM.render(<App />, document.getElementById('app'));
}

以下のコマンドを打ってみます。

eslint resources/js/components/App.js

すると、以下のようにコンソールにエラーが表示されます。
見るとセミコロンを削除して下さいと言われてますね。
スクリーンショット 2020-07-26 11.05.28.png
この場合、いちいち手動で直すの面倒ですよね?

その場合、以下コマンドを入力して下さい。
これによって、EslintPrettierが同時に実行されます。
(= 構文チェックとコードの整形が同時にできる)
これは、先ほどインストールしたeslint-config-prettierによって実現できることです。

eslint resources/js/components/App.js --fix

すると、リアルタイムに、上記App.jsにあった余計なセミコロンが削除されます。
もう一度、以下コマンドを入力してみましょう。

eslint resources/js/components/App.js

すると何もエラーが返ってこない状態になっています。
ここは個々のプロジェクトによって状況が異なるのでご自身の環境に読み替えてみて下さい。

ワイルドカードも使えます。

eslint resources/js/components/*.js
eslint resources/js/components/*.js --fix

最後に

以上で、*.js*.jsxファイルに対して、ESlintとPrettierを導入することができました。
上記手順で、プロジェクトのコーディング規約を定義すれば、開発者間のコーディングスタイルの差異を統一することができるようになります。

実際のプロジェクトでは、Gitフックなど使って、コミット時に、ESlintとPrettierを実行するという設定もするかなと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React SpectrumでBuild a movie search app using React hooksを作ってみた

React Spectrumがすごい

React Spectrumがすごそうということで、実際に使って開発してみました。React Spectrum自体の設計のクオリティの高さは、Adobe製デザインシステム「React Spectrum」がすごいので紹介したいの記事が詳しいです。内部の作りはさておき、開発者視点で良かったと感じた点は以下です。

  • Adobeが開発しているので信頼度が高い
  • Componentのプロパティの統一感があり、わかりやすく使いやすい
  • Flex/Gridが簡単にかける
    • 基本的にタグで書いていく思想?でCSSを書かなくても大体いける。Flex/Gridもタグがある。
    • classNameのプロパティは「UNSAFE_className」になっており、説明にも「最後の手段」と。

Build a movie search app using React hooksについて

2020年のフロントエンドマスターになりたければこの9プロジェクトを作れで1つめに紹介されていたプロジェクトです。元のサイトはこちら。フロントエンドマスターを目指した方なら作ってみたことがあるはず!React Spectrumを試すちょうどよい題材として活用させてもらいました。

React Spectrumを使ってカスタマイズ

完成したイメージ

こんな感じです。
(デザインがダサいと感じたら、それはReact Spectrumではなく私のセンスの問題です。)
image.png

スマホにするとこんな感じです。
文字サイズとかいい感じに変わってくれるところとか素晴らしいです。
image.png

ダークモードにするとこんな感じです。
本来はOSの設定に応じて切り替わりますが、Reactの勉強がてら切り替えボタンをつけてみました。文字色もうまく変えてくれています。
image.png

キャプチャではわかりませんが、検索APIを呼び出している間は、ProgressCircleがくるくる回ります。
image.png

中身の説明

リセットCSS

リセットCSSがなくても大丈夫かもしれませんが、hタグを使った時の余白が気に入らなかったので、リセットCSSを入れました。使用したのはmodern-css-resetです。はじめは、destyle.cssを使ってみたのですが、hタグで見た目の違いが表現できなくなってしまったので、適度にリセットしてくれるmodern-css-resetにしました。導入は簡単で、yarn add modern-css-resetして、index.jsにimport 'modern-css-reset';するだけです。

Flex,Gridで簡単にレイアウト

App.jsでレイアウトを作っていきます。レイアウトを実現するのにCSSを自分で書く必要はなく、用意されたタグだけで十分でした。
全体の構成は

  • ヘッダー
  • 検索部分
  • ガイダンス部分
  • コンテンツ部分
  • フッター

です。
全体をFlexboxで並べて、それぞれのレイアウトの調整にさらにFlexboxやGridを使いました。コンテンツ部分は固定サイズで配置したかったのでGridにしています。React Spectrumのイメージがつかめるように、import部分とreturnのレイアウト部分のみ抜粋します。

App.js
import React, { useReducer, useEffect } from 'react';
import './App.css';
import HookedHeader from "./HookedHeader";
import Movie from "./Movie";
import Search from "./Search";
import { Provider, defaultTheme, Flex, View, Text, Grid, repeat, Footer, ProgressCircle, Link } from '@adobe/react-spectrum';


const App = () => {

  return (
    <Provider theme={defaultTheme}
      colorScheme={colorSchemeStete.colorScheme}>

      {/* 全体をflexbox化する */}
      {/* ダークモードでも白地が見えないように画面の高さ分をコンテンツ領域で確保する */}
      <Flex direction="column" gap="size-100" minHeight="100vh">

        {/* ヘッダー部 */}
        <HookedHeader
          text="HOOKED"
          switch={switchColorScheme}
          currentColorScheme={colorSchemeStete.colorScheme} />

        {/* 検索部 中央寄せにする*/}
        <Flex direction="row" justifyContent="center">
          <Search search={searchMethod} />
        </Flex>

        {/* ガイダンス部 中央寄せにする */}
        <Flex direction="row" justifyContent="center">
          <Text>Sharing a fwe of our favourite movies</Text>
        </Flex>

        {/* コンテンツ部 Grid化する */}
        <Grid
          columns={repeat('auto-fit', 'size-2400')}
          autoRows="size-2400"
          justifyContent="center"
          gap="size-200">
          {loading && !errorMessage ? (
            // ローディング表示
            <View
              // 上下中央表示
              alignSelf="center"
              // 左右中央表示(gridのrpeatを無視)
              justifySelf="center">
              <ProgressCircle aria-label="Loading…" isIndeterminate />
            </View>
          ) : errorMessage ? (
            // エラーメッセージ表示
            <View
              // 左右中央表示(gridのrpeatを無視)
              justifySelf="center">
              <div className="errorMessage">{errorMessage}</div>
            </View>
          ) : (
                // コンテンツ表示
                movies.map((movie, index) => (
                  <View
                    backgroundColor="gray-200">
                    <Movie key={`${index}-${movie.Title}`} movie={movie} />
                  </View>
                ))
              )}


        </Grid>

        {/* フッター リンクをつけてみる*/}
        <Footer alignSelf="center">
          {/* Linkを使うとダークモードでも見やすく色が変わる */}
          <Link>
            <a href="https://www.freecodecamp.org/news/how-to-build-a-movie-search-app-using-react-hooks-24eb72ddfaf7/" target="_blank">
              freeCodeCampHow to build a movie search app using React Hooks
          </a>
          </Link>
          <br />
          <Link>
            <a href="https://react-spectrum.adobe.com/react-spectrum/index.html" target="_blank">
              React SpectrumA React implementation of Spectrum, Adobes design system.
          </a>
          </Link>
        </Footer>
      </Flex>
    </Provider>
  );
};

export default App;

ちなみにヘッダーコンポーネントも中身の配置を制御するのに、flexbox化しています。

HookedHeader.js
import React from "react";
import { Header, Heading, View, Flex } from '@adobe/react-spectrum';
import ColorSchemeSwitch from './ColorSchemeSwitch';

const HookedHeader = (props) => {
  return (
    <Header>
      {/* 背景色 */}
      <View backgroundColor="red-500">
        {/* 文言を中央寄せ */}
        <Flex direction="row" justifyContent="center">
          {/* h2と同等 */}
          <Heading level="2" >
            <font color="white">{props.text}</font>
          </Heading>
          <View position="absolute" right="size-0">
            <ColorSchemeSwitch
              switch={props.switch}
              currentColorScheme={props.currentColorScheme}>
            </ColorSchemeSwitch>
          </View>
        </Flex>
      </View>
    </Header>
  );
};

export default HookedHeader;

いちいち、classNameをつけて、cssを開いてflexにして、うまくいかないから、divを追加してみて、、、なんてことをしなくても、<Flex>タグや<Grid>タグを並べていけばいいんです!しかも、設定すべき項目はパラメータ化してあるので、directionやjustifyContent、gapなど細かい調整もjsファイルだけでできてしまいます。

プロパティの統一感

マニュアルの見やすさからくるものかもしれませんが、コンポーネントに指定できるプロパティが、

  • 固有のプロパティ
  • Event
  • Layout
  • Spacing
  • Sizing
  • Background
  • Borders
  • Positioning
  • Accessibility
  • Advanced

などに体系だって定義されています。コンポーネントごとに、利用できる分類は異なりますが、分類の中では基本的に同じ内容のようです(Eventは違う)。すぐに覚えて使えるようになれました。

また、地味にいいのが、DimensionValueです。sizeなどを12pxとか1remとか指定するのではなく、Spectrumで定義されたDimensionValueを使って指定します。具体的には、size-0size-10size-25・・・といった値です。これらを使っていれば、Spectrum側で、デバイスに合わせた調整などを行ってくれます。指定する側もpxを使うかremを使うかemを使うかいちいち悩まなくて済みますね。同様に色指定もColorValueがあるので簡単に指定できます。

文字色の指定は?

使っていて唯一わからなかったのは、文字色を自分で指定したいときです。ここはこの色にしたい!っていう箇所はやっぱり出てくるのではないかと思います。バックグラウンドカラーを変更するプロパティはあったのですが、文字色を変えるプロパティはぱっと見、見当たりませんでした。classを指定してCSSで書くというのは最後の手段のようなので、一旦、<font>タグで色を指定したのですが、これだと、ColorValueも使えませんし、正しいやり方のようには思いません。そもそも、意味なく文字色だけ変えるなってことですかね。

おわりに

Ract Spectrumはリリースされたばかりということで、まだ情報が少ないですが、公式のマニュアルが充実しているので特段困ることはなかったです。React歴数時間、CSSフレームワーク等はBootstrapを眺めたことがある程度ですが、非常に簡単でした。CSSを書かなくてよくて、且つ、タグも自然と標準化された書き方になっていくので、ストレスなく書けました。人気がでることに期待です!

参考

React Spectrum
freecodecamp:Build a movie search app using React hooks

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Build a movie search app using React hooksをReact Spectrumで作ってみた

React Spectrumがすごい

React Spectrumがすごそうということで、実際に使って開発してみました。React Spectrum自体の設計のクオリティの高さは、Adobe製デザインシステム「React Spectrum」がすごいので紹介したいの記事が詳しいです。内部の作りはさておき、開発者視点で良かったと感じた点は以下です。

  • Adobeが開発しているので信頼度が高い
  • Componentのプロパティの統一感があり、わかりやすく使いやすい
  • Flex/Gridが簡単にかける
    • 基本的にタグで書いていく思想?でCSSを書かなくても大体いける。Flex/Gridもタグがある。
    • classNameのプロパティは「UNSAFE_className」になっており、説明にも「最後の手段」と。

Build a movie search app using React hooksについて

2020年のフロントエンドマスターになりたければこの9プロジェクトを作れで1つめに紹介されていたプロジェクトです。元のサイトはこちら。フロントエンドマスターを目指した方なら作ってみたことがあるはず!React Spectrumを試すちょうどよい題材として活用させてもらいました。

React Spectrumを使ってカスタマイズ

完成したイメージ

こんな感じです。
(デザインがダサいと感じたら、それはReact Spectrumではなく私のセンスの問題です。)
image.png

スマホにするとこんな感じです。
文字サイズとかいい感じに変わってくれるところとか素晴らしいです。
image.png

ダークモードにするとこんな感じです。
本来はOSの設定に応じて切り替わりますが、Reactの勉強がてら切り替えボタンをつけてみました。文字色もうまく変えてくれています。
image.png

キャプチャではわかりませんが、検索APIを呼び出している間は、ProgressCircleがくるくる回ります。
image.png

中身の説明

リセットCSS

リセットCSSがなくても大丈夫かもしれませんが、hタグを使った時の余白が気に入らなかったので、リセットCSSを入れました。使用したのはmodern-css-resetです。はじめは、destyle.cssを使ってみたのですが、hタグで見た目の違いが表現できなくなってしまったので、適度にリセットしてくれるmodern-css-resetにしました。導入は簡単で、yarn add modern-css-resetして、index.jsにimport 'modern-css-reset';するだけです。

Flex,Gridで簡単にレイアウト

App.jsでレイアウトを作っていきます。レイアウトを実現するのにCSSを自分で書く必要はなく、用意されたタグだけで十分でした。
全体の構成は

  • ヘッダー
  • 検索部分
  • ガイダンス部分
  • コンテンツ部分
  • フッター

です。
全体をFlexboxで並べて、それぞれのレイアウトの調整にさらにFlexboxやGridを使いました。コンテンツ部分は固定サイズで配置したかったのでGridにしています。React Spectrumのイメージがつかめるように、import部分とreturnのレイアウト部分のみ抜粋します。

App.js
import React, { useReducer, useEffect } from 'react';
import './App.css';
import HookedHeader from "./HookedHeader";
import Movie from "./Movie";
import Search from "./Search";
import { Provider, defaultTheme, Flex, View, Text, Grid, repeat, Footer, ProgressCircle, Link } from '@adobe/react-spectrum';


const App = () => {

  return (
    <Provider theme={defaultTheme}
      colorScheme={colorSchemeStete.colorScheme}>

      {/* 全体をflexbox化する */}
      {/* ダークモードでも白地が見えないように画面の高さ分をコンテンツ領域で確保する */}
      <Flex direction="column" gap="size-100" minHeight="100vh">

        {/* ヘッダー部 */}
        <HookedHeader
          text="HOOKED"
          switch={switchColorScheme}
          currentColorScheme={colorSchemeStete.colorScheme} />

        {/* 検索部 中央寄せにする*/}
        <Flex direction="row" justifyContent="center">
          <Search search={searchMethod} />
        </Flex>

        {/* ガイダンス部 中央寄せにする */}
        <Flex direction="row" justifyContent="center">
          <Text>Sharing a fwe of our favourite movies</Text>
        </Flex>

        {/* コンテンツ部 Grid化する */}
        <Grid
          columns={repeat('auto-fit', 'size-2400')}
          autoRows="size-2400"
          justifyContent="center"
          gap="size-200">
          {loading && !errorMessage ? (
            // ローディング表示
            <View
              // 上下中央表示
              alignSelf="center"
              // 左右中央表示(gridのrpeatを無視)
              justifySelf="center">
              <ProgressCircle aria-label="Loading…" isIndeterminate />
            </View>
          ) : errorMessage ? (
            // エラーメッセージ表示
            <View
              // 左右中央表示(gridのrpeatを無視)
              justifySelf="center">
              <div className="errorMessage">{errorMessage}</div>
            </View>
          ) : (
                // コンテンツ表示
                movies.map((movie, index) => (
                  <View
                    backgroundColor="gray-200">
                    <Movie key={`${index}-${movie.Title}`} movie={movie} />
                  </View>
                ))
              )}


        </Grid>

        {/* フッター リンクをつけてみる*/}
        <Footer alignSelf="center">
          {/* Linkを使うとダークモードでも見やすく色が変わる */}
          <Link>
            <a href="https://www.freecodecamp.org/news/how-to-build-a-movie-search-app-using-react-hooks-24eb72ddfaf7/" target="_blank">
              freeCodeCampHow to build a movie search app using React Hooks
          </a>
          </Link>
          <br />
          <Link>
            <a href="https://react-spectrum.adobe.com/react-spectrum/index.html" target="_blank">
              React SpectrumA React implementation of Spectrum, Adobes design system.
          </a>
          </Link>
        </Footer>
      </Flex>
    </Provider>
  );
};

export default App;

ちなみにヘッダーコンポーネントも中身の配置を制御するのに、flexbox化しています。

HookedHeader.js
import React from "react";
import { Header, Heading, View, Flex } from '@adobe/react-spectrum';
import ColorSchemeSwitch from './ColorSchemeSwitch';

const HookedHeader = (props) => {
  return (
    <Header>
      {/* 背景色 */}
      <View backgroundColor="red-500">
        {/* 文言を中央寄せ */}
        <Flex direction="row" justifyContent="center">
          {/* h2と同等 */}
          <Heading level="2" >
            <font color="white">{props.text}</font>
          </Heading>
          <View position="absolute" right="size-0">
            <ColorSchemeSwitch
              switch={props.switch}
              currentColorScheme={props.currentColorScheme}>
            </ColorSchemeSwitch>
          </View>
        </Flex>
      </View>
    </Header>
  );
};

export default HookedHeader;

いちいち、classNameをつけて、cssを開いてflexにして、うまくいかないから、divを追加してみて、、、なんてことをしなくても、<Flex>タグや<Grid>タグを並べていけばいいんです!しかも、設定すべき項目はパラメータ化してあるので、directionやjustifyContent、gapなど細かい調整もjsファイルだけでできてしまいます。

プロパティの統一感

マニュアルの見やすさからくるものかもしれませんが、コンポーネントに指定できるプロパティが、

  • 固有のプロパティ
  • Event
  • Layout
  • Spacing
  • Sizing
  • Background
  • Borders
  • Positioning
  • Accessibility
  • Advanced

になどに体系だって定義されています。コンポーネントごとに、利用できる分類は異なりますが、分類の中では基本的に同じ内容のようです(Eventは違う)。すぐに覚えて使えるようになれました。

また、地味にいいのが、DimensionValueです。sizeなどを12pxとか1remとか指定するのではなく、Spectrumで定義されたDimensionValueを使って指定します。具体的には、size-0size-10size-25・・・といった値です。これらを使っていれば、Spectrum側で、デバイスに合わせた調整などを行ってくれます。指定する側もpxを使うかremを使うかemを使うかいちいち悩まなくて済みますね。同様に色指定もColorValueがあるので簡単に指定できます。

文字色の指定は?

使っていて唯一わからなかったのは、文字色を自分で指定したいときです。ここはこの色にしたい!っていう箇所はやっぱり出てくるのではないかと思います。バックグラウンドカラーを変更するプロパティはあったのですが、文字色を変えるプロパティはぱっと見、見当たりませんでした。classを指定してCSSで書くというのは最後の手段のようなので、一旦、<font>タグで色を指定したのですが、これだと、ColorValueも使えませんし、正しいやり方のようには思いません。そもそも、意味なく文字色だけ変えるなってことですかね。

おわりに

Ract Spectrumはリリースされたばかりということで、まだ情報が少ないですが、公式のマニュアルが充実しているので特段困ることはなかったです。React歴数時間、CSSフレームワーク等はBootstrapを眺めたことがある程度ですが、非常に簡単でした。CSSを書かなくてよくて、且つ、タグも自然と標準化された書き方になっていくので、ストレスなく書けました。人気がでることに期待です!

参考

React Spectrum
freecodecamp:Build a movie search app using React hooks

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Next.js】Google App Engineに静的ファイルをリリースした

Next.jsのビルドした静的ファイルをGoogle App Engineにリリースする際に少し詰まったので備忘録を残します。

Next.jsで使用してるAPI等は以下を参考にしました。

https://qiita.com/matamatanot/items/1735984f40540b8bdf91

※まだ検証中の部分もあるためyamlファイルに不要なコードもあるかもしれません。

GAEにリリースするために必要なyamlファイル

app.yaml
runtime: nodejs12

handlers:
  - url: /_next/static
    static_dir: .next/static
    secure: always

  - url: /favicon\.ico
    static_files: favicon.ico
    upload: favicon\.ico

  - url: /static
    static_dir: static
    secure: always

  - url: /.*
    secure: always
    redirect_http_response_code: 301
    script: auto

env_variables:
  HOST: '0.0.0.0'

参考:https://cloud.google.com/appengine/docs/standard/nodejs/config/appref?hl=ja

GAEへリリースする際のコマンド

静的ファイルをビルドしてデプロイするだけですね。

※プロジェクトの作成やログイン等は省略しております。

$ yarn build
$ gcloud app deploy app.yaml

参考:https://cloud.google.com/appengine/docs/flexible/nodejs/testing-and-deploying-your-app?hl=ja

GKEでリリースしたことがあるのですが、それと比べると圧倒的に楽でした…笑

参考:https://qiita.com/arthur_foreign/items/9007695c5ff02dd493cf

他にもGAEのサービスを運用している場合はdispatch.yamlをデプロイする

Next.jsがdefaultのサービスなら特に考えなくてもよいのですが、他にもサービスがある場合はdispatch.yamlを設定する必要があります。

※ここも地味に詰まりました。

dispatch.yaml
dispatch:
  - url: "*/hogefuga"
    service: hoge-static
  # ビルドしたCSSやJSを読み込む必要があるため
  - url: "*/_next/*"
    service: hoge-static
  # static配下の画像を読み込む必要があるため
  - url: "*/static/images/*"
    service: hoge-static

ルーティングによってサービスを切り替える場合は、少し面倒ですがCSSやJSや画像ファイル等もdispatch.yamlで制御してあげる必要があるようでした。

ブラウザのコンソール上に出てたWarning

ブラウザのコンソール上に出てくれていたWarningは以下でした。

Resource interpreted as Stylesheet but transferred with MIME type text/html: "https://hogefuga.come/_next/static/css/styles.xxxxxxxx.chunk.css".

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む