【無料!】 Reactの基礎を学んでTODOアプリを作ろう!
大好評で100部以上売れたReactのチュートリアルの内容が2年前の古い内容となってしまっていたので、アップデートすることにしました
ただ、古い内容とはいえせっかく書いた記事で、またまだまだ基礎の勉強と古き良きReactを学ぶことのできる教材だと思ったので無料公開します!
前回のnoteを買っていただいた方(特に最近買っていただいた方)が引き続きアップデート版を見ていただけるように、こちらのチュートリアルを新しいnoteとして公開しています。
では、本題です。
progateでrailsまで実装したけど、なんか古臭いアプリしか作れない、、そんな悩みありませんか?
それもそのはず。最近のwebアプリはフロントをreactなどでリッチに仕上げているからです。
でも、reactを勉強しようとすると、どうしても情報が点在していて僕も苦労した記憶があるので、これから勉強する人の手助けになればと思いこのreactチュートリアルを作りました。
このチュートリアルのゴールは以下のようなアプリが作れるようになることです。
構成
必要に応じて用語解説や理論の解説もしますが、基本的にはイメージでreactの概念を理解して、とにかく書けるようになることを目的としたチュートリアルにしようと思っています。
というのも説明を書いてみたのですが、どうしても専門用語が多くなってしまい理解に時間がかかってしまうので、とにかく動くreactのコードを書いてreactの楽しさを知ってもらうことに重点を置くことにしました。
楽しくなって興味が出たら理論を学んでいただけたらと思います。
このような構成になっております。
対象
この記事はjavascript(以下js)をある程度勉強した次のステップとなっております。ドットインストールを一通りやっていれば十分だと思います。pragate等でも問題ありません。
0章 reactを勉強する前に
reactを勉強する前にes6(es2015)というものについて解説します。これは簡単にいうとjsの新機能です。jsでがっつり開発をしている人ならわかると思うのですが、従来のjsってクソなんですよ。
それで散々disられてたんですけど、2015年にアップデートされました。これによってjsはモダンな言語として再度注目を浴びるようになりました。
ほとんどのreactの入門記事ではes6知ってるよね?という前提で話が進んでいます。多くの人が通る本家のreactチュートリアルもes6の解説はほとんどありません。ちなみにreactは従来のjsでも書けるのですがコードが冗長になってしまうので、最近ではほとんどのコードがes6で書かれています。
そこで、reactをやる上で最低限知っておきたいes6の書き方について説明していきます。ここを詳しくやるとそれだけで挫折してしまうので、こんなもんなんだと思って進めてもらったらいいと思います。
僕もそんなに使わない構文だと未だに理解できてないのとか知らない構文もあります。それでも大規模なreactの開発をできているのでご安心いただけたらと思います。
では、実際に見ていきましょう。
letとconst
es6で一番よく使う記法です。jsは本来varによって宣言するのですが、es6では、変数をlet、定数をconstで定義します。
定数を使うことに違和感があるかもしれません。値を変更したいときにどうするんだ?って思うかもしれないですが、そのときは元の値を直接いじらずに新たに宣言した定数に元の値を変更したものを代入するという方法をとります。
let a = 1; // 変数を宣言
a += 1; // aにa+1を代入
console.log(a); // => 2
const b = 1;
// b += 1; // 定数だからエラー
const c = b + 1 // 新たに定数を宣言して計算結果を代入
console.log(b); // => 1
console.log(c); // => 2
reactでは安全性の向上のために定数を使うんだと思っておけば大丈夫です。(関数型の考え方です)
これを理解しないと先に進めないというわけではないので、reactができるようになってから、気になる人は勉強してみてください。
また、厳密にはvarとletには違いがありますが、基本的にはletを使うものなんだと思っていてもらったら大丈夫です。(スコープに違いがあります。)
アロー関数
=と>を使って=>という矢印を用いることからそのように呼ばれています。
調べると無名関数云々と書いてありますが、要は定数みたいな感じで関数を宣言できるよってことです。というかもはやこういう書き方をするもんなんだと思っていて問題ありません。
ちょっと雑になってしまって語弊があるかもしれないですが、reactを書けるようになるにはこれぐらいの理解で十分です。あとで解説しますが、関数型コンポーネントの説明で出てきます。
const HelloWorld = () => {
return "Hello World!!";
}
// const HelloWorld = () => "Hello World!!"; // 1行でも書ける
console.log(HelloWorld()); // => Hello World!!
クラス構文
これは、他の言語と同じです。reactに限っていうと使い方が限定されているので、そこまで理解する必要はないです。extendsによってクラスの継承もできるのですが、初めのうちはextends以下はおまじないだと思っていたらいいでしょう。僕も半年ぐらいおまじないだと思っていました。
class HelloWorld extends React.Component {
render() {
return (
<div>
HelloWorld
</div>
);
}
}
デフォルト引数
これ調べるまでjsの標準の機能だと思っていました。これは結構使うやつで、関数の引数に何も渡されなかったら、このデフォルト引数が引数として用いられます。
const PlusOne = (a = 1) => { // aのデフォルト値が1という意味
return a + 1;
}
console.log(PlusOne()); // => 1 + 1 = 2
console.log(PlusOne(100)); // => 100 + 1 = 101
テンプレート文字列
これも標準装備だと思ってました。文字列の中で変数を展開できます。状況に応じてHTMLのclassの名前を変更したいときなどによく用います。shift + @のちょんちょんです。
const Hello = (name) => `${name}, Hello!`, ;
console.log(Hello("Tom")); // Tom, Hello!
スプレッド演算子
これよく使われてるんですけど、知識がないと意味わからない上に"..."でgoogle検索しても全然違うものがヒットしてしまいます。これだけは、名前と意味を覚えておいた方がいいです。内容としては、配列やオブジェクトを展開できます。
array = [1, 2, 3];
console.log(...array); // 1 2 3
分割代入(追記)
これもes6でよく使われる記法です。
const state = {
hoge: "hoge",
fuga: "fuga"
}
っていうオブジェクトがあったときに、
const { hoge, fuga } = state
としてやると、それぞれのhoge, fugaに分割して値を代入することができます。reactでは、this.state.hogeみたいな書き方になって冗長なのでこのような書き方が好まれる傾向があります。
const { hoge: Hoge, piyo = "piyo" } = state
console.log(Hoge) // "hoge"
console.log(hoge) // undefined
console.log(piyo) // "piyo"
こんな感じで初期値や名前を変えて代入することもできるのでけっこう便利です。
押さえておきたいes6の構文は以上です。一応一通り目を通してほしいのですが、これだけ見ても実感がわかないと思います。そこでこの章を辞書的に使っていただけたらと思います。これ以降の章でもできるだけこれはes6だから0章みてねってことを書いていきたいと思います。
以下に参考にさせてもらったQiitaの記事を載せておきます。詳しく知りたいかたやよくわからなかった方は以下を参考にしてください。
1章 Reactとは
やっと本題のreactです。reactとはfacebook製のjsのライブラリです。ライブラリとはjsだけではコードをいっぱい書かないといけなくなってしまうので、それを使いやすいように賢い人が頑張って作ってくれたものです。reactはfacebookの賢い人たちが作ったかなり有能なライブラリです。
本家の説明によると、
うーん、よくわかりませんね。簡単にいうと、フロントエンドといってユーザーが実際に触る部分(=UI, ユーザーインターフェイス)を開発するときに利用されます。特にリロードを必要としないwebアプリでの利用が広まっています。
web版のTwitterとかわざわざリロードしなくてもツイートを読み込めます。そういったリアルタイム更新的な機能が簡単に作れます。
それぐらいならjQueryでもできそうと思うかもしれません。確かにできるんですが、reactの方がより柔軟に変更できる仕組み(=仮想 DOM)をもっているのと、規模が大きくなってもreactは管理がしやすくなっています。
では、次にreactの重要な用語の解説をしていきます。
コンポーネント
reactそのものと言っても過言ではないのに、なぜか説明がおろそかにされています。直訳すると部品、構成要素です。コードで書くと、
class HelloWorld extends React.Component {
render() {
return (
<div>
HelloWorld
</div>
);
}
}
こうです。先ほどのes6の説明のところで使ったコードと同じです。ちょっとややこしくなってきましたね。
一個のクラスが一個の部品(=コンポーネント)になると理解しておけば大丈夫です。ちなみにクラス以外にも関数もコンポーネントを作ることができます。
そして、コンポーネントという名前の通りこれらは部品にすぎず、組み合わせることでアプリケーションに仕上げていきます。
たとえば、TODOアプリを作るとしたら、全体を包むApp Component、TODOを登録するAddTodo Component、登録したTODOを並べるTodoContainer Component、TODOの内容を表示させるTodoElement Componentを上の図のように組み合わせて作ります。
なぜこのようにコンポーネントを分けるのか基準は今はわからなくて大丈夫です。コンポーネントのイメージができればOKです。
props
これは親コンポーネントから渡ってきた情報を参照するときに使います。なぜこのような仕組みが必要かというと、コンポーネント内の情報は基本的に他のコンポーネントから参照できないからです。
これを他のコンポーネントに渡す仕組みがpropsです。propsを指定してやることで、子コンポーネントに情報を渡すことができます。
また、親コンポーネントからpropsで渡ってきた関数を実行することで親コンポーネントに情報を提供することができます。
例えば、TODOアプリではAppでTODOの情報を管理して、その情報をTodoContainer → TodoElement と伝えることで個々のTODOの情報を表示します。この辺りは実装しないと実感が掴めないと思います。
データをバケツリレーしていくイメージです。上の図では矢印がpropsとして受け渡されるデータを表しています。
state
stateは各コンポーネント内でのみ管理することができる情報のことをさします。管理とは、何かイベント(例えばユーザーがそのコンポーネントをクリックした)が起きたときに更新することができる、ということです。
この情報を表示している部分がUI(画面)です。reactではこの情報を簡単にHTMLにできます。stateが変更されるとそれがすぐにUIに反映されます。
TODOでは、AppでTODOに関する情報を管理してTODOが追加されるというイベントに対してstateの更新をする実装が望ましいでしょう。そしてTODOが追加さると、そのTODOがすぐにUIに表示されます。
2章 環境構築
reactには、最新のjsで書くといいましたが、実はこれ対応していないブラウザがあるんですよね。全く画面が表示されないなんてこともあり得るので、対応させる必要があります。その際にwebpackというトランスパイラを使ってトランスパイルします。
???
もうわけわからない、、って人もいると思います。ここでトランスパイルの説明をしてもいいんですが、もう嫌ってなっちゃう人もいると思うので、ここでは具体的にどうしたらいいのかの説明だけに留めます。
まず、ブラウザ上で実行できるサービスがあるのでとりあえず始めたい!って人はこちらを使っていただけたらと思います。
https://codepen.io/anon/pen/jxMmWw?editors=0010
準備しておいたので、forkしてから使っていただくようにお願いします。
他にもjsfiledというサービスでもかけるみたいですね。
まずは、ブラウザ上での実行をしてみるといいと思います。ここで、雰囲気を掴んでからでも環境構築は遅くありません。
自分で環境構築してみたい!という人に向けても簡単に解説していきます。公式のインストーラーを使うように書いていたのですが、あまり良くないので変更しました。
まずはNode.jsをインストールします。インストールにはbrewというMacのパッケージマネージャーを使います。コマンドライン版のAppStoreみたいなイメージです。
ここのサイトに詳しいことは書いてるのですが、要は
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
を実行すればおkです。次にNode.jsをインストールします。
$ brew install node
これでインストールされてnodeコマンドおよびnpmコマンドが使えるようになります。(npmはNode.jsのパッケージマネージャーです。)
次に、create-react-appというものをダウンロードします。これは、コマンド一つでreactの環境構築ができてしまうという優れものです。
$ npm i -g create-react-app
で、インストールできます。使い方は、
$ npx create-react-app myapp
のような感じです。最後のmyappは自分で好きなappの名前をつけることができます。このときに必要なライブラリをインストールするので少し時間がかかります。そして、
$ cd myapp
$ npm start
と、することで開発用のサーバーが立ち上がり、http://localhost:3000に最初の画面が開いてることが確認できると思います。
これで、開発する準備ができたので次から実際にコードを書いていきましょう!
さらにカスタマイズしたい方はwebpackやbabelのインストールから自分でやってみてください。
僕が構築したものをQiitaに投稿してあるのでそちらも参考にしてみてください。
3章 HelloWorld
この章では、HelloWorldと表示させるだけのコンポーネントを作成しましょう。まずは、コンポーネントは何で実装できるか覚えていますか?そうですね、jsのクラス構文でしたね。
class HelloWorld extends React.Component {
render() {
return(
<div>
HelloWorld
</div>
);
}
}
これがコンポーネントの最小単位です。コンポーネントは必ずrender関数を持たなければならなく、そのrender関数は必ず一つのjsxを返さなければいけません。jsxは、HTMLの要素 + reactのコンポーネントと思っていただければ十分です。
返すjsxは0でも2個以上でもだめです。
render() {
return(
<div>
HelloWorld
</div>
<div>
HelloWorld2
</div>
);
}
このようにしてしまうと、エラーが出て何も表示されないはずです。一度やってみてエラーを確認してみてください。このエラーは自分で開発するときに度々みることになるでしょう。僕はそのエラーを見た瞬間にわかります。
そして、このrender関数の返り値が実際にHTML(正確には仮想DOM)として画面上に表示(render)されます。
このコンポーネントの場合は 、
<div>
HelloWorld
</div>
が、HTMLとして作られ、画面にHelloWorldが表示されることになります。
では、これはどこに表示されるのでしょうか。それを指定してやるために、
ReactDOM.render(
<HelloWorld />, // HelloWorldコンポーネントを
document.getElementById('root') // id='root'の要素にrenderする
);
と記述します。これの意味は、ReactDOMというReactとは別のライブラリのrender関数に、表示させたいコンポーネントと、表示させたい場所を指定してやります。
例で用いたReactDOM.renderでは、HelloWorldコンポーネントを
画面に表示されている<div id='root'></div>内にrenderするという意味です。
4章 カウンターAppで学ぶstate
TODOアプリを作る前に、簡単なカウンターAppを作ってstateを学んでみましょう。
この章では、上のようなカウンターアプリを作ってみます。実はめちゃくちゃ簡単に実装できてしまいます。
まずは、stateの復習から始めましょう。
このアプリはCountAppコンポーネントだけでできていて、countというstateを持っています。stateとはそのコンポーネントの情報を管理しているものでしたね。
そして、+ボタンを押すというクリックイベントに対してこのcountが+1されて、-ボタンを押すというクリックイベントに対してcountが-1されます。
実際にコードを書いて方が理解が進むので実装していきましょう。
まずは、何も機能を持たないstatalessコンポーネントを実装してみましょう。
class CountApp extends Component {
render() {
return (
<div>
<h1>カウンターApp</h1>
<p>0</p>
<div>
<button>+</button>
<button>-</button>
</div>
</div>
);
}
}
このような実装になります。カウントの表示は一旦0としておきましょう。
次にstateを初期化してその数字を表示させます。
class CountApp extends Component {
constructor() {
super()
this.state={
count: 0,
}
}
render() {
return (
<div>
<h1>カウンターApp</h1>
<p>{this.state.count}</p>
<div>
<button>+</button>
<button>-</button>
</div>
</div>
);
}
}
まず、初期化にはcostructorという特別な関数を使います。これはclassが初期化されるときに呼ばれるメソッドです。一番はじめに呼ばれるものなんだと思っておいてもらえれば十分です。
constructor() {
super()
}
これはおまじないです。深入りせずにそう書くものなんだと思ってください。追加でstateを初期化したいときには、this.stateにObjectの形で代入することでstateを定義できます。今回はstateを0で初期化したしました。
では、次にstateを画面に表示させてみましょう。render関数内で、
<p>{this.state.count}</p>
という書き方をしてやると文字列ではなく変数として認識しくれます。これで画面を見てみると、先ほどと同じように0が表示されていることでしょう。
次に、カウンター機能を実装してみましょう。ここからがreactの本領発揮です。
まずは、このようにstateを更新する関数を定義します
class CountApp extends Component {
constructor() {
...
}
plus() {
this.setState({count: this.state.count + 1})
}
render() {
...
}
}
constructorやrenderと同じように定義しましょう。そして、setState関数というものを呼び出して、stateを更新します。書き方はこういうものなんだという風に理解しておけば十分です。thisは特に難しいので深追いしないようにしましょう。
ただし、stateを直接変更してはいけないということだけは覚えておいてください。
this.state.count = this.state.count + 1 // だめ!!
stateはImmutable(不変)であるべきとされています。reactのパフォーマンスに影響するようです。関数型の考え方に近いですかね。
次に、この関数をプラスボタンが押されたときに呼び出されるように実装しましょう。
<button onClick={() => this.plus()}>+</button>
たったこれだけです。最初は違和感があるかもしてれませんが、すぐになれるのでこういうものなんだと思ってください。{}の中をアロー関数にすることで、thisの参照が変わってしまうのを固定するためという理由なのですが、これを理解できなくても十分にreactは実装できます。僕は、'this' is not definedって言われたら追記するようにしています。
これで、やっとプラスボタンが動くようになったと思います。マイナスボタンも実装してみましょう。
class CountApp extends Component {
constructor() {
super()
this.state={
count: 0,
}
}
plus() {
this.setState({count: this.state.count + 1})
}
minus() {
this.setState({count: this.state.count - 1})
}
render() {
return (
<div>
<h1>カウンターApp</h1>
<p>{this.state.count}</p>
<div>
<button onClick={() => this.plus()}>+</button>
<button onClick={() => this.minus()}>-</button>
</div>
</div>
);
}
}
最終的にはこのような形になります。カウンターAppの完成です。
わざわざsetStateを外に出さなくてもrender関数内で呼べばいいじゃないかと思うかもしれませんが、絶対にダメです。
<button onClick={this.setState({count: this.state.count + 1})}>+</button>
render関数はpureな関数であるべきとされています。どういうことかというと、render関数内でstateを変更してはいけないということです。なぜかというと、stateの変更が起こるとrender関数が呼ばれるようになっているので無限ループになってしまいます。
今はわからなくても、そのうち出会うことになります。そのときに理解すればいいでしょう。今はこのような書き方が一般的だと思っていただければと思います。
補足
<button onClick={() => this.setState({ count: this.state.count + 1 })}>+</button>
これは動きます。
5章 TODOアプリの雛形を作る
この章では、上のように入力した文字をリストで表示させるという、TODOアプリの雛形を作るところまでやってみましょう。この章では一つのコンポーネントで実装します。次の章で二つ以上のコンポーネントに分割していきます。
この章ぐらいから、少しずつ説明を減らしていきます。前の章まででやったことはあまり説明しません。そしないと思考が停止してしまうからです。
まずは、reactでのformの扱いについて説明します。reactでは、inputなどのformを使うときはそのvalueをstateで管理しないといけません。
<input
type="text"
value={this.state.value}
onChange={e => this.onChange(e)}
/>
少し横に長くなったので、改行して表示してみました。このような書き方もよくされます。
ここで重要なのが、変更があったときに逐一stateを更新しないといけないということです。そして、inputのvalueはstateを参照していないといけません。
<input
type="text"
value=""
onChange={e => this.onChange(e)}
/>
<input
type="text"
value={this.state.value}
/>
たとえば、上記のようにしてしまうとどちらも全く編集できないinputになってしまいます。
<input type="text" />
このようにすると入力自体はできるのですが、肝心のreactで管理できていないのでうまく扱えません。(DOMを直接いじればなんとかなりますが、それではreactを使う意味がない。)
では、inputのvalueがstateで管理できているところを確認してみましょう。
class TodoApp extends Component {
constructor() {
super()
this.state={
value: "",
}
}
onChange(e) {
this.setState({value: e.target.value})
}
render() {
return (
<div>
<h1>TODO App</h1>
<input
type="text"
value={this.state.value}
onChange={e => this.onChange(e)}
/>
<p>{this.state.value}</p>
</div>
);
}
}
まずは、valueを初期化します。そして先ほどのinputをrender内に書きます。変更があったときにはonChangeが呼ばれます。eに関しては少しややこしいので、e.target.valueでinputのvalueを取得できるんだと思ってください。
これで、valueをstateで管理することができるようになりました。試しに、<p>{this.state.value}</p>で出力してみると、リアルタイムでstateが更新されていることがわかると思います。
では次に、TODOを追加できるようにしましょう。todoListというstateを新たに追加します。このstateは文字列の入った配列にします。そうすることでjsのmapで各TODOを取り出すことができるからです。
では、ここまでを実装してみましょう。先ほどの<p>{this.state.value}</p>はもう必要ないので消してもらって大丈夫です。
class TodoApp extends Component {
constructor() {
super()
this.state={
todoList: [],
value: "",
}
}
onChange(e) {
this.setState({value: e.target.value})
}
add() { // 新たに追加
this.setState({
todoList: this.state.todoList.concat(this.state.value)
})
}
render() {
return (
<div>
<h1>TODO App</h1>
<div>
<input
type="text"
value={this.state.value}
onChange={e => this.onChange(e)}
/>
</div>
<button onClick={() => this.add}>追加</button>
</div>
);
}
}
これで、現在のvalueをtodoListという配列に追加することができます。
this.state.todoList.concat(this.state.value)
ancatという書き方に見慣れないかもしれませんね。pushではダメなのかと思う方もいるのではないでしょうか。実は、pushは破壊的メソッドのため、元のthis.state.todoListまで変更してしまいます。これでは、immutableでなくなってしまいます。一方、concatは非破壊的メソッドのため元のstate自体は変更されないのでreactの要件を満たします。
では、実際にstateが更新されているのかをみてみましょう。stateが更新されるとrender関数が呼ばれるので、
render() {
console.log(this.state.todoList);
return (
...
);
}
としてみましょう。これで"aaa"と入力してついかを押すと、
のように表示されると思います。4回空の配列が呼ばれているのは、最初の一回と"a"〜"aaa"の3回stateの更新が行われたからです。
最後に、TODOを表示させてみましょう。
render() {
const todoListNode = this.state.todoList.map((todo, idx) => {
return <li key={idx}>{todo}</li>
})
return (
<div>
<h1>TODO App</h1>
<div>
<input
type="text"
value={this.state.value}
onChange={e => this.onChange(e)}
/>
</div>
<button onClick={() => this.add}>追加</button>
<ul>
{todoListNode}
</ul>
</div>
);
}
このようにすることで、TODOを表示することができます。
const todoListNode = this.state.todoList.map((todo, idx) => {
return <li key={idx}>{todo}</li>
})
mapの引数にはallow関数を渡しています。その関数の第一引数は配列の要素、第二引数はindexとなっています。
reactは、変更を察知して差分で更新を行います。そのため、各要素をreactが判別できなければなりません。そこで、mapなどのループ処理をしたときには、ユニークなkeyを持たせる必要があります。idなどを持たせるのが一般的ですが、今は一旦indexを持たせておきます。(indexで実装するのはほんとはだめ。)あとで、idを持たせるように変更しようと思います。
あとは、それを表示させるだけです。<li>を並べているので<ul>で包んでやりましょう。これで、完成しました。と言いたいところなのですが、これではTODOを追加しても、inputには先ほどのvalueが残ったままです。これでは、いちいちユーザーが消さないといけないのでUIがだめです。
これを、追加と同時に消せるように変更しましょう。といっても、
add() {
this.setState({
todoList: this.state.todoList.concat(this.state.value),
value: "",
})
}
これを追記するだけです。これでは空のvalueが追加されてしまうのではないかと思ってしまいそうですが、そんなことはありません。これがImmutableであるべき理由です。
実は、add関数が実行されてからしかstateは変更されないのです。
試しに、
add() {
this.setState({
todoList: this.state.todoList.concat(this.state.value),
value: "",
})
console.log(this.state.value);
}
としてみましょう。TODOに追加されたvalueと同じ値が出力されるだけで、まだvalueは""になってないはずです。(ちなみにsetStateのコールバックでは、変更後のstateを参照できます)
これで完成です。
class TodoApp extends Component {
constructor() {
super()
this.state={
todoList: [],
value: "",
}
}
onChange(e) {
this.setState({value: e.target.value})
}
add() {
this.setState({
todoList: this.state.todoList.concat(this.state.value),
value: "",
})
}
render() {
const todoListNode = this.state.todoList.map((todo, idx) => {
return <li key={idx}>{todo}</li>
})
return (
<div>
<h1>TODO App</h1>
<div>
<input
type="text"
value={this.state.value}
onChange={e => this.onChange(e)}
/>
</div>
<button onClick={() => this.add}>追加</button>
<ul>
{todoListNode}
</ul>
</div>
);
}
}
ReactDOM.render(
<HelloWorld />,
document.getElementById('root')
);
コードは以上です。
補足
{this.state.todoList.map((todo, idx) => return <li key={idx}>{todo}</li>)}
{todoListNode}の部分はこんな書き方もできます。(わざわざ関数にしていないだけ)
6章 コンポーネント化する
今までは単一のコンポーネントによる実装だったので、この章からはコンポーネントを分けてより実践的な実装にしていきます。設計はこうです。
まずは、TodoElementコンポーネントを作ってみましょう。このコンポーネントはpropsで親コンポーネントから受け取ったcontentを表示させるというものです。
const TodoElement = (props) => {
return(
<li>
{props.content}
</li>
)
}
これは0章で勉強したアロー関数によりコンポーネントを定義しています。propsを引数にとり、jsxを返します。statelessなコンポーネント(=stateをもたないコンポーネント)のときにこような書き方をすることで、スッキリさせることができます。
慣れないうちは無理しないで、class構文でかけばいいでしょう。
class TodoElement extends React.Component {
render() {
return(
<li>
{this.props.content}
</li>
)
}
}
これら二つは全く同じ機能をもつコンポーネントです。
では、これを表示させてみましょう。
const todoListNode = this.state.todoList.map((content, index) => {
return (
<TodoElement
key={index}
content={content}
/>
)
})
これで元のコードと同じように動きます。ポイントはcontentというpropsを渡してやることです。
次は、AddTodoコンポーネントを作っていきましょう。
class AddTodo extends React.Component {
onChange(e) {
this.props.onChange({
value: e.target.value,
})
}
add() {
const todoElement = {
content: this.props.value,
id: this.props.todoList.length + 1,
}
this.props.add(todoElement)
}
render() {
return(
<div>
<input
type="text"
value={this.props.value}
onChange={e => this.onChange(e)}
/>
<button onClick={() => this.add()}>追加</button>
</div>
)
}
}
一気にコードが難しくなったように感じるかもしれません。一つずつ解説していきます。
onChange(e) {
this.props.onChange({
value: e.target.value,
})
}
これは、inputに変更があったときに呼ばれる関数です。inputのevent(eという引数)から値を取り出して、それを親コンポーネントのonChange関数に引数として渡します。
// TodoAppコンポーネント
return (
<div>
<h1>TODO App</h1>
<AddTodo
{...this.state}
onChange={e => this.onChange(e)}
add={() => this.add}
/>
<ul>
{todoListNode}
</ul>
</div>
);
このようにAddTodoを呼び出します。これで、TodoApp内のonChangeを呼び出すことができます。
onChange(key_value) {
this.setState(key_value)
}
このようにスッキリとかけます*。
また、
<AddTodo
{...this.state}
onChange={() => this.onChange}
add={() => this.add}
/>
で、スプレッド演算子が使われています。これはTodoApp内のstateを展開してvalueとして渡しているということです。一つずつ渡すこともできるのですが、それでは効率や保守性が悪いのでこのように書くことが一般的です。
add関数についても少し変更を加えてあります。
add() {
const todoElement = {
content: this.props.value,
id: this.props.todoList.length + 1,
}
this.props.add(todoElement)
}
これまでは、文字列を配列に格納していただけでしたが、AddTodoコンポーネント内で追加するオブジェクト(=todoElement)を作ってそれを親コンポーネントのadd関数に渡して、todoListに格納するという設計にしています。このオブジェクトはcontentとidを持っています。idはuniqになるように要素の数を基準に番号をつけています。
これに伴って、
const todoListNode = this.state.todoList.map(element => {
return (
<TodoElement
key={element.id}
content={element.content}
/>
)
このように変更が必要になります。elementにはそれぞれのtodoの情報が入ったオブジェクトが入っているので、そこからidをkeyに入れてcontentをcontentにいれて渡します。
keyにはuniqなデータを入れるべきなので、これで要件を完全に満たしたことになります。
7章 削除機能を実装する
いよいよ最終章です!削除機能を実装して完成させましょう!
実装の設計としてはTodoElement内で親のTodoAppのonDeleteを呼んで、親コンポーネントで削除を実装するといったところです。
class TodoElement extends React.Component {
onDelete() {
this.props.onDelete(this.props.element.id)
}
render() {
return(
<li>
<span>{this.props.element.content}</span>
<button onClick={() => this.onDelete}>削除</button>
</li>
)
}
}
TodoElementを上のように書き換えてみましょう。これで親コンポーネントのonDeleteを呼び出すことができます。(stateless関数ではエラーが出たので一旦class構文に戻しています。原因がわかったら報告します。)TodoElementの実装はこれで終わりです。
次に、TodoAppでonDeleteを実装します。
<TodoElement
key={element.id}
element={element}
onDelete={() => this.handleDelete()}
/>
のように、onDeleteが呼ばれたらTodoApp内のhandleDeleteが呼ばれるようになります。関数の名前が途中で変わっていますが、これはpropsはon〇〇という名前が一般的なのに対して、そのコールバック関数はhandle〇〇という名前が一般的だからです。よくわからない人は、とりあえず動くコードがかけるようになってからで十分だと思います。
では、handleDeleteを実装していきます。
handleDelete(id) {
let todoList = this.state.todoList.concat()
let index = 0
todoList.map((element, idx) => {
if (element.id == id) {
index = idx
}
})
todoList.splice(index, 1)
this.setState({todoList: todoList})
}
1行目は現在のstateをコピーしています。こうしないと、stateがimmutabelでなくなってしまいます。
let todoList = this.state.todoList.concat()
次に、削除した要素のidが何番目に入っているかを探します。
let index = 0
todoList.map((element, idx) => {
if (element.id == id) {
index = idx
}
})
これで、indexがわかるので、このindexのtodoListの要素を消すことでtodoを消すことができます。
todoList.splice(index, 1)
このメソッドが破壊的メソッドのためにconcatでコピーしておく必要があります。あとはsetStateを呼ぶことで、stateが更新されてDOMに反映されます。
はい!これで完成です!
復習がてら、1から自分で何もみないで書けるか試してみるといいのではないでしょうか。
コードはこちらに載せてあります。
bindのところを修正したのでもしかしたら変更漏れ等あるかもしれません。もし動かない等ありましたらご連絡いただけると助かります!
この程度のがさらっと書けるようになり、非同期通信を扱えるようになると十分にモダンなフロントエンジニアとして活躍していけると思います。
思いのほかreactの理解が曖昧だったみたいで僕自身もいい勉強になりました。逆にいうとそれぐらいの理解でも十分プロダクトは作れるので、まずは動かせるようになることをオススメします!
Twitterでのシェア&noteのフォローお待ちしております!!
この記事が気に入ったらサポートをしてみませんか?