반응형

styled-components 내부 속성에 css 변수 적용하기

const ShellButton = styled.button<ButtonStyles>`
  background-color: ${({ theme }) => theme.btnColor};
  color: ${({ theme }) => theme.btnTxtColor};
  border: none;
  border-radius: 10px;
  cursor: pointer;
  &:hover {
    opacity: var(--atomshell-hover-opacity);
  }

  ${({ color }) => color && getButtonColor(color)}
  ${({ size }) => size && getButtonSize(size)}
`;

버튼 컴포넌트를 작성하였고 hover시 0.7정도의 투명도를 지정하였다.
하지만 이부분이 추후 변경될수도 있고, 공통적으로 사용하기 위해 GlobalStyle쪽에서 변수화를 해두었는데, ts에서 오류가 발생하고 있었다.

'--atomshell-hover-opacity' 사용자 지정 프로퍼티를 확인할 수 없습니다

아래처럼 hover부분을 수정하고 해결하였다.

  &:hover {
    opacity: ${'var(--atomshell-hover-opacity)'};
  }
반응형
반응형

🚫 Pick 사용시 Type ~ does not satisfy the constraint

type Colors = 'white' | 'gray' | 'black' | 'red' | 'orange' | 'blue' | 'purple' | 'green';

type BadgeColors = Pick<Colors, 'black' | 'green' | 'blue' | 'red' | 'orange'>; // Error

특정 컬러 타입을 지정하고 거기서 필요한 타입만 뽑아서 새로운 타입들을 지정하기위해 코드를 작성하였는데, Type "black" does not satisfy the constraint와 같은 에러메시지를 만났다.


Pick을 사용하게 된 경우 타입자체가 반환되는게 아니라 객체 형태가 리턴되는것을 알 수 있다.

아래와 같이 코드를 변경하였다.

type Colors = 'white' | 'gray' | 'black' | 'red' | 'orange' | 'blue' | 'purple' | 'green';

type BadgeColors = Exclude<Colors, 'white' | 'gray' | 'purple'>; // Ok

Exclude<T, U>는 타입에서 특정 요소를 제외 시켜주는데, T에서 사용되는 요소 중 U에 해당하는 것을 제외시켜줍니다.

Exclude를 사용하게되면 타입으로 반환이 되기때문에 타입에러 없이 사용할 수 있습니다.

반응형
반응형

📷 바코드, QR 코드 인식하기

특정 바코드를 인식하여 문자열을 가져와야하는 기능을 만들어야 했다.

몇가지 카메라 관련 라이브러리가 있었지만, deprecated된 라이브러리는 제외하고 비교적 최근까지 버전업이 이루어지고 있는 라이브러리 중 react-native-camera-kit을 채택했다.

📦 라이브러리 설치 및 적용하기

⚙️ 설치하기

  • 라이브러리 설치
    npm install react-native-camera-kit --save
  • pod 패키지 설치
    cd ios && pod install

✍️ 권한 설정

카메라 사용을 위해 권한을 설정한다.

🤖 Android

프로젝트/android/app/src/main/AndroidManifest.xml 파일을 열어준다.

아래 내용을 상단에 추가한다.

<!-- camera 접근 제어 -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 진동 접근 제어 -->
<uses-permission android:name="android.permission.VIBRATE"/>

🍎 IOS

프로젝트/ios/프로젝트명/info.list 파일을 열어준다.

아래 내용을 하단에 추가한다.

<key>NSCameraUsageDescription</key>
<string>For taking photos</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>For saving photos</string>

⚠️ 설치간 이슈 에러 정리

라이브러리 설치 후 프로젝트 실행시 안드로이드가 정상적으로 열리지 않는 현상이 있을 수 있습니다.
프로젝트/android/build.gradle 파일을 열어줍니다.

buildscript {
    ext {
        ...
        // 기존 버전 주석
        // minSdkVersion = 21
        // kotlinVersion = "1.8.0"
        minSdkVersion = 23
        kotlin_version = "1.8.0"
    }
    dependencies {
            ...
            classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
    }
}
  • 기존 minSdkVersion의 21을 23으로 올려주었습니다.
  • 기존 kotlinVersion이라는 텍스트를 카멜케이스 kotlin_version으로 변경하고 dependencies에도 적용하였습니다.

이후 프로젝트 경로로 돌아와서 아래 명령어로 gradle 초기화를 진행하고 안드로이드 구동시 정상 동작을 확인했습니다.
$ ./gradlew clean

🧐 적용예제

전역 state사용을 위해 recoil의 atom을 사용한 예제입니다.

App.tsx

function App(): React.JSX.Element {

  async function requestCameraPermission() {
    try {
      const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
        title: '카메라 권한 요청',
        message: '앱에서 카메라를 사용하기 위해 권한이 필요합니다.',
        buttonNeutral: '나중에 묻기',
        buttonNegative: '취소',
        buttonPositive: '허용',
      });
      if (granted === PermissionsAndroid.RESULTS.GRANTED) {
        console.log('카메라 권한을 얻었습니다.');
      } else {
        console.log('카메라 권한을 거부했습니다.');
      }
    } catch (err) {
      console.warn(err);
    }
  }

  useEffect(() => {
    (async function () {
      await requestCameraPermission();
    })();
  }, []);

  return (
    ...
  );
}

ScanHooks.ts

export const ScanHooks = () => {
  const [scanText, setScanText] = useRecoilState(scanedTextAtom);

  const changeScanText = (text: string) => setScanText(text);
  const initScanText = () => setScanText('');

  return {scanText, changeScanText, initScanText};
};

const scanedTextAtom = atom({
  key: 'scanedTextAtom',
  default: '',
});

Barcode.tsx

import {useEffect, useRef} from 'react';
import {Dimensions, StyleSheet, Vibration, View} from 'react-native';
import {useNavigation} from '@react-navigation/native';
import {Camera, CameraType} from 'react-native-camera-kit';
import {ScanHooks} from '@hooks/common/ScanHooks.ts';

const Barcode = () => {
  const {changeScanText, initScanText} = ScanHooks();

  const ref = useRef(null);
  const navigation = useNavigation();

  useEffect(() => {
    initScanText();
  }, []);

  const onBarCodeRead = (event: any) => {
    changeScanText(event.nativeEvent.codeStringValue); // 바코드 텍스트 저장
    Vibration.vibrate(100); // 바코드 인식 알림을 위한 진동처리
    navigation.goBack(); // 이전페이지로 이동
  };

  return (
    <View style={styles.container}>
      <Camera
        style={styles.scanner}
        ref={ref}
        cameraType={CameraType.Back} // front/back(default)
        scanBarcode
        showFrame={false}
        laserColor="rgba(255, 0, 0, 0)"
        frameColor="rgba(255, 0, 0, 0)"
        surfaceColor="rgba(255, 0, 0, 0)"
        onReadCode={onBarCodeRead}
      />
    </View>
  );
};

export default Barcode;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    width: Dimensions.get('window').width,
    height: Dimensions.get('window').height,
  },
  scanner: {flex: 1},
});
반응형
반응형

Oh no, an error occurred

git 브랜치간에 개발 브랜치로 병합 후 아래와 같은 메시지의 오류가 발생하기 시작했다.

Oh no, an error occurred.
소스 병합을 하면서 podfile쪽에 소스가 꼬이면서 발생하는 이슈라고 하는데, 아래 글을 참고하여 해결하였다.


🧐 해결 방법

프로젝트/ios/Pods/Podfile.lock 파일을 삭제한다.
이후 pod install을 실행하여 확인한다.


참고 사이트.

반응형
반응형

HTTP 통신 허용하기

앱은 기본적으로 https의 보안 프로토콜만 통신을 허용하도록 설정되어 있다.
앱 개발 초기에는 백엔드 서버의 도메인조차 없는 경우가 허다하기 때문에 임시적으로 허용을하고 개발을 해야할텐데, 관련하여 정리하고자 한다.

🍎 IOS에서 HTTP 허용하기

프로젝트/ios/프로젝트명/info.plist 파일을 열어준다.

NSAllowsArbitraryLoads키의 값을 true처리 해준다.

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

🤖 Android에서 HTTP 허용하기

android의 경우 테스트를 위해 apk 배포시에도 동일하다.
해당 옵션을 처리해주면 apk파일도 http서버에 요청을 할 수 있게 된다.

프로젝트/android/app/src/main/AndroidManifest.xml을 열어준다.

application태그에 아래 옵션을 추가한다.
android:usesCleartextTraffic="true"

적용예시)

반응형
반응형

IOS 시뮬레이터 오류

react-native으로 ios 환경에서 정상적으로 동작하는지 확인하기 위해 시뮬레이터를 사용하여 확인을 많이 하는데, 종종 어제까지만 잘되던 프로젝트가 갑자기 안열리는 경우가 많이 있다.

이럴 경우에 대한 해결 방법을 정리하고자 한다.

📝 pod 초기화

일단 가장 손쉽게 해결할 수 있는 방법이다.
프로젝트 경로에서 ios경로로 이동한다.

cd ios

이후 아래의 명령어를 차례대로 수행한다.

pod deintegrate // pod 삭제

pod cache clean --all // pod 클린

pod install // pod 설치

npm start --reset-cache // react-native 프로젝트 캐시 삭제

정상적으로 시뮬레이터가 동작하는지 확인한다.

⚙️ Xcode 캐시 삭제

위 방법으로 시도해도 처리되지 않는다면 아래 방법을 통해 기존의 캐시를 삭제하여 초기화후 해결할 수 있다.


1. 시스템 설정을 열어준다.

2. 저장 공간을 검색 후 -> 개발자를 선택한다.

3. Xcode 프로젝트 빌드 파일탭에 Xcode 캐시가 존재하는데, 삭제 버튼으로 삭제해준다.

반응형
반응형

모바일에서도 페이지별로 구성되어 페이지를 이동하는 형태를 많이 사용되는데,Reactreact-router-dom처럼 페이지를 이동하는 방법을 알아보겠습니다.

🟢 React-Native Navigation 설정하기

📦 패키지 설치

먼저 프로젝트에 해당 패키지를 설치합니다

npm install @react-navigation/native @react-navigation/native-stack

설치가 완료되면 다음 종속성을 설치합니다(expo버전이 아닙니다)

npm install react-native-screens react-native-safe-area-context

여기까지 설치가 끝나면 ios에 pod를 설치를 진행합니다

cd ios
pod install
cd ..

여기까지 설치가 끝났으면 프로젝트를 재시작하면 좋습니다

🚀 프로젝트 구성하기

App.tsx

import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import HomePage from './src/pages/HomePage.tsx';
import ProfilePage from './src/pages/ProfilePage.tsx';

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Home"
          component={HomePage}
          options={{title: 'Welcome'}}
        />
        <Stack.Screen name="Profile" component={ProfilePage} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

/src/pages/HomePage.tsx

import {Button, Text, View} from 'react-native';
import {NativeStackNavigationProp} from '@react-navigation/native-stack';

interface HomePageProps {
  navigation: NativeStackNavigationProp<any, 'Home'>;
}

const HomePage = ({navigation}: HomePageProps) => {
  const linkProfile = () => {
    navigation.navigate('Profile', {name: 'Jane'});
  };
  return (
    <View>
      <Text>Home Page!!!</Text>
      <Button title="Profile 이동" onPress={linkProfile} />
    </View>
  );
};

export default HomePage;

/src/pages/ProfilePage.tsx

import {Button, Text, View} from 'react-native';
import {NativeStackNavigationProp} from '@react-navigation/native-stack';

interface ProfilePageProps {
  navigation: NativeStackNavigationProp<any, 'Home'>;
  route: any;
}
const ProfilePage = ({navigation, route}: ProfilePageProps) => {
  const linkHome = () => {
    navigation.navigate('Home', {title: '제목들어갈자리'});
  };
  return (
    <View>
      <Text>Profile Page</Text>
      <Text>{route?.param?.name} Profile </Text>
      <Button title="Home 이동" onPress={linkHome} />
    </View>
  );
};

export default ProfilePage;

결과

이동

코드구조

반응형
반응형

⭐️ Vite에서 SVG파일을 컴포넌트로 받아서 사용하기

🚫 오류 사항

vite 프로젝트에서 기존 방식으로 import하여 svg파일을 사용하면 아래와 같은 오류가 발생한다.

'".svg?react"' 모듈에 내보낸 멤버 'ReactComponent'이(가) 없습니다.
대신 '"
.svg?react"에서 ReactComponent 가져오기'를 사용하시겠습니까?ts(2614)

✅ vite-plugin-svgr 적용하기

  • vite-plugin-svgr설치
    vite에서는 바로 svg가 적용이 안되고 vite-plugin-svgr 활용해야 한다고 한다.

    npm install vite-plugin-svgr --save-dev    
  • vite.config.ts

    export default defineConfig({
    plugins: [
      react(),
      svgr({
        svgrOptions: {
          // svgr options
        },
      }),
    ],
    resolve: {
      alias: [
        { find: '@assets', replacement: '/src/assets' },
        { find: '@', replacement: '/src' },
      ],
    },
    });

    plugins 배열에 svgr()을 추가한다(옵션 커스텀이 필요하다면 추가)

  • src/vite-env.d.ts

    /// <reference types="vite/client" />
    /// <reference types="vite-plugin-svgr/client" />  //추가
  • src/SvgTest.tsx

    import SearchIconSVG from '@assets/images/svg/searchIcon.svg?react';
    export const SearchIcon = () => <SearchIconSVG />;

🔹 주의사항

import부분에서 from 끝부분에 "경로?react"를 붙여주고 구조분해할당으로 별칭을 주는게 아
사용할 컴포넌트명을 지정하여 사용하면 된다.

반응형