🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more

x-view-model

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

x-view-model

A lightweight, type-safe MVVM state management solution for React applications. Features reactive updates, computed properties, and deep path selection with minimal bundle size.

3.1.5
latest
Version published
Weekly downloads
55
89.66%
Maintainers
1
Weekly downloads
 
Created

x-view-model

npm version License: ISC TypeScript Bundle Size PRs Welcome

React 애플리케이션을 위한 가벼우면서 타입 안전한 MVVM 상태 관리 솔루션

x-view-model을 선택하는 이유

x-view-model은 React 애플리케이션에 간단하면서도 강력한 상태 관리 솔루션을 제공하기 위해 설계되었습니다. MVVM 패턴의 모범 사례를 현대적인 React 기능과 결합합니다:

  • 🚀 높은 성능: 최소한의 리렌더링과 효율적인 업데이트에 최적화
  • 💪 타입 안전성: 포괄적인 타입 추론을 통한 완벽한 TypeScript 지원
  • 🎯 MVVM 패턴: 뷰와 비즈니스 로직 간의 명확한 관심사 분리
  • 🔄 반응형: 상태 변경 시 자동 업데이트
  • 🎨 계산된 속성: 자동 업데이트와 함께 상태에서 값을 파생
  • 🔍 깊은 경로 선택: 중첩된 상태 변경에 효율적으로 구독
  • 📦 경량: 최소 번들 크기 (~13.5KB 압축, ~5KB gzipped)
  • 🛠 개발자 경험: 포괄적인 도구와 직관적인 API
  • 🔄 스마트 메모리 관리: 참조 카운팅을 통한 자동 처리

다른 솔루션 대신 x-view-model을 선택하는 이유

🏆 우수한 TypeScript 지원

TypeScript 지원을 사후 고려로 추가하는 다른 상태 관리 라이브러리와 달리, x-view-model은 처음부터 TypeScript로 구축되었습니다:

// 상태와 메서드에 대한 완전한 타입 추론
const [state, send] = useViewModel(userVM, ["name", "email"]);

// 타입 안전한 경로 선택
const [state] = useMemoizedViewModel(userVM, [
  "profile.avatar",
  "settings.theme",
] as const);

// 타입 안전한 계산된 값
const [state] = useComputedViewModel(
  userVM,
  (state) => ({
    fullName: `${state.firstName} ${state.lastName}`,
  }),
  ["firstName", "lastName"]
);

⚡️ 뛰어난 성능

x-view-model은 최고의 성능을 위해 설계되었습니다:

  • 제로 의존성: 외부 의존성이 없어 더 빠른 로딩과 작은 번들 크기
  • 스마트 업데이트: 구독된 상태가 변경될 때만 컴포넌트 리렌더링
  • 효율적인 경로 선택: 리렌더링을 최소화하기 위해 특정 상태 경로 구독
  • 최적화된 계산: 계산된 값은 캐시되며 의존성이 변경될 때만 재계산
  • 트리 쉐이킹 가능: 최종 번들에 사용하는 코드만 포함
  • 스마트 리소스 관리: 참조 카운팅을 통한 미사용 뷰 모델의 자동 처리

🎯 깔끔한 아키텍처

MVVM 패턴은 관심사의 명확한 분리를 제공합니다:

// 뷰 모델 (비즈니스 로직)
const userVM = registViewModel<UserContext>({
  name: "",
  email: "",
  updateProfile(data) {
    if (data.name) this.state.name = data.name;
    if (data.email) this.state.email = data.email;
  },
});

// 뷰 (UI)
function UserProfile() {
  const [state, send] = useViewModel(userVM, ["name", "email"]);
  return (
    <div>
      <p>Name: {state.name}</p>
      <p>Email: {state.email}</p>
      <button onClick={() => send("updateProfile", { name: "John" })}>
        Update
      </button>
    </div>
  );
}

🔄 원활한 비동기 지원

비동기 작업을 쉽게 처리할 수 있습니다:

const [state, send] = useViewModel(userVM, ["loading", "data"]);

// 이벤트 기반 호출
send("fetchData");

// 반환 값이 있는 비동기 호출
const result = await send("fetchData", {}, true);

// 타입 안전한 오류 처리
try {
  const data = await send("fetchData", {}, true);
} catch (error) {
  // 오류 처리
}

📊 성능 비교

기능x-view-modelReduxMobXZustand
번들 크기~13.5KB~7KB~16KB~1KB
TypeScript 지원⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
학습 곡선⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
성능⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
코드 복잡성⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
비동기 지원⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

설치

npm install x-view-model

# 또는 yarn 사용
yarn add x-view-model

# 또는 pnpm 사용
pnpm add x-view-model

빠른 시작

간단한 카운터 예제로 시작해보세요:

import { registViewModel, useViewModel } from "x-view-model";

// 뷰 모델 인터페이스 정의
interface CounterViewModel {
  count: number;
  increment(): void;
  decrement(): void;
}

// 뷰 모델 생성
const counterVM = registViewModel<CounterViewModel>(
  {
    count: 0,
    increment() {
      this.state.count += 1;
    },
    decrement() {
      this.state.count -= 1;
    },
  },
  { name: "counter-view-model", deep: true }
);

// 컴포넌트에서 사용
function Counter() {
  // 두 번째 매개변수 ["count"]는 구독할 상태 속성을 지정합니다
  // 이는 이러한 특정 속성이 변경될 때만 업데이트하여 리렌더링을 최적화합니다
  const { state, increment, decrement } = useViewModel(counterVM, ["count"]);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

핵심 개념

뷰 모델

뷰 모델은 애플리케이션의 비즈니스 로직과 상태를 캡슐화합니다. UI와 비즈니스 로직 사이의 깔끔한 분리를 제공합니다:

type UserState = {
  name: string;
  email: string;
  firstName: string;
  lastName: string;
  profile: {
    avatar: string;
  };
  settings: {
    theme: "dark" | "light";
  };
};

type UserAction = {
  updateProfile(payload: { name?: string; email?: string }): void;
  fetchUserData(payload: { userId: string }): Promise<{
    id: string;
    name: string;
    email: string;
  }>;
};

export type UserContext = UserState & UserAction;

const userVM = registViewModel<UserContext>(
  {
    name: "",
    email: "",
    firstName: "",
    lastName: "",
    profile: {
      avatar: "",
    },
    settings: {
      theme: "dark",
    },
    updateProfile(data: { name?: string; email?: string }) {
      if (data.name) this.state.name = data.name;
      if (data.email) this.state.email = data.email;
    },
    async fetchUserData(data: { userId: string }) {
      // API 호출 시뮬레이션
      return {
        id: data.userId,
        name: "John Doe",
        email: "john@example.com",
      };
    },
  },
  { name: "user-view-model", deep: true }
);

useViewModel

뷰 모델 상태와 메서드에 접근하기 위한 기본 훅:

const [state, send] = useViewModel(userVM, ["name", "email"]);

// send 함수 사용 예시:
// 1. 프로필 업데이트 (void 반환)
send("updateProfile", { name: "John Doe" }); // 이벤트 기반 호출
await send("updateProfile", { name: "John Doe" }, true); // 비동기 호출

// 2. 데이터 가져오기 (반환 값 있음)
const userData = await send("fetchUserData", { userId: "123" }, true); // 사용자 데이터 반환
// userData는 다음과 같습니다: { id: "123", name: "John Doe", email: "john@example.com" }

/* send 함수의 동작은 async 매개변수에 따라 다릅니다:
 * - async가 false인 경우 (기본값): 메서드를 이벤트로 호출하고 void를 반환
 * - async가 true인 경우: 메서드를 호출하고 결과를 반환
 *   - 메서드가 Promise를 반환하면 풀어서 반환
 *   - 메서드가 직접 값을 반환하면 해당 값을 반환
 */

useMemoizedViewModel

뷰 모델에서 특정 경로를 선택하기 위한 최적화된 훅:

const [state, send] = useMemoizedViewModel(userVM, [
  "name",
  "profile.avatar",
  "settings.theme",
] as const);

/* useMemoizedViewModel은 지정된 상태 경로만 구독하고 반환합니다.
 * 이 예제에서 state 객체는 다음만 포함합니다:
 * - state.name
 * - state.profile.avatar
 * - state.settings.theme
 * 다른 속성은 state 객체에 포함되지 않습니다.
 */

useComputedViewModel

뷰 모델 상태에서 계산된 값을 생성:

const [state, send] = useComputedViewModel(
  userVM,
  (state) => ({
    fullName: `${state.firstName} ${state.lastName}`,
  }),
  ["firstName", "lastName"]
);

/* useComputedViewModel은 의존성이 변경될 때만 계산된 값을 반환합니다.
 * 이 예제에서 firstName이나 lastName이 변경될 때 state 객체는 다음만 포함합니다:
 * - state.fullName
 * 계산된 값 fullName은 firstName이나 lastName이 변경될 때마다 자동으로 업데이트됩니다.
 */

히스토리 기능

x-view-model은 send 메소드를 통한 모든 메소드 호출에 대한 히스토리를 기록하고 관리하는 기능을 제공합니다. 이를 통해 모든 상태 변경 메소드의 호출을 추적하고 모니터링할 수 있습니다:

// 히스토리 핸들러 정의
const counterVM = registViewModel<CounterContext>(
  {
    count: 0,
    increment() {
      this.count += 1;
      return this.count;
    },
    decrement() {
      this.count -= 1;
      return this.count;
    }
  },
  {
    name: "counter",
    deep: true,
    history: {
      handler: (history, state) => {
        // send 메소드를 통한 호출 기록 처리
        console.log('Method called via send:', {
          name: history.name,      // 호출된 메소드 이름
          payload: history.payload, // send 메소드에 전달된 인자
          result: history.result,   // 메소드의 반환값
          error: history.error,     // 에러 발생 시
          timestamp: history.timestamp // 호출 시간
        });
      },
      maxSize: 100 // 최대 히스토리 저장 개수 (선택적)
    }
  }
);

// 사용 예시
const [state, send] = useViewModel(counterVM, ["count"]);

// 이 send 호출들이 히스토리에 기록됩니다
await send("increment", undefined, true);  // history에 기록
await send("decrement", undefined, true);  // history에 기록

히스토리 기능의 주요 특징

  • send 메소드 호출 추적

    • send 메소드를 통한 모든 메소드 호출 기록
    • 호출된 메소드 이름, 전달된 인자, 결과값, 시간 기록
    • 에러 발생 시 에러 정보도 기록
  • 커스텀 핸들러

    • 히스토리 이벤트를 자유롭게 처리할 수 있는 핸들러 제공
    • 외부 로깅 시스템 연동 가능
    • 상태 변화 추적 가능
  • 히스토리 크기 관리

    • maxSize 옵션으로 히스토리 저장 개수 제한
    • 메모리 사용량 최적화
  • 히스토리 조회 및 관리

    // 히스토리 조회
    const history = counterVM.context.getSendHistory();
    
    // 히스토리 초기화
    counterVM.context.clearSendHistory();
    

활용 예시

// 디버깅을 위한 히스토리 로깅
const vm = registViewModel<UserContext>(
  {
    // ... state and methods
  },
  {
    name: "user",
    deep: true,
    history: {
      handler: (history, state) => {
        if (process.env.NODE_ENV === 'development') {
          console.log(`[${new Date(history.timestamp).toISOString()}]`, {
            method: history.name,
            payload: history.payload,
            result: history.result
          });
        }
      }
    }
  }
);

// 외부 모니터링 서비스 연동
const vm = registViewModel<PaymentContext>(
  {
    // ... state and methods
  },
  {
    name: "payment",
    deep: true,
    history: {
      handler: async (history, state) => {
        if (history.error) {
          await sendToErrorTracking({
            method: history.name,
            error: history.error,
            state: state
          });
        }
      },
      maxSize: 50
    }
  }
);

이 기능은 다음과 같은 상황에서 특히 유용합니다:

  • 디버깅 및 문제 해결
  • 사용자 행동 분석
  • 에러 추적 및 모니터링
  • 상태 변화 감사(audit) 로깅
  • 실행 취소/다시 실행 기능 구현

미들웨어

x-view-model은 상태 변경을 가로채고 처리할 수 있는 미들웨어 시스템을 제공합니다. 미들웨어는 상태 변경 전후에 특정 작업을 수행하거나, 변경을 검증하거나, 로깅 등을 할 수 있습니다.

미들웨어 정의

미들웨어는 다음과 같은 형태로 정의됩니다:

type Middleware<T> = (changes: Change[], next: () => void) => void;
  • changes: 상태 변경 정보를 담은 배열
  • next: 다음 미들웨어를 호출하는 함수

미들웨어 사용 예시

import { registViewModel } from 'x-view-model';
import { Change } from 'x-view-model/core/observer';

// 로깅 미들웨어
const loggingMiddleware = (changes: Change[], next: () => void) => {
  console.log('State changes:', changes);
  next();
};

// 검증 미들웨어
const validationMiddleware = (changes: Change[], next: () => void) => {
  const invalidChanges = changes.filter(change => {
    // 특정 조건에 맞지 않는 변경을 필터링
    return !isValid(change);
  });
  
  if (invalidChanges.length > 0) {
    throw new Error('Invalid state changes detected');
  }
  
  next();
};

// ViewModel 정의
const counterVM = registViewModel({
  count: 0,
  increment() {
    this.count += 1;
  }
}, {
  name: 'counter',
  deep: true,
  middlewares: [loggingMiddleware, validationMiddleware]
});

미들웨어 실행 순서

미들웨어는 등록된 순서대로 실행됩니다. 각 미들웨어는 next()를 호출하여 다음 미들웨어로 제어를 넘길 수 있습니다.

const middleware1 = (changes: Change[], next: () => void) => {
  console.log('Middleware 1: before');
  next();
  console.log('Middleware 1: after');
};

const middleware2 = (changes: Change[], next: () => void) => {
  console.log('Middleware 2: before');
  next();
  console.log('Middleware 2: after');
};

// 실행 순서:
// 1. Middleware 1: before
// 2. Middleware 2: before
// 3. Middleware 2: after
// 4. Middleware 1: after

미들웨어 사용 사례

  • 로깅 및 디버깅

    • 상태 변경 추적
    • 성능 모니터링
    • 디버그 정보 수집
  • 검증 및 보안

    • 상태 변경 유효성 검사
    • 접근 제어
    • 데이터 무결성 검증
  • 상태 동기화

    • 다른 시스템과의 상태 동기화
    • 백엔드 API 호출
    • 로컬 스토리지 저장
  • 성능 최적화

    • 변경 배치 처리
    • 불필요한 업데이트 필터링
    • 캐싱 및 메모이제이션

고급 사용법

간단한 폼 예제

기본적인 상태 관리를 보여주는 간단한 폼 예제입니다:

type FormState = {
  username: string;
  email: string;
  isValid: boolean;
};

type FormAction = {
  updateField(payload: { field: keyof FormState; value: string }): void;
  validateForm(): boolean;
};

type FormContext = FormState & FormAction;

const formVM = registViewModel<FormContext>({
  username: "",
  email: "",
  isValid: false,
  updateField({ field, value }) {
    this.state[field] = value;
    this.state.isValid = this.validateForm();
  },
  validateForm() {
    return this.state.username.length > 0 && this.state.email.includes("@");
  },
});

function FormComponent() {
  const [state, send] = useViewModel(formVM, ["username", "email", "isValid"]);

  return (
    <form>
      <input
        value={state.username}
        onChange={(e) =>
          send("updateField", { field: "username", value: e.target.value })
        }
        placeholder="Username"
      />
      <input
        value={state.email}
        onChange={(e) =>
          send("updateField", { field: "email", value: e.target.value })
        }
        placeholder="Email"
      />
      <button disabled={!state.isValid}>Submit</button>
    </form>
  );
}

캔버스와 함께하는 컨트롤러 패턴

이 예제는 Canvas와 같은 복잡한 DOM 조작을 처리하기 위해 컨트롤러 패턴과 함께 x-view-model을 사용하는 방법을 보여줍니다:

// types/canvas.ts
export interface CanvasState {
  width: number;
  height: number;
  color: string;
  lineWidth: number;
  isDrawing: boolean;
  points: Array<{ x: number; y: number }>;
}

// controllers/CanvasController.ts
export class CanvasController {
  private ctx: CanvasRenderingContext2D | null = null;

  constructor(private state: CanvasState) {}

  setCanvas(canvas: HTMLCanvasElement) {
    this.ctx = canvas.getContext("2d");
    if (this.ctx) {
      this.ctx.lineWidth = this.state.lineWidth;
      this.ctx.strokeStyle = this.state.color;
      this.ctx.lineCap = "round";
    }
  }

  startDrawing(x: number, y: number) {
    if (!this.ctx) return;

    this.state.isDrawing = true;
    this.state.points = [{ x, y }];

    this.ctx.beginPath();
    this.ctx.moveTo(x, y);
  }

  draw(x: number, y: number) {
    if (!this.ctx || !this.state.isDrawing) return;

    this.state.points.push({ x, y });

    this.ctx.lineTo(x, y);
    this.ctx.stroke();
  }

  stopDrawing() {
    if (!this.ctx) return;

    this.state.isDrawing = false;
    this.ctx.closePath();
  }

  clearCanvas() {
    if (!this.ctx) return;

    this.ctx.clearRect(0, 0, this.state.width, this.state.height);
    this.state.points = [];
  }

  setColor(color: string) {
    this.state.color = color;
    if (this.ctx) {
      this.ctx.strokeStyle = color;
    }
  }

  setLineWidth(width: number) {
    this.state.lineWidth = width;
    if (this.ctx) {
      this.ctx.lineWidth = width;
    }
  }
}

// viewModels/canvasViewModel.ts
import { registViewModel } from "x-view-model";
import { CanvasController } from "../controllers/CanvasController";
import { CanvasState } from "../types/canvas";

const initialState: CanvasState = {
  width: 800,
  height: 600,
  color: "#000000",
  lineWidth: 2,
  isDrawing: false,
  points: [],
};

const controller = new CanvasController(initialState);

export const canvasViewModel = registViewModel<CanvasState, CanvasController>(
  initialState,
  {
    name: "canvas-view-model",
    deep: true,
  },
  controller
);

// components/CanvasComponent.tsx
import React, { useRef, useEffect } from "react";
import { useViewModel } from "x-view-model";
import { canvasViewModel } from "../viewModels/canvasViewModel";

const CanvasComponent: React.FC = () => {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const [state, send, controller] = useViewModel(canvasViewModel, [
    "color",
    "lineWidth",
    "width",
    "height",
  ]);

  useEffect(() => {
    if (canvasRef.current) {
      controller.setCanvas(canvasRef.current);
    }
  }, [controller]);

  const handleMouseDown = (e: React.MouseEvent<HTMLCanvasElement>) => {
    const rect = canvasRef.current?.getBoundingClientRect();
    if (!rect) return;

    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    controller.startDrawing(x, y);
  };

  return (
    <div>
      <div className="controls">
        <input
          type="color"
          value={state.color}
          onChange={(e) => controller.setColor(e.target.value)}
        />
        <input
          type="range"
          min="1"
          max="20"
          value={state.lineWidth}
          onChange={(e) => controller.setLineWidth(Number(e.target.value))}
        />
        <button onClick={() => controller.clearCanvas()}>Clear</button>
      </div>
      <canvas
        ref={canvasRef}
        width={state.width}
        height={state.height}
        onMouseDown={handleMouseDown}
        onMouseMove={(e) => {
          const rect = canvasRef.current?.getBoundingClientRect();
          if (!rect) return;
          controller.draw(e.clientX - rect.left, e.clientY - rect.top);
        }}
        onMouseUp={() => controller.stopDrawing()}
        onMouseLeave={() => controller.stopDrawing()}
        style={{ border: "1px solid #000" }}
      />
    </div>
  );
};

성능

x-view-model은 성능에 최적화되어 있습니다:

  • 효율적인 업데이트: 구독된 상태가 변경될 때만 컴포넌트 리렌더링
  • 경로 기반 선택: 리렌더링을 최소화하기 위해 특정 상태 경로 구독
  • 메모이제이션된 계산: 계산된 값은 캐시되며 의존성이 변경될 때만 재계산
  • 최소 오버헤드: 제로 의존성과 작은 번들 크기
  • 트리 쉐이킹 가능: 사용하는 코드만 포함
  • 스마트 리소스 관리: 참조 카운팅을 통한 미사용 뷰 모델의 자동 처리

타입 안전성

라이브러리는 우수한 TypeScript 지원을 제공합니다:

  • 상태와 메서드에 대한 완전한 타입 추론
  • 경로 기반 타입 선택
  • 메서드 매개변수 타이핑
  • 반환 값 타이핑
  • 제네릭 타입 지원

기여하기

기여를 환영합니다! Pull Request를 자유롭게 제출해주세요.

라이선스

ISC © seokhwan.kim

지원

자주 묻는 질문

일반적인 질문

Q: 뷰 모델과 일반적인 React 상태 관리의 차이점은 무엇인가요?

A: 뷰 모델은 MVVM 패턴을 사용하여 상태와 비즈니스 로직을 관리하는 구조화된 방법을 제공합니다. 일반적인 React 상태와 달리:

  • 비즈니스 로직을 UI 컴포넌트와 분리
  • 타입 안전한 상태 관리 제공
  • 경로 기반 구독을 통한 효율적인 업데이트 지원
  • 계산된 속성과 비동기 작업 지원

Q: MVVM 패턴을 사용하는 이유는 무엇인가요?

A: MVVM 패턴은 다음과 같은 이점을 제공합니다:

  • 뷰와 비즈니스 로직 간의 명확한 관심사 분리
  • 비즈니스 로직의 더 나은 테스트 가능성
  • 더 유지보수 가능하고 확장 가능한 코드 구조
  • 복잡한 애플리케이션에서 더 쉬운 상태 관리

Q: Redux나 MobX 대신 x-view-model을 선택하는 이유는 무엇인가요?

A: x-view-model은 다음을 제공합니다:

  • 더 간단한 API와 더 적은 보일러플레이트
  • 기본적으로 더 나은 TypeScript 지원
  • 더 작은 번들 크기
  • 더 직관적인 상태 관리
  • 최적화된 업데이트를 통한 더 나은 성능

성능

Q: 프로덕션에서 성능은 어떠한가요?

A: x-view-model은 프로덕션 사용에 최적화되어 있습니다:

  • 최소한의 리렌더링으로 효율적인 업데이트
  • 작은 번들 크기 (~13.5KB 압축)
  • 더 나은 성능을 위한 제로 의존성
  • 작은 애플리케이션과 큰 애플리케이션 모두에 최적화

Q: 대규모 애플리케이션에서 잘 작동하나요?

A: 네, x-view-model은 확장을 위해 설계되었습니다:

  • 효율적인 업데이트를 위한 경로 기반 상태 선택
  • 파생 상태를 위한 계산된 속성
  • 더 나은 코드 구성을 위한 모듈식 아키텍처
  • 더 나은 유지보수성을 위한 타입 안전한 상태 관리

Q: 메모리 사용량은 어떠한가요?

A: 메모리 사용량은 다음을 통해 최적화됩니다:

  • 효율적인 상태 업데이트
  • 스마트 가비지 컬렉션
  • 상태 관리에서 최소한의 오버헤드
  • 불필요한 리렌더링 없음

TypeScript

Q: TypeScript 없이 사용할 수 있나요?

A: 네, x-view-model은 일반 JavaScript에서도 작동하지만 다음을 놓치게 됩니다:

  • 타입 안전성
  • 더 나은 IDE 지원
  • 더 쉬운 리팩토링
  • 타입을 통한 더 나은 문서화

Q: 복잡한 타입 정의는 어떻게 처리하나요?

A: 복잡한 타입의 경우:

  • 가독성을 위한 타입 별칭 사용
  • TypeScript의 유틸리티 타입 활용
  • 복잡한 타입을 더 작은 인터페이스로 분해
  • 재사용 가능한 컴포넌트에 제네릭 사용

Q: 제네릭 타입 사용에 대한 팁이 있나요?

A: 제네릭을 사용할 때:

  • 명확한 타입 제약 조건 정의
  • 가능할 때 타입 추론 사용
  • 제네릭 타입 매개변수 문서화
  • 다른 타입 매개변수로 테스트

상태 관리

Q: 전역 상태와 지역 상태를 어떻게 구분하나요?

A: 모범 사례:

  • 공유 데이터에는 전역 상태 사용
  • 컴포넌트별 데이터에는 지역 상태 사용
  • 다른 관심사에 대해 여러 뷰 모델 사용 고려
  • 효율적인 업데이트를 위한 경로 기반 선택 사용

Q: 빈번한 상태 업데이트를 어떻게 최적화하나요?

A: 최적화 전략:

  • 경로 기반 선택 사용
  • 빠른 업데이트에 디바운싱 구현
  • 파생 상태에 계산된 속성 사용
  • 업데이트 배치 고려

Q: 중첩된 상태를 어떻게 효율적으로 관리하나요?

A: 중첩된 상태의 경우:

  • 경로 기반 선택 사용
  • 적절한 타입 정의 구현
  • 파생 값에 계산된 속성 사용
  • 깊게 중첩된 상태를 평탄화하는 것 고려

비동기 작업

Q: 비동기 작업을 처리하는 가장 좋은 방법은 무엇인가요?

A: 권장 접근 방식:

  • async 플래그와 함께 send 함수 사용
  • 적절한 오류 처리 구현
  • 더 나은 UX를 위한 로딩 상태 사용
  • 더 깔끔한 코드를 위해 async/await 사용 고려

Q: 오류는 어떻게 처리하나요?

A: 오류 처리 모범 사례:

  • try/catch 블록 사용
  • 적절한 오류 경계 구현
  • 의미 있는 오류 메시지 제공
  • 뷰 모델에서 오류 상태 사용 고려

Q: 로딩 상태는 어떻게 관리하나요?

A: 로딩 상태 관리:

  • 상태에서 불리언 플래그 사용
  • 로딩 표시기 구현
  • 로딩 큐 사용 고려
  • UI 컴포넌트에서 로딩 상태 처리

테스트

Q: 뷰 모델을 어떻게 테스트하나요?

A: 테스트 전략:

  • 비즈니스 로직 단위 테스트
  • 의존성 모킹
  • 상태 업데이트 검증
  • 계산된 속성 확인
  • 비동기 작업 테스트

Q: 테스트 환경을 어떻게 설정하나요?

A: 테스트 설정:

  • Jest 또는 선호하는 테스트 프레임워크 사용
  • React 의존성 모킹
  • 적절한 TypeScript 구성 설정
  • 테스트 유틸리티 구현

Q: 모킹은 어떻게 처리하나요?

A: 모킹 접근 방식:

  • 외부 의존성 모킹
  • 의존성 주입 사용
  • 적절한 테스트 픽스처 구현
  • 테스트 팩토리 사용 고려

마이그레이션

Q: Redux에서 어떻게 마이그레이션하나요?

A: 마이그레이션 단계:

  • Redux 스토어 슬라이스 식별
  • 해당 뷰 모델 생성
  • Redux 사용을 점진적으로 교체
  • 컴포넌트를 뷰 모델 사용으로 업데이트
  • Redux 의존성 제거

Q: 기존 코드를 어떻게 리팩토링하나요?

A: 리팩토링 접근 방식:

  • 작고 격리된 컴포넌트로 시작
  • 비즈니스 로직에 대한 뷰 모델 생성
  • 컴포넌트를 뷰 모델 사용으로 업데이트
  • 각 변경 후 철저히 테스트
  • 리팩토링을 점진적으로 확장

Q: 점진적 마이그레이션이 가능한가요?

A: 네, x-view-model은 다음을 지원합니다:

  • 점진적 채택
  • 다른 상태 관리와의 공존
  • 단계별 마이그레이션
  • 전환 기간 동안 병렬 사용

커뮤니티

Q: 어디서 도움을 받을 수 있나요?

A: 지원 채널:

  • GitHub 이슈
  • GitHub 토론
  • 문서
  • 커뮤니티 포럼

Q: 어떻게 기여할 수 있나요?

A: 기여 옵션:

  • 버그 보고
  • 기능 제안
  • 문서 개선
  • Pull Request 제출
  • 예제 공유

Q: 버그를 발견하면 어떻게 해야 하나요?

A: 버그 보고 단계:

  • 기존 이슈 확인
  • 최소 재현 생성
  • 자세한 정보 제공
  • 버그 보고서 제출

seokhwan.kim이 만든 ❤️

FAQs

Package last updated on 24 Apr 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts