프로젝트/TYLEGG

관리가 쉬운 코드 만들기

58청춘 2024. 4. 19. 21:30
728x90

이전 글들 중 유지보수가 편리한 코드를 만드는 강좌의 정리글을 올린적이 있다.

 

내가 구현한 코드를 보던 중 너무 의존성이 강하게 묶여있는 코드를 발견해 이를 리팩토링 하는 기록을 남기고자 한다.

 

코드

const ShowPosition = ({ positions, tyleInfoSetFun, operatorImgData }: positionsInterface) => {
  const ePositions = positions.map(e => e.ePosition);
  // 객체형으로 선택됬는지 여부를 보관하기
  const [selectedState, setSelectedState] = useState<selectedStateInterface>({
    position: {
      duelist: false,
      recon: false,
      sentinels: false,
      controllers: false,
      noDicision: false,
    },
    operator: []
  });

  // API에 정보 전달을 위해 모아두는 상태
  const [selected, setSelected] = useState<selectedInterface>({
    position: [],
    operator: [],
  });
  
  // 선택한 이미지들
  const [positionImg, setPositionImg] = useState<operaotrsImgInterface>({
    duelist: [],
    recon: [],
    sentinels: [],
    controllers: [],
  });

  /**
   * 포지션 선택시 선택한 포지션의 상태를 갱신해주는 함수
   * (리팩토링 필수)
   * @param ePosition 
   */
  const selectPosition = (ePosition: string) => {
    const existIdx = selected.position.findIndex(e => e === ePosition);
    // 선택한 포지션을 다시 눌렀을 때 제거해주는 로직
    if (existIdx !== -1) {
      const remainPos = selected.position.filter(e => e !== ePosition)[0];
      const newImgState = positionImg[ePosition].map((imgInfo: ImgInterface) => {
        if (imgInfo.state === true) {
          imgInfo.state = false;
          return imgInfo;
        }
        else {
          return imgInfo;
        }
      });

      setSelected({
        position: remainPos ? [remainPos] : [],
        operator: [...selected.operator.filter(e => e[0] !== ePosition)],
      });
      setSelectedState({
        position: { ...selectedState.position, [ePosition]: false },
        operator: remainPos ? positionImg[remainPos] : []
      });
      setPositionImg({
        ...positionImg,
        [ePosition]: newImgState,
      })
    }
    // 포지션 추가 로직
    else if (selected.position.length < 2 && existIdx === -1) {
      setSelected({
        ...selected,
        position: [...selected.position, ePosition]
      });
      setSelectedState({
        position: { ...selectedState.position, [ePosition]: true },
        operator: [...positionImg[ePosition]]
      });
    }
    else {
      alert('포지션은 2개 까지 선택 가능합니다.(수정 예정)');
    }
  };

  /**
   * 선택한 포지션의 오퍼레이터를 선택하는 함수이며 2개까지 선택 가능하다.
   * @param ePosition 
   * @param opName 
   * @param idx 
   */
  const selectOperator = (ePosition: string, opName: string, id: number, idx: number): void => {
    const existCheck = selected.operator.filter(e => e[1] === opName);
    const clickedOpPos = [...positionImg[ePosition]];
    
    // 선택한 포지션을 다시 눌렀을 때 제거해주는 로직
    if (existCheck.length !== 0) {
      clickedOpPos[idx].state = false;
      setSelected({
        ...selected,
        operator: [...selected.operator].filter(e => e[1] !== opName)
      });
      setPositionImg({
        ...positionImg,
        [ePosition]: clickedOpPos
      });
    }
    else if (selected.operator.length < 2 && existCheck.length === 0) {
      clickedOpPos[idx].state = true;
      setSelected({
        ...selected,
        operator: [...selected.operator, [ePosition, opName, `${id}`]]
      });
      setPositionImg({
        ...positionImg,
        [ePosition]: clickedOpPos
      });
    }
    else {
      alert('주 요원은 2개 까지 선택 가능합니다.(수정 예정)');
    }
  }
  
...

 

이 코드들 중 selectPosition함수와 selectOperator함수가 컴포넌트의 상태와 세터함수에 의존성을 강하게 띄우고 있는 모습을 볼 수 있다.

 

이러한 코드는 수정사항이 생기면 로직을 수정하기 힘든 코드라고 생각한다.

심지어 컴포넌트의 상태를 직접 가져와 사용하기에 더욱더 힘들어 질것이라 본다....

내가 왜 이런 코드를....

 

내가 작업할 것은 이렇게 state와 컴포넌트 내부에 있는 함수들을 적절히 분리한뒤 길을 정리해 줄 것이다.

 

포지션 선택 함수

이 함수는 사용자가 포지션을 선택 하면 기존 상태에서 새롭게 상태레 변화를 주는 함수이다.

 

이 함수와 다음 함수에서 set함수를 어떻게 처리할까 고민했다.

지금은 새롭게 적용할 상태를 받아 컴포넌트 내부에서 set함수를 사용해 적용해주는 함수를 하나더 선언해 줄것이다.

글로 정리하던 중 customHook으로 함수를 추출해 로직을 정리해 컴포넌트 내부에 customHook으로 만든 상태만 존재하게끔 해주는 방법을 알게 되어 조만간 적용할 예정이다.

 

 

import { ImgInterface, operaotrsImgInterface, selectedInterface, selectedStateInterface } from 'types/newTyle';

/**
   * 포지션 선택시 선택한 포지션의 상태를 갱신해주는 함수
   * @param ePosition
   * @param selected
   * @param positionImg
   * @param selectedState
   */
export const selectPosition = (
  ePosition: string,
  selected: selectedInterface,
  positionImg: operaotrsImgInterface,
  selectedState: selectedStateInterface
) => {
  const existIdx = selected.position.findIndex(e => e === ePosition);
  // 선택한 포지션을 다시 눌렀을 때 제거해주는 로직
  if (existIdx !== -1) {
    const remainPos = selected.position.filter(e => e !== ePosition)[0];

    const newSelected = setSelected(selected, ePosition, remainPos);
    const newSelectesState = setSelectedState(ePosition, selectedState, positionImg, remainPos);
    return {
      newSelected: newSelected,
      newSelectesState: newSelectesState,
    };
  }
  // 포지션 추가 로직
  else if (selected.position.length < 2 && existIdx === -1) {
    const newSelected = setSelected(selected, ePosition, 'add');
    const newSelectesState = setSelectedState(ePosition, selectedState, positionImg, 'add');
    return {
      newSelected: newSelected,
      newSelectesState: newSelectesState,
    };
  }
  else {
    alert('포지션은 2개 까지 선택 가능합니다.(수정 예정)');
  }
};

const setSelected = (selected: selectedInterface, ePosition: string, remainPos?: string) => {
  return remainPos === 'add' ?
    {
      ...selected,
      position: [...selected.position, ePosition]
    }
    :
    {
      position: remainPos ? [remainPos] : [],
      operator: [...selected.operator.filter(e => e[0] !== ePosition)]
    }
}

const setSelectedState = (ePosition: string, selectedState: selectedStateInterface, positionImg: operaotrsImgInterface, remainPos?: string) => {
  return {
    position: { ...selectedState.position, [ePosition]: remainPos === 'add' ? true : false},
    operator: remainPos === 'add' ? [...positionImg[ePosition]] : (remainPos !== undefined ? positionImg[remainPos] : [])
  }
};

 

/**
 * 포지션 선택시 선택한 포지션의 상태를 갱신해주는 함수
 * @param ePosition
 * @param selected
 * @param positionImg
 * @param selectedState
 */
const onclickPosition = (
  ePosition: string,
  selected: selectedInterface,
  positionImg: operaotrsImgInterface,
  selectedState: selectedStateInterface
) => {
  const result = selectPosition(ePosition, selected, positionImg, selectedState);
  if (result) {
    setSelected(result.newSelected)
    setSelectedState(result.newSelectesState)
  }
}

 

 

캐릭터 선택 함수

이 함수는 포지션을 선택한 뒤, 그 포지션에 속한 캐릭터들의 선택을 도와주는 함수이다.

import { operaotrsImgInterface, selectedInterface } from 'types/newTyle';

/**
   * 선택한 포지션의 오퍼레이터를 선택하는 함수이며 2개까지 선택 가능하다.
   * @param selected
   * @param positionImg
   * @param ePosition 
   * @param opName 
   * @param idx 
   */
export const selectOperator = (
  selected: selectedInterface,
  positionImg: operaotrsImgInterface,
  ePosition: string,
  opName: string,
  id: number,
  idx: number
) => {
  const existCheck = selected.operator.filter(e => e[1] === opName);
  const clickedOpPos = [...positionImg[ePosition]];

  // 선택한 포지션을 다시 눌렀을 때 제거해주는 로직
  if (existCheck.length !== 0) {
    clickedOpPos[idx].state = false;

    return {
      newSelected: {
        ...selected,
        operator: [...selected.operator].filter(e => e[1] !== opName)
      },
      newPositionImg: {
        ...positionImg,
        [ePosition]: clickedOpPos
      }
    };
  }
  else if (selected.operator.length < 2 && existCheck.length === 0) {
    clickedOpPos[idx].state = true;

    return {
      newSelected: {
        ...selected,
        operator: [...selected.operator, [ePosition, opName, `${id}`]]
      },
      newPositionImg: {
        ...positionImg,
        [ePosition]: clickedOpPos
      }
    };
  }
  else {
    alert('주 요원은 2개 까지 선택 가능합니다.(수정 예정)');
  }
};

 

/**
 * 선택한 포지션의 오퍼레이터를 선택하는 함수이며 2개까지 선택 가능하다.
 * @param selected
 * @param positionImg
 * @param ePosition 
 * @param opName 
 * @param idx 
 */
const onClickOperator = (
  selected: selectedInterface,
  positionImg: operaotrsImgInterface,
  position: string,
  name: string,
  id: number,
  idx: number
) => {
  const result = selectOperator(selected, positionImg, position, name, id, idx)
  if (result) {
    setSelected(result.newSelected);
    setPositionImg(result.newPositionImg);
  }
}

 

 

 

내 생각

이 작업을 하고난 뒤 생각한 것은 상태 관리가 굉장히 엉켜있다는 느낌을 느꼇다.

또한 아까도 이야기 했듯이 customHook으로 만들어 컴포넌트 내부의 코드들을 좀더 깔끔히 정리해 봐야겠다는 생각이다.

 

개선할 사안이 있다면 계속해서 유지보수할 예정이다.

 

728x90