React.jsでComponentの分割をしてみる

Components と Props

コンポーネントを使用すると、UIを独立した再利用可能な部分に分割し、各部分について個別に考えることができます。概念的には、コンポーネントはJavaScript関数のようなものです。彼らは任意の入力(「小道具」と呼ばれる)を受け入れ、何がスクリーンに現れるべきかを記述するReact要素を返します。

関数とClassコンポーネント

コンポーネントを定義する最も簡単な方法は、JavaScript関数を記述することです。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

この関数は、データとともに単一の “props”オブジェクト引数を受け取り、React要素を返すため、有効なReactコンポーネントです。このようなコンポーネントは、文字通りJavaScript関数であるため、「機能的」と呼んでいます。また、ES6クラスを使用してコンポーネントを定義することもできます。というか、これは現時点では古い書き方であり、古い書き方は、新しい書き方と共存しませんので、新しい書き方を学ぶべきです

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

上記の2つの要素は、Reactの観点から見れば全く同等な機能を提供しあmす。クラスに関しては、こっちで説明されているいくつかの追加機能があり、ここで詳しく説明されているので。ここでは、機能部品を簡潔に使用します。

コンポーネントのレンダリング

以前は、DOMタグを表すReact要素のみに遭遇しましたが、

const element = <div />;

ただし、要素はユーザー定義のコンポーネントを表すこともできます。

const element = <Welcome name="Sara" />;

Reactは、ユーザ定義のコンポーネントを表す要素を見ると、JSX属性を単一のオブジェクトとしてこのコンポーネントに渡します。このオブジェクトを「props」と呼びます。たとえば、次のコードでは、ページに「Hello、Sara」と表示されます。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
  element,
  document.getElementById('root')
);

CodePenで試してみてください。

上記の例で何が起こっているのか見てみましょう

まずReactDOM.render()<Welcome name="Sara" /> エレメントを呼んでいます。次にWelcomeコンポーネントでは{name: 'Sara'}の値を参照しています。Welcomeコンポーネントは結果として<h1> Hello、Sara </ h1>エレメント(要素)を返します。React DOMは効率的に<h1>Hello, Sara</h1>DOMを更新して一致させます

注意点:

コンポーネント名は常に大文字で開始してください。
たとえば、<div />はDOMタグを表しますが、<Welcome />はコンポーネントを表し、
ウェルカムをスコープに入れる必要があります。

コンポーネントの作成

コンポーネントは、他のコンポーネントを参照できます。これにより、どの階層に位置するコンポーネントに対しても同じコンポーネントの抽象化を表現できます。ボタン、フォーム、ダイアログ、スクリーン:Reactアプリケーションでは、すべてがコンポーネントとして一般的に表現されます。たとえば、Welcomを何度もレンダリングするAppコンポーネントを作成できます。

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      //ここ
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

CodePenで試してみてください。

通常、新しいReactアプリケーションには、一番上に1つのコンポーネントがあります。しかし、Reactを既存のアプリケーションに統合する場合、Buttonのような小さなコンポーネントでボトムアップを開始し、徐々にビュー階層の最上位に向かって作業を進めることができます。

注意:
コンポーネントは単一のルート要素を返す必要があるので、すべての<Welcome />要素を含むように<div>を追加しています

コンポーネントの抽出

たとえば、下記のようなコンポーネントを考えてみましょう。

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

CodePenで試してみてください。

大きなコンポーネントを小さなコンポーネントに分割するのを恐れない

全ソース

function formatDate(date) {
  return date.toLocaleDateString();
}

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
             src={props.author.avatarUrl}
             alt={props.author.name} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64'
  }
};


ReactDOM.render(
  <Comment
    date={comment.date}
    text={comment.text}
    author={comment.author} />,
  document.getElementById('root')
);

上記のコードではauthor(オブジェクト)、text(文字列)、 date(日付)をpropsとして受け取り、ソーシャルメディアのWebサイトにコメントを記述します。このコンポーネントは、すべてがネストされているため変更するのが難しく、個々の部分を再利用することも困難です。そこからいくつかのコンポーネントを抽出しましょう。まず、「アバター」を抽出します。

function Avatar(props) {
  return (

    //ここ
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  //ここ

  );
}

AvatarCommentの中でレンダリングされていることを知る必要はありません。その支柱に authorではなくuserというより一般的な名前を与えたのはそのためです。

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        //ここ
        <Avatar user={props.author} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

次に、ユーザーの名前の横にアバターを描画するUserInfoコンポーネントを抽出します。

function UserInfo(props) {
  return (
    //ここ
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  //ここ
  );
}

これにより、さらに単純化することができます。

function Comment(props) {
  return (
    <div className="Comment">
      //ここ
      <UserInfo user={props.author} />
   //ここ
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

CodePenで試してみてください。

コンポーネントの抽出は、最初は面倒な作業のように思えるかもしれませんが、再利用可能なコンポーネントのパレットを持つことは、より大きなアプリケーションを実現するときに役に立ちます。再利用可能なコンポーネントを創造することを意識しましょう。

藤沢瞭介(Ryosuke Hujisawa)
  • りょすけと申します。18歳からプログラミングをはじめ、今はフロントエンドでReactを書いたり、AIの勉強を頑張っています。off.tokyoでは、ハイテクやガジェット、それからプログラミングに関する情報まで、エンジニアに役立つ情報を日々発信しています!

未整理記事