Skip to content

Latest commit

 

History

History
312 lines (234 loc) · 8.2 KB

File metadata and controls

312 lines (234 loc) · 8.2 KB

ファイルも分割する

コンポーネントを分割したので、次はファイルも分割しよう。 ここでは次のような単位で分割する。

  • モデル(Todoクラス、TodoListクラス)
  • データ(contenttodoList
  • アクション(updateContent, tryAddTodo, updateStatus, clear
  • 独立したコンポーネント(InputContent, TodoComponent, TodoListComponent, ClearButton
  • 構造化するコンポーネント(App
  • 描画関数(render

モデルを分割する

TodoクラスもTodoListクラスも他の単位への依存がない(つまり、モデルはデータやアクション・コンポーネントへ依存していない、ということ)のでモデルをファイルに分割するのは簡単だ。

次の内容をsrc/models.jsに保存する。

class Todo {
    constructor(id, content, done) {
        this.id = id;
        this.content = content;
        this.done = done;
    }
    setDone(done) {
        return new Todo(this.id, this.content, done);
    }
    static idGenerator = 0;
    static create(content) {
        return new Todo(++Todo.idGenerator, content, false);
    }
}

class TodoList {
    constructor(list) {
        this.list = list;
    }
    static empty() {
        return new TodoList([]);
    }
    add(todo) {
        return new TodoList([todo, ...this.list]);
    }
    setDone(id, done) {
        return new TodoList(this.list.map(todo => {
            if (todo.id === id) {
                return todo.setDone(done);
            }
            return todo;
        }));
    }
    clear() {
        return new TodoList(this.list.filter(todo => todo.done === false));
    }
}

export { Todo, TodoList };

クラス定義はsrc/index.jsからそのまま持ってきた。

追加されているコードは最終行だ。

export { Todo, TodoList };

これはTodoクラスとTodoListクラスを他のファイルからも使えるようにするためのコードだ。

src/index.jsからはTodoクラスとTodoListクラスを定義していたコードがなくなり、代わりにimportが追加される。

import { Todo, TodoList } from './models';

データを分割する

データの分割は少しだけ面倒だ。

次の内容をsrc/store.jsに保存する。

import { Todo, TodoList } from './models';

let todoList = TodoList.empty()
    .add(Todo.create('環境構築').setDone(true))
    .add(Todo.create('JavaScriptチュートリアル'))
    .add(Todo.create('Reactチュートリアル'));

let content = '';

const store = { todoList, content };

export default store;

データはTodoクラスとTodoListクラスに依存しているのでsrc/model.jsimportしている。 それからtodoListcontentを1つのオブジェクトにまとめている。

ちなみに、オブジェクトリテラルはキーと値のペアを,で区切って設定するのだけど、キーを省略すると値が代入されている変数の名前をキーとみなしてくれる。 つまり次のコードは、

const store = { todoList, content };

次のコードと等価だ。

const store = { todoList: todoList, content: content };

このstoreexportしている。

export default store;

変数contentと変数todoListを参照していた箇所はstore.を付けるように修正する。

exportとexport default

storeexportにはTodoクラスとTodoListクラスのexportのときにはなかったdefaultというキーワードが付いている。

defaultが付いているほうをデフォルトエクスポート、defaultが付いていないほうを名前付きエクスポートと呼ぶ。

デフォルトエクスポートは1ファイルにつき1つの値のみexportできる。 importするときはファイルを指定すればよい。

export default 'hello world';

import Greeting from './hello';

名前付きエクスポートは1ファイルにつき複数の値をexportできる。 importするときは名前を指定しなければいけない。

const foo = 'FOO';
const bar = 12345;
export { foo, bar };
//宣言と同時にexportできる
export const baz = [1, 2, 3];

import { foo, bar, baz } from './foobar';

アクションを分割する

アクションは再描画のためにrender関数を呼び出す。 つまりrender関数を渡さないといけない。

次の内容をsrc/actions.jsへ保存する。

import { Todo } from './models';
import store from './store';

let render;

export const updateContent = event => {
    store.content = event.target.value;
    render();
};

export const tryAddTodo = event => {
    if (store.content !== '' && event.key === 'Enter') {
        const todo = Todo.create(store.content);
        store.todoList = store.todoList.add(todo);
        store.content = '';
        render();
    }
};

export const updateStatus = (id, done) => {
    store.todoList = store.todoList.setDone(id, done);
    render();
};

export const clear = event => {
    store.todoList = store.todoList.clear();
    render();
};

export default (h) => {
  render = h;
};

デフォルトエクスポートしている関数がrender関数を設定するためのものだ。

アクションを使う側は次のようにimportした後に、

import initRender, { updateContent, tryAddTodo, updateStatus, clear } from './actions';

render関数を渡す必要がある。

initRender(render);

独立したコンポーネントを分割する

ファイル分割もかなり進んできた。 次は独立したコンポーネント(つまりApp以外のもの)を分割する。

次の内容をsrc/components.jsへ保存する。

import React from 'react';

export const InputContent = (props) => {
    const content = props.content;
    const updateContent = props.updateContent;
    const tryAddTodo = props.tryAddTodo;
    return (
        <p>
            <input type="text" placeholder="やることある?" value={content}
                onChange={updateContent} onKeyPress={tryAddTodo} />
        </p>
    );
};

export const TodoComponent = (props) => {
    const todo = props.todo;
    const updateStatus = props.updateStatus;
    return (
        <li>
            <label>
                <input type="checkbox" checked={todo.done}
                    onChange={event => updateStatus(todo.id, event.target.checked)} />
                <span>#{todo.id} {todo.content}</span>
            </label>
        </li>
    );
};

export const TodoListComponent = (props) => (
    <ul>
        {props.children}
    </ul>
);

export const ClearButton = (props) => (
    <p>
        <button onClick={props.clear}>終わったやつ消す</button>
    </p>
);

構造化するコンポーネントを分割する

独立したコンポーネントを分割できたら次は構造化するコンポーネントだ。

次の内容をsrc/component-structure.jsへ保存する。

import React from 'react';

import store from './store';
import { updateContent, tryAddTodo, updateStatus, clear } from './actions';
import { InputContent, TodoListComponent, TodoComponent, ClearButton } from './components';

const App = () => (
    <div>
        <h1>やること</h1>
        <InputContent
            content={store.content}
            updateContent={updateContent}
            tryAddTodo={tryAddTodo} />
        <TodoListComponent>
            {store.todoList.list.map(todo => (
                <TodoComponent
                    key={todo.id}
                    todo={todo}
                    updateStatus={updateStatus} />
            ))}
        </TodoListComponent>
        <ClearButton clear={clear} />
    </div>
);

export default App;

描画関数

最後に描画関数まわりを整えよう。

次の内容をsrc/index.jsへ保存する。

import React from 'react';
import ReactDOM from 'react-dom';

import initRender from './actions';
import App from './component-structure';

const render = () => {
    ReactDOM.render(<App />, document.getElementById('root'));
};

initRender(render);

render();

https://github.com/team-cerezo/react-sample/tree/files