はじめに
Custom Elementsをやっている中で配列の一部を変更してレンダリングしようとしておりLitのドキュメントを見ていたところImmerというライブラリが紹介されていたので確かめてみました
このライブラリを利用することで配列自体の変更でなく、その中の1要素の変更でも再レンダリングが行えるようになります
やり方
今回は、ボタンをクリックしたらメンバーの2人目のメールアドレスが変更されるようなコードを書いていきます
import { useState } from "react";
import "./styles.css";
class Member {
constructor(
readonly id: Number,
readonly name: String,
readonly email: String
) {}
}
export default function App() {
const [members, setMembers] = useState<Member[]>([
new Member(1, "yamada", "yamada@example.com"),
new Member(2, "tanaka", "tanaka@example.com")
]);
const changeEmail = () => {
console.log("click button");
members[1] = new Member(2, "tanaka", "chnage@example.com");
setMembers(members);
};
return (
<div className="App">
{members.map((member, i) => {
return (
<div key={i}>
<h1>{member.name}</h1>
<h2>{member.email}</h2>
</div>
);
})}
<button onClick={() => changeEmail()}>Change Tanaka Email</button>
</div>
);
}
この状態だともちろん変更は検知されません
setMembersに新たな配列を代入しないと参照元が同じのため変更として判断されません
そこで、Immerを利用していきます。
import { useCallback, useState } from "react";
import "./styles.css";
import { produce } from "immer";
class Member {
constructor(
readonly id: Number,
readonly name: String,
readonly email: String
) {}
}
export default function App() {
const [members, setMembers] = useState<Member[]>([
new Member(1, "yamada", "yamada@example.com"),
new Member(2, "tanaka", "tanaka@example.com")
]);
const changeEmail = useCallback(() => {
console.log("click button");
setMembers(
produce((draft) => {
draft[1] = new Member(2, "tanaka", "change@example.com");
})
);
}, []);
return (
<div className="App">
{members.map((member, i) => {
return (
<div key={i}>
<h1>{member.name}</h1>
<h2>{member.email}</h2>
</div>
);
})}
<button onClick={() => changeEmail()}>Change Tanaka Email</button>
</div>
);
}
ポイントはuseCallbackの中で、produce関数を使うことです
これで配列の中の1要素の変更を検知して再レンダリングが行われます
注意
ちなみに以下は動かないです
配列の中の1要素の変更でなく、配列の中のメンバーの中の要素を更新しようとしています
この場合は、その中でもproduceが必要になります
import { useCallback, useState } from "react";
import "./styles.css";
import { produce } from "immer";
class Member {
constructor(
readonly id: Number,
readonly name: String,
readonly email: String
) {}
}
export default function App() {
const [members, setMembers] = useState<Member[]>([
new Member(1, "yamada", "yamada@example.com"),
new Member(2, "tanaka", "tanaka@example.com")
]);
const changeEmail = useCallback(() => {
console.log("click button");
setMembers(
produce((draft) => {
// Memberの1要素の変更は検知できない
draft[1].email = "change@exaple.com";
})
);
}, []);
return (
<div className="App">
{members.map((member, i) => {
return (
<div key={i}>
<h1>{member.name}</h1>
<h2>{member.email}</h2>
</div>
);
})}
<button onClick={() => changeEmail()}>Change Tanaka Email</button>
</div>
);
}
おわりに
便利になっているのかな?という感じはしますが、少しばかりコードは読みやすくなっている気がします。
しかし、ライブラリを知らない人が多いと思うので自分だけが使うなら良いのかなといったいんしょうをもちましt
参考
