プログラミングとSEOと暇つぶし

駆け出しエンジニアdallPのブログです。元SEOコンサルタントです。プログラミング、SEO、アフィリエイト、お金などについて役に立つかもしれない情報をやりたいように書きます。

MeteorJSとReactを勉強してみる その2: ReactコンポーネントでViewを作成しよう【公式翻訳】

Meteor公式翻訳の第二弾です。

基本的に1レッスン1記事、計12記事で書く予定です。

ReactコンポーネントでViewを定義する

アプリのViewライブラリとしてReactを利用するにあたって、まずはいくつかのNPMパッケージをインストールしましょう。 新しくターミナルの画面を開いて、前回作成したアプリがあるディレクトリまで移動して下記のコマンドを打ってください。

meteor npm install --save react react-dom

Note: meteor npmコマンドはnpmコマンドと同様の挙動をしますが、一部重要な差があります。詳細についてはmeteor npmのドキュメントを参照してください。

初期のコードを書き換えよう

手始めに、前回作成したデフォルトアプリのコードを書き換えましょう。 まず、client/main.htmlを下記のように書き換えます。

before

<head>
  <title>react-meteor-app</title>
</head>

<body>
  <h1>Welcome to Meteor!</h1>

  {{> hello}}
  {{> info}}
</body>

<template name="hello">
  <button>Click Me</button>
  <p>You've pressed the button {{counter}} times.</p>
</template>

<template name="info">
  <h2>Learn Meteor!</h2>
  <ul>
    <li><a href="https://www.meteor.com/try" target="_blank">Do the Tutorial</a></li>
    <li><a href="http://guide.meteor.com" target="_blank">Follow the Guide</a></li>
    <li><a href="https://docs.meteor.com" target="_blank">Read the Docs</a></li>
    <li><a href="https://forums.meteor.com" target="_blank">Discussions</a></li>
  </ul>
</template>

after

<head>
  <title>Todo List</title>
</head>
 
<body>
  <div id="render-target"></div>
</body>

次に、client/main.jsも下記に書き換えましょう。

before

import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

import './main.html';

Template.hello.onCreated(function helloOnCreated() {
  // counter starts at 0
  this.counter = new ReactiveVar(0);
});

Template.hello.helpers({
  counter() {
    return Template.instance().counter.get();
  },
});

Template.hello.events({
  'click button'(event, instance) {
    // increment the counter when button is clicked
    instance.counter.set(instance.counter.get() + 1);
  },
});

after

import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
 
import App from '../imports/ui/App.js';
 
Meteor.startup(() => {
  render(<App />, document.getElementById('render-target'));
});

そうしたら、次はimportsという新しいディレクトリを作ります。 Meteorのプロジェクトにおいて、importsという名前のディレクトリは特別で、他のものとは異なる挙動をします。

importsの中にないファイルはMeteorサーバーが立ち上がると自動的にロードされますが、importsだけはimport宣言がされたときのみにロードされます。

importsディレクトリをアプリのディレクトリ直下に作成したら、その中に新しく2つのファイルを作成しましょう。

imports/ui/App.js

import React, { Component } from 'react';
 
import Task from './Task.js';
 
// App component - represents the whole app
export default class App extends Component {
  getTasks() {
    return [
      { _id: 1, text: 'This is task 1' },
      { _id: 2, text: 'This is task 2' },
      { _id: 3, text: 'This is task 3' },
    ];
  }
 
  renderTasks() {
    return this.getTasks().map((task) => (
      <Task key={task._id} task={task} />
    ));
  }
 
  render() {
    return (
      <div className="container">
        <header>
          <h1>Todo List</h1>
        </header>
 
        <ul>
          {this.renderTasks()}
        </ul>
      </div>
    );
  }
}

imports/ui/Task.js

import React, { Component } from 'react';
 
// Task component - represents a single todo item
export default class Task extends Component {
  render() {
    return (
      <li>{this.props.task.text}</li>
    );
  }
}

ここまでで3つの新しい要素をアプリに追加しました。

  1. App.js というReactコンポーネント
  2. Task.js というReactコンポーネント
  3. client/main.jsのMeteor.startupスコープに書かれた初期化文

3つめの初期化文には、ページがロードされ表示の準備が整った状態で、どのように他のコードを呼び出すかを書いています。 この初期化文は他のコンポーネントをロードし、#render-target内のHTMLとして描写します。

importsディレクトリがどのように動作するのか、そして実際にはどのような構成でアプリを作っていけば良いのか、より詳細にはApplication Structure articleを読んでみてください。

チュートリアルを進め、コードを追加・変更していく中でこれらのコンポーネントについても再度解説します。

変更結果を確認してみよう

アプリを立ち上げてブラウザを見てみると、アプリの内容が下記のように書き換わっているのが分かるかと思います。

f:id:dallP:20171216012008p:plain

もしこのようになっていなければ、このページ(訳者注: 元ページ)の各コードスニペットの右上リンクからGitHubをチェックし、自分の書いたコードが確実に例と合致しているか確認してみてください。

HTMLファイルには静的コンテンツを書く

Meteorはアプリのフォルダ内の全てのHTMLファイルを解析し、<head><body><template>の3つのタグを特定します。

<head>内の全ての要素はクライアントに送信されるHTMLのheadセクションに、そして<body>内の全ての要素はbodyセクションに追加されます。これは普通のHTMLファイルと同様です。

ただ、<template>内の全ては「Meteor template」にコンパイルされます。templateはHTML内では{{> templateName}}JavaScript内ではTemplate.templateNameで表現されます。

このチュートリアルでは、View内のコンポーネント定義には全てReactを利用するので、templateは利用しません

ReactでViewを構成する

Reactでは、ViewコンポーネントはReact.Componentのサブクラスです。 なお、このReact.Componentはimports/ui/App.jsで書いたようにimport { Component } from 'react';という宣言でインポートできます。

自分で作ったコンポーネントには好きなメソッドを持たせることができますが、renderのように特殊な機能を持つメソッドが既に存在しています。

また、コンポーネントはその親からpropという属性でデータを受け取ることができます

このチュートリアルではさらにいくつかのReactの一般的な特徴について解説していきます。詳細なReactの解説については、FacebookのReactチュートリアルを参照してください。

マークアップ(HTML)の内容はJSXのrenderメソッドでレンダリングされる

どのReactコンポーネントにおいても最も重要なのが、render()メソッドです。 そのコンポーネントは表示すべきである、というHTMLの内容を取得するためにReactによって呼び出されるメソッドがrender()です。

Reactでは、HTMLはJSXというJavaScriptの拡張構文で表現します。イメージとしては、JavaScriptファイルの中にHTMLをそのまま書く感じです。

ただし既に見たとおり、明らかに通常のHTMLと異なる点がいくつかあります。

特に理解すべきなのが、JSXではclassではなくclassName属性を用いるということです。 重要なのは、JSXはSpacebarsやAngularのようなテンプレート言語ではない、ということです。JSXはそのまま正規のJavaScriptコードにコンパイルされます。 より詳しくは、Reactのドキュメントで読んでください。

JSXはecmascriptのAtmosphereJSパッケージを利用していますが、新しいMeteorアプリの中にはデフォルトで上記が含まれています。

CSSを追加する

before

/* CSS declarations go here */

after

/* CSS declarations go here */
body {
  font-family: sans-serif;
  background-color: #315481;
  background-image: linear-gradient(to bottom, #315481, #918e82 100%);
  background-attachment: fixed;
 
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
 
  padding: 0;
  margin: 0;
 
  font-size: 14px;
}
 
.container {
  max-width: 600px;
  margin: 0 auto;
  min-height: 100%;
  background: white;
}
 
header {
  background: #d2edf4;
  background-image: linear-gradient(to bottom, #d0edf5, #e1e5f0 100%);
  padding: 20px 15px 15px 15px;
  position: relative;
}
 
#login-buttons {
  display: block;
}
 
h1 {
  font-size: 1.5em;
  margin: 0;
  margin-bottom: 10px;
  display: inline-block;
  margin-right: 1em;
}
 
form {
  margin-top: 10px;
  margin-bottom: -10px;
  position: relative;
}
 
.new-task input {
  box-sizing: border-box;
  padding: 10px 0;
  background: transparent;
  border: none;
  width: 100%;
  padding-right: 80px;
  font-size: 1em;
}
 
.new-task input:focus{
  outline: 0;
}
 
ul {
  margin: 0;
  padding: 0;
  background: white;
}
 
.delete {
  float: right;
  font-weight: bold;
  background: none;
  font-size: 1em;
  border: none;
  position: relative;
}
 
li {
  position: relative;
  list-style: none;
  padding: 15px;
  border-bottom: #eee solid 1px;
}
 
li .text {
  margin-left: 10px;
}
 
li.checked {
  color: #888;
}
 
li.checked .text {
  text-decoration: line-through;
}
 
li.private {
  background: #eee;
  border-color: #ddd;
}
 
header .hide-completed {
  float: right;
}
 
.toggle-private {
  margin-left: 5px;
}
 
@media (max-width: 600px) {
  li {
    padding: 12px 15px;
  }
 
  .search {
    width: 150px;
    clear: both;
  }
 
  .new-task input {
    padding-bottom: 5px;
  }
}

CSSを追加したので、アプリの見栄えも良くなったはずです。 ブラウザで上記のスタイルが読み込まれているか確認しましょう。


第二弾はここまでです。 どうでしょうか。僕の翻訳もごく一部自信のない部分があるので、もし間違いに気づいたら教えてくださいm( )m

第三弾は来週中にでも。