[Luvit ํ•œ๋‹ฌ ์Šคํ„ฐ๋””] ์ƒˆ๋กœ์šด ์›น ๊ฐœ๋ฐœ์˜ ์‹œ์ž‘ ์Šค๋ฒจํŠธ #3

2025. 7. 20. 23:41ยท๐Ÿ“šbook

์ด๋ฒˆ์ฃผ์—๋Š” ์Šคํƒ€์ผ ์ œ์–ด(CSS, ์• ๋‹ˆ๋ฉ”์ด์…˜, ๋ชจ์…˜, ํŠธ๋žœ์ง€์…˜)์™€ DOM ์ œ์–ด(์•ก์…˜, ํŠน๋ณ„์š”์†Œ)๋ฅผ ๊ณต๋ถ€ํ•˜๊ณ  2๊ฐœ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹ค์Šตํ•ด๋ณด์•˜๋‹ค. ์ด๋ฒˆ ๊ธ€์€ ์‹ค์Šต์—์„œ ๋А๋‚€ ๊ฒƒ ์œ„์ฃผ๋กœ ์ž‘์„ฑํ•ด๋ด„!
 

Luvit ํ•œ๋‹ฌ ์Šคํ„ฐ๋””

 

๐Ÿชฃํ”„๋กœ์ ํŠธ: My Bucket List

๊ฐ„๋‹จํ•œ ํˆฌ๋‘ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•ด๋ณด์•˜๋‹ค. UI ๊ตฌํ˜„, props ํ™œ์šฉ๊ณผ props->store๋กœ ๋ฆฌํŒฉํ† ๋ง ๊นŒ์ง€ ์ ์ง„์ ์œผ๋กœ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๋ฉฐ Svelte์˜ ํ•ต์‹ฌ ๊ฐœ๋…์„ ์ฒดํ—˜ํ•  ์ˆ˜ ์žˆ๋Š” ํ”„๋กœ์ ํŠธ์˜€๋‹ค. ์•„๋ž˜๋Š” ์ฃผ์š” ํ•™์Šต ๋‚ด์šฉ๊ณผ ๋А๋‚€์ ๋“ค
 

์ปดํฌ๋„ŒํŠธ 

UI๋ฅผ ๊ธฐ๋Šฅ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌ์„ฑํ–ˆ๋‹ค. ํ—ค๋”(BucketHeader), ๋ฆฌ์ŠคํŠธ(BucketList), ์•„์ดํ…œ(BucketItem), ์ž…๋ ฅ ํผ(BucketCreate) ๋“ฑ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋‚˜๋ˆ„์–ด ๊ฐœ๋ฐœํ•จ์œผ๋กœ์จ ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.
 
App.svelte์—์„œ๋Š” ๊ฐ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐํ•ฉํ•˜์—ฌ ์ „์ฒด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ๋ฅผ ์™„์„ฑํ–ˆ๋‹ค.

<script>
	import BucketHeader from "./components/BucketHeader.svelte";
    import BucketList from "./components/BucketList.svelte";
    import BucketCreate from "./components/BucketCreate.svelte";
</script>

<svelte:head>
    <title>My Bucket List</title>
</svelte:head>
<div class="bucketbox">
    <BucketHeader />
    <BucketList />
    <BucketCreate />
</div>

 

๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ(Props, Store)

1. Props๋ฅผ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ

ํ”„๋กœ์ ํŠธ ์ฒ˜์Œ์—๋Š” ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์ธ App.svelte๊ฐ€ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ์†Œ์œ ํ•˜๊ณ , ์ž์‹ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ Props๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ์™€ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ๋‹ค. ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ๋ช…ํ™•ํ•˜๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์ง€๋งŒ, ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ๊ฐ€ ๊นŠ์–ด์งˆ์ˆ˜๋ก ์ค‘๊ฐ„ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” props๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•˜๋Š” props drilling์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ๊ตฌ์กฐ์˜€๋‹ค. 
 

2. ์Šคํ† ์–ด๋ฅผ ํ†ตํ•œ ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ

props drilling ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ , ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๋”์šฑ ํšจ์œจ์ ์œผ๋กœ ํ•˜๊ธฐ ์œ„ํ•ด ์Šคํ† ์–ด๋ฅผ ํ™œ์šฉํ•˜๋„๋ก ๋ฆฌํŒฉํ† ๋ง์„ ์ง„ํ–‰ํ–ˆ๋‹ค. store.js ํŒŒ์ผ์— ์ปค์Šคํ…€ ์Šคํ† ์–ด๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋ฐ์ดํ„ฐ์™€ ๊ด€๋ จ๋œ ๋กœ์ง์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•œ๋‹ค.
 
์Šคํ† ์–ด๋Š” writable, derived ์Šคํ† ์–ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์„ธ ๊ฐœ์˜ ์ปค์Šคํ…€ ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“ค์—ˆ๋”ฐ.
 
setBucketData

์ฃผ์š” ๋ฐ์ดํ„ฐ ๋ฐ ์•ก์…˜ ๊ด€๋ฆฌ

writable ์Šคํ† ์–ด๋ฅผ ๊ฐ์‹ธ ๋ฒ„ํ‚ท ๋ฆฌ์ŠคํŠธ์˜ ๋ฐ์ดํ„ฐ(buckets)์™€ ์ˆ˜์ • ๋ชจ๋“œ ์ƒํƒœ(editMode)๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ๋ชจ๋“  ํ•จ์ˆ˜(onToggle, onRemove, onSubmin ๋“ฑ)๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ๋ฌถ์–ด ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปค์Šคํ…€ ์Šคํ† ์–ด์ด๋‹ค. ๋ฐ์ดํ„ฐ์™€ ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง์„ ํ•จ๊ป˜ ์บก์Аํ™”ํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ์˜ ๋ถ€๋‹ด์„ ์ค„์ด๊ณ  ๊ด€๋ฆฌ ํฌ์ธํŠธ๋ฅผ ์ผ์›ํ™”ํ–ˆ๋‹ค.

// ch20_bucketlist/store/src/store.js
import { writable } from "svelte/store";
import { initialBuckets } from "./bucketData";
import { v4 as uuidv4 } from "uuid";

const setBucketData = () => {
    let initBucketData = {
        buckets: initialBuckets,
        editMode: ''
    }
    const { subscribe, update } = writable(initBucketData);

    const onToggle = (id) => {
        update(datas => { /* ... ์ฒดํฌ ํ† ๊ธ€ ๋กœ์ง ... */ });
    };
    const onRemove = (id) => {
        update(datas => { /* ... ์•„์ดํ…œ ์‚ญ์ œ ๋กœ์ง ... */ });
    };
    const onSubmit = (bucketText) => {
         update(datas => { /* ... ์ƒˆ ์•„์ดํ…œ ์ถ”๊ฐ€ ๋กœ์ง ... */ });
    };
    // ... onEditMode, offEditMode, onEditItem ๋“ฑ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋“ค

    // ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋“ค์„ ๊ฐ์ฒด๋กœ ๋ฆฌํ„ด
    return {
        subscribe,
        onToggle,
        onRemove,
        onEditMode,
        offEditMode,
        onEditItem,
        onSubmit
    }
}
export const buckets = setBucketData();

 
setFormBucket

๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ ์‹œ ์‚ฌ์šฉ๋  ํ…์ŠคํŠธ ๊ด€๋ฆฌ

์ƒˆ๋กœ์šด ๋ฒ„ํ‚ท์„ ์ถ”๊ฐ€ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” <input>์˜ ํ…์ŠคํŠธ ๊ฐ’์„ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ writable ์Šคํ† ์–ด์ด๋‹ค. ๊ฐ’์„ ์ดˆ๊ธฐํ™”ํ•˜๋Š” resetForm ๊ฐ™์€ ์ปค์Šคํ…€ ๋ฉ”์„œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ writable ์Šคํ† ์–ด๋ฅผ ํ™•์žฅํ–ˆ๋‹ค.

// ch20_bucketlist/store/src/store.js
const setFormBucket = () => {
    const { subscribe, set } = writable('');

    const resetFrom = () => set(''); // ๊ฐ’์„ ์ดˆ๊ธฐํ™”

    return {
        subscribe,
        set,
        resetFrom
    }
}
export const bucketText = setFormBucket();

 
setChkCount

์Šคํ† ์–ด๋กœ๋ถ€ํ„ฐ ํŒŒ์ƒ๋œ ๋ฐ์ดํ„ฐ(์ฒดํฌ๋œ ๊ฐœ์ˆ˜) ๊ด€๋ฆฌ

derived ์Šคํ† ์–ด๋Š” ๋‹ค๋ฅธ ์Šคํ† ์–ด์˜ ๊ฐ’์— ์˜์กดํ•˜๋Š” ์ƒˆ๋กœ์šด ๊ฐ’์„ ๋งŒ๋“œ๋Š” ๋ฐ ์‚ฌ์šฉ๋œ๋‹ค. setChkCount๋Š” buckets ์Šคํ† ์–ด๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค, ์™„๋ฃŒ๋˜์ง€ ์•Š์€(chk:false) ํ•  ์ผ์˜ ๊ฐœ์ˆ˜๋ฅผ ์ž๋™์œผ๋กœ ๋‹ค์‹œ ๊ณ„์‚ฐํ•˜์—ฌ ์ œ๊ณตํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ํ•  ์ผ ๊ฐœ์ˆ˜๋ฅผ ์ˆ˜๋™์œผ๋กœ ๊ด€๋ฆฌํ•  ํ•„์š” ์—†์ด ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค.

// ch20_bucketlist/store/src/store.js
import { derived } from "svelte/store";
// ...
const setChkCount = () => {
    const count = derived(buckets, $buckets => {
        return $buckets.buckets.filter((bucket) => !bucket.chk).length;
    });
    return count;
}
export const chkCount = setChkCount();

 
๋ฐ์ดํ„ฐ ํ๋ฆ„: ์ปดํฌ๋„ŒํŠธ-์Šคํ† ์–ด ์ƒํ˜ธ์ž‘์šฉ
์ปค์Šคํ…€ ์Šคํ† ์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋‹ˆ ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ํ•œ๊ฒฐ ๋‹จ์ˆœํ•˜๊ณ  ๋ช…ํ™•ํ•ด์กŒ๋‹ค.

  • ์ฝ๊ธฐ: BucketHeader๋Š” ๋‚จ์€ ํ•  ์ผ ๊ฐœ์ˆ˜๋ฅผ ํ‘œ์‹œํ•˜๊ธฐ ์œ„ํ•ด chkCount ์Šคํ† ์–ด๋ฅผ ๊ตฌ๋…ํ•œ๋‹ค. Svelte์˜ $ ๋ฌธ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ $chkCount ์ฒ˜๋Ÿผ ์ ‘๊ทผํ•˜๋ฉด, ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง๋œ๋‹ค. BucketList ์—ญ์‹œ $buckets.buckets๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ๋ฅผ ํ™”๋ฉด์— ๊ทธ๋ฆฐ๋‹ค.
  • ์“ฐ๊ธฐ: BucketItem์˜ ํœด์ง€ํ†ต ์•„์ด์ฝ˜์„ ํด๋ฆญํ•˜๋ฉด, onRemove(bucket.id) ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋œ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” buckets ์Šคํ† ์–ด์—์„œ ๊ฐ€์ ธ์˜จ ๊ฒƒ์œผ๋กœ, ๋‚ด๋ถ€์ ์œผ๋กœ update๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์Šคํ† ์–ด์˜ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค. ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด, ์ด ์Šคํ† ์–ด๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๋˜ BucketList์™€ BucketHeader๊ฐ€ ์ฆ‰์‹œ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜์˜ํ•˜์—ฌ ์—…๋ฐ์ดํŠธ๋œ๋‹ค.

์ด๋ ‡๊ฒŒ ์ปดํฌ๋„ŒํŠธ๋Š” ์Šคํ† ์–ด์˜ ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ƒํƒœ ๋ณ€๊ฒฝ์„ ์š”์ฒญํ•˜๊ณ , ์Šคํ† ์–ด๋Š” ๋‚ด๋ถ€ ๋กœ์ง์— ๋”ฐ๋ผ ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํ„ฐํ•˜๋ฉฐ, ์ด ์ƒํƒœ๋ฅผ ๊ตฌ๋…ํ•˜๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๋Š” ์ž๋™์œผ๋กœ ํ™”๋ฉด์„ ๊ฐฑ์‹ ํ•˜๋Š” ๋‹จ๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ํ๋ฆ„์ด ์™„์„ฑ๋œ๋‹ค.
 

๋™์ ์ธ ํ™”๋ฉด ์ „ํ™˜ ํšจ๊ณผ

์Šค๋ฒจํŠธ์˜ ๋‚ด์žฅ ํŠธ๋žœ์ง€์…˜๊ณผ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜์—ฌ ํšจ๊ณผ๋ฅผ ์ฃผ์—ˆ๋‹ค. ๋ฆฌ์ŠคํŠธ ์•„์ดํ…œ์ด ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜, ์‚ญ์ œ๋  ๋•Œ ํšจ๊ณผ๋ฅผ ์ ์šฉํ•˜์—ฌ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ? ์ƒ๋™๊ฐ ์žˆ๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.
 

<!-- ch20_bucketlist/store/src/components/BucketList.svelte -->
<script>
    import { fade, slide } from "svelte/transition";
    import { flip } from "svelte/animate";
    // ...
</script>

{#each $buckets.buckets as bucket, index(bucket) }
    <div in:fade out:slide animate:flip>
        <BucketItem {bucket} />
    </div>
{/each}

in:fade, out:slide, animate:flip์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹จ ๋ช‡์ค„๋งŒ์œผ๋กœ ์ž์—ฐ์Šค๋Ÿฌ์šด ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์™„์„ฑ๋๋‹ค.


๐Ÿ›ฉ๏ธํ”„๋กœ์ ํŠธ: Best Tour

My Bucket List์— ์ด์€ ๋‘ ๋ฒˆ์งธ ํ”„๋กœ์ ํŠธ. ์ƒํ’ˆ ๋ชฉ๋ก์„ ๋ณด์—ฌ์ฃผ๊ณ , ์ข‹์•„์š” ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ์„ ํ˜ธ๋„๋ฅผ ํ‘œ์‹œํ•˜๋ฉฐ ์ƒˆ๋กœ์šด ์—ฌํ–‰ ์ƒํ’ˆ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ์‚ญ์ œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค. 
์ด์ „ ํ”„๋กœ์ ํŠธ์—์„œ ๋‹ค๋ค˜๋˜ ์Šคํ† ์–ด๋ฅผ ์ด์šฉํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ์ ์šฉํ•˜๋ฉฐ ์Šคํ† ์–ด์— ๋Œ€ํ•œ ์ดํ•ด๋ฅผ ํ•œ์ธต ๋†’์ผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ด๋ฏธ์ง€์™€ ๋ณตํ•ฉ์ ์ธ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ๋‹ค๋ค„์„œ ์ข€ ๋” ์‹ค์ œ์ ์ธ ์• ํ•„๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ํ•œ ๊ฑธ์Œ ๋‹ค๊ฐ€๊ฐ
 

์ปดํฌ๋„ŒํŠธ

์—ญ์‹œ UI๋ฅผ ๊ธฐ๋Šฅ๋ณ„๋กœ ๋‚˜๋ˆ„์—ˆ๋‹ค. 

  • Title.svelte: ํŽ˜์ด์ง€์˜ ์ œ๋ชฉ์„ ํ‘œ์‹œํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ
  • BestList.svelte: ์ „์ฒด ์—ฌํ–‰ ์ƒํ’ˆ ๋ชฉ๋ก์„ ํ‘œ์‹œํ•˜๋Š” ์ปจํ…Œ์ด๋„ˆ ์ปดํฌ๋„ŒํŠธ
  • BestCreate.svelte: ์ƒˆ๋กœ์šด ์—ฌํ–‰ ์ƒํ’ˆ์„ ์ถ”๊ฐ€ํ•˜๋Š” ํผ๊ณผ ๋ฒ„ํŠผ์„ ๋‹ด์€ ์ปดํฌ๋„ŒํŠธ
// ch21_besttour/complete/src/App.svelte
<script>
	import Title from "./components/Title.svelte";
	import BestList from "./components/BestList.svelte";
	import BestCreate from "./components/BestCreate.svelte";
</script>

<div class="bestbox">
	<Title name="BEST TOUR" />
	<BestList />
	<BestCreate />
</div>

 

์ปค์Šคํ…€ ์Šคํ† ์–ด

ํ”„๋กœ์ ํŠธ ํ•ต์‹ฌ ๋กœ์ง์€ store.js ํŒŒ์ผ์— ์ •์˜๋œ ์ปค์Šคํ…€ ์Šคํ† ์–ด๋ฅผ ํ†ตํ•ด ๊ด€๋ฆฌ๋œ๋‹ค. ๋ฐ์ดํ„ฐ์™€ ๋ฐ์ดํ„ฐ๋ฅผ ์กฐ์ž‘ํ•˜๋Š” ๋กœ์ง์„ UI๋กœ๋ถ€ํ„ฐ ๋ถ„๋ฆฌํ•˜์—ฌ ์ฝ”๋“œ์˜ ์‘์ง‘๋„์™€ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์˜€๋‹ค.
 
setBestData

์—ฌํ–‰ ์ƒํ’ˆ ๋ฐ์ดํ„ฐ, ํ•ต์‹ฌ ๋กœ์ง

writable ์Šคํ† ์–ด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ, ์—ฌํ–‰ ์ƒํ’ˆ ๋ชฉ๋ก์˜ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •ํ•˜๊ณ  '์ข‹์•„์š”' ํ† ๊ธ€, ์ƒํ’ˆ ์‚ญ์ œ, ์ƒˆ ์ƒํ’ˆ ์ถ”๊ฐ€์™€ ๊ฐ™์€ ํ•ต์‹ฌ์ ์ธ ๋ฐ์ดํ„ฐ ์กฐ์ž‘ ํ•จ์ˆ˜๋“ค์„ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๋Š” ์ปค์Šคํ…€ ์Šคํ† ์–ด์ด๋‹ค.
 

// ch21_besttour/complete/src/store.js
import { writable } from "svelte/store";
import { initialBests } from './bestData';
import { v4 as uuidv4 } from 'uuid';

const setBestData = () => {
    const { subscribe, update } = writable(initialBests);

    // '์ข‹์•„์š”' ์ƒํƒœ๋ฅผ ํ† ๊ธ€ํ•˜๋Š” ํ•จ์ˆ˜
    const onToggle = (id) => {
        update(datas => {
            const setDatas = datas.map((best) => {
                return best.id === id ? { ...best, like: !best.like } : best;
            });
            return setDatas;
        });
    };

    // ํŠน์ • ์•„์ดํ…œ์„ ์‚ญ์ œํ•˜๋Š” ํ•จ์ˆ˜
    const onRemove = (id) => { /* ...์‚ญ์ œ ๋กœ์ง... */ };

    // ์ƒˆ ์•„์ดํ…œ์„ ๋ฆฌ์ŠคํŠธ์— ์ถ”๊ฐ€ํ•˜๋Š” ํ•จ์ˆ˜
    const onSubmit = (bestTexts) => { /* ...์ถ”๊ฐ€ ๋กœ์ง... */ };

    return {
        subscribe,
        onToggle,
        onRemove,
        onSubmit
    };
};

export const bests = setBestData();

 
setFormBest

์ž…๋ ฅ ํผ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ

์ƒˆ๋กœ์šด ์—ฌํ–‰ ์ƒํ’ˆ์„ ์ถ”๊ฐ€ํ•  ๋•Œ ์‚ฌ์šฉ๋˜๋Š” ์—ฌ๋Ÿฌ ์ž…๋ ฅ ํ•„๋“œ์˜ ์ƒํƒœ๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ๊ด€๋ฆฌํ•˜๋Š” ์ปค์Šคํ…€ ์Šคํ† ์–ด์ด๋‹ค. ํผ์„ ์ดˆ๊ธฐํ™”ํ•˜๋Š” resetFrom ๊ฐ™์€ ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜๋„ ํ•จ๊ป˜ ์ œ๊ณตํ•˜์—ฌ ํผ ๊ด€๋ฆฌ๋ฅผ ์šฉ์ดํ•˜๊ฒŒ ํ–ˆ๋‹ค.

// ch21_besttour/complete/src/store.js
const setFormBest = () => {
    let formText = { name: '', price: '', image: '', descript: '' };
    const { subscribe, set } = writable(formText);

    const resetForm = () => {
        set({ name: '', price: '', image: '', descript: '' });
    };

    return { subscribe, set, resetForm };
};

export const bestTexts = setFormBest();

 

์Šคํ† ์–ด์™€ ์ปดํฌ๋„ŒํŠธ ์ƒํ˜ธ์ž‘์šฉ

๊ฐ ์ปดํฌ๋„ŒํŠธ๋Š” ํ•„์š”ํ•œ ์Šคํ† ์–ด๋ฅผ ์ง์ ‘ ๊ตฌ๋…ํ•˜์—ฌ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๊ณ , ์Šคํ† ์–ด๊ฐ€ ์ œ๊ณตํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•œ๋‹ค. 

  • ๋ฐ์ดํ„ฐ ๊ตฌ๋…($ ๋ฌธ๋ฒ•): BestList.svelte๋Š” $bests๋ฅผ each ๋ธ”๋ก์—์„œ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌํ–‰ ์ƒํ’ˆ ๋ชฉ๋ก์„ ๋ฐ˜๋ณต ๋ Œ๋”๋งํ•˜๊ณ , BestCreate.svelte๋Š” $bestTexts๋ฅผ ๊ฐ input์— bind:valueํ•˜์—ฌ ์–‘๋ฐฉํ–ฅ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.
  • ์•ก์…˜ ํ˜ธ์ถœ: BestItem.svelte์˜ ์ข‹์•„์š” ๋ฒ„ํŠผ์€ on:Click={()=>onToggle(id)}์™€ ๊ฐ™์ด bests ์Šคํ† ์–ด์—์„œ ๊ฐ€์ ธ์˜จ onToggle ํ•จ์ˆ˜๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•œ๋‹ค. ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด $bests๋ฅผ ๊ตฌ๋…ํ•˜๋Š” BestList ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜์–ด ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ํ™”๋ฉด์— ์ฆ‰์‹œ ๋ฐ˜์˜๋œ๋‹ค.
<!-- ch21_besttour/complete/src/components/BestItem.svelte -->
<script>
    import { bests } from '../store';
    // ...
    const { onToggle, onRemove } = bests;
</script>

<li>
    <a href="#!">
        <img src={image} alt={name} />
        <button class="likebox" on:click={() => onToggle(id)}>
            <!-- ... -->
        </button>
        <!-- ... -->
    </a>
</li>

๐Ÿˆ๋А๋‚€์ 

ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜๋ฉด์„œ Svelte์˜ ๊ฐœ๋ฐœ ๋ฐฉ์‹์— ์ต์ˆ™ํ•ด์งˆ ์ˆ˜ ์žˆ์—ˆ๋‹ค. React์— ์ต์ˆ™ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋™์ผํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ React๋กœ ๊ตฌํ˜„ํ•˜๋Š” ์ƒํ™ฉ์„ ๊ฐ€์ •ํ•˜๋ฉด์„œ ๋น„๊ตํ•ด๋ณด๋‹ˆ Svelte๋งŒ์˜ ์žฅ์ ๋“ค์ด ๋” ๋ช…ํ™•ํ•˜๊ฒŒ ๋А๊ปด์ง€๋Š” ๋“ฏ ํ•˜๋‹ค.
 
1. ์ƒํƒœ๊ด€๋ฆฌ: React์—์„œ๋Š” ์ƒํƒœ์™€ ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜๋“ค์„ Props๋กœ ๊ณ„์†ํ•ด์„œ ๋‚ด๋ ค๋ณด๋‚ด์•ผ ํ•˜์ง€๋งŒ, Svelte์—์„œ๋Š” ์Šคํ† ์–ด๋ฅผ ๋งŒ๋“ค์–ด์„œ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ”๋กœ ๋ถˆ๋Ÿฌ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. props drilling์„ ์›์ฒœ์ ์œผ๋กœ ์ฐจ๋‹จํ•˜๊ณ , ์ปดํฌ๋„ŒํŠธ๋“ค์ด ์ƒํƒœ ๋กœ์ง์œผ๋กœ๋ถ€ํ„ฐ ๋…๋ฆฝ๋˜์–ด ํ›จ์”ฌ ๊น”๋”ํ•˜๋‹ค๊ณ  ๋А๊ผˆ๋‹ค. ๋ฌผ๋Ÿฐ React์—์„œ๋„ useReducer๋‚˜ Context๋กœ ์ƒํƒœ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ธด ํ•˜์ง€๋งŒ, ์Šค๋ฒจํŠธ Store๊ฐ€ ๋” ์ง๊ด€์ ์œผ๋กœ ๋А๊ปด์กŒ๋‹ค.
 
2. ๋ฐ˜์‘์„ฑ: ์Šค๋ฒจํŠธ์—์„œ๋Š” ์Šคํ† ์–ด ์•ž์— $ ๊ธฐํ˜ธ๋ฅผ ๋ถ™์ด๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋„ ๊ฐ’์˜ ๋ณ€ํ™”๋ฅผ ๊ตฌ๋…ํ•˜๊ณ  ํ™”๋ฉด์„ ์ž๋™์œผ๋กœ ๊ฐฑ์‹ ํ•  ์ˆ˜ ์žˆ๋‹ค. React์—์„œ useEffect๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํŠน์ • ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  ๋ถ€์ˆ˜ ํšจ๊ณผ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ๊ฐ„๋‹จํ•˜๋‹ค๊ณ  ๋А๊ผˆ๋‹ค. ์ฝ”๋“œ์˜ ์–‘์ด ์ค„์–ด๋“ค ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, ์ด ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด ์ด ๋ถ€๋ถ„๋„ ๋ฐ”๋€๋‹ค๋Š” ๊ด€๊ณ„๊ฐ€ ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚ฌ๋‹ค.
 
3. ๊ฐ„๊ฒฐํ•œ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ: ์Šค๋ฒจํŠธ์—์„œ๋Š” <script>, <style> ๋งˆํฌ์—…์ด ํ•œ ํŒŒ์ผ ์•ˆ์— ๊ณต์กดํ•˜๋Š” ๊ตฌ์กฐ ๋•๋ถ„์— ์ปดํฌ๋„ŒํŠธ์˜ ๋ชจ๋“  ์ฝ”๋“œ๋ฅผ ํ•œ ๋ˆˆ์— ํŒŒ์•…ํ•˜๊ธฐ ์‰ฌ์› ๋‹ค. ๋˜ํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋กœ์ง์„ Store๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด์„œ UI ์ปดํฌ๋„ŒํŠธ๋“ค์€ ์˜ค์ง ํ™”๋ฉด์„ ๊ทธ๋ฆฌ๋Š” ์ฑ…์ž„์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. React์˜€๋‹ค๋ฉด ์ƒํƒœ ๋กœ์ง์„ ํ’ˆ์–ด ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ›จ์”ฌ ๋น„๋Œ€ํ•ด์กŒ์„ ๊ฒƒ. Svelte๋ฅผ ์“ฐ๋‹ˆ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ฐ€๋ณ๊ฒŒ ๋А๊ปด์ง€๋Š” ๋“ฏ ํ•˜๋‹ค.
 
ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด ์Šค๋ฒจํŠธ์˜ ํ•ต์‹ฌ ๋ฌธ๋ฒ•์„ ๊ฒฝํ—˜ํ•ด๋ณผ ์ˆ˜ ์žˆ์—ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ ๊ตฌ์„ฑ๋ถ€ํ„ฐ props, store๋ฅผ ์‚ฌ์šฉํ•œ ์ƒํƒœ ๊ด€๋ฆฌ, ๊ทธ๋ฆฌ๊ณ  ํŠธ๋žœ์ง€์…˜์œผ๋กœ ํšจ๊ณผ๊นŒ์ง€ ๋‹จ๊ณ„๋ณ„๋กœ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์ด์ „ ํ•™์Šต์—์„œ๋Š” ๊ฒฝํ—˜ํ•  ์ˆ˜ ์—†์—ˆ๋˜ ๋ฌธ๋ฒ• ํ™œ์šฉ๊ณผ Svelte ์•ฑ ํ๋ฆ„์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค. Svelte๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ฐœ๋ฐœ ํŽธ์˜์„ฑ๊ณผ ์žฌ๋ฏธ๋ฅผ ์ œ๋Œ€๋กœ ๋А๋‚„ ์ˆ˜ ์žˆ์—ˆ๋˜ ํ•™์Šต์ด์—ˆ์Œ!

'๐Ÿ“šbook' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[Luvit ํ•œ๋‹ฌ ์Šคํ„ฐ๋””] ์ƒˆ๋กœ์šด ์›น ๊ฐœ๋ฐœ์˜ ์‹œ์ž‘ ์Šค๋ฒจํŠธ #4  (4) 2025.07.27
[npm deep dive] ์™„๋… ์ฑŒ๋ฆฐ์ง€-์™„๋… ๋ชปํ•œ ํ›„๊ธฐ  (12) 2025.07.23
[ํ˜ผ๊ณตํ•™์Šต๋‹จ 14๊ธฐ] ๊ฐ•์•„์ง€ ๊ณ ์–‘์ด ์‚ฌ์ง„ ๋ถ„๋ฅ˜ ์‹ค์Šต(AlexNet, VGGNet, ResNet)  (6) 2025.07.20
[ํ˜ผ๊ณตํ•™์Šต๋‹จ 14๊ธฐ] LeNet ์‹ค์Šต-Fashoin MNIST ๋ถ„๋ฅ˜ ์‹ค์Šต  (3) 2025.07.13
[Luvit ํ•œ๋‹ฌ ์Šคํ„ฐ๋””] ์ƒˆ๋กœ์šด ์›น ๊ฐœ๋ฐœ์˜ ์‹œ์ž‘ ์Šค๋ฒจํŠธ #2  (8) 2025.07.13
'๐Ÿ“šbook' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€
  • [Luvit ํ•œ๋‹ฌ ์Šคํ„ฐ๋””] ์ƒˆ๋กœ์šด ์›น ๊ฐœ๋ฐœ์˜ ์‹œ์ž‘ ์Šค๋ฒจํŠธ #4
  • [npm deep dive] ์™„๋… ์ฑŒ๋ฆฐ์ง€-์™„๋… ๋ชปํ•œ ํ›„๊ธฐ
  • [ํ˜ผ๊ณตํ•™์Šต๋‹จ 14๊ธฐ] ๊ฐ•์•„์ง€ ๊ณ ์–‘์ด ์‚ฌ์ง„ ๋ถ„๋ฅ˜ ์‹ค์Šต(AlexNet, VGGNet, ResNet)
  • [ํ˜ผ๊ณตํ•™์Šต๋‹จ 14๊ธฐ] LeNet ์‹ค์Šต-Fashoin MNIST ๋ถ„๋ฅ˜ ์‹ค์Šต
ํ‚ํ‚์ž‰
ํ‚ํ‚์ž‰
๋ฟŒ๋ก ํŠธ ๊ฐœ๋ฐœ์ž(์ง€๋ง์ƒ)์˜ ํ’€์Šคํƒ ๊ฐœ๋ฐœ์ž ๋„์ „๊ธฐ
  • ํ‚ํ‚์ž‰
    monicx.dev
    ํ‚ํ‚์ž‰
  • ์ „์ฒด
    ์˜ค๋Š˜
    ์–ด์ œ
    • ๋ถ„๋ฅ˜ ์ „์ฒด๋ณด๊ธฐ (173)
      • ๐Ÿ–ฅ๏ธdevelop (2)
        • Github (2)
        • Frontend (4)
        • Backend (5)
        • Mobile (0)
        • CS (0)
        • Three.js (0)
        • Docker (2)
      • ๐Ÿ“šbook (9)
        • npm Deep Dive (4)
      • ๐Ÿ“•review (33)
        • ์ฑ… (24)
        • ํ–‰์‚ฌ (1)
        • ํšŒ๊ณ  (2)
      • โญproject (5)
        • petiary (2)
        • ๆšŽ่ฉ  (0)
        • ์ธํ„ด (2)
      • ๐Ÿ˜ถ‍๐ŸŒซ๏ธalgorithm (0)
      • ๐Ÿ’กtips (1)
      • ๐Ÿ˜Ždaily (10)
      • ๐Ÿ•น๏ธgame (0)
      • ๐Ÿ•Š๏ธํ”„๋กœ๊ทธ๋ž˜๋จธ์Šค ๋ฐ๋ธŒ์ฝ”์Šค (87)
        • TIL (61)
        • ํ”„๋กœ์ ํŠธ (18)
        • ํšŒ๊ณ  (8)
  • ๋ธ”๋กœ๊ทธ ๋ฉ”๋‰ด

    • ํ™ˆ
    • ํƒœ๊ทธ
  • ๋งํฌ

    • ๋ฒจ๋กœ๊ทธ
  • ์ธ๊ธฐ ๊ธ€

  • ์ตœ๊ทผ ๊ธ€

  • hELLOยท Designed By์ •์ƒ์šฐ.v4.10.3
ํ‚ํ‚์ž‰
[Luvit ํ•œ๋‹ฌ ์Šคํ„ฐ๋””] ์ƒˆ๋กœ์šด ์›น ๊ฐœ๋ฐœ์˜ ์‹œ์ž‘ ์Šค๋ฒจํŠธ #3
์ƒ๋‹จ์œผ๋กœ

ํ‹ฐ์Šคํ† ๋ฆฌํˆด๋ฐ”