Skip to content

Latest commit

 

History

History
366 lines (309 loc) · 10.2 KB

File metadata and controls

366 lines (309 loc) · 10.2 KB

コンポーネントを分割する

ざっくり書いた画面はAppという1つのコンポーネントだけで構成されていた。 これを次のように分割したい。

  • テキスト入力
  • ToDo一覧
  • 1つのTodo
  • 完了Todoを消すボタン

まずは何も考えずに分割

テキスト入力部分を分割してみよう。

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

const App = () => (
    <div>
        <h1>やること</h1>
        <InputContent />
        <ul>
            {todoList.list.map(todo => (
                <li key={todo.id}>
                    <label>
                        <input type="checkbox" checked={todo.done}
                            onChange={event => updateStatus(todo.id, event.target.checked)} />
                        <span>#{todo.id} {todo.content}</span>
                    </label>
                </li>
            ))}
        </ul>
        <p>
            <button onClick={clear}>終わったやつ消す</button>
        </p>
    </div>
);

Appと同じようにInputContentという変数にテキスト入力部を分割した。 App側ではdivpといった要素と同じようにInputContentをタグで追加している。

同じようにToDo一覧とボタンも分割しよう。

const TodoListComponent = () => (
    <ul>
        {todoList.list.map(todo => (
            <li key={todo.id}>
                <label>
                    <input type="checkbox" checked={todo.done}
                        onChange={event => updateStatus(todo.id, event.target.checked)} />
                    <span>#{todo.id} {todo.content}</span>
                </label>
            </li>
        ))}
    </ul>
);

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

const App = () => (
    <div>
        <h1>やること</h1>
        <InputContent />
        <TodoListComponent />
        <ClearButton />
    </div>
);

TodoListクラスが既に存在するのでToDo一覧はTodoListComponentという名前にしてある。

リストの要素を分割

次は1つのToDoを分割する。 これまでのように単純に分割するとtodoを解決できずエラーになる。

//これはエラーになるコード!
//"'todo' is not defined  no-undef"というエラーが出る!
const TodoComponent = () => (
    <li>
        <label>
            <input type="checkbox" checked={todo.done}
                onChange={event => updateStatus(todo.id, event.target.checked)} />
            <span>#{todo.id} {todo.content}</span>
        </label>
    </li>
);

todotodoList.listmapしているところで渡さなくてはいけない。 Reactでこのような値の受け渡しをするにはpropsが使える。

TodoComponentは次のようになる。

const TodoComponent = (props) => {
    const todo = props.todo;
    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>
    );
};

これまでJSXで書かれたコンポーネントは引数が0個のアロー関数で表現されていたが、TodoComponentは引数propsを受け取っている。 この引数を経由してコンポーネントの親子間で値を受け渡せる。

TodoComponenttodoを渡すため、TodoListComponentは次のようにする

const TodoListComponent = () => (
    <ul>
        {todoList.list.map(todo => (
            <TodoComponent key={todo.id} todo={todo} />
        ))}
    </ul>
);

TodoComponenttodo属性を設定しているが、これがTodoComponent側でprops.todoとして取り出せる。

TodoListComponentからTodoComponentの依存を切り離す

TodoListComponenttodoList.listmapTodoComponentを使ってToDoリストを構築している。 これはTodoListComponentTodoComponentに依存していることになるが、この依存は切り離せる。

TodoListComponentを次のように変更する。

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

次にAppを次のように変更する。

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

これでTodoListComponentからTodoComponentへの依存を切り離せた。 このようにコンポーネント間はできるだけ疎結合にしてAppのようなルートコンポーネントで構造化させる方法もあり、おそらくこのやり方のほうが拡張性、保守性は高いと考えている(まだ実践はしていないので、これまでの経験からくる勘)。

表示する値をpropsのみに依存させる

コンポーネント間の依存が解消できたので、次は参照している値に関する依存を解消しよう。

今のコードはInputContentが変数contentupdateContenttryAddTodoに依存している。 これはつまりInputContentの外側のスコープにそれらの変数が存在しなければいけないということだ。

これもpropsを使用することで解消する。 InputContentを次のように変更する。

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>
    );
};

それからAppを次のように変更する。

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

先ほどのコンポーネント間の依存と同じように、各コンポーネントは外側のスコープに依存せず、構造化を担当するAppが必要な値を渡すようにした。 こうすることでコンポーネントの独立性が高まり、依存関係をAppに集約できた。

ここまでのコード

同じように他のコンポーネントも独立させた。

ここまでのコードを次に示す。

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

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));
    }
}

//Hello, world!のときのmessageと同じ理由でletを使用している。
let todoList = TodoList.empty()
    .add(Todo.create('環境構築').setDone(true))
    .add(Todo.create('JavaScriptチュートリアル'))
    .add(Todo.create('Reactチュートリアル'));

//Hello, world!のときのmessageと同じ理由でletを使用している。
let content = '';

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

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

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

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

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

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>
    );
};

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>
    );
};

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

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

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

render();

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