React Navigation's state

박은정·2022년 8월 1일
0

리액트네이티브

목록 보기
4/27
post-thumbnail

react navigation에서 제공하는 state를 활용해서 내가 접속했던 페이지의 history를 확인했습니다.

Accessing the 'state' property of the 'route' object is not supported.

하지만 route 객체에서 state 속성을 제공하지 않는다는 에러문구를 맞이했는데 다행히 useNavigationState hooks을 사용해서 금방 해결은 했다.

왜 이런 에러가 발생한걸까... 일단 공식문서를 살펴보기로 했습니다.

Route props

react navigation 공식문서을 번역한 내용입니다.

앱 안에서 각각의 화면 컴포넌트는 route prop과 함께 자동으로 제공됩니다.
이 prop은 현재 route에 대한 다양한 정보가 들어있습니다.

  • key : 화면의 고유 키입니다. 이 화면으로 이동하는 동안 자동으로 생성되거나 추가되었습니다.
  • name : 화면의 이름입니다. 네비게이터 컴포넌트 계층에서 정의됩니다.
  • path : 화면을 연 경로를 포함하는 옵션적인 문자열은 딥 링크를 통해 화면을 열 때 존재합니다.
  • params : navigation 작업 중에 정의된 매개변수를 포함하는 옵션적인 객체입니다. navigate('Twitter', { user: 'Dan Abramov' })

6.x 버전의 공식문서에 따르면 route 객체에서 state 속성을 따로 제공하지는 않는다고 되어잇는데 이전버전을 찾아봤는데 5.x버전에서 아래와 같은 내용을 찾을 수 있었습니다.

5.x버전의 공식문서 내용
경우에 따라 route 객체에서 state property를 찾을 수도 있습니다. 이 속성은 하위 네비게이터의 상태를 포함하며, 화면 안에서 네비게이터가 있을 때 존재할 수 있습니다.
하위 네비게이터에서 처음 navigation된 이후에만 초기화됩니다. 따라서 하위 네비게이터가 있더라도 이 속성은 정의되지 않을 수 있기 때문에 사용하지 않는 것이 좋습니다.

react navigation 공식문서를 번역한 내용입니다.

네비게이션 state(상태)는 리액트 네비게이션이 앱의 네비게이션 구조와 히스토리(이력)을 저장하는 상태입니다. 상태 재설정, 사용자 지정 초기 상태 제공 등과 같은 고급 작업을 수행할 때 네비게이션 state의 구조를 알면 유용합니다.

state는 다음과 같은 자바스크립트 객체입니다.

const state = {
  type: 'stack',
  key: 'stack-1',
  routeNames: ['Home', 'Profile', 'Settings'],
  routes: [
    { key: 'home-1', name: 'Home', params: { sortBy: 'latest' } },
    { key: 'settings-1', name: 'Settings' },
  ],
  index: 1,
  stale: false,
};

모든 네비게이션 state에는 다음과 같은 몇 가지 속성이 있습니다.

  • type : state가 속한 네비게이터의 타입으로 stack, tab, drawer가 있습니다.
  • key : 네비게이터를 식별하는 고유한 키입니다.
  • routeNames : 네비게이터에 정의된 화면 이름입니다. 이것은 각 화면의 문자열을 포함하는 고유한 배열입니다.
  • routes : 네비게이터에 렌더링되는 라우트(=화면)의 목록입니다. 또한 스택 네비게이터에서 히스토리를 나타냅니다. 이 배열에 하나 이상의 항목이 있어야 합니다.
  • index : 라우터 배열 중에서 포커스된 경로의 인덱스입니다.
  • history : 방문한 화면의 목록입니다. 이 속성은 선택 사항이며 일부 네비게이터에는 없습니다. 예를 들어, 코어의 tab, drawer 네비게이터만 있을때 히스토리 배열의 항목 모양은 네비게이터에 따라 달라질 수 있습니다. 이 배열에는 하나 이상의 항목이 있어야 합니다.
  • stale : 오래된 속성을 명시적으로 false로 설정하지 않는 한, 네비게이션 상태는 오래된 것으로 간주됩니다. 이것은 state가 "rehydrated"되어야 한다는 것을 의미합니다.

rehydrated이란..?

hydrate = 수화

출처

우리 몸에 수분을 보충하는 행위를 의미합니다만 리액트에서 왜 이 용어를 사용하는지 알아보겠습니다.

React.render(element, container[, callback])

리액트는 DOM에 리액트 컴포넌트를 렌더링해주는 render 메서드를 제공합니다.
컨테이너 DOM에 리액트 요소를 렌더링하는 함수입니다. 이 render 메서드는 컨테이너의 자식으로 리액트 컴포넌트를 넣어주는데, 기존에 이미 렌더링된 리액트 컴포넌트가 있다면 새로 렌더링하는 것이 아니라 업데이트만 해줍니다.
그리고 렌더링이 완료되면 세번째 인자로 전달된 콜백함수가 실행되게 해줍니다.

즉, ReactDOM의 render메서드는 컴포넌트를 렌더링한 다음에 콜백함수를 실행합니다.

한편 ReactDOm에는 hydrate이라는 메서드도 존재합니다.

ReactDOM.hydrate(element, container[, callback])

위와 같이 메서드의 모양이 render와 동일하지만, hydrate는 렌더링은 하지 않고 이벤트핸들러만 붙여줍니다.
서버사이드렌더링을 해서 이미 마크업이 채워져있는 경우에는 굳이 render메서드를 사용할 필요가 없기 때문에 hydrate으로 콜백함수만 붙여주면 됩니다.

클라이언트사이드렌더링을 하는 경우에는 타겟 컨테이너에 리액트 컴포넌트가 렌더링된 적이 없기 때문에 render메서드를 사용해야하지만 서버사이드렌더링 프레임워크와 함께 리액트를 사용하는 경우에는 hydrate 사용을 고려해야 합니다.

서버가 완성된 HTML을 내려줄 때 동적인 것을 정적인 것으로 만드는 행위를 dehydrate이라고 표현합니다.
그리고나서 자바스크립트가 실행되면서 리액트가 정적인 HTML와 store를 동적인 리액트 컴포넌트 트리와 스토어로 변환하는 과정이 일어나는데 이러한 과정을 Rehydrate, hydrate이라고 합니다.

문제는 이렇게 rehydrate가 일어나면서 쓸데없이 화면이 한번 더 그려지는 현상이 발생합니다.
왜냐하면 리애그는 서버에서 완성된 HTML을 받아서 이미 화면에 제대로 렌더링이 되었는지 안되었는지 모르고 자신이 할 일을 하기 때문입니다.
따라서 서버사이드렌더링을 하는 경우에는, ReactDOM의 render메서드가 아니라 hydrate메서드를 사용합니다.

서버에서 보내준 HTML로 렌더링된 화면은 그냥 단순한 그림이라 리액트가 관리할 수 없습니다.
서버사이드렌더링을 하더라도 리액트 컴포넌트를 리액트가 관리하기 위해서는 hydration 작업을 꼭 해줘야합니다.

정리
리액트에서 hydration이라는 용어를 사용하는 이유는 서버사이드렌더링으로 만들어진 수분이 없는, 정적인 HTML와 스토어로부터 동적인 상태로 변화하는, 수분을 보충하는 과정인 hydrate가 일어나기 때문이라고 추측한다고 합니다.


routes 배열에서의 각각의 route object는 다음 속성이 포함될 수 있습니다.

  • key : 화면의 고유 키입니다. 이 화면으로 이동하는 동안 자동으로 생성되거나 추가되었습니다.
  • name : 화면의 이름입니다. 네비게이터 구성요소 계층에서 정의됩니다.
  • params : navigate 중에서 정의된 매개변수를 포함하는 선택적인 객체입니다. navigate('Home', {sortBy: 'latest'})
  • state : 이 화면 안에서 중첩된 하위 탐색기의 탐색 상태를 포함하는 선택적 객체입니다.

예를 들어서, Home 화면 내에서 중첩된 tab 네비게이터를 포함하는 stack navigator는 다음과 같은 네비게이터 state를 가질 수 있습니다.

const state = {
  type: 'stack',
  key: 'stack-1',
  routeNames: ['Home', 'Profile', 'Settings'],
  routes: [
    {
      key: 'home-1',
      name: 'Home',
      state: {
        key: 'tab-1',
        routeNames: ['Feed', 'Library', 'Favorites'],
        routes: [
          { key: 'feed-1', name: 'Feed', params: { sortBy: 'latest' } },
          { key: 'library-1', name: 'Library' },
          { key: 'favorites-1', name: 'Favorites' },
        ],
        index: 0,
      },
    },
    { key: 'settings-1', name: 'Settings' },
  ],
  index: 1,
};

중첩된 네비게이터가 있더라도 네비게이션이 발생할 때까지 경로 객체의 상태 속성이 추가되지 않기 때문에 네비게이션을 사용할 수 없습니다.

Partial state objects: state객체의 부분

이전에 navigation state의 stale 속성에 대해 언급이 있었는데, stale 네비게이션 state은 사용 전에 누락된 키를 추가하고 잘못된 화면을 제거하는 등의 state를 다시 hydrate하거나 고정해야한다는 것을 의미합니다.

사용자로서 걱정할 필요가 없는게, 리액트 네비게이션은 stale 속성이 false로 설정되지 않는 이상 state의 모든 문제를 자동으로 해결할 것입니다. 사용자 지정 라우터를 작성하는 경우 getRehydratedState 메서드를 사용해서 state를 수정하는 사용자 지정 rehydration 로직을 작성할 수 있습니다.

이 기능은 재설정, 초기 상태 제공 등과 같은 작업을 수행할 때 유용합니다.
네비게이션 상태에서 많은 속성을 안전하게 생략하고 리액트 네비게이션을 사용해서 이러한 속성을 추가할 수 있기 때문에 코드를 단순하게 만들 수 있습니다.
예를 들어, 키 없이 route배열만 제공할 수 있으며, 리액트 네비게이션은 다음과 같은 작업을 수행하는 데 필요한 모든 항목을 자동으로 추가합니다.

const state = {
  routes: [{ name: 'Home' }, { name: 'Profile' }],
};

rehydration 작업 이후에는 다음과 같이 변합니다.

const state = {
  type: 'stack',
  key: 'stack-1',
  routeNames: ['Home', 'Profile', 'Settings'],
  routes: [
    { key: 'home-1', name: 'Home' },
    { key: 'settings-1', name: 'Settings' },
  ],
  index: 1,
  stale: false,
};

여기서 네비게이션 state는 key, routeNames, index 등과 같은 조금 누락된 항목을 채웁니다.

존재하지 않는 화면과 같은 잘못된 데이터를 제공할 수도 있고 이는 자동으로 수정됩니다.
잘못된 state를 사용해서 코드를 작성하는 것은 권장되지는 않지만 상태 지속성 등의 작업을 수행하는 경우 매우 유용할 수 있습니다. 이 경우 업데이트 후 구성된 화면이 변경되어 리액트 네비게이션이 state를 자동으로 수정하지 않으면 문제가 발생할 수 있습니다.

리액트 네비게이션으로 잘못된 상태를 수정하려면 state에서 stale: false 가 없는지 확인해야 합니다. stale: false가 있는 state는 유효한 state이라고 간주되며 리액트 네비게이션은 이러한 state를 수정하려고 시도하지 않습니다.

initialState에서 state를 제공할 때 리액트 네비게이션은 항상 stale state로 간주해서 상태 지속성과 같은 작업이 state를 추가로 조작하지 않고도 원할하게 작동하도록 합니다.

useNavigationState

react navigation 공식문서

useNavigationState는 화면을 포함하는 네비게이션의 네비게이터 상태에 대한 접근을 제공하는 hook입니다. 네비게이션 상태에 따라 렌더링하려는 경우에 유용합니다.

네비게이터의 state는 내부적인 것이며 보조 release에서 변경될 수 있습니다. 따라서 꼭 필요한 경우가 아니라면 인덱스 및 경로를 제외한 navigation state의 속성을 사용하지 않는 것이 좋습니다.

selector 함수를 인수로 사용합니다. 셀렉터는 전체 네비게이션 상태를 수신하고 다음 상태에서 특정 값을 반환할 수 있습니다.

const index = useNavigationState(state => state.index);

셀렉터 함수는 불필요한 재렌더링을 줄이는데 도움이 되기 때문에 관심이 있을때만 화면이 재렌더링됩니다. 전체 state객체가 실제로 필요한 경우 이 작업을 명시적으로 수행할 수 있습니다.

const state = useNavigationState(state => state);

이 hook은 고급 케이스에 유용하면 주의하지 않으면 성능 문제가 발생하기 쉽습니다. 대부분의 경우 네비게이터의 상태가 필요하지 않습니다.

navigation.getState() 함수도 혐재 네비게이션 상태를 반환합니다.
주요 차이점은 useNavigationState 훅은 값이 변경될 때 다시 렌더링을 트리거하는 반면, navigation.getState()은 트리거하지 않습니다.

function Profile() {
  const routesLength = navigation.getState().routes.length; // Don't do this

  return <Text>Number of routes: {routesLength}</Text>;
}

이러한 예시에서는 새 화면을 눌러도 이 텍스트가 업데이트 되지 않습니다.
useNavigationState 훅을 사용하면 예상대로 작동합니다.

function Profile() {
  const routesLength = useNavigationState(state => state.routes.length);

  return <Text>Number of routes: {routesLength}</Text>;
}

그러면 navigation.getState() 은 렌더링된 내용에 대해 신경 쓰지 않는 이벤트 리스너에서 대부분 유용합니다. 대부분의 경우에서는 useNavigationState 훅을 사용하는 것이 더 선호됩니다.

결론

스택오버플로우에서 react navigation에서 제공하는 route.state을 활용해서 현재 navigation의 history를 파악했지만, 이는 react navigation 5.x 버전까지만 접근이 되었고 이마저도 navigation 메서드를 통해 화면이 변경되었을때만 존재하기 때문에 route.state로 접근하는것을 권장하지 않았습니다.

react navigation에서 제공하는 state 속성을 접근하기 위해서 react navigation에서는 useNavigationState 훅을 제공합니다. 이마저도 꼭 필요할때만, indexpath에 대해서만 사용하는 것을 권장하기는 합니다.

profile
새로운 것을 도전하고 노력한다

0개의 댓글