VueJS

[VueJS] watch를 활용한 컴포넌트 재활용해보기

SongMinu 2021. 5. 19. 16:53
728x90

사용자 등록을 하는 컴포넌트를 하나 만든 후 수정을 할 때 어떻게 하면 만든 컴포넌트를 재사용할 수 있을까 고민하다 만들어본 소스입니다.

디자인 프레임워크인 VuetifyJS를 사용해서 만들었습니다.


 

먼저 구성 화면

위 화면은 사용자 리스트를 그려주는 listTable.vue파일이고 사용자 등록 버튼을 누르거나 리스트에 있는 펜 모양 아이콘을 누르면

사용자 정보 컴포넌트인 editUser.vue 파일이 열린다.

 

listTable.vue 파일 소스

<template>
  <v-card>
    <v-card-title>
      <v-spacer></v-spacer>
      <edit-user :user_info="user_info_data" @set_info="set_user_info"/>
    </v-card-title>
    <v-card-title>
      <v-spacer></v-spacer>
      <v-spacer></v-spacer>
      <v-spacer></v-spacer>
      <v-text-field
        v-model="search"
        append-icon="mdi-magnify"
        label="Search"
        single-line
      ></v-text-field>
    </v-card-title>
    <v-card-text>
      <v-data-table
        :headers="headers"
        :items="listData"
        :page.sync="page"
        :items-per-page="itemsPerPage"
        hide-default-footer
        class="elevation-1"
        @page-count="pageCount = $event"
        :search="search"
        height="500"
        :loading="loading"
        loading-text="데이터를 불러오는 중입니다."
        no-data-text="데이터가 없습니다."
        >
        <template v-slot:[`item.actions`]="{ item }">
          <v-icon
            small
            class="mr-2"
            @click="editItem(item)"
          >
            mdi-pencil
          </v-icon>
          <v-icon
            small
            @click="deleteItem(item)"
          >
            mdi-delete
          </v-icon>
        </template>
      </v-data-table>
      <div class="text-center pt-2">
        <v-pagination v-model="page" :length="pageCount"></v-pagination>
      </div>
    </v-card-text>
  </v-card>
</template>

<script>
import editUser from '~/components/setting/user/editUser'

export default {
  props:["list"],
  data() {
    return {
      user_info: null,
      search:'',
      page: 1,
      pageCount: 0,
      itemsPerPage: 10,
      headers: [ //props로 받아서 처리하게끔 변경해보기
        { text: '아이디', value: 'user_id' },
        { text: '사용자명', value: 'user_nm' },
        { text: '권한', value: 'user_auth_nm' },
        { text: '설명', value: 'user_desc' },
        { text: '-', value: 'actions', sortalbe: false}
      ]
    }
  },
  computed: {
    listData() { //리스트 불러오기
      return this.list;
    },
    user_info_data() { //상세정보
      return this.user_info;
    },
    loading() {
      return this.$store.getters['GET_LOADING_1'];
    }
  },
  methods: {
    editItem(data) {
      this.user_info = data;
    },
    async deleteItem(data) {
      if (confirm(`${data.user_id} 계정을 삭제하시겠습니까?`)) {
        const rs = await this.$store.dispatch('setting/user/deleteUser', data._id);
        if (rs.data.result.error == false) {
          this.$store.dispatch('setting/user/initUserList');
        } else {
          alert(rs.data.msg)
        }
      }
    },
    set_user_info(data) {
    	this.user_info = data;
    }
  },
  components: { editUser }
}
</script>

<style>

</style>

봐 둘 부분은 수정 버튼을 누르면 실행하는 editItem(data) 부분과 editUser.vue 컴포넌트에 넘길 속성으로 만든 user_info 부분,

editUser.vue 컴포넌트로부터 데이터를 전달 받을 @set_info="set_user_info" 이렇게 2개다.

수정 버튼을 누르면 user_info값이 null이 었다가 사용자 정보를 넣게 되고,

computed에서 user_info 값이 변경된 값을 user_info_data가 가지게 되는데

editUser.vue 컴포넌트의 속성인 user_info에 그 값을 넘겨준다.

 

@set_info="set_user_info" 이걸 사용하는 이유는 watch로 데이터 변화를 감지해서 다이얼로그를 열게 해놨더니

한번 수정 눌렀던 데이터를 닫고 다시 똑같은 데이터를 누르면 watch로 감시하고있는 user_info 값이 변화가 없어서 다이얼로그가 열리지 않는 현상이 있어서 해결 방안으로 추가했다.

 

그리고 editUser.vue 컴포넌트 소스

<template>
  <v-row align="start" justify="end">
    <v-dialog v-model="dialog" persistent max-width="600px">
      <template v-slot:activator="{ on, attrs }">
        <v-btn
          raised
          color="primary"
          v-bind="attrs"
          v-on="on"
        >
          사용자 등록
        </v-btn>
      </template>
      <v-card raised outlined class="pa-3" :loading="loading"> 
        <v-card-title>
          <span class="headline">사용자 등록</span>
        </v-card-title>
        <v-card-text>
          <v-form ref="form" lazy-validation>
            <v-row>
              <v-col cols="12">
                <v-text-field v-model="user_id" label="아이디*" :rules="user_id_rule" :disabled="state == 'ins' ? false : true" required></v-text-field>
                <!-- <v-text-field :value="user_id" @change="v => user_id = v" label="아이디*" :rules="user_id_rule" :disabled="state == 'ins' ? false : true" required></v-text-field> -->
              </v-col>
              <v-col cols="12">
                <v-text-field v-model="user_nm" label="이름*" :rules="user_nm_rule" required></v-text-field>
              </v-col>
              <v-col cols="12">
                <v-text-field v-model="user_pw" label="비밀번호*" type="password" :rules="user_pw_rule"></v-text-field>
              </v-col>
              <v-col cols="12">
                <v-text-field v-model="user_pw_chk" label="비밀번호 확인*" type="password" :rules="user_pw_rule2"></v-text-field>
              </v-col>
              <v-col cols="12">
                <v-select
                v-model="user_auth"
                label="권한*"
                :items="authList"
                item-text="name"
                item-value="value"
                return-object
                :rules="user_auth_rule"
                >
                </v-select>
              </v-col>
              <v-col cols="12">
                <v-text-field v-model="user_desc" label="설명" :rules="user_desc_rule"></v-text-field>
              </v-col>
            </v-row>
          </v-form>
          <small class="red--text">*표시는 반드시 입력해야하는 항목입니다.</small>
        </v-card-text>
        <v-card-actions>
          <v-spacer></v-spacer>
          <v-btn class="ma-2" raised depressed color="primary" @click="save">
            <v-icon left>mdi-check</v-icon> 저장
          </v-btn>
          <v-btn class="ma-2" raised depressed @click="close">
            <v-icon left>mdi-close</v-icon> 닫기
          </v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </v-row>
</template>

<script>
export default {
  props: [ 'user_info' ],
  data() {
    return {
      dialog: false,
      state: 'ins',
      authList: [
        { name: '관리자', value: 'A'},
        { name: '일반 사용자', value: 'M'}
      ],
      user_id: '',
      user_id_rule: [
        v => !!v || '아이디는 필수 입력사항입니다.',
        v => /^[a-zA-Z0-9]*$/.test(v) || '아이디는 영문+숫자만 입력 가능합니다.',
        v => !( v && v.length >= 15) || '아이디는 15자 이상 입력할 수 없습니다.',
        v => this.state === 'ins' ? this.checkDuplicate(v) : true
      ],
      user_nm: '',
      user_nm_rule: [
        v => !!v || '이름은 필수 입력사항입니다.',
        v => !(v && v.length >= 30) || '이름은 30자 이상 입력할 수 없습니다.',
        v => !/[~!@#$%^&*()_+|<>?:{}]/.test(v) || '이름에는 특수문자를 사용할 수 없습니다.'
      ],
      user_pw: '',
      user_pw_chk: '',
      user_pw_rule: [
        v => this.state === 'ins' ? !!v || '패스워드는 필수 입력사항입니다.' : true,
        v => !(v && v.length >= 30) || '패스워드는 30자 이상 입력할 수 없습니다.',
      ],
      user_pw_rule2: [
        v => this.state === 'ins' ? !!v || '패스워드는 필수 입력사항입니다.' : true,
        v => !(v && v.length >= 30) || '패스워드는 30자 이상 입력할 수 없습니다.',
        v => v === this.user_pw || '패스워드가 일치하지 않습니다.'
      ],
      user_auth: '',
      user_auth_rule: [
        v => !!v || '권한은 필수 선택 사항입니다.'
      ],
      user_desc: '',
      user_desc_rule: [
        v => !(v && v.length >= 100) || '설명은 100자 이상 입력할 수 업습니다.'
      ]
    }
  },
  watch: {
    user_info() { 
      if(this.user_info !== null) {
        //listTable 컴포넌트에서 user_info 데이터를 넘기면 수정화면으로 판단 시키고 text field에 데이터를 넣어줌
        const user = this.user_info;
        this.state = 'upd';
        this.user_id = user.user_id;
        this.user_nm = user.user_nm;
        this.user_auth = user.user_auth_code;
        this.user_desc = user.user_desc;
        this.dialog = true;
      }
    }
  },
  computed: {
    loading() {
      return this.$store.getters['GET_LOADING_2'];
    }
  },
  methods: {
    async save() {
      const validate = this.$refs.form.validate();
      if (validate) {
        if (confirm ('저장하시겠습니까?')) {
          const params = {
            user_id: this.user_id,
            user_nm: this.user_nm,
            user_pw: this.user_pw,
            user_auth_code: this.user_auth.value,
            user_auth_nm: this.user_auth.name,
            user_desc: this.user_desc
          }
          if (this.state == 'upd') {
            params._id = this.user_info._id;
            params.user_mk_dt = this.user_info.user_mk_dt;
  
          }
          try {
            this.$nuxt.$loading.start();
            const url = (this.state == 'ins' ? 'setting/user/insertUser' : 'setting/user/updateUser');
            const rs = await this.$store.dispatch(url, params);
            if (rs.data.result.error == false) {
                this.$nuxt.$loading.finish();
                this.$store.dispatch('setting/user/initUserList');
                this.close();
            }
          } catch (err) {
            alert(err);
          }
        }
      }
    },
    //id 중복체크
     checkDuplicate(user_id) {     
      const user_data = this.$store.getters['setting/user/GET_USER_LIST'];
        for(var i in user_data) {
          var user_idcheck = user_data[i].user_id;
        if(user_id == user_idcheck){
          return '이미 사용중인 아이디입니다.';
        }
      }
      return true
    },
    close() {
      this.dialog = false;
      this.state = 'ins';
      this.$refs.form.reset();
      this.$emit('set_info', null);
    }
  }
}
</script>

<style>

</style>

close 부분에 $emit('set_info', null); 이걸 통해 null을 listTable.vue로 전달을 하고 watch는 user_info가 null 아닐 때만 다이얼로그를 열기 때문에 반응하지 않는다.

 

사용자 등록 버튼을 누르면 dialog 값이 true가 되면서 등록 화면이 출력된다.

사용자 등록 버튼을 눌렀을 때 editUser.vue

그리고 아까 listTable.vue에서 수정 버튼을 누르면 컴포넌트로 데이터를 넘긴다고 했는데.

이 컴포넌트가 받는 속성으로 props에 user_info 여기로 받게 해 놨다.

watch를 이용해서 user_info를 감시하게 해 놔서 수정 버튼을 눌러서 데이터를 받으면

data() 안에 사용자 데이터에 받은 데이터를 넣어주고 dialog를 true로 바꿔서 컴포넌트를 띄운다.

 

수정 버튼(펜 모양)을 눌렀을 때 editUser.vue

이런 식으로 컴포넌트를 재활용해봤고, 더 좋은 방법이 있다면 조언 부탁드립니다.

 

2021-05-28 추가
listTable.vue에서 editUser.vue 컴포넌트 속성으로 @set_info="set_user_info" 라는걸 추가했는데
이게 동일한 데이터를 수정 누르고 닫기 누르고 다시 수정을 누르면 :user_info="user_info_data"로 넘기는 데이터가 똑같기 때문에 editUser.vue에서 watch가 감시중인 user_info의 데이터가 변하지 않아 다이얼로그가 열리지 않는 현상이 발견 됐다.
방법을 찾아보다 $emit을 이용하여 처리하긴 했는데
재사용하기엔 상당히 불필요한 과정이 아닌가 하는 생각이 든다.
나중에 좀더 다른 방법을 찾아봐야할 것 같다.

 

 

반응형