[회고] 내가 그 날 toRefs를 사용한 이유를 찾아서.

0
post-thumbnail

🖥 문제의 코드

const { expiredAt } = toRefs(reactive({ expiredAt: ref(null) }));
const { clientId } = toRefs(reactive({ clientId: ref(null) }));

📌 사건의 경위

💡 흔적을 발견했다.

과거의 나. 무슨 생각을 했을까?(생각을 했을까?라는 질문이 더 적절할지도?)

  1. api로 받아온 값 계속 바뀌는 값이라고 생각했다.
  2. 받아온 데이터 중 '만료일' 값은 객체로 이루어진 데이터 중 1개 라고 생각했다.

일단. vue의 반응성에 대해서 논의하고자 함

⭐ 반응성(Reactivity)이란?

  • 컴포넌트의 상태(state)가 바뀌면 자동으로 컴포넌트의 DOM을 변경하는 것

자동으로 DOM을 변경하는게 어떻게 가능할까?

전제조건: 자바스크립트의 프록시와 getter, setter
cf) Proxy란? 다른 객체에 대해 수행되는 기본 작업을 수정하고 사용자 정의할 수 있는 객체 생성을 허용하는 기능
Proxy란? Proxy의 역할을 직역하자면 '대리자'라고 할 수 있는데, 이때 한정적인 무언가만을 대리하는 것이 아닌 그냥 무엇이든 대리할 수 있는 역할을 하는 말그대로 대리자임

  • 자바스크립트에서 속성 접근을 가로채는 방법 getter/setter 그리고 Proxy(그러니깐 다른 오브젝트를 감싸 연산을 가로챔)
  • get은 프로퍼티 접근 로그를 남김
  • set은 추가하여 해당 연산이 일어날 때마다 개발자가 입력한 추가 작업이 수행되도록 하는 방식
  • 특히 vue3에서 프록시는 반응형 객체에 사용됨
  • Proxy를 통해서 Object의 get()/set()메소드를 재정의
  • 원하는 동작으로!

반응형 상태의 객체를 Proxy 객체로 만들어서 값이 바뀌는 동작이 발생하는 경우에는 set()이 호출, set()은 DOM을 업데이트하게 함

  • Vue의 인스턴스에 자바스크립트 객체를 data 옵션으로 전달
  • getter/setter로 변환되어 존재
  • getter/setter는 보이는 것은 아니지만 속성에 접근하거나 수정할 때 종속성 추적 및 변경 알림을 수행할 수 있음
  • 모든 컴포넌트 인스턴스에는 watcher 인스턴스(컴포넌트가 종속적으로 렌더링 되는 동안, 수정된 모든 속성을 기록)가 존재
  • 추후에 setter가 트리거되면 watcher에 알리고 컴포넌트가 리렌더 됨

정.리.하.자.면

Proxy를 통해 대상 객체의 set함수를 Vue에서 만든 반응형 handler로 교체해주는 작업

  • 대상 객체를 직접 조작X -> Proxy를 통해 상태를 관리하도록 함
  • Proxy 객체의 값을 변화 발생 -> set handler에서 DOM을 업데이트
  • 그러니깐 vue에서는 proxy를 통해 반응성을 확보하는 것! '값이 바뀔 때마다 watcher에게 값이 바꼈다고 알려줘!'

대표적으로 ref와 reactive

1) ref

  • ref를 호출하면? .value속성을 가지는 객체를 리턴
  • 이 과정을 자동으로 동작하게 해주는 역할
  • 해당 함수의 인자로는 모든 타입이 올 수 있음

2) reactive

  • 역할은 ref와 동일
  • reactive 함수의 인자로는 객체와 배열 정도가 주로 쓰임

그러면 ref만 있으면 되지 않나😨? 어차피 ref를 쓰면 객체, 배열 다 되지 않냐는 말이지!

아차차! 인자의 타입만 차이점이 아니었음

  • reactive를 사용하는 경우 객체의 접근 방법이 다름
<script setup>
import { ref, reactive } from 'vue'

const refState = ref({ count: 0 });
const reactiveState = reactive({ count: 0 });

function refPlus() {
  refState.value.count++;
}

function reactivePlus() {
  reactive.count++;
}
  • reactive일 때 객체에게 접근하는 경우 .value속성을 붙이지 않아도 됨! 바로 접근이 가능해짐
  • 왜? ref는 단일 값을 파라미터로 갖는데 내부적으로는 value라는 키 값에 파라미터를 매핑하는 객체로, 따라서 실제 값에 접근하기 위해서는 .value를 통해 한 단계 더 들어가서 접근할 수 있음
  • 주의할 점! .value의 접근은 자바스크립트 영역에서만 필수임(그러니깐 html / template에서는 사용하지 않음)

💡원점으로 toRefs 왜 쓴거지 도대체?

toRefs()에 관한 고찰

  • toRefs()는 객체의 속성을 하나씩 분리 -> ref로 만듦

  • reactive의 모든 프로퍼티에 대해 toRef를 적용해 반환
    cf) toRef? 단일 reactive객체 프로퍼티를 ref로!

    반응성을 가진 객체를 받아 해당 객체의 속성들을 반응성을 가진 개별적인 ref로 변환

  • 구조분해할당에서의 toRefs

// reactive로 구조분해 할당 하는 경우
import { reactive } from 'vue';

const { count } = reactive({
  count: 0
});

console.log(count);

count는 더이상 리액티브 하지 않음!(구조분해가 반응성 시스템을 깨뜨림) 사실 이 문제를 해결하기 위해 toRefs가 두둥 등장!

toRefs는 구조분해할당 하는 경우를 대비해서 등장한 기능(반응성 절대 지켜!)

cf) 구조분해 할당이란? 배열이나 객체에서 속성을 추출하여 보다 간결한 방식으로 변수에 할당할 수 있는 구분

// 1. 간결하고 가독성이 좋다 & 반복적인 코드를 방지
const closet = { category: 'shirts', color: 'red' };
// 구조분해 할당이 없는 경우 객체에 접근하는 방법
const category = closet.category;
const color = closet.color;
// 구조분해 할당을 하는 경우
const { category, color } = closet;

// 2. 유연성: 필요한 속성이나 요소만 선택하고 나머지는 버릴 수 있음
const closet = { category: 'shirts', color: 'red', price:20000 };
const { category, price } = closet;

그러니깐 사실상 일반적으로는 이런식으로 쓰이면 쓰임이 맞다고 볼 여지가 어느 정도 있는 편(나 자신과 진짜 최대한 타협했을 경우임)

const toRefObject = reactive({
  expiredAt: null,
  clientId: null
});

const {expiredAt, clientId} = toRefs(toRefObject);
  • expiredAt은 toRefObject.expiredAt을 참조하는 반응형 ref가 됨
  • 그렇다면 그냥 ref를 쓴다면?
const expiredAt = ref('null');
const clientId = ref('null');
  • 똑.같.다ㅎ

이쯤에서 다시 한 번 나의 문제 코드는..... 그래도 해석을 해보자면

const { expiredAt } = toRefs(reactive({ expiredAt: ref(null) }));
const { clientId } = toRefs(reactive({ expiredAt: ref(null) }));
  • expiredAt은 expiredAt을 참조하는 반응형 ref가 됨
  • 코드에서 reactive 함수는 객체를 반응형으로 만들고
  • ref함수는 null 값을 갖는 반응형 ref를 생성
  • 그리고 toRefs 함수는 객체를 분해해서 반응형 ref로 만듦

결론

  • toRefs 함수는 주어진 객체의 각 속성을 ref로 변환하는 데 사용 됨
  • 하지만 내가 작성했던 코드에서는 각 변수에 대해 toRefs를 호출할 필요가 없었음
  • 따라서 성능적인 부분에서도 문제가 발생할 수 있음(객체 생성하는 과정에서 불필요한 메모리 할당 및 객체 초기화가 있음, 또한 ref 객체로 변환하기 위해 toRefs에 두 개의 함수를 별도로 호출하고 있음)

나의 반성

첫 번째. '일단 되니깐 끝'이라는 생각으로 끝내면 안 된다.
두 번째. 성능에 관해서도 관심을 갖자.

profile
`나는 ${job} 개발자`

0개의 댓글