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.js | id, name, code |
孫コンポーネント | Grandchild.js | code, 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;
では、画面で確認してみましょう。
コンソールログは下記になります。
コンポーネントの上位から下位へ順に描画しているのが分かります。
コンポーネントが再描画する条件は下記3つになります。
上記再描画条件より、各コンポーネントのStateが更新された場合、どのコンポーネントが再描画するのかが分かります。
- 親コンポーネントのStateが更新される → 親・子・孫コンポーネントが再描画
- 子コンポーネントのStateが更新される → 子・孫コンポーネントが再描画
- 孫コンポーネントのStateが更新される → 更新した孫が再描画
では、この通りに再描画されるか、一つ一つ確認してみましょう。
親Stateの更新を行う
親コンポーネントのボタンをクリックしてみます。
親のStateが1になったので、全コンポーネントの「親Countが1」と表示されています。
console.logの出力を確認してみます。
全コンポーネントが再描画されている事がわかります。
次に、孫コンポーネントから親Stateを変更してみましょう。
全コンポーネントの「親Countが2」となり、あわせて全コンポーネントが再描画されています。
親のStateを変更する関数を、子や孫のコンポーネントで実行しても、正常に親のStateが変更出来ているのが確認できました。
子のStateを更新する
次は子のStateを、孫コンポーネントから更新してみましょう。
子と孫のコンポーネントで、「子Countが1」となりました。
console.logの出力を確認してみます。
親のStateに変更が無い為、親コンポーネントは再描画していません。
今回は子のStateに変更があったので、子と孫のコンポーネントに対し再描画が実行されています。
孫のStateを更新する
id=1の孫コンポーネントに対し、Stateの更新を行います。
「孫Countが1」になっているのが確認できます。
また、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の重要な要素「コンポーネント」の基礎をご紹介いたしました。
コンポーネントの作成はかなり奥深いです。
アプリの画面構成をどの様に組み立てるか、事前に設計しておくと開発時の手戻りは少ないです。
そのため、コンポーネントの基礎知識は必須の知識となります。
皆さまの今後の制作に、何らかのお役になれば幸いです。