- Published on
Evaluation (평가)
- Authors
- Name
평가(Evaluation)
자바스크립트에서 평가(Evaluation)는 코드가 실행되는 시점과 방식을 결정하는 중요한 개념입니다. 이번 글에서는 즉시 평가(Eager Evaluation)와 지연 평가(Lazy Evaluation)에 대해 다룹니다.
즉시 평가(Eager Evaluation)
즉시 평가는 표현식이나 함수가 정의되는 즉시 계산되는 방식입니다.
자바스크립트는 기본적으로 즉시평가를 사용합니다.
// 즉시 평가 예시
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map(n => n * 2); // 즉시 계산되어 [2, 4, 6, 8, 10] 생성
즉시 평가의 특징
- 예측가능성 : 코드가 작성된 순서대로 실행됩니다.
- 메모리사용 : 모든 결과가 미리 계산되어 메모리에 저장됩니다.
- 성능 : 한번만 계산하고 재사용할 때 유리합니다.
지연 평가(Lazy Evaluation)
지연 평가는 실제로 값이 필요할 때까지 계산을 미루는 방식입니다.
자바스크립트에서는 제너레이터, 이터레이터, 프로미스 등을 통해 지연 평가를 구현할 수 있습니다.
1. 제너레이터 사용
제너레이터(Generator)는 자바스크립트에서 지연 평가를 구현하는 가장 간단하고 직관적인 방법입니다.
제너레이터 함수는 실행을 일시 중지하고 나중에 다시 시작할 수 있는 특별한 함수로, yield 키워드를 사용하여 값을 하나씩 생성합니다.
function* createLazyEvaluator(numbers: number[]) {
for (const n of numbers) {
yield n * 2;
}
}
const lazyEvaluator = createLazyEvaluator([1, 2, 3, 4, 5]); // 아직 계산되지 않음
console.log(lazyEvaluator.next().value); // 2
console.log(lazyEvaluator.next().value); // 4
console.log(lazyEvaluator.next().value); // 6
console.log(lazyEvaluator.next().value); // 8
console.log(lazyEvaluator.next().value); // 10
2. 이터레이터 사용
이터레이터 프로토콜은 값이 필요할 때만 계산하는 지연 평가의 대표적인 예시입니다.
이터레이터는 next()
메서드가 호출될 때에만 다음 값을 계산하므로, 모든 값을 미리 계산하지 않고 필요할 때에만 계산할 수 있습니다.
function createLazyEvaluator(numbers: number[], callback: (n: number) => number) {
let index = 0;
return {
next() {
if(index < number.length) {
const value = callback(numbers[index]);
index++;
return { value, done: false };
} else {
return { value: undefined, done: true };
}
},
[Symbol.iterator]() {
return this;
},
};
}
const lazyEvaluator = createLazyEvaluator([1, 2, 3, 4, 5], (n) => n * 2);
console.log(lazyEvaluator.next().value); // 2
console.log(lazyEvaluator.next().value); // 4
console.log(lazyEvaluator.next().value); // 6
console.log(lazyEvaluator.next().value); // 8
console.log(lazyEvaluator.next().value); // 10
3. 프로미스 체이닝 사용
프로미스 체이닝을 사용하면 비동기 작업의 파이프라인을 구성할 수 있습니다.
function createLazyEvaluator(numbers: number[]) {
let promise = Promise.resolve(numbers);
const pipeline = {
map(fn: (n: number) => number) {
promise = promise.then(fn);
return pipeline;
},
filter(fn: (n: number) => boolean) {
promise = promise.then((number) => fn(number) ? number : Promise.reject('Filter failed'));
return pipeline;
},
execute() {
return promise
}
}
return pipeline;
}
const lazyEvaluator = createLazyEvaluator([1, 2, 3, 4, 5])
.map((numbers) => {
return numbers.map((n) => n * 2);
})
.filter((numbers) => {
return numbers.filter((n) => n % 2 === 0);
})
lazyEvaluator.execute()
.then((result) => {
console.log(result); // [2, 4, 6, 8, 10]
})
.catch((error) => {
console.error(error);
})
지연 평가의 특징
- 메모리 효율성 : 필요한 값만 계산하고 메모리에 저장합니다.
- 성능 : 불필요한 계산을 피할 수 있습니다.
- 비동기 처리 : 비동기 작업에서 유용합니다.
지연 평가의 실제 활용 사례
1. UI 컴포넌트의 지연 로딩
웹 애플리케이션에서 UI 컴포넌트를 지연 로딩하여 초기 로딩 시간을 단축할 수 있습니다.
const LazyFirstTabComponent = React.lazy(() => import('./FirstTabComponent'));
const LazySecondTabComponent = React.lazy(() => import('./SecondTabComponent'));
function TabComponent() {
const [selectedTab, setSelectedTab] = useState<'FIRST' | 'SECOND'>('FIRST');
return (
<div>
<button onClick={() => setSelectedTab('FIRST')}>First Tab</button>
<button onClick={() => setSelectedTab('SECOND')}>Second Tab</button>
<Suspense fallback={<div>Loading...</div>}>
{match(selectedTab)
.with('FIRST', () => <LazyFirstTabComponent />)
.with('SECOND', () => <LazySecondTabComponent />)
.exhaustive()
}
</Suspense>
</div>
)
}
2. 이미지 지연 로딩
웹 페이지의 성능을 향상시키기 위해 이미지를 지연 로딩하는 것은 매우 효과적인 기법입니다.
const LazyImage = ({ src, alt, placeholder }) => {
const [isLoaded, setIsLoaded] = useState(false);
const imgRef = useRef(null);
useEffect(() => {
// IntersectionObserver를 사용하여 이미지가 뷰포트에 들어올 때만 로드
const observer = new IntersectionObserver(entries => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
if (imgRef.current) {
imgRef.current.src = src;
setIsLoaded(true);
observer.disconnect();
}
}
});
});
if (imgRef.current) {
observer.observe(imgRef.current);
}
return () => {
observer.disconnect();
};
}, [src])
return (
<div className="image-container">
{!isLoaded && <div className="placeholder">{placeholder}</div>}
<img
ref={imgRef}
alt={alt}
className={`lazy-image ${isLoaded ? 'loaded' : ''}`}
data-src={src} // 실제 src는 나중에 설정
/>
</div>
)
}
이 컴포넌트는 이미지가 뷰포트에 들어올 때만 실제 이미지를 로드 하므로, 페이지 초기 로딩 시간을 단축하고 불필요한 네트워크 요청을 줄일 수 있습니다.
3. 상태의 지연 초기화(Lazy Initialization)
리액트에서 상태의 지연 초기화는 초기 렌더링 성능을 최적화하는 중요한 기법입니다.
특히 초기 상태를 계산하는 비용이 높은 경우 유용합니다.
const ExpensiveComponent = () => {
// 지연 초기화: 함수를 전달하여 초기 렌더링 시에만 실행되도록 함
const [data, setData] = useState(() => {
console.log('Computing initial state...');
// 복잡한 초기 상태 계산
const initialData = [];
for (let i = 0; i < 1000; i++) {
initialData.push({
id: i,
value: Math.random() * 100,
processed: someExpensiveCalculation(i)
});
}
return initialData;
});
return (
<div>
<h2>데이터 항목 수: {data.length}</h2>
<button onClick={() => setData([])}>데이터 지우기</button>
{/* 데이터 렌더링 */}
</div>
);
}
const someExpensiveCalculation = (input: number) => {
// 비용이 높은 계산 시뮬레이션
let result = 0;
for (let i = 0; i < 10000; i++) {
result += Math.sin(input * i);
}
return result;
}
useState
에 함수를 전달하면, 그 함수는 컴포넌트의 초기 렌더링 시에만 실행됩니다. 이후 렌더링시에는 이전 상태를 재사용합니다.
이 기법은 초기 상태를 계산하는 비용이 높은 경우 특히 유용합니다.
4. 컨텍스트 값의 지연 평가
리액트 Context API에서도 지연 평가를 활용하여 성능을 최적화 할 수 있습니다.
const ThemeContext = React.createContext({
theme: 'light',
// 지연 평가를 위한 함수 제공
getThemeStyles: () => ({})
});
const ThemeProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
// 메모이제이션된 컨텍스트 값
const themeContextValue = useMemo(() => {
// 스타일 계산 함수를 제공하여 실제 필요할 때만 계산되도록 함
return {
theme,
getThemeStyles: () => {
console.log('Computing theme styles...');
// 테마에 따른 스타일 계산
if (theme === 'light') {
return {
backgroundColor: '#ffffff',
color: '#333333',
// ... 다른 스타일 속성
};
} else {
return {
backgroundColor: '#333333',
color: '#ffffff',
// ... 다른 스타일 속성
};
}
}
};
}, [theme]);
return (
<ThemeContext.Provider value={themeContextValue}>
{children}
</ThemeContext.Provider>
);
}
// 컨텍스트 소비자
const ThemedButton = () => {
const { theme, getThemeStyles } = useContext(ThemeContext);
// 버튼이 렌더링될 때만 스타일 계산
const styles = getThemeStyles();
return (
<button style={styles}>
{theme === 'light' ? '다크 모드로 전환' : '라이트 모드로 전환'}
</button>
);
}
이 패턴은 컨텍스트를 통해 함수를 제공하여, 컴포넌트가 실제로 값이 필요할 때만 계산을 수행하도록 합니다.
결론
엔지니어링에서 지연 평가는 성능 최적화에 매우 유용한 기법입니다. 프론트엔드에서는 초기 로딩 시간 단축, 불필요한 계산 방지, 네트워크 요청 최적화 등 다양한 성능 최적화 기법에 사용될 수 있습니다.
지연 평가 패턴을 적절히 활용하면 사용자 경험을 향상시키고, 애플리케이션의 반응성을 개선할 수 있습니다. 컴포넌트 지연 로딩, 이미지 지연 로딩, 무한 스크롤, 폼 유효성 검사, 상태의 지연 초기화 등 다양한 기법을 상황에 맞게 적용하는 것이 중요합니다.