Reactフロントエンド開発ことはじめ
近年のクライアントサイドJavaScriptはもはや単なるスクリプトではなく、より構造化されたプログラミングが求められるようになっています。 フロントエンドのフレームワークが作られていますが、今回はその一つであるReactについてまとめます。
Reactを導入するメリット
ランディングページやキャンペーンサイトなどの少ないWebサイトと異なり、 SNSやWebサービスなどのWebアプリケーションでは、大量の情報を扱ったり、多様な条件による表示の出し分けなどが必要になります。 Reactは次のようなメリットがあります。
- コンポーネント化して再利用
- XMLライクで視覚的なJSX記法
- データの「状態」と「表示」を分離
ひとつつずつ解説します。
コンポーネント化による再利用
一般的に、Webアプリはボタン、メニュー、テキストなど多くの要素で構成されます。 このそれぞれの要素をそれぞれ組み立て可能な部品に見立てたものをコンポーネントと考えます。
例えば、誰かのコメントの投稿は、その人のユーザ名、コメント本文、投稿日時などといった要素をひとまとまりと考えることができます。 これをコンポーネントと呼ばれる部品やパーツのようなものに見立て、繰り返し利用するのです。
1import React from 'react'
2
3//コメント1件に相当する、Commentコンポーネント
4const Comment = (props) => {
5  return (
6    <li className="article">
7      <h3>{props.userName}</h3>
8      <p>
9        {props.comment}
10        <span>{props.postedTime}</span>
11      </p>
12    </li>
13  )
14}
15
16export default Comment上記のCommentがコンポーネントを関数コンポーネントとして書いたものです。
XMLライクで視覚的なJSX記法
1import React from 'react'
2import Title from './components/Title'
3import Body from './components/Body'
4import Comment from './components/Comment'
5
6const Article = () => {
7  const comments = []; //コメントの配列
8  return (
9    <div className="article">
10      <Title />
11      <Body />
12      { comments.legth > 0 ?
13        comments.map((e, index) => {
14          <Comment key={index} data={e} />
15        }) : '' }
16    </li>
17  )
18}
19
20export default ArticleXMLライクで視覚的なJSX記法
ブラウザ上でデータを扱う場合、そのデータと表示の整合性を保つことが必要ですが、 そのために、常に気をかけなければなりません。Reactを利用すれば、データの状態変化を検知して自動的に画面レンダリングし直してくれる上、 なるべく必要なDOM要素だけを変更するので、処理が早くキビキビと見えます。
1import React, {useState} from 'react'
2
3const LikeButton = () => {
4
5  //likeという変数の状態を保持(初期値0)
6  //setLike()メソッドでlikeの値を更新する
7  const [like, setLike] = useState(0)
8
9  //ボタンを押したときのイベント
10  const handleLikeButton = () => {
11    setLike(like + 1) //likeの値に1を足した値で更新
12  }
13
14  return (
15      <button onClick={handleLikeButton}>{like}</button>
16  )
17}
18
19export default LikeButtonuseState()はReact16.8から導入されたReact Hooksによる状態管理のためのメソッドです。
React開発環境構築
Reactの導入
新規プロジェクト用のディレクトリを作成し、npmパッケージをインストールします。 index.htmlと、Reactを記述するindex.jsを作成します。 index.html内で読み込むbundle.jsは後述するWebpackでindex.jsから変換したものを指定しています。
1npm init -f
2npm install react react-dom1import React from 'react'
2import ReactDOM from 'react-dom'
3
4const Welcome = props => {
5  return <h1>Hello, {props.name}</h1>
6}
7
8ReactDOM.render(
9  <Welcome name="Johnny" />,
10  document.getElementById('root')
11)1...
2<body>
3  <div id="root"></div>
4  <script src="bundle.js"></script>
5</body>Webpackの導入
Reactではコンポーネントという単位のJSファイルと多数のパッケージを扱いますが、これを組み合わせてアプリケーションとして動作させます。 また、他のパッケージとこれらの依存関係を解決して、たった一枚のJSファイルにまとめてくれる(ビルドしてくれる)npmパッケージがWebpackです。 モジュールバンドルツールと呼ぶらしい。
1npm install -g webpack webpack-cli1module.exports = {
2  entry: './index.js',
3  output: {
4    filename: 'bundle.js'
5  }
6}Babelの導入
また、ビルドする際にBabelを適用します。ReactやTypeScriptなどの拡張JSをブラウザが認識できるJavaScriptに変換させるようにします。 IEなどのブラウザへの後方互換性をもたせます。
1npm install --save-dev babel-loader babel-core
2npm install --save-dev @babel/core @babel/preset-env @babel/preset-reactrulesの中に、変換対象のファイル(test)、使うローダー(loader)、オプション(options)を指定します。 以下の場合は、.js拡張子のファイルについて、Babelでes2015とreactを変換するということになります。
1module.exports = {
2  mode : 'development',
3  entry: './index.js',
4  output: {
5    publicPath: '/dist/',
6    filename: 'bundle.js'
7  },
8  module: {
9    rules: [
10      {
11        test: /.js$/,
12        loader: 'babel-loader',
13        options: {
14          presets: [
15            '@babel/preset-env',
16            '@babel/preset-react',
17          ],
18        }
19      }
20    ]
21  }
22}webpackコマンドでReact+ES2015で記述したindex.jsから、JavaScriptへの変換したdist/bundle.jsが生成できます。
webpack
webpack-dev-serverの導入
Reactアプリケーションをローカルのブラウザ上で確認しながら開発できます。
変更を検知してリロードしてくれるなど便利ですが、更新スピードが早くバージョンアップによる破壊的な変更も多いです。 ドキュメントを参考に設定してください。 以下はバージョン3系による設定です。
npm install —-save-dev webpack-dev-server
1module.exports = {
2  module: {
3    ...
4  },
5  devServer: {
6    contentBase: __dirname,
7    compress: true,
8    port: 3333,
9    inline: true,
10    watchOptions: {
11      ignored: /node_modules/
12    }
13  },
14  ...
15}webpack serve --open
ローカルサーバが起動し、ブラウザが立ち上がります(--openオプション)。
React関数コンポーネント開発
コンポーネント間のやり取り
一般的にコンポーネントには親子関係があります。 より大きいコンポーネントが親、その親に含まれるコンポーネントが子となります。 コンポーネントはそのコンポーネントが保持すべき状態(state)を持っており、 親子間でその状態をあらわすデータをpropsまたはイベントを通して受け渡します。
親とstate
コンポーネントは状態(state)を保てます。
1import React, {useState} from 'react'
2import Child from './Child'
3
4const Parent = () => {
5  const [state, setState] = useState({
6      firstName: "John",
7      lastName: "Smith"
8  })
9
10  return <Child lastName={state.lastName} />
11}親から子へ値(props)を渡す
親コンポーネントから子コンポーネントへは、propsを通して値を渡します。
1import React from 'react'
2
3const Child = props => {
4  return (
5    <div>Hello, Anthony {props.lastName}.</div>
6  )
7}
8export default Child子から親へイベントを渡す
ボタンが押されたことを通知するときや、テキスト入力があった場合など、 子コンポーネントから親コンポートへは、イベントを通して値を渡します。 ここでは、子コンポーネントのテキストを入力したら、親コンポーネントのstateを変更するようなイベントを作成しています。
1import React from 'react'
2
3const Child = props => {
4  //入力に変更があった場合のイベント
5  function handleChange(e) {
6    //親からpropsとして渡されたイベントを呼ぶ
7    props.onChangeFirstName(e.target.value);
8  }
9
10  return (
11    <div>
12      <div>Hello, {props.name.firstName} {props.name.lastName}</div>
13      <input
14        type="text"
15        placeholder="firstName"
16        onChange={handleChange}
17      />
18    </div>
19  );
20}
21export default Child1import React from 'react'
2import Child from './Child'
3
4const Parent = () => {
5  const [state, setState] = useState({
6      firstName: "John",
7      lastName: "Smith"
8  })
9  const handleChangeFirstName = name => {
10    setState({
11      ...state,
12      firstName: name
13    })
14  }
15  return (
16    <Child
17      name={state}
18      onChangeFirstName={handleChangeFirstName} //onChangeFirstNameイベントハンドラを渡す
19    />
20  )
21}ステップアップ
非同期通信(axios)とasync/await
非同期通信を行うパッケージにaxiosがあります。
npm install --save-dev axios
また、Reactで非同期通信を使用する場合は以下のパッケージが必要になります。
npm install --save-dev @babel/polyfill
1`module.exports = {
2    mode : 'development',
3    entry: ['@babel/polyfill', './src/index.js'], // @babel/polyfillを追記
4...Reactに限らずJavaScriptのプロセスでは、非同期通信の結果が来るのを待たずに次の処理に進んでしまいます。 ところが非同期通信の結果を待って処理を進めたい場合は、async/awaitを使うことで平易に書けます。
async
この処理が非同期の関数であることを表す。
"async function SomeFunc() {...}" や "const someFunc = async () => {...}"
などのように関数定義の前に書く。
await
この処理の結果が返ってくるまでは次の処理に進まない。なお、awaitさせる処理を含む関数にはasyncをつける。
1import axios from 'axios'
2
3async function componentDidMount {
4  const response = await axios.get('https://example.com/')
5    .then(response => {
6      return response.data;
7    });
8  return response;
9}Styled Componentの導入
React自体にはCSSなどのスタイルは持たないので、利用者が独自にCSSで装飾を加える必要があります。 Reactにおけるスタイリングの方法としてStyled Componentsというものがあり、コンポーネントのjsファイル内にCSS(Sassも可)を書くことができます。 これにより、CSSも含めたコンポーネントを作成できるので、コンポーネントの依存性が少なくなります。
Reduxの導入
フロントエンドで扱う情報量が多くなってくると、その状態を管理するのが煩雑になってきます。 ReduxではデータをStoreと呼ばれる場所に一元的においておくことで、データの変更の流れの見通しを良くすることができます。 Reduxについては別記事で扱います。
create-react-appについて
Reactの環境構築はやや手順が多く面倒ですが、手っ取り早くReactの環境を始めたい場合は、 create-react-appパッケージを使うとすぐに導入できます。
1npm install -g create-react-app
2create-react-app my_app