Notes

はじめに

  • これまで、HTMLとCSSを使って簡単なWebページを構築する方法、GitとGitHubを使ってコードの変更を追跡し、他の人と共同作業するために方法について説明してきました。​また、Pythonプログラミング言語に慣れ、Djangoを使用してWebアプリケーションを作成し始め、Djangoモデルを使用してサイトに情報を保存する方法を学びました。​次にJavaScriptを紹介し、それを使ってWebページをよりインタラクティブにする方法を学びました。
  • 本日は、JavaScriptとCSSを使用してサイトをさらに使いやすくするユーザインターフェース設計の共通規則について説明します。

ユーザインターフェース

ユーザインターフェイスは、Webページへの訪問者がそのページと対話する方法です。​Web開発者としての私たちの目標は、これらのインタラクションをユーザにとってできるだけ快適なものにすることであり、これを行うために使える多くの方法があります。

単一ページのアプリケーション

以前は、複数のページを持つWebサイトが必要な場合は、Djangoアプリケーションで異なるルートを使用して実現していました。​これで、1つのページのみをロードし、JavaScriptを使用してDOMを操作できるようになりました。これを行う主な利点の1つは、実際に変更されるページの部分のみを変更する必要があることです。​たとえば、現在のページに基づいて変更されないナビゲーションバーがある場合、ページの新しい部分に切り替えるたびにそのナビゲーションバーをレンダリングし直す必要はありません。

​JavaScriptでページ切り替えをシミュレートする例を見てみましょう。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Single Page</title>
        <style>
            div {
                display: none;
            }
        </style>
        <scriptscript>
    </head>
    <body>
        <button data-page="page1">Page 1</button>
        <button data-page="page2">Page 2</button>
        <button data-page="page3">Page 3</button>
        <div id="page1">
            <h1>This is page 1</h1>
        </div>
        <div id="page2">
            <h1>This is page 2</h1>
        </div>
        <div id="page3">
            <h1>This is page 3</h1>
        </div>
    </body>
</html>

上記のHTMLには、3つのボタンと3つのdivがあることに注意してください。現時点では、divにはほんの少しのテキストしか含まれていませんが、各divには、このサイトの1ページのコンテンツが含まれていると考えることができます。次に、ボタンを使用してページを切り替えるJavaScriptを追加します。

// Shows one page and hides the other two
function showPage(page) {

    // Hide all of the divs:
    document.querySelectorAll('div').forEach(div => {
        div.style.display = 'none';
    });

    // Show the div provided in the argument
    document.querySelector(`#${page}`).style.display = 'block';
}

// Wait for page to loaded:
document.addEventListener('DOMContentLoaded', function() {

    // Select all buttons
    document.querySelectorAll('button').forEach(button => {

        // When a button is clicked, switch to that page
        button.onclick = function() {
            showPage(this.dataset.page);
        }
    })
});
single page 1

多くの場合、サイトに初めてアクセスするときに各ページのコンテンツ全体をロードするのは非効率的なため、新しいデータにアクセスするにはサーバを使用する必要があります。たとえば、ニュースサイトを初めて訪問したときに、利用可能なすべての記事を読み込む必要がある場合、サイトの読み込みに時間がかかりすぎます。この問題は、前の講義で為替レートをロードするときに使用したのと同様の方法を使用して回避できます。今回は、Djangoを使用して1ページのアプリケーションから情報を送受信する方法を見てみましょう。この仕組みを説明するために、単純なDjangoアプリケーションを見てみましょう。urls.py には2つのURLパターンがあります。

urlpatterns = [
    path("", views.index, name="index"),
    path("sections/<int:num>", views.section, name="section")
]

views.py に2つの対応するルートがあります。section ルートは整数を受け取り、その整数に基づいたテキスト文字列をHTTP応答として返すことに注意してください。

from django.http import Http404, HttpResponse
from django.shortcuts import render

# Create your views here.
def index(request):
    return render(request, "singlepage/index.html")

# The texts are much longer in reality, but have
# been shortened here to save space
texts = ["Text 1", "Text 2", "Text 3"]

def section(request, num):
    if 1 <= num <= 3:
        return HttpResponse(texts[num - 1])
    else:
        raise Http404("No such section")

index.html ファイルでは、前のレッスンで学習したAJAXを利用して、特定のセクションのテキストを取得して画面に表示するようサーバに要求します。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Single Page</title>
        <style>
        </style>
        <script>

            // Shows given section
            function showSection(section) {
                
                // Find section text from server
                fetch(`/sections/${section}`)
                .then(response => response.text())
                .then(text => {
                    // Log text and display on page
                    console.log(text);
                    document.querySelector('#content').innerHTML = text;
                });
            }

            document.addEventListener('DOMContentLoaded', function() {
                // Add button functionality
                document.querySelectorAll('button').forEach(button => {
                    button.onclick = function() {
                        showSection(this.dataset.section);
                    };
                });
            });
        </script>
    </head>
    <body>
        <h1>Hello!</h1>
        <button data-section="1">Section 1</button>
        <button data-section="2">Section 2</button>
        <button data-section="3">Section 3</button>
        <div id="content">
        </div>
    </body>
</html>
Single page 2

これで、HTMLページ全体を再ロードすることなく、サーバから新しいデータをロードできるサイトを作成できました。

しかし、私たちのサイトの欠点の1つは、URLの情報が少なくなったことです。上のビデオを見ればわかるように、セクションを切り替えてもURLは変わりません。 JavaScript履歴API を使用すると、この問題を解決できます。このAPIを使用すると、ブラウザ履歴に情報をプッシュし、URLを手動で更新できます。このAPIを使用する方法を見てみましょう。前のものと同じDjangoプロジェクトがありますが、今回は履歴APIを使用するようにスクリプトを変更したいと考えています。

// When back arrow is clicked, show previous section
window.onpopstate = function(event) {
    console.log(event.state.section);
    showSection(event.state.section);
}

function showSection(section) {
    fetch(`/sections/${section}`)
    .then(response => response.text())
    .then(text => {
        console.log(text);
        document.querySelector('#content').innerHTML = text;
    });

}

document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('button').forEach(button => {
        button.onclick = function() {
            const section = this.dataset.section;

            // Add the current state to the history
            history.pushState({section: section}, "", `section${section}`);
            showSection(section);
        };
    });
});

上記の showSection 関数では、history.pushState 関数を使用しています。この関数は、3つの引数に基づいて、ブラウズ履歴に新しい要素を追加します。

  1. 状態に関連付けられたデータ
  2. ほとんどのWebブラウザで無視されるタイトルパラメータ
  3. URLの表示内容

上記のJavaScriptで行ったもう1つの変更は、onpopstate パラメーターの設定です。このパラメーターは、ユーザが戻る矢印をクリックしたときに何を行うかを指定します。ここでは、ボタンを押したときに前のセクションを表示します。これでサイトは少し使いやすくなりました。

single page with URL change

スクロール

ブラウザーの履歴を更新してアクセスするために、ウィンドウと呼ばれる重要なJavaScriptオブジェクトを使用しました。ウィンドウのその他のプロパティを使用して、サイトを見やすくすることができます。

  • window.innerWidth:ウィンドウの幅 (ピクセル単位)
  • window.innerHeight:ウィンドウの高さ (ピクセル単位)
inner measures

ウィンドウは現在ユーザに表示されている内容を表しますが、ドキュメント はWebページ全体を参照します。多くの場合、ウィンドウよりもはるかに大きいため、ユーザはページのコンテンツを表示するために上下にスクロールする必要があります。スクロールを操作するには、他の変数にアクセスします。

  • window.scrollY:ページの先頭からスクロールしたピクセル数
  • document.body.offsetHeight:ドキュメント全体の高さ (ピクセル単位) 
Scrolling measures

これらの測度を使用すると、比較 window.scrollY + window.innerHeight >= document.body.offsetHeight を使用して、ユーザがページの最後までスクロールしたかどうかを判断できます。たとえば、次のページでは、ページの下部に達したときに背景色が緑色に変わります。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Scroll</title>
        <script>

            // Event listener for scrolling
            window.onscroll = () => {

                // Check if we're at the bottom
                if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {

                    // Change color to green
                    document.querySelector('body').style.background = 'green';
                } else {

                    // Change color to white
                    document.querySelector('body').style.background = 'white';
                }

            };

        </script>
    </head>
    <body>
        <p>1</p>
        <p>2</p>
        <!-- More paragraphs left out to save space -->
        <p>99</p>
        <p>100</p>
    </body>
</html>
scroll green white

無限スクロール

ページの最後で背景色を変更するのはあまり便利ではありませんが、無限スクロールを実装する場合は、ページの最後であることを検出する必要があります。たとえば、ソーシャルメディアサイトで、すべての投稿を一度にロードする必要がない場合は、最初の10件をロードし、ユーザが一番下に達したら次の10件をロードします。これを実現するDjangoアプリケーションを見てみましょう。このアプリは urls.py に2つのパスを持っています。

urlpatterns = [
    path("", views.index, name="index"),
    path("posts", views.posts, name="posts")
]

views.py に2つの対応するビューがあります。

import time

from django.http import JsonResponse
from django.shortcuts import render

# Create your views here.
def index(request):
    return render(request, "posts/index.html")

def posts(request):

    # Get start and end points
    start = int(request.GET.get("start") or 0)
    end = int(request.GET.get("end") or (start + 9))

    # Generate list of posts
    data = []
    for i in range(start, end + 1):
        data.append(f"Post #{i}")

    # Artificially delay speed of response
    time.sleep(1)

    # Return list of posts
    return JsonResponse({
        "posts": data
    })

posts ビューには、startend の2つの引数が必要です。このビューでは、独自のAPIを作成し、URL localhost:8000/posts?start=10&end=15 にアクセスしてテストすることができます。次のJSONを返します。

{
    "posts": [
        "Post #10",
        "Post #11", 
        "Post #12", 
        "Post #13", 
        "Post #14", 
        "Post #15"
    ]
}

ここで、サイトがロードする index.html テンプレートでは、本文に空の div とスタイル設定だけから始めます。最初に静的ファイルをロードし、次に static フォルダー内のJavaScriptファイルを参照することに注意してください。


{% load static %}
<!DOCTYPE html>
<html>
    <head>
        <title>My Webpage</title>
        <style>
            .post {
                background-color: #77dd11;
                padding: 20px;
                margin: 10px;
            }

            body {
                padding-bottom: 50px;
            }
        </style>
        <script scr="{% static 'posts/script.js' %}"></script>
    </head>
    <body>
        <div id="posts">
        </div>
    </body>
</html>

JavaScriptでは、ユーザがページの最後までスクロールするのを待ってから、APIを使ってさらに投稿をロードします。

// Start with first post
let counter = 1;

// Load posts 20 at a time
const quantity = 20;

// When DOM loads, render the first 20 posts
document.addEventListener('DOMContentLoaded', load);

// If scrolled to bottom, load the next 20 posts
window.onscroll = () => {
    if (window.innerHeight + window.scrollY >= document.body.offsetHeight) {
        load();
    }
};

// Load next set of posts
function load() {

    // Set start and end post numbers, and update counter
    const start = counter;
    const end = start + quantity - 1;
    counter = end + 1;

    // Get new posts and add posts
    fetch(`/posts?start=${start}&end=${end}`)
    .then(response => response.json())
    .then(data => {
        data.posts.forEach(add_post);
    })
};

// Add a new post with given contents to DOM
function add_post(contents) {

    // Create new post
    const post = document.createElement('div');
    post.className = 'post';
    post.innerHTML = contents;

    // Add post to DOM
    document.querySelector('#posts').append(post);
};

これで無限スクロールするサイトができました!

infinite scroll

アニメーション

サイトをもう少し面白くする別の方法は、アニメーションを追加することです。CSSはスタイリングを提供するだけでなく、HTML要素をアニメーション化することも容易にします。

CSSでアニメーションを作成するには、次の形式を使用します。この形式では、アニメーション固有の開始スタイルと終了スタイル( to and from )、または継続時間内のさまざまな段階のスタイル( 0% から100% まで)を含めることができます。たとえば、次のようになります。

@keyframes animation_name {
    from {
        /* Some styling for the start */
    }

    to {
        /* Some styling for the end */
    }
}

または:

@keyframes animation_name {
    0% {
        /* Some styling for the start */
    }

    75% {
        /* Some styling after 3/4 of animation */
    }

    100% {
        /* Some styling for the end */
    }
}

次に、アニメーションを要素に適用するために、animation-nameanimation-duration (秒単位)、animation-fill-mode (通常 forwards)を含めます。たとえば、最初にページに入ったときにタイトルが大きくなるページを次に示します。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Animate</title>
        <style>
            @keyframes grow {
                from {
                    font-size: 20px;
                }
                to {
                    font-size: 100px;
                }
            }

            h1 {
                animation-name: grow;
                animation-duration: 2s;
                animation-fill-mode: forwards;
            }
        </style>
    </head>
    <body>
        <h1>Welcome!</h1>
    </body>
</html>
Growing title

以下の例は、数行を変更するだけで見出しの位置を変更する方法を示しています。

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Animate</title>
        <style>
            @keyframes move {
                from {
                    left: 0%;
                }
                to {
                    left: 50%;
                }
            }

            h1 {
                position: relative;
                animation-name: move;
                animation-duration: 2s;
                animation-fill-mode: forwards;
            }
        </style>
    </head>
    <body>
        <h1>Welcome!</h1>
    </body>
</html>
Moving header

次に、中間CSSプロパティの設定についても説明します。アニメーションの途中で任意の比率でスタイルを指定できます。下の例では、タイトルを左から右に移動し、アニメーションだけを上から変更して左に戻します。

@keyframes move {
    0% {
        left: 0%;
    }
    50% {
        left: 50%;
    }
    100% {
        left: 0%;
    }
}
back and forth

アニメーションを複数回繰り返したい場合は、animation-iteration-count を1つ以上の数値 (無限アニメーションの場合は infinite ) に変更します。アニメーションのさまざまな設定を変更するためのアニメーションプロパティが多数あります。

CSSに加えて、JavaScriptを使用してアニメーションをさらに制御できます。移動ヘッダの例 (無限の繰り返し) を使用して、アニメーションを開始および停止するボタンを作成する方法を示します。すでにアニメーション、ボタン、見出しがある場合は、次のスクリプトを追加してアニメーションを開始および一時停止できます。

document.addEventListener('DOMContentLoaded', function() {

    // Find heading
    const h1 = document.querySelector('h1');

    // Pause Animation by default
    h1.style.animationPlayState = 'paused';

    // Wait for button to be clicked
    document.querySelector('button').onclick = () => {

        // If animation is currently paused, begin playing it
        if (h1.style.animationPlayState == 'paused') {
            h1.style.animationPlayState = 'running';
        }

        // Otherwise, pause the animation
        else {
            h1.style.animationPlayState = 'paused';
        }
    }

})
play/pause animation

では、アニメーションに関する新しい知識を、以前作成した投稿ページに適用する方法を見てみましょう。具体的には、投稿を読んだ後に非表示にしたいとします。先ほど作成したDjangoプロジェクトとまったく同じですが、HTMLとJavaScriptが少し異なっているとします。まず、add_post 関数を変更し、今度は投稿の右側にボタンを追加します。

// Add a new post with given contents to DOM
function add_post(contents) {

    // Create new post
    const post = document.createElement('div');
    post.className = 'post';
    post.innerHTML = `${contents} <button class="hide">Hide</button>`;

    // Add post to DOM
    document.querySelector('#posts').append(post);
};

ここでは、hide ボタンをクリックしたときに投稿を非表示にする方法を説明します。これを行うには、ユーザがページの任意の場所をクリックするたびにトリガーされるイベントリスナを追加します。次に、event.target 属性を使用してクリックされたものにアクセスできるため、引数としてeventを取る関数を作成します。parentElement クラスを使用して、DOM内の特定の要素の親を検索することもできます。

// If hide button is clicked, delete the post
document.addEventListener('click', event => {

    // Find what was clicked on
    const element = event.target;

    // Check if the user clicked on a hide button
    if (element.className === 'hide') {
        element.parentElement.remove()
    }
    
});
naive hide

これで、非表示ボタンを実装したことがわかりますが、これはあまり美しくありません。削除する前に、ポストをフェードアウトさせて縮小させることもできます。これを行うには、まずCSSアニメーションを作成します。下のアニメーションでは、75%の時間をかけて不透明度 opacity を1から0に変更しています。これにより、ポストは徐々にフェードアウトします。その後、残りの時間はすべての height 関連の属性を0に移動し、投稿を事実上縮小してゼロにします。

@keyframes hide {
    0% {
        opacity: 1;
        height: 100%;
        line-height: 100%;
        padding: 20px;
        margin-bottom: 10px;
    }
    75% {
        opacity: 0;
        height: 100%;
        line-height: 100%;
        padding: 20px;
        margin-bottom: 10px;
    }
    100% {
        opacity: 0;
        height: 0px;
        line-height: 0px;
        padding: 0px;
        margin-bottom: 0px;
    }
}

次に、このアニメーションを投稿のCSSに追加します。最初にアニメーション再生状態 animation-play-state を一時停止 paused に設定します。これは、投稿が既定で非表示にならないことを意味します。

.post {
    background-color: #77dd11;
    padding: 20px;
    margin-bottom: 10px;
    animation-name: hide;
    animation-duration: 2s;
    animation-fill-mode: forwards;
    animation-play-state: paused;
}

最後に、非表示 hide ボタンをクリックしてアニメーションを開始し、投稿を削除します。これを行うには、上からJavaScriptを編集します。

// If hide button is clicked, delete the post
document.addEventListener('click', event => {

    // Find what was clicked on
    const element = event.target;

    // Check if the user clicked on a hide button
    if (element.className === 'hide') {
        element.parentElement.style.animationPlayState = 'running';
        element.parentElement.addEventList
    }
    
});
Pretty hide

このように、非表示機能が大幅に改善されました。

React

この時点で、より複雑なWebサイトにどれだけのJavaScriptコードを組み込む必要があるか想像できます。CSSフレームワークとしてBootstrapを採用し、実際に書かなければならないCSSの量を削減したように、JavaScriptフレームワークを採用することで、実際に書かなければならないコードの量を減らすことができます。最も一般的なJavaScriptフレームワークの1つはReactというライブラリーです。

このコースでは、これまで命令型プログラミング手法を使用してきました。この手法では、実行するステートメントのセットをコンピュータに与えます。たとえば、HTMLページのカウンタを更新するには、次のようなコードを使用します。

表示:

<h1>0</h1>

ロジック:

let num = parseInt(document.querySelector("h1").innerHTML);
num += 1;
document.querySelector("h1").innerHTML = num;

Reactを使えば、宣言型プログラミングを使うことができ、表示したいものを説明するコードを書くだけで、どのように表示するかを気にする必要がなくなります。Reactでは、カウンタは次のようになります。

表示:

<h1>{num}</h1>

ロジック:

num += 1;

Reactフレームワークは、それぞれが基礎となる状態を持つことができるコンポーネントの概念に基づいて構築されています。コンポーネントは、投稿やナビゲーションバーなどのWebページに表示されるもので、状態はそのコンポーネントに関連付けられた変数のセットです。Reactの長所は、状態が変化すると、それに応じてReactが自動的にDOMを変更することです。

Reactを使う方法はいくつかあります (これにはFacebookが公開している人気の create-react-app コマンドも含まれます) が、今日はHTMLファイルで直接始めることに焦点を当てます。これを行うには、次の3つのJavaScriptパッケージをインポートする必要があります。

  • React:コンポーネントとその動作を定義します。
  • ReactDOM:Reactコンポーネントを取得してDOMに挿入します。
  • Babel:これからReactで記述する言語である JSX から、ブラウザが解釈できるプレーンなJavaScriptに翻訳します。JSXはJavaScriptに非常に似ていますが、コード内でHTMLを表現する機能など、いくつかの追加機能があります。

最初のReactアプリケーションを作成します。

<!DOCTYPE html>
<html lang="en">
    <head>
        <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script>
        <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script>
        <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
        <title>Hello</title>
    </head>
    <body>
        <div id="app" />
        <script type="text/babel">

            class App extends React.Component {

                render() { 
                    return (
                        <div>
                            <h1>Welcome!</h1>
                            Hello!
                        </div>
                    );
                }
            }
             
            ReactDOM.render(<App />, document.querySelector("#app"));
        </script>
    </body>
</html>

これは私たちの最初のReactアプリなので、このコードの各部分が何をしているかを詳しく見てみましょう。

  • タイトルの上の3行では、React、ReactDom、Babelの最新バージョンをインポートします。
  • 本文には、app というIDを持つ1つの div を含めます。ほとんどの場合、これを空のままにして、下のリアクション・コードに入力します。
  • type="text/babel" を指定するscriptタグを含めます。これは、Babelを使用して次のスクリプトを変換する必要があることをブラウザに通知します。
  • 次に、React.Component を拡張するApp というコンポーネントを作成します。ReactのコンポーネントはJavaScriptクラスとして表現されますが、これは前に説明したPythonクラスに似ています。これにより、React.Component クラス定義に含まれる多くのコードを書き直すことなくコンポーネントの作成を開始できます。
  • コンポーネントの内部には、render 関数が含まれています。すべてのコンポーネントがこの関数を持つ必要があり、関数内で返されるものはすべてDOMに追加されます。この場合は、<div>Hello!</div> が追加されます。
  • スクリプトの最後の行では、2つの引数を取る ReactDOM.render 関数を使用します。
    1. レンダリングするコンポーネント
    2. コンポーネントが描画されるDOM内の要素

コードが何をしているのか理解できたので、結果のWebページを見てみましょう。

welcome hello react

Reactの便利な機能の1つは、他のコンポーネント内のコンポーネントをレンダリングできることです。これを説明するために、Hello という別のコンポーネントを作成します。

class Hello extends React.Component {
    render() { 
        return (
            <h1>Hello</h1>
        );
    }
}

次に、Appコンポーネント内に3つの Hello コンポーネントを描画します。

class App extends React.Component {
    render() { 
        return (
            <div>
                <Hello />
                <Hello />
                <Hello />
            </div>
        );
    }
}

次のようなページが表示されます。

Three hellos

これまでのところ、コンポーネントはすべてまったく同じであるため、それほど興味深いものではありません。これらのコンポーネントにプロパティ(React用語でprops )を追加することで、コンポーネントの柔軟性を高めることができます。たとえば、3人の人に挨拶したいとします。HTMLタグに似たメソッドで、これらの人の名前を提供できます。

class App extends React.Component {
    render() { 
        return (
            <div>
                <Hello name="Harry" />
                <Hello name="Ron" />
                <Hello name="Hermione" />
            </div>
        );
    }
}

その後、this.props.PROP_NAME を使用してpropsにアクセスできます。ここで、this は現在のオブジェクトを表します。次に、中括弧を使用してこれをHTMLに挿入します。

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

このページには、3つの名前が表示されます。

Three names

では、Reactを使って、最初にJavaScriptを使って作ったカウンター・ページを再実装する方法を見てみましょう。全体的な構造は変わりませんが、App クラスの内部には、コンポーネントが最初に作成されたときに呼び出される constructor メソッドを含めます。このコンストラクタは常に引数として props を取り、最初の行は常に super(props); であり、React.Component クラスに基づいてオブジェクトを設定します。次に、コンポーネントの state を初期化します。これは、コンポーネントに関する情報を格納するJavaScriptオブジェクトです。現時点では、count を0に設定します。

constructor(props) {
    super(props);
    this.state = {
        count: 0
    };
}

これで、ヘッダとボタンを指定する render 関数を使用できるようになりました。ボタンがクリックされたときのイベントリスナも追加します。これは、Reactが onClick 属性を使用して実行します。

render() { 
    return (
        <div>
            <h1>{this.state.count}</h1>
            <button onClick={this.count}>Count</button>
        </div>
    );
}

最後に、count 関数を定義します。これを行うには、this.setState 関数を使用します。この関数は、古い状態から新しい状態への関数を引数として取ることができます。

count = () => {
    this.setState(state => ({
        count: state.count + 1
    }))
}

これで、機能するカウンターサイトができました!

counter

足し算

Reactフレームワークの感触をつかんだので、ここで学んだことを利用して、ユーザが追加の問題を解決するゲームのようなサイトを構築してみましょう。最初に、他のReactページと同じ設定で新しいファイルを作成します。このアプリケーションの構築を始めるにあたり、この状態で何を追跡しておきたいかを考えてみましょう。ユーザがページを開いている間に変更される可能性があるものはすべて含める必要があります。状態を次のように設定します。

  • num1:最初に追加する番号。
  • num2:2番目に追加する番号。
  • response:ユーザが入力した内容。
  • score:ユーザが正しく回答した質問の数。

ここで、コンストラクタは次のようになります。

constructor(props) {
    super(props);
    this.state = {
        num1: 1,
        num2: 1,
        response: '',
        score: 0
    };
}

ここで、状態の値を使用して、表示させたい内容を持つ render 関数を作成します。

render() { 
    return (
        <div>
            <div>{this.state.num1} + {this.state.num2}</div>
            <input type="text" value={this.state.response} />
            <div> Score: {this.state.score}</div>
        </div>
    );
}

サイトの基本レイアウトは次のようになります。

Addition layout

この時点では、その値は現在空の文字列である this.state.response として固定されているため、ユーザは入力ボックスに何も入力できません。これを修正するには、onChange 属性を入力ボックスに追加し、updateResponse という関数と同じ値に設定します。

onChange={this.updateResponse}

次に、updateResposne 関数を定義する必要があります。この関数は、関数をトリガしたイベントを取り込み、response を入力の現在の値に設定します。この関数は、ユーザが入力できるようにし、その state で入力された内容を保存します。

updateResponse = (event) => {
    this.setState({
        response: event.target.value
    });
}

次に、ユーザが問題を送信する機能を追加します。最初に別のイベントリスナーを追加し、次に記述する関数にリンクします。

onKeyPress={this.inputKeyPress}

次に、inputKeyPress 関数を定義します。この機能では、まず Enter キーが押されたかどうかを確認してから、答えが正しいかどうかを確認します。ユーザが正しい場合は、スコアを1だけ増やし、次の問題の乱数を選択し、応答をクリアします。回答が間違っている場合は、スコアを1減らして回答をクリアします。

inputKeyPress = (event) => {
                    
    // Check if the Enter key was pressed
    if (event.key === 'Enter') {

        // Extract answer
        const answer = parseInt(this.state.response)

        // Check if answer is correct
        if (answer === this.state.num1 + this.state.num2) {
            this.setState(state => ({
                score: state.score + 1,
                num1: Math.ceil(Math.random() * 10),
                num2: Math.ceil(Math.random() * 10),
                response: ''
            }));
        } else {
            this.setState(state => ({
                score: state.score - 1,
                response: ''
            }));
        }
    }
}

アプリケーションの仕上げとして、ページにスタイルを追加しましょう。アプリケーションのすべてを中央に配置し、問題を含む divに問題 problem の id を追加し、styleタグに次のCSSを追加することで、問題を大きくします。

#app {
    text-align: center;
    font-family: sans-serif;
}

#problem {
    font-size: 72px;
}

最後に、10ポイントを獲得した後にゲームに勝つ能力を追加しましょう。これを行うには、render 関数に条件を追加し、ポイントが10になるとまったく異なるものを返します。

render() { 

    // Check if the score is 10
    if (this.state.score === 10) {
        return (
            <div id="winner">
                You won!
            </div>
        );
    }

    return (
        <div>
            <div id="problem">{this.state.num1} + {this.state.num2}</div>
            <input onKeyPress={this.inputKeyPress} onChange={this.updateResponse} type="text" value={this.state.response} />
            <div> Score: {this.state.score}</div>
        </div>
    );
}

勝利をよりエキサイティングにするために、代替 div にもいくつかのスタイルを追加します。

#winner {
    font-size: 72px;
    color: green;
}

では、アプリケーションを見てみましょう。

finished

今日のレッスンはこれで終わりです!次回は、大規模なWebアプリケーションを構築するためのベストプラクティスについて説明します。