มารู้จักกับ ShimmerShards กัน
ShimmerShards (ซิมเมอร์ชาร์ด) คืออะไร?
React State Management Library ที่ออกแบบมาเพื่อลดความซับซ้อน (Complexity) ในการจัดการเกี่ยวกับ State พร้อมยังช่วยลดการ Re-rendering Component และใส่ใจในเรื่องของ DX (Developer Experience)
ถูกออกแบบมาจากความคิดที่ การใช้ state management ใน react ต้องเริ่มจากความที่ต้องเรียบง่าย ไม่มี Provider มาครอบ การ setup ต่างๆต้องน้อยที่สุดเท่าที่จะเป็นได้
จนผมได้เจอกับ Jotai และ Recoil ที่ใช้การ Atomic state model ทำให้การ update state และ rerender ต่างๆนั้น เกิดขึ้นแค่ ในส่วนที่จำเป็นเท่านั้น ShimmerShards เองก็ใช้ concept เดียวกันเหมือนกับทั้งคู่ แต่เราคิดในมุมมองที่จะทำยังไงให้การทำงานต่างๆ นั้น minimal ที่สุดและคนที่ไม่เคยใช้ state management library ตัวไหนมาก่อนสามารถเข้าใจได้ง่าย
โดย concept ของเราแค่ install สร้าง shard แล้วก็สามารถ share state ต่างๆได้ทั้ง App ได้เลย อีกทั้งยัง support TypeScript อีกด้วยไม่ต้องกังวลเรื่อง type ต่างๆ
เรามาเริ่มกันเลยยย 🎉
วิธีการติดตั้ง
สำหรับวิธีการติดตั้งนั้นสุดแสนจะง่าย สามารถติดต้ังผ่าน NPM (node package manager) ได้เลย
npm i shimmershards
วิธีการใช้เบื้องต้น
ขั้นตอนที่ 1 สร้าง shard ขึ้นมาก่อนเลย
import { shard } from "shimmershards";
// create a shard
const counterShard = shard(0);
ว่าแต่ shard คืออะไร???
shard
จะทำหน้าหน้าเป็นโครงสร้างของ state โดยที่ตัว shard
เองจะไม่มีการเก็บการเปลี่ยน แปลงของค่า state ใดๆ ในการใช้ ฟังก์ชั่น shard
นั้นต้องการ parameter 1 ตัวคือค่าเริ่มต้น
หากต้องการใช้ state จาก
shard
นั้นต้องทำยังไง??
ขั้นตอนที่ 2 ใช้ shard ที่สร้างขึ้นมาผ่าน useShard()
const Component = () => {
// using created shard via useShard
const [counter, setCounter] = useShard(counterShard);
return (
<button onClick={() => setCounter((prev) => prev + 1)}>
counter: {counter} + 1
</button>
);
};
หน้าที่ของ useShard
ก็ไม่ต่างจากตัว useState เลยเพียงแต่จะรับตัว shard
เป็นค่า initial แทน ส่วน state และ ตัว set state สามารถใช้ได้เหมือนกับค่าที่มาจาก useState ทุกประกาศ
แล้วมันแตกต่างจาก useState ปกติยังไง?
แตกต่างกันที่ shard นั้นสามารถ share state ออกไปใช้ได้ทั้ง web เพียงแค่ export shard และ import ไปยัง component ที่ต้องการ
import { shard } from "shimmershards";
// create a shard
export const counterShard = shard(0);
import {counterShard} from '../somewhere';
const Component = () => {
// using created shard via useShard
const [counter, setCounter] = useShard(counterShard);
return (
<button onClick={() => setCounter((prev) => prev + 1)}>
counter: {counter} + 1
</button>
);
};
เมื่อ useShard นั้นใช้ shard ตัวเดียวกัน ถ้ามี component ไหน update state ทุก component ที่ใช้ shard เดียวกับก็จะ sync กันในทันที
นี่คือการใช้ ShimmerShard แบบง่ายๆ ในเบี้องต้น จะสังเกตุว่าในการ share state ให้กับ component อื่นๆนั้น จะไม่มี Provider หรือ Context มาครอบ
ถ้าหากมีข้อมูลหรือ state เยอะมากๆ แล้วอยาก share ให้ component อื่นๆใช้ แต่ไม่อยาก import หลายตัวทำยังไง?
Cluster
cluster
คือการจัดกลุ่มในกับ shard
ที่มีความสัมพันธ์กันในเป็นกลุ่มเดียวกัน โดยวิธีการสร้าง cluster
คือการสร้าง object ขึ้นมา ตามตัวอย่างข้างล่าง
import { shard, useCluster } from "shimmershards";
const nameShard = shard("John");
const ageShard = shard(18);
export const cluster = {
useName: nameShard,
useAge: ageShard,
};
จากเดิมที่เราจะใช้ useShard สำหรับ cluster นั้นจะใช้ function ที่เรียกว่า
useCluster
โดยที่การใช้ state จาก cluster เราต้อง import ฟังก์ชั่น ที่ชื่อว่า useCluster
จาก library shimmershards
โดยที่ function จะมีรูปแบบการใช้เหมือนกับ useShard
โดยที่จะรับ parameter 1 ตัวคือ cluster object ที่เราสร้างขึ้นมา ดังตัวอย่างข้างล่าง
const Component = () => {
const { useName, useAge } = useCluster(cluster);
const [name, setName] = useName();
const [age, setAge] = useAge();
return (
<div>
name: {name} age: {age}
</div>
);
}
cluster
ที่เราสร้างขึ้นมาสามารถ export แล้วนำไปใช้ได้ในหลายๆ component เช่นกัน เหมือนกับตัว shard
อีกทั้ง shimmershards ยังให้ความสามารถในการ แยกสถานะของ shard ออกจากกันสิ่งนี้เรียกว่า
Scope
Scope
คือการทำให้ state ของ shard นั้นใช้แยกการ sync ของ state ออกจากตัว global ของการทำงาน shard แบบปกติ เราสามารถแบ่งออกเป็นส่วนๆ ได้ โดยที่แต่ละ Scope
นั้นจะมี state ที่แยกออกเป็นของตัวเองถึงแม้ว่าเราจะใช้ shard ตัวเดียวกัน
import { Scope, shard, useShard } from "shimmershards";
// Create two shards, one for name and one for age
const nameShard = shard("John");
const ageShard = shard(18);
// Child component that uses the name and age shards
const Child = () => {
const [age, setAge] = useShard(ageShard);
const [name, setName] = useShard(nameShard);
return (
<div>
age: {age}
name: {name}
<button onClick={() => setAge((prev) => prev + 1)}>increase age</button>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
};
const Component = () => {
const [age, setAge] = useShard(ageShard);
const [name, setName] = useShard(nameShard);
return (
<div>
{/* Outside scope */}
<p>
age: {age}
name: {name}
</p>
<br />
<Scope shards={[nameShard, ageShard]}>
{/* Scope A */}
<Child />
</Scope>
<br />
<Scope shards={[nameShard, ageShard]}>
{/* Scope B */}
<Child />
</Scope>
</div>
);
};
Scope
จะแยกการทำงานของ shard นั้น ออกจากกันโดยการที่เราใช้ตัว Scope
component นั้น Wrap ตัว child component ไว้และส่ง props ที่ชื่อว่า shards
โดยที่ shards จะรับเป็น Shard Array
ดังตัวอย่าง ส่วน shard ตัวอื่นๆ ที่ไม่ได้อยู่ใน shard array ดังกล่าวก็จะใช้ค่าตาม global state ตามปกติ
หรือว่าถ้าหากอยากใช้กับ cluster ก็ทำได้เช่นกัน
import { Scope, shard, useCluster } from "shimmershards";
// Create two shards, one for name and one for age
const nameShard = shard("John");
const ageShard = shard(18);
// Create a cluster with the name and age shards
const cluster = {
useName: nameShard,
useAge: ageShard,
};
// Child component that uses the cluster
const Child = () => {
const { useName, useAge } = useCluster(cluster);
const [age, setAge] = useAge();
const [name, setName] = useName();
return (
<div>
age: {age}
name: {name}
<button onClick={() => setAge((prev) => prev + 1)}>increase age</button>
<input value={name} onChange={(e) => setName(e.target.value)} />
</div>
);
};
const Component = () => {
const { useName, useAge } = useCluster(cluster);
const [age, setAge] = useAge();
const [name, setName] = useName();
return (
<div>
{/* อยู่นอก Scope*/}
<p>
age: {age}
name: {name}
</p>
<br />
<Scope shards={cluster}>
{/* Scope A */}
<Child />
</Scope>
<br />
<Scope shards={cluster}>
{/* Scope B */}
<Child />
</Scope>
</div>
);
};
ข้อควรระวัง
ในการใช้ Scope
นั้นฟังก์ชัน useShard
หรือ useCluster
นั้นจะต้องถูกใช้ภายใต้ Scope component เท่านั้นถึงจะนับว่าเป็นค่าใน Scope นั้นๆ หากเราใช้ดังตัวอย่างข้างล่างนี้จะไม่สามารถใช้งานค่าใน scope นั้นได้
const Component = () => {
const { useName, useAge } = useCluster(cluster);
const [age, setAge] = useAge();
const [name, setName] = useName();
return (
<div>
{/* อยู่นอก Scope*/}
<p>
age: {age}
name: {name}
</p>
<br />
<Scope shards={cluster}>
{/* ตัวแปร age จะใช้ค่าจาก global scope ถึงแม้ว่าจะอยู่ข้างใน
Scope component ก็ตาม เพราะ state มาจาก useCluster ที่อยู่ข้างนอก Scope*/}
<p> age: {age}</p>
</Scope>
<br />
</div>
);
};
นี่คือ Basic ทั้งหมดของการใช้งาน ShimmerShards ยังมีอีกหลายๆ function ที่ยังไม่ได้พูดถึง เช่น การทำ custom hook ด้วย memo and effect, persist shard
สามารถอ่าน documentation แบบเต็มๆได้ใน shimmershards.dev (มีทั้ง eng และ ไทย)