Posts 리액트 리팩토링
Post
Cancel

리액트 리팩토링

리액트 리팩토링

useEffect 사용

useEffect 올바른 사용

  • props, state 변경에 따라 다른 데이터, state를 업데이트해야할 때 useEffect의 사용을 자제하자.
  • useEffect vs EventHandler 중 어느 쪽에서 유저 인터랙션을 처리해야할까?
  • useEffect 내에서 data fetching을 할 때 주의해야할 점.


race condition 문제

주로 API 요청을 보낼 때 발생함. 요청을 여러 번 보낼 때, 앞 요청이 끝나기 전에 뒤 요청이 먼저 끝날 수도 있다. 이러한 상황을 항상 고려하자.

예시

  1. “true”란 응답이 올 때까지 앞 요청에서 “false”란 응답이 왔는데, 뒷 요청에서 “true”란 응답이 온다고 해보자. 뒷 요청이 먼저 끝나면 나중에 오는 앞 요청의 응답이 잘


Silent Error를 없애자

1
2
3
4
5
6
7
8
9
10
11
12
const onClickCorrect = async () => {
  if (
    selectedUserState !== null 
  ) {
    try {
      const res = await applyForUser({selectedUserState})
      setApplicationId(res.applicationId);
      navigate('/waiting');
    } catch (e) {
      console.log(e);
    }   
}};
  • 위와 같이 클릭 시 발동되는 이벤트핸들러가 있다고 해보자. 만약 selectedUserState 가 null 일 경우 유저는 아무리 클릭을 해도 아무런 반응을 얻지 못할 것이다.
  • 따라서 else 문을 추가하여 selectedUserState 가 null 일 경우를 대비하자.

새로고침, 뒤로가기 고려

컴포넌트가 새로고침이나 뒤로가기를 통해 다시 마운트 될 시, 고려 해야 하는 부분이 몇 가지 있다.

  • 상태가 초기화 된다.
  • useEffect가 실행된다.
  • 컴포넌트 내에서 react-query 사용 시, refetch가 이루어진다.


아래 코드를 보자. 컴포넌트가 마운트 되면, react-query는 유저타입 데이터를 받아온다. 그리고 useEffect 에서는 그 중 첫 번째 타입을 selectedUserState 상태에 설정한다.

유저타입 데이터는 선택된 상태로 렌더링 되고, 유저가 리스트에서 원하는 Row를 선택하면 그 타입으로 selectedUserState 를 업데이트한다.

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
export function CreditCardTypePage() {
    const { data: userTypes } = useQuery(reactQueryKey.USER_TYPE, fetchUserTypes);
    const [{ userType: selectedUserType }, setSelectedUserState] = useRecoilState(recoil_SelectedUserState);

    useEffect(() => {
      if (userTypes !== undefined) {
        setSelectedUserState(prev => ({ ...prev, userType: userTypes[0] }));
      }
    }, [userTypes, setSelectedUserState]);

    const onClickUserTypeRow = (userType: UserType) => {
      setSelectedUserState(prev => ({ ...prev, userType }));
    };

    return () => {
        <List>
            {userTypes?.map(item => (
              <ListRow.Selectable
                key={item.id}
                contents={<ListRow.Text1Row top={item.title} />}
                right={<Checkbox readOnly={true} checked={selectedUserType !== null && item.id === selectedUserType.id} />}
                onClick={() => onClickUserTypeRow(item)}
              />
            ))}
        </List>
    }
}

이 코드에는 새로고침, 뒤로가기 시 유저가 선택한 상태가 초기화 되어 첫 번째 Row가 선택된 상태로 렌더링 된다는 문제점이 존재한다. 이러한 문제점을 해결하기 위해서는 다음과 같이 처리 해주어야 한다.

  1. recoil-persist 등을 활용하여 상태를 로컬스토리지에 저장하고, 페이지가 리로드 될 시 상태를 로컬에 저장된 상태로 초기화한다.
  2. useEffect내에서 상태를 업데이트할 때, selectedUserType이 null일 때만 업데이트하도록 한다.

1번을 통해 새로고침 되어 상태가 전부 초기화 되었을 때를 대응하고, 2번을 통해 유저가 선택된 상태를 유지하도록 한다.


중복코드 분리

위 코드 예시에서 보여 준 컴포넌트는 recoil_SelectedUserState에서 userType 데이터만 사용한다. 만약 recoil_SelectedUserState가 다음과 같이 작은 상태이고 한 부분에서만 사용한다면 큰 문제는 없다.

1
2
3
4
5
6
7
8
9
10
11
12
const recoil_SelectedUserState = atom<{
  userType: UserType | null;
  userDesign: UserDesign | null;
  userFields: SelectedUserField[] | null;
}>({
  key: 'selectedUserState',
  default: {
    userType: null,
    userDesign: null,
    userFields: null,
  },
});

하지만 만약 userType만, userDesign만, userFields만 따로 사용하는 부분이 반복된다면 어떻게 될까? 만약 새로고침과 뒤로가기에 대응하느라 그러한 부분들을 전부 같은 형식으로 수정해야 한다면 어떻게 될까?

이럴 때가 바로 중복되는 부분을 분리해줘야 할 때다.

같은 부분을 두 번 이상 반복하고 있다면, 바로 그때가 코드를 분리해야 할 때다.

이러한 부분은 hook이나 컴포넌트로 분리할 수 있다.

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
// hook
const reactQueryPropsByUse = {
    "userType" : {
        reactQueryKey : reactQueryKey.USER_TYPE,
        fetch : fetchUserTypes,
    }, 
}

export function SingleSelectedUserState({use = "userType", defaultValue = null}) {
    const { data } = useQuery(reactQueryPropsByUse[use].reactQueryKey, reactQueryPropsByUse[use].fetch);
    const [{ [use]: selectedUserData }, setSelectedUserState] = useRecoilState(recoil_SelectedUserState);
    
    useEffect(() => {
      if (data !== undefined && selectedUserData === null && defaultValue === null) {
        setSelectedUserState(prev => ({ ...prev, [use]: data[0] }));
      }
      if(defaultValue !== null) {
        setSelectedUserState(prev => ({ ...prev, [use]: defaultValue	] }));
      }
    }, [data, selectedUserData, use, defaultValue, setSelectedUserState]);
    
    const setSingleSelectedUserState = (userData) => {
        setSelectedUserState(prev => ({...prev, [use] : userData}));
    }
    
    
    return [selectedUserData, setSingleSelectedUserState];
}

컴포넌트로 분리할 때는 원하는 로직을 따로 뺀 후, children으로 그 로직을 사용할 컴포넌트를 불러오면 된다.

1
2
3
4
5
6
7
8
const ModalLayout = (props) => {
  const {children, setContent, isOpenModal} = props;
  return (
    <ModalBox>
      {React.cloneElement(children, {setContent, isOpenModal})}
    </ModalBox>
  )
}


참고

https://velog.io/@hyunjoogo/React-children-Component%EC%97%90-props-%EC%A0%84%EB%8B%AC%ED%95%98%EA%B8%B0

This post is licensed under CC BY 4.0 by the author.

자바 기본

Mysql Count 속도

Comments powered by Disqus.