12월 21, 2025
48 분 읽기

React Native 시니어 면접 질문과 답변

interview
career-advice
job-search
React Native 시니어 면접 질문과 답변
Milad Bonakdar

Milad Bonakdar

작성자

시니어 React Native 면접을 위해 아키텍처, FlatList 성능, 네이티브 모듈, 상태 관리, 테스트, 운영 트레이드오프를 준비하세요.


소개

시니어 React Native 면접은 문법만 묻지 않습니다. 앱 아키텍처, 성능 프로파일링, 네이티브 통합, 상태와 데이터 경계, 테스트 전략, 릴리스 품질, 실제 제품 제약 안에서 트레이드오프를 설명하는 능력을 봅니다.

이 가이드는 시니어 수준의 간결한 답변을 연습하기 위한 자료입니다. 먼저 어떤 결정을 내릴지 말하고, 그 이유와 위험, 대안을 설명하세요. 예를 들어 FlatList는 단순히 최적화할 수 있다고 말하지 말고, 빈 영역, 렌더링 배치, 메모이즈한 row, 안정적인 key, 고정 높이 또는 무거운 이미지가 진짜 원인일 때의 설계 변경까지 설명해야 합니다.


고급 React & Hooks (5개 질문)

1. useMemouseCallback에 대해 설명하십시오. 언제 사용해야 합니까?

답변: 두 훅 모두 값/함수를 메모이제이션하여 성능을 최적화합니다.

  • useMemo: 계산된 값(비용이 많이 드는 계산)을 메모이제이션합니다.
  • useCallback: 함수 참조를 메모이제이션합니다(재생성 방지).
  • 언제 사용: 성능 문제가 있을 때만 사용하십시오. 시기상조의 최적화는 코드를 읽기 어렵게 만들 수 있습니다.
import { useMemo, useCallback, useState } from 'react';

function ExpensiveComponent({ items, onItemClick }) {
  // useMemo - 비용이 많이 드는 계산을 메모이제이션합니다.
  const sortedItems = useMemo(() => {
    console.log('Sorting items...');
    return items.sort((a, b) => a.price - b.price);
  }, [items]); // items가 변경될 때만 다시 계산합니다.
  
  // useCallback - 함수를 메모이제이션합니다.
  const handleClick = useCallback((id) => {
    console.log('Item clicked:', id);
    onItemClick(id);
  }, [onItemClick]); // onItemClick이 변경될 때만 다시 만듭니다.
  
  return (
    <FlatList
      data={sortedItems}
      renderItem={({ item }) => (
        <ItemRow item={item} onClick={handleClick} />
      )}
    />
  );
}

// React.memo를 사용하는 자식 컴포넌트
const ItemRow = React.memo(({ item, onClick }) => {
  console.log('Rendering item:', item.id);
  return (
    <TouchableOpacity onPress={() => onClick(item.id)}>
      <Text>{item.name}</Text>
    </TouchableOpacity>
  );
});

희소성: 매우 흔함 난이도: 중간


2. useRef는 무엇이며 어떤 사용 사례가 있습니까?

답변: useRef는 재 렌더링을 유발하지 않고 렌더링 전반에 걸쳐 유지되는 변경 가능한 참조를 만듭니다.

  • 사용 사례:
    • DOM/네이티브 요소에 접근
    • 재 렌더링을 트리거하지 않고 변경 가능한 값 저장
    • 이전 값 유지
    • 타이머/인터벌 저장
import { useRef, useEffect } from 'react';
import { TextInput, Animated } from 'react-native';

function FormComponent() {
  const inputRef = useRef(null);
  const previousValue = useRef('');
  const animatedValue = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    // 마운트 시 입력에 초점을 맞춥니다.
    inputRef.current?.focus();
  }, []);
  
  const handleChange = (text) => {
    console.log('Previous:', previousValue.current);
    console.log('Current:', text);
    previousValue.current = text;
  };
  
  const startAnimation = () => {
    Animated.timing(animatedValue, {
      toValue: 1,
      duration: 300,
      useNativeDriver: true,
    }).start();
  };
  
  return (
    <View>
      <TextInput ref={inputRef} onChangeText={handleChange} />
      <Animated.View style={{ opacity: animatedValue }}>
        <Text>Animated Content</Text>
      </Animated.View>
    </View>
  );
}

// 타이머 예제
function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    
    return () => clearInterval(intervalRef.current);
  }, []);
  
  return <Text>{count}</Text>;
}

희소성: 흔함 난이도: 중간


3. 사용자 정의 훅을 설명하고 언제 만들어야 하는지 설명하십시오.

답변: 사용자 정의 훅은 재사용 가능한 상태 저장 로직을 별도의 함수로 추출합니다.

  • 장점: 코드 재사용, 관심사 분리, 쉬운 테스팅
  • 규칙: "use"로 시작해야 합니다.
// API 가져오기를 위한 사용자 정의 훅
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch(url);
        const json = await response.json();
        setData(json);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

// 폼 처리를 위한 사용자 정의 훅
function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  
  const handleChange = (name, value) => {
    setValues(prev => ({ ...prev, [name]: value }));
  };
  
  const validate = (validationRules) => {
    const newErrors = {};
    Object.keys(validationRules).forEach(field => {
      const rule = validationRules[field];
      if (rule.required && !values[field]) {
        newErrors[field] = `${field} is required`;
      }
    });
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };
  
  return { values, errors, handleChange, validate };
}

// 사용법
function UserProfile() {
  const { data, loading, error } = useFetch('https://api.example.com/user');
  const { values, errors, handleChange, validate } = useForm({
    name: '',
    email: '',
  });
  
  if (loading) return <ActivityIndicator />;
  if (error) return <Text>Error: {error}</Text>;
  
  return <Text>{data.name}</Text>;
}

희소성: 흔함 난이도: 중간


4. React Context는 무엇이며 언제 사용해야 합니까?

답변: Context는 prop drilling 없이 컴포넌트 트리를 통해 데이터를 전달하는 방법을 제공합니다.

  • 사용 사례: 테마, 인증, 언어 설정
  • 주의: 주의해서 사용하지 않으면 불필요한 재 렌더링을 유발할 수 있습니다.
import { createContext, useContext, useState } from 'react';

// Context 생성
const ThemeContext = createContext();
const AuthContext = createContext();

// 테마 제공자
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  
  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };
  
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 인증 제공자
function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  
  const login = async (credentials) => {
    const user = await loginAPI(credentials);
    setUser(user);
  };
  
  const logout = () => {
    setUser(null);
  };
  
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// Context를 위한 사용자 정의 훅
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

function useAuth() {
  return useContext(AuthContext);
}

// 사용법
function App() {
  return (
    <ThemeProvider>
      <AuthProvider>
        <MainApp />
      </AuthProvider>
    </ThemeProvider>
  );
}

function ThemedButton() {
  const { theme, toggleTheme } = useTheme();
  const { user } = useAuth();
  
  return (
    <TouchableOpacity
      style={{ backgroundColor: theme === 'light' ? '#fff' : '#333' }}
      onPress={toggleTheme}
    >
      <Text>{user ? `Hello, ${user.name}` : 'Guest'}</Text>
    </TouchableOpacity>
  );
}

희소성: 매우 흔함 난이도: 중간


5. useEffectuseLayoutEffect의 차이점을 설명하십시오.

답변: 둘 다 부작용을 실행하지만 다른 시간에 실행합니다.

  • useEffect: 렌더링이 화면에 그려진 후 비동기적으로 실행됩니다.
  • useLayoutEffect: 페인트 전에 동기적으로 실행됩니다(시각적 업데이트를 차단).
  • useLayoutEffect 사용 시기: DOM을 측정하거나 시각적 깜박임을 방지해야 할 때
import { useEffect, useLayoutEffect, useRef, useState } from 'react';

function MeasureComponent() {
  const [height, setHeight] = useState(0);
  const elementRef = useRef(null);
  
  // useLayoutEffect - 페인트 전에 측정
  useLayoutEffect(() => {
    if (elementRef.current) {
      const { height } = elementRef.current.measure((x, y, width, height) => {
        setHeight(height);
      });
    }
  }, []);
  
  // useEffect - 페인트 후
  useEffect(() => {
    console.log('Component rendered');
  }, []);
  
  return (
    <View ref={elementRef}>
      <Text>Height: {height}</Text>
    </View>
  );
}

희소성: 중간 난이도: 어려움


상태 관리 (4개 질문)

6. Redux와 핵심 원칙을 설명하십시오.

답변: Redux는 JavaScript 앱을 위한 예측 가능한 상태 컨테이너입니다.

Loading diagram...
  • 핵심 원칙:
    • 단일 진실 공급원(하나의 스토어)
    • 상태는 읽기 전용입니다(변경하려면 액션 디스패치).
    • 순수 함수(리듀서)로 변경
// 액션 타입
const ADD_TODO = 'ADD_TODO';
const TOGGLE_TODO = 'TOGGLE_TODO';

// 액션 생성자
const addTodo = (text) => ({
  type: ADD_TODO,
  payload: { id: Date.now(), text, completed: false },
});

const toggleTodo = (id) => ({
  type: TOGGLE_TODO,
  payload: id,
});

// 리듀서
const todosReducer = (state = [], action) => {
  switch (action.type) {
    case ADD_TODO:
      return [...state, action.payload];
    case TOGGLE_TODO:
      return state.map(todo =>
        todo.id === action.payload
          ? { ...todo, completed: !todo.completed }
          : todo
      );
    default:
      return state;
  }
};

// 스토어
import { createStore } from 'redux';
const store = createStore(todosReducer);

// React Native 컴포넌트
import { useSelector, useDispatch } from 'react-redux';

function TodoList() {
  const todos = useSelector(state => state.todos);
  const dispatch = useDispatch();
  
  return (
    <View>
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => dispatch(toggleTodo(item.id))}>
            <Text style={{ textDecorationLine: item.completed ? 'line-through' : 'none' }}>
              {item.text}
            </Text>
          </TouchableOpacity>
        )}
      />
      <Button title="Add" onPress={() => dispatch(addTodo('New Task'))} />
    </View>
  );
}

희소성: 매우 흔함 난이도: 어려움


7. Redux Toolkit은 무엇이며 Redux를 어떻게 간소화합니까?

답변: Redux Toolkit은 Redux 로직을 작성하는 공식 권장 방법입니다.

  • 장점:
    • 상용구 감소
    • 불변 업데이트를 위한 내장 Immer
    • Redux Thunk 포함
    • 더 나은 TypeScript 지원
import { createSlice, configureStore } from '@reduxjs/toolkit';

// Slice (액션과 리듀서 결합)
const todosSlice = createSlice({
  name: 'todos',
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      // Immer는 "변형" 코드를 허용합니다.
      state.push({
        id: Date.now(),
        text: action.payload,
        completed: false,
      });
    },
    toggleTodo: (state, action) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) {
        todo.completed = !todo.completed;
      }
    },
  },
});

export const { addTodo, toggleTodo } = todosSlice.actions;

// 스토어
const store = configureStore({
  reducer: {
    todos: todosSlice.reducer,
  },
});

// 비동기 썽크
import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchTodos = createAsyncThunk(
  'todos/fetchTodos',
  async () => {
    const response = await fetch('https://api.example.com/todos');
    return response.json();
  }
);

const todosSlice = createSlice({
  name: 'todos',
  initialState: { items: [], loading: false, error: null },
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(fetchTodos.pending, (state) => {
        state.loading = true;
      })
      .addCase(fetchTodos.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload;
      })
      .addCase(fetchTodos.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

희소성: 흔함 난이도: 중간


8. 상태 관리를 위한 Redux의 대안은 무엇입니까?

답변: 여러 상태 관리 솔루션이 존재합니다.

  • Context API + useReducer: 내장, 간단한 앱에 적합
  • MobX: Observable 기반, 상용구 감소
  • Zustand: 최소, 훅 기반
  • Recoil: Facebook의 Atom 기반
  • Jotai: 기본 Atom
// Zustand 예제
import create from 'zustand';

const useStore = create((set) => ({
  todos: [],
  addTodo: (text) => set((state) => ({
    todos: [...state.todos, { id: Date.now(), text, completed: false }],
  })),
  toggleTodo: (id) => set((state) => ({
    todos: state.todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ),
  })),
}));

// 사용법
function TodoList() {
  const { todos, addTodo, toggleTodo } = useStore();
  
  return (
    <View>
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <TouchableOpacity onPress={() => toggleTodo(item.id)}>
            <Text>{item.text}</Text>
          </TouchableOpacity>
        )}
      />
    </View>
  );
}

희소성: 흔함 난이도: 중간


9. Redux에서 부작용을 어떻게 처리합니까?

답변: 비동기 작업을 위해 미들웨어를 사용하십시오.

  • Redux Thunk: 함수를 반환하는 함수
  • Redux Saga: 생성기 기반, 더 강력함
  • Redux Observable: RxJS 기반
// Redux Thunk
const fetchUser = (userId) => async (dispatch) => {
  dispatch({ type: 'FETCH_USER_REQUEST' });
  
  try {
    const response = await fetch(`https://api.example.com/users/${userId}`);
    const user = await response.json();
    dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
  } catch (error) {
    dispatch({ type: 'FETCH_USER_FAILURE', payload: error.message });
  }
};

// 사용법
dispatch(fetchUser(123));

// Redux Saga
import { call, put, takeEvery } from 'redux-saga/effects';

function* fetchUserSaga(action) {
  try {
    const user = yield call(fetch, `https://api.example.com/users/${action.payload}`);
    const data = yield call([user, 'json']);
    yield put({ type: 'FETCH_USER_SUCCESS', payload: data });
  } catch (error) {
    yield put({ type: 'FETCH_USER_FAILURE', payload: error.message });
  }
}

function* watchFetchUser() {
  yield takeEvery('FETCH_USER_REQUEST', fetchUserSaga);
}

희소성: 흔함 난이도: 어려움


성능 최적화 (5개 질문)

10. FlatList 성능을 어떻게 최적화합니까?

답변: 여러 전략으로 FlatList 스크롤을 개선할 수 있습니다.

  1. keyExtractor 사용: 고유한 키 제공
  2. getItemLayout: 고정 높이 항목에 대한 측정 건너뛰기
  3. removeClippedSubviews: 화면 밖 뷰 언마운트(Android)
  4. maxToRenderPerBatch: 배치 크기 제어
  5. windowSize: 렌더링된 창 제어
  6. initialNumToRender: 초기에 렌더링할 항목
  7. renderItem 메모이제이션: 불필요한 재 렌더링 방지
import React, { memo, useCallback } from 'react';

const ITEM_HEIGHT = 80;

// 메모이제이션된 항목 컴포넌트
const ListItem = memo(({ item, onPress }) => {
  return (
    <TouchableOpacity onPress={() => onPress(item.id)} style={{ height: ITEM_HEIGHT }}>
      <Text>{item.name}</Text>
    </TouchableOpacity>
  );
});

function OptimizedList({ data }) {
  const handlePress = useCallback((id) => {
    console.log('Pressed:', id);
  }, []);
  
  const renderItem = useCallback(({ item }) => (
    <ListItem item={item} onPress={handlePress} />
  ), [handlePress]);
  
  const keyExtractor = useCallback((item) => item.id.toString(), []);
  
  const getItemLayout = useCallback((data, index) => ({
    length: ITEM_HEIGHT,
    offset: ITEM_HEIGHT * index,
    index,
  }), []);
  
  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={keyExtractor}
      getItemLayout={getItemLayout}
      removeClippedSubviews={true}
      maxToRenderPerBatch={10}
      updateCellsBatchingPeriod={50}
      initialNumToRender={10}
      windowSize={5}
    />
  );
}

희소성: 매우 흔함 난이도: 중간


11. React Native 브리지는 무엇이며 성능에 어떤 영향을 미칩니까?

답변: 브리지는 JavaScript와 네이티브 코드 간의 통신 계층입니다.

  • 작동 방식:
    • JavaScript는 별도의 스레드에서 실행됩니다.
    • 네이티브 모듈은 네이티브 스레드에서 실행됩니다.
    • 브리지는 그 사이의 데이터를 직렬화합니다(JSON).
  • 성능 영향:
    • 브리지 통신은 비동기적입니다.
    • 직렬화 오버헤드
    • 빈번한 통신으로 인해 병목 현상이 발생할 수 있습니다.
  • 솔루션:
    • 브리지 교차 최소화
    • 배치 작업
    • 네이티브 애니메이션 사용(브리지 우회)
    • 새 아키텍처(JSI)는 브리지를 제거합니다.
// 나쁨 - 빈번한 브리지 교차
const BadAnimation = () => {
  const [position, setPosition] = useState(0);
  
  useEffect(() => {
    const interval = setInterval(() => {
      setPosition(p => p + 1); // 모든 프레임마다 브리지 교차!
    }, 16);
    return () => clearInterval(interval);
  }, []);
  
  return <View style={{ transform: [{ translateX: position }] }} />;
};

// 좋음 - 네이티브 애니메이션(브리지 없음)
const GoodAnimation = () => {
  const translateX = useRef(new Animated.Value(0)).current;
  
  useEffect(() => {
    Animated.timing(translateX, {
      toValue: 100,
      duration: 1000,
      useNativeDriver: true, // 네이티브 스레드에서 실행됩니다!
    }).start();
  }, []);
  
  return <Animated.View style={{ transform: [{ translateX }] }} />;
};

희소성: 흔함 난이도: 어려움


12. 불필요한 재 렌더링을 어떻게 방지합니까?

답변: 여러 기술로 낭비되는 렌더링을 방지할 수 있습니다.

  1. React.memo: 컴포넌트 메모이제이션
  2. useMemo/useCallback: 값/함수 메모이제이션
  3. 적절한 키 prop: React가 변경 사항을 식별하는 데 도움
  4. 인라인 객체/배열 피하기: 새 참조 만들기
  5. 컴포넌트 분할: 더 작고 집중된 컴포넌트
// 나쁨 - 모든 렌더링마다 새 객체 생성
function BadComponent() {
  return <ChildComponent style={{ margin: 10 }} />; // 새 객체!
}

// 좋음 - 안정적인 참조
const styles = StyleSheet.create({
  container: { margin: 10 },
});

function GoodComponent() {
  return <ChildComponent style={styles.container} />;
}

// 사용자 정의 비교를 사용하는 React.memo
const ExpensiveComponent = React.memo(
  ({ data, onPress }) => {
    console.log('Rendering ExpensiveComponent');
    return (
      <View>
        <Text>{data.name}</Text>
        <Button onPress={onPress} />
      </View>
    );
  },
  (prevProps, nextProps) => {
    // 사용자 정의 비교 - data.id가 변경된 경우에만 다시 렌더링합니다.
    return prevProps.data.id === nextProps.data.id;
  }
);

// 재 렌더링을 격리하기 위해 컴포넌트 분할
function ParentComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  return (
    <View>
      {/* count가 변경될 때만 다시 렌더링합니다. */}
      <CountDisplay count={count} />
      
      {/* text가 변경될 때만 다시 렌더링합니다. */}
      <TextDisplay text={text} />
      
      <Button onPress={() => setCount(c => c + 1)} />
    </View>
  );
}

const CountDisplay = React.memo(({ count }) => <Text>{count}</Text>);
const TextDisplay = React.memo(({ text }) => <Text>{text}</Text>);

희소성: 매우 흔함 난이도: 중간


13. React Native에서 이미지를 어떻게 최적화합니까?

답변: 이미지 최적화는 성능에 매우 중요합니다.

  1. 이미지 크기 조정: 적절한 크기 사용
  2. 이미지 캐싱: react-native-fast-image와 같은 라이브러리 사용
  3. 지연 로딩: 요청 시 이미지 로드
  4. 점진적 로딩: 먼저 자리 표시자 표시
  5. WebP 형식 사용: 더 나은 압축
import FastImage from 'react-native-fast-image';

function OptimizedImage({ uri }) {
  return (
    <FastImage
      source={{
        uri,
        priority: FastImage.priority.normal,
        cache: FastImage.cacheControl.immutable,
      }}
      resizeMode={FastImage.resizeMode.cover}
      style={{ width: 200, height: 200 }}
    />
  );
}

// 점진적 이미지 로딩
function ProgressiveImage({ thumbnailUri, fullUri }) {
  const [imageLoaded, setImageLoaded] = useState(false);
  
  return (
    <View>
      <Image
        source={{ uri: thumbnailUri }}
        style={styles.image}
        blurRadius={imageLoaded ? 0 : 5}
      />
      <Image
        source={{ uri: fullUri }}
        style={[styles.image, { opacity: imageLoaded ? 1 : 0 }]}
        onLoad={() => setImageLoaded(true)}
      />
    </View>
  );
}

// 이미지 미리 가져오기
import { Image } from 'react-native';

const prefetchImages = async (urls) => {
  const promises = urls.map(url => Image.prefetch(url));
  await Promise.all(promises);
};

희소성: 흔함 난이도: 중간


14. 성능 프로파일링에 어떤 도구를 사용합니까?

답변: 여러 도구가 성능 문제를 식별하는 데 도움이 됩니다.

  • React DevTools Profiler: 컴포넌트 렌더링 시간
  • Flipper: 디버깅 및 프로파일링 도구
  • Performance Monitor: 내장 FPS 모니터
  • Systrace: Android 성능 추적
  • Instruments: iOS 성능 프로파일링
// 개발 시 성능 모니터 활성화
import { LogBox } from 'react-native';

if (__DEV__) {
  // 성능 모니터 표시
  require('react-native').unstable_enableLogBox();
}

// 컴포넌트 렌더링 시간 측정
import { Profiler } from 'react';

function onRenderCallback(
  id,
  phase,
  actualDuration,
  baseDuration,
  startTime,
  commitTime
) {
  console.log(`${id} (${phase}) took ${actualDuration}ms`);
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <MyComponent />
    </Profiler>
  );
}

// 사용자 정의 성능 추적
const measurePerformance = (name, fn) => {
  const start = performance.now();
  const result = fn();
  const end = performance.now();
  console.log(`${name} took ${end - start}ms`);
  return result;
};

희소성: 흔함 난이도: 중간


네이티브 모듈 및 플랫폼별 (4개 질문)

15. React Native에서 네이티브 모듈을 어떻게 만듭니까?

답변: 네이티브 모듈을 사용하면 플랫폼별 코드를 사용할 수 있습니다.

// iOS (Objective-C) - CalendarManager.m
#import "CalendarManager.h"
#import <React/RCTLog.h>

@implementation CalendarManager

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
  RCTLogInfo(@"Creating event %@ at %@", name, location);
}

@end

// Android (Java) - CalendarModule.java
package com.myapp;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;

public class CalendarModule extends ReactContextBaseJavaModule {
  CalendarModule(ReactApplicationContext context) {
    super(context);
  }

  @Override
  public String getName() {
    return "CalendarManager";
  }

  @ReactMethod
  public void addEvent(String name, String location) {
    Log.d("CalendarModule", "Creating event " + name + " at " + location);
  }
}

// JavaScript 사용법
import { NativeModules } from 'react-native';

const { CalendarManager } = NativeModules;

function MyComponent() {
  const createEvent = () => {
    CalendarManager.addEvent('Meeting', 'Office');
  };
  
  return <Button title="Create Event" onPress={createEvent} />;
}

희소성: 중간 난이도: 어려움


16. 플랫폼별 코드를 어떻게 처리합니까?

답변: 플랫폼별 코드를 위한 여러 접근 방식:

  1. Platform 모듈: 런타임에 플랫폼 확인
  2. 플랫폼별 파일: .ios.js.android.js
  3. Platform.select: 플랫폼에 따라 값 선택
import { Platform } from 'react-native';

// Platform 모듈
const styles = StyleSheet.create({
  container: {
    paddingTop: Platform.OS === 'ios' ? 20 : 0,
  },
});

// Platform.select
const styles = StyleSheet.create({
  container: {
    ...Platform.select({
      ios: {
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.3,
      },
      android: {
        elevation: 4,
      },
    }),
  },
});

// 플랫폼별 파일
// Button.ios.js
export default function Button() {
  return <Text>iOS Button</Text>;
}

// Button.android.js
export default function Button() {
  return <Text>Android Button</Text>;
}

// 사용법 - 올바른 파일 자동 선택
import Button from './Button';

// 플랫폼 버전 확인
if (Platform.Version >= 21) {
  // Android API 21+
}

희소성: 매우 흔함 난이도: 쉬움


17. 새 아키텍처(Fabric 및 TurboModules)는 무엇입니까?

답변: 새 아키텍처는 React Native 성능을 향상시킵니다.

  • Fabric: 새 렌더링 시스템
    • 동기 레이아웃
    • 네이티브 뷰와의 더 나은 상호 운용성
    • 타입 안전성
  • TurboModules: 새 네이티브 모듈 시스템
    • 지연 로딩
    • JSI(JavaScript Interface) - 직접 C++ 통신
    • 브리지 직렬화 없음

장점:

  • 더 빠른 시작
  • 더 낮은 메모리 사용량
  • 동기 네이티브 호출
  • 더 나은 타입 안전성

희소성: 중간 난이도: 어려움


18. React Native에서 딥 링크를 어떻게 처리합니까?

답변: 딥 링크를 사용하면 URL에서 특정 화면을 열 수 있습니다.

import { Linking } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';

// 딥 링크 구성
const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Home: 'home',
      Profile: 'profile/:id',
      Settings: 'settings',
    },
  },
};

function App() {
  return (
    <NavigationContainer linking={linking}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Profile" component={ProfileScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

// 들어오는 링크 처리
useEffect(() => {
  const handleUrl = ({ url }) => {
    console.log('Opened with URL:', url);
    // myapp://profile/123
  };
  
  // 링크에서 앱 열림
  Linking.getInitialURL().then(url => {
    if (url) handleUrl({ url });
  });
  
  // 앱이 이미 열려 있고 새 링크가 있습니다.
  const subscription = Linking.addEventListener('url', handleUrl);
  
  return () => subscription.remove();
}, []);

// 프로그래밍 방식으로 URL 열기
const openURL = async (url) => {
  const supported = await Linking.canOpenURL(url);
  if (supported) {
    await Linking.openURL(url);
  }
};

희소성: 흔함 난이도: 중간

Newsletter subscription

실제로 효과가 있는 주간 커리어 팁

최신 인사이트를 받은 편지함으로 직접 받아보세요

지원을 멈추세요. 채용되기 시작하세요.

전 세계 구직자들이 신뢰하는 AI 기반 최적화로 이력서를 면접 자석으로 변환하세요.

무료로 시작하기

이 게시물 공유

6초를 최대한 활용하세요

채용 담당자는 평균적으로 6~7초만 이력서를 훑어봅니다. 우리의 검증된 템플릿은 즉시 주목을 끌고 계속 읽게 하도록 설계되었습니다.