Climeeting / climeet-fe

https://www.cli-meet.com/
2 stars 2 forks source link

함수형으로 코드 작성하기 #20

Closed piggmme closed 5 months ago

piggmme commented 6 months ago

참고글: https://velog.io/@teo/functional-programming https://velog.io/@teo/functional-programming-study

저는 보통 FE에서 함수형 적용할 때 계산 로직들은 함수로 따로 생성해두고, 핸들러에서 계산 + 액션을 실행하도록 해요.

이렇게 하는 이유는 액션은 기획이 변경되면 빠르게 수정되어야 하는 로직인데, 계산과 액션이 서로 구별되지 않고 결합이 되어버리면(즉 함수형으로 개발하지 않는다면) 수정해야 하는 범위가 커져버리고, 이 때문에 버그가 발생하는 이슈가 생길 수 있기 때문이에요.

그리고 무엇보다 계산과 액션을 분리하면 가독성이 더 좋아지더라구요.

다음은 함수형으로 작성되지 않은 예시와, 함수형으로 수정했을 때의 예시입니다. (더 좋은 예시를 들고싶은데 생각이 나지 않네요 ㅠㅠ)

A. 함수형으로 작성하지 않은 코드

// 함수형으로 작성하지 않은 코드
// 액션과 계산이 구별되어 있지 않다.

export function Page() {
  const login = async () => {
    // 1. 카카오톡 토큰 받기
    const result = await post_kakao_oauth_token(code)
    // 2. 로그인 요청
    const userInfo = await post_oauth_login(result.data.tokens.accessToken)
    return userInfo
  }

  const onClick = async () => {
    const userInfo = await login()
    // 3. 유저 정보 저장
    saveUserInfo({ name: userInfo.name, email: userInfo.email })
  }

  return (
    <div>
      <button onClick={onClick}>전송하기</button>
    </div>
  )
}

B. 함수형으로 작성한 코드

// 함수형으로 작성한 로직

export function Page() {
  return (
    <div>
      <button
        onClick={async () => {
          // 액션과 계산을 분리하고, 핸들러 안에서 액션을 수행한다. 액션에서 사용되는 계산들도 여기서 실행하여 액션의 매개변수로 넘긴다.

          // 1. 카카오톡 토큰 받기 (액션)
          const oauthData = await post_kakao_oauth_token(code)
          // 2. 필요한 토큰 데이터 추출 (계산)
          const accessToken = getToken(oauthData)
          // 3. 로그인 요청
          const userInfo = await post_oauth_login(accessToken)
          // 4. 유저 정보 추출 (계산)
          const user = getUserInfo(userInfo)
          // 5. 유저 정보 저장 (액션)
          saveUserInfo(user)
        }}
      >
        전송하기
      </button>
    </div>
  )
}

// -------- 계산 로직 ------------
const getToken = (oauthData) => {
  return oauthData.data.tokens.accessToken
}

const getUserInfo = (userInfo) => {
  return {
    name: userInfo.name,
    email: userInfo.email,
  }
}

추가로 onClick, handleClick 이런 식으로 핸들러를 따로 빼내지 않는 이유는 컴포넌트의 핸들러가 어떤 기능을 수행하는지 함수를 대략적으로 쭉 읽었을 때 기능이 파악되었으면 하기 때문입니다.

// 1. 카카오톡 토큰 받기 (액션)
const oauthData = await post_kakao_oauth_token(code)
// 2. 필요한 토큰 데이터 추출 (계산)
const accessToken = getToken(oauthData)
// 3. 로그인 요청
const userInfo = await post_oauth_login(accessToken)
// 4. 유저 정보 추출 (계산)
const user = getUserInfo(userInfo)
// 5. 유저 정보 저장 (액션)
saveUserInfo(user)

적절한 액션/계산 구별을 통해서 핸들러에 함수들을 호출하는 위의 예제(B)를 보시면 수루룩 읽히는 느낌을 받을 수 있을것입니다.

하지만 onClick로 핸들러를 따로 선언하고 전달하게 되면 하나의 기능을 하는 컴포넌트와 핸들러 사이의 물리적(코드적?)거리가 생기므로 코드상을 왔다갔다 하면서 읽을 수 밖에 없어지고 이는 가독성이 떨어지게 하는 요인이라고 생각해요. 그리고 대부분 코드를 작성하다 보면 onClick함수를 여러 개의 다른 컴포넌트에게 전달하는 경우는 거의 없더라구요. 있더라도 2번 정도는 반복해서 작성해도 문제가 없으며, 기획이 변경되어 두 컴포넌트의 기능이 갈라지는 경우가 생기면 오히려 의존성이 높아지기도 합니다.