Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

전반적인 React Native 개발 관련 #56

Open
1 of 2 tasks
hansol775 opened this issue Aug 16, 2023 · 4 comments
Open
1 of 2 tasks

전반적인 React Native 개발 관련 #56

hansol775 opened this issue Aug 16, 2023 · 4 comments
Assignees

Comments

@hansol775
Copy link

Description

시간을 잡아서 미팅이나 QNA세션을 진행하고 싶었는데..

그동안 정말 바쁜 나머지 이슈에 간략하게 정리합니다. ㅎㅎ

제가 2019년부터 2023년까지 5년의 기간동안 React Native로 앱을 개발해왔는데요,

한 서비스만 2년동안 유지보수도 해봤고, 한달에 3~4개의 앱들도 개발해보면서 겪었던 경험들을 정리하려고 합니다.

1. axios -> apisauce

저는 5년동안 axios가 아닌 apisauce를 사용하고 있습니다.
axios를 표준화했다고 생각하시면 되는데요, 요즘 axios는 안써서.. 습관이되서 쓰고 있는데
익숙해진만큼 빠른 개발을 제공합니다.

import { ApiResponse, create } from 'apisauce';
import { isEmptyObject } from '../utils/helpers';

const baseURL = process.env.NODE_ENV === 'development' ? '' : '';

export const API = create({
    baseURL,
    withCredentials: true,
});

function methodLog(response: ApiResponse<any, any>) {
    const isSuccess = response.data?.status === 0;
    const method = response.config?.method?.toLocaleUpperCase();
    const bodyLog = (body: any) => {
        if (body instanceof FormData) return body;
        if (typeof body === 'string') return JSON.parse(body);
        return undefined;
    };
    switch (method) {
        case 'GET':
            console.log(
                `${isSuccess ? '😄' : '🤢'} %c${method}`,
                'background: #191919; color: #14C38E',
                response.status,
                response.config?.url,
                '\n',
                {
                    data: response.data,
                    params: isEmptyObject(response.config?.params) ? undefined : response.config?.params,
                },
            );
            break;
        case 'POST':
            console.log(
                `${isSuccess ? '😄' : '🤢'} %c${method}`,
                'background: #191919; color: #40DFEF',
                response.status,
                response.config?.url,
                '\n',
                {
                    data: response.data,
                    body: bodyLog(response.config?.data),
                    params: isEmptyObject(response.config?.params) ? undefined : response.config?.params,
                },
            );
            break;
        case 'PUT':
            console.log(
                `${isSuccess ? '😄' : '🤢'} %c${method}`,
                'background: #191919; color: #FFD24C',
                response.status,
                response.config?.url,
                '\n',
                {
                    data: response.data,
                    body: bodyLog(response.config?.data),
                    params: isEmptyObject(response.config?.params) ? undefined : response.config?.params,
                },
            );
            break;
        case 'PATCH':
            console.log(
                `${isSuccess ? '😄' : '🤢'} %c${method}`,
                'background: #191919; color: #FFD24C',
                response.status,
                response.config?.url,
                '\n',
                {
                    data: response.data,
                    body: bodyLog(response.config?.data),
                    params: isEmptyObject(response.config?.params) ? undefined : response.config?.params,
                },
            );
            break;
        case 'DELETE':
            console.log(
                `${isSuccess ? '😄' : '🤢'} %c${method}`,
                'background: #191919; color: #F47C7C',
                response.status,
                response.config?.url,
                '\n',
                {
                    data: response.data,
                    params: isEmptyObject(response.config?.params) ? undefined : response.config?.params,
                },
            );
            break;
        default:
            break;
    }
}


API.addMonitor((response) => {

    if (response.problem === 'CANCEL_ERROR') return;
    if (response?.originalError) {
        // api error 발생 시 처리할 로직 추가
    }

    // 개발 모드 일 경우에만 log 찍기
    if (process.env.NODE_ENV === 'development') methodLog(response);

    // status code에 따라 처리할 로직 추가
    if (response.data?.status === 403) return;
    if (response.data?.status === 400) return;
    if (Math.abs(response.data?.status) >= 500) return;
});

위 코드가 api index.tsx 파일의 일부분입니다. 웹에서도 쓸 수 있으니 활용해보세요.

2. Custom Bottom Tab 사용하기

react navigation은 Custom Bottom Tab을 제공하는데요, 기존의 직사각형 바텀탭과 다르게 나만의 바텀탭을 구현하기 쉽습니다.

const Router = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator
        initialRouteName={'Splash'}
        screenOptions={{
          gestureEnabled: true,
          // cardStyleInterpolator: forSlide,
          animation: 'slide_from_right',
          headerShown: false,
        }}>
        <Stack.Screen name="Splash" component={Splash} />
        <Stack.Screen
          name="MainTab"
          component={MainTab}
          options={{
            animation: 'fade',
            // cardStyleInterpolator: forFade,
          }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

위와 같이 저는 Container안에 부모 Stack을 둡니다.
부모 Stack 안에 MainTab이라는 Tab Navigation을 두는데요,

const MainTab = () => {
  return (
    <Tab.Navigator
      initialRouteName="홈"
      screenOptions={{headerShown: false, lazy: true}}
      tabBar={renderTabBar}>
      <Tab.Screen name="메뉴1" component={Main1} />
      <Tab.Screen name="메뉴2" component={Main2} />
      <Tab.Screen name="메뉴3" component={Main3} />
      <Tab.Screen name="메뉴4" component={Main4} />
      <Tab.Screen name="메뉴5" component={Main5} />
    </Tab.Navigator>
  );
};

위와 같이 각 컴포넌트도 Stack으로 구성하면 바텀탭이 필요한 화면과, 필요없는 화면 분리가 쉽고 관리하기가 편해집니다.

단점은... 카카오톡 공유하기 등의 다이나믹 링크로 디테일한 화면으로 바로 넘어가기 힘들다는 점.. 입니다 ㅎㅎ

그 외엔 장점만 가득해서 한번 사용해보세요 ㅎㅎ

3. Modal 사용하기

화면이 많아지면 Routor 코드가 엄청나게 길어져 유지보수하기 불편할 수 있습니다.

저는 Modal을 아주 많이.. 잘 사용하는 편인데요,

React Native Modal 을 사용하고 있고 WebView랑 같이 쓰면 환상의 조합으로 쓰실 수 있습니다.

기본 코드는 아래와 같습니다.

<Modal
                useNativeDriver
                hideModalContentWhileAnimating
                isVisible={isModal}
                onBackButtonPress={() => setIsModal(false)}
                deviceWidth={width}
                deviceHeight={height}
                backdropColor="#fff"
                backdropOpacity={0}
                style={{ margin: 0 }}>
                <View
                    style={{
                        flex: 1,
                        paddingTop: top,
                        backgroundColor: '#fff',
                    }}>
                    <BasicHeader
                        title={'Web View'}
                        backpage={() => setIsModal(false)}
                    />
                    {isLoad && (
                        <View
                            style={{
                                ...StyleSheet.absoluteFill,
                                justifyContent: 'center',
                                alignItems: 'center',
                                zIndex: 1,
                            }}>
                            <ActivityIndicator />
                        </View>
                    )}
                    <WebView
                        source={{ uri: url }}
                        onLoad={() => {
                            setIsLoad(false);
                        }}
                    />
                </View>
            </Modal>

일단 3가지 기본 설정들에 대해 코드를 적어보았는데요,

그 외에 추가적인 부분은 여러분 코드를 보면서.. 하겠습니다 ㅋㅋㅋㅋ

To-do

  • todo 1
  • todo 2

ETC

@chajuhui123
Copy link
Member

저와 민정님 모두 RN 프로젝트가 처음이여서 낯선 개념들을 하나씩 해결하고 있는데 좋은 글 공유 감사드립니다 🙏
특히 2번 같은 경우는 당장 저희 하단 네비게이션에 적용할 수 있을 것 같네요! 특히 <바텀탭이 필요한 화면과, 필요없는 화면 분리> 해당 부분을 고민하고 있었는데 도움이 되는 내용입니다..!
바쁘신 일정에도 검토해주셔서 감사합니다! ㅎㅎ

@minimalKim
Copy link
Collaborator

RN에서 제공하는 stylesheet의 모든 기능을 emotion에서 제공하지 않는군요! 상세한 스타일 적용이 필요한 곳에서 Stylesheet를 사용해 보아야겠습니다..! apisauce도 이번에 처음 알게된 라이브러린데, axios보다 Problem Code에 대해 자세한 정보를 주기 때문에 에러 파악이 용이할 것 같습니다. 개인적으로라도 꼭 써보싶네요ㅎㅎ 좋은 내용들 공유 감사합니다!👍

@hansol775
Copy link
Author

추가적으로 RN에서는 수직 ScrollView 안에 수직 ScrollView가 있으면 안됩니다.(수평은 가능)

기본적인 리스트뷰는 RN에서 기본적으로 제공하는 FlatList를 이용하여 구현합니다.

<View style={{ flex: 1 }}>
                    <FlatList
                        ref={scrollRef}
                        showsVerticalScrollIndicator={false}
                        initialNumToRender={5}
                        data={commentList}
                        renderItem={renderItem}
                        keyExtractor={(item) => item.created_at}
                        ListEmptyComponent={renderEmpty}
                    />
                </View>

ScrollView를 최적화했다고 생각하시면 됩니다.

RN에서 이미지는 Image 컴포넌트로 다 구현가능하며,

큰 이미지의 빠른 로드를 위해 react native fastimage라는 라이브러리를 쓰기도 합니다.

버튼의 경우 Button을 쓰기 보다는

TouchableOpacity를 사용합니다.

<TouchableOpacity onPress={() => Linking.openURL(`tel:${detailData.phone_number}`)} activeOpacity={0.8} style={styles.contentsWrapper}>
                <Text allowFontScaling={false} style={styles.contentsTitle}>
                    시설 전화번호
                </Text>
                <Text allowFontScaling={false} style={styles.contents}>
                    {detailData.phone_number}
                </Text>
            </TouchableOpacity>

Pressable 도 사용하셔도 됩니다.

<Pressable
                            onPress={() => navigation.navigate('ReviewDetail', { data: detailData })}
                            style={{ borderWidth: 1, borderColor: '#4AABFF', borderRadius: 8, justifyContent: 'center', alignItems: 'center', paddingVertical: 10 }}
                        >
                            <Text allowFontScaling={false} style={{ fontFamily: fonts.NOTO, fontSize: 13, lineHeight: 20, color: '#3A3A3A' }}>
                                리뷰 더보기
                            </Text>
                        </Pressable>

@hansol775
Copy link
Author

Native Alert은

Alert 컴포넌트를 사용합니다.

Alert.alert('오류', '', [{ text: '닫기', onPress: onBackPress, style: 'default' }], { cancelable: false });

ios, android 공통 알럿창은 modal로 직접 만들어야 합니다.

작은 알럿창, toast 같은 경우는 직접 만들어도 되고,

react-native easy-toast 등과 같은 라이브러리로 대체 가능합니다.

추가로 NativeBase라는 다양한 컴포넌트 모아놓은(ant-d 같은) RN 전용 라이브러리도 있으니 사용하셔도 됩니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants