親子コンポーネント関係についてまとめ
練習内容のアウトプット
個人的な解釈とか考え方もあるかも
目的
・イベント処理を 子コンポーネント ⇔ 親コンポーネント 間でやり取りできるようにする
<template>
<template for:each={todoList} for:item="todo" for:index="i">
<c-todo-item
key={todo.id}
item={todo} <!-- todoオブジェクトを渡す -->
username={userName} <!-- 親のuserName変数を渡す -->
index={i} <!-- ループの番号を渡す -->
ondeleteitem={handleDelete}> <!-- 子がイベント発火したらhandleDeleteを実行 -->
</c-todo-item>
</template>
</template>
import { LightningElement, track } from 'lwc';
export default class TodoApp extends LightningElement {
@track todoList = [
{ id: 1, name: '掃除' },
{ id: 2, name: '買い物' }
];
userName = '太郎';
handleDelete(event) {
const idToDelete = event.detail;
this.todoList = this.todoList.filter(todo => todo.id !== idToDelete);
}
}
<template>
<div>
{index}番目: {item.name}(担当: {username})
<button onclick={deleteItem}>削除</button>
</div>
</template>
import { LightningElement, api } from 'lwc';
export default class TodoItem extends LightningElement {
@api item; // 親からの todo
@api username; // 親からの userName
@api index; // 親からのループ番号
deleteItem() {
this.dispatchEvent(new CustomEvent('deleteitem', {
detail: this.item.id
}));
}
}
まずコードを読むときの流れとして親 → 子 → 親 で読むようになる
親コンポーネントはApp とか List みたいに「全体を管理する名前」が付く名称が多い
子コンポーネントはItem とか Row みたいに「個別要素っぽい名前」が付く名称が多い
①c-todo-item この記述で親から子に渡す記述を行う
この c-todo-item と書いた瞬間に書いたコンポーネントが親になる
ondeleteitem={handleDelete} この記述は関数を子に渡しているわけじゃなく、イベント名と親の処理を結びつけているだけの記述という認識
<c-todo-item
key={todo.id}
item={todo} <!-- todoオブジェクトを渡す -->
username={userName} <!-- 親のuserName変数を渡す -->
index={i} <!-- ループの番号を渡す -->
ondeleteitem={handleDelete}> <!-- 子がイベント発火したらhandleDeleteを実行 -->
</c-todo-item>
②子側で @api の記述で親から受け取る
受け取りたいものをそれぞれ子のjs側に記述する必要がある
@api item; // 親からの todo
@api username; // 親からの userName
@api index; // 親からのループ番号
上記の記述で親から値を取得しているので子側のHTMLの記述でitem,username,index等の値を画面表示できる
ここまではまだ、イベントは発火していない状態で、値をjs側で保持しているだけの状態
下記記述部分が子側のHTML該当箇所
{index}番目: {item.name}(担当: {username})
<button onclick={deleteItem}>削除</button>
削除ボタンを押下するとdeleteItemの関数内の this.dispatchEventが発火する
deleteItem関数を子側のjsで記述している
deleteItem() {
this.dispatchEvent(new CustomEvent('deleteitem', {
detail: this.item.id
}));
}
・this.dispatchEvent この記述はボタン押下した時に、この中の処理の内容を親側に送る為の記述となる
・new CustomEvent('deleteitem この記述はカスタムイベントの新規作成の記述でイベント名はdeleteitemで作成する記述 (親側でondeleteitemと記述してイベントとして使えるようになる)
・detail: this.item.id この記述のdetail は イベントオブジェクトに入れられる「入れ物」 です。実際にはオブジェクトでも何でも渡せる。detail は「追加情報の箱」で、その中身は 開発者が決められる。今回はIDを入れて親コンポーネントに渡すように記述しています。
③親側で子から値を受け取って処理(イベント伝播)
ondeleteitem={handleDelete}> <!-- 子がイベント発火したらhandleDeleteを実行 -->
子から受け取った値(detail: this.item.id)を元にhandleDeleteの関数を実行して処理を行う
子は dispatchEvent(new CustomEvent('deleteitem', { detail: id })) で「削除して!」というイベントを親に通知。親は ondeleteitem={handleDelete} でそれを受け取って処理。これが「イベント伝播」で、LWCの 親子連携の基本の仕組み
下記記述で子から受けとったdetailの値を変数に入れて表示を制限する記述となる
const idToDelete = event.detail; の記述について
・event はフレームワークが「自動で渡してくれるオブジェクト」
・detail は「自分が箱に入れた中身」
だから event.detail で受け取るのは自然な流れとなる
const idToDelete = event.detail;
this.todoList = this.todoList.filter(todo => todo.id !== idToDelete);
全体の流れ
1,親が todoList を持っている(例:[{id: 1, name: "掃除"}, {id: 2, name: "勉強"}])。
2,親は繰り返し処理で を子に渡す。
3,子は @api item; を使って todo データを受け取る。
(@api item;)= 値を受け取って箱に入れておく段階。まだ何も起きてない。
4,子で「削除」ボタンをクリックすると dispatchEvent(new CustomEvent('deleteitem', {detail: this.item.id})) が走る。
(ボタン押下 → dispatchEvent)= 初めて親に通知が飛ぶ。
5,親が ondeleteitem={handleDelete} で受け取り、event.detail から id を取得。
6,親の todoList を filter で更新 → 再描画される。
補足
親と子のデータのやり取りについての補足
できること
・detail にデータを入れて渡す(数値・文字列・オブジェクトなんでも)
・bubbles: true を指定すると 親のさらに親 まで伝えることもできる
・composed: true を指定すると Shadow DOM を超えて 上位まで伝える
例:
new CustomEvent('deleteitem', {
detail: { id: 3, name: '掃除' },
bubbles: true,
composed: true
});
できないこと
親の関数や変数に 直接アクセスすること
(これができちゃうと、親と子の独立性がなくなって設計が壊れるから)