안 쓰던 블로그
To Do List 웹 프로젝트_UI 디자인 및 구성(css사용) 본문
컴포넌트 계획
1. PageTemplate
이 컴포넌트는 UI의 전체적인 틀을 잡는다
흰색 배경+그림자 위에 '일정 관리'라는 타이틀 보여 주고, 타이틀 아래에는 children값으로 아래 들어갈 컴포넌트들을 넣는다
2. TodoInput
이 컴포넌트는 일정을 추가할 때 사용하는 input 컴포넌트로, 버튼을 누르는 버튼 이벤트가 들어갈 것이다
3. TodoItemList
TodoItem 컴포넌트 여러 개를 렌더링 하는 역할이다
4. TodoItem
일정을 렌더링하는 컴포넌트다
클릭하면 체크되면서 전체에 줄을 긋는다
지우기 버튼을 누르면 일정을 화면에서 없앤다
1. PageTemplate 컴포넌트 생성
먼저 스타일링부터 한다
src/components/PageTemplate/PageTemplate.scss를 만든다
.todo-list-template{
margin-top: 5rem;
margin-left: auto;
margin-right: auto;
width: 500px;
background: white;
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
padding-top: 2rem;
}
.title{
padding: 2rem;
font-size: 3rem;
text-align: center;
font-weight: 100;
background: #ffa94d;
color: white;
}
.form-wrapper{
text-align: center;
font-size: 4rem;
font-weight: 300;
margin: 0;
border-bottom: #ffa94d;
}
.todos-wrapper{
margin-top: 2rem;
}
색 참고: https://yeun.github.io/open-color/
다음 PageTemplate.js를 만든다
import React from 'react';
import './PageTemplate.scss';
const PageTemplate = ({form, children}) => {
return(
<main className="todo-list-template">
<div className="title">
오늘 할 일
</div>
<section className="form-weapper">
{form}
</section>
<section className="todos-wrapper">
{children}
</section>
</main>
);
};
export default PageTemplate;
함수형 컴포넌트지만 파라미터를 (props)=>{...}가 아닌 ({form, children})=>{...}으로 비구조화 할당을 하였다
즉 form과 children이라는 props 두 가지를 받게 된다
form은 나중에 JSX형태로 전달해서 인풋과 버튼 컴포넌트를 렌더링 할 때 사용하고,
children도 JSX형태로 wrapper 컴포넌트를 만들어 넣을 것이다
index.css에서 배경색을 회색으로 만들고
@import 'utils';
body{
background: $oc-gray-1;
margin: 0px;
}
App.js에서 렌더링 해 준다
import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
class App extends Component {
render() {
return (
<PageTemplate>
일정관리
</PageTemplate>
);
}
}
export default App;
이제 인풋과 버튼이 담긴 form을 만들어봅시다
2. TodoInput 컴포넌트 생성
TodoInput.js를 만들었다
import React from 'react';
import './TodoInput.scss';
const TodoInput = ({value, onChange, onCreate, onKeyPress})=>{
return(
<div className="form">
<input value={value} onChange={onChange} onKeyPress={onKeyPress}/>
<div className="create-button" onClick={onCreate}>
추가
</div>
</div>
)
}
export default TodoInput;
value는 인풋 내용
conCreate는 버튼 클릭 시 실행 될 함수
onChange는 인풋 내용이 변경 될 때 실행 될 함수
onKeyPress는 인풋에서 키를 입력 할 때 실행되는 함수. 엔터를 누르면 onCreate를 한 것과 같다
const handleKeyPress=(e)=>{
if(e.key=='Enter'){
onInsert();
}
}
전에는 이걸 내부에 만들고 바로 onKeyPree={handleKeyPress}하고 불러왔는데 이제는 그냥 위에처럼 써도 되는 것 같다
TodoInput.scss 파일을 만들어 준다
.form{
//border-top: 1px solid #ced4da;
border-bottom: 2px solid #ffa94d;
display: flex;
padding: 1rem;
input{
flex:1; //부모 요소에서 add-button을 제외한 나머지 공간 차지
font-size: 1.1rem;
outline: none;
border:none;
background: transparent;
border-bottom: 1px solid #ced4da;
&:focus{
border-bottom: 1px solid #ffa94d;
}
}
}
.create-button{
width: 5rem;
height: 2rem;
margin-left:1rem;
border: 1px solid #ffa94d;
color: #ffa94d;
font-weight: 500;
font-size: 1.1rem;
text-align: center;
line-height: 2rem;
cursor: pointer;
&:hover{
background: #ffa94d;
color:white;
}
&:active{
background: #fab005;
}
}
App.js에 추가 해 준다
import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
import TodoInput from './TodoInput';
class App extends Component {
render() {
return (
<PageTemplate>
<TodoInput />
</PageTemplate>
);
}
}
export default App;
이쁜 인풋과 버튼이 나왔다
버튼 위에 커서를 올리면 색이 변한다
3. TodoItemList 컴포넌트 생성
일정 정보를 보여주는 TodoItem 컴포넌트를 만들기 전에, 그 컴포넌트 여러 개를 묶어 렌더링하는 TodoItemList 컴포넌트를 생성한다
리스트를 렌더링 할 때는 함수형이 아닌 클래스형 컴포넌트로 작성하는 게 좋다(특히 동적 리스트인 경우)
왜냐하면 클래스형 컴포넌트로 작성해야 나중에 컴포넌트 성능 최적화를 할 수 있기 때문이다
TodoItemList.js를 만들어 준다
import React, { Component } from 'react';
import TodoItem from '../TodoItem/TodoItem';
class TodoItemList extends Component {
render() {
//const { todos, onToggle, onRemove } = this.props;
return (
<div>
<TodoItem text="안녕"/>
<TodoItem text="리액트 공부"/>
</div>
);
}
}
export default TodoItemList;
todos는 todo 객체들이 들어있는 배열
onToggle은 체크박스를 키고 끄는 함수
onRemove는 아이템을 삭제시키는 함수다
지금은 구현하지 않지만 나중을 위해 미리 적어두고 주석처리 해주었다
4. TodoItemList 컴포넌트 생성
TodoItem.js 생성
import React, { Component } from 'react';
import './TodoItem.scss';
class TodoItem extends Component {
render() {
const { text, checked, id, onToggle, onRemove } = this.props;
return (
<div className="todo-item" onClick={() => onToggle(id)}>
<div className="remove" onClick={(e) => {
e.stopPropagation(); //onToggle이 실행되지 않도록
onRemove(id)}
}>×</div>
<div className={`todo-text ${checked && 'checked'}`}>
<div>{text}</div>
</div>
{
checked && (<div className="check-mark">✓</div>)
}
</div>
);
}
}
export default TodoItem;
text는 todo의 내용
checked는 체크박스 상태
id는 todo의 고유 아이디
onToggle은 체크박스를 키고 끄는 함수
onRemove는 아이템을 삭제시키는 함수
const { text, checked, id, onToggle, onRemove } = this.props;
이렇게 비구조화 할당 문법으로 props안에 있는 값들의 레퍼런스를 만들어 주면,
props를 사용할 때마다 this.props.text 처럼 앞에 this.props를 붙이는 것을 생략할 수 있다
앞으로도 class문법으로 컴포넌트를 만들면서 props를 사용할 때 이런 식으로 비구조화 할당을 해 주면 좋다
편리할 뿐만 아니라 렌더링 함수 위쪽에서 이 컴포넌트가 어떤 props를 사용하는지 한 눈에 들어오기 때문이다
<div className="todo-item" onClick={() => onToggle(id)}>
최상의 div요소에는 onClick이벤트로 onToggle함수를 연결했다
<div className="remove" onClick={(e) => {
e.stopPropagation(); //onToggle이 실행되지 않도록
onRemove(id)}
}>×</div>
삭제를 위한 x 마크가 아이템 요소마다 붙을 건데, 거기에 들어갈 onRemove를 만들어주었다
e.stopPropagation()을 하지 않으면, x를 눌렀을 때 onRemove 뿐만 아니라 이 DOM의 부모의 클릭 이벤트에 연결되어 있는 onToggle도 실행이 된다
그러면 onRemove도 눌리고 onToggle도 눌리니까 원하지 않은 동작을 할 수도 있다
이건 onClick을 상위 요소와 하위 요소에 다 사용했을 때의 문제점인데, e.topPropagation을 쓰면 부모를 실행하지 않고 onRemove만 딱 실행해 준다
<div className={`todo-text ${checked && 'checked'}`}>
이 코드는 "todo-text"+checked && 'checked' 와 동일하다
checked값에 따라 className이 유동적으로 설정되는 코드다
근데 사실 이 부분은 className을 쓰면 훨씬 간단한 코드로 만들 수 있다 (참고: https://velog.io/@velopert/react-component-styling )
참고하고 있는 책 '리액트를 다루는 기술'에서는 classname을 썼는데, 이 글에서는 이 블로그( https://velopert.com/3480 ) 도 함께 참고하느라 이런 방식을 사용했다
저 책을 참고하면 classname을 사용한 todolist 코드를 볼 수 있다
참고로 classname을 사용하면 이렇게 된다
classnames('todo-text', {checked})
다음으로 TodoItem.scss를 만들어준다
.todo-item {
padding: 1rem;
display: flex;
align-items: center; //세로 가운데 정렬
cursor: pointer;
transition: all 0.15s;
user-select: none;
}
.todo-item:hover {
background: #e3fafc;
}
.todo-item:hover .remove {
opacity: 1;
}
.todo-item + .todo-item {
border-top: 1px solid #f1f3f5;
}
.remove {
margin-right: 1rem;
color: #e64980;
font-weight: 600;
opacity: 0;
}
.todo-text {
flex: 1;
word-break: break-all;
}
.checked {
text-decoration: line-through;
color: #adb5bd;
}
.check-mark {
font-size: 1.5rem;
line-height: 1rem;
margin-left: 1rem;
color: #3bc9db;
font-weight: 800;
}
App.js에 추가해준다
import React, { Component } from 'react';
import PageTemplate from './PageTemplate';
import TodoInput from './TodoInput';
import TodoItemList from './TodoItemList';
class App extends Component {
render() {
return (
<PageTemplate>
<TodoInput />
<TodoItemList />
</PageTemplate>
);
}
}
export default App;
이쁘게 UI가 나왔다
아직 추가, 삭제 등의 이벤트는 구현되지 않은 상태다
참고: 리액트를 다루는 기술_김민준 저, https://velopert.com/3480
'Web > React' 카테고리의 다른 글
To Do List 웹 프로젝트-상태 관리 (0) | 2020.07.23 |
---|---|
code: 'MODULE_NOT_FOUND', requireStack: [webpack.config.js'] 해결 방법 react error (0) | 2020.07.21 |
To Do List 웹 프로젝트_준비 및 시작 (0) | 2020.07.16 |
React-컴포넌트 스타일링(CSS, Sass, styled-components) (0) | 2020.07.09 |
React-함수형 컴포넌트 (0) | 2020.07.09 |