깃허브 주소 ▼
개요
이전 글의 SquareButton과 SquareButtonContainer의 구조에 대한 고민 참고 ▼
목표
전에 Styled-Component로 메인 버튼 컴포넌트를 만들던 도중 한 가지 고민이 들었다. 버튼에 들어가는 값들을 `SquareButton` 컴포넌트 안에 값을 직접 입력해놓은 채로, 하드코딩을 해놔서, 이를 어떻게 수정할 지에 대한 고민을 했었다. ▼
이 고민에 대한 핵심적인 문제는 호출과 수정의 인지 부조화. 호출은 `MainPage.js`에서 하는데, 프로퍼티에 문제가 생기면 수정은 `SquareButtonContainer.js`에서 한다. 이 부분에서 파일을 왔다갔다 하게 되고 State Hoisting도 제대로 되지 않게 되어 수정하기로 했다.
이 문제를 해결하고 나서는 추가 Component를 만들어야 한다. 만들 Component가 한 두개가 아니기 때문에 당분간은 컴포넌트를 만들 거 같다. 컴포넌트를 다 만들고 나서는 페이지를 만들어야 하기에 페이지와 페이지 경로 설정하는 법을 볼 예정이다.
그래서 이번에는 이 고민에 대한 해결과 추가로 `DilemmaListCell` Component를 만드는 것을 목표로 했다.
고민 해결 (SquareButton, SquareButtonContainer)
SquareButton에 생긴 변화
이름은 `Button`인데 생각해보니 `Button`의 기능을 전혀 하지 못하고 있었다. 그래서 onClick에 함수를 넘겨줄 수 있게 프로퍼티를 수정했다. ▼
/* ... 중간 코드 생략 ... */
function SquareButton({ text, image, onClick }) {
return (
<StyledSquareButton onClick={onClick}>
<StyledTopLeftText>{text}</StyledTopLeftText>
<StyledBottomRightImage>{image}</StyledBottomRightImage>
</StyledSquareButton>
);
}
export default SquareButton;
이 부분을 바꾸면서 `SquareButtonContainer`의 구조도 바꿔줘야 하기에, 겸사겸사 고민 해결도 같이 했다.
SquareButtonContainer의 프로퍼티
이전과는 다르게 굉장히 깔끔하게 코드가 바뀌었다.
이전 코드
이전 코드는 `text`, `image`, `onClick`을 직접 넣어줘야 했다. 아래의 코드를 보면 '어라 이게 더 깔끔한데?' 할 수 있지만, 실제로는 image와 onClick을 넣지 않은 상태이기에 빠진 프로퍼티들을 넣어주고 나면 좀 더 복잡해진다. `onClick`에 람다 함수까지 들어가게 되면 길이는 몇 배로 길어지게 된다.
이전 코드의 문제점은 가독성보다는 State Hoisting도 안되고 코드 수정이 굉장히 불편해진다는 점이다. 해당 컴포넌트에 제목 텍스트와 이미지, 그리고 눌렀을 때 이벤트라는 상태들이 고정되기에 재사용이 불가능해진다. 선언형 UI의 진가는 Stateless하게 만들어 재사용을 하게 하는 것인데, 아래의 컴포넌트는 Stateless하지 않게 되면서 컴포넌트 자체가 단 하나만 존재하는, 개별적인 컴포넌트가 된다. ▼
import React from "react";
import styled, { css } from "styled-components";
import SquareButton from "./SqureButton.js";
const StyledButtonContainer = styled.div`
display: flex;
justify-content: left;
align-items: left;
gap: 16px;
`;
function ButtonContainer() {
return (
<StyledButtonContainer>
<SquareButton text={"오늘 랜덤 딜레마"}></SquareButton>
<SquareButton text={"전체 딜레마"}></SquareButton>
</StyledButtonContainer>
);
}
export default ButtonContainer;
수정된 코드
그에 비해 수정된 코드는 `data` 객체로 State Hoisting을 하여 Stateless하게 사용할 수 있다. 만약 다른 페이지에서 같은 컴포넌트가 필요하게 되면 상태만 변경하여 다시 사용할 수 있게 된다. 또한 State Hoisting을 해놓았기 때문에 상태에 변경이 생기면 원본 파일로 가서 수정을 하는게 아니라 호출한 곳에서 수정을 하면 된다. ▼
import React from "react";
import styled from "styled-components";
import SquareButton from "./SqureButton.js";
const StyledButtonContainer = styled.div`
display: flex;
justify-content: left;
align-items: left;
gap: 16px;
margin: 20px 0px 0px 20px;
`;
function ButtonContainer({ data }) {
return (
<StyledButtonContainer>
{data.map((item, index) => (
<SquareButton key={index} text={item.text} image={item.image} onClick={item.onClick} />
))}
</StyledButtonContainer>
);
}
export default ButtonContainer;
`MainPage.js`에서 호출하면 아래와 같이 호출 할 수 있다. ▼
import NavigationBar from "../components/NavigationBar";
import ButtonContainer from "../components/SquareButtonContainer";
import DilemmaListCell from "../components/DilemmaListCell";
import { ReactComponent as DilemmaIcon } from "../assets/dilemma_icon.svg";
import { ReactComponent as DilemmaListIcon } from "../assets/dilemma_list_icon.svg";
const buttonsData = [
{ text: "오늘의 랜덤 딜레마", image: <DilemmaIcon width={48} height={48}></DilemmaIcon>, onClick: () => alert('오늘의 랜덤 딜레마 클릭!') },
{ text: "전체 딜레마", image: <DilemmaListIcon width={48} height={48}></DilemmaListIcon>, onClick: () => alert('전체 딜레마 클릭!') },
];
function MainPage() {
return (
<>
<NavigationBar></NavigationBar>
<ButtonContainer data={buttonsData}></ButtonContainer>
<DilemmaListCell titleText={"hello"} likeCount={100} participationCount={100}></DilemmaListCell>
</>
);
}
export default MainPage;
이걸로 고민 끝!
State Hoisting에 대한 글은 이전에 안드로이드 개발을 하며 작성했던 포스트로 있다. 안드로이드 뿐만 아니라 선언형 UI를 사용하는 모든 곳에서도 적용된다. ▼
폰트 사이즈 적용 문제, 그리고 Styled-Component를 통한 전역 스타일링
문제 발생
이전에 스타일링을 할 때는 `px`단위를 사용해서 스타일링을 했었다. 하지만 이게 좋은 방식이 아니라는 것을 깨닫고 `rem`단위를 사용하는 것으로 교체했다. `rem` 방식을 사용하기 위해서는 root 단위, 최상단에 기준이 되는 `font-size`가 필요한데 생각해보니 index.css 파일을 지워버려서 root 단위의 스타일링이 없었다.
그래서 Styled-Component의 `createGlobalStyle`을 사용하여 전역 스타일을 만들었다. ▼
import { createGlobalStyle } from 'styled-components';
const GlobalStyle = createGlobalStyle`
/*전역 스타일 내용*/
`;
export default GlobalStyle;
그리고 이를 index.js에 적용했다. ▼
import React, { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import reportWebVitals from './reportWebVitals';
import MainPage from './pages/MainPage.js';
import GlobalStyle from './components/ui/GlobalStyle.js';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<GlobalStyle />
<MainPage />
</StrictMode>
);
reportWebVitals();
여기까지는 큰 문제 없었는데, 잘 되나 확인해보려고 `body`의 `font-size`를 `30px`로 설정해보니 아래와 같이 폰트는 커지지 않고 폰트가 차지하는 공간만 커졌다. ▼
그래서 한참을 뭐가 문제일까 찾아보다가, `html` 태그를 수정해줘야한다는 해결방법을 보고 수정을 해봤다. 아래와 같이 초기화를 해주고 시작하고, `body`에서 이를 다시 받아서 `1rem`으로 설정을 해주면 된다는 글이었다. ▼
html, body {
margin: 0;
padding: 0;
font-size: 16px;
}
body {
font-size: 1rem;
...
}
이렇게 설정하고 나니 잘 돌아갔다. 아무래도 문제가 생긴 이유가 기준이 되는 최상단의 `html` 태그와 `body` 태그의 정보가 맞지 않아서 문제가 생겼던 것 같다. 설정을 하고 나니 `font-size`를 `30px`로 하니 `rem` 단위로 바꿔놓은 곳은 크기가 전부 바뀌었다. ▼
딜레마 리스트 셀 (DilemmaListCell)
필요한 것들 정의하기
딜레마 리스트의 각 항목에는 3가지 요소가 들어가야한다. 첫번째는 제목, 두번째는 좋아요수, 세번째는 참여한 인원수다. 그리고 아래와 같이 보여져야 한다. ▼
코드
완성된 코드는 더보기를 누르면 볼 수 있다. ▼
import styled from "styled-components";
import { theme } from "./ui/Theme";
const StyledListCell = styled.button`
background: ${theme.color.darkgray};
border-radius: 8px;
border: none;
align-items: flex-end;
width: 100%;
`;
const StyledTitle = styled.div`
font-family: "JalnanGothic";
color: white;
`;
const StyledLike = styled.div`
color: ${theme.color.lightgray};
`;
const StyledParticipations = styled.div`
color: ${theme.color.lightgray};
`;
const StyledInfoWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 5px;
width: 100%;
`;
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
align-items: start;
padding: 15px 15px 10px 15px;
`;
function TitleText({titleText}) {
return (
<StyledTitle>
{titleText}
</StyledTitle>
);
}
function Like({likeCount}) {
return (
<StyledLike>
❤️ {likeCount}
</StyledLike>
);
}
function Participations({participationCount}) {
return (
<StyledParticipations>{participationCount}명 참여</StyledParticipations>
)
}
function InfoWrapper({likeCount, participationCount}) {
return (
<StyledInfoWrapper>
<Like likeCount={likeCount}></Like>
<Participations participationCount={participationCount}></Participations>
</StyledInfoWrapper>
);
}
function Wrapper({titleText, likeCount, participationCount}) {
return (
<StyledWrapper>
<TitleText titleText={titleText}></TitleText>
<InfoWrapper likeCount={likeCount} participationCount={participationCount}></InfoWrapper>
</StyledWrapper>
);
}
function DilemmaListCell({titleText, likeCount, participationCount}) {
return (
<StyledListCell>
<Wrapper titleText={titleText} likeCount={likeCount} participationCount={participationCount}></Wrapper>
</StyledListCell>
);
}
export default DilemmaListCell;
트러블 슈팅 : space-between 적용이 안되는 문제
만들면서 몇가지 문제점이 있었다. 아래의 그림과 같이 공간이 이렇게나 많이 남는데, `justify-content: space-between`이 적용되지 않는다는 것이었다. ▼
이것저것 찾아봤는데 아무리 해도 다 해본 것들이고 새로운 해결법이 나오지 않아서 예제 코드를 몇개 보고 있었다. 그러다가 하나 의문이 들었는데, '`StyledInfoWrapper`(하단 정보를 감싸는 wrapper다.)에 너비가 지정이 되어있는가?' 였다.
바탕 영역을 정의한 `StyledListCell`에서는 너비를 꽉 차게 차지하라고 설정을 해두었는데 `StyledInfoWrapper`에는 너비가 지정이 된 적이 없었다는 것이다. ▼
const StyledListCell = styled.button`
background: ${theme.color.darkgray};
border-radius: 8px;
border: none;
align-items: flex-end;
width: 100%;
`;
const StyledInfoWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 5px;
/*너비가 없다...*/
`;
즉, 둘이 붙어 있던 이유는 둘이 가질 수 있는 최소치의 너비만을 가지고 있었기 때문이다. 최소치의 너비로는 아무리 space-between을 적용해봐야 넓어질 수 있는 공간이 없으니 의미가 없던 것이었고, `width: 100%`를 넣어주면서 해결이 됐다. ▼
const StyledInfoWrapper = styled.div`
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-top: 5px;
width: 100%;
`;
마치며
이번에는 State Hoisting에 대한 고민 해결과 Styled-Component 사용, 그리고 새로운 컴포넌트 개발을 해보았다. 아직은 많이 부족한 모습이 보인다. Flutter의 개발 방식에 너무 적응해버려서 자꾸만 앱개발의 관점에서 바라보게 된다. 반응형 UI도 생각해야하는데 이 부분을 크게 생각하지 않고 개발하고 있어 빨리 반응형 UI에 대해 학습해야겠다고 생각하고 있다.
'Develop > React' 카테고리의 다른 글
[React][개발기] 7. RoutePaths, NavBarData 등 데이터를 효율적으로 (0) | 2024.07.17 |
---|---|
[React][개발기] 6. Stateless하기 만들기, 페이지 만들기, 그리고 Map 활용 (0) | 2024.07.17 |
[React][개발기] 4. Styled-Component로 Component 만들기 (0) | 2024.07.16 |
[React][개발기] 3. 우선 기초적인 것들부터 학습2(HTML, CSS, JS) (0) | 2024.07.14 |
[React][개발기] 2. 우선 기초적인 것들부터 학습(HTML, CSS) (0) | 2024.07.13 |