20200107のReactに関する記事は8件です。

まだ相対パスで消耗してるの?

タイトル、煽りでスイマセン。

今回は↓これ↓の話です。

hoge.js
import SomeComponent from ../../../components/hoge/fuga

相対パスのimportです。
"../../"の数がいちいちわからなくなるのって私だけですかね?
また、ファイルを別のフォルダに打ちしたときなんかにエラーが出ることもあったりして、
相対パス、書き換えるの面倒くさいですよね。

今回はそんな悩みを絶対パスにして解決してしまおうという記事です。
題して、「絶対パスでReactを書こう!」
(よく考えたら題してないな)

内容に間違い等がございましたら、ご意見・ご指摘のほど宜しくお願いいたします。

前提:やり方は意外に簡単だよ!という話

調べたら色んなやり方が出てきましたが、一番手っ取り早い方法をご紹介します。
以下は概要。

  • webpack.config.jsonは使いません。
  • .babelrcは作りません。
  • ts.config.jsonを使います。
  • typescriptじゃなくてもOKです。

素のjavascirpt(typescriptじゃないという意味で素)でもts.config.jsonが使えるというのは、私も今回初めて知りました。
以下の記事が大変参考になったので貼っておきます。感謝!
https://qiita.com/terrierscript/items/a9826bc58d550d1b2764
また公式ドキュメントも先に貼っておきますのでお急ぎの方はこちらをご参照ください。
https://create-react-app.dev/docs/importing-a-component/#absolute-imports

では早速、方法をご紹介していきます。

step① tsconfig.jsonを作る

プロジェクトのroot(package.jsonなどがある場所)にts.config.jsonを作成します。
npx typescript --initして下記のように設定してください。
(下記はhttps://create-react-app.dev/より引用。)

tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]
}

自分は下記のように設定を加えています。

tsconfig.json
{
  "compilerOptions": {
    "incremental": true,
    "target": "es5",
    "module": "esnext",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "jsx": "react",
    "isolatedModules": true,
    "strict": true,
    "baseUrl": "src",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "skipLibCheck": true,
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "noEmit": true
  },
  "include": ["src"],
  "exclude": ["node_modules", "build", "scripts", "functions"]
}

なおts.config.jsonの書き方については、下記を参考にさせていただきました。
https://usefuledge.com/pb_00009_tscconfig_json.html
自分はこの辺りの設定にあまり詳しくないので、「ここがおかしい」とか「こうした方がいいよ」というのがございましたら、ぜひぜひコメント欄にお願いいたします。

ちなみに"allowJs": true”というやーつは、名前の通り「TypeScriptやなくても許してやりまっか〜」という意味なので、JSに段階的にTSを導入していきたい方は加えておくといいかもしれません。

step② TypeScriptを入れる

素のJSでtsconfig.jsonを作成すると、TypeScriptじゃないのにtsconfigがあるよ!
というような趣旨のエラーがでるので、その案内に従ってTypeScriptを入れてください。
具体的に書くと、
”It looks like you're trying to use TypeScript but do not have typescript installed.Please install typescript by running yarn add typescript”
とお叱りを受けるので、npm install --save-dev typescriptでおkということです。
とはいえ、拡張子をtsxにしたり、全部anyにして無理やりTypeScript化する必要はありません。
ただインストールするだけです。
(ts入れて使わないならjsconfig.jsonで良くね?という話は冒頭でご紹介した記事を御覧ください。)

結果:絶対パスで書けるようになる

hoge.js
import AbsoluteImportComponent from components/hoge/fuga
import RelativeImportComponent from ../components/hoge/hoge

絶対パスで書けるようになります!意外と簡単でしたね。
絶対パスでしか書けなくなるのではなく、どうやら両方いけるみたいです。

まとめ

絶対パスを使うとimportがむっちゃすっきりするよ、という話でした。
最後まで読んでいただいてありがとうございました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reduxの学習に躊躇うReact初級者に送る話

Reduxつらい問題ってありますよね。
ちょっとした実装がしたいだけなのに、Reduxだとボイラーテンプレートが多くてやたら量を書かないといけなかったりする。
「action? dispatch? reducer? 色々と学ぶことが多くて大変そう」と思う方もいらっしゃるでしょう。
今回はそんなReduxに尻込みしているReact初級者の方に送るReduxの話です。

メリット① ReduxDevTools便利すぎ

あなたがもし、Reactを書くためにいろんなところにConsole.logをしかけて、挙動を確認しているとしたら、ReduxDevToolsはそんなあなたの手間を劇的に削減することでしょう
ReduxDevToolsを使うためにReduxを書いているといっても過言ではありません。
いやさすがにそれは言い過ぎました。(どっちやねん!)
何はともあれ、ReduxDevToolsは非常に便利ですのでReduxを使ってみましょう

メリット② 大きめのプロジェクトを管理しやすい

あなたはこう言うでしょう。
「簡単なことがしたいだけなのに!10行で書けることがReduxだと30行にも40行にもなる!」
その気持ちぐぅわかるよ。。。
Reduxのメリットは、大きめのプログラムでReduxを使ったことがある人にしかわからないものです。

そうReduxは大きくなればなるほどその力を発揮するものであって、
個人で開発するWebアプリにとってはほぼ例外なくオーバーキルです。
しかし、いざ大規模の開発となるとReduxは輝きます。
そのときに向けて個人開発で予行演習しておくことを強く推します。

メリット③ 綺麗なコードについて考えるようになる

②とも共通することですが、Reduxで書くとそうでないときより読みやすくなることが多いです。
なぜなら、Reduxというのは単に状態管理のライブラリではなく、コードを体系的に書くための1個の考え方でもあるからです。(あくまで個人的な意見ですが。)
Reduxを勉強する過程で、綺麗なコードを書くための先人たちの知恵や思想に数多く出会うことでしょう。
それはducksかもしれないし、re-ducksかもしれないし、presentational-componentsとfunctional-componentsを分離させることかもしれません。
それが何であれ、その考え方自体は、いつかReduxが別のライブラリにとって代わられたとしても、一個に思想として残り続ける価値のあるものであるだと思います。

もし「immutableってどういうことだ?」「ディレクトリ構成ってどうやったらいんだ?」などと考えたことがあるなら、それはReduxを学ぶための最良のチャンスですよ!

余談:HooksもいいけどReduxもいいよ

最後に余談。
Reduxに代わるものとして昨今Hooksに注目が集まっています。
「useContextなら関数も渡せるしReduxみたいな面倒なこともない」とか、
「MobXなどの後発ライブラリを使ったほうが幸せになれる」というような意見もあります。
それでもReduxを推してみたい私です。
理由は、Reduxは必ずしもスマートではないかもしれませんが
やはり数の暴力といいますか、ちゃんとした情報がいろいろ落ちていたり、
Reduxを強力にサポートするツールやライブラリが盛りだくさんだからです。
筆者は個人的にreact-redux-firebaseというreduxとfirebaseを良い感じに併用可能にしてくれるツールが大好きで愛用しています。

初心者の方に「大変だけどHooksもReduxも両方学ぶのがベストだよ!」というのが、
今回の結論ということにさせてください!

Let's get Started!!

幸いにしてQiitaにはReduxのを学ぶための良記事があるので、まずはそれから始めてはいかがでしょう。
下記にリンクを貼っておきます。
https://qiita.com/mpyw/items/a816c6380219b1d5a3bf

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reduxの学習に躊躇うReact初級者の方に送る話

Reduxつらい問題ってありますよね。
ちょっとした実装がしたいだけなのに、Reduxだとやたら量を書かないといけなかったりする。
「action? dispatch? reducer? 色々と学ぶことが多くて大変そう」と思う方もいらっしゃるでしょう。
今回はそんなReduxに尻込みしているReact初級者の方に送るReduxのメリットについての話です。

メリット① ReduxDevTools便利すぎ

Reactを書くときにいろんなところにconsole.logをしかけて、いちいち挙動を確認しているとしたら、ReduxDevToolsはそんなあなたの手間を劇的に削減することでしょう。
ReduxDevToolsを使うためにReduxを書いているといっても過言ではありません。
いやさすがにそれは言い過ぎました。(どっちやねん!)
何はともあれ、ReduxDevToolsは非常に便利ですのでReduxを使ってみましょう

メリット② 大きめのプロジェクトを管理しやすい

あなたはこう言うでしょう。
「簡単なことがしたいだけなのに!10行で書けることがReduxだと30行にも40行にもなる!」
その気持ちぐぅわかるよ。。。
Reduxのメリットは、大きめのプログラムでReduxを使ったことがある人にしかわからないものです。

そうReduxは大きくなればなるほどその力を発揮するものであって、
個人で開発するWebアプリにとってはほぼ例外なくオーバーキルです。
しかし、いざ大規模の開発となるとReduxは輝きます。
そのときに向けて個人開発で予行演習しておくことを強く推します。

メリット③ 綺麗なコードについて考えるようになる

②とも共通することですが、Reduxで書くとそうでないときより読みやすくなることが多いです。
なぜなら、Reduxというのは単に状態管理のライブラリではなく、コードを体系的に書くための1個の考え方でもあるからです。(あくまで個人的な意見ですが。)
Reduxを勉強する過程で、綺麗なコードを書くための先人たちの知恵や思想に数多く出会うことでしょう。
それはducksかもしれないし、re-ducksかもしれないし、presentational-componentsとfunctional-componentsを分離させることかもしれません。
それが何であれ、その考え方自体は、いつかReduxが別のライブラリにとって代わられたとしても、一個に思想として残り続ける価値のあるものであるだと思います。

もし「immutableってどういうことだ?」「ディレクトリ構成ってどうやったらいんだ?」などと考えたことがあるなら、それはReduxを学ぶための最良のチャンスですよ!

余談:HooksもいいけどReduxもいいよ

最後に余談。
Reduxに代わるものとして昨今Hooksに注目が集まっています。
「useContextなら関数も渡せるしReduxみたいな面倒なこともない」とか、
「MobXなどの後発ライブラリを使ったほうが幸せになれる」というような意見もあります。
それでもReduxを推してみたい私です。
理由は、Reduxは必ずしもスマートではないかもしれませんが
やはり数の暴力といいますか、ちゃんとした情報がいろいろ落ちていたり、
Reduxを強力にサポートするツールやライブラリが盛りだくさんだからです。
筆者は個人的にreact-redux-firebaseというreduxとfirebaseを良い感じに併用可能にしてくれるツールが大好きで愛用しています。

初心者の方に「大変だけどHooksもReduxも両方学ぶのがベストだよ!」というのが、
今回の結論ということにさせてください!

Let's get Started!!

幸いにしてQiitaにはReduxのを学ぶための良記事があるので、まずはそれから始めてはいかがでしょう。
下記にリンクを貼っておきます。
https://qiita.com/mpyw/items/a816c6380219b1d5a3bf

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reduxの学習に躊躇う初級者の方に送る話

Reduxつらい問題ってありますよね。
ちょっとした実装がしたいだけなのに、Reduxだとやたら量を書かないといけなかったりする。
「action? dispatch? reducer? 色々と学ぶことが多くて大変そう」と思う方もいらっしゃるでしょう。
今回はそんなReduxに尻込みしている初級者の方に送るReduxのメリットについての話です。

メリット① ReduxDevTools便利すぎ

Reactを書くときにいろんなところにconsole.logをしかけて、いちいち挙動を確認しているとしたら、ReduxDevToolsはそんなあなたの手間を劇的に削減することでしょう。
ReduxDevToolsを使うためにReduxを書いているといっても過言ではありません。
いやさすがにそれは言い過ぎました。(どっちやねん!)
何はともあれ、ReduxDevToolsは非常に便利ですのでReduxを使ってみましょう

メリット② 大きめのプロジェクトを管理しやすい

あなたはこう言うでしょう。
「簡単なことがしたいだけなのに!10行で書けることがReduxだと30行にも40行にもなる!」
その気持ちぐぅわかるよ。。。
Reduxのメリットは、大きめのプログラムでReduxを使ったことがある人にしかわからないものです。

そうReduxは大きくなればなるほどその力を発揮するものであって、
個人で開発するWebアプリにとってはほぼ例外なくオーバーキルです。
しかし、いざ大規模の開発となるとReduxは輝きます。
そのときに向けて個人開発で予行演習しておくことを強く推します。

メリット③ 綺麗なコードについて考えるようになる

②とも共通することですが、Reduxで書くとそうでないときより読みやすくなることが多いです。
なぜなら、Reduxというのは単に状態管理のライブラリではなく、コードを体系的に書くための1個の考え方でもあるからです。(あくまで個人的な意見ですが。)
Reduxを勉強する過程で、綺麗なコードを書くための先人たちの知恵や思想に数多く出会うことでしょう。
それはducksかもしれないし、re-ducksかもしれないし、presentational-componentsとfunctional-componentsを分離させることかもしれません。
それが何であれ、その考え方自体は、いつかReduxが別のライブラリにとって代わられたとしても、一個に思想として残り続ける価値のあるものであるだと思います。

もし「immutableってどういうことだ?」「ディレクトリ構成ってどうやったらいんだ?」などと考えたことがあるなら、それはReduxを学ぶための最良のチャンスですよ!

余談:HooksもいいけどReduxもいいよ

最後に余談。
Reduxに代わるものとしてReact界隈だと昨今はHooksに注目が集まっています。
「useContextなら関数も渡せるしReduxみたいな面倒なこともない」という意見もあります。
それでもReduxを推してみたい私です。
Reduxは必ずしもスマートではないかもしれませんが、やはり数の暴力といいますか、ちゃんとした情報がいろいろ落ちていたり、Reduxを強力にサポートするツールやライブラリが盛りだくさんだからです。
筆者は個人的にreact-redux-firebaseというreduxとfirebaseを良い感じに併用可能にしてくれるツールが好きで愛用しています。

「大変だけどHooksもReduxも両方学ぶのがベストだよ!」というのを今回の結論とさせてください!

Let's get Started!!

幸いにしてQiitaにはReduxのを学ぶための良記事があるので、まずはそれから始めてはいかがでしょう。
下記にリンクを貼っておきます。
https://qiita.com/mpyw/items/a816c6380219b1d5a3bf

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reduxの学習コストに躊躇う初学者の方に伝えたいこと

Reduxつらい問題ってありますよね。
ちょっとした実装がしたいだけなのに、Reduxだとやたら量を書かないといけなかったりする。
「action? dispatch? reducer? 色々と学ぶことが多くて大変そう」と思う方もいらっしゃるでしょう。
今回はそんなReduxに尻込みしている初学者の方に送るReduxのメリットについての話です。

メリット① redux-devtools-extention便利すぎ

Reactを書くときにいろんなところにconsole.logをしかけて、いちいち挙動を確認しているとしたら、redux-devtools-extentionはそんなあなたの手間を劇的に削減することでしょう。
redux-devtools-extentionを使うためにReduxを書いているといっても過言ではありません。
いやさすがにそれは言い過ぎました。(どっちやねん!)
何はともあれ、redux-devtoolsは非常に便利ですのでReduxを使ってみましょう
下記の記事をご参照ください。
https://qiita.com/elzup/items/fc24588b2c6bae0834a6

メリット② 大きめのプロジェクトを管理しやすい

あなたはこう言うでしょう。
「簡単なことがしたいだけなのに!10行で書けることがReduxだと30行にも40行にもなるよ!」
その気持ち、ぐぅわかるよ。。。
Reduxのメリットは、大きめのプロジェクトでReduxを使ったことがある人にしかわからないものです。

そうReduxは大きくなればなるほどその力を発揮するものであって、
個人で開発するWebアプリにとってはほぼ例外なくオーバーキルです。
しかし、いざ大規模の開発となるとReduxは輝きます。
そのときに向けて個人開発で予行演習しておくことを強く推します。

メリット③ 綺麗なコードについて考えるようになる

②とも共通することですが、Reduxで書くとそうでないときより読みやすくなることが多いです。
なぜなら、Reduxというのは単に状態管理のライブラリではなく、コードを体系的に書くための1個の考え方でもあるからです。(あくまで個人的な意見ですが。)
Reduxを勉強する過程で、綺麗なコードを書くための先人たちの知恵や思想に数多く出会うことでしょう。
それはducksかもしれないし、re-ducksかもしれないし、presentational-componentsとcontainer-componentsを分離させることかもしれません。
それが何であれ、その考え方自体は、いつかReduxが別のライブラリにとって代わられたとしても、一個の思想として残り続ける価値のあるものだと思います。

もし「immutableってどういうことだ?」「ディレクトリ構成ってどうやったらいんだ?」などと考えたことがあるなら、それはReduxを学ぶための最良のチャンスですよ!

余談:HooksもいいけどReduxもいいよ

最後に余談。
Reduxに代わるものとしてReact界隈だと昨今はHooksに注目が集まっています。
「useContextなら関数も渡せるしReduxみたいな面倒なこともない」という意見もあります。
それでもReduxを推してみたい私です。
Reduxは必ずしもスマートではないかもしれませんが、やはり数の暴力といいますか、ちゃんとした情報がいろいろ落ちていたり、Reduxを強力にサポートするツールやライブラリが盛りだくさんだからです。
筆者は個人的にreact-redux-firebaseというreduxとfirebaseを良い感じに併用可能にしてくれるツールが好きで愛用しています。

「大変だけどHooksもReduxも両方学ぶのがベストだよ!」というのを今回の結論とさせてください!

Let's get Started!!

幸いにしてQiitaにはReduxのを学ぶための良記事があるので、まずはそれから始めてはいかがでしょう。
下記にリンクを貼っておきます。
https://qiita.com/mpyw/items/a816c6380219b1d5a3bf

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsアプリでページ遷移時にプログレスバー(ページ上部に出るやつ)を出す

progress7.gif

なかなかにミニマルなフレームワーク「Next.js」ではデフォルトではプログレスバーを表示しません。
Nuxt.jsなら勝手にやってくれるんだけどな…と思いつつ、ここではNext.jsでの実現方法を書いておきます。超簡単です!

必要なもの

nextjs-progressbar

以上!

実装方法

まず、当然ですけどnextjs-progressbarをインストールします。

$ yarn add nextjs-progressbar

/pages/_app.js(ない場合は作る)(_app.jsって何、という方はこちらをどうぞ)を編集します。

/pages/_app.js
import React from 'react'
import App from 'next/app'
import NextNprogress from 'nextjs-progressbar' // ここ!

class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props
    return (
      <>
        <NextNprogress /> { /* ここ! */ }
        <Component {...pageProps} />
      </>
    )
  }
}

export default MyApp

ここで一旦宣伝です。

宣伝

先日、@svfreerider さんと共にRemote Club|海外移住や海外フリーランスの情報サイトというサービスを出しました!
既に海外移住した人と、これからしたいと考えている人のつながりを作り、より多くの方々が好きなところで生きていけるようになればいいという思いからRemote Clubを作りました。

少しでも海外移住に興味のある方、既に移住している方は是非Remote Clubを覗いていっていただけると嬉しいです!
フィードバックも待ってます!
よろしくおねがいします!

まとめ

Next.jsでも特に困ることなくプログレスバーを実装できました。
Nuxt.jsだとデフォルトで表示されるし便利、と思ったけど、まぁ必要な人は追加する、というスタンスのほうが自然な気もしました。
遷移先でAPIなどを使って外部からデータを取得する場合は、どうしても遷移に時間がかかり不自然に感じてしまいます(自分は)。
ということで、クライアントでの遷移にはプログレスバー表示したい!というかたは是非どうぞ!

ちなみにnextjs-progressbar作者の方の記事はこちらです。詳しく知りたい方は覗いてみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのUIライブラリ ReactでToDoアプリを作成してみました

はじめに

この記事ではJavaScriptのライブラリであるReactを使用して簡単なToDoアプリの実装を行います。
クライアント側のみの実装になります。
Reactのドキュメントチュートリアル(三目並べ)を一通り行った後の練習になるように書きたいと思います。
環境構築に関しては、create-react-appを使用して作成しています。環境構築に関しては以前書いた記事があります。
もちろん、オレオレな環境でもokです。
Reactについては初心者なので認識の齟齬や到らない点も多々あると思いますが、よろしくお願いします。

目次

  1. 環境準備
  2. コンポーネントの確認
  3. ファイル構成
  4. Reactで保持するデータについて
  5. 各コンポーネントの解説
  6. まとめ

1. 環境準備

以下からソースコードを引っ張ってきます。
ソースコード
gitでクローンした場合は、ブランチはtodo-app-pure-cssです。(何かダサい名前なのは目を瞑っておいてください。。。)
動作を確認するために、ブラウザ上で確認できる環境も用意しました。

1-1. DockerImageのビルド

docker build --rm -f "react-tutorial/Dockerfile" -t react-tutorial:latest "react-tutorial"

1-2. DockerContainerの起動

$ docker run --rm -it -v ${PWD}/app:/home/react-tutorial  -p 3000:3000/tcp react-tutorial:latest /bin/bash
root@03887209ce2d:/home# 

1-3. 追加のパッケージをインストール(コンテナの内部で操作してます)

root@03887209ce2d:/home# cd react-tutorial
root@03887209ce2d:/home/react-tutorial# yarn install

1-4. Reactアプリケーションの起動

root@03887209ce2d:/home/react-tutorial# yarn start

1-5. ブラウザで確認

Reactアプリケーションの起動に成功すると、以下のような表示がされます。

Compiled successfully!

You can now view react-tutorial in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://172.17.0.3:3000/

Note that the development build is not optimized.
To create a production build, use yarn build.

ブラウザでhttp://localhost:3000/を入力して開いてみましょう。
そうすると、下記の様な画面が表示されるはずです。
今回は、この画面をReactで作成していきたいと思います。

SampleReactToDoApp.png

2. コンポーネントの確認

ReactはUIのパーツをコンポーネントという独立した一つの部品とみなして構成していきます。
今回の例では下記の様に分割しました。

ReactToDoApp.png

ToDoアプリケーションを構成するコンポーネントは全部で4つあります。

  • ToDo
    ToDoアプリケーションの全体を表します

  • TaskAdd
    新しいタスクの追加を行います

  • TaskList
    追加されたタスクをリストにして表示します

  • TaskItem
    一つのタスクを表します

3. ファイル構成

ファイル構成を確認しましょう。

 フォルダ構成
.
├── Dockerfile
├── README.md
└── app
    ├── README.md
    ├── package.json
    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   ├── logo192.png
    │   ├── logo512.png
    │   ├── manifest.json
    │   └── robots.txt
    ├── src
    │   ├── App.js
    │   ├── App.scss
    │   ├── components
    │   │   ├── Header.js
    │   │   ├── Task.js
    │   │   ├── TaskAdd.js
    │   │   ├── TaskList.js
    │   │   └── ToDo.js
    │   └── index.js
    ├── yarn-error.log
    └── yarn.lock

沢山のファイルがありますが、今回の記事で注目するのはapp/src以下のファイルのみです。

フォルダ構成
src
├── App.js
├── App.scss
├── components
│   ├── Header.js
│   ├── Task.js
│   ├── TaskAdd.js
│   ├── TaskList.js
│   └── ToDo.js
└── index.js

ファイルの解説(ToDo Appのコンポーネントを除く)

components配下のファイルが先ほど確認した各コンポーネントに対応しています。
後ほど、詳しく見ていきます。

Header.js

Header.jsはアプリのタイトルのToDoを表しているのみです。
公式ドキュメントで出てくるHelloWorldと同じですね。

Header.js
import React from "react";

const Header = () => {
  return (
    <header>
      <h1>ToDo</h1>
    </header>
  );
};

export default Header;

App.scss

App.scssはレイアウトの部分を書いてあります。
デフォルトの状態では見にくいので、見やすい様に少しレイアウトを調整しています。
今回の内容とはあまり関係無いので割愛します。

index.js

index.jsAppコンポーネントをレンダリンしています。
では、Appコンポーネントを見てみましょう。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

App.js

App.jsHeaderコンポーネントToDoコンポーネントをレンダリングしています。

App.js
import React from 'react';
import 'reset-css'
import Header from './components/Header';
import ToDo from './components/ToDo'
import './App.scss';

function App() {
  return (
    <div className="App">
      <Header />
      <ToDo />
    </div>

  );
}

export default App;

4. Reactで保持するデータについて

各コンポーネントの説明に入る前にReactで保持して、各コンポーネントにどんなデータを渡すのかを説明します。
今回のToDoアプリはタスクの作成、更新、削除ができます。
では、ToDo AddコンポーネントTask Listコンポーネントで共通で使用したいデータは何でしょうか??
言い換えると、ToDoコンポーネントで持っておいた方が楽なデータは何でしょうか??
タスクの新規作成時には、既に同じ名前のタスクが登録されていないか確認したいのでタスクのリストTask Listコンポーネントで保持するより
ToDoコンポーネントに持つ方が、ToDo Addコンポーネントが参照しやすく無いでしょうか??
また、今回はDBなどの保存する機能はありませんが後々のことを考慮するとなれば各タスクの識別子(id)が必要になってくると考えれます。
なので、ToDoコンポーネントにはタスクのIDを持たせる様にします。
ここまでを、再度コンポーネントのイメージと一緒に確認すると以下の様になります。

ReactToDoApp-state (2).png

本当は更新した際のステータスもToDoコンポーネントで保持するべきだと思いますが省略。

5. 各コンポーネントの解説

各コンポーネントの関係をイメージにすると以下のようになります。
一つずつ確認しながら、見てみてください。

5-1.全体図

ReactToDoStateFlow.png

5-2. ToDo.js

5-2-1. ソース全体

ToDo.js
import React from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';

class ToDo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      TaskList: [],
      TaskId: 0
    };
    this.deleteTask = this.deleteTask.bind(this);
    this.addTask = this.addTask.bind(this);
  }

  deleteTask(TaskId) {
    var NewTaskList = this.state.TaskList;
    let TaskIndex = 0;
    for (var i = 0; i < NewTaskList.length; i++) {
      if (NewTaskList[i].key.toString() === TaskId.toString()) {
        TaskIndex = i;
      }
    }
    NewTaskList.splice(TaskIndex, 1);
    this.setState({ TaskList: NewTaskList });
  }

  addTask(newTask) {
    let TaskList = this.state.TaskList;
    TaskList.push(newTask);
    this.setState({ TaskList: TaskList });
  }

  render() {

    return (
      <main className='todo-component'>
        <TaskAdd id={this.state.TaskId} addTask={this.addTask} TaskList={this.state.TaskList} deleteTask={this.deleteTask} />
        <TaskList TaskList={this.state.TaskList} />
      </main>
    );
  }
}

export default ToDo; 

5-2-2. モジュールのインポート

import
import React from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';

5-2-3. stateの定義と関数の登録

  • タスクのリストとして TaskList
  • タスクの識別子(id)として TaskId

をstateとして定義しています。

  • deleteTaskはタスクの削除
  • addTaskはタスクの登録

を示しています。

ToDoコンポーネントのconstructor
constructor(props) {
  super(props);
  this.state = {
    TaskList: [],
    TaskId: 0
  };
  this.deleteTask = this.deleteTask.bind(this);
  this.addTask = this.addTask.bind(this);
}

5-2-4. deleteTask

タスクの削除はタスクIDを元にタスクリストから削除対象のリストのインデックスを探します。
そして、インデックスが見つかればタスクリストに対してspliceで対象のタスクの削除を実行します。
削除が完了したら、this.setStateでタスクリストを更新します。

ToDoコンポーネントのdeleteTask
deleteTask(TaskId) {
  var NewTaskList = this.state.TaskList;
  let TaskIndex = 0;
  for (var i = 0; i < NewTaskList.length; i++) {
    if (NewTaskList[i].key.toString() === TaskId.toString()) {
      TaskIndex = i;
    }
  }
  NewTaskList.splice(TaskIndex, 1);
  this.setState({ TaskList: NewTaskList });
}

5-2-5. addTask

新しいタスクの情報をタスクリストにpushして更新しています。
後ほど、説明しますがnewTaskTaskコンポーネントになっています。

ToDoコンポーネントのaddTask
addTask(newTask) {
  let TaskList = this.state.TaskList;
  TaskList.push(newTask);
  this.setState({ TaskList: TaskList });
}

5-2-6. render

TaskAddには、以下のpropが送られています。

  • TaskId
  • TaskList
  • addTask関数
  • deleteTask関数

TaskListにはタスクリストのみがpropとして送られています。

なぜTaskAddの方に削除する関数を持たせているのか疑問に思う方も居ると思いますが
後に解決すると思うので、ここでは送っているということだけ覚えておいてください。

ToDoコンポーネントのrender
render() {
  return (
    <main className='todo-component'>
      <TaskAdd id={this.state.TaskId} addTask={this.addTask} TaskList={this.state.TaskList} deleteTask={this.deleteTask} />
      <TaskList TaskList={this.state.TaskList} />
    </main>
  );
}

5-3. TaskAdd.js

5-3-1. ソース全体

TaskAdd
import React from "react";
import Task from './Task'

class TaskAdd extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            NewTask: '',
            TaskId: this.props.id,
            ErrorMessage: '',
        };
        this.handleClick = this.handleClick.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({ NewTask: event.target.value });
    }
    handleClick() {
        // 空白チェック
        if (this.state.NewTask === '') {
            this.setState({ ErrorMessage: '入力が空です。' })
            return 0
        }
        // 重複チェック
        for (var i = 0; i < this.props.TaskList.length; i++) {
            if (this.props.TaskList[i].props.name === this.state.NewTask) {
                this.setState({ ErrorMessage: 'タスク名が重複しています。' })
                return 0
            }
        }
        let TaskId = this.state.TaskId;
        this.props.addTask(<Task key={TaskId} id={TaskId} name={this.state.NewTask} deleteTask={this.props.deleteTask} />);
        this.setState({ TaskId: TaskId + 1 })
        this.setState({ NewTask: '' })
        this.setState({ ErrorMessage: '' })
    }

    render() {
        return (
            <section className='task-creator'>
                <h2>Task Add</h2>
                <input className='task-item-text' type="text" placeholder="Task" value={this.state.NewTask} onChange={this.handleChange} />
                <button className='task-add-btn' type="button" onClick={this.handleClick}>Add</button>
                <p className='error-msg'>{this.state.ErrorMessage}</p>
            </section>
        );
    }
}

export default TaskAdd; 

5-3-2. モジュールのインポート

タスクを新規に作成するのでTaskコンポーネントをインポートしています。

import
import React from "react";
import Task from './Task'

5-3-3. stateの定義と関数の登録

  • 新規タスク名
  • タスクID(ToDoコンポーネントから受け取ったもの)
  • エラーメッセージ

  • handleClickはAddボタン押下時の関数

  • handleChangeはテキストボックスの変更を検知してstateの新規タスク名を書き換えています

TaskAddコンポーネントのconstructor
constructor(props) {
    super(props);
    this.state = {
        NewTask: '',
        TaskId: this.props.id,
        ErrorMessage: '',
    };
    this.handleClick = this.handleClick.bind(this);
    this.handleChange = this.handleChange.bind(this);

5-3-4. handleChange

シンプルにsetStateで値を書き換えています。
renderの箇所でonChange で指定するとできます。
JavaScriptを書いてた人なら、平常運転的な感じですかね?

TaskAddコンポーネントのhandleChange
handleChange(event) {
    this.setState({ NewTask: event.target.value });
}

5-3-5. handleClick

Addボタン押下時の関数です。
空白チェックや重複チェックは特に言うことは無いと思います。
propsで受け取ったTaskListをここで使用して、重複の確認をしています。
処理の途中で抜けるのにreturn 0にしてるけど、あまりよろしく無いかも。。。。

諸々のチェックが完了すると、ToDoコンポーネントからpropsとして受け取ったaddTask関数を使用してリストに新しいタスクを登録しています。
タスク自体はTaskとしてコンポーネント化しているので、addTaskの引数はTaskになります。

deleteTaskをTaskAddコンポーネントに持たせている理由ですが、タスクIDと削除機能がセットである方が削除処理が書きやすいからです。
そして、削除処理を持たせることができるタイミングがタスクの新規作成時だからです。
何か別の方法も考えられそうですが。。。(状態管理のフレームワークを使わない方向で。。)

最後のsetStateは新しいタスクIDを振ったり、タスクのテキストボックスやエラーメッセージを空白にしているのみです。

TaskAddコンポーネントのhandleClick
handleClick() {
    // 空白チェック
    if (this.state.NewTask === '') {
        this.setState({ ErrorMessage: '入力が空です。' })
        return 0
    }
    // 重複チェック
    for (var i = 0; i < this.props.TaskList.length; i++) {
        if (this.props.TaskList[i].props.name === this.state.NewTask) {
            this.setState({ ErrorMessage: 'タスク名が重複しています。' })
            return 0
        }
    }
    let TaskId = this.state.TaskId;
    this.props.addTask(<Task key={TaskId} id={TaskId} name={this.state.NewTask} deleteTask={this.props.deleteTask} />);
    this.setState({ TaskId: TaskId + 1 })
    this.setState({ NewTask: '' })
    this.setState({ ErrorMessage: '' })
}

5-3-6. render

特に説明はいらないと思います。普通のhtmlタグを並べているだけです。

TaskAddコンポーネントのrender
render() {
    return (
        <section className='task-creator'>
            <h2>Task Add</h2>
            <input className='task-item-text' type="text" placeholder="Task" value={this.state.NewTask} onChange={this.handleChange} />
            <button className='task-add-btn' type="button" onClick={this.handleClick}>Add</button>
            <p className='error-msg'>{this.state.ErrorMessage}</p>
        </section>
    );

5-4. Task.js

5-4-1. ソース全体

Task.js
import React from "react";

class Task extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isDone: this.props.isDone,
        }
        this.handleChange = this.handleChange.bind(this);
    }

    checkTaskStatus(isDone) {
        if (isDone) {
            return 'isDone task-item-label'
        } else {
            return 'WorkInProgress task-item-label'
        }
    }

    handleChange() {
        if (this.state.isDone) {
            this.setState({ isDone: false })
        } else {
            this.setState({ isDone: true })
        }
    }

    render() {
        return (
            <li className='task-item-row'>
                <input id={'task-id-' + this.props.id.toString()} className='task-item-checkbox' type='checkbox' onChange={this.handleChange}></input>
                <label htmlFor={'task-id-' + this.props.id.toString()} className={this.checkTaskStatus(this.state.isDone)}>{this.props.name}</label>
                <i className="material-icons icon" onClick={() => this.props.deleteTask(this.props.id)}>delete</i>
            </li>
        );
    }
}
export default Task; 

5-4-2. stateの定義と関数の登録

stateにはタスクが完了したかどうかを判定するis_Doneフラグ
handleChangeはタスクのチェックボックスのイベント処理に使用しています。

Taskコンポーネントのconstructor
constructor(props) {
    super(props);
    this.state = {
        isDone: this.props.isDone,
    }
    this.handleChange = this.handleChange.bind(this);
}

5-4-3. checkTaskStatus

タスクのラベルにcssのクラスを適応させています。
ちょっと不格好。。

TaskコンポーネントのcheckTaskStatus
checkTaskStatus(isDone) {
    if (isDone) {
        return 'isDone task-item-label'
    } else {
        return 'WorkInProgress task-item-label'
    }

5-4-4. handleChange

stateのis_Doneを見て判定しているだけです。

TaskコンポーネントのhandleChange
handleChange() {
    if (this.state.isDone) {
        this.setState({ isDone: false })
    } else {
        this.setState({ isDone: true })
    }
}

5-4-5. render

普通のhtmlタグにOnChangeやOnClickのイベント処理を追加しているだけです。
onClick={() => this.props.deleteTask(this.props.id)}
こう書けば、handleChangeみたに定義して書かなくていいから楽ですよね。

Taskコンポーネントのrender
render() {
    return (
        <li className='task-item-row'>
            <input id={'task-id-' + this.props.id.toString()} className='task-item-checkbox' type='checkbox' onChange={this.handleChange}></input>
            <label htmlFor={'task-id-' + this.props.id.toString()} className={this.checkTaskStatus(this.state.isDone)}>{this.props.name}</label>
            <i className="material-icons icon" onClick={() => this.props.deleteTask(this.props.id)}>delete</i>
        </li>
    );
}

5-5. TaskList.js

5-5-1. ソース全体

renderでToDoコンポーネントから受け取ったTaskListを表示しているだけです。

TaskList
import React from "react";

class TaskList extends React.Component {
    render() {
        return (
            <section className='task-list'>
                <h2>Task List</h2>
                <ul>
                    {this.props.TaskList}
                </ul>
            </section>
        );
    }
}

export default TaskList; 

6. まとめ

今回は結構なボリュームになってしまいましたが、うまく説明できたでしょうか?(ちょっと心配・・・)
ほんの数日前にReactを改めて勉強しなおして、それっぽいものは作れたかなと思っていたりします。
私は公式のドキュメントやチュートリアルだけでは、なかなか手が進まなくて四苦八苦したので
今回の記事作成の過程でReactと少しは仲良くなれた気がします。
まだまだ、HookやReduxなど関門が立ちはだかっているのが見えますが、地道に取り組んで行きたいと思っています。
また、最後までお読みくださりありがとうございます。
もしかしたら、有識者の方から見たらデタラメな書き方をしているかもしれませんがご容赦ください。
質問、指摘、コメントは大歓迎ですので、よろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ReactでToDoアプリを作成してみました

はじめに

この記事ではJavaScriptのライブラリであるReactを使用して簡単なToDoアプリの実装を行います。
クライアント側のみの実装になります。
Reactのドキュメントチュートリアル(三目並べ)を一通り行った後の練習になるように書きたいと思います。
環境構築に関しては、create-react-appを使用して作成しています。環境構築に関しては以前書いた記事があります。
もちろん、オレオレな環境でもokです。
Reactについては初心者なので認識の齟齬や到らない点も多々あると思いますが、よろしくお願いします。

目次

  1. 環境準備
  2. コンポーネントの確認
  3. ファイル構成
  4. Reactで保持するデータについて
  5. 各コンポーネントの解説
  6. まとめ

1. 環境準備

以下からソースコードを引っ張ってきます。
ソースコード
gitでクローンした場合は、ブランチはtodo-app-pure-cssです。(何かダサい名前なのは目を瞑っておいてください。。。)
動作を確認するために、ブラウザ上で確認できる環境も用意しました。

1-1. DockerImageのビルド

docker build --rm -f "react-tutorial/Dockerfile" -t react-tutorial:latest "react-tutorial"

1-2. DockerContainerの起動

$ docker run --rm -it -v ${PWD}/app:/home/react-tutorial  -p 3000:3000/tcp react-tutorial:latest /bin/bash
root@03887209ce2d:/home# 

1-3. 追加のパッケージをインストール(コンテナの内部で操作してます)

root@03887209ce2d:/home# cd react-tutorial
root@03887209ce2d:/home/react-tutorial# yarn install

1-4. Reactアプリケーションの起動

root@03887209ce2d:/home/react-tutorial# yarn start

1-5. ブラウザで確認

Reactアプリケーションの起動に成功すると、以下のような表示がされます。

Compiled successfully!

You can now view react-tutorial in the browser.

  Local:            http://localhost:3000/
  On Your Network:  http://172.17.0.3:3000/

Note that the development build is not optimized.
To create a production build, use yarn build.

ブラウザでhttp://localhost:3000/を入力して開いてみましょう。
そうすると、下記の様な画面が表示されるはずです。
今回は、この画面をReactで作成していきたいと思います。

SampleReactToDoApp.png

2. コンポーネントの確認

ReactはUIのパーツをコンポーネントという独立した一つの部品とみなして構成していきます。
今回の例では下記の様に分割しました。

ReactToDoApp.png

ToDoアプリケーションを構成するコンポーネントは全部で4つあります。

  • ToDo
    ToDoアプリケーションの全体を表します

  • TaskAdd
    新しいタスクの追加を行います

  • TaskList
    追加されたタスクをリストにして表示します

  • TaskItem
    一つのタスクを表します

3. ファイル構成

ファイル構成を確認しましょう。

 フォルダ構成
.
├── Dockerfile
├── README.md
└── app
    ├── README.md
    ├── package.json
    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   ├── logo192.png
    │   ├── logo512.png
    │   ├── manifest.json
    │   └── robots.txt
    ├── src
    │   ├── App.js
    │   ├── App.scss
    │   ├── components
    │   │   ├── Header.js
    │   │   ├── Task.js
    │   │   ├── TaskAdd.js
    │   │   ├── TaskList.js
    │   │   └── ToDo.js
    │   └── index.js
    ├── yarn-error.log
    └── yarn.lock

沢山のファイルがありますが、今回の記事で注目するのはapp/src以下のファイルのみです。

フォルダ構成
src
├── App.js
├── App.scss
├── components
│   ├── Header.js
│   ├── Task.js
│   ├── TaskAdd.js
│   ├── TaskList.js
│   └── ToDo.js
└── index.js

ファイルの解説(ToDo Appのコンポーネントを除く)

components配下のファイルが先ほど確認した各コンポーネントに対応しています。
後ほど、詳しく見ていきます。

Header.js

Header.jsはアプリのタイトルのToDoを表しているのみです。
公式ドキュメントで出てくるHelloWorldと同じですね。

Header.js
import React from "react";

const Header = () => {
  return (
    <header>
      <h1>ToDo</h1>
    </header>
  );
};

export default Header;

App.scss

App.scssはレイアウトの部分を書いてあります。
デフォルトの状態では見にくいので、見やすい様に少しレイアウトを調整しています。
今回の内容とはあまり関係無いので割愛します。

index.js

index.jsAppコンポーネントをレンダリンしています。
では、Appコンポーネントを見てみましょう。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

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

App.js

App.jsHeaderコンポーネントToDoコンポーネントをレンダリングしています。

App.js
import React from 'react';
import 'reset-css'
import Header from './components/Header';
import ToDo from './components/ToDo'
import './App.scss';

function App() {
  return (
    <div className="App">
      <Header />
      <ToDo />
    </div>

  );
}

export default App;

4. Reactで保持するデータについて

各コンポーネントの説明に入る前にReactで保持して、各コンポーネントにどんなデータを渡すのかを説明します。
今回のToDoアプリはタスクの作成、更新、削除ができます。
では、ToDo AddコンポーネントTask Listコンポーネントで共通で使用したいデータは何でしょうか??
言い換えると、ToDoコンポーネントで持っておいた方が楽なデータは何でしょうか??
タスクの新規作成時には、既に同じ名前のタスクが登録されていないか確認したいのでタスクのリストTask Listコンポーネントで保持するより
ToDoコンポーネントに持つ方が、ToDo Addコンポーネントが参照しやすく無いでしょうか??
また、今回はDBなどの保存する機能はありませんが後々のことを考慮するとなれば各タスクの識別子(id)が必要になってくると考えれます。
なので、ToDoコンポーネントにはタスクのIDを持たせる様にします。
ここまでを、再度コンポーネントのイメージと一緒に確認すると以下の様になります。

ReactToDoApp-state (2).png

本当は更新した際のステータスもToDoコンポーネントで保持するべきだと思いますが省略。

5. 各コンポーネントの解説

各コンポーネントの関係をイメージにすると以下のようになります。
一つずつ確認しながら、見てみてください。

5-1.全体図

ReactToDoStateFlow.png

5-2. ToDo.js

5-2-1. ソース全体

ToDo.js
import React from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';

class ToDo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      TaskList: [],
      TaskId: 0
    };
    this.deleteTask = this.deleteTask.bind(this);
    this.addTask = this.addTask.bind(this);
  }

  deleteTask(TaskId) {
    var NewTaskList = this.state.TaskList;
    let TaskIndex = 0;
    for (var i = 0; i < NewTaskList.length; i++) {
      if (NewTaskList[i].key.toString() === TaskId.toString()) {
        TaskIndex = i;
      }
    }
    NewTaskList.splice(TaskIndex, 1);
    this.setState({ TaskList: NewTaskList });
  }

  addTask(newTask) {
    let TaskList = this.state.TaskList;
    TaskList.push(newTask);
    this.setState({ TaskList: TaskList });
  }

  render() {

    return (
      <main className='todo-component'>
        <TaskAdd id={this.state.TaskId} addTask={this.addTask} TaskList={this.state.TaskList} deleteTask={this.deleteTask} />
        <TaskList TaskList={this.state.TaskList} />
      </main>
    );
  }
}

export default ToDo; 

5-2-2. モジュールのインポート

import
import React from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';

5-2-3. stateの定義と関数の登録

  • タスクのリストとして TaskList
  • タスクの識別子(id)として TaskId

をstateとして定義しています。

  • deleteTaskはタスクの削除
  • addTaskはタスクの登録

を示しています。

ToDoコンポーネントのconstructor
constructor(props) {
  super(props);
  this.state = {
    TaskList: [],
    TaskId: 0
  };
  this.deleteTask = this.deleteTask.bind(this);
  this.addTask = this.addTask.bind(this);
}

5-2-4. deleteTask

タスクの削除はタスクIDを元にタスクリストから削除対象のリストのインデックスを探します。
そして、インデックスが見つかればタスクリストに対してspliceで対象のタスクの削除を実行します。
削除が完了したら、this.setStateでタスクリストを更新します。

ToDoコンポーネントのdeleteTask
deleteTask(TaskId) {
  var NewTaskList = this.state.TaskList;
  let TaskIndex = 0;
  for (var i = 0; i < NewTaskList.length; i++) {
    if (NewTaskList[i].key.toString() === TaskId.toString()) {
      TaskIndex = i;
    }
  }
  NewTaskList.splice(TaskIndex, 1);
  this.setState({ TaskList: NewTaskList });
}

5-2-5. addTask

新しいタスクの情報をタスクリストにpushして更新しています。
後ほど、説明しますがnewTaskTaskコンポーネントになっています。

ToDoコンポーネントのaddTask
addTask(newTask) {
  let TaskList = this.state.TaskList;
  TaskList.push(newTask);
  this.setState({ TaskList: TaskList });
}

5-2-6. render

TaskAddには、以下のpropが送られています。

  • TaskId
  • TaskList
  • addTask関数
  • deleteTask関数

TaskListにはタスクリストのみがpropとして送られています。

なぜTaskAddの方に削除する関数を持たせているのか疑問に思う方も居ると思いますが
後に解決すると思うので、ここでは送っているということだけ覚えておいてください。

ToDoコンポーネントのrender
render() {
  return (
    <main className='todo-component'>
      <TaskAdd id={this.state.TaskId} addTask={this.addTask} TaskList={this.state.TaskList} deleteTask={this.deleteTask} />
      <TaskList TaskList={this.state.TaskList} />
    </main>
  );
}

5-3. TaskAdd.js

5-3-1. ソース全体

TaskAdd
import React from "react";
import Task from './Task'

class TaskAdd extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            NewTask: '',
            TaskId: this.props.id,
            ErrorMessage: '',
        };
        this.handleClick = this.handleClick.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        this.setState({ NewTask: event.target.value });
    }
    handleClick() {
        // 空白チェック
        if (this.state.NewTask === '') {
            this.setState({ ErrorMessage: '入力が空です。' })
            return 0
        }
        // 重複チェック
        for (var i = 0; i < this.props.TaskList.length; i++) {
            if (this.props.TaskList[i].props.name === this.state.NewTask) {
                this.setState({ ErrorMessage: 'タスク名が重複しています。' })
                return 0
            }
        }
        let TaskId = this.state.TaskId;
        this.props.addTask(<Task key={TaskId} id={TaskId} name={this.state.NewTask} deleteTask={this.props.deleteTask} />);
        this.setState({ TaskId: TaskId + 1 })
        this.setState({ NewTask: '' })
        this.setState({ ErrorMessage: '' })
    }

    render() {
        return (
            <section className='task-creator'>
                <h2>Task Add</h2>
                <input className='task-item-text' type="text" placeholder="Task" value={this.state.NewTask} onChange={this.handleChange} />
                <button className='task-add-btn' type="button" onClick={this.handleClick}>Add</button>
                <p className='error-msg'>{this.state.ErrorMessage}</p>
            </section>
        );
    }
}

export default TaskAdd; 

5-3-2. モジュールのインポート

タスクを新規に作成するのでTaskコンポーネントをインポートしています。

import
import React from "react";
import Task from './Task'

5-3-3. stateの定義と関数の登録

  • 新規タスク名
  • タスクID(ToDoコンポーネントから受け取ったもの)
  • エラーメッセージ

  • handleClickはAddボタン押下時の関数

  • handleChangeはテキストボックスの変更を検知してstateの新規タスク名を書き換えています

TaskAddコンポーネントのconstructor
constructor(props) {
    super(props);
    this.state = {
        NewTask: '',
        TaskId: this.props.id,
        ErrorMessage: '',
    };
    this.handleClick = this.handleClick.bind(this);
    this.handleChange = this.handleChange.bind(this);

5-3-4. handleChange

シンプルにsetStateで値を書き換えています。
renderの箇所でonChange で指定するとできます。
JavaScriptを書いてた人なら、平常運転的な感じですかね?

TaskAddコンポーネントのhandleChange
handleChange(event) {
    this.setState({ NewTask: event.target.value });
}

5-3-5. handleClick

Addボタン押下時の関数です。
空白チェックや重複チェックは特に言うことは無いと思います。
propsで受け取ったTaskListをここで使用して、重複の確認をしています。
処理の途中で抜けるのにreturn 0にしてるけど、あまりよろしく無いかも。。。。

諸々のチェックが完了すると、ToDoコンポーネントからpropsとして受け取ったaddTask関数を使用してリストに新しいタスクを登録しています。
タスク自体はTaskとしてコンポーネント化しているので、addTaskの引数はTaskになります。

deleteTaskをTaskAddコンポーネントに持たせている理由ですが、タスクIDと削除機能がセットである方が削除処理が書きやすいからです。
そして、削除処理を持たせることができるタイミングがタスクの新規作成時だからです。
何か別の方法も考えられそうですが。。。(状態管理のフレームワークを使わない方向で。。)

最後のsetStateは新しいタスクIDを振ったり、タスクのテキストボックスやエラーメッセージを空白にしているのみです。

TaskAddコンポーネントのhandleClick
handleClick() {
    // 空白チェック
    if (this.state.NewTask === '') {
        this.setState({ ErrorMessage: '入力が空です。' })
        return 0
    }
    // 重複チェック
    for (var i = 0; i < this.props.TaskList.length; i++) {
        if (this.props.TaskList[i].props.name === this.state.NewTask) {
            this.setState({ ErrorMessage: 'タスク名が重複しています。' })
            return 0
        }
    }
    let TaskId = this.state.TaskId;
    this.props.addTask(<Task key={TaskId} id={TaskId} name={this.state.NewTask} deleteTask={this.props.deleteTask} />);
    this.setState({ TaskId: TaskId + 1 })
    this.setState({ NewTask: '' })
    this.setState({ ErrorMessage: '' })
}

5-3-6. render

特に説明はいらないと思います。普通のhtmlタグを並べているだけです。

TaskAddコンポーネントのrender
render() {
    return (
        <section className='task-creator'>
            <h2>Task Add</h2>
            <input className='task-item-text' type="text" placeholder="Task" value={this.state.NewTask} onChange={this.handleChange} />
            <button className='task-add-btn' type="button" onClick={this.handleClick}>Add</button>
            <p className='error-msg'>{this.state.ErrorMessage}</p>
        </section>
    );

5-4. Task.js

5-4-1. ソース全体

Task.js
import React from "react";

class Task extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isDone: this.props.isDone,
        }
        this.handleChange = this.handleChange.bind(this);
    }

    checkTaskStatus(isDone) {
        if (isDone) {
            return 'isDone task-item-label'
        } else {
            return 'WorkInProgress task-item-label'
        }
    }

    handleChange() {
        if (this.state.isDone) {
            this.setState({ isDone: false })
        } else {
            this.setState({ isDone: true })
        }
    }

    render() {
        return (
            <li className='task-item-row'>
                <input id={'task-id-' + this.props.id.toString()} className='task-item-checkbox' type='checkbox' onChange={this.handleChange}></input>
                <label htmlFor={'task-id-' + this.props.id.toString()} className={this.checkTaskStatus(this.state.isDone)}>{this.props.name}</label>
                <i className="material-icons icon" onClick={() => this.props.deleteTask(this.props.id)}>delete</i>
            </li>
        );
    }
}
export default Task; 

5-4-2. stateの定義と関数の登録

stateにはタスクが完了したかどうかを判定するis_Doneフラグ
handleChangeはタスクのチェックボックスのイベント処理に使用しています。

Taskコンポーネントのconstructor
constructor(props) {
    super(props);
    this.state = {
        isDone: this.props.isDone,
    }
    this.handleChange = this.handleChange.bind(this);
}

5-4-3. checkTaskStatus

タスクのラベルにcssのクラスを適応させています。
ちょっと不格好。。

TaskコンポーネントのcheckTaskStatus
checkTaskStatus(isDone) {
    if (isDone) {
        return 'isDone task-item-label'
    } else {
        return 'WorkInProgress task-item-label'
    }

5-4-4. handleChange

stateのis_Doneを見て判定しているだけです。

TaskコンポーネントのhandleChange
handleChange() {
    if (this.state.isDone) {
        this.setState({ isDone: false })
    } else {
        this.setState({ isDone: true })
    }
}

5-4-5. render

普通のhtmlタグにOnChangeやOnClickのイベント処理を追加しているだけです。
onClick={() => this.props.deleteTask(this.props.id)}
こう書けば、handleChangeみたに定義して書かなくていいから楽ですよね。

Taskコンポーネントのrender
render() {
    return (
        <li className='task-item-row'>
            <input id={'task-id-' + this.props.id.toString()} className='task-item-checkbox' type='checkbox' onChange={this.handleChange}></input>
            <label htmlFor={'task-id-' + this.props.id.toString()} className={this.checkTaskStatus(this.state.isDone)}>{this.props.name}</label>
            <i className="material-icons icon" onClick={() => this.props.deleteTask(this.props.id)}>delete</i>
        </li>
    );
}

5-5. TaskList.js

5-5-1. ソース全体

renderでToDoコンポーネントから受け取ったTaskListを表示しているだけです。

TaskList
import React from "react";

class TaskList extends React.Component {
    render() {
        return (
            <section className='task-list'>
                <h2>Task List</h2>
                <ul>
                    {this.props.TaskList}
                </ul>
            </section>
        );
    }
}

export default TaskList; 

6. まとめ

今回は結構なボリュームになってしまいましたが、うまく説明できたでしょうか?(ちょっと心配・・・)
ほんの数日前にReactを改めて勉強しなおして、それっぽいものは作れたかなと思っていたりします。
私は公式のドキュメントやチュートリアルだけでは、なかなか手が進まなくて四苦八苦したので
今回の記事作成の過程でReactと少しは仲良くなれた気がします。
まだまだ、HookやReduxなど関門が立ちはだかっているのが見えますが、地道に取り組んで行きたいと思っています。
また、最後までお読みくださりありがとうございます。
もしかしたら、有識者の方から見たらデタラメな書き方をしているかもしれませんがご容赦ください。
質問、指摘、コメントは大歓迎ですので、よろしくお願いします。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む