ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Vue3 반응형 (reactive, ref)
    Vue 2023. 9. 10. 17:34

    배경

    실무에서 반응형 함수에 대한 이해부족

    예를 들어, let과 ref(), reactive()를 사용할때 명확한 기준이 없었다.

     

    따라서 Vue에서 사용하는 반응형 함수 ref()와 reactive()에 대한 내용을 정리하겠다.

    * 이 문서는 composition API 기준으로 작성되었습니다.

     

     


     

    반응형이란? 

    데이터가 변경되었을 때 이를 감지하고 관련된 동작을 수행하는 것.

    즉, 데이터가 변경되었을 때 화면도 변경된다. 

    프로그래밍 관점에서 말하면, 상태(state)가 변경되었을 때 컴포넌트의 DOM을 변경해주는 것이다. 

     

    여기서 상태(state)는 컴포넌트와 연관된 데이터를 의미한다. Vue에서 반응형 함수를 사용하면, 상태(state)가 변경될 때 컴포넌트의 DOM을 바꿔주는 코드를 짤 필요가 없어진다. 따라서 개발자는 상태(state)가 변경되는 로직에 집중하고, 렌더링 과정을 신경을 쓸 필요가 없어진다. 

     

     

    반응형 예시

    예를 들어, 단순 값을 더하는 코드가 있다. 

     

    let a = 1;
    let b = 2;
    let sum = a + b;
    
    console.log(sum) // 3
    b = 3;
    console.log(sum) // 3

    일반적으로 b값을 3으로 변경했기 때문에 1 + 3 = 4로 두 번째 출력값이 4가 나와야한다고 생각할 수 있다. 
    실제로는 3이 출력된다.

     

    자바스크립트에서는 변수를 생성하고 값을 할당할 때, 변수는 메모리 내의 해당 값에 대한 참조를 가지게 된다. 변수에 대한 연산을 수행하거나 값을 변경할 때, 메모리에 있는 원래 값은 변경되지 않고, 값에 대한 참조만 업데이트 된다. 

    코드를 자세히 살펴보자

    1. let sum = a + b: 변수 sum에 a와 b의 합인 3이 할당된다. 

    2. console.log(sum) : a와 b의 합인 3이 출력된다.

    3. b = 3: 변수 b의 값을 3으로 변경한다. 

    4. console.log(sum) : 여전히 sum 변수는 이전에 a + b의 합인 3을 참조하고 있으므로, sum의 값은 변경되지 않고 3이 출력된다.

    따라서, sum변수는 처음에 a + b로 계산된 값을 가지는 것이지만, 이후에 b의 값이 변경되더라도 sum은 처음의 값을 유지한다. 이는 변수의 주소와 관련된 것이 아니라 변수가 참조하는 값의 변경과 관련된 개념이다. 

     

     


    JavaScript의 Proxy

    반응형을 알기 전에 JavaScript의 Proxy에 대해서 우선 간단하게 알아보자

    자바스크립트의 Proxy는 ES6에서 도입된 기능으로, 객체에 대한 접근을 가로채거나 수정하는 데 사용되는 메타 프로그래밍 기능이다. 이를 통해 동작을 커스터마이징하고, 객체에 대한 접근을 제어할 수 있다. 

    쉽게 말해, Proxy는 다른 객체를 감싸는 대리 객체이다. 

     

    new Proxy(target, handler)

    Proxy는 두 가지 주요 요소로 구성된다. 

    • target (대상 객체): Proxy가 감싸는 원본 객체이다. 
    • handler (처리기) : Proxy에 대한 작업을 처리하는 객체로, 여러 가지 트랩(trap) 메서드를 가지고 있다. 이 메서드들은 Proxy를 통해 접근하는 동안 작동하며, 원하는 동작을 구현할 수 있다. 

    ※ 자바스크립트 Object를 만들면 접근자 프로퍼티라는 getter, setter 함수가 자동으로 포함된다. (get(), set()함수이다.)

    Proxy를 통해 Object의 get(), set() 메서드를 재정의해서 원하는 동작을 만들 수 있다. get과 set트랩을 사용하여 객체 속성에 접근하거나 수정하기 전에 특정 동작을 수행 가능하도록 한다. 이 부분을 이용해 Vue에서 반응형 데이터를 생성할 수 있다.

     

     

    반응형의 원리

    뒤에서 설명하겠지만 ref(), reactive()를 사용하면 Vue에서 반응형 데이터를 만들 수 있다. 즉, 상태값이 바뀌면 DOM이 자동으로 업데이트된다. 

     

    상태값을 바꿨는데 DOM이 자동으로 업데이트 되어 렌더링 되는 것이 어떻게 가능한 것인가?

    → 위에서 말한 JavaScript의 Proxy를 사용하면 된다. 

     

    data()와 reactive()함수는 Proxy를 사용한다. ref()는 객체타입에서는 Proxy를 사용하고 기본 타입에서는 Proxy와 유사하게 동작한다. (Proxy는 객체 타입에서만 동작하기 때문이다.)

     

    Vue에서 상태(state)객체는 set함수를 Vue에서 제공하는 handler로 교체한다. 대상 객체를 직접 조회하는 것이 아니라 Proxy객체를 이용하여 관리한다. 

    → 따라서 Proxy객체를 바꾸는 코드가 발생하면, set핸들러에서 DOM을 업데이트 한다. 

     

     

    추가로 상태값이 변경될 때마다 모든 값이 업데이트되고 렌더링 되면 매우 비효율적이다. 따라서 Vue에서는 DOM 업데이트 작업을 스케쥴링하여 버퍼에 모은 뒤 tick단위로 한 번에 처리한다. Vue는 업데이트 주기의 "다음 tick"까지 버퍼링하여 얼마나 많은 상태 변경을 수행하든 각 컴포넌트가 한 번만 업데이트되도록 한다. 

     

     


     

     

    반응형 함수 사용법 (reactive, ref)

    reactive

    reactive()함수를 사용하여 객체 또는 배열을 반응형으로 만들 수 있다. 

    다음과 같이 사용한다.

    import { reactive } from 'vue'
    
    const state = reactive({ count: 0 })

    마찬가지로 반응형 상태를 변경하는 함수를 같은 범위에서 선언하고 상태와 함께 메서드로 노출할 수 있다.

     

    반응형 객체는 JavaScript Proxy이며 일반 객체와 유사하다. 차이점은 Vue가 속성에 접근 및 반응형 객체의 변경사항을 감지할 수 있다는 것이다.

     

    위에서 ‘tick’단위로 DOM에 업데이트 된다고 했다. 상태 변경 후, DOM 업데이트가 완료될 때까지 기다리려면 nextTick() API를 사용하면 된다.

    import { nextTick } from 'vue'
    
    function increment() {
      state.count++
      nextTick(() => {
        // 업데이트된 DOM에 접근 가능
      })
    }

     

     

    Vue는 반응형 상태 내부 깊숙이 추적하므로 (깊은 반응형), 중첩된 객체나 배열을 변경할 때에도 변경 사항이 감지된다. 즉, 직접 접근해서 변경이 가능하다.

    import { reactive } from 'vue'
    
    const obj = reactive({
      nested: { count: 0 },
      arr: ['foo', 'bar']
    })
    
    function mutateDeeply() {
      // 변경 사항이
      obj.nested.count++
      obj.arr.push('baz')
    }

     

     

    리액트에서는 이와 같이 깊은 반응형이 안되기 때문에 스프레드 연산자(…)를 사용하여 중첩된 객체에 접근해야한다. 위의 과정이 번거롭기 때문에 리액트에서는 상태관리라이브러리를 사용하게 되었다. 리액트에서 직관적으로 객체의 속성을 변경하기 위해서 Immer를 사용한다.

     

    reactive()의 반환 값은 원본 객체와 같지 않고 원본 객체를 재정의한 Proxy다. 따라서 원본 객체와 같지 않으며, 원본 객체를 변경해도 업데이트가 트리거되지 않는다.

     

    reactive()는 Map, Set과 같은 컬렉션 유형에만 작동 (string, number, boolean같은 기본유형에 사용할 수 없음.)

    항상 반응형 객체에 대한 동일한 참조를 유지해야한다. 즉, 첫 번째 참조에 대한 반응형 연결이 손실되기 때문에 반응형 객체를 쉽게 교체할 수 없다.

    let state = reactive({ count: 0 })
    
    // 위에서 참조한 ({ count: 0 })는 더 이상 추적되지 않습니다. (반응형 연결이 끊어졌습니다.)
    state = reactive({ count: 1 })

     

     

    ref는 reactive의 제한 사항을 해결하기 위해 어떤 유형의 데이터라도 반응형으로 재정의 할 수 있다. 

    다음과 같이 사용한다. 

    import { ref } from 'vue'
    
    const count = ref(0)

    ref()는 받은 인자를 .value속성을 포함하는 ref 객체에 래핑 후 반환한다.

    ref의 value속성은 반응형이고, 객체 유형의 데이터를 가질 경우 ref는 내부적으로 .value를 자동으로 reactive로 변경한다. 

     

    즉, ref()를 사용하면 모든 값에 대한 "참조"를 만들어 반응성을 잃지 않고 전달할 수 있다.

     

     


    reactive와 ref중 무엇을 사용해야할까?

    • 기본 유형의 데이터(string, number, boolean)이면 ref를 사용
    • 객체, 배열 등의 데이터라면 ref, reactive 사용

     

    그러면 객체, 배열 등의 컬렉션 타입일 때 ref와 reative중 무엇을 사용할까?

    ※ 이 부분은 개인적인 경험에서 나온 내용을 정리한 것으로 정확하지 않을 수 있다.

     

    const a = ref([]);   
    a[0] = 1;
    const b = reactive([]);
    b[0] = 1;

    첫번째 코드는 동작하지만 두번째 코드는 동작하지 않는다.

    그 이유는 reactive를 사용하여 생성된 객체의 속성에 대한 변경은 Vue의 반응성 시스템에 포함되지 않기 때문이다.

     

    따라서 나는 

    배열이나 객체의 속성에 접근하여 변경사항을 반응적으로 처리할때는 ref()를, 

    선언할 때 복잡한 상태 관리는 reactive()를 써서 주로 관리한다.

     

     

     

     

     

     

    참고

    https://v3-docs.vuejs-korea.org/guide/essentials/reactivity-fundamentals.html

    https://goodteacher.tistory.com/531

    https://analogcode.tistory.com/41

    'Vue' 카테고리의 다른 글

    Vue 비동기처리에서의 반응형함수  (0) 2024.02.20
Designed by Tistory.