개발기록

RunBase ( 2 ) - 극한의 컴포넌트 재활용

Frontend Developer 2023. 8. 13. 22:49

 

 

배송지 컴포넌트

 

 

컴포넌트를 작성하던 중 문제가 생겼다. 컴포넌트 교체로 UI를 리렌더링하면 되겠다라는 생각으로 접근했다가 기획을 엎어야 할지 코드를 엎어야 할지 그 중간에서 팀원들과 고민을 했었다.

배송지 컴포넌트는 내가 같은 레이아웃 부분이었고, 컴포넌트는 2개 왼쪽<AddressList /> 오른쪽<AddressEdit /> 최종 구현은 다음과 같다.

 

 

조건부 렌더링

 

 

위에서 구현이 끝나는 것은 아니었다. 오른쪽 파란박스안에 Input들은 <AddressEdit />인데 "결제하기-배송옵션"에서도 쓰이지만 "배송지수정"에서도 쓰인다. 아래에서 살펴보자.

 

 

왼쪽 배송지 추가버튼을 누르면 Modal컴포넌트 안에 컨텐츠로 Edit(<AddressEdit />)이 들어간다. 위 Edit은 "배송지 추가(수정)"모달에서는 수령인이 보여야하고 "결제하기-배송옵션"에서는 수령인이 필요없기 때문에 조건에 따라 렌더링되도록 구현해야 했다. 위의 문제는 간단히 props를 전달해주는 것으로 만약 showRecipient가 true면? 수령인Input을 보여주고 false면 보여지지 않게 구분하여 사용하면 되었다. 추가로 '기본배송지 저장'도 "배송옵션"에서만 나오는 기능이므로 showRecipient가 false일때 보여지도록 구현하면 되었다.

먼저 코드로 해결방법을 살펴보자.

 

 

const AddressEdit: FC<PaymentShowRecipientProps> = ({
  showRecipient
}) => {
  const [isChecked, setIsChecked] = useState(false);

  return (
    <S.AddressEditContainer>
          {showRecipient && (
            <StyledInput
              margin="0 0 0.5rem 0"
              label="수령인 *"
              defaultValue={recipient}
            />
          )}
          {!showRecipient && (
            <S.StyledCheckBox>
              <Checkbox
                checked={isChecked}
                size={14}
                onChange={() => setIsChecked(!isChecked)}
              />
              <Caption1>기본배송지 저장</Caption1>
            </S.StyledCheckBox>
          )}

showRecipient를 props로 받아 showRecipient={true} 이면 수령인Input을 보여주고 showRecipient={true}라는 말은 "배송지 추가(수정)"에 사용한다는 말이므로 !showRecipient 일때 checkBox가 구현되도록 props를 전달해주었다.

이로 문제가 해결되는 듯 했으나 한 가지 더 고려해야 할 부분이 생겼다.

 

 

빨간색 배송지추가 버튼을 누르면 "배송지추가(수정)"모달이 나오고, 파란색 배송지목록 버튼을 누르면 파란색 배송지 목록이 Modal에 나와야 하는 것이다. 최대한 컴포넌트를 재사용하고자 했기 때문에 초기에 아래와 같이 작성했던 코드들에서 더 관리가 용이하게 수정하고 싶었다. 

 

 

굳이 저 flow를 위해서 Modal 컴포넌트를 갖고와 opener로 '배송지목록' 버튼을 만들어주어야 했다.

코드의 가독성과 컴포넌트 재활용성을 높이기 위해서 props를 하나 더 추가했고, 다음과 같이 코드를 줄일 수 있었다.

// 결제하기-배송옵션
<S.DelieveryWrap>
		<S.EditBox>
            <S.SubHeadline>배송 옵션</S.SubHeadline>
            <S.ModalBox>
              <Modal
                opener={<S.DelieveryList>배송지 목록</S.DelieveryList>}
                title="배송지 추가"
                contents={<AddressList />}
                successText="확인"
                onSuccess={() => alert('선택 완료!')}
              />
            </S.ModalBox>
          </S.EditBox>
          <S.AddressEditBox>
            <AddressEdit />
          </S.AddressEditBox>
<S.DelieveryWrap>
// 결제하기-배송옵션
<S.DelieveryWrap>
	<AddressList showAddressTitle={false} showContents={false} />
	<S.AddressEditBox>
		<AddressEdit showRecipient={false} />
	</S.AddressEditBox>
</S.DelieveryWrap>

showAddressTitle의 prop와 showContents의 props를 추가하였고, 보다 가독성있게 코드를 작성할 수 있고 컴포넌트를 재사용할 수 있다는 측면에서 props를 추가하게 되었다. 

추가적으로 팀원이 AddressEdit의 width값을 props로 조절할 수 있게 작성해달라는 부탁을 받아 width값도 props로 지정하게 되었다.

// AddressEdit 사용법
	<Modal
            opener={<S.StyledTextButton>배송지추가</S.StyledTextButton>}
            title="배송지 추가"
            contents={<AddressEdit width='100%'(default) showRecipient={true} />}
            successText="확인"
            cancelText="취소"
          />
          
// styled-components
export const AddressEditContainer = styled.div<AddressEditWidthProps>`
  width: ${({ width }) => (width ? width : '100%')};
`;

 

 

마이페이지의 배송지리스트도 props로 쉽게 구현할 수 있도록 작성해봤다.

///마이페이지 수정 코드
<S.PersonalInfoBox>
	<S.InputBox>
	</S.InputBox>
	<S.ShippingAdderssBox>
		<AddressList showAddressTitle={true} showContents={true} />
	</S.ShippingAdderssBox>
</S.PersonalInfoBox>

 

 

아래는 props를 받는 <AddressList /> 코드이다.

const AddressList: FC<AddressTitleProps> = ({
  showAddressTitle,
  showContents
}) => {

  return (
    <S.AddressList>
      {showAddressTitle ? (
        <S.AddressTitleBox>
          <Body2>배송지목록</Body2>
          <Modal
            opener={<S.StyledTextButton>배송지추가</S.StyledTextButton>}
            title="배송지 추가"
            contents={<AddressEdit showRecipient={true} />}
            successText="확인"
            cancelText="취소"
            modalWidth="30rem"
          />
        </S.AddressTitleBox>
      ) : (
        <S.AddressAddBox>
          <Modal
            opener={<S.StyledTextButton>배송지추가</S.StyledTextButton>}
            title="배송지 추가"
            contents={
              <AddressList showAddressTitle={true} showContents={true} />
            }
            successText="확인"
            cancelText="취소"
            modalWidth="30rem"
          />
        </S.AddressAddBox>
      )}
      {showContents ? (
        <S.AddressListContainer>
          {addressData.map(addressItem => (
            <S.StyledRadioBtn key={addressItem.id}>
              <RadioBtn
                value={addressItem}
                name="address"
                contents={
                  <>
                    <S.StyledBody1>{recipient}</S.StyledBody1>
                    <Body2 color="gray">
                    </Body2>
                  </>
                }
                align="top"
                onChange={handleRadioChange}
                btnMargin="3px"
              />
              <S.AddressEditBox>
                <Modal
                  opener={<S.StyledTextButton>수정</S.StyledTextButton>}
                  title="배송지 추가"
                  contents={
                    <AddressEdit
                      addressData={addressItem}
                      data={true}
                      showRecipient={true}
                    />
                  }
                  successText="확인"
                  cancelText="취소"
                  modalWidth="30rem"
                />
                <S.StyledTextButton>삭제</S.StyledTextButton>
              </S.AddressEditBox>
            </S.StyledRadioBtn>
          ))}
        </S.AddressListContainer>
      ) : (
        <></>
      )}
    </S.AddressList>
  );
};

export default AddressList;

 

 

AddressEdit 또한 props로 받는 코드로 수정해봤다. 

const AddressEdit: FC<PaymentShowRecipientProps> = ({
  showRecipient,
  width,
}) => {
  const [isChecked, setIsChecked] = useState(false);

  return (
    <S.AddressEditContainer width={width}>
          <StyledInput
            margin="0 0 0.5rem 0"
            label="배송지이름 *"
            defaultValue={addressName}
          />
          {showRecipient && (
            <StyledInput
              margin="0 0 0.5rem 0"
              label="수령인 *"
              defaultValue={recipient}
            />
          )}
          <StyledInput
            margin="0 0 0.5rem 0"
            label="휴대폰번호 *"
            defaultValue={phoneNumber}
          />
          {!showRecipient && (
            <S.StyledCheckBox>
              <Checkbox
                checked={isChecked}
                size={14}
                onChange={() => setIsChecked(!isChecked)}
              />
              <Caption1>기본배송지 저장</Caption1>
            </S.StyledCheckBox>
          )}
          <S.StyledBox>
            <StyledInput
              width="100%"
              margin="0 0 0.5rem 0"
              disabled
              label="우편번호"
              defaultValue={postCode}
            />
            <S.StyledButton size={ButtonSize.LARGE}>주소 검색</S.StyledButton>
          </S.StyledBox>
          <StyledInput
            margin="0 0 0.5rem 0"
            label="도로명주소 *"
            defaultValue={address}
          />
          <StyledInput
            margin="0 0 0.5rem 0"
            label="상세주소 *"
            defaultValue={addressDetail}
          />
        </>
      ) : (
        <></>
      )}
    </S.AddressEditContainer>
  );
};

export default AddressEdit;

 

 

팀원들의 코드는 깔끔해졌지만 어째서인지 props를 많이 고려하여 작성하니 내 코드의 가독성이 떨어진 것 같기도 하다. 일단 팀원들이 편하게 props로 컴포넌트를 재사용할 수 있게 되어서 다행이다. 추후에 위 코드를 더 줄이거나 가독성을 높이는 방향으로 리팩토링 해봐야겠다.

 

 

ISSUE

 

 

props로 수정할 코드들을 팀원들에게 쉽게 설명해주기 위해 기술설명서를 작성했다. 

https://github.com/afterWe/Run_Base_front-end/issues/48#issue-1861318040

 

배송지 컴포넌트 사용설명..~ · Issue #48 · afterWe/Run_Base_front-end

📌 AddressList <AddressList showAddressTitle={true} showContents={true} /> AddressList는 showAddressTitle와 showContents를 props로 받습니다. {true} or {false}로 '배송지목록' 타이틀과 '배송지목록'을 렌더링할 수 있습니다. /

github.com