Neoself의 기술 블로그

React Native와 비교해보는 Swift,SwiftUI 본문

개발지식 정리/Swift

React Native와 비교해보는 Swift,SwiftUI

Neoself 2024. 10. 8. 23:06

저처럼 React Native 개발을 해오다가, Swift와 SwiftUI 앱개발 하고자 기술스택을 변경한 사람들의 시간을 절약시키고자, 제가 여태껏 Swift로 모바일 앱을 제작하면서 느꼈던 React Native와의 유사점과 차이점들을 중점으로 설명을 드리고자 합니다. 

 

1. 상태관리

우선 Swift로 넘어오면서, 가장 적응하기 어려웠던 것은 상태관리 방식이였습니다.

import React, { useState } from 'react';

function HomeScreen() {
    const [selectedTags, setSelectedTags] = useState([]);
    const [sortOption, setSortOption] = useState('default');
    const [currentTab, setCurrentTab] = useState(0);

    return (
        // View content
    );
}

 

React Native

React Native 프레임워크의 경우, 각 컴포넌트마다 useState 훅을 통해 상태관리가 필요한 변수들을 선언하여 뷰 컨텐츠 내부에서 접근이 가능합니다.

 

SwiftUI

SwiftUI의 경우에는 ObservableObject & ObservedObject 속성 래퍼로 View와 ViewModel 간 관계를 선언한 후, ObservableObject 클래스 내부에 선언된 @Published 속성 래퍼로 상태변수를 선언 및 정의합니다. 여기서 @Published 속성 래퍼가 적용된 변수의 경우, useState 변수와 같이 변경될때마다 해당 변수가 활용되는 뷰가 업데이트된다는 점은 동일합니다.

class HomeViewModel: ObservableObject {
    @Published var selectedTags: [String] = []
    @Published var sortOption: String = "default"
    @Published var currentTab: Int = 0
}

struct HomeView: View {
    @ObservedObject var viewModel: HomeViewModel
    
    var body: some View {
        // View content
    }
}

struct HomeViewWithState: View {
    @State private var selectedTags:[String] = []
    
    var body: some View {
     	// View content
    }
}

 

하지만, 상황에 따라 @State 속성 래퍼를 병행하여 상태를 관리하는 방안도 Swift에는 존재합니다. 이 속성 래퍼 또한 데이터의 변경을 관찰하고 UI를 업데이트에 사용된다는 점에서 @Published 래퍼와 역할을 공유하지만, @State는 주로 바텀시트 표시 여부나, 애니메이션 재생여부와 같이 뷰 내부에서 간단한 로컬상태를 관리할 때 사용됩니다. 

래퍼 @Published @State
범위 여러 뷰에서 공유될 수 있는 데이터에 사용됨.  주로 private로 선언되어, 특정 뷰 내부에서만 사용됨
사용 방법 ObservableObject를 준수하는 클래스 내에서 선언하고, 뷰에서는 @ObservedObject나 @StateObject로 해당 객체를 관리 뷰 구조체 내부에서 직접 선언
소유권 개발자가 직접 관리하는 객체의 일부 SwiftUI가 관리하며, 뷰의 생명주기와 연결됨
데이터 복잡성 복잡한 참조 타입에 더 적합함 간단한 값 타입에 더 적합함

 

이때 @State가 뷰의 생명주기와 연결된다는 것은 뷰가 처음 생성될 때, @State 프로퍼티를 위해 할당된 메모리가 뷰가 제거될때까지 유지된다는 것을 의미합니다. 때문에, @State 변경이나 다른 이유로 인해 뷰가 재생성, 즉 업데이트 되더라도 SwiftUI는 내부적으로 이 값을 저장하고 있다가, 뷰가 다시 그려질 때 복원합니다.

 

2. 비동기 작업 처리

다음은 비동기 작업 처리입니다.

 

React Native

제가 React Native를 사용했을 당시에는 axios 라이브러리를 통해 비동기 데이터 처리를 하였는데요.

export const testFunc = async (todo) => {
    let endpoint = `/api/todos`
    try {
        const response = await axios.get(
          url + endpoint
        )
        return response.status
    } catch (e) {
    	console.log(e)
    }
}
// 실제로는 AxiosInstance를 직접 생성하여 api 함수를 호출하였으나,
// 로직 설명을 위해 인스턴스 생성없이 바로 데이터 통신을 수행하는 구문을 작성해보았습니다.

 

Swift

주로 Promise와, async/await를 사용해 비동기 처리를 하는 React Native와 달리 Swift에서는 Combine 프레임워크를 사용해 비동기 작업과 데이터 스트림을 처리합니다.

 

func fetchTodos() {
    todoService.fetchAllTodos(mode: sortOption)
        .receive(on: DispatchQueue.main)
        .sink { completion in
            // Handle completion
        } receiveValue: { todos in
            // Update state with fetched todos
        }
        .store(in: &cancellables)
}

 

3. 네비게이션

React Native

저는 React Native를 사용할 때는, 주로 React Navigation 라이브러리를 통해 스크린 내비게이션을 구현하였습니다.

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

const Stack = createStackNavigator();

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

 

 

SwiftUI

이에 반해 SwiftUI는 NavigationStack와 NavigationLink를 사용해 내비게이션을 구현할 수 있습니다. 여기서 NavigationStack은 React Native에서의 Stack.Navigator 역할을 한다고 이해하면 쉽습니다.

NavigationView {
    List(viewModel.todos) { todo in
        NavigationLink(destination: DetailView(todo: todo)) {
            Text(todo.title)
        }
    }
}

 

4. 리스트 렌더링

React Native

import { FlatList } from 'react-native';

<FlatList
    data={filteredTodos}
    renderItem={({ item }) => <TodoItem todo={item} />}
    keyExtractor={item => item.id}
/>

리스트 렌더링의 경우, React Native에서는 LazyRendering에 최적화된 FlatList 컴포넌트를 제공하고 있는데요. 

 

SwiftUI

List {
    ForEach(viewModel.filteredTodos) { todo in
        TodoItemView(todo: todo)
    }
}

SwiftUI는 List와 ForEach를 통해 FlatList와 유사한 컴포넌트를 구현할 수 있습니다.

허나, Apple의 HIG(Human Interface Guideline)를 충족하는 리스트를 기본 제공하고 있기 때문에, React Native의 FlatList와 유사한 UI를 구현하려면 .listRowSeparator(.hidden)과 같은 많은 메서드 체이닝이 필요합니다...

 

이 외에도 스타일링의 경우, React Native는 StyleSheet를, SwiftUI는 메서드 체이닝을 통해 스타일 적용한다는 차이점 또한 존재합니다.

 

감사합니다.