React Component の実用
why
class Componentを使用したReactでの動的サイト製作の記事
wesのReact Firebase Note App Tutorialを参考にして作っている.
index
index.js
import App from './App';
ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') );
基本的にはindex.htmlにlinkがあるindex.jsが起点になる
そのindex.jsでComponentの読み込み地点となる App Component(以下App)をimportし,get...Idでrenderする
App
App.jsが基本的にCotainerになる.
このApp.js自体もComponentであり,note
を描画する<Note/>
note
を追加するためのform, <NoteForm/>
を読みこむ.
Componentはclassを使い,基本的にこのように書く
App.js
class App extends Component { render() { return ( <div> <h1>React and Firebase To-Do List</h1> <Note /> </div> ); } }
file nameと同じclass nameをとり,render() { return( ) }のナカに 一つのdivを作り,そのナカにhtmlを書く.
Appではこのように他のComponentを<Hoge/>
と呼び出す
jsなので当然他のファイルから使うためには
Note.jsxでexport default Note
をして
App.jsでもNote.jsxを使うために
import Note from './Note/Note';
でexportされたComponentをimportする必要がある
そして今回はCRUDのReadの役割を果たすリスト表示のNote.jsx
は
Note.jsx
import React, { Component } from 'react'; import './Note.css'; import PropTypes from 'prop-types';
でまずReact, Component, css, PropTypesをimportする.
prop-typesは型を判定してくれる.(後述)
class Note extends Component { constructor(props) { super(props); this.message = 'Hello from Note Component'; }
ここではconstructorを書き,superをして, このComponentのなかにmessageを直接定義する.
render(props) { return( <div> <h1>{this.message}</h1> </div> ) } } export default Note;
本当はstateを使う.
今回はrenderにもpropを食わせてpropsの値を表示する.
そうするとlocalhost3kでNote.jsxのComponent fileに書かれた内容が App.jsで呼び出されてこうやって表示される
NoteForm.jsx
前回の
Appのfooter will go here...を
react, component, cssをimportし,constructorにemptyなstateを作り,
renderにinputとbtnを設ける.
NoteForm.jsx > render()
render(){ return( <div className = "formWrapper"> <input className="noteInput" placeholder = "Write a new note..." /> <button className="noteButton">Add Note</button> </div> ) }
そしてApp.jsで使えるようにNoteFormをexport default NoteForm;してimportする.
これでinputとaddが表示された
input内のvalueとread-only
NoteFormのinputのvalueを追加し,stateの中身を{}で埋め込む
<input className="noteInput" placeholder = "Write a new note..." value = {this.state.newNoteContent} />
これでこのComponentのstateのnewNoteContentがinputに 最初の読み込みに表示される.
だがこのままだとRead-Only Propertyだと警告をされる
index.js:1 Warning: Failed prop type: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field.
なのでonChangeを書き込む必要がある.
valueと同じように{}bracketで埋め込み, 関数のtriggerにする.
onChange
constructorのbracketの閉じた後にhandle関数を仕込む.
handleUserInput(e) { this.setState({ newNoteContent : e.target.value, }) }
これをrenderのinputのなかにonChangeで
onChange = {this.handleUserInput}
と仕込むと,このinputの中が変更されるたびにhandleUserInputがその中の valueで起動する.
だがこのままではCannot read property 'setState' of undefined
なので
constructorの中にbindする.そうすると使える.
constructor(props) { super(props); this.state = { newNoteContent: 'New Note Content', } this.handleUserInput = this.handleUserInput.bind(this); }
ここでhandleUserInputにconsole.logでthis.stateを吐かせると
変更されるたびに中身を吐き出してくれる.
これがstateと連携したonChangeのhandleだ
これでGUIでinputからstateを変更できるようになる.
次は同じようにして,btnにonClickを仕込む.
btnにonClickをつけwriteNoteのtriggerにする
btnも同じようにrenderのタグ内に仕込む
<button className="noteButton" onClick = {this.writeNote} >Add Note</button>
propsには{}を使い,関数名の後に()はつけない.
setState({})を使ってstateを操作する関数を追加する.
writeNote(){ this.setState({ newNoteContent: '', // empty input box }) }
constructorの後にwriteNoteのfuncを書く.
とりあえずnewNoteContentをemptyにする処理を入れておく
this.writeNote = this.writeNote.bind(this);
そして先ほどと同じようにbind(this)する.
すると押すとinputの中にvalueで紐づけられているnewNoteContentというstateが
空のstringになるwriteNoteが起動するbuttonが完成する.
考えたらこれは動作テストで入れてる処理ではなく,「DBへの追加処理が発生したら 入力欄を空にする」という当たり前に必要なMethodだった.これがないからFirebaseとjQで作った家計簿アプリは「btnを押した時に追加した手応えがなくて連続押して,二つ同じものが入ってしまう」ということが起こりがちだったんだな
(悪い例: https://moneylog-e7c4b.firebaseapp.com/ )
NoteFormとAppの連携
AppでAppのstateにpushするaddNote()を作成してNoteFormのwriteNoteで使用させる.
ここではNoteForm Componentに作成したwriteNoteのなかにaddNoteを使用し,
そのaddNoteはApp.jsに書く.NoteFormのstateではなく,Appのstateに入れるためだ.
addNote(note) { this.state.notes.push(note) }
これでstateの中のnotesというarrayを引数でとったnoteに追加する.
this.addNote = this.addNote.bind(this);
handleやwriteの関数と同じように,Appのconstuctorのナカでbindする.
また,これだけではNoteFormで使えないので,
renderにある
<NoteForm addNote={this.addNote}/>
という形addNoteの関数をNoteFormに渡す.
NoteForm use addNote
そしてNoteFormで使う.
writeNote(){ this.props.addNote(this.state.newNoteContent); this.setState({ newNoteContent: '', // empty input box }) }
Appのrenderの中で渡されたthis.addNoteはthis.propsの中にはいるので取り出し
ここNoteFormのstateの中にあるnewNoteContentをaddNoteを使って
Appのstateのnotesにpushする.
これでaddNoteにconsole.logを仕込んで見るとnotesは一つ増えているのが見える
0: {id: 1, noteContent: "Note 1 here"} 1: {id: 2, noteContent: "Note 2 here"} 2: "New Note Content" length: 3 __proto__: Array(0)
だが画面の更新はされない.
previous noteを挟む
ここでaddNoteの処理に一つ噛ませる
addNote(note) { const previousNotes = this.state.notes; previousNotes.push({ id: previousNotes.length + 1, noteContent: note, }); this.setState({ notes: previousNotes, }) console.log(this.state.notes); }
previousNotesという変数にstateのnotesを入れてから
その変数に引数でとった追加する内容であるnoteを
noteContentとして, さらに現在の追加さき配列notesの次の添字をidとして
pushする.
最後にnotesにpreviousNotesの内容を入れる.
stateに直接値を入れては行けないからsetState({})を使用する.
回りくどく見えるが,stateを更新する処理を別の処理と分割するためにpreviousNote介する.長くなってきた時にわかる.
さらにreact特有のkeyを渡す必要が出てくる.これがないとエラー.
render, give state id, content, key,
だからApp.jsのrenderでnotesをmapで展開して,Note Componentにkey, id, Contentを渡すことで描画している.
これでlocalなReactのstateにinputを介して追加して,それを即座に更新して 表示することができるようになった.
conclude
おさらいすると,<App />
のstateにidとcontentのobjをもつ配列notesを作成.
renderではそのstateにあるnotesをmapで展開し,id, key = id, contentを
<Note />
に渡す.<Note />
ではconstructorでpropsから受け取り,
renderでcontentを表示する.そこにcssがかかってカードになる.また
propTypesによって型がstringかチェックが入る.
追加, ADD編ではaddNoteという関数をAppのconstructorの後に作り,
stateのnotesに配列の長さ+1でid,引数をcontentにpushする.
それを変数を介してsetStateし,この関数addNote()を<NoteForm />
に私,
propから受け取り,inputのvalueにstate newNoteContentを組み込み,
onChangeでhandleしてe.target.valueからnewNoteContentに渡し,
btnのonClickで先ほど<App />
からもらったaddNoteをpropから使って
addNoteにnewNoteContentを渡し,今inputのvalue に入っている
newNoteContentを空にする.
ReactはここでRenderが更新される.
これでCRUDのRead, Create, Updateの処理までができた.
これではbrowserをrefleshした時にstateに入れたdataも消えてしまうから,Firebaseに保存し,起動した時にFirebaseから値を取ってくる物にする.
その後,moneylogのようにhttpsでアクセスできるようにfirebase hostingを活用する.