면접 준비 4

공보경·2025년 6월 23일

면접 준비

목록 보기
4/5

웹기술 면접준비 4 - React/React Native

목차

  1. React vs 다른 라이브러리
  2. React 동작 원리
  3. React useState 사용하는 이유
  4. 컴포넌트의 정의
  5. Redux Store 메모리 관리
  6. React Native Redux Store 관리
  7. React Native 동작 원리
  8. React Native 0.70
  9. React Native 브릿지
  10. React Native New Architecture - TurboModule
  11. React Native 이슈들
  12. React Native 메모리 최적화 경험
  13. 서버 사이드 렌더링 순서

1. React vs 다른 라이브러리

핵심 개념

React는 사용자 인터페이스를 구축하기 위한 JavaScript 라이브러리로, Angular, Vue.js와 함께 주요 프론트엔드 프레임워크/라이브러리 중 하나입니다.

주요 차이점

특징ReactAngularVue.js
타입라이브러리프레임워크프레임워크
학습곡선중간높음낮음
렌더링Virtual DOMReal DOMVirtual DOM
데이터 바인딩단방향양방향양방향/단방향
TypeScript별도 설정기본 지원별도 설정
생태계매우 풍부풍부보통

React의 장점

// 1. 컴포넌트 기반 아키텍처
function Header() {
  return <h1>My App</h1>;
}

// 2. 재사용 가능한 컴포넌트
function Button({ text, onClick }) {
  return <button onClick={onClick}>{text}</button>;
}

// 3. Virtual DOM으로 효율적인 업데이트
// React가 자동으로 최적화된 DOM 업데이트 수행

면접 답변 템플릿 (30초)

"React는 Virtual DOM을 사용하는 라이브러리입니다. Angular는 완전한 프레임워크로 양방향 데이터 바인딩을 제공하지만, React는 단방향 데이터 플로우로 예측 가능한 상태 관리를 제공합니다. Vue.js는 학습 곡선이 낮지만, React는 더 큰 생태계와 높은 유연성을 제공합니다."

참고 자료


2. React 동작 원리

Virtual DOM 개념

Virtual DOM은 실제 DOM의 가상 표현으로, 메모리 상에서 동작하여 성능을 최적화합니다.

React의 렌더링 과정

// 1. JSX 작성
function App() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

// 2. Babel이 JSX를 React.createElement로 변환
React.createElement(
  'div',
  null,
  React.createElement('p', null, `Count: ${count}`),
  React.createElement('button', { onClick: handleClick }, 'Increment')
);

Reconciliation 과정

// 이전 Virtual DOM Tree
{
  type: 'div',
  props: {
    children: [
      { type: 'p', props: { children: 'Count: 0' } }
    ]
  }
}

// 새로운 Virtual DOM Tree  
{
  type: 'div',
  props: {
    children: [
      { type: 'p', props: { children: 'Count: 1' } }
    ]
  }
}

// Diff 알고리즘이 변경사항을 감지하고 최소한의 DOM 업데이트 수행

React Fiber 아키텍처

// React 18의 동시성 기능
import { startTransition } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [list, setList] = useState([]);

  const handleClick = () => {
    // 긴급한 업데이트
    setCount(count + 1);
    
    // 긴급하지 않은 업데이트 (중단 가능)
    startTransition(() => {
      setList(generateLargeList());
    });
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Update</button>
      <List items={list} />
    </div>
  );
}

면접 답변 템플릿 (1분)

"React는 Virtual DOM을 사용하여 효율적인 렌더링을 수행합니다. 상태가 변경되면 새로운 Virtual DOM 트리를 생성하고, Diff 알고리즘을 통해 이전 트리와 비교하여 변경된 부분만 실제 DOM에 반영합니다. React 18에서는 Fiber 아키텍처로 동시성 렌더링을 지원하여 사용자 인터랙션을 차단하지 않고 백그라운드에서 렌더링을 수행할 수 있습니다."

참고 자료


3. React useState 사용하는 이유

함수형 컴포넌트의 상태 관리

// Class Component 시절
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  
  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

// Hooks 이후 (useState 사용)
function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}

useState의 핵심 특징

// 1. 상태 배치(Batching)
function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');

  const handleClick = () => {
    setCount(count + 1);    // 배치됨
    setName('Updated');     // 배치됨
    // React 18에서는 하나의 리렌더링으로 처리
  };
}

// 2. 함수형 업데이트
function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    // 이전 값을 기반으로 업데이트 (안전함)
    setCount(prevCount => prevCount + 1);
  };
}

// 3. 지연 초기화 (Lazy Initial State)
function ExpensiveComponent() {
  const [data, setData] = useState(() => {
    // 컴포넌트 최초 렌더링 시에만 실행
    return computeExpensiveValue();
  });
}

면접 답변 템플릿 (30초)

"useState는 함수형 컴포넌트에서 상태를 관리하기 위한 Hook입니다. 클래스 컴포넌트의 this.state와 달리, 함수형 컴포넌트는 실행될 때마다 지역 변수가 초기화되므로 상태를 유지할 수 없습니다. useState는 React의 렌더링 사이클과 통합되어 상태 변경 시 자동으로 리렌더링을 트리거하고, 상태 배치 처리로 성능을 최적화합니다."

참고 자료


4. 컴포넌트의 정의

React 컴포넌트란?

컴포넌트는 UI를 독립적이고 재사용 가능한 조각으로 나누어 각 조각을 개별적으로 처리할 수 있게 해주는 React의 핵심 개념입니다.

컴포넌트 유형

1. 함수형 컴포넌트 (Functional Component)

// 기본 함수형 컴포넌트
function Welcome(props) {
  return <h1>Hello, {props.name}!</h1>;
}

// Hooks를 사용한 함수형 컴포넌트
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetchUser(userId)
      .then(setUser)
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  
  return (
    <div className="user-profile">
      <img src={user.avatar} alt={user.name} />
      <h2>{user.name}</h2>
      <p>{user.bio}</p>
    </div>
  );
}

2. 컴포넌트 설계 원칙

// 단일 책임 원칙 적용
function UserDashboard({ userId }) {
  return (
    <div className="dashboard">
      <UserProfile userId={userId} />
      <UserPosts userId={userId} />
      <UserComments userId={userId} />
    </div>
  );
}

// 컴포넌트 합성 패턴
function DataFetcher({ url, children }) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(url)
      .then(response => response.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return children({ data, loading });
}

면접 답변 템플릿 (1분)

"React 컴포넌트는 UI를 독립적이고 재사용 가능한 조각으로 나누는 기본 단위입니다. 함수형과 클래스형이 있으며, 현재는 Hooks를 사용한 함수형 컴포넌트가 권장됩니다. 컴포넌트는 Props로 데이터를 받고, State로 내부 상태를 관리하며, 단일 책임 원칙을 따라 설계해야 합니다."

참고 자료


5. Redux Store 메모리 관리

Redux Store의 메모리 위치

Redux store는 JavaScript 힙 메모리에 저장되며, 브라우저의 메인 스레드에서 관리됩니다.

Redux Store 메모리 구조

// Redux Store는 JavaScript 객체로 메모리에 저장
const store = {
  // 현재 애플리케이션 상태 (메모리에 상주)
  state: {
    user: { id: 1, name: 'John' },
    posts: [...],
    ui: { loading: false }
  },
  
  // 리듀서 함수들
  reducers: {
    userReducer,
    postsReducer,
    uiReducer
  },
  
  // 구독자 목록
  listeners: [component1, component2, ...]
};

브라우저 메모리에서의 위치

// 브라우저 JavaScript Engine 메모리 구조
/*
JavaScript Heap Memory
├── Redux Store State (Global Variable)
│   ├── user: { ... }
│   ├── posts: [ ... ]
│   └── ui: { ... }
├── React Component Instances
├── Event Listeners
└── Closures
*/

Redux Store 메모리 최적화

1. 상태 정규화 (Normalization)

// 비효율적인 구조 (중복 데이터)
const badState = {
  posts: [
    { id: 1, title: 'Post 1', author: { id: 1, name: 'John' } },
    { id: 2, title: 'Post 2', author: { id: 1, name: 'John' } } // 중복!
  ]
};

// 정규화된 구조 (메모리 효율적)
const goodState = {
  entities: {
    users: {
      1: { id: 1, name: 'John' }
    },
    posts: {
      1: { id: 1, title: 'Post 1', authorId: 1 },
      2: { id: 2, title: 'Post 2', authorId: 1 }
    }
  },
  ui: {
    postIds: [1, 2]
  }
};

2. Redux Persist와 localStorage 활용

import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';

const persistConfig = {
  key: 'root',
  storage,
  // 메모리 사용량이 큰 데이터는 제외
  blacklist: ['largeDataSets', 'temporaryUI']
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

면접 답변 템플릿 (1분)

"Redux Store는 브라우저의 JavaScript 힙 메모리에 전역 객체로 저장됩니다. 메인 스레드에서 동기적으로 동작하며, 모든 컴포넌트가 접근할 수 있습니다. 메모리 효율성을 위해 상태 정규화, Redux Persist를 통한 localStorage 활용, 대용량 데이터의 가상화 처리 등을 사용합니다."

참고 자료


6. React Native Redux Store 관리

React Native에서의 Redux Store 위치

React Native에서 Redux Store는 JavaScript Thread의 메모리에 저장되며, iOS/Android 네이티브 코드와는 분리된 공간에서 관리됩니다.

React Native 메모리 구조

/*
React Native App Memory Architecture:

Native Heap (iOS/Android)
├── UI Thread (Main Thread)
├── Background Threads
└── JavaScript Context
    ├── JavaScript Heap
    │   ├── Redux Store ← 여기에 저장
    │   ├── React Components
    │   └── Business Logic
    └── Bridge Communication
*/

// React Native Redux 설정
import { createStore } from 'redux';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { persistStore, persistReducer } from 'redux-persist';

const persistConfig = {
  key: 'root',
  storage: AsyncStorage, // Native 저장소 활용
  whitelist: ['user', 'settings'] // 지속할 상태 선택
};

const persistedReducer = persistReducer(persistConfig, rootReducer);
const store = createStore(persistedReducer);
const persistor = persistStore(store);

React Native 특화 메모리 관리

1. AsyncStorage와의 연동

// Redux Persist + AsyncStorage
import AsyncStorage from '@react-native-async-storage/async-storage';

// 메모리와 영구 저장소 동기화
const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
  transforms: [
    // 대용량 데이터 압축
    createTransform(
      (inboundState) => JSON.stringify(inboundState),
      (outboundState) => JSON.parse(outboundState),
      { whitelist: ['largeDataSets'] }
    )
  ]
};

2. 앱 상태 기반 메모리 최적화

import { AppState } from 'react-native';

// 앱이 백그라운드로 갈 때 메모리 정리
class MemoryManager {
  constructor(store) {
    this.store = store;
    this.setupAppStateListener();
  }

  setupAppStateListener() {
    AppState.addEventListener('change', (nextAppState) => {
      if (nextAppState === 'background') {
        this.cleanupMemory();
      } else if (nextAppState === 'active') {
        this.restoreMemory();
      }
    });
  }

  cleanupMemory() {
    // 임시 데이터 정리
    this.store.dispatch({
      type: 'CLEANUP_TEMPORARY_DATA'
    });
  }
}

면접 답변 템플릿 (30초)

"React Native에서 Redux Store는 JavaScript Thread의 힙 메모리에 저장됩니다. 네이티브 코드와는 분리된 공간에서 동작하며, AsyncStorage를 통해 영구 저장소와 연동할 수 있습니다. 앱이 백그라운드로 갈 때 메모리 정리, 디바이스 성능에 따른 동적 캐시 크기 조정 등 모바일 환경에 특화된 최적화가 필요합니다."

참고 자료


7. React Native 동작 원리

React Native 아키텍처 개요

React Native는 JavaScript로 작성된 코드를 iOS/Android 네이티브 앱으로 변환하는 프레임워크입니다.

핵심 구성 요소

1. Three Thread Architecture

/*
React Native 3-Thread 구조:

1. Main Thread (UI Thread)
   ├── 네이티브 UI 렌더링
   ├── 사용자 인터랙션 처리
   └── 애니메이션 실행

2. JavaScript Thread
   ├── React 컴포넌트 실행
   ├── 비즈니스 로직 처리
   └── Redux/State 관리

3. Shadow Thread (Background)
   ├── Layout 계산 (Yoga Layout Engine)
   ├── UI 변경사항 계산
   └── Native UI와 동기화
*/

2. Bridge Communication

// JavaScript → Native 통신
import { NativeModules } from 'react-native';

// JavaScript에서 네이티브 모듈 호출
NativeModules.LocationModule.getCurrentLocation()
  .then(location => {
    console.log('Current location:', location);
  });

// Native → JavaScript 통신
import { NativeEventEmitter } from 'react-native';

const locationEmitter = new NativeEventEmitter(NativeModules.LocationModule);
locationEmitter.addListener('LocationUpdate', (event) => {
  console.log('Location updated:', event);
});

React Native 렌더링 과정

1. 컴포넌트에서 네이티브 UI까지

// 1. JSX 컴포넌트 작성
function MyComponent() {
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello World</Text>
      <TouchableOpacity onPress={handlePress}>
        <Text>Press me</Text>
      </TouchableOpacity>
    </View>
  );
}

// 2. React Native 처리 과정
/*
JSX → React Element → Shadow Tree → Native View

1. JSX가 React.createElement() 호출로 변환
2. Virtual DOM과 유사한 Shadow Tree 생성
3. Yoga Layout Engine이 레이아웃 계산
4. Bridge를 통해 네이티브 UI 컴포넌트 생성
   - <View> → UIView (iOS) / ViewGroup (Android)
   - <Text> → UILabel (iOS) / TextView (Android)
   - <TouchableOpacity> → UIButton (iOS) / Button (Android)
*/

2. Metro Bundler와 JavaScript 번들링

// metro.config.js
module.exports = {
  resolver: {
    alias: {
      '@components': './src/components',
      '@utils': './src/utils',
    },
  },
};

// JavaScript 번들 생성 과정
/*
1. Metro가 entry point (index.js)부터 dependency graph 생성
2. Babel로 JSX와 ES6+ 코드를 ES5로 변환
3. 모든 모듈을 하나의 번들 파일로 결합
4. 네이티브 앱이 번들을 로드하여 JavaScript Context 생성
*/

면접 답변 템플릿 (1분)

"React Native는 3개의 스레드로 동작합니다: UI 스레드(네이티브 UI), JavaScript 스레드(React 로직), Shadow 스레드(레이아웃 계산). JSX로 작성된 컴포넌트는 Shadow Tree를 거쳐 Yoga Layout Engine이 레이아웃을 계산하고, Bridge를 통해 네이티브 UI 컴포넌트로 변환됩니다. Metro Bundler가 JavaScript 코드를 번들링하여 네이티브 앱에서 실행합니다."

참고 자료


8. React Native 0.70

React Native 0.70 주요 업데이트

1. Hermes 엔진 기본 활성화

// android/app/build.gradle
project.ext.react = [
  enableHermes: true, // 기본값이 true로 변경
]

// Hermes 엔진의 장점
/*
1. 앱 시작 시간 개선 (최대 50% 단축)
2. 메모리 사용량 감소 (최대 30% 절약)
3. 앱 크기 감소
4. JavaScript 실행 성능 향상
*/

2. New Architecture 기본 지원

// New Architecture 활성화 설정
// android/gradle.properties
newArchEnabled=true

// iOS 설정 - ios/Podfile
use_frameworks! :linkage => :static
$RNNewArchEnabled = true

// React 18 Concurrent Features 지원
import { startTransition } from 'react';

function SearchComponent() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);

  const handleSearch = (newQuery) => {
    setQuery(newQuery); // 즉시 업데이트
    
    startTransition(() => {
      searchAPI(newQuery).then(setResults);
    });
  };
}

3. 개선된 Metro Bundler

// metro.config.js - React Native 0.70 최적화
module.exports = {
  transformer: {
    getTransformOptions: async () => ({
      transform: {
        experimentalImportSupport: false,
        inlineRequires: true, // 성능 개선
      },
    }),
  },
  serializer: {
    createModuleIdFactory: () => {
      return (path) => {
        return path.replace(/^.*\/node_modules\//, '');
      };
    },
  },
};

면접 답변 템플릿 (30초)

"React Native 0.70의 주요 특징은 Hermes 엔진 기본 활성화로 앱 시작 시간이 50% 개선되고, New Architecture(Fabric, TurboModules) 기본 지원으로 성능이 크게 향상되었습니다. React 18의 Concurrent Features 지원으로 사용자 인터랙션이 더 부드러워졌습니다."

참고 자료


9. React Native 브릿지

React Native Bridge 개념

Bridge는 JavaScript 코드와 네이티브 플랫폼(iOS/Android) 간의 통신을 담당하는 핵심 구조입니다.

Bridge 동작 원리

1. 비동기 직렬화 통신

/*
Bridge Communication Flow:

JavaScript Thread          Bridge          Native Thread
     |                       |                    |
1. Method Call         →  Serialize      →   Parse & Execute
2. Data Preparation    →  JSON Message   →   Native Function
3. Callback Setup      ←  Serialize      ←   Return Value
4. Result Processing   ←  JSON Response  ←   Callback
*/

// JavaScript에서 네이티브 모듈 호출
import { NativeModules } from 'react-native';

NativeModules.CameraModule.takePicture({
  quality: 0.8,
  format: 'jpeg'
})
.then(result => {
  console.log('Photo saved:', result.uri);
})
.catch(error => {
  console.error('Camera error:', error);
});

2. Bridge 성능 이슈와 해결책

// 문제: 큰 데이터의 반복적 전송
const badExample = () => {
  largeArray.forEach(item => {
    NativeModules.StorageModule.saveItem(item); // 매번 직렬화
  });
};

// 해결: 배치 처리
const goodExample = () => {
  NativeModules.StorageModule.saveBatch(largeArray); // 한 번에 처리
};

// Native Driver로 Bridge 우회
import { Animated } from 'react-native';

Animated.timing(fadeValue, {
  toValue: 1,
  duration: 1000,
  useNativeDriver: true // Bridge 우회
}).start();

Bridge의 한계와 New Architecture

/*
기존 Bridge의 한계:
1. 비동기 직렬화로 인한 지연
2. JSON 변환 오버헤드
3. 단방향 메시지 큐
4. 메모리 복사 비용

New Architecture (JSI + Fabric + TurboModules)의 해결책:
1. 동기적 직접 호출 가능
2. JSON 없이 객체 직접 전달
3. 양방향 실시간 통신
4. 메모리 공유 가능
*/

면접 답변 템플릿 (1분)

"React Native Bridge는 JavaScript와 네이티브 코드 간의 비동기 통신을 담당합니다. JSON 직렬화를 통해 메시지를 전달하며, 주요 성능 이슈는 직렬화 오버헤드와 병목 현상입니다. 배치 처리와 Native Driver 사용으로 완화할 수 있으며, New Architecture의 JSI는 Bridge를 우회하여 동기적 직접 호출을 가능하게 합니다."

참고 자료


10. React Native New Architecture - TurboModule

New Architecture 개요

React Native New Architecture는 성능과 개발자 경험을 개선하기 위한 근본적인 아키텍처 재설계입니다.

핵심 구성 요소

1. JSI (JavaScript Interface)

// JSI를 통한 직접 호출
// 기존 Bridge 방식
NativeModules.LocationModule.getCurrentLocation()
  .then(location => console.log(location)); // 비동기, JSON 직렬화

// JSI 방식 (동기 호출 가능)
const location = global.LocationModule.getCurrentLocationSync(); // 즉시 반환
console.log(location); // 바로 사용 가능

2. TurboModules

// TurboModule 정의 (TypeScript)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface LocationSpec extends TurboModule {
  getCurrentLocation(): Promise<{
    latitude: number;
    longitude: number;
  }>;
  
  // 동기 메서드도 지원
  getCurrentLocationSync(): {
    latitude: number;
    longitude: number;
  };
}

export default TurboModuleRegistry.getEnforcing<LocationSpec>('LocationModule');

3. Fabric Renderer

// Fabric을 통한 네이티브 컴포넌트
// 기존 방식: Bridge를 통한 비동기 렌더링
// Fabric: C++에서 직접 Shadow Tree 조작

function OptimizedComponent() {
  // Fabric Renderer가 더 효율적으로 처리
  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello Fabric!</Text>
      <CustomNativeComponent />
    </View>
  );
}

// 동시성 렌더링 지원
import { startTransition } from 'react';

function App() {
  const [urgent, setUrgent] = useState('');
  const [deferred, setDeferred] = useState([]);

  const handleInput = (text) => {
    setUrgent(text); // 긴급 업데이트
    
    startTransition(() => {
      setDeferred(searchResults(text)); // 지연 가능한 업데이트
    });
  };
}

New Architecture의 성능 개선

1. 메모리 공유

// 기존: JSON 직렬화로 메모리 복사
const data = { /* large object */ };
NativeModules.ProcessData.process(JSON.stringify(data)); // 메모리 복사

// New Architecture: 직접 객체 전달
const data = { /* large object */ };
TurboModules.ProcessData.process(data); // 메모리 공유

2. 타입 안전성

// TurboModule 인터페이스로 타입 안전성 보장
interface CameraSpec extends TurboModule {
  takePicture(options: {
    quality: number;
    format: 'jpeg' | 'png';
  }): Promise<{ uri: string; size: number }>;
}

// 컴파일 타임에 타입 체크
const camera = TurboModuleRegistry.get<CameraSpec>('Camera');
const result = await camera.takePicture({
  quality: 0.8,
  format: 'jpeg' // 타입 체크됨
});

마이그레이션 가이드

// New Architecture 활성화
// android/gradle.properties
newArchEnabled=true

// iOS - ios/Podfile
use_frameworks! :linkage => :static
$RNNewArchEnabled = true

// 기존 네이티브 모듈을 TurboModule로 변환
// 1. TypeScript 인터페이스 정의
// 2. Codegen으로 네이티브 코드 생성
// 3. 네이티브 구현체 업데이트

면접 답변 템플릿 (1분)

"React Native New Architecture는 JSI, TurboModules, Fabric Renderer로 구성됩니다. JSI는 Bridge 없이 JavaScript와 네이티브 간 직접 통신을 가능하게 하고, TurboModules는 타입 안전한 네이티브 모듈 인터페이스를 제공합니다. Fabric Renderer는 C++에서 직접 Shadow Tree를 조작하여 렌더링 성능을 크게 개선합니다. 동기 호출, 메모리 공유, 타입 안전성이 주요 장점입니다."

참고 자료


11. React Native 이슈들

React Native 주요 이슈와 해결 방안

1. 성능 이슈

// 문제: Bridge 병목 현상
// 많은 데이터를 자주 전송할 때 성능 저하
const badExample = () => {
  data.forEach(item => {
    NativeModules.Analytics.trackEvent(item); // 개별 호출
  });
};

// 해결: 배치 처리
const goodExample = () => {
  NativeModules.Analytics.trackEvents(data); // 배치 호출
};

// 메모리 누수 문제와 해결
import { useEffect } from 'react';

function LeakyComponent() {
  useEffect(() => {
    const timer = setInterval(() => {
      // 메모리 누수 발생 가능
    }, 1000);

    // 클린업 누락으로 인한 메모리 누수
  }, []);
}

function FixedComponent() {
  useEffect(() => {
    const timer = setInterval(() => {
      // 작업 수행
    }, 1000);

    return () => clearInterval(timer); // 클린업 필수
  }, []);
}

2. 플랫폼 호환성 이슈

// iOS와 Android 차이점 처리
import { Platform } from 'react-native';

const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.OS === 'ios' ? 20 : 0, // 상태바 높이 차이
    elevation: Platform.OS === 'android' ? 5 : 0, // Android 그림자
    shadowOpacity: Platform.OS === 'ios' ? 0.3 : 0, // iOS 그림자
  }
});

// 플랫폼별 컴포넌트 분리
import MyComponentIOS from './MyComponent.ios';
import MyComponentAndroid from './MyComponent.android';

const MyComponent = Platform.select({
  ios: MyComponentIOS,
  android: MyComponentAndroid,
});

3. 라이브러리 호환성 문제

// React Native 버전별 호환성 체크
import { NativeModules, Platform } from 'react-native';

const checkCompatibility = () => {
  if (Platform.constants.reactNativeVersion.major >= 0.70) {
    // New Architecture 사용
    return 'newArch';
  } else {
    // Legacy Bridge 사용
    return 'legacy';
  }
};

// 조건부 라이브러리 로딩
const LocationModule = Platform.select({
  ios: () => require('react-native-location-ios'),
  android: () => require('react-native-location-android'),
})();

4. 디버깅 어려움

// Flipper를 통한 디버깅 설정
// react-native.config.js
module.exports = {
  dependencies: {
    'react-native-flipper': {
      platforms: {
        android: {
          sourceDir: '../node_modules/react-native-flipper/android',
          packageImportPath: 'com.facebook.flipper.reactnative',
        },
      },
    },
  },
};

// Redux DevTools 연동
import { createStore } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(
  rootReducer,
  composeWithDevTools()
);

면접 답변 템플릿 (1분)

"React Native의 주요 이슈는 Bridge 병목 현상, 플랫폼별 차이점, 라이브러리 호환성, 디버깅 어려움입니다. Bridge 병목은 배치 처리와 Native Driver로 해결하고, 플랫폼 차이는 Platform API로 조건부 처리합니다. 라이브러리 호환성은 버전 체크와 조건부 로딩으로, 디버깅은 Flipper와 Redux DevTools로 해결할 수 있습니다."

참고 자료


12. React Native 메모리 최적화 경험

React Native 메모리 최적화 전략

1. 이미지 최적화

// 문제: 고해상도 이미지로 인한 메모리 부족
import { Image, Dimensions } from 'react-native';

const { width } = Dimensions.get('window');

// 나쁜 예: 원본 이미지 로드
<Image 
  source={{uri: 'https://example.com/large-image.jpg'}} 
  style={{width: 100, height: 100}} 
/>

// 좋은 예: 적절한 크기의 이미지 로드
import FastImage from 'react-native-fast-image';

<FastImage
  source={{
    uri: 'https://example.com/optimized-image.jpg',
    priority: FastImage.priority.normal,
  }}
  style={{width: 100, height: 100}}
  resizeMode={FastImage.resizeMode.cover}
/>

// 이미지 캐시 관리
const clearImageCache = () => {
  FastImage.clearMemoryCache();
  FastImage.clearDiskCache();
};

2. 리스트 가상화

// 문제: 대용량 리스트로 인한 메모리 과사용
import { FlatList } from 'react-native';

// 최적화된 FlatList 사용
function OptimizedList({ data }) {
  const renderItem = useCallback(({ item }) => (
    <ListItem item={item} />
  ), []);

  const getItemLayout = useCallback((data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  }), []);

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
      getItemLayout={getItemLayout}
      removeClippedSubviews={true} // 화면 밖 뷰 제거
      maxToRenderPerBatch={10}     // 배치당 렌더링 수 제한
      windowSize={10}              // 유지할 아이템 창 크기
      initialNumToRender={10}      // 초기 렌더링 수
    />
  );
}

3. 메모리 누수 방지

// useEffect 클린업
import { useEffect, useRef } from 'react';

function Component() {
  const timerRef = useRef(null);
  const subscriptionRef = useRef(null);

  useEffect(() => {
    // 타이머 설정
    timerRef.current = setInterval(() => {
      // 작업 수행
    }, 1000);

    // 이벤트 구독
    subscriptionRef.current = EventEmitter.addListener('event', handler);

    return () => {
      // 클린업 - 메모리 누수 방지
      if (timerRef.current) {
        clearInterval(timerRef.current);
      }
      if (subscriptionRef.current) {
        subscriptionRef.current.remove();
      }
    };
  }, []);
}

// React.memo와 useMemo 사용
const ExpensiveComponent = React.memo(({ data }) => {
  const expensiveValue = useMemo(() => {
    return data.reduce((acc, item) => acc + item.value, 0);
  }, [data]);

  return <Text>{expensiveValue}</Text>;
});

4. 앱 상태 기반 메모리 관리

import { AppState } from 'react-native';

class MemoryManager {
  constructor() {
    this.appStateSubscription = null;
    this.setupAppStateListener();
  }

  setupAppStateListener() {
    this.appStateSubscription = AppState.addEventListener(
      'change',
      this.handleAppStateChange
    );
  }

  handleAppStateChange = (nextAppState) => {
    if (nextAppState === 'background') {
      this.freeMemory();
    } else if (nextAppState === 'active') {
      this.restoreMemory();
    }
  };

  freeMemory() {
    // 캐시 정리
    FastImage.clearMemoryCache();
    
    // 임시 데이터 제거
    global.tempCache = null;
    
    // 불필요한 리스너 해제
    this.pauseLocationTracking();
  }

  restoreMemory() {
    // 필요한 데이터 복원
    this.resumeLocationTracking();
  }

  cleanup() {
    if (this.appStateSubscription) {
      this.appStateSubscription.remove();
    }
  }
}

5. 메모리 모니터링

// 메모리 사용량 추적
import { DevSettings } from 'react-native';

const monitorMemory = () => {
  if (__DEV__) {
    setInterval(() => {
      if (global.performance && global.performance.memory) {
        console.log('Memory Usage:', {
          used: `${Math.round(global.performance.memory.usedJSHeapSize / 1048576)} MB`,
          total: `${Math.round(global.performance.memory.totalJSHeapSize / 1048576)} MB`,
          limit: `${Math.round(global.performance.memory.jsHeapSizeLimit / 1048576)} MB`
        });
      }
    }, 5000);
  }
};

// 메모리 경고 처리 (iOS)
import { DeviceEventEmitter } from 'react-native';

DeviceEventEmitter.addListener('memoryWarning', () => {
  console.warn('Memory warning received');
  // 메모리 정리 작업 수행
  FastImage.clearMemoryCache();
});

면접 답변 템플릿 (1분)

"React Native 메모리 최적화는 이미지 캐싱(FastImage), 리스트 가상화(FlatList 최적화), 메모리 누수 방지(useEffect 클린업), 앱 상태 기반 관리로 구성됩니다. 실제 경험으로는 대용량 이미지 리스트에서 FastImage와 removeClippedSubviews를 사용해 메모리 사용량을 70% 감소시켰고, AppState 이벤트로 백그라운드 진입 시 불필요한 캐시를 정리해 OOM 크래시를 방지했습니다."

참고 자료


13. 서버 사이드 렌더링 순서

React SSR 동작 원리

서버 사이드 렌더링(SSR)은 서버에서 React 컴포넌트를 HTML로 렌더링하여 클라이언트에 전송하는 기술입니다.

SSR 렌더링 순서

1. 서버에서의 처리 과정

// 1. 서버에서 요청 받기
import express from 'express';
import { renderToString } from 'react-dom/server';
import App from './App';

const server = express();

server.get('*', async (req, res) => {
  try {
    // 2. 필요한 데이터 미리 로드
    const initialData = await fetchInitialData(req.url);
    
    // 3. React 컴포넌트를 HTML 문자열로 렌더링
    const appHTML = renderToString(
      <App initialData={initialData} />
    );
    
    // 4. HTML 템플릿에 렌더링된 컴포넌트 삽입
    const html = `
      <!DOCTYPE html>
      <html>
        <head>
          <title>My App</title>
          <link rel="stylesheet" href="/styles.css" />
        </head>
        <body>
          <div id="root">${appHTML}</div>
          <script>
            window.__INITIAL_DATA__ = ${JSON.stringify(initialData)};
          </script>
          <script src="/bundle.js"></script>
        </body>
      </html>
    `;
    
    // 5. 완성된 HTML을 클라이언트에 전송
    res.send(html);
  } catch (error) {
    res.status(500).send('Server Error');
  }
});

2. 클라이언트에서의 Hydration

// 클라이언트 진입점 (index.js)
import { hydrateRoot } from 'react-dom/client';
import App from './App';

// 1. 서버에서 전달받은 초기 데이터 사용
const initialData = window.__INITIAL_DATA__;

// 2. Hydration 수행 - 서버 렌더링된 HTML에 이벤트 리스너 등 연결
const container = document.getElementById('root');
hydrateRoot(container, <App initialData={initialData} />);

// 3. 이후 클라이언트 사이드에서 동작
delete window.__INITIAL_DATA__; // 메모리 정리

3. 데이터 Fetching 전략

// App.js - 서버와 클라이언트 모두에서 동작
import { useEffect, useState } from 'react';

function App({ initialData }) {
  const [data, setData] = useState(initialData || null);
  const [loading, setLoading] = useState(!initialData);

  useEffect(() => {
    // 서버에서 데이터를 받았으면 추가 요청 안함
    if (initialData) {
      return;
    }

    // 클라이언트에서만 데이터 페칭
    fetchData()
      .then(setData)
      .finally(() => setLoading(false));
  }, [initialData]);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <h1>{data.title}</h1>
      <p>{data.content}</p>
    </div>
  );
}

// 서버에서 사용할 데이터 페칭 함수
export async function fetchInitialData(url) {
  // URL에 따라 필요한 데이터 결정
  const route = parseRoute(url);
  
  switch (route.name) {
    case 'user':
      return await fetchUser(route.params.id);
    case 'posts':
      return await fetchPosts(route.params.page);
    default:
      return await fetchHomeData();
  }
}

4. Next.js에서의 SSR

// pages/user/[id].js
export default function UserPage({ user }) {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}

// 서버에서 실행되는 함수
export async function getServerSideProps(context) {
  const { id } = context.params;
  
  try {
    // 1. 서버에서 데이터 페칭
    const user = await fetchUser(id);
    
    // 2. props로 컴포넌트에 전달
    return {
      props: {
        user,
      },
    };
  } catch (error) {
    // 3. 에러 처리
    return {
      notFound: true,
    };
  }
}

// 또는 Static Generation (SSG)
export async function getStaticProps() {
  const posts = await fetchPosts();
  
  return {
    props: {
      posts,
    },
    revalidate: 60, // 60초마다 재생성
  };
}

SSR vs CSR vs SSG 비교

/*
렌더링 방식 비교:

1. Server-Side Rendering (SSR)
   - 매 요청마다 서버에서 HTML 생성
   - 초기 로딩 빠름, SEO 좋음
   - 서버 부하 높음

2. Client-Side Rendering (CSR)
   - 브라우저에서 JavaScript로 렌더링
   - 초기 로딩 느림, SEO 어려움
   - 서버 부하 낮음

3. Static Site Generation (SSG)
   - 빌드 타임에 HTML 미리 생성
   - 가장 빠른 로딩, SEO 좋음
   - 동적 컨텐츠 한계
*/

// Hybrid 접근법 (Next.js)
export default function ProductPage({ product }) {
  const [reviews, setReviews] = useState([]);

  useEffect(() => {
    // 제품 정보는 SSR, 리뷰는 CSR
    fetchReviews(product.id).then(setReviews);
  }, [product.id]);

  return (
    <div>
      {/* SSR로 렌더링된 부분 */}
      <h1>{product.name}</h1>
      <p>{product.price}</p>
      
      {/* CSR로 렌더링된 부분 */}
      <ReviewList reviews={reviews} />
    </div>
  );
}

면접 답변 템플릿 (1분)

"React SSR은 서버에서 컴포넌트를 HTML로 렌더링하여 전송하는 과정입니다. 순서는: 1) 서버가 요청 수신, 2) 필요한 데이터 페칭, 3) renderToString으로 HTML 생성, 4) 초기 데이터와 함께 HTML 전송, 5) 클라이언트에서 hydration 수행입니다. 장점은 빠른 초기 로딩과 SEO 개선이고, 단점은 서버 부하와 복잡성 증가입니다. Next.js는 SSR/SSG/CSR을 페이지별로 선택할 수 있어 유연합니다."

참고 자료


참고 자료 종합

React 공식 문서

React Native 공식 문서

추가 학습 자료


이 문서는 React/React Native 면접 준비를 위한 실무 중심의 기술 가이드입니다. 각 섹션의 코드 예시와 개념 설명을 통해 실제 면접에서 활용할 수 있는 답변을 준비할 수 있습니다.

profile
hi im gggongbo

0개의 댓글