Vue 개발공부

김동영·2023년 8월 1일
0
post-thumbnail

뷰 공부 도중 알게 된 사실이나 모호한 부분에 대한 정리를 위한 포스트입니다.

1. computed 변경 감지

computed 의 경우 화면에서 시작된 기능이 아닌 경우 변경이 감지되지 않는 특이사항을 발견함.

0. 버전정보

vue - 2.7.10
nuxt - 2.15.8
vuetify - 2.6.10

1. 테스트1 - computed only

  1. 테스트 코드
// store/index.js
export default {
  state() {
    return {
      testState: '',
    };
  },
  mutations: {
    setTestState(state, payload) {
      state.testState = payload.testState;
    },
  },
  actions: {
    testStateAction(context, payload) {
      context.commit('setTestState', payload);
    },
  },
};

  // page.vue 내 computed 영역
  computed: {
    isTestState() {
      console.log('computed isTestState time: ' + new Date());
      return !!this.$store.state.testState;
    },
  },    
  1. 화면 초기 computed 및 관련 데이터 확인
  2. computed 관련 값 변경 및 확인
  3. 화면 스크롤 시 시 변경이 감지됨.
    참고) 화면 스크롤 기능은 computed 기능과는 전혀 관련이 없습니다.

2. 테스트2 - watch only

이와 비슷하지만 캐싱없이 항상 호출되는 watch 는 어떻게 동작하는지 확인함.
0. 테스트 코드

// store/index.js
export default {
  state() {
    return {
      testState: '',
    };
  },
  mutations: {
    setTestState(state, payload) {
      state.testState = payload.testState;
    },
  },
  actions: {
    testStateAction(context, payload) {
      context.commit('setTestState', payload);
    },
  },
};

  // page.vue 내 data 영역
  data() {
    return {
      testStateWatch: false,
    };
  },
  
  // page.vue 내 watch 영역
  watch: {
    '$store.state.testState'(value) {
      this.testStateWatch = !!value;
      console.log(`testState value: ${value}, watch time:  + ${new Date()}`);
    },
  },
  1. 화면 초기 watch 관련 값 확인
    watch 관련 값 false 확인, watch 는 최초 화면 진입이라 동작하지 않음.

  2. watch 관련값 변경 및 확인
    관련 값 변경 후 바로 호출되어 timeMillis 가 거의 일치함.
    watch 관련 값 변경 -> watch 호출 -> 변경 완료 후 callback 동작

3. 테스트3 - computed with watch

watch 의 경우 값이 변경되면 바로 호출됨을 확인함.
그렇다면 watch 와 computed 를 같이 사용할 경우 어떨지 확인함.
0. 테스트 코드

// store/index.js
export default {
  state() {
    return {
      testState: '',
    };
  },
  mutations: {
    setTestState(state, payload) {
      state.testState = payload.testState;
    },
  },
  actions: {
    testStateAction(context, payload) {
      context.commit('setTestState', payload);
    },
  },
};
  
  // page.vue 내 computed 영역
  computed: {
    isTestState() {
      console.log('computed isTestState time: ' + new Date());
      return !!this.$store.state.testState;
    },
    
  // page.vue 내 watch 영역
  watch: {
    '$store.state.testState'(value) {
      // watch 호출확인을 위해 로그만 기록함.
      console.log(`testState value: ${value}, watch time:  + ${new Date()}`);
    },
  },
  1. 화면 초기 watch, computed 관련 값 확인
    computed 호출 확인, computed 초기값 false 확인

  2. watch, computed 관련 값 변경 및 확인
    testState 변경 -> watch 호출 -> state 변경 콜백 -> computed 호출 확인
    watch 감지 후 약간의 딜레이 후 computed 가 호출된다.(밀리초 이내)

4. 1차 분석

  • 마우스 클릭 등 javascript 내장 이벤트로는 변경되지 않음을 확인.
  • vue 이벤트 영역 확인 결과, 스크롤 시 v-app-bar 엘리먼트(vuetify 컴포넌트)에서 update 이벤트를 발생시키는 것을 확인함.
  • 위 경우로 보아 vue 관련 행위가 발생해야 변경된 값을 감지하는 듯 하다.

5. 테스트4 - Vue cycle 내 변경

그렇다면 vue 의 lifecycle 내에서 computed 관련 값을 변경하는 경우에는 감지하는지 확인
테스트는 methods 영역, watch 영역에서 각각 변경하는 케이스로 확인함.
0. 테스트 코드

// store/index.js
export default {
  state() {
    return {
      testState: '',
    };
  },
  mutations: {
    setTestState(state, payload) {
      state.testState = payload.testState;
    },
  },
  actions: {
    testStateAction(context, payload) {
      context.commit('setTestState', payload);
    },
  },
};
  

  // page.vue 내 method 호출 버튼, watch 감지 스위치
  <v-btn style="margin-right: 10px" depressed @click="changeTestState"
          >testState 변경</v-btn
        >
  <v-btn style="margin-right: 10px" depressed @click="changeTestState"
          >testState 변경</v-btn
        >
  
  // page.vue 내 computed 영역
  computed: {
    isTestState() {
      console.log('computed isTestState time: ' + new Date());
      return !!this.$store.state.testState;
    },
  },
  
  // page.vue 내 data 영역
  data() {
    return {
      watchTestState: false,
    };
  },
    
  // page.vue 내 watch 영역
  watch: {
    watchTestState() {
      const testState = this.$store.state.testState === '' ? 'abc' : '';
      const payload = { testState };
      this.$store.dispatch('testStateAction', payload);
      console.log(`watchTestState time: ${new Date()}`);
    },
  }
  
  // page.vue 내 methods 영역 changeTestState 메소드
  methods:{
    changeTestState() {
      const testState = this.$store.state.testState === '' ? 'abc' : '';
      const payload = { testState };
      this.$store.dispatch('testStateAction', payload);
      console.log(`changeTestState time: ${new Date()}`);
    },
  }
  1. 화면 초기 computed 관련 값 확인

  2. watch 영역에서 관련 값을 변경하는 경우

  • watch 로 값 변경 후 computed 정상 호출됨을 확인

  1. methods 영역의 메서드 내에서 관련 값을 변경하는 경우
  • 값이 변경되었음에도 computed 감지되지 않음
  • 변경 후 화면스크롤로 vue 이벤트 다시 발생할 경우 변경감지됨.
  • 동일한 버튼으로는 computed 값이 변경되지 않음

6. 2차 분석

  • methods 영역에서 변경 시에는 감지되지 않음.
  • 변경된 값에 대해 스크롤, watch 이벤트 등을 통해야만 감지된다.
  • watch 로 변경감지하는 경우, watch -> computed 순으로 변경되기 때문에 싱크가 발생한다.
    => 이로 인해 computed 변경 전 값이 사용된다면 잘못된 동작을 일으킬 수 있다.

7. 결론

  • watch 에 비해 변화에 빠르게 대처하기 힘들다.
  • 변화가 적은 값을 캐싱하여 사용할 때 고려해보자.

8. 보완할점

store 영역의 데이터를 기준으로 테스트했기에 보편적인 data 영역의 변수를 기준으로 테스트도 해봐야되지 않을까 싶다.

2. SockJs disconnect 이슈

0. 이슈 환경

websocket 1.0.34
webstomp-client 1.2.6

1. 이슈

disconnect 시, setTimeout 0 초로 내부적으로 값을 한번 더 변경한다.

이로 인해 store 영역에서 사용 시, disconnect 된 stompClient 값이 state 에 할당된 이후 한번 더 값의 변경이 발생하여 아래 오류가 발생한다.

2. 해결방안

이를 해결하기 위해 state 에 할당하는 시간을 setTimeout 0 으로 값의 변경 이후 동작하도록 수정하여 문제를 해결함

    // store.js 의 actions 영역
    disconnect({ state, commit }) {
      if (!state.stompClient?.connected) {
        const e = new Error('소켓이 연결되지 않았습니다.');
        throw e;
      }
      const stompClient = _.cloneDeep(state.stompClient);
      stompClient.disconnect(() =>
        setTimeout(() => commit('setStompClient', stompClient), 0),
      );
    },

3. 결론

setTimeout 인 점을 확인하고 문제를 해결했으나, 기존에 알던 setTimeout(callback, timeMillis) 와 문법이 달라서 다소 헷갈렸다.
또한 setTimeout 0초이지만 이는 이벤트루프를 통해 동작하므로 비동기로 처리되게 된다는 점도 새롭게 배울 수 있었다.

23.08.08 stompClient.send() 실패 이슈

이후 생성된 소켓클라이언트로 메시지 통신해본 결과
SockJs 를 래핑한 WebStomp 내부함수 send() 에서 내부 변수를 조작하는 것을 확인되었다.

  • 이를 해결하기 위해 메시지 전송마다 deepcopy 로 state 영역의 webstomp 객체를 클론할 경우 성능이 저하되는 문제가 있다.
  • 또한 기존 기능을 래핑하는 케이스가 늘어나기에 실제 동작까지 depth 가 깊어져 유지보수 효율성이 떨어질 수 있다.
위 2가지 문제점을 고려하여 store 영역에서 stompclient 를 관리하는 방안은 폐기하는 것이 맞을듯 하다.

3. Composition API (Vue3)

  • 컴포넌트의 로직을 유연하게 구성할 수 있도록 하는 함수 기반의 API
  • 코드의 재사용성과 타입 추론(타입스크립트)이 개선되었다고 함.
  • 기존 Vue2 의 옵션 api 처럼 영역을 나누어 설정하던 방식에서 기능 단위로 한데모아 관리함으로서 코드 가독성을 늘리고 유지보수 효율성을 올릴 수 있다.
    // 기존 Option API 방식
    // -> 테이블에 데이터를 넣고 정제하는 로직이 하나의 기능임에도 분리되어있어 관리포인트가 늘어남.
    export default {
      data() {
        return {
          table: [],
        };
      },
      methods: {
        addRow(row) {
          this.table.push(row);
        },
      },
      computed: {
        filteredTable() {
          return this.table.filter(row => !!row);
        },
      }
    }
    // Vue3 기준 Composition API -> 영역 구분없이 한데모아 관리가 가능함.
    <script setup>
      const table = ref([]);
      const addRow = row => table.value.push(row);
      const filteredTable = computed(() => table.value.filter(row => !row));
    </script>
profile
프레임워크와 함께하는 백엔드 개발자입니다.

2개의 댓글

comment-user-thumbnail
2023년 8월 1일

잘 봤습니다. 좋은 글 감사합니다.

1개의 답글