ソースコードはこちら; netsphere / react-by-examples · GitLab
今回は、ユーザがWebブラウザ内でデータを追加できるようにする。まだ永続化はしない。
スクリーンショット
1行テキストの追加, [Done] ボタンで状態変更。表示モードの切り替え、を実装する。Example: Todo List | Redux ここの例を, Redux を使わないように書き換えたもの。
コンポーネントを跨ぐ状態管理。
コードを書く
まずは, エントリポイント (src/index.js
ファイル). Redux を使う場合, <Provider>
コンポーネントを呼び出す。我々のバージョンでは、特に前回と変わりない。
JavaScript
- ReactDOM.render(
-
-
- <App />,
- document.getElementById('root') );
今回の例では, 1画面に, 新しいアイテムの追加, リスト表示を並べる。コンポーネントはそれぞれ TodoEdit
, TodoList
と名付ける.
TodoEdit
がアイテムを追加して, TodoList
コンポーネントに表示するので, データがコンポーネント内で完結しない。表示するデータは App
コンポーネントに保存することにする。
App
クラスのコンストラクタで state
を設定。
JavaScript
- class App extends React.Component
- {
- constructor(props) {
- super(props);
- this.state = {
-
- todos: TodoModel.find_all(VisibilityFilters.SHOW_ALL),
-
- filter: VisibilityFilters.SHOW_ALL,
- };
- }
注意したいのは、state
に保存するのは、あくまでも表示すべきデータだ、ということ。モデルデータのすべてではない。フロントエンドとバックエンドとの分離で、一部を切り取って取り出して表示するイメージ。
App#render()
にて、必要なデータを各コンポーネントに渡す。App
に状態を保存する関係で, ボタンが押されたなどに対応するアクションハンドラも App
で定義する。それも渡してあげる。
JavaScript
- render() {
- return (<div className="App">
- <header>Todo List</header>
-
- <TodoEdit onAdd={this.onAdd} />
- <TodoList todos={this.state.todos} onFinish={this.onFinish}
- selectedFilter={this.state.filter}
- onChangeFilter={this.onChangeFilter} />
- </div> );
- }
入力フォーム
前回見たように、JSX の属性は、JSON オブジェクトのプロパティとして組み立てられる。これが, コンポーネントの constructor の props
引数として渡される。TodoEdit
コンポーネントには onAdd
関数が渡ってくる。
JavaScript
-
- class TodoEdit extends React.Component
- {
-
- constructor(props) {
-
- super(props);
- this.state = {
- submitEnabled: false,
- todoText: ''
- };
-
-
-
- }
コメントアウト部分について。はるか昔の JavaScript にはアロー関数がなく, function
式しかなかった。this
がダイナミックスコープなので, bind(this)
によって this
が指すオブジェクトを自クラスに固定しなければならなかった。アロー関数はレキシカルスコープなので、そのような手間は不要。
入力フォーム部分は、次のようにする。
JavaScript
-
- handleTodoTextChanged = (event) => {
- const value = event.target.value;
- this.setState( {
- todoText:value,
- submitEnabled: value.trim() !== '' } );
- }
-
- handleSubmit = (event) => {
-
- event.preventDefault();
- const text = this.state.todoText.trim();
- if (!text)
- return;
- this.props.onAdd(text);
- this.setState( {todoText: ''} );
- }
-
- render() {
- return (<div>
- <form onSubmit={this.handleSubmit} >
- <input value={this.state.todoText} onChange={this.handleTodoTextChanged} />
- <button type="submit" disabled={this.state.submitEnabled ? '' : 'disabled'}>
- Add Todo</button>
- </form>
- </div> );
- }
まず、render()
内では, state
の読み出ししかできない。input
タグ値が変わる度に, ハンドラ handleTodoTextChanged
を呼び出させる。いかにも重そうだが、クライアント側で完結するので、問題ない。
例として, リアルタイムで簡単な値の検査を行っている。メイルアドレスに限定、なども、この場所に記述すればよい。ここでは単に setState()
で値を変更するだけで, render()
内で実際のボタンの有効/無効を設定する。
今回は, App
コンポーネントにデータを格納しているので, handleSubmit
ハンドラでは, App#onAdd()
を呼び出すようにしている。
モデルの更新
App.js
ファイルに戻る。レコードの作成に見立てた create()
を呼び出す。
JavaScript
-
-
- onAdd = (text) => {
- TodoModel.create({
- text: text,
- completed: false
- });
- this.setState( {
- todos: TodoModel.find_all(this.state.filter),
- });
- }
モデルクラスで定義している。この例では、メモリ上に保持するだけで, まだ永続化は行っていない。
JavaScript
- static create(newItem) {
- const todoItem = {
- id: TodoModel.nextId,
- text: newItem.text.trim(),
- completed: newItem.completed };
- TodoModel.todos = [...TodoModel.todos, todoItem]
- ++TodoModel.nextId;
- }
...
はスプレッド構文 Spread syntax で, 次のように書くと、要素を入れ替えたり追加したりできる。
[... iterableObj, '4', 'five', 6]
今回の例では, 単に Array#push()
でもよいが。
React コンポーネントは入れ子にすることができる。行21で Footer
コンポーネントを呼び出している。
JavaScript
- class TodoList extends React.Component
- {
-
- constructor(props) {
- super(props);
- this.state = {}
- }
-
-
- render() {
- return (<div>
- {this.props.todos.map(todo => (
- <TodoListRow key={todo.id} {...todo}
- onClick={() => this.props.onFinish(todo.id)} />
- ))}
- <Footer selectedFilter={this.props.selectedFilter}
- onChangeFilter={this.props.onChangeFilter} />
- </div> );
- }
- }
TodoListRow(onClick: ...)
部分では、引数の数を調整している。Babel コンパイルで展開されたときに, JSONプロパティ値になる。関数式であればよいので、このようにアロー関数を書くことができる。
行18 で呼び出されている TodoListRow
コンポーネントについて見てみよう。
単に表示するだけのコンポーネントについては, いちいちクラスを定義しなくても, 関数で済ますことができる。
JavaScript
-
-
-
- const TodoListRow = ({ onClick, completed, text }) => (<div>
- <span style={{
- textDecoration: completed ? 'line-through' : 'none' }} >
- {text}
- </span>
- {!completed && <button type="button" onClick={onClick}>Done</button>}
- </div> );
-
- TodoListRow.propTypes = {
- onClick: PropTypes.func.isRequired,
- completed: PropTypes.bool.isRequired,
- text: PropTypes.string.isRequired
- }
PropTypes
は、React の機能で, 型チェックと必須パラメタかどうかを指示できる。