Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

부귀영화

React Navgation navigate with object params 본문

개발/React Native

React Navgation navigate with object params

Jinhoda 2022. 3. 8. 14:58
navigation.navigate('FoodSetChangeCount', {state});

React Navigation에서 navigate를 사용하면 다른 화면으로 이동할 수 있는데, 이때 params에 전달하고자 하는 정보를 넣어서 보낼 수 있다. 

 

포스팅하고 있는 내용과는 별개로 React Navigation에서는 params로 object를 그대로 전달하는 방법을 지양해달라고 말하고 있다. 뒤에서 서술할 내용과도 연관이 있고, https://reactnavigation.org/docs/params/#what-should-be-in-params 를 참고하면 

 

However, this is an anti-pattern. Data such as user objects should be in your global store instead of the navigation state. Otherwise you have the same data duplicated in multiple places. This can leads to bugs such as the profile screen showing outdated data even if the user object has changed after navigation.

 

It also becomes problematic to link to the screen via deep linking or on the Web, since:

  1. The URL is a representation of the screen, so it also needs to contain the params, i.e. full user object, which can make the URL very long and unreadable
  2. Since the user object is in the URL, it's possible to pass a random user object representing a user which doesn't exist, or has incorrect data in the profile
  3. If the user object isn't passed, or improperly formatted, this could result in crashes as the screen won't know how to handle it

A better way is to pass only the ID of the user in params

 

라고 한다. 다음부터는 애초에 이런 식으로 객체를 전달하는 것이 아닌 Redux에 넣어놓고 ID만을 전달하는 방법으로 구현하는 것이 더 좋을 것 같다. 하지만 프로젝트를 진행할 당시에는 이미 먼 길을 떠난 상태(...)였기 때문에 객체를 보내는 방법을 유지할 수밖에 없었고, 이 방식을 사용하면서 나타난 문제점을 해결한 과정을 써보도록 하겠다. 

 

// FoodSetPresenter
<ChangeCountButton
  onPress={() => {
    navigation.navigate('FoodSetChangeCount', {state});
  }}>
  <ChangeCountText>수량변경</ChangeCountText>
</ChangeCountButton>

FoodSet 화면에서 FoodSetChangeCounter로 이동하면서 위와 같이 state를 넣어줘서 사용하려고 했다. 전달받는 화면에서는 아래와 같이 params를 불러올 수 있다.

// FoodSetChangeCounterContainer
const [state, setState] = useState({
    isLoaded: false,
    ...route.params.state,
  });

데이터도 잘 불러와져서 그냥 사용했지만 이후에 큰 문제가 발생했다(...)

 

먼저 FoodSet 화면에서의 state는 다음과 같다

  const [state, setState] = useState({
    memberCnt: 1,
    foodSet: {},
    items: [],
    id: route.params.id,
  });

 

눈여겨 봐야할 부분은 음식 세트를 먹을 인원수를 나타내는 memberCnt와, items 배열이 담고 있는 item 객체의 count 변수이고, 둘 다 1로 초기화되어 있다. FoodSet 화면에서는 memberCnt만 수정할 수 있으며 FoodSetChangeCount 화면에서는 memberCnt와, item들의 count를 수정할 수 있다

왼쪽 FoodSet에서는 총 인원수만, 오른쪽 FoodSetChangeCount에서는 총 인원수와 각 구성품의 개수를 변경할 수 있다

FoodSet에서 인원수를 변경하고 FoodSetChangeCount으로 넘어가면 변경된 인원수가 잘 반영되었지만, 문제는 구성품 변경에서 나타났다. 기획에서 원했던 것은 FoodSetChangeCount에서 구성품을 변경하더라도 다시 FoodSet으로 돌아왔을 때 구성품의 개수는 1로 초기화되어 있어야 하는 것이었다. 그러나 FoodSetChangeCount로 navigate할 때 state를 같이 보내서 이동한 다음, 구성품을 변경하고 다시 FoodSet으로 돌아오면 구성품의 개수가 1이 아닌 FoodSetChangeCount에서 변경했던 개수로 보여지는 문제가 발생했다. 

 

즉 의도했던 시나리오는 FoodSet[총 인원수: 1, 각 구성품 개수: 1] -> FoodSetChangeCount[총 인원수: 2, 각 구성품 개수: 3] -> FoodSet[총 인원수: 2, 각 구성품 개수: 1] 이었지만, FoodSet[총 인원수: 1, 각 구성품 개수: 1] -> FoodSetChangeCount[총 인원수: 2, 각 구성품 개수: 3] -> FoodSet[총 인원수: 2, 각 구성품 개수: 3]으로 동작하고 있었다. 

 

해당 이슈를 전달 받았을 때 다행히 곧바로 깊은 복사, 얕은 복사가 떠올랐다. state 객체는 Object 자료형으로 javascript에서는 참조값이기 때문에 변수가 객체의 주소를 나타내기 때문에 복사했을 때 원본 객체의 주소 값을 가리키게 된다. 따라서

navigation.navigate('FoodSetChangeCount', {state});

로 params를 전달하면 state의 값이 아닌 주소가 전달되어 FoodSetChangeCount에서 구성품의 개수를 변경했을 때 FoodSet에서도 변경된 것이다. javascript에서 객체를 깊은 복사하려면 재귀적으로 돌면서 새로운 객체를 생성하거나, JSON.Stringfy 등의 방법이 있지만 lodash 라이브러리를 사용하면 더 쉽게 복사할 수 있다고 해서 사용해보았다.

<ChangeCountButton
  onPress={() => {
    const _state = _.cloneDeep(state);
    navigation.navigate('FoodSetChangeCount', {_state});
  }}>
  <ChangeCountText>수량변경</ChangeCountText>
</ChangeCountButton>

이후 FoodSetChangeCount에서 변경해도 FoodSet에 변화가 없음을 확인했다. 앞으로는 글로벌 저장소에 넣어놓고 id를 전달해서 값을 주고 받는 방식으로 구현할 것이다.