มารู้จักกับ ShimmerShards กัน

Nattapong Anuwong
20Scoops CNX
Published in
4 min readSep 15, 2023

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 และ ไทย)

--

--