JIGGAG

11월 한달동안 로그

2022년 12월 6일

읽어보았던

추억의 CSS-in-JS

  • (번역) 우리가 CSS-in-JS와 헤어지는 이유
  • styled-components를 좋았지만 현재는 이런저런 이유로 사용하지 않게 되었다
    • 그럼에도 흥미롭다고 생각해서 돌아보기
  • 런타임 CSS-in-JS는 코드 안에 작성된 스타일을 런타임에 해석하여 적용한다
  • colocation이라는 장점을 가장 먼저 접했다
    • 컴포넌트 안에 스타일이 함께 위치한다는 것이 코드를 관리하는데 용이해진다
  • dynamic props 사용할 수 있다
    •   const Comp = styled(View)`
        	backgroundColor: ${(props) => props.color};
        	height: ${(props) => props.size};
        `
        ...
        <Comp color="red" size={15} />
      
    • 특정 상태에 따라 스타일을 다르게 적용하기 위해서 각각의 스타일을 따로 구성하거나 인라인으로 넣어줘야하는데
    • 이런식으로 props를 전달하여 바로 사용할 수 있던 것에서 가장 큰 매력을 느꼈다
  • 반면 런타임이기 때문에 컴포넌트 레이아웃 직전에 스타일을 계산하고 반영해야하는 성능 이슈가 존재한다
    • 이를 개선한 것으로 제로런타임(컴파일타임) css-in-js 가 있당 (ex. Linaria)
    • 런타임에 스타일을 계산하는게 아니라 빌드 타임에 미리 스타일을 만들어 두는 방법
    • 하지만 빌드타임에는 dynamic props에 대한 처리는 불가능하다
    • 결국 인라인으로 파라미터 처리를 하게 되는데, 개인적으로 인라인은 ❌ 하고 있다
  • 그 외 번들 크기나 디버깅이 어려운 것은 개인적으로 단점으로 느껴지지 않았다
    • 장점이 더 좋으니깐...!
    • 번들 크기 줄이는 방향으로 무언가 선택할 수 있다면 그것이 그 방향의 장점이 되는 것 아닐까
  • 결국 가장 큰 문제점은 렌더타임의 성능 이슈이다
    • 모든 컴포넌트가 css-in-js 라면 실질적인 UI 변경사항이 없더라도 컴포넌트가 리렌더 될 때 스타일도 다시 계산해야한다
    • 렌더마다 스타일 직렬화를 위한 비용이 추가되는 것이다
  • 이러한 css-in-js를 사용하면서 얻고자 했던 장점만을 가져가는 형태로 스타일을 작성할 수 없을까?
    • 하는 고민을 하게 된다 (계속 읽다보니 글에서도 언급하고 있었다 🥳)
    • 런타임, 컴파일타임 모두 해결할 수 없었던 오직 장점만 갖고 있는 시스템
  • colocation + dynamic props 이것 아닐까
    • colocation을 위해 우선 하나의 컴포넌트 파일에서 해결하고자 한다
    • dynamic props를 처리하기 위해서는 컴포넌트 안에 존재해야한다
  • 추가로 위에서 언급한 내용 중 가장 큰 장점으로 느꼈졌던 colocation에 대한 글도 같이 읽어본다
  • 좋았던 기억은 뒤로한채 단점이 더 크다고 느껴지기 때문에 사용하지 않고 있다 ㅠㅠ
    • 다이나믹 스타일을 상태로 관리하고 있다
    • colocation + dynamic props를 처리하는 방법으로
    • 컴포넌트 안에서 useMemo로 하나의 상태를 스타일로 주고 있다
    • 리렌더 상황에서 스타일을 재계산 하는 이슈를 그나마 해결하면서 번들 사이즈가 줄었다는 위안
  • 지금 갑자기 의문을 갖게 되는데
    const dynamicStyles = useMemo(() => ({
    	a: {...},
    	b: {...},
    }), [...]);
    
    ...
    <Comp style={dynamicStyles.a} />
    <Comp style={dynamicStyles.b} />
    
    • 이거 메모이제이션 제대로 되고 있는거 맞나..????
    • 확인하러 가야지
  • (+추가)
  • 확인결과 메모이제이션된 컴포넌트는 리렌더 되지 않았다
    • 리렌더는 되었지만 DOM이 업데이트 되지 않았다
    • 실질적으로 우리가 얻고자 하는 것이 이것 아닌가
  • 디펜던시만 제대로 정리된다면 메모이제이션 효과를 보는게 맞다

forwardRef + memo의 props 타입 추론 어떻게 하지

  • forwardRef를 쓰면 이상하게 props 타입이 추론이 안되어서 불편했다
    • 내가 무언가 잘못 사용한건 아닐까
  • 마치 경험기를 작성한 느낌이였다
  • 맨 처음 사용하고 있었던 방법은 forwardRef 후 memo 하는 것이다
    const Component = forwardRef((props: Props, ref: RefObject<View>) => {
      return <View ref={ref} {...props}></View>;
    });
    const MemoizedComponent = memo(Component);
    
    • 이렇게 하면 MemoizedComponent를 사용했을때 Props 추론이 정확하게 된다
    • 하지만 MemoizedComponent를 얻기 위해 너무 불필요한 단계를 거쳐야한다고 생각했다 (귀찮았다)
  • memo(forwardRef(...)) 이렇게 쓰고 싶다!
    const Component = memo(forwardRef<View, Props>((props, ref) => {
      return <View ref={ref} {...props}></View>;
    }));
    
    • 그래서 이렇게 정리했더니 Props 인터페이스에서 PropType is defined but prop is never used이 발생하였다
    • 쓰고 있지만 쓰고 있는 것을 알지 못하는게 타입 추론이 잘 되고 있지 않은 것 같다
  • 대체 왜 추론이 안될까 하면서 제네릭을 통한 HoC로 빼보았다
    export const withMemoRef = <Props, Ref>(
      Component: (props: Props, ref?: ForwardedRef<Ref>) => JSX.Element,
    ) => memo(forwardRef<Ref, Props>(Component));
    
    const Component = withMemoRef<Props, View>((props, ref) => {
      return <View ref={ref} {...props}></View>;
    })
    
    • 이제 아무런 이슈 없이 정상적으로 타입이 잡히고 있었다
  • 중간 과정에서 위 글에 나온 것처럼 타입을 assert하거나 props 이름을 바꾸거나 시도해봤는데 모두 아름답지 않았다... ㅠㅠ
  • 글에서 제안하는 마지막 방법인 declare 하는 것은 생각 못해봤는데 이게 가장 깔끔해보인다
    • 근데... memo로만 감싸면 ㅠㅠ
    • PropType is defined but prop is never used이 재발한다

RN 안드 빌드 실패

  •   Execution failed for task ':app:mergeDebugNativeLibs'.
      > A failure occurred while executing com.android.build.gradle.internal.tasks.MergeNativeLibsTask$MergeNativeLibsTaskWorkAction
         > 2 files found with path 'lib/arm64-v8a/libc++_shared.so' from inputs:
            - /Users/jiggag/Projects/react-native-starter/node_modules/react-native/ReactAndroid/build/intermediates/library_jni/debug/jni/arm64-v8a/libc++_shared.so
            - /Users/jiggag/.gradle/caches/transforms-3/87b4b130d2c85494c51740eb879d8459/transformed/jetified-react-native-0.70.0-debug/jni/arm64-v8a/libc++_shared.so
           If you are using jniLibs and CMake IMPORTED targets, see
           https://developer.android.com/r/tools/jniLibs-vs-imported-targets
    
  • 미리 빌드된 네이티브 라이브러리 포함
    • Gradle에서 외부 네이티브 빌드에 사용되지 않은 미리 빌드된 네이티브 라이브러리를 패키징하도록 하려면 모듈의 src/main/jniLibs/ABI 디렉터리에 이 라이브러리를 추가합니다.
    • 4.0 버전 이전의 Android Gradle 플러그인은 jniLibs 디렉터리에 CMake IMPORTED 타겟을 포함해야 앱에 포함됩니다. 이전 버전의 플러그인에서 이전하는 경우 다음과 같은 오류가 발생할 수 있습니다.
    • Android Gradle 플러그인 4.0을 사용한다면 IMPORTED CMake 타겟에서 사용하는 모든 라이브러리를 jniLibs 디렉터리 밖으로 이동하여 이러한 오류를 방지하세요.
  • 현재 프로젝트에서 사용하고 있는 AGP는 v7인데 v4에서 발생하는 이슈가 나타났다?? ⁉️
  • 이슈로 가보았지만 검색되는 것은 없었고 FabricExample이 있어서 이를 참고하여 build.gradle을 다시 세팅해보았다
    • 무언가 수상한 차이점을 발견하였는데
    • Fabric을 설정하면 react-native 모듈 대신 ReactAndroid를 바로 바라보도록 설정이 되어있었다
  • Fabric 적용하면서 c++ 영향이 여기에서 시작된걸까?
    • 혹시나 기대를 해보았지마.. 여전히 실패하였다
  • 다시 처음으로 돌아왔다
  • 같은 이슈를 겪은 이 블로그처럼 이슈 내용 그대로 중복 발생하는 패키지를 정리하도록 설정을 하면 될 것 같다
    • 그렇게 해도 안되던데....
    • 맨 처음 이슈를 마주하고 시도했던 패키지 설정을 변경해보았을때 계속 이슈 발생하였다
  • AGP + jniLibs 관련해서 react-native-gradle-plugin 에 검색해보았다
  • 이게 지금 적용이 안되고 있는건가??
    • 빌드하면 발생하는 lib*.so를 하나씩 추가해보았다
    • 오류 메세지의 so가 달라지고 있었다
    • 하나씩 다음 단계로 넘어가고 있는듯 하다!!
    • 하나씩 추가해서는 끝이 나지 않을 것 같았다
    • 희망을 갖고 RNGP의 옵션을 전부 그대로 추가해보았다
    • 그랬더니 되지 않는다?!
    • 아..... **/*.so 통째로 넣어볼까?
    • 이게 되네....
  • 이렇게 넣으면 안될 것 같은데
    • 이 옵션이 수정된 RNGP 버전이 아직 RN@0.70.4에는 반영되지 않았나보다 🤔
    • RN@0.71.0에 반영되려나!!!
    • v0.71-rc를 또 먼저 들어가볼까 고민하다가도... 지난번 빌드 오류를 릴리즈 버전까지 해결하지 못했던 것을 떠올리면 기다려본다
  • (+추가)
  • RN@0.71.0-rc.0build.gradle에 이런 변경사항을 보았다
    • // The version of react-native is set by the React Native Gradle Plugin
      implementation("com.facebook.react:react-native")
      
    • 이건 분명 위의 이슈가 해결됨을 암시하는 코멘트
  • 악마의 속삭임
    • 시작해보았지만 hermes 부터 안되는걸 🙈
    • hermes를 꺼야 안드 빌드가 된다
  • 이건 어떤 메리트가..?!?!
    • 버전을 올리면서 hermes는 포기 했지만
    • 위에서 임시로 대응했던 **/*.so 설정을 제거할 수 있다는점?
    • 인줄 알았으나 react-native-safe-area-context와 충돌 🚨
    • gradle이 항상 발목을
  • iOS 빌드는 왜 실패하는 걸까요
    • fabric과 함께 c++이 들어왔다가 이번에 버전 올리면서 사라졌는데 (RN이 알아서 처리하기로 했나보다)
    • react-native/ReactCommon/hermes/inspector/chrome/MessageConverters.cpp:222:34: no member named 'toString' in 'facebook::jsi::BigInt'
    • 왜 터지는걸까요 🥳
    • 🚨 pod update 하는데 옵션을 안붙이고 써버렸네
    • 옵션이 있어야 Cpp 처리를 하는데 옵션이 없으니 fabric을 사용하지 않는 버전으로 업데이트를 받아버림