๐Ÿ‘ฉ‍๐Ÿ’ป/Vue

[Vue-todo-list] #2 ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ์žˆ๋Š” Todo List ๋งŒ๋“ค๊ธฐ

ํ•œ๋‚˜ 2021. 2. 8. 01:39

2021/02/08 - [๐Ÿ‘ฉ‍๐Ÿ’ป/Vue.js] - [Vue-todo-list] #1 Date ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ๋‹ฌ๋ ฅ ์ˆซ์ž ๊ตฌํ•˜๊ธฐ/์ง€๋‚œ ๋‚ ์งœ, ์˜ค๋Š˜ ๋‚ ์งœ ๊ตฌ๋ณ„์„ ์œ„ํ•œ CSS ์Šคํƒ€์ผ๋ง

 

[Vue-todo-list] #1 Date ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•ด ๋‹ฌ๋ ฅ ์ˆซ์ž ๊ตฌํ•˜๊ธฐ/์ง€๋‚œ ๋‚ ์งœ, ์˜ค๋Š˜ ๋‚ ์งœ ๊ตฌ๋ณ„์„ ์œ„ํ•œ CSS ์Šคํƒ€์ผ

๋งจ ์ฒ˜์Œ Vue๋ฅผ ์ ‘ํ–ˆ์„ ๋•Œ ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ๋ณด๋ฉฐ ๋งŒ๋“ค์—ˆ๋˜ To do list ์˜ˆ์ œ๊ฐ€ ์žˆ์—ˆ๋‹ค. ์žŠ๊ณ  ์ง€๋‚ด๋‹ค๊ฐ€ ๋ช‡ ์ฃผ ์ „์— ์ด ์˜ˆ์ œ ์œ„์— TypeScript๋‚˜ CSS framework์ธ Tailwinds ๋“ฑ์„ ๊ณต๋ถ€ํ•ด๋ณด๊ณ  ์‹ถ์–ด์„œ ์ด๊ฒƒ์ €๊ฒƒ ์ƒ๊ฐ์ด ๋‚ 

uiyoji-journal.tistory.com


์œ„์ฒ˜๋Ÿผ ๋‹จ์ˆœํ•˜๊ฒŒ to do ๋ฆฌ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ , ํŽธ์ง‘ํ•˜๊ณ , ์™„๋ฃŒํ•˜๋Š” ๊ธฐ๋Šฅ์—์„œ, ์•„๋ž˜์˜ ์นดํ…Œ๊ณ ๋ฆฌ ํŽธ์ง‘ ๊ธฐ๋Šฅ์„ ๋„ฃ์—ˆ๋‹ค.

 

์นดํ…Œ๊ณ ๋ฆฌ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ์œ„ํ•œ UI

์œ„ Nav Bar์— Daily Report์—์„œ๋Š” ๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํŒŒ์ด ์ฐจํŠธ, ๋ผ์ธ ์ฐจํŠธ๋กœ ์‹œ๊ฐํ™”ํ•ด ๋ณด์—ฌ์ฃผ๋Š”๋ฐ, ๊ทธ๋•Œ๋ฅผ ์œ„ํ•œ ์ƒ‰ ์ง€์ •์ด ํ•„์š”ํ•ด <input type="color"> ํƒœ๊ทธ์˜ ์ปฌ๋Ÿฌ ํ”ผ์ปค๋ฅผ ์ฒ˜์Œ ์จ๋ดค๋‹ค.

๊ธฐ์กด input ์ฐฝ์—์„œ ๊ธ€ ์“ฐ๊ณ  ์—”ํ„ฐ๋ฅผ hit ํ•˜๋ฉด ์ƒˆ๋กœ์šด ์นดํ…Œ๊ณ ๋ฆฌ๊ฐ€ ๋“ฑ๋ก๋˜๋Š” ๊ฒƒ์—์„œ ์ปฌ๋Ÿฌ๋„ ํ•จ๊ป˜ ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค. @keyup.enter="addCate()" ๋ฉ”์„œ๋“œ๋ฅผ ์ผ๋‹ค.

 

์ปจํŠธ๋กค ํŒจ๋„ ๋ถ€๋ถ„์€ table๋กœ ์งฐ๋Š”๋ฐ, ๋‘ ๋ฒˆ์งธ row์ธ <tr> ๋‚ด๋ถ€์—์„œ select์— ๋Œ€ํ•œ v-for ๋•Œ๋ฌธ์— ์‚ด์ง ์• ๋ฅผ ๋จน์—ˆ๋‹ค. ์œ„ ์ƒํƒœ์˜ ๊น”๋”ํ•œ UI๋ฅผ ๊ทธ๋Œ€๋กœ ์ง„ํ–‰ํ•˜๋ ค๋Š”๋ฐ ๊ตฌ์„ฑ์ด ์•„๋ž˜์™€ ๊ฐ™์•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

<tr v-for="(item, index) in categories." :key="index">
	<td><label /></td>
    <td><input /><td>
    <td><label /></td>
    <td>
    	<select>
        	<option v-for="(cate, idx) in categories" :key="idx" :value="cate.title" :label="cate.title">
        </select>
        <input />
   	</td>
    

 

๊ฐ„๋žตํ•˜๊ฒŒ ์ด๋Ÿฐ ๊ตฌ์กฐ์ธ๋ฐ, select ํƒœ๊ทธ ๋‚ด option์— v-for๋กœ ํ˜„์žฌ๊นŒ์ง€ ์ €์žฅ๋œ categories ์˜ ๋ฐฐ์—ด์„ ๋ฐ›์•„์™€ ๋ฟŒ๋ ค์ฃผ๋Š” ๊ฒƒ๊นŒ์ง€๋Š” ์ข‹์•˜๋Š”๋ฐ, v-for๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š๋Š” ๊ณณ์—์„œ cate๋ฅผ ๊ฐ€์ง€๊ณ  ์˜ฌ ์ˆ˜๊ฐ€ ์—†์—ˆ๋‹ค. ํ•  ์ˆ˜ ์—†์ด <td> ๋‚ด๋ถ€์˜ <select>๋Š” ๊ทธ๋Œ€๋กœ ๋‘๊ณ  <tr>์—์„œ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  v-for๋ฅผ ํ•œ ๋ฒˆ ๋” ์ผ๋‹ค.

 

์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” <tr>์ด ๋ฐ˜๋ณต๋˜๋ฏ€๋กœ, ์•„๋ž˜์ฒ˜๋Ÿผ ๋ฐฐ์—ด์„ slice ํ•ด์ค„ ํ•„์š”๊ฐ€ ์ƒ๊ธด๋‹ค.

 

<tr v-for="(item, index) in categories.slice(this.sliceNum, this.sliceNum + 1)" :key="index">

 

๊ทธ๋Ÿฌ๋ฉด ํ™”๋ฉด์—์„œ๋Š” ์•„๊นŒ์ฒ˜๋Ÿผ ๋”ฑ ํ•„์š”ํ•œ ํ•œ ์ค„๋งŒ ์–ป์„ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. 0, 1์ด ์•„๋‹Œ sliceNum ๋ณ€์ˆ˜๋ฅผ ๋„ฃ์–ด์ค€ ๊ฒƒ์€ ์นดํ…Œ๊ณ ๋ฆฌ๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ์ด <tr>๋ฅผ ๋™์ ์œผ๋กœ ๋ฐ”๊ฟ”์ฃผ๊ธฐ ์œ„ํ•ด์„œ์˜€๋‹ค. <select>์˜ <option> ๋‚ด๋ถ€์—๋Š” :value๋‚˜ :label ๋“ฑ์˜ ๋””๋ ‰ํ‹ฐ๋ธŒ๊ฐ€ ์—ฌ๋Ÿฌ ๊ฐœ๊ฐ€ ์žˆ์ง€๋งŒ, v-for๋ฅผ ์ค‘์ฒฉ์œผ๋กœ ์“ธ ์ˆœ ์—†์—ˆ์œผ๋ฏ€๋กœ, <select> ํƒœ๊ทธ์— @chage ์ด๋ฒคํŠธ๋ฅผ ๊ฑธ์–ด ๋ณ€ํ™”๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ changeCateInfo($event) ๋ฉ”์„œ๋“œ๋ฅผ invoke ์‹œ์ผฐ๋‹ค.

 

changeCateInfo(event: any) {
      const currentTitle = event.target.value;
      for (let i = 0; i < this.categories.length; i++) {
        if (this.categories[i].title === currentTitle) this.sliceNum = i;
      }
    },

this.categories๋Š” Vuex๋ฅผ ํ†ตํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ store.js์— ์ €์žฅํ•ด๋‘” state์˜ ํ•˜๋‚˜์ธ๋ฐ, ์ €์žฅํ•ด๋‘” ๋ฐฐ์—ด์„ ๊ฐ€์ง€๊ณ  ์˜ค๋Š” ๋ณ€์ˆ˜์ด๋‹ค. this.categories ๊ธธ์ด๋งŒํผ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ event.target.value์™€ title์ด ์ผ์น˜ํ•  ๋•Œ ํ•ด๋‹น ์ธ๋ฑ์Šค๋ฅผ sliceNum์— ๋„ฃ์–ด์ฃผ๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

๊ทธ๋Ÿฌ๋ฉด <tr> ๋ฐ‘์˜ color picker๋„ ํ•ด๋‹น ์นดํ…Œ๊ณ ๋ฆฌ ํƒ€์ดํ‹€์— ๋งž๋Š” ์ƒ‰์„ ์ถœ๋ ฅํ•ด์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

๋‹ค๋งŒ ์—ฌ๊ธฐ์„œ ๋˜ <select>์˜ <option>์˜ ๋ผ๋ฒจ์ด ์ œ๋Œ€๋กœ ํ‘œ๊ธฐ๊ฐ€ ์•ˆ ๋˜๋Š” ๋ฌธ์ œ์ ์ด ์ƒ๊ธฐ๋Š”๋ฐ ํ•œ์ฐธ ํ—ค๋งค๋‹ค๊ฐ€ :required ๋””๋ ‰ํ‹ฐ๋ธŒ์™€ :selected๋กœ ํ•ด๊ฒฐ์„ ๋ดค๋‹ค.

 

<select
    name="categories"
    id="categories"
    class="w-1/3 mr-3 pl-2 border rounded border-solid border-primary-lightgray"
    @change="changeCateInfo($event)"
    :required="true"
  >
    <option
      v-for="(cate, idx) in categories"
      :key="idx"
      :value="cate.title"
      :label="cate.title"
      :selected="cate.title === item.title"
      >{{ cate.title }}</option
    >
</select>

๋‘ ๋””๋ ‰ํ‹ฐ๋ธŒ ๋ชจ๋‘ boolean ๊ฐ’์„ ๋ฐ›๋Š”๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ค‘์ฒฉ๋œ ๋‘ v-for์˜ item๊ณผ cate ๊ฐ์ฒด๋ฅผ ๋ชจ๋‘ ๊ฐ€์ง€๊ณ  ์™€ ์„œ๋กœ์˜ title์„ ๋น„๊ตํ•˜๊ณ , ์ผ์น˜ํ•œ๋‹ค๋ฉด true๋ฅผ ๋ฐ˜ํ™˜ํ•ด ํ•ด๋‹น ๋ผ๋ฒจ์„ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

Store

 

์นดํ…Œ๊ณ ๋ฆฌ ์—ญ์‹œ store์— ์ €์žฅํ•ด state๋กœ ๊ด€๋ฆฌํ•˜๊ณ , localStorage์— ์ €์žฅํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ–ˆ๋‹ค. ๋ผ๋ฒจ์„ ์ง€์ •ํ•˜๊ณ  ์ปฌ๋Ÿฌ๋ฅผ pickํ•˜๊ฑฐ๋‚˜ ์ปฌ๋Ÿฌ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ํƒ€์ดํ‹€๋งŒ ์ž‘์„ฑํ•˜๊ณ  ์—”ํ„ฐ๋ฅผ hitํ•˜๋ฉด this.$store.commit("<mutation ์ด๋ฆ„ >", payload) store๋กœ ์ปค๋ฐ‹์„ ํ•˜๋„๋ก ํ–ˆ๋‹ค.

 

changeExistingCate(item: Category, event: any, param: string) {
      const cateIndex = this.categories.indexOf(item);
      if (param === "color") {
        this.$store.commit("modifyCate", {
          backgroundColor: event.target.value,
          index: cateIndex
        });
      } else {
        this.$store.commit("modifyCate", {
          title: event.target.value,
          index: cateIndex
        });
      }
    },

chageExistingCate๋Š” ๋‘ ๋ฒˆ ์“ฐ์ด๋Š”๋ฐ, ํ•˜๋‚˜๋Š” param์ด 'color'์ผ ๋•Œ ํ•˜๋‚˜๋Š” 'title'๋กœ ๋“ค์–ด์˜ฌ ๋•Œ์ด๋‹ค. commit ํ•ด์ค„ ๋•Œ์˜ paylaod๊ฐ€ ๊ฐ๊ฐ ๋‹ค๋ฅด๋ฏ€๋กœ, store.js๋„ ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑํ•ด์ฃผ์—ˆ๋‹ค.

 

// store/store.js

modifyCate(state, payload) {
      const categories = this.state.categories;
      const index = payload.index;
      const bgColor = payload.backgroundColor;
      const title = payload.title;

      if (bgColor && index) {
        categories[index].backgroundColor = bgColor;
      }
      if (title && index) {
        categories[index].title = title;
      }
      localStorage.setItem("categories", JSON.stringify(state.categories));
}

 

๐Ÿ’Œ todo.vue ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์ „์ฒด ์†Œ์Šค๋Š” ์ด๊ณณ์—์„œ