Front-End

[Vue.js] 배열과 오브젝트의 데이터 변경 후 화면 갱신이 안될 때 해결방법(v-if, v-show가 동작하지 않을 때)

데이터를 배열또는 오브젝트 타입으로 선언 후 더보기 버튼( 한번 더 누르면 닫기 버튼)을 누를 때 마다 서버로 부터 데이터를 받아서 처리하도록 기능 추가를 하고 있었다. 서버로 부터 데이터를 정상적으로 가져왔으나 화면의 변화는 없는 문제가 발생되었다. v-if, v-show가 동작하지 않는 것이다.

 

데이터가 변경되면 Vue는 해당 데이터가 변경된 것을 감지하여 화면을 다시 렌더링 하는 것으로 알고 있었다. 그럼에도 불구하고 배열과 오브젝트의 데이터는 변경되어도 아무런 변화가 없었다. 해결책을 찾기 위해 3시간의 삽질을 시도하였고, 아무래도 코드상의 문제는 아닌 것 같아 구글링을 시작했다. 개발 일정상 딜레이 발생하면 낭패이기 때문이다. 해결책을 찾지 못하면 몇일이고 날라갈 것만 같은 느낌을 받았다.

 

현재 구현된 코드는 동적으로 서버로 부터 데이터를 가져와서 추가 정보를 보여주는 구조로 되어있다.

서버로 부터 한 번에 모든 데이터를 받은 후 필터링처리를 하였으나 서버로 부터 필요한 데이터를 받아오는 것 보다 시간이 많이 걸려서 필터링 처리는 안하기로 하였다.

 

[HTML]

<p class="sub-tit">제품 정보</p><p class="test reduced"></p>
<div v-show="item.IS_OPEN">
  <v-expansion-panels multiple>
	<v-expansion-panel
	  v-for="(itemName, idx) in productHeaders[item.KEY_ID]"
	  :key="`addProduct-${idx}${itemName}`">
	  <v-expansion-panel-header>
		<v-row no-gutters>
		  <v-chip x-small
			outlined
			color="red" class="mr-2">제품</v-chip>{{ itemName }}
		</v-row>
	  </v-expansion-panel-header>
	  <v-expansion-panel-content>
	  <v-row v-for="(el, i) in productList[item.KEY_ID]" :key="i">
		<v-row  v-if="el.PRODUCT_NAME === itemName">
		  <v-row no-gutters>
			<v-col >
			  <label class="ubs-label">{{ el.ATTR_NAME }}</label>
			</v-col>
			<v-col>
			  <v-text-field
				hide-details
				solo
				outlined
				flat
				dense
				class="ubs-textfield"
				v-model="el.TEXT_INFO"
				readonly
				disabled
				label=""></v-text-field>
			</v-col>
			</v-row>
		  </v-row>
		</v-row>
	  </v-expansion-panel-content>
	</v-expansion-panel>
  </v-expansion-panels>
</div>
<v-btn
	block
	text
	color="#999999" @click="clickToggle(item)">
	<v-icon v-if="!item.IS_OPEN" dark right>mdi-chevron-down</v-icon><!--아래 방향 꺽세-->
	<v-icon v-if="item.IS_OPEN" dark right>mdi-chevron-up</v-icon>
</v-btn>

[JS]

<script>
import moment from 'moment';

export default {
  name: 'PageHist',
  props: {
    testId: { type: String },
    companyId: { type: String },
  },
  data: () => ({
    mainProductList: [],
    productHeaders: {},
    productList: {},
    ctxMenu: [
      { id: 'menu1', name: '제품' },
      { id: 'menu2', name: '상품' },
    ],
    data: {
      startDate: moment(new Date()).format('YYYY-MM-DD'),
      endDate: '',
      actionId: '',
    },
  }),
  created() {
  },
  mounted() {
   this.inqueryMainProductList();
  },
  watch: {
    // isDetailView: {
    //   handler() {
    //     console.log(`## watch isDetailView = ${JSON.stringify(this.isDetailView)}`);
    //   },
    // },
    callHistList: {
      deep: true,
      // immediate: true,
      handler() {
        // // init: 모든 꺽세 UI 닫힘 처리
        // for (let i = 0; i < this.mainProductList.length; i += 1) {
        //   const el = this.mainProductList[i];
        //   el.IS_OPEN = false;
        // }
        console.log('## watch mainProductList ####');
      },
    },
  },
  computed: {
  },
  methods: {
	newToggleTxt(clsRow) { 
	  // this.isDetailView = !this.isDetailView;
	  const row = clsRow;
	  // init: 모든 꺽세 UI 닫힘 처리
	  // for (let i = 0; i < this.mainProductList.length; i += 1) {
	  //   const el = this.mainProductList[i];
	  //   if (clsRow.KEY_ID !== el.KEY_ID) {
	  //     // console.log(`clsRow.KEY_ID= ${clsRow.KEY_ID}  el.KEY_ID=${el.KEY_ID}`);
	  //     // 클릭했던 이력에 대한 닫힘 버튼 기능 살리기 위해 예외처리
	  //     el.IS_OPEN = false;
	  //   }
	  // }
	  row.IS_OPEN = !row.IS_OPEN;
	  if (row.IS_OPEN) {
		// this.clickToggle(row.KEY_ID, row.TEST_ID, row.COMPANY_ID);
		// this.isDetailView = true;
		if (this.productList[row.KEY_ID] === undefined) {
			this.clickToggle(row.KEY_ID);
		  // this.$nextTick(() => {
		  // });
		}
	  } else {
	   // this.isDetailView = false;
	  }
	},  
 
	async clickToggle(pActionID) { 
	  const param = {
		ac: 'TEST01',
	  };
	  const rs = await this.axiosCall(param);
	  const clsRow = rs.data.resultList;   
	  const productList = clsRow[2];

	  this.productList[pActionID] = productList;
	  console.log(`### productList => ${JSON.stringify(this.productList[pActionID])}`);
	  console.log(`### productList[pActionID].length => ${JSON.stringify(this.productList[pActionID].length)}`);


	  // v-expansion-panel-header용 제품 정보
	  let oldProductName = '';
	  const headerName = [];
	  for (let i = 0; i < this.productList[pActionID].length; i += 1) {
		const el = this.productList[pActionID][i];
		if (oldProductName !== el.PRODUCT_NAME) {
		  oldProductName = el.PRODUCT_NAME;
		  headerName.push(oldProductName);
		}
	  }
	  this.productHeaders[pActionID] = headerName;
	  console.log(`############## productHeaders => ${JSON.stringify(this.productHeaders[pActionID])}`);
	 
	},	
  },
};
</script>

제품 리스트에 대한 동적 처리임으로 배열로 처리할 수는 없기에 객체로 선언하여 처리하였다.

productList: {}

productHeaders: {}

 

객체와 배열의 데이터 타입은 다름으로 주의해야한다. 객체로 선언 후 데이터를 추가하는 방법은

this.productList[pActionId] = tmpList; 형식으로 하면 되고, 

객체 데이터에 접근할 때도 this.productList[pActionId]로 접근하면 된다.

 

pActionId = "A123"
const tmpList = rs.resultlist[2];

productList = []
this.productList.push([pActionId, tmpList]);
[["A123",[{"KEY_ID":"A123","ITEM_ID":"S11","PRODUCT_NAME":"약품","ATTR_NAME":"진행상태","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"진행상태","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"성공사유","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"실패사유","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"기타 코멘트","REMARK":"","TEXT_INFO":""}]]]

/배열로 선언해서 데이터를 추가했을 경우
this.productList.push(pActionId, tmpList);
["A123",[{"KEY_ID":"A123","ITEM_ID":"S11","PRODUCT_NAME":"약품","ATTR_NAME":"진행상태","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"진행상태","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"성공사유","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"실패사유","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"기타 코멘트","REMARK":"","TEXT_INFO":""}]]

//객체로 선언해서 데이터를 추가한 경우 
productList: {},

this.productList[pActionId] = tmpList;
{"A123":[{"KEY_ID":"A123","ITEM_ID":"S11","PRODUCT_NAME":"약품","ATTR_NAME":"진행상태","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"진행상태","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"성공사유","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"실패사유","REMARK":"","TEXT_INFO":""},{"KEY_ID":"A123","ITEM_ID":"S12","PRODUCT_NAME":"약품2","ATTR_NAME":"기타 코멘트","REMARK":"","TEXT_INFO":""}]}

 

해결책은 매우 간단했다. Vue는 배열의의 다음과 같은 메소드를 호출하여 처리하는 경우 변화를 감지하고 화면을 갱신한다. 

push(), pop(), shift(), unshift(), splice(), sort(), reverse()

하지만 배열의 특정값을 변경하거나 위와 같이 객체 타입의 경우 값이 변경되어도 화면을 갱신할 수 없는 문제가 발생한다.

 

배열과 오브젝트의 경우 Vue.set (this.$set) 또는 Vue.delete (this.$delete) 메소드를 사용하여 이러한 문제에 대한 처리를 할 수 있다. 그럼 정상적으로 화면이 갱신 된다. 

 

가장 쉬운 방법은 this.$forceUpdate(); 메소드를 호출하여 화면을 강제로 갱신하는 방법이 있다. 

우선 급한 마음에 후자를 선택하여 처리하였다. 

async clickToggle(pActionID) { 
  .......생략...........

  this.$forceUpdate();
},

기록해야 살아남는다. 나중에 까먹어도 다시 찾아 볼 수 있으니 기록하자!!

 

 

[REFERENCE]

  • jh-7.tistory.com/19

[관련자료]

 

 

Leave a Reply

error: Content is protected !!