본문 바로가기

FrontEnd

Valtio의 프록시 상태관리가 어떻게 동작할까? (React Part)

반응형

원문 : https://blog.axlight.com/posts/how-valtio-proxy-state-works-react-part/

 

How Valtio Proxy State Works (React Part)

Introduction In the previous article, we explained how Valtio proxy state works. It tracks mutations of state and create immutable snapshot. Let’s recap the API in vanilla part of Valtio. // Create a new proxy state to detect mutations const state = prox

blog.axlight.com

1편 링크 : https://itchallenger.tistory.com/631

 

Valtio의 프록시 상태관리가 어떻게 동작할까? (Vanila Part)

원문 : https://blog.axlight.com/posts/how-valtio-proxy-state-works-vanilla-part/ How Valtio Proxy State Works (Vanilla Part) Introduction Valtio is a library for global state primarily for React. It..

itchallenger.tistory.com

도입

이전 기사에서 Valtio 프록시 상태가 작동하는 방식을 설명했습니다.
상태의 변형을 추적하고 변경 불가능한 스냅샷을 생성합니다.
Valtio의 바닐라 파트 API를 요약해 보겠습니다.
주 : 프록시는 상태 변경 시에 로직을 끼워넣기 위해서 사용되는것 외에 (버전업, 변형, 변형 이벤트 퍼블리싱) 딱히 큰 역할 안함.
// Create a new proxy state to detect mutations
const state = proxy({ count: 0 });

// You can mutate it
++state.count;

// Create a snapshot
const snap1 = snapshot(state); // ---> { count: 1 }

// Mutate it again
state.count *= 10;

// Create a snapshot again
const snap2 = snapshot(state); // ---> { count: 10 }

// The previous snapshot is not changed
console.log(snap1); // ---> { count: 1 }

// You can subscribe to it
subscribe(state, () => {
  console.log('State changed to', state);
});

// Then, mutate it again
state.text = 'hello'; // ---> "State changed to { count: 10, text: 'hello' }"

이제 React에서 상태를 사용하는 방법을 살펴보겠습니다.

useSyncExternalStore 소개

React 18은 useSyncExternalStore라는 새로운 훅을 제공합니다.
React에서 외부 저장소를 안전하게 사용하도록 설계되었습니다.
Valtio의 프록시 객체는 정확히 외부 저장소입니다.
 
불변 상태를 생성하는 스냅샷 기능이 있으므로 매우 간단해야 합니다.
// 상태 생성
const stateFoo = proxy({ count: 0, text: 'hello' });

// 구독 함수 정의
const subscribeFoo = (callback) => subscribe(stateFoo, callback);

// 스냅샷 함수 정의
const snapshotFoo = () => snapshot(stateFoo);

// 상태 사용 함수 정의
const useStateFoo = () => useSyncExternalStore(
  subscribeFoo,
  snapshotFoo
);​

정말 간단하네요!
모든 프록시 상태를 처리하는 커스텀 훅을 만들 수 있습니다.
useCallback을 사용하는 것을 잊어서는 안됩니다.
그러나 Valtio에는 더 고급 기능인 자동 렌더링 최적화가 있습니다.
주 : 상태 업데이트에 의한 렌더링 발생 부분은 리액트에서 담당함. 상태는 그냥 vanila js임

자동 렌더 최적화? (What is automatic render optimization)

렌더링 최적화는 사용자에게 아무런 차이가 없는 결과를 생성하는 추가 재렌더링을 피하기 위한 것입니다.
stateFoo의 text 속성 값을 표시하는 컴포넌트가 있다고 가정합니다.
const TextComponent = () => {
  const { text } = useStateFoo();
  return <span>{text}</span>;
};

 

++stateFoo.count와 같이 stateFoo에서 count 값을 변경하면 이 TextComponent는 실제로 다시 렌더링되지만
count 값을 사용하지 않고 텍스트 값이 변경되지 않기 때문에 동일한 결과를 생성합니다.
불필요한 리렌더링입니다.
 
렌더 최적화는 이러한 추가 재렌더링을 피하는 것이며 이를 해결하는 한 가지 방법은 사용할 속성을 훅에 수동으로 알려주는 것입니다.
 
예를 들어 훅이 문자열 배열을 인자로 허용한다고 가정하면 다음과 같은 속성을 알릴 수 있습니다.
const TextComponent = () => {
  const { text } = useStateFoo(['text']);
  return <span>{text}</span>;
};​

자동 렌더링 최적화는 이를 자동으로 수행하는 것입니다.
이게 가능해? 프록시를 사용하면 가능합니다.
프록시를 사용하면 상태 속성 액세스를 감지할 수 있습니다.
나는 몇 년 동안 이 작업을 해왔고 react-tracked는 이 기술을 사용하는 프로젝트 중 하나입니다.
proxy-compare라는 내부 라이브러리가 있습니다.
 

proxy-compare 동작 방식

proxy-compare는 자동 렌더링 최적화를 활성화하는 라이브러리입니다.
우리가 알고 싶은 것은 앞의 예에서 text 값이 TextComponent에서 사용된다는 것입니다.
프록시를 사용하여 수행할 수 있는 방법을 살펴보겠습니다.
// 접근한 속성들을 저장해두는 배열
const accessedProperties = [];

// 상태를 프록시로 래핑함
const obj = new Proxy(stateFoo, {
  get: (target, property) => {
    accessedProperties.push(property);
    return target[property];
  },
});

// Use it
console.log(obj.text);

// We know what are accessed.
console.log(accessedProperties); // ---> ['text']​

이것이 기본 아이디어입니다. 이를 확장하여 중첩된 개체에 대한 액세스를 지원하고자 합니다.

// More complex state
const obj = { nested: { count: 0, text: 'hello' }, others: [] };

// Use a nested property
console.log(obj.nested.count);

// As a result, `nested.count` is detected as used.
// `nested.text` and `others` are known to be unused.

이것은 꽤 많은 작업이지만 프록시 비교는 이러한 경우를 처리합니다. 그리고 그것은 매우 효율적인 방식으로 이루어집니다.
궁금하시다면 proxy-compare의 소스코드를 확인해보세요.
Valtio는 자동 렌더링 최적화를 활성화하기 위해 프록시 비교를 기반으로 하는 훅을 제공합니다.
 

useSnapshot

Valtio에서 제공하는 hook을 useSnapshot이라고 합니다.
변경할 수 없는 스냅샷을 반환하지만 렌더링 최적화를 위해 프록시로 래핑됩니다.
다음과 같이 사용할 수 있습니다.
import { proxy, useSnapshot } from 'valtio';

const state = proxy({ nested: { count: 0, text: 'hello' }, others: [] });

const TextComponent = () => {
  const snap = useSnapshot(state);
  return <span>{snap.nested.text}</span>;
};

이 컴포넌트는 텍스트 값이 변경될 때만 다시 렌더링합니다.
count 또는 other가 변경되더라도 다시 렌더링되지 않습니다.
 
useSnapshot의 구현은 약간 까다롭습니다.
따라서 우리는 여기서 자세히 다루지 않습니다.
기본적으로 useSyncExternalStore와 proxy-compare의 조합일 뿐입니다.
 
Valtio의 변경 가능한 상태 모델은 useSnapshot의 멘탈 모델과 매우 잘 맞습니다.
기본적으로 proxy를 사용하여 상태 개체를 정의하고 useSnapshot과 함께 사용하며 원하는 대로 상태 개체를 변경할 수 있습니다.
라이브러리는 다른 모든 것을 처리합니다.
 
공정하게 말하면 프록시 작동 방식으로 인해 몇 가지 제한 사항이 있습니다.
예를 들어 프록시는 Map에서 돌연변이를 감지할 수 없습니다.
또 다른 예는 프록시가 Object.keys의 사용을 감지할 수 없다는 것입니다.
 
(2022년 2월 3일 편집: Object.keys는 실제로 제대로 작동하고 위의 설명은 잘못되었습니다.
또한 valtio/utils에는 이제 Map 동작을 에뮬레이트하고 돌연변이를 감지하는 proxyMap이 있습니다.)

맺음말

저희가 이전 글과 이번 글에서 Valtio의 전체적인 개념을 충분히 설명했으면 좋겠습니다.
실제로 구현에는 일부 엣지 케이스를 처리하고 효율성을 위해 더 많은 작업이 필요합니다.
우리는 그것은 상당히 작은 부분이라고 생각하며 관심이 있으면 아래 페이지를 읽어보세요
 

GitHub - pmndrs/valtio: 💊 Valtio makes proxy-state simple for React and Vanilla

💊 Valtio makes proxy-state simple for React and Vanilla - GitHub - pmndrs/valtio: 💊 Valtio makes proxy-state simple for React and Vanilla

github.com

참고 :

https://valtio.pmnd.rs/

 

https://valtio.pmnd.rs/

const state = proxy({ dur: 4, count: 1 }); const incDur = () => {++state.dur}; const decDur = () => {--state.dur}; const incCount = () => { ++state.count; setTimeout(incCount, 100 * state.dur); }; incCount(); const snap = useSnapshot(state) return ( {snap.

valtio.pmnd.rs

 

반응형