sagara.inkITエンジニアのまとめノート

Reactフロントエンド開発ことはじめ

近年のクライアントサイドJavaScriptはもはや単なるスクリプトではなく、より構造化されたプログラミングが求められるようになっています。 フロントエンドのフレームワークが作られていますが、今回はその一つであるReactについてまとめます。

Reactを導入するメリット

ランディングページやキャンペーンサイトなどの少ないWebサイトと異なり、 SNSやWebサービスなどのWebアプリケーションでは、大量の情報を扱ったり、多様な条件による表示の出し分けなどが必要になります。 Reactは次のようなメリットがあります。

  • コンポーネント化して再利用
  • XMLライクで視覚的なJSX記法
  • データの「状態」と「表示」を分離

ひとつつずつ解説します。

コンポーネント化による再利用

一般的に、Webアプリはボタン、メニュー、テキストなど多くの要素で構成されます。 このそれぞれの要素をそれぞれ組み立て可能な部品に見立てたものをコンポーネントと考えます。
例えば、誰かのコメントの投稿は、その人のユーザ名、コメント本文、投稿日時などといった要素をひとまとまりと考えることができます。 これをコンポーネントと呼ばれる部品やパーツのようなものに見立て、繰り返し利用するのです。

Comment.js
import React from 'react'

//コメント1件に相当する、Commentコンポーネント
const Comment = (props) => {
  return (
    <li className="article">
      <h3>{props.userName}</h3>
      <p>
        {props.comment}
        <span>{props.postedTime}</span>
      </p>
    </li>
  )
}

export default Comment

上記のCommentがコンポーネントを関数コンポーネントとして書いたものです。

XMLライクで視覚的なJSX記法

コンポーネントで要素を表現する際、JSXという記法を用います。 XMLタグのように表現することができ、コンポーネントの構造を視覚的に記述することができます。 JavaScriptによるレンダリングの制御もできます。

Article.js
import React from 'react'
import Title from './components/Title'
import Body from './components/Body'
import Comment from './components/Comment'

const Article = () => {
  const comments = []; //コメントの配列
  return (
    <div className="article">
      <Title />
      <Body />
      { comments.legth > 0 ?
        comments.map((e, index) => {
          <Comment key={index} data={e} />
        }) : '' }
    </li>
  )
}

export default Article

XMLライクで視覚的なJSX記法

ブラウザ上でデータを扱う場合、そのデータと表示の整合性を保つことが必要ですが、 そのために、常に気をかけなければなりません。Reactを利用すれば、データの状態変化を検知して自動的に画面レンダリングし直してくれる上、 なるべく必要なDOM要素だけを変更するので、処理が早くキビキビと見えます。

Article.js
import React, {useState} from 'react'

const LikeButton = () => {

  //likeという変数の状態を保持(初期値0)
  //setLike()メソッドでlikeの値を更新する
  const [like, setLike] = useState(0)

  //ボタンを押したときのイベント
  const handleLikeButton = () => {
    setLike(like + 1) //likeの値に1を足した値で更新
  }

  return (
      <button onClick={handleLikeButton}>{like}</button>
  )
}

export default LikeButton

useState()はReact16.8から導入されたReact Hooksによる状態管理のためのメソッドです。

React開発環境構築

Reactの導入

新規プロジェクト用のディレクトリを作成し、npmパッケージをインストールします。 index.htmlと、Reactを記述するindex.jsを作成します。 index.html内で読み込むbundle.jsは後述するWebpackでindex.jsから変換したものを指定しています。

console
npm init -f
npm install react react-dom
index.js
import React from 'react'
import ReactDOM from 'react-dom'

const Welcome = props => {
  return <h1>Hello, {props.name}</h1>
}

ReactDOM.render(
  <Welcome name="Johnny" />,
  document.getElementById('root')
)
index.html
...
<body>
  <div id="root"></div>
  <script src="bundle.js"></script>
</body>

Webpackの導入

Reactではコンポーネントという単位のJSファイルと多数のパッケージを扱いますが、これを組み合わせてアプリケーションとして動作させます。 また、他のパッケージとこれらの依存関係を解決して、たった一枚のJSファイルにまとめてくれる(ビルドしてくれる)npmパッケージがWebpackです。 モジュールバンドルツールと呼ぶらしい。

console
npm install -g webpack webpack-cli
webpack.config.js
module.exports = {
  entry: './index.js',
  output: {
    filename: 'bundle.js'
  }
}

Babelの導入

また、ビルドする際にBabelを適用します。ReactやTypeScriptなどの拡張JSをブラウザが認識できるJavaScriptに変換させるようにします。 IEなどのブラウザへの後方互換性をもたせます。

console
npm install --save-dev babel-loader babel-core
npm install --save-dev @babel/core @babel/preset-env @babel/preset-react

rulesの中に、変換対象のファイル(test)、使うローダー(loader)、オプション(options)を指定します。 以下の場合は、.js拡張子のファイルについて、Babelでes2015とreactを変換するということになります。

webpack.config.js
module.exports = {
  mode : 'development',
  entry: './index.js',
  output: {
    publicPath: '/dist/',
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /.js$/,
        loader: 'babel-loader',
        options: {
          presets: [
            '@babel/preset-env',
            '@babel/preset-react',
          ],
        }
      }
    ]
  }
}

webpackコマンドでReact+ES2015で記述したindex.jsから、JavaScriptへの変換したdist/bundle.jsが生成できます。

console
webpack

webpack-dev-serverの導入

Reactアプリケーションをローカルのブラウザ上で確認しながら開発できます。
変更を検知してリロードしてくれるなど便利ですが、更新スピードが早くバージョンアップによる破壊的な変更も多いです。 ドキュメントを参考に設定してください。 以下はバージョン3系による設定です。

console
npm install —-save-dev webpack-dev-server
webpack.config.js
module.exports = {
  module: {
    ...
  },
  devServer: {
    contentBase: __dirname,
    compress: true,
    port: 3333,
    inline: true,
    watchOptions: {
      ignored: /node_modules/
    }
  },
  ...
}
console
webpack serve --open

ローカルサーバが起動し、ブラウザが立ち上がります(--openオプション)。

React関数コンポーネント開発

コンポーネント間のやり取り

一般的にコンポーネントには親子関係があります。 より大きいコンポーネントが親、その親に含まれるコンポーネントが子となります。 コンポーネントはそのコンポーネントが保持すべき状態(state)を持っており、 親子間でその状態をあらわすデータをpropsまたはイベントを通して受け渡します。

親とstate

コンポーネントは状態(state)を保てます。

Parent.js
import React, {useState} from 'react'
import Child from './Child'

const Parent = () => {
  const [state, setState] = useState({
      firstName: "John",
      lastName: "Smith"
  })

  return <Child lastName={state.lastName} />
}

親から子へ値(props)を渡す

親コンポーネントから子コンポーネントへは、propsを通して値を渡します。

Child.js
import React from 'react'

const Child = props => {
  return (
    <div>Hello, Anthony {props.lastName}.</div>
  )
}
export default Child

子から親へイベントを渡す

ボタンが押されたことを通知するときや、テキスト入力があった場合など、 子コンポーネントから親コンポートへは、イベントを通して値を渡します。 ここでは、子コンポーネントのテキストを入力したら、親コンポーネントのstateを変更するようなイベントを作成しています。

Child.js
import React from 'react'

const Child = props => {
  //入力に変更があった場合のイベント
  function handleChange(e) {
    //親からpropsとして渡されたイベントを呼ぶ
    props.onChangeFirstName(e.target.value);
  }

  return (
    <div>
      <div>Hello, {props.name.firstName} {props.name.lastName}</div>
      <input
        type="text"
        placeholder="firstName"
        onChange={handleChange}
      />
    </div>
  );
}
export default Child
Parent.js
import React from 'react'
import Child from './Child'

const Parent = () => {
  const [state, setState] = useState({
      firstName: "John",
      lastName: "Smith"
  })
  const handleChangeFirstName = name => {
    setState({
      ...state,
      firstName: name
    })
  }
  return (
    <Child
      name={state}
      onChangeFirstName={handleChangeFirstName} //onChangeFirstNameイベントハンドラを渡す
    />
  )
}

ステップアップ

非同期通信(axios)とasync/await

非同期通信を行うパッケージにaxiosがあります。

console
npm install --save-dev axios

また、Reactで非同期通信を使用する場合は以下のパッケージが必要になります。

console
npm install --save-dev @babel/polyfill

また、Reactで非同期通信を使用する場合は以下のパッケージが必要になります。

webpack.config.js
module.exports = {
    mode : 'development',
    entry: ['@babel/polyfill', './src/index.js'], // @babel/polyfillを追記
...

Reactに限らずJavaScriptのプロセスでは、非同期通信の結果が来るのを待たずに次の処理に進んでしまいます。 ところが非同期通信の結果を待って処理を進めたい場合は、async/awaitを使うことで平易に書けます。

async
この処理が非同期の関数であることを表す。
"async function SomeFunc() {...}" や "const someFunc = async () => {...}" などのように関数定義の前に書く。
await
この処理の結果が返ってくるまでは次の処理に進まない。なお、awaitさせる処理を含む関数にはasyncをつける。
index.js
import axios from 'axios'

async function componentDidMount {
  const response = await axios.get('https://example.com/')
    .then(response => {
      return response.data;
    });
  return response;
}

Styled Componentの導入

React自体にはCSSなどのスタイルは持たないので、利用者が独自にCSSで装飾を加える必要があります。 Reactにおけるスタイリングの方法としてStyled Componentsというものがあり、コンポーネントのjsファイル内にCSS(Sassも可)を書くことができます。 これにより、CSSも含めたコンポーネントを作成できるので、コンポーネントの依存性が少なくなります。

Reduxの導入

フロントエンドで扱う情報量が多くなってくると、その状態を管理するのが煩雑になってきます。 ReduxではデータをStoreと呼ばれる場所に一元的においておくことで、データの変更の流れの見通しを良くすることができます。 Reduxについては別記事で扱います。

Reduxでフロントで扱うデータを一元管理する

create-react-appについて

Reactの環境構築はやや手順が多く面倒ですが、手っ取り早くReactの環境を始めたい場合は、 create-react-appパッケージを使うとすぐに導入できます。

console
npm install -g create-react-app
create-react-app my_app