[React] 더 나은 함수 컴포넌트를 위한 3가지 팁



더 나은 함수 컴포넌트를 위한 3가지 팁

나는 폼이 전송되기 전에, 추가적인 발리데이션을 추가하는 업무를 배정받았습니다.

해당 폼 컴포넌트를 보니, 300줄이 넘는 컴포넌트였고, JSX 코드만 150줄이었습니다. 로직은 많은 useState와 많은 useEffect 그리고 많은 의존성들.. 이 존재했고, 이해하기 아주 어려웠습니다.

내가 쓴 코드는 나도 이해하지 못하는, 말 그대로 코드 괴물이 되었고, 한동안 그저 바라볼 수 밖에 없었습니다.

너무 부끄럽고, 놀랐지만, 더이상 같은 실수를 반복할 순 없습니다.

useState대신 useReducer를 사용

서론에서, 저는 이 컴포넌트에 많은 useState가 있다고 언급했습니다. 컴포넌트에서 어느 부분이 특정 상태를 업데이트 하는지 알기 위해 모든 useState를 찾아야 했기 때문에 읽기 상당히 어려웠습니다.

	const [questionState, setQuestionState] = useState([])
  const [formState, setFormState] = useState([])
  const [
    currentTicketForm,
    setCurrentTicketForm
  ] = useState(null)
  const [success, setSuccess] = useState(false)
  const [ticketName, setTicketName] = useState('')
  const [ticketSubcategoryName, setTicketSubcategoryName] = useState('')
  ...

누가봐도, 이 코드는 보기힘듭니다. 리팩토링을 해야합니다. 이 폼을 제어하기 위해 useReducer를 사용할 수 있을 것 같군요.

export const formReducer = (state, action) => {
  switch (action.type) {
    case 'UPDATE_TICKER_FORM':
      return {
        ...state,
        ticketForm: action.payload,
        ticketAnswers: action.payload.answers,
        ticketQuestions: action.payload.questions,
        ticketFields: action.payload.newTickets,
        currentField: action.payload.newTickets[0]
      }
    case 'CHANGE_VALUE':
      return {
        ...state,
        temporaryValue: action.payload.temporaryValue
      }
    case 'GO_NEXT_QUESTION':
      return {
        ...state,
        ticketAnswers: {
          ...state.ticketAnswers,
          [state.currentField]: state.temporaryValue
        },
        currentField: state.ticketFields[
          state.ticketFields?.indexOf(state.currentField) + 1
        ],
        questionHistory: [
          ...state.questionHistory,
          state.currentField.id
        ],
      }
    ...
  }
}

자, 이제 어떤 동작이 어떤 상태에 영향을 미치는지 쉽게 알 수 있겠군요! 이렇게 하면 상태를 더 많이 제어할 수 있으며 새 기능을 구현하는 것이 더 쉽습니다.

로직 나누기

이 폼에선, 데이터를 받아와 상태를 업데이트해줘야 합니다. 이 때 저는 fetch API를 사용하려고 했습니다.

데이터를 받아와 컴포넌트의 상태를 업데이트해줘야 하는 비슷한 컴포넌트가 있을 시에, 커스텀 훅을 작성할 수 있습니다.

export const useFetch = (url) => {
  const [listState, setListState] = React.useState([])

  useEffect(() => {
    const abortFetch = Fetch.get(url, {
      onCompleted: data => {
        setListState(data)
      }
    })

    return abortFetch
  }, [])

  return { listState }
}

추가적인 쿼리가 필요한다던가, 예외가 있다면, 로직을 해당 훅에 작성할 수도 있습니다.

작은 조각으로 분할하기

내가 작성한, 코드 괴물에는 100줄이 넘는 UI 코드가 있습니다. 코드에는 div들 그리고 그 다음에 div들이 있었으며, 코드가 어떤 UI를 그리는지 정확히 2분 뒤에 알 수 있었습니다. 이 div 유니버스에서는 조건부 렌더링도 있었으며 저는 코드를 읽기위해 스크롤을 아래로 내려야했습니다.

<div>
  <div>
    {currentForm ? (
      <div>
        <span>Question</span>
        <h3>
          {currentForm.name}
        </h3>
        <div className="mt-8">
          <OptionComponent
            value={currentForm.value}
            type={currentForm.type}
            options={currentForm.options}
          />
        </div>
        <div>
          <Button>
            Back
          </Button>
          <Button>
            Save
          </Button>
        </div>
      </div>
    ) : null}
  </div>
</div>

보시면, currentForm 은 큰 JSX를 감싸고 있습니다. 가독성을 위해 다른 컴포넌트로 옮기는 게 좋을 것 같습니다.

<div>
  <div>
    {currentForm ? (
      <Form
        value={temporaryValue}
        type={currentForm.type}
        options={currentForm.options?.split('\n') || []}
      />
    ) : null}
  </div>
</div>

마치면서

코드를 작성하는 완벽한 방법은 없습니다. 팀마다 모두 다른 패턴을 따르지만, 코드를 보았을 때 어떤 느낌이 든다면, 리팩토링을 해야합니다.

  • 많은 useState를 사용하고 있다면, useReducer로 변경하는 걸 고려해봅시다. 더 나은 유지보수와 향후 기능 추가에 도움이 될 것입니다.
  • 재사용할 수 있는 로직들은 나눕시다. 이것은 데이터 가져오기와 같이 다른 컴포넌트에서 사용할 수 있는 유틸리티 함수가 될 수 있습니다.
  • 컴포넌트를 분할합시다. 코드의 가독성을 위해서도 도움이 되고, 성능에도 도움이 됩니다. 되도록 100줄을 넘지 마세요.





© 2019. by jong-hui

Powered by aiden