ユーザインターフェースを構築するためのJavaScriptのフレームワーク「React」を使ってみます。
Reactの特徴
Reactは前述の通り、ユーザインターフェースを構築するためのフレームワークで、コンポーネントを組み合わせて作成していきます。
ReactのコンポーネントはJavaScriptの関数で、コンポーネント内ではJSXという記法を使って、JavaScriptの中にHTMLタグを記述する形になっています。
const App = () => { return ( <div className="contents"> <Heading /> </div> ); }
使い方
まずはフレームワークの読み込みですが、今回は簡単に使用するためCDNから読み込みます。
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script> <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
ReactとReactDOM、Babelを読み込んでいます。
上記は開発用のコードになるので、本番用の場合は下記を読み込むように変更してください。
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script> <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script> <script src="https://unpkg.com/babel-standalone@6.26.0/babel.min.js"></script>
Reactで描画する先を用意します。
<div id="app"></div>
実際にReactで描画してみます。
<script type="text/babel"> const root = ReactDOM.createRoot(document.querySelector('#app')); root.render(<h1>Hello, world</h1>); </script>
ReactDOM.createRoot()でReactで描画するルートを作成して、render()で要素を表示しています。
Reactのデモページ
render()の指定でHTMLタグを直接記述していますが、これがJSXになります。
この書き方は通常のJavaScriptだとエラーになりますが、scriptタグのtype属性でtext/babelを指定することで、BabelでJavaScriptのコードにコンパイルして表示しています。
次にコンポーネントを作成して使ってみます。
<script type="text/babel"> const root = ReactDOM.createRoot(document.querySelector('#app')); const App = () => { return ( <div className="contents"> <Heading /> </div> ); } const Heading = () => { return ( <h1>Hello, world</h1> ); } root.render(<App />); </script>
HeadingとAppというコンポーネントを作成して、HeadingはAppのコンポーネント内で読み込み、render()でAppのコンポーネントを指定するように変更しました。
コンポーネント作成のデモページ
ポイントとしては2点です。
1つ目はコンポーネント名の頭文字を大文字にする点です。
2つ目はJSXでclassを指定する際は、属性名をclassNameとする点です。
描画された後はclassになります。
次にHeadingのコンポーネント内に別要素を追加してみます。
まずは駄目な例です。
<script type="text/babel"> const root = ReactDOM.createRoot(document.querySelector('#app')); const App = () => { return ( <div className="contents"> <Heading /> </div> ); } const Heading = () => { return ( <h1>Hello, world</h1> <p>sample description</p> ); } root.render(<App />); </script>
ルート要素が複数ある場合のデモページ
この書き方の場合はSyntaxErrorになります。
エラーの内容は下記で、JSXではルート要素が一つである必要があるため、今回のようにh1とpがルート要素となっている場合はエラーになります。
Inline Babel script: Adjacent JSX elements must be wrapped in an enclosing tag
対応方法としてはdivタグで囲うなどでも解消できますが、不要なタグが増えてしまいます。
こういったときに使えるのがFragmentというコンポーネントです。
<script type="text/babel"> const root = ReactDOM.createRoot(document.querySelector('#app')); const App = () => { return ( <div className="contents"> <Heading /> </div> ); } const Heading = () => { return ( <React.Fragment> <h1>Hello, world</h1> <p>sample description</p> </React.Fragment> ); } root.render(<App />); </script>
Fragmentのデモページ
Fragmentをルート要素として使うことでエラーを解消でき、描画の際にはFragmentは生成されません
データの表示
次にJSX内でのデータの表示を試してみます。
<script type="text/babel"> const root = ReactDOM.createRoot(document.querySelector('#app')); const title = '見出しタイトル'; const App = () => { return ( <div className="contents"> <Heading /> </div> ); } const Heading = () => { return ( <React.Fragment> <h1>{title}</h1> <p>sample description</p> </React.Fragment> ); } root.render(<App />); </script>
JSX内で波括弧{}を使うことで、JavaScriptのコードを埋め込むことができます。
データ表示のデモページ
次に配列のデータをループで表示してみます。
<script type="text/babel"> const root = ReactDOM.createRoot(document.querySelector('#app')); const data = [ {name: '透', age: 17}, {name: '円香', age: 17}, {name: '小糸', age: 16}, {name: '雛菜', age: 15} ]; const App = () => { return ( <div className="contents"> <List /> </div> ); } const List = () => { const items = data.map((item) => <li key={item.name}> {item.name}({item.age}) </li> ); return ( <ul> {items} </ul> ); } root.render(<App />); </script>
配列を表示するデモページ
Listというコンポーネントを作成して、その中でmap()でliタグの配列を作成して、ul内に追加しています。
注意点として、liにkey属性を設定していますが、リスト内の各項目にはkey属性で一意の値を指定する必要があります。
イベントの設定
次にイベントの設定を試してみます。
<script type="text/babel"> const root = ReactDOM.createRoot(document.querySelector('#app')); const App = () => { return ( <div className="contents"> <Button /> </div> ); } const Button = () => { const clickEvent = () => { console.log('click'); } return ( <button onClick={clickEvent}>button</button> ); } root.render(<App />); </script>
これでボタンクリック時にconsoleが出るようになりました。
イベント設定のデモページ
ポイントは2点です。
1つ目はonclickではなくonClickのようにキャメルケースでの表記になる点です。
2つ目はonClickで指定した関数の末尾に()がつかない点です。
次にイベントが発生した際に動的に表示を変更してみます。
ボタンクリックでカウントアップする例で試してみます。
<script type="text/babel"> const useState = React.useState; const root = ReactDOM.createRoot(document.querySelector('#app')); const App = () => { return ( <div className="contents"> <Button /> </div> ); } const Button = () => { const [count, setCount] = useState(0); const clickEvent = () => { setCount(count + 1); } return ( <button onClick={clickEvent}>count: {count}</button> ); } root.render(<App />); </script>
Reactで用意されているuseStateを使用しています。
useStateを使うことで、現在の値(count)と更新用の関数(setCount)を取得することができ、これを使って現在の値の表示やボタンクリック時の値の更新を行うことができます。
useStateのデモページ
useStateの値はコンポーネント毎に保持されるので、同じコンポーネントを複数配置した場合でもそれぞれで値を保持します。
<script type="text/babel"> const useState = React.useState; const root = ReactDOM.createRoot(document.querySelector('#app')); const App = () => { return ( <div className="contents"> <Button /> <Button /> </div> ); } const Button = () => { const [count, setCount] = useState(0); const clickEvent = () => { setCount(count + 1); } return ( <button onClick={clickEvent}>count: {count}</button> ); } root.render(<App />); </script>
コンポーネント間でデータを共有したい場合、値の管理を上のコンポーネントで行い、必要な情報をpropsで各コンポーネントに渡す方法で行えます。
<script type="text/babel"> const useState = React.useState; const root = ReactDOM.createRoot(document.querySelector('#app')); const App = () => { const [count, setCount] = useState(0); const clickEvent = () => { setCount(count + 1); } return ( <div className="contents"> <Button count={count} onClick={clickEvent} /> <Button count={count} onClick={clickEvent} /> </div> ); } const Button = ({ count, onClick }) => { return ( <button onClick={onClick}>count: {count}</button> ); } root.render(<App />); </script>
コンポーネント呼び出しの際に渡したい値を属性で設定して、受け取り側のコンポーネントでは引数で受け取る値を設定します。
これで親コンポーネントから受け取った値を子コンポーネントで使用することができます。
propsのデモページ
コメントが承認されるまで時間がかかります。