たとえば、Reactで概念実証を作成する必要があるとします。これは、ユーザーが作業したいタスクを追加、編集、削除したり、タスクを削除せずに完了としてマークしたりできるアプリです。この記事では、基本的なApp
コンポーネントの構造とスタイルを設定し、個々のコンポーネントの定義と変更に対応できるようにします。これについては後で追加します。
注:コードをGoogleのバージョンと照合する必要がある場合は、todo-reactリポジトリでサンプルのReactアプリコードの完成版を見つけることができます。実行中のバージョンについては、https://mdn.github .io / todo-react-build /を参照してください。
前提条件: |
コアHTML、CSS、JavaScriptの言語に精通していること、ターミナル/マンドコラインに関すてる知識。 |
---|---|
目的: | todoリストのケーススタディを紹介し、基本的なApp 構造とスタイルを整える。 |
アプリの「ユーザーストーリー」
ソフトウェア開発では、ユーザーストーリーはユーザーの観点から実行可能な目標を指します。作業を開始する前にユーザーストーリーを定義すると、作業に集中することができます。今回、私たちのアプリは以下のストーリーを実現する必要があります:
ユーザーができること
- タスクのリストを読むこと
- マウスまたはキーボードを使用してタスクを追加すること
- マウスまたはキーボードを使用して、タスクに完了のマークを付けること
- マウスまたはキーボードを使用して、タスクを削除すること
- マウスまたはキーボードを使用して、タスクを編集すること
- タスクの特定の一部を表示する:すべてのタスク、アクティブなタスクのみ、または完了したタスクのみ。
これらのストーリーに1つずつ取り組みます。
プロジェクトに入る前に現状整理
create-react-appは、プロジェクトでまったく使用しないファイルをいくつか作成しています。
- コンポーネントごと
App.js
のスタイルシートは作成しないので、最初にの上部にあるインポートApp.css
を削除します。 - また、
logo.svg
ファイルを使用しないので、そのインポートも削除します。
次に、以下のコマンドをコピーしてターミナルに貼り付け、不要なファイルをいくつか削除します。アプリのルートディレクトリから開始していることを確認してください。
# Move into the src directory of your project cd src # Delete a few files rm -- App.test.js App.css logo.svg serviceWorker.js setupTests.js # Move back up to the root of the project cd ..
ノート:
- 削除する2つのファイルは、アプリケーションのテスト用です。ここではテストを扱いません。
- 上記のターミナルタスクを実行するためにサーバーを停止した場合は、
npm start
を使用してサーバーを再起動する必要があります。
はじめのプロジェクトコード
このプロジェクトの開始点として、次の2つを提供します。現在持っているものを置き換えるApp関数と、アプリのスタイルを設定するCSSです。
JSX
次のスニペットをクリップボードにコピーして貼り付けApp.js
、既存のApp()
関数を置き換えます。
function App(props) { return ( <div className="todoapp stack-large"> <h1>TodoMatic</h1> <form> <h2 className="label-wrapper"> <label htmlFor="new-todo-input" className="label__lg"> What needs to be done? </label> </h2> <input type="text" id="new-todo-input" className="input input__lg" name="text" autoComplete="off" /> <button type="submit" className="btn btn__primary btn__lg"> Add </button> </form> <div className="filters btn-group stack-exception"> <button type="button" className="btn toggle-btn" aria-pressed="true"> <span className="visually-hidden">Show </span> <span>all</span> <span className="visually-hidden"> tasks</span> </button> <button type="button" className="btn toggle-btn" aria-pressed="false"> <span className="visually-hidden">Show </span> <span>Active</span> <span className="visually-hidden"> tasks</span> </button> <button type="button" className="btn toggle-btn" aria-pressed="false"> <span className="visually-hidden">Show </span> <span>Completed</span> <span className="visually-hidden"> tasks</span> </button> </div> <h2 id="list-heading"> 3 tasks remaining </h2> <ul role="list" className="todo-list stack-large stack-exception" aria-labelledby="list-heading" > <li className="todo stack-small"> <div className="c-cb"> <input id="todo-0" type="checkbox" defaultChecked={true} /> <label className="todo-label" htmlFor="todo-0"> Eat </label> </div> <div className="btn-group"> <button type="button" className="btn"> Edit <span className="visually-hidden">Eat</span> </button> <button type="button" className="btn btn__danger"> Delete <span className="visually-hidden">Eat</span> </button> </div> </li> <li className="todo stack-small"> <div className="c-cb"> <input id="todo-1" type="checkbox" /> <label className="todo-label" htmlFor="todo-1"> Sleep </label> </div> <div className="btn-group"> <button type="button" className="btn"> Edit <span className="visually-hidden">Sleep</span> </button> <button type="button" className="btn btn__danger"> Delete <span className="visually-hidden">Sleep</span> </button> </div> </li> <li className="todo stack-small"> <div className="c-cb"> <input id="todo-2" type="checkbox" /> <label className="todo-label" htmlFor="todo-2"> Repeat </label> </div> <div className="btn-group"> <button type="button" className="btn"> Edit <span className="visually-hidden">Repeat</span> </button> <button type="button" className="btn btn__danger"> Delete <span className="visually-hidden">Repeat</span> </button> </div> </li> </ul> </div> ); }
次にpublic/index.html
を開いて、<title>
要素のテキストを開いてTodoMatic
に変更します。こうすることでアプリ上部の<h1>
と一致します。
<title>TodoMatic</title>
ブラウザが更新されると、次のように表示されます。
まだ見た目が整っていなくて、機能もしていませんが、一旦問題はありません。すぐにスタイルを設定します。まず、このJSXと、それがユーザーストーリーにどのように対応するかを考えます。
- 新しいタスクを書き込むためのと、フォームを送信するためのボタンを備えた
<form>
と<input type="text">
要素があります。 - タスクのフィルタリングに使用するボタンの配列があります。
- 残っているタスクの数を示す見出しがあります。
- 3つのタスクがあり、順序付けられていないリストに配置されています。各タスクはリストアイテム(
<li>
)であり、タスクを編集および削除するためのボタンと、完了時にチェックボックスをオンにするチェックボックスがあります。
このフォームにより、タスクを作成できます。また、ボタンでフィルタリングもできます。見出しとリストは、それらを読むためのものです。現在のところ、タスクを編集するためのUIはあまりよくありません。大丈夫です。後ほど書き足していきます。
アクセシビリティ
ここでいくつかの変わった属性に気付くかもしれません。例えば:
<button type="button" className="btn toggle-btn" aria-pressed="true"> <span className="visually-hidden">Show </span> <span>all</span> <span className="visually-hidden"> tasks</span> </button>
ここでは、aria-pressed
ボタンは2つの状態のいずれかであることができることを(スクリーンリーダーなど)技術的に伝えます(pressed
かunpressed
)。on
とoff
の切替のように考えられます。true
の値を設定すると、デフォルトでボタンが押されます。
CSSが含まれていないため、このvisually-hidden
クラスはまだ意味がありません。ただし、スタイルを設定すると、このクラスの要素はすべて、目の見えるユーザーからは隠され、スクリーンリーダーのユーザーは引き続き使用できます。これは、目の見えるユーザーがこれらの単語を必要としないためです。それらは、ボタンを使用してスクリーンリーダーのユーザーに役立つ追加の視覚的コンテキストがないユーザーのために、ボタンの機能に関する詳細情報を提供するためにあります。
さらに下には<ul>
要素があります:
<ul role="list" className="todo-list stack-large stack-exception" aria-labelledby="list-heading" >
role
属性は、タグが表す要素の種類を説明するのに役立ちます。<ul>
はデフォルトではリストのように扱われますが、これから追加するスタイルはその機能を壊します。この役割は、<ul>
要素の「リスト」の意味を復元します。これが必要な理由について詳しく知りたい場合は、Scott O'Haraの記事「Fixing Lists」をチェックしてください。
このaria-labelledby
属性は、リストの見出しをその下にあるリストの目的を説明するラベルとして扱っていることを伝えます。この関連付けを行うと、リストの情報が豊富になり、スクリーンリーダーを通じてユーザーがリストの目的を理解しやすくなります。
最後に、リスト項目のラベルと入力には、JSXに固有のいくつかの属性があります。
<input id="todo-0" type="checkbox" defaultChecked={true} /> <label className="todo-label" htmlFor="todo-0"> Eat </label>
<input/ >
タグのdefaultChecked
属性は、デフォルトでこのチェックボックスをチェックするようにReactに指示します。通常のHTMLの場合と同様に、checked
を使用すると、Reactは、チェックボックスでのイベントの処理に関する警告をブラウザーコンソールに表示しますが、これは望ましくありません。とりあえず、これについてあまり心配しないでください。後でイベントを使用するときにこれについて説明します。
このhtmlFor
属性は、HTMLで使用されるfor
属性に対応しています。for
は予約語であるため、JSXでは属性として使用できません。そのためReactはfor
の代わりにhtmlFor
を使用します。
ノート:
- JSX属性でブール値(
true
およびfalse
)を使用するには、それらを中括弧で囲む必要があります。あなたが書く場合はdefaultChecked="true"
、の値はdefaultChecked
ではなくなり、文字列リテラル"true"
になります。覚えておいてください—これは実際にはHTMLではなくJavaScriptです! - 先ほどのコードで使われたいた
aria-pressed
属性は"true"
をもっていましたが、これはcheckedのようにブール値としてのtrueではありません。
スタイルを実装する
次のCSSコードをに貼り付けて、src/index.css
を現在あるものを置き換えます。
/* RESETS */ *, *::before, *::after { box-sizing: border-box; } *:focus { outline: 3px dashed #228bec; outline-offset: 0; } html { font: 62.5% / 1.15 sans-serif; } h1, h2 { margin-bottom: 0; } ul { list-style: none; padding: 0; } button { border: none; margin: 0; padding: 0; width: auto; overflow: visible; background: transparent; color: inherit; font: inherit; line-height: normal; -webkit-font-smoothing: inherit; -moz-osx-font-smoothing: inherit; -webkit-appearance: none; } button::-moz-focus-inner { border: 0; } button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; } button, input { overflow: visible; } input[type="text"] { border-radius: 0; } body { width: 100%; max-width: 68rem; margin: 0 auto; font: 1.6rem/1.25 Arial, sans-serif; background-color: #f5f5f5; color: #4d4d4d; } @media screen and (min-width: 620px) { body { font-size: 1.9rem; line-height: 1.31579; } } /*END RESETS*/ /* GLOBAL STYLES */ .form-group > input[type="text"] { display: inline-block; margin-top: 0.4rem; } .btn { padding: 0.8rem 1rem 0.7rem; border: 0.2rem solid #4d4d4d; cursor: pointer; text-transform: capitalize; } .btn.toggle-btn { border-width: 1px; border-color: #d3d3d3; } .btn.toggle-btn[aria-pressed="true"] { text-decoration: underline; border-color: #4d4d4d; } .btn__danger { color: #fff; background-color: #ca3c3c; border-color: #bd2130; } .btn__filter { border-color: lightgrey; } .btn__primary { color: #fff; background-color: #000; } .btn-group { display: flex; justify-content: space-between; } .btn-group > * { flex: 1 1 49%; } .btn-group > * + * { margin-left: 0.8rem; } .label-wrapper { margin: 0; flex: 0 0 100%; text-align: center; } .visually-hidden { position: absolute !important; height: 1px; width: 1px; overflow: hidden; clip: rect(1px 1px 1px 1px); clip: rect(1px, 1px, 1px, 1px); white-space: nowrap; } [class*="stack"] > * { margin-top: 0; margin-bottom: 0; } .stack-small > * + * { margin-top: 1.25rem; } .stack-large > * + * { margin-top: 2.5rem; } @media screen and (min-width: 550px) { .stack-small > * + * { margin-top: 1.4rem; } .stack-large > * + * { margin-top: 2.8rem; } } .stack-exception { margin-top: 1.2rem; } /* END GLOBAL STYLES */ .todoapp { background: #fff; margin: 2rem 0 4rem 0; padding: 1rem; position: relative; box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1); } @media screen and (min-width: 550px) { .todoapp { padding: 4rem; } } .todoapp > * { max-width: 50rem; margin-left: auto; margin-right: auto; } .todoapp > form { max-width: 100%; } .todoapp > h1 { display: block; max-width: 100%; text-align: center; margin: 0; margin-bottom: 1rem; } .label__lg { line-height: 1.01567; font-weight: 300; padding: 0.8rem; margin-bottom: 1rem; text-align: center; } .input__lg { padding: 2rem; border: 2px solid #000; } .input__lg:focus { border-color: #4d4d4d; box-shadow: inset 0 0 0 2px; } [class*="__lg"] { display: inline-block; width: 100%; font-size: 1.9rem; } [class*="__lg"]:not(:last-child) { margin-bottom: 1rem; } @media screen and (min-width: 620px) { [class*="__lg"] { font-size: 2.4rem; } } .filters { width: 100%; margin: unset auto; } /* Todo item styles */ .todo { display: flex; flex-direction: row; flex-wrap: wrap; } .todo > * { flex: 0 0 100%; } .todo-text { width: 100%; min-height: 4.4rem; padding: 0.4rem 0.8rem; border: 2px solid #565656; } .todo-text:focus { box-shadow: inset 0 0 0 2px; } /* CHECKBOX STYLES */ .c-cb { box-sizing: border-box; font-family: Arial, sans-serif; -webkit-font-smoothing: antialiased; font-weight: 400; font-size: 1.6rem; line-height: 1.25; display: block; position: relative; min-height: 44px; padding-left: 40px; clear: left; } .c-cb > label::before, .c-cb > input[type="checkbox"] { box-sizing: border-box; top: -2px; left: -2px; width: 44px; height: 44px; } .c-cb > input[type="checkbox"] { -webkit-font-smoothing: antialiased; cursor: pointer; position: absolute; z-index: 1; margin: 0; opacity: 0; } .c-cb > label { font-size: inherit; font-family: inherit; line-height: inherit; display: inline-block; margin-bottom: 0; padding: 8px 15px 5px; cursor: pointer; touch-action: manipulation; } .c-cb > label::before { content: ""; position: absolute; border: 2px solid currentColor; background: transparent; } .c-cb > input[type="checkbox"]:focus + label::before { border-width: 4px; outline: 3px dashed #228bec; } .c-cb > label::after { box-sizing: content-box; content: ""; position: absolute; top: 11px; left: 9px; width: 18px; height: 7px; transform: rotate(-45deg); border: solid; border-width: 0 0 5px 5px; border-top-color: transparent; opacity: 0; background: transparent; } .c-cb > input[type="checkbox"]:checked + label::after { opacity: 1; }
保存してブラウザーを確認すると、アプリに適切なスタイルが設定されているはずです。
まとめ
これで、todoリストアプリはまるで実際のアプリのように見えます。問題は、実際には何も動かないことです。次の章で修正を始めます!
In this module
- Introduction to client-side frameworks
- Framework main features
- React
- Ember
- Vue
- Getting started with Vue
- Creating our first Vue component
- Rendering a list of Vue components
- Adding a new todo form: Vue events, methods, and models
- Styling Vue components with CSS
- Using Vue computed properties
- Vue conditional rendering: editing existing todos
- Focus management with Vue refs
- Vue resources