• [Vue/Vuex] 뷰 실습 - Todo 웹앱 만들기 (1)

    2020. 5. 26.

    by. 나나 (nykim)

    이 글은 Vue.js 중급 강좌 - 웹앱 제작으로 배워보는 Vue.js, ES6, Vuex 강의를 바탕으로 작성한 내용입니다.
    멋진 강의 감사합니다 (__)

     

     

     

    1. 프로젝트 구상

    Todo List를 보여주는 간단한 웹 페이지를 만들어보겠습니다.

    localStorage를 활용해 리스트를 추가/삭제하려고 합니다.

     

     

     

    얍, 이런 느낌으로 만들어보려고 해요 🙋‍♀️

    (디자인은 미래의 나에게 맡긴다!)

     

    여기에 필요한 컴포넌트를 나열하면 이렇겠네요.

    - header : 웹앱 이름과 오늘 날짜를 보여줍니다

    - title : 몇 개의 할 일이 남았는지 보여줍니다

    - input : 할 일을 입력받습니다

    - controller: 필터와 모두 삭제 기능이 들어 있습니다

    - list : 할 일 목록을 보여줍니다

    - footer : 카피라이트가 들어갑니다

    - modal : 할 일을 입력하지 않은 상태일 때 안내문을 보여줍니다

     

    또, 아래 상황에 맞춰 서로 다른 내용을 보여줘야하는 컴포넌트도 있습니다.

    - title : 최초 진입 시 이름을 묻는 화면이 표시됩니다

    - emptyList : 아무런 리스트가 없을 때 표시됩니다

     

     

    그럼 뚝딱뚝딱 만들어 봅시다 🛠

     

     


     

    2. 프로젝트 생성

    # shell
    $ npm install -g @vue/cli
    $ vue create vue-mytodo

     

    @vue/cli 설치 후 vue create <프로젝트 이름>으로 프로젝트를 만듭니다.

    우선 default 옵션으로 생성했습니다.

     

     

    알아서 잘 만들어집니다 (크으)

     

     

    Git 저장소 연결

    git remote add origin <원격 서버 주소>로 원격 저장소에 연결한 뒤,

    git push origin master로 푸쉬합니다.

     

     

     

    기본 컴포넌트 파일 생성

    components 폴더 내의 HelloWorld.vue를 지우고 필요한 컴포넌트 파일을 만듭니다.

    파일명은 파스칼 표기법으로 작성합니다. 내용은 나중에 작성하고 파일만 생성해 둘게요.

    이때, 모달은 common 폴더 내에 넣어둡니다.

     

     

    이렇게 만든 컴포넌트들을 App에 연결할 차례입니다.

    App.vue 파일에서 import TodoHeader from "./components/TodoHeader";와 같이 파일들을 임포트 해줍니다.

    그런 다음 components: { } 아래에 하나씩 넣어줍니다.

     

    <!-- App.vue -->
    
    <template>
      <div id="app">
        <TodoHeader />
        <TodoTitle />
        <TodoInput />
        <TodoController />
        <TodoList />
        <TodoFooter />
      </div>
    </template>
    
    <script>
    import TodoHeader from "./components/TodoHeader";
    import TodoTitle from "./components/TodoTitle";
    import TodoInput from "./components/TodoInput";
    import TodoController from "./components/TodoController";
    import TodoList from "./components/TodoList";
    import TodoFooter from "./components/TodoFooter";
    
    export default {
      name: "App",
      components: {
        TodoHeader,
        TodoTitle,
        TodoInput,
        TodoController,
        TodoList,
        TodoFooter
      }
    };
    </script>

     

     


     

     

    3. 컴포넌트 마크업

    만들어 둔 컴포넌트에 마크업 작업을 진행합니다.

     

     

    헤더

    로고와 현재 날짜가 들어가는 영역입니다.

     

    <!-- TodoHeader.vue -->
    
    <template>
      <header class="header">
        <h1 class="logo">My Todo</h1>
        <p class="date">{{ timestamp }}</p>
      </header>
    </template>
    
    <script>
    export default {
      data() {
        return {
          timestamp: ""
        };
      },
      created() {
        const now = new Date();
        const month = now.getMonth() + 1;
        const date = now.getDate();
        const weekList = new Array("Sun.", "Mon.", "Tue.", "Wed.", "Thu.", "Fri.", "Sat.");
        const week = weekList[now.getDay()];
        this.timestamp = `${month}/${date} ${week}`;
      }
    };
    </script>

     

    마크업은 아니지만 스크립트도 살짝 추가했습니다.

    날짜를 구하는 기능을 어디에 넣을까 고민을 하다가created()부분에 구현했어요!

     

    아직 라이프사이클 개념이 명확하지 않지만, created는 data와 events에 접근할 수 있고 가상 DOM은 접근할 수 없는 상태라고 합니다. 날짜를 구하는 건 data만 있으면 되기 때문에 이 단계에 넣어도 괜찮을 거라 생각했습니다.

    더 좋은 방법이 있으면 언제든 알려 주세요 👐

     

     

    타이틀

    인사말과 태스크 총 개수를 보여주는 곳입니다. 얘네는 나중에 따로 처리해 줄 생각입니다.

     

    <!-- TodoTitle.vue -->
    
    <template>
      <div class="title">
        <p class="title__message">{{ message }}</p>
        <p class="title__task">
          You've got
          <span class="title__task-total">{{ taskTotal }}</span> tasks today.
        </p>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          message: "Hello, nana.",
          taskTotal: 5
        };
      }
    };
    </script>

     

     

    리스트

    태스크의 리스트를 보여줍니다. 나중에 반복문으로 처리하겠지만, CSS 확인을 위해 쌩으로 넣어놨습니다.

     

    <!-- TodoList.vue -->
    
    <template>
      <ul class="list">
        <li class="list__item">
          <input type="checkbox" id="list-item-1" />
          <label for="list-item-1">
            <p class="list__text">Smile! :)</p>
          </label>
          <p class="list__date">5/26</p>
          <button class="list__delete">Delete</button>
        </li>
      </ul>
    </template>

     

     

    인풋 / 컨트롤러 / 푸터

    화면상에 따로 변경될 부분이 적은 컴포넌트입니다. 빠르게 마크업 슥슥!

     

    <!-- TodoInput.vue -->
    
    <template>
      <div class="add">
        <input type="text" class="add__input" placeholder="Enter your task" />
        <button class="add__button">Add</button>
      </div>
    </template>
    <!-- TodoController.vue -->
    
    <template>
      <div class="controller">
        <div class="select">
          <label for="order">Order</label>
          <select name="order" id="order" class="selectbox">
            <option value="date-asc">Date Ascending</option>
            <option value="date-desc">Date Descending</option>
            <option value="name-asc">Name Ascending</option>
            <option value="name-desc">Name Descending</option>
          </select>
        </div>
        <button class="clear">Clear All</button>
      </div>
    </template>
    <!-- TodoFooter.vue -->
    
    <template>
      <footer class="footer">Made by nana with 💛</footer>
    </template>

     

    마크업이 중요한 작업이 아니므로 빠르게 진행했습니다.

    작업 도중에 내용이 바뀔 수도 있을 거 같아요ㅎㅎ

     

    페이지를 확인하면 이런 모습으로 나옵니다!

     

     

     


     

    4. 컴포넌트 스타일링

    😋제가 제일 좋아하고 잘하는 작업이에요!!! 

    마크업한 컴포넌트를 예쁘게 꾸며줍니다.

     

     

    디자인 시안 제작

    와이어프레임을 바탕으로 시안을 쓱쓱 작업합니다.

     

     

    모바일 기준으로 작업했고, 시안은 만들지 않았지만 미디어쿼리로 반응형 작업을 진행할 예정입니다.

     

     

    Sass 설정

    효율적인 CSS 작성을 위해 전처리기를 쓸 수 있도록 설정합니다.

    Vue CLI는 PostCSS, CSS Modules를 지원하고 Sass, Less, Stylus까지 지원합니다. (공식문서 링크)

     

    저는 Sass, 특히 scss 문법을 사용하므로 아래와 같이 설치했습니다.

     

    npm install node-sass sass-loader --save-dev
    <style lang="scss">
    $color: #f0f0f0;
    .list {
      background: $color;
    
      &__text {
        font-weight: bold;
      }
    }
    </style>

     

    믹스인이나 변수, 공통 스타일은 아래처럼 외부에서 불러와 사용할 수 있습니다.

     

    <style lang="scss">
    @import "./assets/style/reset";
    @import "./assets/style/mixins";
    .footer {
      @include flexbox;
      @include ellipsis(3);
    }
    </style>

     

    그런데 리셋은 상위 컴포넌트에서 한 번만 불러오면 된다고 해도, 믹스인은 여기저기서 필요한 스타일시트입니다.

    이걸 각 컴포넌트마다 @import로 불러올 수도 있지만 디폴트로 지정하면 편하겠죠.

     

    vue.config.js 파일에 아래와 같이 작성합니다. (참고 글 링크)

    저는 vue create로 시작해서인지 해당 파일이 기본으로 생성되지 않았습니다. 파일을 직접 만든 다음 yarn global add @vue/cli-init를 입력했더니 정상적으로 동작했습니다.

     

    module.exports = {
      css: {
        loaderOptions: {
          scss: {
            prependData: `
              @import "@/assets/style/_mixins.scss";
              @import "@/assets/style/_variables.scss";
            `
          }
        }
      }
    }

     

    이후 서버를 재시작하면 _mixins.scss와 _variables.scss를 아무데서나 자유롭게 사용할 수 있습니다.

     

    <style lang="scss">
    .header {
      color: $my-var;
    }
    </style>

     

     

    SourceMap 설정

    vue.config.js에 아래와 같이 추가하면 됩니다.

     

    module.exports = {
      css: {sourceMap: true},  

     

     

    스타일시트 작성

    스타일시트를 컴포넌트에 바로 작성하는 게 재밌었어요.

    리액트의 스타일드 컴포넌트와 비슷하지만 좀 더 기존의 CSS와 친숙한 방식이란 느낌이었습니다.

    다만 컴포넌트 내에 직접 쓰는 거다보니 협업 시에 충돌 위험은 없을지 궁금하네요 🧐

     

    <style lang="scss">
    .title {
      max-width: $max-width;
      margin: 0 auto;
      letter-spacing: 0.03rem;
      color: #fff;
    
      &__message {
        font-size: 1.6rem;
      }
    
      &__task {
        margin-top: 3.5rem;
        margin-bottom: 5rem;
        font-weight: bold;
    
        &-top {
          display: block;
          font-size: 2.6rem;
        }
    
        &-total {
          font-size: 5.4rem;
          line-height: 110%;
        }
      }
    }
    </style>

     

    스타일링은 SCSS 문법 + BEM 방법론 + REM 단위를 활용해 작업했습니다.

    덕분에 별도의 시안이 없더라도 손쉽게 반응형 작업을 진행할 수 있었어요.

     

    마크업 + 스타일링을 마친 화면입니다.

     

     

    드디어 퍼블리싱까지 끝났네요!

    본격적인 Vue 코드 작성은 다음 포스팅에 이어서 진행하겠습니다 ;)

     

     

    다음 글: Todo 웹앱 만들기(2)

     

    댓글 0