Utility Village 단위 변환기는 7개 카테고리의 단위 변환을 지원합니다. 한국 전통 단위(평, 돈, 냥, 근)를 포함하고, 어떤 단위든 클릭하면 즉시 기준 단위로 전환되는 UX를 구현했습니다.

데이터 구조 설계

단위 변환의 핵심은 모든 단위를 **기준 단위(base unit)**로 변환하는 함수 쌍을 정의하는 것입니다.

interface UnitDef {
  id: string;
  label: string;        // 표시 이름
  symbol: string;       // 단위 기호
  toBase: (v: number) => number;    // 해당 단위 → 기준 단위
  fromBase: (v: number) => number;  // 기준 단위 → 해당 단위
}

interface CategoryDef {
  id: UnitCategory;
  label: string;
  units: UnitDef[];
}

linear() 헬퍼로 선언적 정의

선형 변환(비례 관계)인 대부분의 단위는 linear(factor) 헬퍼로 간결하게 정의합니다.

const linear = (factor: number): Pick<UnitDef, 'toBase' | 'fromBase'> => ({
  toBase: (v) => v * factor,
  fromBase: (v) => v / factor,
});

// 길이 카테고리 예시 (기준 단위: 미터)
const lengthUnits: UnitDef[] = [
  { id: 'm',  label: '미터',       symbol: 'm',  ...linear(1) },
  { id: 'km', label: '킬로미터',   symbol: 'km', ...linear(1000) },
  { id: 'cm', label: '센티미터',   symbol: 'cm', ...linear(0.01) },
  { id: 'ft', label: '피트',       symbol: 'ft', ...linear(0.3048) },
  { id: 'in', label: '인치',       symbol: 'in', ...linear(0.0254) },
  { id: 'mi', label: '마일',       symbol: 'mi', ...linear(1609.344) },
];

온도: 비선형 변환

온도는 선형 비례 관계가 아니므로 linear()를 사용할 수 없습니다. 기준 단위를 섭씨(°C)로 정하고 직접 변환 함수를 작성합니다.

const temperatureUnits: UnitDef[] = [
  {
    id: 'celsius', label: '섭씨', symbol: '°C',
    toBase: (v) => v,
    fromBase: (v) => v,
  },
  {
    id: 'fahrenheit', label: '화씨', symbol: '°F',
    toBase: (v) => (v - 32) * 5 / 9,
    fromBase: (v) => v * 9 / 5 + 32,
  },
  {
    id: 'kelvin', label: '켈빈', symbol: 'K',
    toBase: (v) => v - 273.15,
    fromBase: (v) => v + 273.15,
  },
];

한국 전통 단위

// 넓이 카테고리 (기준 단위: m²)
{ id: 'pyeong', label: '평', symbol: '평', ...linear(3.30579) },

// 무게 카테고리 (기준 단위: g)
{ id: 'don',  label: '돈', symbol: '돈', ...linear(3.75) },
{ id: 'nyang', label: '냥', symbol: '냥', ...linear(37.5) },  // 10돈
{ id: 'geun', label: '근', symbol: '근', ...linear(600) },    // 고기 기준 600g

기준 단위 클릭 UX

입력 필드 옆의 단위 버튼을 클릭하면 해당 단위가 새로운 기준 단위가 됩니다. 입력값은 유지하고 나머지 단위들이 그 기준에서 역산되어 실시간으로 갱신됩니다.

const [baseUnit, setBaseUnit] = useState<string>(category.units[0].id);
const [inputValue, setInputValue] = useState<string>('1');

// 기준 단위의 값을 base로 변환 후, 각 단위의 fromBase로 표시
const baseValue = useMemo(() => {
  const base = category.units.find(u => u.id === baseUnit);
  return base ? base.toBase(parseFloat(inputValue) || 0) : 0;
}, [baseUnit, inputValue, category]);

// 특정 단위의 표시값
const getDisplayValue = (unit: UnitDef) =>
  formatNumber(unit.fromBase(baseValue));

단위 버튼 클릭 시 setBaseUnit(unit.id)만 호출하면 useMemo가 자동으로 모든 표시값을 재계산합니다. 상태 관리가 단순하면서도 실시간 반응이 자연스럽게 구현됩니다.

숫자 포맷팅

변환 결과는 유효 숫자 6자리로 표시하되, 매우 작거나 큰 값은 지수 표기법으로 전환합니다.

function formatNumber(value: number): string {
  if (value === 0) return '0';
  const abs = Math.abs(value);
  if (abs < 0.000001 || abs >= 1e10) {
    return value.toExponential(4);
  }
  return parseFloat(value.toPrecision(6)).toString();
}

사이트 바로가기: https://utility.dreamurl.biz/calculator/unit-converter