react入門 1

対象者

reactの公式ガイドをベースに理解を深めていこうと思います。想定読者は、javascript,htmlに触ったことがあり、react を始めようかという人です。

reactが解決する課題

reactが解決しようとしているweb開発上の目的は主に下記の3点だと考えられます。

  • webページの部品(コンポーネント)単位にファイル分割して、プログラムの見通しをよくする。(es6以前は、javascript自体にファイル分割の仕組みがなかった。)

  • ファイル分割したコンポーネント間のデータのやり取り方法に、ルールを取り入れて、コンポーネントごとに改造、流用しやすくする。

  • コンポーネントの内部状態やユーザの入力に応じた動的な動きの実装にルールをとり入れて、コンポーネントごとに改造、流用しやすくする。

他のプログラミング言語では、よく表示とロジックを分けるスタイルが取られます。他のプログラミング言語でGUIを実装した経験のある人はわかると思いますが、ロジック部分は比較的容易にクラスなどに分割できますが、GUI部分は、何も考えずに書くと、長くなる傾向にあります。ただ、ボタンやイベント、描画エリアなどの機能がクラスとして提供されており、表示とロジックの整合が取りやすいことが多いです。
web開発において、これまで、静的表示のhtml、動的表示のjavascript,ロジックのバックエンドと機能の境界を言語自体を分けていた状況から、近年、ユーザ体験の向上を目的として、その境界が曖昧になってきており、統一した言語で扱いたいという流れになっているのだと思います。
flutterのように完全に別言語にする方法も増えていくかもしれませんが、v8エンジン、html、javascriptは広く普及しています。

reactの動作環境構築とHello world

eactを使うためには、多くの記事で紹介されている、node.jsをインストールして、reactをインストールして、create-react-appなどのツールチェインによりテンプレートを生成する方法が一般的です。
create-react-appを使用してテンプレートを用意しますが、説明には余分な部分が多いので、下記のようにpublicフォルダのindex.htmlとsrcフォルダのindex.js以外のファイルを削除します。

最小化したcreate-react-app


またそれぞれのファイルの内容を下記のようにシンプルにしておきます。

index.html

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>test</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

ほぼ最小限のHTMLです。body要素の中にid="root"のdivタグがあります。特筆すべきは、スクリプトを読み込む行がないことです。

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';

const root = ReactDOM.createRoot(document.getElementById('root'));
const jsx = <p>hello react</p>
root.render(jsx)

1,2行目は、reactのライブラリを読み込んでいます。
次に、ReactDOM.createRootでreactのルートオブジェクトを生成し、rootという変数名をつけています。インスタンス生成時の引数として、index.htmlの要素をgetElementById('root')で取得した、表示位置とルートオブジェクトを紐づけています。次の行の、<p>hello react</p>がJSXです。JSXはhtmlの構文に近いですが、同じではありません。文字列としてではなく、直接、JSXのオブジェクトとして変数に代入できます。ここではjsxという変数に格納しています。最後に、ルートオブジェクトのrender関数で、jsxをindex.htmlのid="root"の要素の子要素として描画します。
コンソールにnpm startと入力すると、ブラウザにhello reactと表示されます。

ファイル名でのファイル間のリンクと独自ファイルの追加

Hello worldができたのですが、なぜindex.htmlからindex.jsを明示的に呼び出していないのに実行されていたのでしょうか。ここでindex.jsを適当な名前にリネームしてみてください。エラーが起きます。reactはファイル名を監視しファイル間のリンクとして活用しているようです。
先ほどのHello worldプロジェクトにコンポーネントと呼ばれる、部品を追加します。index.jsと同じフォルダに下記App.jsを格納します。

App.js

const App= () => {
    return(
        <p>my component </p>
    )
}

export default App

シンプルにJSXを返すだけの関数です。最新のreactでは、何もインポートしなくてもJSX式がエラーになりません。ファイル名と関数名が同一で、大文字から始まることに注意してください。

index.js

import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
    <StrictMode>
        <p>hello react</p>
        <App/>
    </StrictMode>
)


3行目に先ほど作成したユーザ定義のコンポーネントAppを読み込んでいます。javascriptはimportの順番が大切ですので、ユーザ定義のコンポーネントは3行目以降に記述してください。
また、root.render関数内で、JSXを記述していますが、StrictModeタグがまず最上位に来ていることに注意してください。これはなくても動きますが、記載しておくと、内部で古い記述を自動で検出してコンソールに出力したりしてくれます。
<p>hello react</p>の後ろに<App/>が記載されています。JSXならではの機能で、先ほど追加したApp.jsの中に記載された関数Appを呼び出す仕組みとなっています。
ここで、JSXの利点を2つまとめておきます。

  • javascriptで記載した、JSXを返す関数を、JSXの中から呼び出せる仕組みになっています。

  • JSXのユーザ定義のタグ名(大文字で始まる)を見つけると、タグと同一のファイルを探してくれるため、明示的なファイル読み込みが不要となります。


引数を渡す

ユーザー定義の関数コンポーネントを生成し、呼び出すことができました。当然、関数なので引数を渡す方法を知りたくなります。まずはいわゆる値渡し、受け取った側は読み取り専用として扱うべき引数を渡す方法を示します。この読み取り専用の引数をpropsと呼びます。

引数を渡す側:index.js

...
 <App name="tom" age={10}/>
...

Appコンポーネントを呼びだす際に、コンポーネント名の後に、スペース区切りでプロパティ名={値}の形で渡します。文字列でも数字でもJSONのオブジェクトでも、ほぼなんでも渡すことができます。

引数を受け取る側:App.js

const App= (props) => {

    const age = props.age + 1

    return(
        <div>
            <p>name = {props.name} </p>
            <p>age = {age}</p>
        </div>

    )
}
export default App

関数の引数にpropsと記載します。これはルールです。渡す際に複数の引数が分割して渡されても、受け取る側では propsと呼ばれる1つのオブジェクトにまとめられます。渡されたそれぞれのプロパティは props.プロパティで読み取れます。JSX内でjavascriptの構文を書く際は{ }で構文を括ります。

部品化:javascriptにhtmlを取り込むJSX

これまで説明してきたJSXは何が良いのでしょうか。htmlの構文をjavascriptに取り込む仕組みがJSXです。JSXでhtmlをjsファイルに取り込むことで、部品化を簡単にします。
まず、以下のhtmlの部品化を検討します。タイトル、日付、内容からなるarticleを連ねるブログのイメージです。(今時直書きしている人はいなさそうですが。)

index.html

<!DOCTYPE html>
<html lang="ja">
    <head>
        <title>test1</title>
    </head>
    <body>

        <article>
            <h2>title 1</h2>
            <p>date 1</p>
            <p>aaaa</p>
        </article>
        <article>
            <h2>title  2</h2>
            <p>date 2</p>
            <p>bbbb</p>
        </article>
        <article>
            <h2>title 3</h2>
            <p>date 3</p>
            <p>cccc</p>
        </article>
   
        <footer>
            <h2>footer</h2>
            <p>author tomy </p>
            <p>Copyright 2022</p>
        </footer>
  
    </body>
</html>


通常のjavascriptで部品化する場合

通常のjavascriptで整理する方法を考えます。いろいろなやり方があると思いますが、下記の構成での分割を実装してみます。

  • blog.js …ブログのタイトル、日付、内容を受け取って、 それぞれ<article>タグの要素としてhtmlを生成するコンポーネント。

  • footer.js…フッター部分のhtmlを生成するコンポーネント。

  • app.js…コンポーネントのインスタンスを生成したり、コンポーネントに引数で情報を流したりするための、上位のスクリプト

index.html

分割前のhtmlのうち、ブログ部分をid="Blogのdivタグで、フッター部分をid="Footer"のdivタグに置き換え、bodyの最後にapp.jsをmoduleとして読み込んでいます。

<!DOCTYPE html>
<html lang="ja">
    <head>
        <title>test1</title>
    </head>
    <body>
        <div id = "Blog"></div>
        <div id = "Footer"></div>
        <script type="module" src="./app.js"></script>
    </body>
</html>

app.js

ブログの内容をjsonオブジェクトとして保持しています。Blogクラスのインスタンスを生成します。その際に、ブログ表示箇所のhtml要素のidを指定しています。関数renderにタイトル、日付、内容を渡して表示させています。Footerクラスはインスタンス生成時に、フッター部分を表示するDOMルートのidと、フッターに反映させたい著者名を渡し、フッターを表示します。

import { Blog } from "./blog.js"
import { Footer } from "./footer.js"

const article_1 = {
    "title":"title 1",
    "date":"date 1",
    "text":"aaaa"
}
const article_2 = {
    "title":"title 2",
    "date":"date 2",
    "text":"bbbb"
}
const article_3 = {
    "title":"title 3",
    "date":"date 3",
    "text":"cccc"
}

let blogObj = new Blog("Blog")
let footerObj = new Footer("Footer","tomy")

console.log("hello")
blogObj.render(article_1.title,article_1.date,article_1.text)
blogObj.render(article_2.title,article_2.date,article_2.text)
blogObj.render(article_3.title,article_3.date,article_3.text)

blog.js

インスタンス生成時に、記事を表示するhtml要素をgetElementByIdにより取得します。render関数により、引数として受けとったタイトル、日付、内容をhtmlとして生成、保持している要素に追加します。

export class Blog{
    constructor(elmId){
        this.elmId = document.getElementById(elmId)    
    }

    render(title,date,text){
        this.html_article = `
        <h2>${title}</h2>
        <p>${date}</p>
        <p>${text}</p>
        `;
        this.containerElm = document.createElement("article")
        this.containerElm.innerHTML = this.html_article

        this.elmId.appendChild(this.containerElm)
    }

}

footer.js

こちらはblog.jsと異なり、インスタンス生成時に、生成したhtmlの表示まで実行しています。

export class Footer{
    constructor(elmId,auther){
        this.containerElm = document.getElementById(elmId)
        
        this.html_footer = `
        <footer>
            <h2>footer</h2>
            <p>author ${auther}</p>
            <p>Copyright 2022</p>
        </footer>
        `;
        
        this.containerElm.innerHTML = this.html_footer
    }
}

上記のファイルを一つのフォルダに格納し、ローカルサーバーで実行すると、ブラウザに分割前と同様に表示されるはずです。

ローカルサーバーでの表示


コンポーネントクラスを作る際には、DOMノードをidにより指定し取得、そこにクラスの関数内部で生成したHTMLを記述したテキストをDOMノードのinnerHTMLに指定して部品化していました。

react,JSXを使用して部品化する場合

次に、reactで同様のことを実装してみます。

index.js

import React, { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
    <StrictMode>
        <App/>
    </StrictMode>
)

特になく、Appコンポーネントを呼び出しているだけです。

App.js

import Blog from "./Blog"
import Footer from "./Footer"

const article_1 = {
    "title":"title 1",
    "date":"date 1",
    "text":"aaaa"
}
const article_2 = {
    "title":"title 2",
    "date":"date 2",
    "text":"bbbb"
}
const article_3 = {
    "title":"title 3",
    "date":"date 3",
    "text":"cccc"
}

const App = () => {
    return(
        <div>
            <Blog title = {article_1.title} date={article_1.date} text={article_1.text}/>
            <Blog title = {article_2.title} date={article_2.date} text={article_2.text}/>
            <Blog title = {article_3.title} date={article_3.date} text={article_3.text}/>
            <Footer author="tomy"/>
        </div>

    )
}
export default App

BlogコンポーネントとFooterコンポーネントをインポートしてJSX内で呼び出しています。

Blog.js

const Blog = (props) =>{
    return(
        <article>
            <h2>{props.title}</h2>
            <p>{props.date}</p>
            <p>{props.text}</p>
        </article>
    )
}

export default Blog

受け取った3つのpropsの値をJSXとして表示し、return(render)しています

Footer.js

const Footer = (props) =>{
    return(
        <footer>
            <h2>footer</h2>
            <p>author {props.author} </p>
            <p>Copyright 2022</p>
        </footer>
    )

}

export default Footer

このようにreactですっきり読み取れました。

この記事が気に入ったらサポートをしてみませんか?