【React】React基礎編(コンポーネント編)

Reactは、複数のコンポーネントを組み合わせて作成します。

そのためコンポーネントの知識は必要不可欠です。
この記事では、コンポーネントの作成方法から、stateの更新タイミングを中心に、サンプルを紹介しつつ初めての方向けに作成致しました。

はじめに

コンポーネントはページを構成する部品の事で、データや処理を1つの関数(又はクラス)にしたものです。
部品」なので、色々な画面で使えるように共通部品として構築すると、開発効率が上がります。
 ※多数作成されるコンポーネントの管理は大変ですが・・・。

Reactは、このコンポーネントを複数組み合わせる事で、ページを構成していきます。
他言語のオブジェクト指向のイメージですね。

今回は、Reactにおけるコンポーネントについて記載致します。

コンポーネントついて

React 16.8より、フック(hook)が主流になり、今までのクラスコンポーネントは非推奨となりました。
そのため、サンプルは全て関数コンポーネントで紹介したいと思います。

下記は、最小サイズの関数コンポーネントになります。
関数名は大文字から始め(Sample)、戻り値にJSX「<div>サンプルページ</div>」を返しています。

function Sample() {
    return <div>サンプルページ</div>;
}
export default Sample;

このコンポーネントを「App.js」に組み込みます。

import Sample from './Sample'

function App() {
  return <Sample/>;
}
export default App;

修正後、画面で表示すると「サンプルページ」と表示されます。
簡単ですが、Reactでの画面制作第一歩です!

開発者ツールで確認すると、<div id=”root”>直下に「<div>サンプルページ</div>」が表示されているのが確認できます。
この部分が、今回作成したコンポーネントになります。

仮想DOM

Reactは仮想DOMを採用しており、アプリで何かしらの変化が発生した場合、仮想DOMを操作します。
そして、ユーザが参照しているDOMへは、変更より下部のDOMのみが反映されます。

この仕組みを採用する事で、従来のDOM操作(jquery等)に比べ、Reactは描画処理のスピードが速く、描画コストが最小で済むようなっています。

アプリで何かしらの変化」に関しては、次より紹介するPropsとStateを使って管理しています。

Props

親コンポーネントから子コンポーネントへ情報を伝達する手段として、Propsを利用します。

サンプルとして下記構成の画面を作成したいと思います。

各コンポーネントのファイル名と「Props」の内容

関連性ファイル名Props
親コンポーネントApp.js
子コンポーネントChild.jsid, name, code
孫コンポーネントGrandchild.jscode, title

各ファイルのサンプル

import './App.css';
import Child from './Child'

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <Child id={1} name='コンポーネントの世界へようこそ' code='001'  />
      </header>
    </div>
  );
}
export default App;
import Grandchild from './Grandchild'

function Child({id, name, code}) {
    return (
        <div id={id}>
            <div>{name}:</div>
            <Grandchild code={code} title={'JSXの世界へようこそ'} />
            <Grandchild code={code} title={'改行できるのか?'} />
        </div>
    );
}
export default Child;
function Grandchild({code, title}){
    return <p data-code={code}>{title}</p>;
}
export default Grandchild;

親から子へ値の受け渡しをする場合は、下記の通り子コンポーネントの要素として渡します。
文字列のみ「{}」が省できます。

<Child id={1} name='コンポーネントの世界へようこそ' code='001'  />

子コンポーネントは、Propsの受け取りを分割代入の形で受け取ります。

function Child({id, name, code})

Propsの数が多い時は、一旦「props」で受け取って、別途分割代入で取得しても問題ありません。

function Child(props) {
    const {
        id,
        name,
        code
    } = props;

Propsで引き渡せるのはリテラル値だけではなく、オブジェクト、配列、関数等も子コンポーネントへ渡す事が可能です。

では、画面を確認してみしょう。

想定通りの構成で表示されました。
また、開発者ツールよりDOMの状態を確認します。

親から子へ、子から孫へ値の引き渡しが行われているのが確認できます。

State

コンポーネントは関数なので、変数等の値を保存しておく事が出来ません。
アプリで何らかの変化が発生し再描画となった時に、関数内部で使用していた変数の値は全てクリアされてしまいます。

再描画の度に変数の値がクリアされれば、画面なんて思うように作成できません。
そのため、関数内部で使用してた値を保存するのが「State」になります。

Stateは下記のように記述します。

const [stateの値, stateの更新関数] = useState(初期値);

useStateを利用するには「react」よりuseStateをインポートする必要があります。

import { useState } from 'react';

では、先ほどのjsファイルに組み込みつつ、少し修正を行います。
また、各コンポーネントにconsole.logを仕込み、各コンポーネントの更新と更新順番を確認したいと思います。

先ほどのサンプルに修正を行います。
「App.js」「Child.js」「Grandchild.js」のそれぞれに、「useState」を追加しました。

また、自コンポーネントのStateを、ボタンクリックでカウントアップできるようにし、下位コンポーネントは上位コンポーネントのStateもカウントアップできるようにしています。
 →孫コンポーネントは、親・子もカウントアップ可能

import { useState } from 'react';
import './App.css';
import Child from './Child'

function App() {
  const [pCount, setPCount ] = useState(0);
  console.log('App.js更新');
  return (
    <div className="App">
      <header className="App-header">
        <div>親コンポーネント</div>
        <div>
          <span>親Count:{pCount}</span>
          <input
            type='button'
            value={'親コンポーネント-親カウントアップ'}
            onClick={()=>setPCount(pre => ++pre)} />
        </div>
        <Child name='子コンポーネント' pCount={pCount} setPCount={setPCount}  />
      </header>
    </div>
  );
}
export default App;
import { useState } from 'react';
import Grandchild from './Grandchild'

function Child({name, pCount, setPCount}) {
    const [ cCount, setCCount ] = useState(0);
    console.log('Child.js 更新');
    return (
        <div className='cchild'>
            <div>{name}:</div>
            <div>
                <span>親Count:{pCount}</span>
                <input
                  type='button'
                  value={'子コンポーネント-親カウントアップ'}
                  onClick={()=>setPCount(pre => ++pre)} />
            </div>
            <div>
                <span>子Count:{cCount}</span>
                <input
                  type='button'
                  value={'子コンポーネント-子カウントアップ'}
                  onClick={()=>setCCount(pre => ++pre)} />
            </div>
            <Grandchild id={1} pCount={pCount} setPCount={setPCount}
               cCount={cCount} setCCount={setCCount} />
            <Grandchild id={2} pCount={pCount} setPCount={setPCount}
               cCount={cCount} setCCount={setCCount} />
        </div>
    );
}
export default Child;
import { useState } from 'react';
function Grandchild({id, pCount, setPCount, cCount, setCCount}){
    const [ gCount, setGCount  ] = useState(0);
    console.log(`Grandchild.js 更新 id=${id}`);
    return(
        <div className='cgrandchild'>
            <div>孫コンポーネントid:{id}</div>
            <div>
                <span>親Count:{pCount}</span>
                <input
                  type='button'
                  value={'孫コンポーネント-親カウントアップ'}
                  onClick={()=>setPCount(pre => ++pre)} />
            </div>
            <div>
                <span>子Count:{cCount}</span>
                <input
                  type='button'
                  value='子孫コンポーネント-カウントアップ'
                  onClick={()=>setCCount(pre => ++pre)} />
            </div>
            <div>
                <span>孫Count:{gCount}</span>
                <input
                  type='button'
                  value='孫コンポーネント-孫カウントアップ'
                  onClick={()=>setGCount(pre => ++pre)} />
            </div>
        </div>
    )
}
export default Grandchild;

では、画面で確認してみましょう。

コンソールログは下記になります。

コンポーネントの上位から下位へ順に描画しているのが分かります。

index.jsファイルに「<React.StrictMode>」があると、console.logが2重に出力されます。
2重に出力される事自体は、システムの仕様なので問題はありません。
ただ、ログが見難くなります。

今回は、重要なシステムを作成している訳ではないので、一旦除去して確認したいと思います。

コンポーネントが再描画する条件は下記3つになります。

  • Stateが更新された時
  • 渡されたPropsが変更された時
  • 親コンポーネントが再描画された時

上記再描画条件より、各コンポーネントのStateが更新された場合、どのコンポーネントが再描画するのかが分かります。

  1. 親コンポーネントのStateが更新される → 親・子・孫コンポーネントが再描画
  2. 子コンポーネントのStateが更新される → 子・孫コンポーネントが再描画
  3. 孫コンポーネントのStateが更新される → 更新した孫が再描画

では、この通りに再描画されるか、一つ一つ確認してみましょう。

親Stateの更新を行う

親コンポーネントのボタンをクリックしてみます。

親のStateが1になったので、全コンポーネントの「親Count1」と表示されています。

console.logの出力を確認してみます。

全コンポーネントが再描画されている事がわかります。

次に、孫コンポーネントから親Stateを変更してみましょう。
全コンポーネントの「親Count2」となり、あわせて全コンポーネントが再描画されています。

親のStateを変更する関数を、子や孫のコンポーネントで実行しても、正常に親のStateが変更出来ているのが確認できました。

子のStateを更新する

次は子のStateを、孫コンポーネントから更新してみましょう。

子と孫のコンポーネントで、「子Count1」となりました。

console.logの出力を確認してみます。

親のStateに変更が無い為、親コンポーネントは再描画していません。
今回は子のStateに変更があったので、子と孫のコンポーネントに対し再描画が実行されています。

孫のStateを更新する

id=1の孫コンポーネントに対し、Stateの更新を行います。

孫Count1」になっているのが確認できます。
また、id=2の孫コンポーネントの「孫Countは0」のままになっています。

同じコンポーネントですが、Stateはコンポーネント固有となっていて独立している事が分かります。

console.logの出力を確認してみます。

id=1の孫に対しStateを更新したため、id=1の孫のみコンポーネントの再描画が行われています。
id=2の孫は変更も再描画も一切発生していません。

Stateとコンポーネントの更新条件は、今後開発を行っていく上で重要な知識になっていきます。

子要素(children)を受け取る

共通フレームを作成する際、子要素を受け取りを求める場面があります。
その際に使用するのが、Propsの「children」になります。

下記サンプル「Wrap.js」は、引数に「name」と子要素「children」を受け取り、子要素の上に「name」を表示するコンポーネントになります。

function Wrap({children, name}) {
    return(
        <div>
            <div>{name}</div>
            {children}
        </div>
    );
}
export default Wrap;
import './App.css';
import Wrap from './Wrap'

function App() {
  return (
    <Wrap name='ラーメングルメ日記'>
      <div>麺屋〇×亭</div>
      <div>濃厚とんこつラーメン</div>
    </Wrap>
  );
}
export default App;

画面を確認してみましょう。
子要素の上に、引数「name」が表示されています。

おわりに

Reactの重要な要素「コンポーネント」の基礎をご紹介いたしました。

コンポーネントの作成はかなり奥深いです。
アプリの画面構成をどの様に組み立てるか、事前に設計しておくと開発時の手戻りは少ないです。

そのため、コンポーネントの基礎知識は必須の知識となります。

皆さまの今後の制作に、何らかのお役になれば幸いです。