프로젝트 구조
만들어볼 프로젝트의 구조는 아래와 같습니다.
프로젝트는 초기 세팅 상태 로 진행되어집니다.
1) 레이아웃 구성
스타일링을 위해 styled-components 를 설치합니다.
Copy $ npm i -S styled-components
먼저 왼쪽의 목록 컴포넌트부터 만들어보도록 하겠습니다.
목록 컴포넌트가 해주는 일은 간단합니다.
목록 데이터를 받아 그려줍니다. 목록의 메모를 클릭시 클릭된 메모를 오른쪽에 그려줍니다.
이번 예제에서는 Context API 를 이용하여 데이터의 상호작용이 이루어 질 수 있도록 합니다.
먼저 src 폴더 아래에 memos 폴더와 content 폴더를 만들고 내부에 index.js 들을 생성해주세요
Copy // src/memos/index.js
import React from "react" ;
import styled from "styled-components" ;
const MemoFrame = styled . div `` ;
function Memos () {
return < MemoFrame >Memos</ MemoFrame >;
}
export default Memos;
// src/content/index.js
import React from "react" ;
import styled from "styled-components" ;
const ContentFrame = styled . div `` ;
function Content () {
return < ContentFrame >Memo</ ContentFrame >;
}
export default Content;
혹시 styled-component 의 사용법을 잘 모르신다면 이전 예제들을 확인해주세요
만들어진 컴포넌트들을 App.js 에서 불러와서 간단한 레이아웃 구조를 생성해보겠습니다.
Copy // src/App.js
import React from "react" ;
import styled , { css } from "styled-components" ;
import Memos from "./memos" ;
import Content "./content" ;
// 왼쪽 오른쪽 구조를 나눠야 하기 때문에 flex 를 이용합니다.
const AppFrame = styled . div `
display: flex;
height: 100vh;
` ;
/*
나눠진 왼쪽 오른쪽에 flex 사이즈를 유동적으로 조절하기 위해 flex 값을
props 로 내려받습니다.
*/
const Container = styled . div `
${ ({ flex }) =>
flex &&
css `
display: flex;
flex: ${ flex } ;
` }
` ;
function App() {
return (
<AppFrame>
<Container flex={1}>
<Memos />
</Container>
<Container flex={2}>
<Content />
</Container>
</AppFrame>
);
}
export default App;
아래와 같이 나누어진 화면을 보실 수 있을거에요
2) useContext 를 이용하여 Context API 구성
왼쪽 목록의 컴포넌트에서는 선택된 메모의 값을 알아야하고
오른쪽 컴포넌트에서는 선택된 메모 값을 바탕으로 데이터를 그려줘야합니다.
이처럼 컴포넌트들 끼리의 상호작용이 일어날때 가장 간단한 방법은
두 컴포넌트를 감싸는 컴포넌트로 state 를 올려서 사용하는 방법이 있지만
이번에는 Context API 를 이용하여 데이터를 주고 받는 방법을 알아보려고합니다.
use-Context
라는 hook 을 이용하여 Context API 를 보다 손쉽게 사용하도록 할 수 있습니다.
다양한 Context 를 만들 수 있는 방법은 여기 를 참고해주세요
src 아래에 application-context 라는 이름의 파일을 만들어주세요
Copy // src/application-context.js
import React , { createContext , useContext , useState } from "react" ;
// Context 를 생성합니다.
const Context = createContext ( null );
// Provider 로 감싸지는 컴포넌트들은 value 의 값을 props 로 받을 수 있습니다.
export function ApplicationContextProvider ({ children }) {
const [ memos , setMemos ] = useState ( null );
const [ memo , setMemo ] = useState ( null );
const value = {
memos ,
setMemos ,
memo ,
setMemo
};
return < Context.Provider value = {value}>{children}</ Context.Provider >;
}
// 외부에서 context 를 손쉽게 가져다 쓸 수 있도록 도와줍니다.
export function useApplicationContext () {
return useContext (Context);
}
여기서 잠깐, useState 란 ?
useState 는 이전에 보았던 class component 의 state = { // ... }
와 같습니다. 다른 점이 있다면 setState 로 변경했던 것과는 다르게 useState
는 해당 state 를 변경 할 수 있는 짝을 지원해 준다는 것입니다.
Copy const [ count , setCount ] = useState ( 0 )
// count 의 값을 바꿀 수 있는 방법은 setCount 를 이용하는 방법뿐이다.
// naming 규칙은 보통 set + state 명 입니다.
3) 만들어진 Context API 이용하기
Context API 는 말그대도 관련있는 Context 에서만 값을 이용할 수 있도록 만들어 줄 수 있습니다.
하지만 지금 우리는 Context 범위가 적기 때문에 두 컴포넌트를 감싸고 있는 App.js 에서 Provider 를 적용해주도록 하겠습니다.
Copy // src/app.js
import React from "react" ;
import styled , { css } from "styled-components" ;
import { ApplicationContextProvider } from "./application-context" ;
import Memos from "./memos" ;
import Content from "./content" ;
const AppFrame = styled . div `
display: flex;
height: 100vh;
` ;
const Container = styled . div `
${ ({ flex }) =>
flex &&
css `
display: flex;
flex: ${ flex } ;
` }
` ;
function App () {
return (
< ApplicationContextProvider >
< AppFrame >
< Container flex = { 1 }>
< Memos />
</ Container >
< Container flex = { 2 }>
< Content />
</ Container >
</ AppFrame >
</ ApplicationContextProvider >
);
}
export default App;
이제 Provider 로 감싸져 있는 내부 요소들에서는 useApplicationContext 를 이용하여 Context 내부 값에 접근 할 수 있습니다.
4) Memos 컴포넌트에서 Context API 사용하기
Memos 컴포넌트를 작성하기전에 먼저 Context API의 memos에 dummy data 를 채워보고자합니다.
메모는 아래와 같은 데이터 구조를 갖습니다.
Copy id : number = '메모의 고유한 id'
title : string = '메모의 제목'
content : string = '메모의 내용'
useState 는 인자로 default value 를 줄 수 있습니다. memos 의 default Value 로 dummy data 가 추가된 배열을 줍니다.
Copy import React , { createContext , useContext , useState } from "react" ;
const Context = createContext ( null );
export function ApplicationContextProvider ({ children }) {
// useState 의 default Value 를 이용하여 값을 채워줍니다.
const [ memos , setMemos ] = useState ([
{
id : Date .now () ,
title : "임시 메모 데이터" ,
content : "임시 메모 데이터의 내용"
}
]);
const [ memo , setMemo ] = useState ( null );
const value = {
memos ,
setMemos ,
memo ,
setMemo
};
return < Context.Provider value = {value}>{children}</ Context.Provider >;
}
export function useApplicationContext () {
return useContext (Context);
}
이제 memos 에서 context api 에 접근하여 memos 데이터가 잘 불러와지는지 확인해보겠습니다.
Copy // src/memos/index.js
import React from "react" ;
import styled from "styled-components" ;
import { useApplicationContext } from "../application-context" ;
const MemosFrame = styled . div `` ;
function Memos () {
const { memos } = useApplicationContext ();
console .log ( "memos" , memos);
return < MemosFrame >Memos</ MemosFrame >;
}
export default Memos;
memos 데이터를 가지고 왼쪽 리스트를 구성해야하는데 레이아웃 그림에서 보신 것 처럼 같은 형식의 구조가 반복되어지고 있습니다.
이 컴포넌트를 리스트자체에서 그려주는 것 보다는 memo 라는 컴포넌트를 만들어 따로 그려주는것이 렌더링 이점과 사용 측면에서도 좋기 때문에 따로 분리를 하겠습니다.
memo 컴포넌트는 id, title, content 의 데이터를 props 로 받습니다.
Copy // src/memo/index.js
import React from "react" ;
import styled from "styled-components" ;
const MemoFrame = styled . div `` ;
function Memo ({ source: { title , content }}) {
return < MemoFrame >Memo</ MemoFrame >;
}
export default Memo;
만든 Memo 를 Memo 에서 사용하도록 추가합니다.
Copy // src/memos/index.js
import React from "react" ;
import styled from "styled-components" ;
import { useApplicationContext } from "../application-context" ;
import Memo from "../memo" ;
// MemosFrame style 을 추가합니다.
const MemosFrame = styled . div `
width: 100%;
padding: 10px;
box-sizing: border-box;
overflow-y: auto;
` ;
function Memos () {
const { memos } = useApplicationContext ();
console .log ( "memos" , memos);
// 만들어진 Memo 를 가져와 사용합니다.
return (
< MemosFrame >
{ memos .map (memo => (
< Memo key = { memo .id} source = {memo} />
))}
</ MemosFrame >
);
}
export default Memos;
5) 공통 컴포넌트 만들기
다양한 텍스트를 표현하기 위해 공통적으로 사용할 Text 컴포넌트와
컴포넌트들을 감싸줄 Container 만들어보겠습니다.
Text 의 경우 당장의 필요한 속성은 텍스트의 사이즈, 굵기, 말줄임, line-height 입니다.
앞으로 필요한 속성은 하나씩 추가하면서 살펴보겠습니다.
Copy // src/text.js
import styled , { css } from "styled-components" ;
const Text = styled . div `
font-size: ${ ({ size }) => size || 14 } px;
${ ({ lineHeight }) =>
lineHeight &&
css `
line-height: ${ lineHeight } ;
` }
${ ({ bold }) =>
bold &&
css `
font-weight: bold;
` }
${ ({ ellipsis }) => css `
overflow: hidden;
text-overflow: ellipsis;
white-space: normal;
word-wrap: break-word;
display: -webkit-box;
-webkit-line-clamp: ${ ellipsis } ;
-webkit-box-orient: vertical;
` }
` ;
export default Text;
Container 는 float, margin, padding, display 가 필요합니다.
Copy // src/container.js
import styled , { css } from "styled-components" ;
const Container = styled . div `
width: 100%;
${ ({ justify }) =>
justify &&
css `
justify-content: ${ justify } ;
` }
${ ({ margin }) =>
margin &&
css `
margin-top: ${ margin .top } px;
margin-right: ${ margin .right } px;
margin-bottom: ${ margin .bottom } px;
margin-left: ${ margin .left } px;
` }
${ ({ padding }) =>
padding &&
css `
padding-top: ${ padding .top } px;
padding-right: ${ padding .right } px;
padding-bottom: ${ padding .bottom } px;
padding-left: ${ padding .left } px;
` } ;
${ ({ flex }) =>
flex &&
css `
display: flex;
flex: ${ flex } ;
` }
` ;
export default Container;
App 에 있는 Container 를 공통 Container 로 변경합니다.
Copy // src/App.js
import React from "react" ;
import styled from "styled-components" ;
import { ApplicationContextProvider } from "./application-context" ;
import Container from './container'
import Memos from "./memos" ;
import Content from "./content" ;
const AppFrame = styled . div `
display: flex;
height: 100vh;
` ;
function App () {
return (
< ApplicationContextProvider >
< AppFrame >
< Container flex = { 1 }>
< Memos />
</ Container >
< Container flex = { 2 }>
< Content />
</ Container >
</ AppFrame >
</ ApplicationContextProvider >
);
}
export default App;
6) Memo 구성해보기
위에서 만들어 놓은 text 를 이용하여 Memo 컴포넌트를 만들어보겠습니다.
content 부분은 너무 길어질 수 있기 때문에 말줄임을 적용합니다.
Copy import React from "react" ;
import styled from "styled-components" ;
import Text from "../text" ;
const MemoFrame = styled . div `
border: 1px solid #ebebeb;
border-radius: 5px;
padding: 10px;
&:not(:last-child) {
margin-bottom: 15px;
}
` ;
function Memo ({ source: { title , content } }) {
return (
< MemoFrame >
< Text size = { 16 } lineHeight = { 1.53 } bold >
{title}
</ Text >
< Text size = { 15 } lineHeight = { 1.53 } ellipsis = { 2 }>
{content}
</ Text >
</ MemoFrame >
);
}
export default Memo;
7) Content 컴포넌트 구성하기
Content 컴포넌트는 선택된 Memo 에 대한 내용을 보여줍니다.
Content 에서는 Context API 의 Memo State 를 사용하여 내용을 구성합니다.
Context API 에 dummy data 를 추가하여 view 부터 구성해보겠습니다.
우리가 추가해놓았던 임시메모데이터라는 메모가 선택되어졌다고 가정하겠습니다.
Copy import React , { createContext , useContext , useState } from "react" ;
const Context = createContext ( null );
export function ApplicationContextProvider ({ children }) {
const [ memos , setMemos ] = useState ([
{
id : Date .now () ,
title : "임시 메모 데이터" ,
content : "임시 메모 데이터의 내용" ,
createdAt : new Date ()
}
]);
// memos 와는 다르게 단일 객체입니다.
const [ memo , setMemo ] = useState ({
id : Date .now () ,
title : "임시 메모 데이터" ,
content : "임시 메모 데이터의 내용" ,
createdAt : new Date ()
});
const value = {
memos ,
setMemos ,
memo ,
setMemo
};
return < Context.Provider value = {value}>{children}</ Context.Provider >;
}
export function useApplicationContext () {
return useContext (Context);
}
Content 에서 Context API 의 memo 값을 가져옵니다.
이전에 만들어뒀던 Container 를 이용하여 간격을 조절하고 Text 를 이용하여 텍스트 스타일을 추가합니다
Copy // src/content/index.js
import React from "react" ;
import { useApplicationContext } from "../application-context" ;
import Text from "../text" ;
import Container from "../container" ;
function Content () {
const { memo } = useApplicationContext ();
const { title , content } = memo;
return (
< Container padding = {{ top : 10 , right : 10 , bottom : 10 , left : 10 }}>
< Container margin = {{ bottom : 20 }}>
< Text size = { 27 } bold >
{title}
</ Text >
</ Container >
< Text size = { 16 }>{content}</ Text >
</ Container >
);
}
export default Content;
8) Content 컴포넌트 상태에 따른 버튼
Content 컴포넌트에선 선택된 Memo 의 내용을 수정 할 수 있습니다.
Content 본문 윗 쪽에 수정 모드로 전환 할 수 있는 버튼을 추가합니다.
3 가지의 상태에 따라 버튼은 동작과 텍스트가 달라집니다.
1) 선택된 Memo 가 있고 수정 상태가 아니면 (새 글 작성 + 수정) 버튼을 노출
2) 선택된 Memo 가 있고 수정 상태이면 (수정 + 취소) 버튼을 노출
3) 수정 상태가 아니라면 (새 글 작성 노출)
Copy // src/content/index.js
import React , { useState } from "react" ;
import styled from "styled-components" ;
import { useApplicationContext } from "../application-context" ;
import Text from "../text" ;
import Container from "../container" ;
const Button = styled . button `
padding: 5px 15px;
color: ${ ({ active }) => (active ? "#fff" : "#368fff" ) } ;
background: ${ ({ active }) => (active ? "#368fff" : "#fff" ) } ;
border: 1px solid #368fff;
border-radius: 2px;
font-size: 13px;
font-weight: bold;
&:not(:last-child) {
margin-right: 5px;
}
` ;
function Content () {
const { memo } = useApplicationContext ();
// 후에 메모를 수정 할 때 사용될 state 입니다.
const [ editMemo ] = useState ({
title : "" , // 수정 할 타이틀
content : "" , // 수정 할 컨텐츠
isEditing : false // 수정 모드인지
});
const { title , content } = memo;
// 수정 모드를 판단합니다.
const { isEditing } = editMemo;
return (
< Container padding = {{ top : 10 , right : 10 , bottom : 10 , left : 10 }}>
< Container flex justify = "flex-end" >
{memo && ! isEditing && < Button active >수정</ Button >}
{memo && isEditing && (
<>
< Button >취소</ Button >
< Button active >수정</ Button >
</>
)}
{ ! isEditing && < Button >새 글 작성</ Button >}
</ Container >
< Container margin = {{ bottom : 20 }}>
< Text size = { 27 } bold >
{title}
</ Text >
</ Container >
< Text size = { 16 }>{content}</ Text >
</ Container >
);
}
export default Content;