반응형

문제 상황

// _app.tsx

function App({ Component, pageProps }) {
  useEffect(() => {
    console.log('rendering')
  }, [])
  
  return <Component {...pageProps} />
}

영향을 줄만한 코드 일체없이

_app.tsx에서 위와 같이 코드를 설정했음에도

useEffect가 두번도는 큰 문제가 발생했다.

 

이것을 나중에 api요청과 처리 과정에서 발견했는데,

redux 코드에 문제가 있는건지

re-rendering 과정에 사이드 이펙트가 발생하는건지

한참을 돌고 돌아 문제의 원인을 찾았다.

(15분은 낭비한듯..)

 

문제 원인

바로, react의 strict모드 설정 이슈였다.

strict모드의 어떤 부분에서 이슈가 발생했는지

stict모드가 무엇인지 살펴보자.

 

react docs에 보면 아래와 같은 문장이 있다.

Strict mode can’t automatically detect side effects for you, 
but it can help you spot them by making them a little more deterministic. 
This is done by intentionally double-invoking the following functions:

1. Class component constructor, render, and shouldComponentUpdate methods
2. Class component static getDerivedStateFromProps method
3. Function component bodies
4. State updater functions (the first argument to setState)
5. Functions passed to useState, useMemo, or useReducer

사이드 이펙트를 방지하는데 도움을 주기위해

5가지 경우에 대해 함수를 두번 호출한다고 명시되어있다.

 

그 중에서도 내 눈에 띈것은 Function component body...

함수형 컴포넌트의 Body구문에서 두번 호출된다면

어지간하면 두번 호출된다는 것이라 생각된다.

 

문제 해결

그렇다. strict 모드를 해제하면 일단

두번 호출되는 문제는 해결된다.

// next.js 기준
// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
    reactStrictMode: false, // <-- 이 부분 false로 변경
    swcMinify: true
};

module.exports = nextConfig;

 

Strict 모드란?

strict모드가 어떤 도움을 주는지

해제했을때 어떤 문제가 발생할 수 있는지

가볍게 살펴보자

 

문서 첫 줄에 이렇게 명시되어있다.

"StrictMode는 애플리케이션 내 잠재적인 문제를 알아내기 위한 도구이다."

그리고 "Strict 모드는 개발 모드에서만 활성화되며, 프로덕션에 영향을 주지는 않는다."

 

즉 위의 문제도 사실 프로덕션에서는 발생하지 않을 이슈였던것이다.

그럼에도 개발단에 거슬리긴 했다.

 

다른 도움되는 부분이 없다면 해제하고 마무리하는것으로 하자.

Strict가 도움을 주는 부분 6가지 (확장 예정)

1. Identifying components with unsafe lifecycles
2. Warning about legacy string ref API usage
3. Warning about deprecated findDOMNode usage
4. Detecting unexpected side effects
5. Detecting legacy context API
6. Ensuring reusable state

 

사이드 이펙트 방지가 마음에 드는데

방지를 위해 두번 요청하는 (우리가 부작용이라 생각했던) 동작이

아래와 같은 경우에 발생한다고 한다.

렌더링 단계 생명주기 메서드는 클래스 컴포넌트의 메서드를 포함

1. constructor
2. componentWillMount (or UNSAFE_componentWillMount)
3. componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
4. componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
5. getDerivedStateFromProps
6. shouldComponentUpdate
7. render
8. setState

 

 

결론

아직 대부분 class component의 생명주기 메서드들에 해당하는 내용으로 보인다.

그러므로 나는 일단 strict mode는 해제하도록 하겠다.

 

 

참고

반응형
반응형

누군가 내게 이런 질문을 해주었고,

다시 한번 고민해보고 공부해볼 좋은 기회가 되었다.

(보다 정확한 공부를 위해 React Docs를 살펴보았다.)

 

useEffect

둘을 비교하기 위해서는 useEffect를 먼저 이해할 필요가 있다.

 

useEffect를 이렇게 설명할 수 있을것 같다.

1) 특정 조건이 발생할 때(인자),

2) 지정된 명령을 수행하라(실행함수).
3) 컴포넌트가 제거될때 수행할것이 있다면 return을 작성하라.

 

(구조)

useEffect(실행 함수, 인자);


(사용 예시)

useEffect(() => {

}, [])

useEffect(() => {
  return () => {
  }
}, [])

useEffect(() => {
  return () => {
  }
}, [state.user])

 

 

useEffect의 동작 기준

1. 인자가 빈 배열인 경우: 모든 레이아웃 배치와 그리기를 마친 후 실행됨

2. 인자가 빈 배열이 아닌 경우: 배열의 state나 props가 변경될 때 실행됨

 

 

모든 레이아웃 배치와 그리기를 마친 후 실행된다고 했는데,

이 부분에 대해서 더 자세히 살펴보자.

 

useEffect 상세 실행순서

1) component render가 시작됨

2) rendered component가 화면에 그려짐

3) useEffect가 실행됨

 

자, 여기까지 살펴보고아래의 코드가 동작했을 때

사용자가 어떤 경험을 하게될지 예상해보자.

const [username, setUsername] = useState('')
useEffect(() => {
  setUsername('홍길동')
}, [])

return (
  <p>{username || '비회원'}</p>
)

사용자 경험에 어떤 문제가 있었을지

대충 눈치챘을 것이다.

 

사용자는 비회원이 아님에도

비회원을 목격한 뒤 홍길동이라는 이름이 보여지는 것을

목겨하게 될것이다.

 

 

useLayoutEffect

이러한 상황을 위해 useLayoutEffect가 존재한다.

 

useLayoutEffect 상세 실행순서

1) component render가 시작됨

2) useLayoutEffect가 동기적으로 실행됨

3) rendered component가 화면에 그려짐

4) (useEffect가 실행됨)

 

실행 순서 덕분에 위와 같은 상황에서는

useLayoutEffect를 쓴다면

조금 더 좋은 사용자 경험을 제공할 수 있다.

 

 

 

그렇다면 언제 useEffect를 써야할까?

일반적으로는 useEffect를 쓰는것이 퍼포먼스 면에서 유리하다.

 

왜냐면 위에서 언급했듯이 useLayoutEffect는

동기적으로 동작하기 때문에 퍼포먼스면에서는

권장되지 않기 때문이다.

 

일반적으로 useEffect를 사용하도록 하며

특히나 구독이나 이벤트 핸들러의 설정 등의 상황에서는

useEffect를 통해 필요한 시점에서만 실행되도록 하고

메모리 누수 방지를 위해 return을 통해 구독 해지시키는 것이 좋다.

 

 


(번외)

그 외에 react를 처음 사용하다보면

한번쯤 useEffect로 인해 겪게되는 이슈가 있다.

 

"어? 왜 자꾸 상태 값이 초기값으로 나오지?"

 

Docs를 자세히 보면 위 문제의 원인을 확인할 수 있다.

원인은 바로 useEffect에서 다루는 state, props가 인자에서 누락되었기 때문이다.

(useEffect에서 변경된 state,props를 확인하려면 인자에 useEffect에서 다루는 모든 state,props가 있어야함)

 

이것을 달리 말하면,

useEffect에서 인자로 빈 배열을 받았다면

useEffect에서 다루는 state나 props는 항상 초기값을 갖는다는 뜻이다.

 

심지어 이 사항은

useEffect에서 호출하는 함수에도 모두 적용되는 사항이니

명확히 알고 사용해야한다.

 

 

 

(수정할 사항이 있다면 제발 알려주세요. 저에게 큰 도움이 됩니다.)

반응형
반응형

React 개발을 하다보면

state, props로는 depth가 너무 깊어지는

props drilling문제가 발생하는 시점이 온다.

 

또한,

단계별 진행이나 페이지 변경 후에도 상태를 유지해야 하는 List뷰 등에서

어려움을 겪게된다.

 

이러한 문제들을 해결해주는 것이

global state관리를 도와주는 라이브러리들이다.

그 중에서 local global state라고 부르도록 하겠다.

 

주요 상태관리 라이브러리

1. Context API

2. Recoil

3. MobX

4. Redux

 

위 4가지 정도가 있는데,

1에서 4로 갈수록 러닝커브가 높다고 생각하는 바이다.

그럼에도 각 장,단점이 있으니 알아보도록 하자.

 

1. Context API

ContextAPI는 4가지 중에 유일하게
React 내장 상태관리 기능이다.

즉, 별도의 라이브러리 설치 없이 사용 가능하다는 말이다.

 

Docs를 보고 처음 사용 하는 사람도
쉽게 적용할 수 있는 수준이니

러닝커브 또한 낮다고 할 수 있다.

 

ContextAPI가 처음 나왔을때는

언어설정이나 색상테마 등

잘 변경되지 않는 상태를 관리할때 쓰였다.

 

그러나 지금은

전역상태 관리로 사용하던

지역상태 관리로 사용하던

개발자의 자유일뿐

상태 관리 기능으로써 충분한 지원을 하고있다.

 

참고

 

그럼에도 다른 라이브러리들을

추가 설치해 사용하는 이유가 있다.

 

 

2. MobX

언급할 내용은 MobX뿐 아니라

Redux, Recoil등 다른 라이브러리들을

사람들이 사용하는 이유이다.

 

4가지 모두 상태관리라는 컨셉을 가지고 있지만 그 안에서 나뉘는 개별 특징들이 있다.

그중에 내게 맞는 특징을 갖는 라이브러리를 택하면 되는것이다.

 

MobX는 전역상태관리 기능을 제공한다.

뿐만 아니라 상태 업데이트 로직을 View Component 밖에서(코드 분리) 할 수 있도록 도와준다.

Redux에 비해 적은 보일러 플레이트 코드, 직관적인 코드를 갖는다는 특징도 있다.

 

다만, MobX에서는 여러개의 Store를 둘 수 있는데

그로인해 예상치 못한 업데이트 등이 발생할 수 있다는 단점아닌 단점이 있다.

 

3. Redux

Redux도 MobX와 마찬가지로

전역상태관리 + 상태 업데이트 로직 분리를 돕는다.

 

하나의 Store를 갖는 특징으로

상태가 업데이트 될때 정확하게(직관적으로)

해당하는 상태를 갖는 컴포넌트들만 업데이트 된다는 장점이 있다. (유지보수의 편안함)

 

다만, Redux는 비동기 처리를 위해

thunk, saga 등 추가적으로 라이브러리들이 붙게되고

보일러플레이트 코드가 너무 많아진다는 단점을 가지고 있다.

 

4. Recoil

최근 각광받고 있는 Recoil은

react를 개발/운영하고 있는 Facebook(Meta)에서 개발한 만큼

가장 react 친화적이라는 장점을 가지고있다.

뿐만 아니라 매우 사용 방법이 매우 간단하고 직관적이다.

 

ContextAPI가 상태를 일일이 만들어야 하는 과정을 가진데 반해

Atom을 통해 매우 간결하게 상태관리를 할 수 있다.

캐싱을 통한 최적화 기능은 보너스이다.

 

 

 

최근,

이직 준비를 하며 많은 기업들에서 Recoil을 도입하고 있는것을 보게되었다.
사이드 프로젝트에 적용해 공부중인데 매우 합리적인 선택지라는 생각이 든다.

 

현직 개발자들은 대부분 현명하고, 최선의 선택을 하기위해 노력하는 만큼

Recoil이 사랑받는데는 분명히 이유가 있다.

 

 

 

(수정할 사항이 있다면 자유롭게 알려주세요. 저에게 큰 도움이 됩니다.)

반응형
반응형
* What went wrong:
Could not determine the dependencies of task ':app:mergeDebugAssets'.
> Could not resolve all task dependencies for configuration ':app:debugRuntimeClasspath'.
   > Could not find com.yqritc:android-scalablevideoview:1.0.4.
     Searched in the following locations:
       - file:/Users/user/project/lll/node_modules/react-native/android/com/yqritc/android-scalablevideoview/1.0.4/android-scalablevideoview-1.0.4.pom
       - file:/Users/user/project/lll/node_modules/jsc-android/dist/com/yqritc/android-scalablevideoview/1.0.4/android-scalablevideoview-1.0.4.pom
       - https://repo.maven.apache.org/maven2/com/yqritc/android-scalablevideoview/1.0.4/android-scalablevideoview-1.0.4.pom
       - https://dl.google.com/dl/android/maven2/com/yqritc/android-scalablevideoview/1.0.4/android-scalablevideoview-1.0.4.pom
       - https://www.jitpack.io/com/yqritc/android-scalablevideoview/1.0.4/android-scalablevideoview-1.0.4.pom

react-native-video 설치 후

빌드 과정에서 위 에러가 발생했다.

 

먼저, 설치방법

yarn add react-native-video

cd io && pod install && cd ..

npx react-native link react-native-video

 

위 에러 해결을 위한 단서는

https://github.com/react-native-video/react-native-video/issues/2454

https://stackoverflow.com/questions/68835157/error-when-trying-to-run-my-react-native-app-on-android/68841906#68841906

 

위 링크에서 확인할 수 있었다.

// android/build.gradle
allprojects {
    repositories {
        .... # Keep the rest
        jcenter() {
            content {
                includeModule("com.yqritc", "android-scalablevideoview")
            }
        }
    }
}
반응형
반응형
cannot find symbol import android.support.v4.app.ActivityCompat;

react-native-iap를 설치하고

빌드하려보니 위의 에러가 나타났다.

 

검색결과

npx jetify
or
yarn jetify

위 두가지 명령어를 먼저

실행한 뒤

다시 빌드를 시도하면

정상적으로 진행되는 것을

확인할 수 있었다.

반응형
반응형

옵션에 vertical을 부여했을때

위와 같은 에러가 발생함을 발견했다.

 

크기 지정에 있어 에러가 발생하는듯 했고

Carousel의 Props을 확인해보니

Vertical 속성이 부여됐을때

sliderHeight와 itemHeight가 필수 옵션임을 확인할 수 있었다.

 

두 옵션을 임의로 지정해준 결과

에러가 해결됐음을 확인할 수 있었다.

 

에러가 발생했을때는 마땅한 이유가 있다.

특히, 라이브러리 사용에 있어 에러가 발생한 경우에는

제작자의 의도대로 사용하지 않았을 확률이 제법 있다.

 

에러 로그를 정확히 확인하고

분석하는 노력을 해보자.

반응형
반응형

https://mingule.tistory.com/65

 

React에서 setInterval 현명하게 사용하기(feat. useInterval)

들어가기 Babble의 방 목록 페이지에 들어가면 유저가 생성한 방들이 쭉 나열되어 있는 것을 볼 수 있다. (안타깝게도 유저가 없으면 방도 없다ㅜㅜ) 그리고 이 방들은 5초마다 서버에 요청을 보

mingule.tistory.com

위 블로그에서 언급한 원인으로

불완전한 함수인 setInterval로 인해

버그를 맞이할수있다.

 

간략히 말하자면

setInterval이 지연 시간을 보장해주지 않기 때문

 

따라서 ,

나는 빠른 주기로

state를 업데이트 하는

stopWatch 훅스를 만들때

아래와 같이 처리해서 목적을

달성하였따.

export function useStopWatch(start: boolean) {
  const [time, setTime] = useState(0);

  useEffect(() => {
    let interval: any = null;
        
    if (start) {
      const startedTime = Date.now();
      interval = setInterval(() => {
        setTime(Date.now() - startedTime);
      }, 50);
    } else {
      if (interval) clearInterval(interval);
    }

    return () => {
      if (interval) clearInterval(interval);
    }
  }, [start]);

  return { time, setTime };
}
반응형
반응형
<textares
  style={{ resize: 'none' }}
  onKeyDown={e => {
    const numberOfLines = (e.target.value.match(/\n/g) || []).length + 1;
    if (e.which === 13 && numberOfLines >= 3) {
      e.stopPropagation();
      e.preventDefault();
      return false;
    }
  }}
  onChange={onChange}
/>
반응형
반응형

1. LifeCycle

class 기반 react를 오랫동안 써왔다.

그동안은 LifeCycle이 굉장히 중요시 여겨지며

LifeCycle을 기반으로 적재 적소에 함수들을 호출해 개발했다.

 

Hooks로 기반을 변경한 뒤,

useEffect에 의존한 단조로운 LifeCycle위에서 개발하게 되었다.

 

그런데, 이 useEffect에서 호출되는 함수들은

어느 시점에 호출되는걸까?

 

useEffect : render -> useEffect -> (re-rendering)

 

DOM의 레이아웃 배치와 페인팅이 끝난 후,

useEffect의 사이트 이펙트에 해당하는 함수들이 호출된다.

 

여기서 아래와 같은 문제가 발생할 수 있다.

1) 사용자가 짧게나마 빈 페이지를 보게됨

2) 기본 값으로 구성된 페이지를 보게됨

 

대부분의 웹 페이지들이

페이지가 열릴때

데이터를 비동기적으로 불러와

state의 갱신을 동해 화면을 re-rendering해서

사용자에게 제공된다는 것을 생각하면

useEffect를 이런식으로 활용하는것에는

어느정도 문제가 있다고 볼수있겠다.

 

그러면 어떻게 하면 좋을까

하고 알아보니

useLayoutEffect라는 것이 있었다.

 

useLayoutEffect : useLayoutEffect -> render -> (useEffect) -> (re-rendering)

 

useEffect와 달리

DOM이 레이아웃 배치 및 페인팅을 하기 전에

즉, render 보다도 먼저 호출된다.

 

useLayoutEffect를 통해

완벽하게 위의 1), 2) 문제를 제어할수는 없어도

좀더 효과적으로 서비스를 제공할수는 있겠다.

 

(위 문제를 완벽하게 해결하려면 SSR방식의 next를 써야할듯하다.)

 

 

 

2. useState

useState는 단연코 가장 많이 쓰이는 hooks일것이다.

프로젝트를 진행하다보니

코드를 올바르게 작성한듯한데

state가 제때 갱신되지 않아서 의도와 다르게

결과값이 나오는 경우가 심심치않게 보였다.

 

이는 동일블록 내에서 setter를 사용할 때

Closer 구조로 인해 발생하는 문제로,

class기반의 react를 사용할때도 가지고 있던 문제다.

 

class기반에서도 

setState(prevState => ({ ...prevState, type: value }));

위와 같이 처리하곤 했었는데

hooks에서도 마찬가지로 위와 같은 방식으로

해당 문제를 해결할 수 있다.

 

예를 들어,

const [list, setList] = useState([]);


const updateList = (data) => {
  // setList(list.concat(data)); <-- X
  
  setList(prevList => {
    const newList = list.concat(data);
    return newList;
  });
}

이런식으로 처리하면 된다.

반응형
반응형

앱 개발이 완료되어가는 시점이다

 

눈이 안좋은 사람들이

시스템 설정상의 폰트 크기를 조정해서

앱의 텍스트나 디자인이 깨지는것을

방지하기위한 체크를 하는 도중

 

(안드로이드에서만)

앱을 백그라운드로 실행해둔 상태에서

환경 설정에서 폰트 크기를 변경한 뒤

다시 앱으로 돌아오니

앱이 튕겨버리는 현상이 나타났다.

 

추적결과

react-native-screens에서

문제가 있는것으로 발견되었다.

 

 rn-screens docs에도 나와 있듯이

// MainAcitivity.java

import android.os.Bundle;

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(null); <-- 여기
}

 

이 부분을 수정해줌으로써

튕겨버리는 이슈가 해결되었다.

 

그런데 여기서 파생된 이슈가 있었다.

우리는 기본적으로 폰트에

dp 단위  + font scale 무시를 적용하는데,

 

위의 null 처리로 인해

백그라운드의 앱이 열렸을때

무조건 그 당시의  font-scale을

1로 간주해버리는 것이 아닌가...

 

바꿔 말하자면,

사용자가 시스템 설정에서

폰트 크기를

1 -> 2로 변경한뒤 앱을 열면

앱이 2라는 것을 1이라는 기준으로 잡아버린다.

 

따라서, font scale을 무시하는

우리의 코드는 오작동을 했다.

(우리의 폰트 크기 = dp / font scale)

 

따라서, storage에 항상

이전 폰트 크기를 저장해뒀다가

폰트 크기가 변경된 경우

앱을 재실행 하는 코드를 추가하였다.

(재실행하면 font-scale 기준을 제대로 잡게됨)

 

const detectChangeFontScaleOnAndroid = async () => {
  const fontScale = PixelRatio.getFontScale();
  const _fontScale = await storage.getItem('fontScale');
  storage.setItem('fontScale', fontScale?.toString());
        
  if (_fontScale && _fontScale !== fontScale.toString()) {
    console.log('changed font scale');
    Platform.OS === 'android' && CodePush.restartApp();
  }
}

 

반응형