리액트기본
1. 기초영상
2. 심화영상
3. Composition? (컴포지션?)
Nested Component 컴포넌트 라고도 하고 중첩 컴포넌트라고도 합니다.컴포넌트를 중첩하는 것을 말합니다.
리액트 앱은 수많은 컴포넌트를 가지게 됩니다.
컴포넌트간의 주고받는 데이터 뿐만 아니라 박스나 컨테이너에서 사용하는 스타일코드의 중복까지도 최소화 하려면 props 만으로는 한계가 있는데요.
이때 사용할수 있는것이 composition(합성) 입니다.
앱에서 반복 사용되는 UI 디자인 코드를 별도의 컴포넌트로 생성하여 합성하는 학습을 해보겠습니다.
3.1.컴포지션 준비
아래의 링크는 index.js에서 App 컴포넌트 대신 Comp 컴포넌트를 임포트 한 예제입니다.또한 컴포넌트 내부에 style 을 객체로 작성하여 적용하였습니다.
에디터 우측하단의 Open Sandbox 버튼을 클릭하면 예제 코드를 내려 받으시거나 바로 온라인에서 편집하실수 있습니다.
컴포넌트는 아래의 그림과 같은 구조로 이루어져 있으며 동일한 스타일을 중복하여 사용하고 있습니다.
중복되는 스타일을 컴포지션으로 수정해 봅시다.
우선 중복되는 스타일을 마구마구 넣어서 컴포넌트를 만들어 봅시다.
3.1.1.src\index.js
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import Comp from "./Comp";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
<StrictMode>
<Comp />
</StrictMode>,
);
3.1.1.src\Comp.js
import CompA from './CompA';
const style = {
color: 'white',
background: '#23b1c2',
width: '50%',
textAlign: 'center',
borderRadius: '20px',
margin: '20px auto',
padding: '20px',
};
const Comp = (props) => {
return (
<div style={style}>
<CompA />
</div>
);
};
export default Comp;
3.1.3.src\CompA.js
import CompAA from "./CompAA";
const style = {
color: "white",
width: "80%",
padding: "20px",
background: "#d3b1b1",
textAlign: "center",
borderRadius: "20px",
marginRight: "20px",
marginTop: "20px",
};
const CompA = (props) => {
return (
<div style={style}>
<CompAA />
</div>
);
};
export default CompA;
3.1.4.src\CompAA.js
const style = {
color: "white",
background: "#a5b1b1",
textAlign: "center",
borderRadius: "20px",
marginRight: "20px",
marginTop: "20px",
display: "inline-block",
padding: "20px",
};
const CompAA = (props) => {
return (
<>
<div style={style}>CompAA</div>
<div style={style}>CompAA</div>
<div style={style}>CompAA</div>
</>
);
};
export default CompAA;
이미지와 같은 컴포넌트가 만들어질 것입니다.
빨간배경의 코드는 컴포넌트마다 공통적으로 사용하는 UI스타일 입니다.
3.2.컴포지션 시작
3.2.1 src\index.js
index.js 에서 시작컴포넌트를 Comp 에서 Box 로 변경합니다.
...
import Box from "./Box";
...
root.render(
<StrictMode>
<Box />
</StrictMode>,
);
3.2.1 src\Box.js
src 폴더하위에 Box 컴포넌트를 생성하고 아래의 코드를 작성합니다.
변수 style 에 css 속성값을 객체형태로 초기화 했습니다.
Box 컴포넌트를 선언하고 div 요소에 미리 정의한 style 객체를 style 속성으로 할당하여 반환하는 로직을 구현합니다.
마지막으로 Box 컴포넌트를 외부에서 사용할수 있도록 export 합니다.
const style = {
marginTop: '20px',
borderRadius: '20px',
textAlign: 'center',
padding: '20px',
color: 'white',
};
const Box = (props) => {
return <div style={style}></div>;
};
export default Box;
3.2.2. src\CompAA.js
박스컴포넌트를 임포트 합니다.
return 함수의 기존의 코드를 삭제한 후 임포트한 Box 컴포넌트를 반환하도록 수정합니다.
이때 Box 컴포넌트는 CompAA 의 style 객체를 style 속성으로 할당받아 사용하도록 구현합니다.
그럼 이제 화면을 예상하면서 실행해봅시다
Box 컴포넌트에 style 객체의 값이 적용돼서 렌더링 될것 같습니다.
import Box from './Box';
const style = {
background: '#a5b1b1',
display: 'inline-block',
marginRight: '20px',
};
const CompAA = (props) => {
return (
<>
<Box style={style}>CompAA</Box>
<Box style={style}>CompAA</Box>
<Box style={style}>CompAA</Box>
</>
);
};
export default CompAA;
예상과 다르게 CompAA 의 데이터가 전혀 렌더링 되지 않습니다.
개발자 모드에서 소스코드를 확인해보면 Box 컴포넌트의 스타일 속성이 적용된것은 보이나 기존 CompAA 의 스타일과 데이터가 보이지 않습니다.
왜그럴까요?
CompAA 의 UI는 Box 컴포넌트가 임포트 된것이며 Box 컴포넌트는 다른 함수이기 때문에 CompAA 의 값을 전달하려면 인자와 매개변수를 활용해야 하기 때문입니다.
어떻게 해야 할까요?
3.3.props.children
컴포넌트는 함수입니다.함수는 인수와 매개변수의 형태로 서로 값을 전달할수 있죠.
3.3.1. src\Box.js
const style = {
marginTop: '20px',
borderRadius: '20px',
textAlign: 'center',
padding: '20px',
color: 'white',
};
const Box = (props) => {
console.log(props);
return <div style={style}></div>;
};
export default Box;
리액트에서는 하위 컴포넌트에 매개변수 를 작성하면 기본적으로 상위 컴포넌트에서 전달하는 값 (인수) 을 받을수 있습니다.
이때 매개변수의 이름은 abc, def 등 여러분이 마음대로 지정하셔도 되나 일반적으로 props 로 작성합니다.
코드를 작성후 콘솔창을 열어보면 props 매개변수에 저장된 값이 확인됩니다.
이제 Box 컴포넌트에 전달받은 값을 적용해 보겠습니다.
3.3.2 콘텐츠 합성
props.children은 React에서 제공하는 특별한 속성입니다.
이 속성은 부모 컴포넌트에서 자식 컴포넌트로 전달된 모든 컨텐츠를 참조할 수 있습니다.
//src/Box.js
const style = {
marginTop: '20px',
borderRadius: '20px',
textAlign: 'center',
padding: '20px',
color: 'white',
};
const Box = (props) => {
console.log(props);
return <div style={style}>{props.children}</div>;
};
export default Box;
3.3.3 스타일 합성
스타일은 객체형태로 전달받고 있으므로 전개연산자를 사용하겠습니다.
새 변수에 Box컴포넌트의 속성과 부모컴포넌트의 속성을 연결합니다.
요소에는 새 변수의 이름을 넣어줍니다.
//src/Box.js
...
const newStyle = { ...style, ...props.style };
return <div style={newStyle}>{props.children}</div>;
...
3.4.문제
나머지 컴포넌트도 모두 Box컴포넌트와 합성해보세요
4. Composition 실전예제
이제 멤버앱에 적용해보겠습니다.영상을 보고 바로 코드를 따라 치시기 보다는 일시정지 하시고여러분이 스스로 컴포지션을 해보시고 제 방식을 참고하셔야 실력이 향상됩니다.
아래의 그림에 표시된 부분을 컴포넌트로 분리하고 합성해보시기 바랍니다.
4.2. src/index.js 수정
- index.js 파일을 열고 App 컴포넌트를 다시 임포트 합니다
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import Comp from './Comp';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
{/* <Comp /> */}
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
4.3. 컴포넌트 구조 준비
리액트의 앱은 규모가 커지게 되면 컴포넌트 파일도 많아집니다.많은 컴포넌트 파일을 어떻게 관리 하는지에 대한 방법론도 제시되고 있으며 관리하는 프레임워크가 존재하는 정도입니다.
가장 기본적인 방법은 컴포넌트의 역할별로 폴더를 나누어 보는것인데요.
우리의 앱은 아래의 구조로 컴포넌트를 관리하겠습니다.
- src/component 폴더에 아래의 폴더를 생성 합니다
- components/listViews
- 리스트를 구성하는 컴포넌트 폴더
- components/ui
- UI 스타일을 구성하는 컴포넌트 폴더
- components/layout
- 전체 레이아웃을 구성하는 컴포넌트 폴더
- 각 폴더에 컴포넌트 파일과 스타일 파일을 생성합니다.
- App 컴포넌트에 임포트 한 MemberItem 컴포넌트를 수정합니다.
- MemberItem 컴포넌트의 코드는 listViews 폴더의 Lists 로 옮깁니다.
- 로고도 삭제합니다.
완료코드
- 아래의 OpenSandbox 를 클릭하면 시작코드를 받을수 있습니다.
4.4. List 작성
- MemberItem 컴포넌트의 코드를 잘라내어 Lists 컴포넌트에 붙여넣습니다.
- List 컴포넌트의 최상위 요소를 div 로 변경합니다.
- MemberItemList 컴포넌트 임포트를 Items로 변경합니다.
4.5. Items 작성
- MemberItemList 컴포넌트의 코드를 잘라내어 Items 컴포넌트에 붙여넣습니다.
- Item.js 에서 MemberItemList 단어를 모두 Items 로 치환합니다.
import './Items.css';
를 잊지 말고 작성합니다.import ItemThumb from './ItemThumb'; import ItemDesc from './ItemDesc';
라인을 삭제후import ItemEl from './ItemEl';
로 수정합니다.- Item.css 를 수정합니다
.items {
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 18px 0px;
padding: 3vw;
margin: 5rem auto;
border-radius: 2rem;
}
img {
width: 25rem;
}
4.6. ItemEl 작성
- 아래와 같이 작성합니다.
import './ItemEl.css';
const ItemEl = (props) => {
return (
<div className='item_el'>
<span className='title'></span>
<span className='content'></span>
</div>
);
};
export default ItemEl;
- Items 컴포넌트로 이동하여 ItemEl 컴포넌트로 표시할 props 를 전달합니다.
return (
<li className='items'>
<img src={props.thumb} alt={props.name} />
<ItemEl title='이름' content={props.name} />
<ItemEl title='이메일' content={props.email} />
</li>
);
- ItemEl 컴포넌트에서 props를 적용합니다.
<span className='title'>{props.title}</span>
<span className='content'>{props.content}</span>
- ItemEl.css 를 작성합니다
.title {
color: var(--tone-1);
font-weight: 900;
margin-right: 2rem;
width: 6rem;
text-align: justify;
font-size: 1.4rem;
letter-spacing: 0.5px;
}
.content {
font-size: 1.8rem;
letter-spacing: -0.8px;
font-size: 1.4rem;
color: var(--shade-2);
border-radius: 25rem;
padding: 0.4rem 1rem;
box-shadow: 2px 2px 1px #3a54771a, 0px 0px 2px #3a547769;
box-shadow: 0px 0px 3px var(--blue);
margin-right: 0.6rem;
background-color: #2ea2cc33;
}
4.7. ui 작성
4.7.1. Row 컴포넌트
- Row 컴포넌트는 박스 정렬 관련 속성을 갖고 있습니다. 부모 컴포넌트의 콘텐츠를 표시할수 있도록 props를 넣습니다.
import './Row.css';
const Row = (props) => {
return <div className='row'>{props.children}</div>;
};
export default Row;
- Row.css 를 작성합니다.
.row {
display: flex;
box-sizing: border-box;
margin-top: 0.5rem;
}
@media screen and (max-width: 900px) {
.row {
flex-direction: column;
gap:2vw;
}
}
4.7.2. Heading 컴포넌트
- Heading 컴포넌트는 제목의 스타일을 갖고 있습니다. 부모 컴포넌트의 콘텐츠를 표시할수 있도록 props를 넣습니다.
import './Heading.css';
const Heading = (props) => {
return (
<>
<h2 className='heading'>{props.content}</h2>
</>
);
};
export default Heading;
- Heading.css 를 작성합니다.
.heading {
padding: 4rem 10% 2rem;
text-align: center;
font-size:4rem;
text-transform:uppercase;
}
4.8. composition 작성
4.8.1.List.js
- Row,Heading 컴포넌트를 임포트 합니다.
- 컴포넌트에 적용합니다.
4.8.2.Items.js
- Row,Heading 컴포넌트를 임포트 합니다.
- 최상위 요소를 div로 수정합니다
- ItemEl 컴포넌트를 Row 컴포넌트로 래핑합니다.
return (
<div className='items'>
<img src={props.thumb} alt={props.name} />
<Row>
<ItemEl title='이름' content={props.name} />
<ItemEl title='이메일' content={props.email} />
</Row>
</div>
);
4.8.3. layout 작성
- layout.css
.main {
margin: 3rem auto;
width: 90%;
max-width: calc(100vw - 10rem);
padding: 2vw 20vw;
}
@media screen and (max-width: 1300px) {
.main {
padding: 2vw 10vw;
}
}
@media screen and (max-width: 700px) {
.main {
padding: 2vw 4vw;
}
}
- Layout.js
import './Layout.css';
const Layout = (props) => {
return <div className='main'>{props.children}</div>;
};
export default Layout;
- App.js
import Layout from './components/layout/Layout';
return (
<div className='App'>
<Layout>
<Lists db={MemberDB} />
</Layout>
</div>
);
5. composition 심화
만약 특정 컴포넌트에서 Row 컴포넌트의 속성을 변형 하려면 어떻게 해야할까요?예를 들면 Lists 컴포넌트에 적용된 Row 만
flex-wrap:warp
로 수정하여 레이아웃의 너비보다 넓을 경우 레이아웃을 개행하고 싶습니다.적용된 Row 컴포넌트에 class 와 속성을 추가허고 부모컴포넌트에서 클래스를 조정하여 모양을 바꿀수는 없을까요?
List 컴포넌트에는 Row 컴포넌트에 className 속성을 작성한 코드가 있습니다. css 로 이동하여 list 클래스의 속성을 정의 합니다.
5.2. src\components\listViews\Lists.css
.list {
flex-wrap: wrap;
}
실행 화면이 변경되지 않았습니다.
이것이 실제 적용이 되었는지 개발자모드에서 살펴보면 row 에는 list 클래스가 적용되지 않았습니다.
Row 컴포넌트에 클래스를 추가하는 코드가 없기 때문인데요 Row 컴포넌트로 이동하여 수정해보겠습니다.
- Row 컴포넌트에서 콘솔로그로 props 의 값을 확인합니다.
- 변수를 만들고 props.className 을 할당합니다
import './Row.css';
const Row = (props) => {
console.log(props);
const addCls = props.className;
return <div className='row'>{props.children}</div>;
};
export default Row;
- div className 을 자바스크립트 표현식으로 변경합니다.
return <div className={`row ${addCls}`}>{props.children}</div>;
이제 Row를 임포트 하는 컴포넌트에서 클래스를 추가하면 개별적인 스타일링을 할수 있습니다.