React Nativeの面接でよく聞かれる質問30選: 2026年完全ガイド
React Nativeの面接で頻繁に問われる30の質問を厳選しました。コード例を交えた詳細な解説で、モバイル開発職の獲得を目指せます。

React Nativeの技術面接では、クロスプラットフォームのモバイル開発スキル、iOSとAndroid固有の知識、パフォーマンス設計が評価されます。本ガイドでは最も頻出する30問を取り上げ、効果的な準備のために詳細な回答とコード例を示します。
本記事の質問は基礎から発展的な概念までを網羅しています。React Nativeのアーキテクチャを理解し、Web版Reactとの相違点を把握することが面接突破の鍵となります。
React Nativeの基礎
1. ReactとReact Nativeの違いは何ですか?
ReactはWebインターフェース構築のためのライブラリですが、React NativeはiOSとAndroid向けのネイティブモバイルアプリ開発を可能にする技術です。
根本的な違いはレンダリング方式にあります。ReactはVirtual DOMを使ってHTML要素に変換しますが、React Nativeはブリッジを介して各プラットフォームのネイティブコンポーネントと通信します。
// React (Web) - uses HTML elements
function WebComponent() {
return (
<div className="container">
<span>Web text</span>
<button onClick={handleClick}>Click</button>
</div>
)
}
// React Native - uses native components
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native'
function NativeComponent() {
return (
<View style={styles.container}>
<Text>Native text</Text>
<TouchableOpacity onPress={handlePress}>
<Text>Press</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16
}
})React Nativeのコンポーネントは、iOSではUIView、Androidではandroid.viewに変換され、ネイティブ品質のパフォーマンスを実現します。
2. React Nativeのアーキテクチャはどのように動作しますか?
React Nativeは、JavaScript層、Bridge層(新アーキテクチャではJSI)、Native層という三層構造を採用しています。
JavaScriptコードはJSエンジン(HermesまたはJavaScriptCore)上で実行されます。ネイティブコードとの通信は、旧アーキテクチャではJSONシリアライゼーションを介して行われ、新アーキテクチャではJSI(JavaScript Interface)を通じて直接行われます。
// Old architecture: asynchronous communication via Bridge
// The Bridge serializes messages as JSON between JS and Native
// New architecture (Fabric + TurboModules)
// JSI enables synchronous direct calls to native modules
// Example TurboModule usage
import { TurboModuleRegistry } from 'react-native'
// Synchronous access to native module
const DeviceInfo = TurboModuleRegistry.get('DeviceInfo')
const deviceName = DeviceInfo.getDeviceName() // Synchronous call
// With Fabric, rendering is smoother
// Components can be created synchronously
// Reducing jank during animations新アーキテクチャはJSONシリアライゼーションを排除し、同期的な呼び出しを可能にすることで、パフォーマンスを大幅に改善します。
3. Metroバンドラーとは何ですか?
MetroはReact Nativeで使われるJavaScriptバンドラーであり、ソースコードをモバイル実行向けに最適化されたバンドルへ変換します。
Metroはモジュール解決、コード変換(Babel経由)、開発時のホットリロードを担当します。
const { getDefaultConfig } = require('expo/metro-config')
const config = getDefaultConfig(__dirname)
// Custom configuration
config.resolver.assetExts.push('db') // Add extensions
config.resolver.sourceExts.push('cjs') // CommonJS support
// Transformer configuration
config.transformer.babelTransformerPath = require.resolve(
'react-native-svg-transformer'
)
// Production optimizations
config.transformer.minifierConfig = {
keep_classnames: true,
keep_fnames: true,
mangle: {
keep_classnames: true,
keep_fnames: true
}
}
module.exports = configMetroはFast Refreshに対応しており、アプリの状態を失わずに即座に変更を反映できます。
4. StyleSheet.createとその利点を説明してください
StyleSheet.createはスタイルを検証し、数値参照に変換することで最適化を行い、ブリッジ通信のオーバーヘッドを削減します。
// ❌ Inline styles - recreated on every render
function BadExample() {
return (
<View style={{ flex: 1, padding: 16, backgroundColor: '#fff' }}>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>Title</Text>
</View>
)
}
// ✅ StyleSheet.create - optimized and validated
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
backgroundColor: '#fff'
},
title: {
fontSize: 18,
fontWeight: 'bold'
},
// Style composition
row: {
flexDirection: 'row',
alignItems: 'center',
gap: 8
}
})
function GoodExample() {
return (
<View style={styles.container}>
<Text style={styles.title}>Title</Text>
{/* Style combination */}
<View style={[styles.row, { marginTop: 10 }]}>
<Text>Content</Text>
</View>
</View>
)
}
// StyleSheet.absoluteFillObject for absolute positioning
const overlayStyles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0,0,0,0.5)'
}
})5. WebのFlexboxとReact Nativeの違いは何ですか?
React NativeもFlexboxを使用しますが、縦方向のモバイル画面に適応するため、Webとは異なるデフォルト値が設定されています。
// Key differences from web
const styles = StyleSheet.create({
container: {
// flexDirection: 'column' by default (vs 'row' on web)
// alignItems: 'stretch' by default
flex: 1
},
// React Native Flexbox
row: {
flexDirection: 'row', // Horizontal
justifyContent: 'space-between', // Main axis
alignItems: 'center', // Cross axis
flexWrap: 'wrap', // Line wrapping
gap: 8 // Supported since RN 0.71
},
// Flex grow/shrink
flexItem: {
flex: 1, // Equivalent to flex: 1 1 0
flexGrow: 1, // Grow to fill
flexShrink: 0, // Don't shrink
flexBasis: 100 // Base size
},
// Absolute positioning
absolute: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0
}
})
// Practical example: card with image and content
function Card() {
return (
<View style={cardStyles.container}>
<Image source={{ uri: imageUrl }} style={cardStyles.image} />
<View style={cardStyles.content}>
<Text style={cardStyles.title}>Title</Text>
<Text style={cardStyles.description}>Description</Text>
</View>
</View>
)
}
const cardStyles = StyleSheet.create({
container: {
flexDirection: 'row',
backgroundColor: '#fff',
borderRadius: 8,
overflow: 'hidden'
},
image: {
width: 100,
height: 100
},
content: {
flex: 1, // Takes remaining space
padding: 12,
justifyContent: 'center'
},
title: {
fontSize: 16,
fontWeight: '600'
},
description: {
fontSize: 14,
color: '#666'
}
})ナビゲーションとコンポーネント
6. React Navigationでナビゲーションをどう実装しますか?
React NativeにおけるナビゲーションのデファクトスタンダードはReact Navigationです。モバイル特有のパターンに合わせた複数のナビゲーター種別が用意されています。
// Installing dependencies
// npm install @react-navigation/native @react-navigation/native-stack
// npm install react-native-screens react-native-safe-area-context
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
// TypeScript typing for navigation params
type RootStackParamList = {
Home: undefined
Profile: { userId: string }
Settings: { section?: string }
}
const Stack = createNativeStackNavigator<RootStackParamList>()
const Tab = createBottomTabNavigator()
// Tab navigation
function TabNavigator() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
// Dynamic icon based on tab
const iconName = route.name === 'Home' ? 'home' : 'settings'
return <Icon name={iconName} size={size} color={color} />
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: 'gray'
})}
>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
)
}
// Stack navigation
function App() {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerStyle: { backgroundColor: '#007AFF' },
headerTintColor: '#fff',
animation: 'slide_from_right' // Native animation
}}
>
<Stack.Screen
name="Home"
component={TabNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Profile"
component={ProfileScreen}
options={({ route }) => ({
title: `Profile ${route.params.userId}`
})}
/>
</Stack.Navigator>
</NavigationContainer>
)
}7. FlatListでパフォーマンスの高いリストをどう扱いますか?
FlatListは自動仮想化により、画面に表示されている要素のみを描画する長尺リスト向けの最適化されたコンポーネントです。
import { FlatList, RefreshControl } from 'react-native'
function ProductList() {
const [products, setProducts] = useState([])
const [refreshing, setRefreshing] = useState(false)
const [loading, setLoading] = useState(false)
// Initial loading
const fetchProducts = async (page = 1) => {
const response = await api.getProducts(page)
return response.data
}
// Pull-to-refresh
const onRefresh = useCallback(async () => {
setRefreshing(true)
const data = await fetchProducts(1)
setProducts(data)
setRefreshing(false)
}, [])
// Infinite pagination
const loadMore = useCallback(async () => {
if (loading) return
setLoading(true)
const nextPage = Math.ceil(products.length / 20) + 1
const data = await fetchProducts(nextPage)
setProducts(prev => [...prev, ...data])
setLoading(false)
}, [products.length, loading])
// Item rendering
const renderItem = useCallback(({ item }) => (
<ProductCard product={item} />
), [])
// Key extraction
const keyExtractor = useCallback((item) => item.id.toString(), [])
// Item separator
const ItemSeparator = useCallback(() => (
<View style={{ height: 12 }} />
), [])
return (
<FlatList
data={products}
renderItem={renderItem}
keyExtractor={keyExtractor}
ItemSeparatorComponent={ItemSeparator}
// Performance optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
windowSize={5}
// Pull-to-refresh
refreshControl={
<RefreshControl
refreshing={refreshing}
onRefresh={onRefresh}
tintColor="#007AFF"
/>
}
// Infinite pagination
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={loading ? <ActivityIndicator /> : null}
// Empty list
ListEmptyComponent={<EmptyState message="No products" />}
/>
)
}renderItemは必ずuseCallbackでメモ化し、重いコンポーネントは外部に切り出してください。renderItem内のインラインアロー関数は不要な再レンダリングの原因となります。
8. TouchableOpacity、Pressable、TouchableHighlightの違いは何ですか?
これらはタッチ操作を扱うコンポーネントですが、視覚的なフィードバックの方法が異なります。
import {
TouchableOpacity,
TouchableHighlight,
Pressable,
StyleSheet
} from 'react-native'
function InteractionExamples() {
return (
<View style={styles.container}>
{/* TouchableOpacity: reduces opacity on touch */}
<TouchableOpacity
activeOpacity={0.7}
onPress={() => console.log('Pressed')}
style={styles.button}
>
<Text>TouchableOpacity</Text>
</TouchableOpacity>
{/* TouchableHighlight: adds background color */}
<TouchableHighlight
underlayColor="#ddd"
onPress={() => console.log('Pressed')}
style={styles.button}
>
<Text>TouchableHighlight</Text>
</TouchableHighlight>
{/* Pressable: modern API with more control */}
<Pressable
onPress={() => console.log('Pressed')}
onLongPress={() => console.log('Long press')}
delayLongPress={500}
style={({ pressed }) => [
styles.button,
pressed && styles.buttonPressed
]}
>
{({ pressed }) => (
<Text style={pressed && styles.textPressed}>
{pressed ? 'Pressed!' : 'Pressable'}
</Text>
)}
</Pressable>
{/* Pressable with hitSlop to enlarge touch area */}
<Pressable
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
onPress={() => console.log('Pressed')}
style={styles.smallButton}
>
<Text>Small button</Text>
</Pressable>
</View>
)
}
const styles = StyleSheet.create({
container: {
gap: 16,
padding: 20
},
button: {
backgroundColor: '#007AFF',
padding: 16,
borderRadius: 8,
alignItems: 'center'
},
buttonPressed: {
backgroundColor: '#0056b3',
transform: [{ scale: 0.98 }]
},
textPressed: {
color: '#fff'
},
smallButton: {
padding: 8,
backgroundColor: '#eee'
}
})新規プロジェクトでは、より柔軟な制御と一貫したAPIを提供するPressableの利用が推奨されます。
9. 滑らかなアニメーションをどう実装しますか?
React Nativeには複数のアニメーションAPIがあります。組み込みのAnimatedと、より高性能なReanimatedです。
import { Animated, Easing } from 'react-native'
import Reanimated, {
useSharedValue,
useAnimatedStyle,
withSpring,
withTiming
} from 'react-native-reanimated'
// Animation with Animated (native API)
function FadeInView({ children }) {
const fadeAnim = useRef(new Animated.Value(0)).current
useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
easing: Easing.ease,
useNativeDriver: true // Performant on UI thread
}).start()
}, [])
return (
<Animated.View style={{ opacity: fadeAnim }}>
{children}
</Animated.View>
)
}
// Animation with Reanimated (recommended for complex animations)
function BouncyButton() {
const scale = useSharedValue(1)
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }]
}))
const handlePressIn = () => {
scale.value = withSpring(0.95, {
damping: 10,
stiffness: 400
})
}
const handlePressOut = () => {
scale.value = withSpring(1, {
damping: 10,
stiffness: 400
})
}
return (
<Pressable onPressIn={handlePressIn} onPressOut={handlePressOut}>
<Reanimated.View style={[styles.button, animatedStyle]}>
<Text style={styles.buttonText}>Press</Text>
</Reanimated.View>
</Pressable>
)
}
// List animation with LayoutAnimation
import { LayoutAnimation, UIManager, Platform } from 'react-native'
// Enable on Android
if (Platform.OS === 'android') {
UIManager.setLayoutAnimationEnabledExperimental?.(true)
}
function AnimatedList() {
const [items, setItems] = useState([])
const addItem = () => {
// Configure animation before state change
LayoutAnimation.configureNext(LayoutAnimation.Presets.spring)
setItems(prev => [...prev, { id: Date.now() }])
}
const removeItem = (id) => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setItems(prev => prev.filter(item => item.id !== id))
}
return (
<View>
<Button title="Add" onPress={addItem} />
{items.map(item => (
<TouchableOpacity key={item.id} onPress={() => removeItem(item.id)}>
<View style={styles.item}>
<Text>Item {item.id}</Text>
</View>
</TouchableOpacity>
))}
</View>
)
}React Nativeの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
状態管理とデータ
10. React Nativeでグローバル状態をどう管理しますか?
React Webと同じ選択肢が利用できます。Context API、Redux、Zustand、MobXのいずれかを採用するのが一般的です。
// Lightweight solution with Zustand
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
import AsyncStorage from '@react-native-async-storage/async-storage'
// Store with persistence
const useAuthStore = create(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (email, password) => {
const response = await api.login(email, password)
set({
user: response.user,
token: response.token,
isAuthenticated: true
})
},
logout: () => {
set({ user: null, token: null, isAuthenticated: false })
},
updateProfile: (updates) => {
set(state => ({
user: { ...state.user, ...updates }
}))
}
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => AsyncStorage)
}
)
)
// Usage in a component
function ProfileScreen() {
const { user, logout, updateProfile } = useAuthStore()
if (!user) return <LoginPrompt />
return (
<View style={styles.container}>
<Text style={styles.name}>{user.name}</Text>
<Text style={styles.email}>{user.email}</Text>
<TouchableOpacity onPress={logout} style={styles.logoutButton}>
<Text>Logout</Text>
</TouchableOpacity>
</View>
)
}
// Cart store
const useCartStore = create((set, get) => ({
items: [],
addItem: (product) => set(state => {
const existing = state.items.find(i => i.id === product.id)
if (existing) {
return {
items: state.items.map(i =>
i.id === product.id
? { ...i, quantity: i.quantity + 1 }
: i
)
}
}
return { items: [...state.items, { ...product, quantity: 1 }] }
}),
removeItem: (id) => set(state => ({
items: state.items.filter(i => i.id !== id)
})),
getTotal: () => {
return get().items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
},
clearCart: () => set({ items: [] })
}))11. キャッシュ管理付きでAPI呼び出しをどう行いますか?
サーバー状態の管理にはReact Query(TanStack Query)が推奨されます。
import { QueryClient, QueryClientProvider, useQuery, useMutation } from '@tanstack/react-query'
// Client configuration
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 5 * 60 * 1000, // 5 minutes
cacheTime: 30 * 60 * 1000, // 30 minutes
retry: 2,
refetchOnWindowFocus: false // Mobile doesn't have "window focus"
}
}
})
// Provider in App.tsx
function App() {
return (
<QueryClientProvider client={queryClient}>
<NavigationContainer>
<AppNavigator />
</NavigationContainer>
</QueryClientProvider>
)
}
// Custom hook for products
function useProducts(categoryId) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: async () => {
const response = await fetch(`/api/products?category=${categoryId}`)
if (!response.ok) throw new Error('Network error')
return response.json()
},
enabled: !!categoryId // Don't execute if no categoryId
})
}
// Mutation with cache invalidation
function useAddToCart() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: async (product) => {
const response = await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify(product)
})
return response.json()
},
onSuccess: () => {
// Invalidate cart cache to force refetch
queryClient.invalidateQueries({ queryKey: ['cart'] })
},
onError: (error) => {
Alert.alert('Error', error.message)
}
})
}
// Usage in a component
function ProductListScreen({ categoryId }) {
const { data: products, isLoading, error, refetch } = useProducts(categoryId)
const addToCart = useAddToCart()
if (isLoading) return <LoadingSpinner />
if (error) return <ErrorView error={error} onRetry={refetch} />
return (
<FlatList
data={products}
renderItem={({ item }) => (
<ProductCard
product={item}
onAddToCart={() => addToCart.mutate(item)}
isAddingToCart={addToCart.isPending}
/>
)}
refreshing={isLoading}
onRefresh={refetch}
/>
)
}12. ローカルにデータをどう保存しますか?
データの種類に応じて選択肢があります。シンプルなデータにはAsyncStorage、性能重視ならMMKV、構造化データにはSQLiteが適しています。
// AsyncStorage - simple but slow for large volumes
import AsyncStorage from '@react-native-async-storage/async-storage'
const storage = {
async get(key) {
const value = await AsyncStorage.getItem(key)
return value ? JSON.parse(value) : null
},
async set(key, value) {
await AsyncStorage.setItem(key, JSON.stringify(value))
},
async remove(key) {
await AsyncStorage.removeItem(key)
},
async clear() {
await AsyncStorage.clear()
}
}
// MMKV - very performant (recommended)
import { MMKV } from 'react-native-mmkv'
const mmkv = new MMKV()
const fastStorage = {
get(key) {
const value = mmkv.getString(key)
return value ? JSON.parse(value) : null
},
set(key, value) {
mmkv.set(key, JSON.stringify(value))
},
remove(key) {
mmkv.delete(key)
},
// Primitive type support
getNumber(key) {
return mmkv.getNumber(key)
},
setNumber(key, value) {
mmkv.set(key, value)
},
getBoolean(key) {
return mmkv.getBoolean(key)
}
}
// Hook to use MMKV with React
function useMMKVStorage(key, defaultValue) {
const [value, setValue] = useState(() => {
const stored = fastStorage.get(key)
return stored ?? defaultValue
})
const setStoredValue = useCallback((newValue) => {
setValue(newValue)
fastStorage.set(key, newValue)
}, [key])
return [value, setStoredValue]
}
// Usage
function SettingsScreen() {
const [theme, setTheme] = useMMKVStorage('theme', 'light')
const [notifications, setNotifications] = useMMKVStorage('notifications', true)
return (
<View>
<Switch
value={theme === 'dark'}
onValueChange={(v) => setTheme(v ? 'dark' : 'light')}
/>
<Switch
value={notifications}
onValueChange={setNotifications}
/>
</View>
)
}トークンや個人情報などの機密データには、expo-secure-storeまたはreact-native-keychainを利用してください。これらはiOSのKeychainおよびAndroidのKeystoreで暗号化を行います。
パフォーマンスと最適化
13. React Nativeアプリのパフォーマンスをどう最適化しますか?
最適化はレンダリング、メモリ、ユーザー操作の複数の側面にまたがります。
const ProductCard = React.memo(function ProductCard({ product, onPress }) {
return (
<TouchableOpacity onPress={() => onPress(product.id)}>
<View style={styles.card}>
<Image source={{ uri: product.image }} style={styles.image} />
<Text style={styles.title}>{product.name}</Text>
<Text style={styles.price}>{product.price}$</Text>
</View>
</TouchableOpacity>
)
}, (prevProps, nextProps) => {
// Custom comparison
return prevProps.product.id === nextProps.product.id
})
// 2. Memoize callbacks
function ProductList({ products }) {
// ❌ New function on every render
// onPress={(id) => handlePress(id)}
// ✅ Stable function
const handlePress = useCallback((id) => {
navigation.navigate('Product', { id })
}, [navigation])
return (
<FlatList
data={products}
renderItem={({ item }) => (
<ProductCard product={item} onPress={handlePress} />
)}
/>
)
}
// 3. Optimize images
import FastImage from 'react-native-fast-image'
function OptimizedImage({ uri }) {
return (
<FastImage
source={{
uri,
priority: FastImage.priority.normal,
cache: FastImage.cacheControl.immutable
}}
style={styles.image}
resizeMode={FastImage.resizeMode.cover}
/>
)
}
// 4. Use InteractionManager for heavy tasks
import { InteractionManager } from 'react-native'
function HeavyScreen() {
const [data, setData] = useState(null)
useEffect(() => {
// Wait for animations to complete
const task = InteractionManager.runAfterInteractions(() => {
const result = performHeavyComputation()
setData(result)
})
return () => task.cancel()
}, [])
return data ? <DataView data={data} /> : <LoadingView />
}
// 5. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
function Navigator() {
return (
<Stack.Navigator>
<Stack.Screen
name="Heavy"
component={HeavyScreen}
options={{ lazy: true }}
/>
</Stack.Navigator>
)
}14. パフォーマンス問題をどうデバッグしますか?
React Nativeはボトルネックを特定するための複数のツールを提供します。
// Configuration in android/app/build.gradle and ios/Podfile
// See: https://fbflipper.com/
// 2. Console.time to measure operations
function fetchData() {
console.time('fetchData')
const data = await api.getData()
console.timeEnd('fetchData') // fetchData: 234ms
return data
}
// 3. Performance monitor (shake device → Show Perf Monitor)
// Shows JS and UI FPS
// 4. Hermes profiler for CPU
// Enable in metro.config.js
module.exports = {
transformer: {
hermesParser: true
}
}
// 5. Identify re-renders with why-did-you-render
// Installation: npm install @welldone-software/why-did-you-render
import React from 'react'
if (__DEV__) {
const whyDidYouRender = require('@welldone-software/why-did-you-render')
whyDidYouRender(React, {
trackAllPureComponents: true
})
}
// Mark a component to monitor
ProductCard.whyDidYouRender = true
// 6. Measure mount time
function useComponentTiming(componentName) {
const mountTime = useRef(Date.now())
useEffect(() => {
const duration = Date.now() - mountTime.current
console.log(`${componentName} mounted in ${duration}ms`)
return () => {
console.log(`${componentName} unmounted`)
}
}, [componentName])
}
// Usage
function MyComponent() {
useComponentTiming('MyComponent')
// ...
}15. オフラインモードをどう扱いますか?
オフライン対応にはキャッシュと同期戦略が必要です。
import NetInfo from '@react-native-community/netinfo'
// Hook to monitor connectivity
function useNetworkStatus() {
const [isConnected, setIsConnected] = useState(true)
const [connectionType, setConnectionType] = useState(null)
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected)
setConnectionType(state.type)
})
return () => unsubscribe()
}, [])
return { isConnected, connectionType }
}
// Service with offline queue
class OfflineQueue {
constructor() {
this.queue = []
this.isProcessing = false
}
async add(action) {
this.queue.push({
id: Date.now(),
action,
timestamp: new Date().toISOString()
})
await this.persist()
}
async persist() {
await AsyncStorage.setItem('offline_queue', JSON.stringify(this.queue))
}
async load() {
const data = await AsyncStorage.getItem('offline_queue')
this.queue = data ? JSON.parse(data) : []
}
async process() {
if (this.isProcessing || this.queue.length === 0) return
this.isProcessing = true
const { isConnected } = await NetInfo.fetch()
if (!isConnected) {
this.isProcessing = false
return
}
while (this.queue.length > 0) {
const item = this.queue[0]
try {
await this.executeAction(item.action)
this.queue.shift()
await this.persist()
} catch (error) {
console.error('Failed to process action:', error)
break
}
}
this.isProcessing = false
}
async executeAction(action) {
switch (action.type) {
case 'CREATE_ORDER':
return api.createOrder(action.payload)
case 'UPDATE_PROFILE':
return api.updateProfile(action.payload)
default:
throw new Error(`Unknown action: ${action.type}`)
}
}
}
const offlineQueue = new OfflineQueue()
// Network status banner component
function NetworkBanner() {
const { isConnected } = useNetworkStatus()
if (isConnected) return null
return (
<View style={styles.banner}>
<Text style={styles.bannerText}>
Offline mode - Changes will be synchronized
</Text>
</View>
)
}モバイル特有の話題
16. iOSとAndroidの権限をどう管理しますか?
権限はプラットフォームごとに扱いが異なります。react-native-permissionsのようなライブラリを使うとAPIを統一できます。
import { Platform, Alert, Linking } from 'react-native'
import {
check,
request,
PERMISSIONS,
RESULTS,
openSettings
} from 'react-native-permissions'
// Permission configuration per platform
const PERMISSION_TYPES = {
camera: Platform.select({
ios: PERMISSIONS.IOS.CAMERA,
android: PERMISSIONS.ANDROID.CAMERA
}),
photos: Platform.select({
ios: PERMISSIONS.IOS.PHOTO_LIBRARY,
android: PERMISSIONS.ANDROID.READ_MEDIA_IMAGES
}),
location: Platform.select({
ios: PERMISSIONS.IOS.LOCATION_WHEN_IN_USE,
android: PERMISSIONS.ANDROID.ACCESS_FINE_LOCATION
})
}
// Hook to manage permissions
function usePermission(type) {
const [status, setStatus] = useState(RESULTS.UNAVAILABLE)
const permission = PERMISSION_TYPES[type]
useEffect(() => {
check(permission).then(setStatus)
}, [permission])
const requestPermission = useCallback(async () => {
const result = await request(permission)
setStatus(result)
if (result === RESULTS.BLOCKED) {
Alert.alert(
'Permission required',
`This feature requires ${type} access. Do you want to open settings?`,
[
{ text: 'No', style: 'cancel' },
{ text: 'Open', onPress: openSettings }
]
)
}
return result
}, [permission, type])
return {
status,
isGranted: status === RESULTS.GRANTED,
isDenied: status === RESULTS.DENIED,
isBlocked: status === RESULTS.BLOCKED,
requestPermission
}
}
// Usage
function CameraButton() {
const { isGranted, requestPermission } = usePermission('camera')
const handlePress = async () => {
if (!isGranted) {
const result = await requestPermission()
if (result !== RESULTS.GRANTED) return
}
// Open camera
navigation.navigate('Camera')
}
return (
<TouchableOpacity onPress={handlePress}>
<Text>Take a photo</Text>
</TouchableOpacity>
)
}17. プッシュ通知をどう実装しますか?
プッシュ通知にはネイティブの設定と送信用のバックエンドが必要です。
import messaging from '@react-native-firebase/messaging'
import notifee, { AndroidImportance } from '@notifee/react-native'
// Notification service
class NotificationService {
async initialize() {
// Request permission (iOS)
const authStatus = await messaging().requestPermission()
const enabled = authStatus === messaging.AuthorizationStatus.AUTHORIZED
if (enabled) {
// Get FCM token
const token = await messaging().getToken()
await this.registerToken(token)
// Listen for token changes
messaging().onTokenRefresh(this.registerToken)
// Create Android channel
await notifee.createChannel({
id: 'default',
name: 'Notifications',
importance: AndroidImportance.HIGH
})
}
return enabled
}
async registerToken(token) {
// Send token to backend
await api.registerPushToken(token)
}
// Setup handlers
setupHandlers() {
// Notification received in foreground
messaging().onMessage(async (remoteMessage) => {
await this.displayNotification(remoteMessage)
})
// Notification tapped (app in background)
messaging().onNotificationOpenedApp((remoteMessage) => {
this.handleNotificationPress(remoteMessage)
})
// App opened from notification (app closed)
messaging()
.getInitialNotification()
.then((remoteMessage) => {
if (remoteMessage) {
this.handleNotificationPress(remoteMessage)
}
})
}
async displayNotification(remoteMessage) {
const { title, body } = remoteMessage.notification
await notifee.displayNotification({
title,
body,
android: {
channelId: 'default',
pressAction: { id: 'default' }
},
data: remoteMessage.data
})
}
handleNotificationPress(remoteMessage) {
const { type, id } = remoteMessage.data
switch (type) {
case 'order':
navigation.navigate('OrderDetail', { orderId: id })
break
case 'message':
navigation.navigate('Chat', { conversationId: id })
break
}
}
}
// Usage in App.tsx
function App() {
useEffect(() => {
const notificationService = new NotificationService()
notificationService.initialize()
notificationService.setupHandlers()
}, [])
return <AppNavigator />
}18. ディープリンクをどう扱いますか?
ディープリンクを利用すると、外部URLからアプリの特定画面を直接開けます。
import { Linking } from 'react-native'
import { NavigationContainer } from '@react-navigation/native'
// Deep link configuration
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: 'home',
Product: {
path: 'product/:id',
parse: {
id: (id) => parseInt(id, 10)
}
},
Profile: 'profile/:userId?',
Settings: {
path: 'settings',
screens: {
Notifications: 'notifications',
Privacy: 'privacy'
}
}
}
},
// Custom function to get initial URL
async getInitialURL() {
// Check if app was opened via deep link
const url = await Linking.getInitialURL()
if (url) return url
// Check notifications
const message = await messaging().getInitialNotification()
if (message?.data?.link) return message.data.link
return null
},
// Subscribe to incoming links
subscribe(listener) {
// Standard deep links
const linkingSubscription = Linking.addEventListener('url', ({ url }) => {
listener(url)
})
// Links from notifications
const unsubscribeNotification = messaging().onNotificationOpenedApp(
(message) => {
const link = message.data?.link
if (link) listener(link)
}
)
return () => {
linkingSubscription.remove()
unsubscribeNotification()
}
}
}
// Usage
function App() {
return (
<NavigationContainer linking={linking} fallback={<LoadingScreen />}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
// Testing deep links
// myapp://product/123
// https://myapp.com/profile/user456React Nativeの面接対策はできていますか?
インタラクティブなシミュレーター、flashcards、技術テストで練習しましょう。
ネイティブコードとモジュール
19. ネイティブコードはいつ、どう書きますか?
JavaScriptから利用できない機能にアクセスする場合や、極端に高いパフォーマンスが必要な場合にネイティブコードを書きます。
// iOS - Native module in Swift
// ios/MyModule.swift
import Foundation
@objc(MyModule)
class MyModule: NSObject {
@objc
func getDeviceInfo(_ resolve: @escaping RCTPromiseResolveBlock,
rejecter reject: @escaping RCTPromiseRejectBlock) {
let info: [String: Any] = [
"model": UIDevice.current.model,
"systemVersion": UIDevice.current.systemVersion,
"name": UIDevice.current.name
]
resolve(info)
}
@objc
static func requiresMainQueueSetup() -> Bool {
return false
}
}
// ios/MyModule.m (Bridge)
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(MyModule, NSObject)
RCT_EXTERN_METHOD(getDeviceInfo:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@end
// Android - Native module in Kotlin
// android/app/src/main/java/com/myapp/MyModule.kt
package com.myapp
import com.facebook.react.bridge.*
class MyModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName() = "MyModule"
@ReactMethod
fun getDeviceInfo(promise: Promise) {
val info = Arguments.createMap().apply {
putString("model", android.os.Build.MODEL)
putString("systemVersion", android.os.Build.VERSION.RELEASE)
putString("manufacturer", android.os.Build.MANUFACTURER)
}
promise.resolve(info)
}
}
// JavaScript - Using native module
import { NativeModules } from 'react-native'
const { MyModule } = NativeModules
async function getDeviceInfo() {
try {
const info = await MyModule.getDeviceInfo()
console.log('Device info:', info)
return info
} catch (error) {
console.error('Error getting device info:', error)
throw error
}
}20. Expoとは何で、どんな時に使うべきですか?
Expoはネイティブの設定を肩代わりしてReact Native開発を簡素化するフレームワークです。
// Expo benefits
// - No need for Android Studio or Xcode to start
// - OTA (over-the-air) updates without app stores
// - Rich SDK with preconfigured modules
// - EAS Build for cloud builds
// Creating an Expo project
// npx create-expo-app@latest MyApp
// Using Expo modules
import * as ImagePicker from 'expo-image-picker'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'
async function pickImage() {
// Request permission
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync()
if (status !== 'granted') {
alert('Permission denied')
return
}
// Open image picker
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8
})
if (!result.canceled) {
return result.assets[0].uri
}
}
// Build configuration
// app.json
{
"expo": {
"name": "MyApp",
"slug": "myapp",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"splash": {
"image": "./assets/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.company.myapp"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"package": "com.company.myapp"
},
"plugins": [
"expo-router",
[
"expo-camera",
{
"cameraPermission": "Allow camera access"
}
]
]
}
}
// When NOT to use Expo
// - Need for complex custom native modules
// - Integration with specific native SDKs
// - Total control over native configuration
// - Very lightweight application (Expo adds weight)21. Hot Reloadingはどのように動作しますか?
Hot Reloading(Fast Refresh)はアプリの状態を保持したまま即座に変更を反映する仕組みです。
// Fast Refresh preserves hooks state
function Counter() {
const [count, setCount] = useState(0)
// Modify this text and save
// The count state will be preserved
return (
<View>
<Text>Counter: {count}</Text>
<Button title="+1" onPress={() => setCount(c => c + 1)} />
</View>
)
}
// ⚠️ Cases where Fast Refresh does full reload
// 1. Syntax error
// 2. Modifying a class component
// 3. File with mixed non-component exports
// ❌ This file will do full reload because of mixed exports
export const API_URL = 'https://api.example.com'
export function MyComponent() { /* ... */ }
// ✅ Separate into distinct files
// constants.js
export const API_URL = 'https://api.example.com'
// MyComponent.js
export function MyComponent() { /* ... */ }
// Force remount if needed
// Add this comment at top of file:
// @refresh reset
// Metro configuration for Fast Refresh
// metro.config.js
module.exports = {
transformer: {
experimentalImportSupport: false,
inlineRequires: true
}
}テストと品質
22. React Nativeアプリをどうテストしますか?
React Nativeのテストは、Jestと描画・操作向けの専用ライブラリを組み合わせて行います。
// Jest configuration
// jest.config.js
module.exports = {
preset: 'react-native',
setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'],
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|@react-navigation)/)'
],
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|webp|svg)$': '<rootDir>/__mocks__/fileMock.js'
}
}
// Component test with React Native Testing Library
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native'
import { ProductCard } from './ProductCard'
describe('ProductCard', () => {
const mockProduct = {
id: '1',
name: 'iPhone 15',
price: 999,
image: 'https://example.com/iphone.jpg'
}
it('renders product information', () => {
render(<ProductCard product={mockProduct} />)
expect(screen.getByText('iPhone 15')).toBeOnTheScreen()
expect(screen.getByText('$999')).toBeOnTheScreen()
})
it('calls onPress when tapped', () => {
const onPress = jest.fn()
render(<ProductCard product={mockProduct} onPress={onPress} />)
fireEvent.press(screen.getByTestId('product-card'))
expect(onPress).toHaveBeenCalledWith('1')
})
it('shows loading state when adding to cart', async () => {
const onAddToCart = jest.fn(() => new Promise(r => setTimeout(r, 100)))
render(<ProductCard product={mockProduct} onAddToCart={onAddToCart} />)
fireEvent.press(screen.getByText('Add to cart'))
expect(screen.getByTestId('loading-indicator')).toBeOnTheScreen()
await waitFor(() => {
expect(screen.queryByTestId('loading-indicator')).not.toBeOnTheScreen()
})
})
})
// Navigation test
import { NavigationContainer } from '@react-navigation/native'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
const Stack = createNativeStackNavigator()
function renderWithNavigation(component, { initialRouteName = 'Test' } = {}) {
return render(
<NavigationContainer>
<Stack.Navigator initialRouteName={initialRouteName}>
<Stack.Screen name="Test" component={component} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
// Custom hook test
import { renderHook, act } from '@testing-library/react-native'
import { useCounter } from './useCounter'
test('increments counter', () => {
const { result } = renderHook(() => useCounter(0))
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})23. DetoxでE2Eテストをどう実装しますか?
Detoxは実機シミュレーター・エミュレーター上でアプリ全体をテストするツールです。
// Installation
// npm install detox --save-dev
// detox init -r jest
// .detoxrc.js
module.exports = {
testRunner: {
args: {
$0: 'jest',
config: 'e2e/jest.config.js'
},
jest: {
setupTimeout: 120000
}
},
apps: {
'ios.debug': {
type: 'ios.app',
binaryPath: 'ios/build/MyApp.app',
build: 'xcodebuild -workspace ios/MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build'
},
'android.debug': {
type: 'android.apk',
binaryPath: 'android/app/build/outputs/apk/debug/app-debug.apk',
build: 'cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug'
}
},
devices: {
simulator: {
type: 'ios.simulator',
device: { type: 'iPhone 15' }
},
emulator: {
type: 'android.emulator',
device: { avdName: 'Pixel_5_API_34' }
}
}
}
// e2e/login.test.js
describe('Login Flow', () => {
beforeAll(async () => {
await device.launchApp()
})
beforeEach(async () => {
await device.reloadReactNative()
})
it('should login successfully with valid credentials', async () => {
// Fill the form
await element(by.id('email-input')).typeText('test@example.com')
await element(by.id('password-input')).typeText('password123')
// Submit
await element(by.id('login-button')).tap()
// Verify navigation to dashboard
await waitFor(element(by.text('Welcome')))
.toBeVisible()
.withTimeout(5000)
})
it('should show error with invalid credentials', async () => {
await element(by.id('email-input')).typeText('wrong@example.com')
await element(by.id('password-input')).typeText('wrongpassword')
await element(by.id('login-button')).tap()
await expect(element(by.text('Invalid credentials'))).toBeVisible()
})
it('should navigate to forgot password', async () => {
await element(by.id('forgot-password-link')).tap()
await expect(element(by.text('Reset Password'))).toBeVisible()
})
})
// Detox commands
// detox build --configuration ios.debug
// detox test --configuration ios.debugデプロイと本番運用
24. 環境(dev、staging、prod)をどう管理しますか?
環境管理には用途別に分けた設定変数が必要です。
// Option 1: react-native-config
// .env.development
API_URL=https://dev-api.myapp.com
ANALYTICS_KEY=dev_key
// .env.staging
API_URL=https://staging-api.myapp.com
ANALYTICS_KEY=staging_key
// .env.production
API_URL=https://api.myapp.com
ANALYTICS_KEY=prod_key
// Usage
import Config from 'react-native-config'
const api = {
baseUrl: Config.API_URL,
analyticsKey: Config.ANALYTICS_KEY
}
// Option 2: JavaScript configuration file
// config/index.js
const ENV = {
development: {
apiUrl: 'https://dev-api.myapp.com',
analyticsEnabled: false,
logLevel: 'debug'
},
staging: {
apiUrl: 'https://staging-api.myapp.com',
analyticsEnabled: true,
logLevel: 'info'
},
production: {
apiUrl: 'https://api.myapp.com',
analyticsEnabled: true,
logLevel: 'error'
}
}
const getEnv = () => {
if (__DEV__) return 'development'
// Logic to determine staging vs prod
return 'production'
}
export const config = ENV[getEnv()]
// Option 3: Expo with app.config.js
// app.config.js
export default ({ config }) => {
const env = process.env.APP_ENV || 'development'
const envConfig = {
development: {
apiUrl: 'https://dev-api.myapp.com',
bundleId: 'com.myapp.dev'
},
production: {
apiUrl: 'https://api.myapp.com',
bundleId: 'com.myapp'
}
}
return {
...config,
extra: {
...envConfig[env],
env
},
ios: {
bundleIdentifier: envConfig[env].bundleId
},
android: {
package: envConfig[env].bundleId
}
}
}25. アプリストアにどうデプロイしますか?
デプロイにはビルド設定、メタデータ、提出作業が含まれます。
# Option 1: EAS Build (Expo)
# Installation
npm install -g eas-cli
# Configuration
eas build:configure
# eas.json
{
"cli": {
"version": ">= 5.0.0"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"distribution": "internal",
"android": {
"buildType": "apk"
}
},
"production": {
"autoIncrement": true
}
},
"submit": {
"production": {
"ios": {
"appleId": "your@email.com",
"ascAppId": "1234567890"
},
"android": {
"serviceAccountKeyPath": "./google-services.json",
"track": "production"
}
}
}
}
# Production build
eas build --platform all --profile production
# Store submission
eas submit --platform all --profile production
# Option 2: Fastlane (React Native CLI)
# Gemfile
source "https://rubygems.org"
gem "fastlane"
# ios/fastlane/Fastfile
default_platform(:ios)
platform :ios do
desc "Deploy to TestFlight"
lane :beta do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(scheme: "MyApp")
upload_to_testflight
end
desc "Deploy to App Store"
lane :release do
increment_build_number(xcodeproj: "MyApp.xcodeproj")
build_app(scheme: "MyApp")
upload_to_app_store(
skip_screenshots: true,
skip_metadata: true
)
end
end
# android/fastlane/Fastfile
default_platform(:android)
platform :android do
desc "Deploy to Play Store internal"
lane :beta do
gradle(task: "clean bundleRelease")
upload_to_play_store(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab"
)
end
end26. OTAアップデートをどう実装しますか?
Over-The-Airアップデートを使うと、ストアを介さずにJavaScriptコードを配布できます。
// With Expo Updates
import * as Updates from 'expo-updates'
async function checkForUpdates() {
if (__DEV__) return // Not in development
try {
const update = await Updates.checkForUpdateAsync()
if (update.isAvailable) {
// Download update
await Updates.fetchUpdateAsync()
// Ask user to restart
Alert.alert(
'Update available',
'A new version is available. Restart now?',
[
{ text: 'Later', style: 'cancel' },
{
text: 'Restart',
onPress: () => Updates.reloadAsync()
}
]
)
}
} catch (error) {
console.error('Update check error:', error)
}
}
// Automatic check on startup
function App() {
useEffect(() => {
checkForUpdates()
}, [])
return <AppNavigator />
}
// eas.json configuration for update channels
{
"build": {
"production": {
"channel": "production"
},
"preview": {
"channel": "preview"
}
}
}
// Command to publish an update
// eas update --branch production --message "Bug fix"
// With CodePush (Microsoft)
import codePush from 'react-native-code-push'
const codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
installMode: codePush.InstallMode.ON_NEXT_RESTART
}
function App() {
return <AppNavigator />
}
export default codePush(codePushOptions)(App)上級者向けの質問
27. アプリの起動時間をどう最適化しますか?
起動時間はユーザー体験を左右する重要な要素です。
// android/app/build.gradle
project.ext.react = [
enableHermes: true
]
// ios/Podfile
:hermes_enabled => true
// 2. Lazy loading screens
const HeavyScreen = React.lazy(() => import('./HeavyScreen'))
// 3. Defer non-critical initializations
import { InteractionManager } from 'react-native'
function App() {
useEffect(() => {
// Execute after first render
InteractionManager.runAfterInteractions(() => {
// Initialize analytics
Analytics.init()
// Prefetch data
prefetchCriticalData()
})
}, [])
return <AppNavigator />
}
// 4. Optimize splash screen
import * as SplashScreen from 'expo-splash-screen'
// Prevent automatic hiding
SplashScreen.preventAutoHideAsync()
function App() {
const [appIsReady, setAppIsReady] = useState(false)
useEffect(() => {
async function prepare() {
try {
// Load critical resources
await Font.loadAsync(customFonts)
await Image.prefetch(criticalImages)
// Restore authentication
await restoreAuth()
} catch (e) {
console.warn(e)
} finally {
setAppIsReady(true)
}
}
prepare()
}, [])
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// Hide splash screen
await SplashScreen.hideAsync()
}
}, [appIsReady])
if (!appIsReady) return null
return (
<View style={{ flex: 1 }} onLayout={onLayoutRootView}>
<AppNavigator />
</View>
)
}
// 5. Inline requires to defer imports
// metro.config.js
module.exports = {
transformer: {
inlineRequires: true
}
}
// Manual usage
function loadHeavyModule() {
const HeavyModule = require('./HeavyModule').default
return HeavyModule
}28. React Nativeアプリのセキュリティをどう扱いますか?
モバイルセキュリティでは複数の防御層を組み合わせる必要があります。
import * as SecureStore from 'expo-secure-store'
// or
import * as Keychain from 'react-native-keychain'
async function saveToken(token) {
await SecureStore.setItemAsync('auth_token', token)
}
async function getToken() {
return await SecureStore.getItemAsync('auth_token')
}
// 2. Certificate pinning for network calls
// android/app/src/main/res/xml/network_security_config.xml
/*
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.myapp.com</domain>
<pin-set>
<pin digest="SHA-256">AAAAAA...</pin>
<pin digest="SHA-256">BBBBBB...</pin>
</pin-set>
</domain-config>
</network-security-config>
*/
// 3. Jailbreak/root detection
import JailMonkey from 'jail-monkey'
function SecurityCheck() {
useEffect(() => {
if (JailMonkey.isJailBroken()) {
Alert.alert(
'Insecure device',
'This application cannot run on a rooted/jailbroken device'
)
}
}, [])
}
// 4. Code obfuscation
// metro.config.js (for Hermes)
module.exports = {
transformer: {
minifierConfig: {
mangle: true,
output: {
ascii_only: true
}
}
}
}
// 5. Screenshot/recording protection
import { usePreventScreenCapture } from 'expo-screen-capture'
function SensitiveScreen() {
usePreventScreenCapture() // iOS only
return <View>{/* Sensitive data */}</View>
}
// 6. Session timeout
function useSessionTimeout(timeoutMs = 5 * 60 * 1000) {
const lastActivity = useRef(Date.now())
const { logout } = useAuth()
useEffect(() => {
const subscription = AppState.addEventListener('change', (state) => {
if (state === 'active') {
const elapsed = Date.now() - lastActivity.current
if (elapsed > timeoutMs) {
logout()
}
} else {
lastActivity.current = Date.now()
}
})
return () => subscription.remove()
}, [timeoutMs, logout])
}29. アクセシビリティをどう実装しますか?
アクセシビリティはあらゆる利用者がアプリを使えるようにするために欠かせません。
import { AccessibilityInfo } from 'react-native'
// 1. Basic accessibility props
function AccessibleButton({ label, onPress, disabled }) {
return (
<TouchableOpacity
onPress={onPress}
disabled={disabled}
accessible={true}
accessibilityLabel={label}
accessibilityRole="button"
accessibilityState={{ disabled }}
accessibilityHint={`Tap to ${label.toLowerCase()}`}
>
<Text>{label}</Text>
</TouchableOpacity>
)
}
// 2. Group elements for screen readers
function ProductCard({ product }) {
return (
<View
accessible={true}
accessibilityLabel={`${product.name}, ${product.price} dollars`}
>
<Image
source={{ uri: product.image }}
accessibilityIgnoresInvertColors={true}
/>
<Text>{product.name}</Text>
<Text>${product.price}</Text>
</View>
)
}
// 3. Announce dynamic changes
function NotificationBadge({ count }) {
useEffect(() => {
if (count > 0) {
AccessibilityInfo.announceForAccessibility(
`${count} new notification${count > 1 ? 's' : ''}`
)
}
}, [count])
return (
<View accessibilityLabel={`${count} notifications`}>
<Text>{count}</Text>
</View>
)
}
// 4. Detect accessibility preferences
function useAccessibilityPreferences() {
const [isScreenReaderEnabled, setIsScreenReaderEnabled] = useState(false)
const [isReduceMotionEnabled, setIsReduceMotionEnabled] = useState(false)
useEffect(() => {
AccessibilityInfo.isScreenReaderEnabled().then(setIsScreenReaderEnabled)
AccessibilityInfo.isReduceMotionEnabled().then(setIsReduceMotionEnabled)
const screenReaderListener = AccessibilityInfo.addEventListener(
'screenReaderChanged',
setIsScreenReaderEnabled
)
const reduceMotionListener = AccessibilityInfo.addEventListener(
'reduceMotionChanged',
setIsReduceMotionEnabled
)
return () => {
screenReaderListener.remove()
reduceMotionListener.remove()
}
}, [])
return { isScreenReaderEnabled, isReduceMotionEnabled }
}
// 5. Adapt animations based on preferences
function AnimatedComponent() {
const { isReduceMotionEnabled } = useAccessibilityPreferences()
const animation = useSharedValue(0)
useEffect(() => {
animation.value = withTiming(1, {
duration: isReduceMotionEnabled ? 0 : 300
})
}, [isReduceMotionEnabled])
return <Animated.View style={animatedStyle} />
}30. 大規模なReact Nativeプロジェクトをどう構成しますか?
明快なアーキテクチャは保守性と拡張性を支えます。
// Recommended structure
src/
├── app/ # App configuration
│ ├── App.tsx
│ ├── Navigation.tsx
│ └── Providers.tsx
│
├── features/ # Feature modules
│ ├── auth/
│ │ ├── screens/
│ │ │ ├── LoginScreen.tsx
│ │ │ └── RegisterScreen.tsx
│ │ ├── components/
│ │ │ └── AuthForm.tsx
│ │ ├── hooks/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ └── index.ts # Public export
│ │
│ ├── products/
│ │ ├── screens/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── services/
│ │
│ └── cart/
│ └── ...
│
├── shared/ # Shared code
│ ├── components/
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ └── Card.tsx
│ ├── hooks/
│ │ ├── useDebounce.ts
│ │ └── useNetworkStatus.ts
│ ├── utils/
│ │ ├── format.ts
│ │ └── validation.ts
│ └── types/
│ └── index.ts
│
├── services/ # Global services
│ ├── api/
│ │ ├── client.ts
│ │ └── interceptors.ts
│ ├── storage/
│ │ └── secureStorage.ts
│ └── analytics/
│ └── analytics.ts
│
├── store/ # Global state
│ ├── slices/
│ └── index.ts
│
└── theme/ # Design system
├── colors.ts
├── typography.ts
├── spacing.ts
└── index.ts// Example feature module organization
// features/products/index.ts
export { ProductListScreen } from './screens/ProductListScreen'
export { ProductDetailScreen } from './screens/ProductDetailScreen'
export { useProducts } from './hooks/useProducts'
export { ProductCard } from './components/ProductCard'
// features/products/hooks/useProducts.ts
import { useQuery } from '@tanstack/react-query'
import { productService } from '../services/productService'
export function useProducts(categoryId?: string) {
return useQuery({
queryKey: ['products', categoryId],
queryFn: () => productService.getProducts(categoryId)
})
}
// features/products/services/productService.ts
import { apiClient } from '@/services/api/client'
import { Product } from '../types'
export const productService = {
async getProducts(categoryId?: string): Promise<Product[]> {
const params = categoryId ? { category: categoryId } : {}
const response = await apiClient.get('/products', { params })
return response.data
},
async getProduct(id: string): Promise<Product> {
const response = await apiClient.get(`/products/${id}`)
return response.data
}
}
// Import alias configuration
// babel.config.js
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
[
'module-resolver',
{
alias: {
'@': './src',
'@features': './src/features',
'@shared': './src/shared',
'@services': './src/services'
}
}
]
]
}まとめ
本記事の30問は、React Native面接で問われる必須知識を網羅しています。押さえるべき要点は次のとおりです。
- アーキテクチャ: ブリッジ、JSI、新アーキテクチャの理解
- コンポーネント: ナビゲーション、高性能リスト、アニメーション
- 状態とデータ: Zustand/Redux、React Query、ローカルストレージ
- パフォーマンス: FlatList最適化、メモ化、プロファイリング
- モバイル特有の話題: 権限、通知、ディープリンク
- テスト: Jest、Testing Library、Detox
- デプロイ: EAS Build、ストア配信、OTAアップデート
- セキュリティとアクセシビリティ: セキュアストレージ、WCAG対応
React Nativeの面接対策では、Web版Reactの知識に加えてモバイル固有の事項を理解する必要があります。実プロジェクトでの開発と実機テストを通じて、これらの概念を確実に身につけてください。
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
タグ
共有
関連記事

React Native:2026年に本格的なモバイルアプリを構築する
React Nativeを使ったiOS・Androidモバイルアプリ開発の実践ガイドです。環境構築からストアへの公開まで、必須の基礎知識をすべて解説します。

2026年のReact Native新アーキテクチャ:Hermes V1、ブリッジレスモードとインタビュー質問
React Nativeの新アーキテクチャを徹底解説。Hermes V1エンジン、ブリッジレスモード、TurboModules、Fabricレンダラーの仕組み、パフォーマンスベンチマーク、移行ガイド、技術面接の質問と回答を網羅。

Expo RouterによるReact Nativeファイルベースナビゲーション完全ガイド
Expo Routerを使ったReact Nativeのファイルベースルーティングを徹底解説。レイアウト、ダイナミックルート、型安全なナビゲーション、タブ、モーダル、ミドルウェアまで、2026年最新のパターンを網羅します。