ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Nuxt.js] Nuxt 학습기 - (4) Store, asyncData & fetch
    Front-End(Web)/Vue 2021. 11. 30. 02:28
    반응형

    Nuxt 학습기의 4번째 시리즈는 이전에 정리하지 못한 Nuxt.js의 주요기능들이다.

    Nuxt.js 역시 Vuex Store를 사용할 수 있으며, SSR의 핵심이라 할 수 있는 서버에서의 최초 비동기 요청 메서드인 asyncData, fetch 등을 학습해보고자 한다.


    📗 Store (Vuex)

    Nuxt.js 역시 Vue와 같이 Vuex Store 전역 저장소를 패키지에서 기본적으로 제공한다.

    특정 규모 이상의 프로젝트는 보통 전역상태가 필요하며, 세션 정보나 복수의 페이지에서 필요하는 공통 데이터를 저장하는 용도로 사용된다.

     

    Vuex는 기본적으로 비활성화 상태이다. 그렇기에, 프로젝트 구조에는 /store 디렉토리가 존재하나 이는 빈 폴더로 존재하고 있다.

    여기에 index.js 파일을 생성하면 Vuex Store가 활성화되며, Nuxt.js의 Vuex는 2가지 모드(Classic, Module)가 존재한다.

     

    * Vuex 기능(프로퍼티) 의 의미? (리마인드)

    • state : 공통 변수(상태값). 복수의 컴포넌트에서 같은 값을 활용. this.$store.state['경로명/함수명'] 으로 사용 가능.
    • getters : Store state의 계산된 값.(computed 와 유사) 컴포넌트에서 this.$store.getters['경로명/함수명'] 으로 사용 가능.
    • mutations : state를 갱신하는 메서드(동기). 메서드는 ('state', '전달인자')를 매개변수로, 컴포넌트에서 this.$store.commit('메서드명', '전달인자') 형태로 사용 가능.
    • actions : mutations를 실행시키는 역할이며 그렇기에 비동기 통신에 사용된다. 컴포넌트에서 this.$store.dispatch('메서드명', '전달인자') 형태로 사용 가능.

     

    1. Classic Mode

    Classic Mode는 기본적으로 통용되는 Store 사용형태다. /store 디렉토리 내 index.js 파일을 생성하고, store 인스턴스를 export 하면 된다. (Nuxt.js에 Vuex가 기본적으로 설치되어 있어, 별도로 install 할 필요가 없음)

    전역상태의 성질이나 기능에 따라 이를 분리할 필요도 있으며, 이는 아래 'example'처럼 별도 파일에 분리 및 store 인스턴스에 전개 연산자로 포함시켜준다.

    import Vuex from 'vuex'
    import example from './example'
    
    const store = () => new Vuex.Store({
      state: {
        counter: 0
      },
      mutations: {
        increment (state) {
          state.counter++
        }
      },
      ...example,		// 별도 구성요소
    })
    
    export default store

    컴포넌트에서는, this.$store 메서드를 통해 Store 상태를 참조 및 접근할 수 있다.

    <template>
      <button @click="$store.commit('increment')">{{ $store.state.counter }}</button>
    </template>

    * Nuxt 3버전 부터는 Vuex의 Classic Mode를 지원하지 않는다고 한다!

     

    2. Module Mode

    Nuxt.js 프로젝트의 /store 디렉토리는 모든 파일들을 모듈로서 관리할 수 있도록 해준다.

    Module Mode를 사용하려면, store/index.js 에서 store 인스턴스가 아닌 state, mutations, actions를 각각 export 해준다.

    export const state = () => ({
      counter: 0
    })
    
    export const mutations = {
      increment (state) {
        state.counter++
      }
    }

    이제, /store에 파일을 추가하면 자동으로 모듈화되어 this.$store.[기능].[모듈명].[상태 or 메서드명] 으로 참조가 가능하다.

    index.js에 있던 state, mutations, actions는 store 루트로 접근하면 된다. (this.$store.[기능].[상태 or 메서드명])

    // [모듈추가] /store/todos.js
    
    export const state = () => ({
      list: []
    })
    
    export const mutations = {
      add (state, text) {
        state.list.push({
          text: text,
          done: false
        })
      },
      remove (state, { todo }) {
        state.list.splice(state.list.indexOf(todo), 1)
      },
      toggle (state, todo) {
        todo.done = !todo.done
      }
    }
    // [사용] /pages/todos.vue
    
    <template>
      <ul>
        <li v-for="todo in todos">
          <input type="checkbox" :checked="todo.done" @change="toggle(todo)">
          <span :class="{ done: todo.done }">{{ todo.text }}</span>
        </li>
        <li><input placeholder="What needs to be done?" @keyup.enter="addTodo"></li>
      </ul>
    </template>
    
    <script>
    // mapMutations - 전역 mutations 참조를 위한 Vuex 내부 메서드
    import { mapMutations } from 'vuex'
    
    export default {
      computed: {
        todos () { return this.$store.state.todos.list }		// 전역 state 참조
      },
      methods: {
        addTodo (e) {
          this.$store.commit('todos/add', e.target.value)
          e.target.value = ''
        },
        ...mapMutations({
          toggle: 'todos/toggle'					// 전역 mutations 참조
        })
      }
    }
    </script>
    
    <style>
    .done {
      text-decoration: line-through;
    }
    </style>

    📗 asyncData & fetch

     

    1. asyncData

    SSR 간에 컴포넌트를 최초 로드할 때 서버측에서 비동기로 데이터를 처리할 수 있도록, Nuxt.js 에서 기본적으로 제공하는 메서드다.

    asyncData() 는 페이지 컴포넌트에서만 사용 가능하며, 컴포넌트를 로드하기 전에 매번 호출된다.

     

    매개변수는 context 객체로, 여기에 담긴 데이터(route, params, store 등)를 활용해서 컴포넌트 데이터를 반환할 수 있다. (context)

    asyncData() 가 return 하는 객체는 컴포넌트의 data() 프로퍼티와 병합된다.

     

    * asyncData() 는 컴포넌트 초기화(Vue 인스턴스 형성) 전에 실행되므로, 메서드 내에서 this로 인스턴스에 접근할 수 없다.

     

    export default {
      data() {
        return {
          title: '',			// asyncData 반환값 병합
        }
      },
      async asyncData({ params }) {
        const { data } = await axios.get(`https://my-api/posts/${params.id}`);
        return { title: data.title };
      },
    };

    위처럼, 컴포넌트의 프로퍼티로 사용하면 되며, 반환값이 data()의 title 프로퍼티에 병합된다는 것을 알 수 있다.

     

    * Error Handling (에러 핸들링)

     

    context 객체 안에는 error(params) 라는 메서드가 있다. 이를 사용해서 에러 페이지를 노출할 수 있다.

    export default {
      asyncData ({ params, error }) {
        return axios.get(`https://my-api/posts/${params.id}`)
        .then((res) => {
          return { title: res.data.title }
        })
        .catch((e) => {
          error({ statusCode: 404, message: 'Post not found' })
        })
      }
    }

     

     

    2. fetch

    마찬가지로 SSR 에서 컴포넌트 렌더링 전에 비동기 데이터 처리를 위한 메서드다. fetch() 는 기본적으로 store 저장의 목적이 있다.

    fetch()는 모든 컴포넌트에서 사용가능하며, 컴포넌트가 로드되기 전에 마찬가지로 매번 호출된다.

     

    역시 context 객체를 매개변수로 받으며, 이를 통해 가공한 데이터를 Store에 commit 하면 된다.

    <template>
      <h1>Stars: {{ $store.state.stars }}</h1>
    </template>
    
    <script>
    export default {
      async fetch ({ store, params }) {
        let { data } = await axios.get('http://my-api/stars')
        store.commit('setStars', data)
      }
    }
    </script>

     

    - asyncData vs fetch 비교하기

    둘 다, Nuxt.js SSR 모드에서 서버에서 최초 비동기 데이터 요청을 위한 메서드인 것을 알 수 있었다.

    컴포넌트 단 혹은 전역(Store) 단에서 데이터를 병합시킨다는 큰 차이점과 더불어, 둘을 비교해보면 각각의 사용의의를 쉽게 이해할 수 있다!

    • asyncData는 페이지 컴포넌트에서만 사용가능 / fetch는 모든 컴포넌트에서 사용가능 (Nuxt 2.12~)
    • asyncData는 컴포넌트 형성전에 호출 / fetch는 컴포넌트가 형성된 뒤에 호출
    • asyncData는 페이지가 로드될 때마다 호출 / fetch는 컴포넌트 메서드처럼 활용가능($fetch)하며, activated hook을 통해 조건부로 호출가능.
    • asyncData는 this를 사용하지 못하기에, context를 인자로 받는다. / fetch는 this 접근이 가능하며, context에 params가 없다. (Nuxt 2.12~, fetch는 Vue 인스턴스 생성 후에 실행되므로 this 접근이 가능한 것이다.)
    • asyncData는 반환값이 data와 병합 / fetch는 this를 통해 data 변경이 가능
    • asyncData는 완료전까지 페이지가 렌더링되지 않으며 에러페이지로 전환될 수 있음 / fetch는 $fetchState로 pending, error 처리가능
    • asyncData는 캐싱을 지원하지 않음 / fetch는 캐싱을 지원하여 한 번 방문했던 곳에 저장이 가능

    즉, 페이지 단에서 미리 불러올 데이터 혹은 분량이 많지 않고 미리 데이터를 호출해야하는 경우 asyncData가,

    반복적으로 사용될 로직, this 접근이 필요한 경우, 유연한 에러 핸들링이 요구되는 경우 등에는 fetch를 사용하는 것이 더 유리해보인다. 

    * Nuxt 2.12 버전 이후로 fetch에 다소 변화가 일어났다. 공식문서를 한 번 짚고 넘어가면 좋을 것 같아 링크를 공유했다.

    (https://nuxtjs.org/announcements/understanding-how-fetch-works-in-nuxt-2-12/)


    이번 포스팅까지 진행하며, Nuxt.js 미니 프로젝트를 진행하며 사용해본 기능들을 어느정도 정리하게 된 것 같다.

    회사의 코드 리뉴얼과 겹치면서 기간이 길어지긴 했지만, Next.js와 Nuxt.js를 사용하며 나름대로 SSR에 대한 개념과 주요기능들을 학습해보고 정리하고자한 계획을 어떻게든 마무리지을 수 있었다.

     

    최근, Vue2를 주로 사용하면서 Typescript를 사용할 수 있는 기회가 많이 없었다.

    아마, 다음 포스팅과 그에 앞서서는 React를 오랜만에 하고싶기도 하고, 여기에 Typescript 및 새로운 상태관리 라이브러리(아마 Recoil?) 을 사용한 미니 프로젝트를 진행한 뒤 이에 대한 학습을 이어갈 생각이다.

     

    [참고]

    - [Nuxt.js 미들웨어] Nuxt.js 가이드북 : https://develop365.gitlab.io/nuxtjs-0.10.7-doc/ko/guide/routing/#middleware  

    - [Nuxt.js 전체] doozi0316 님의 블로그 : https://doozi0316.tistory.com/entry/Nuxtjs-%EC%9D%98-%EA%B0%9C%EB%85%90%EA%B3%BC-%EC%98%88%EC%A0%9C-SSR-CSR-Universal  

    - [asyncData, fetch] bongjoki 님의 블로그 : https://velog.io/@bongjoki/Nuxt.js-asyncData  

    - [asyncData vs fetch 차이점] mclearning 블로그 : https://www.mclearning.dev/vuejs/2021/09/nuxt-asyncData-vs-fetch

    반응형
Designed by Tistory.