1. JSX란?
JSX는 Javascript Syntax extension, 자바스크립트의 확장 문법이다.
리액트에서는 JSX를 이용해 화면에서 보이는 UI를 구성하는데, 이 때 자바스크립트의 로직과 HTML 구조를 같이 사용할 수 있어서
기본 UI에 데이터가 변하는 것이나 이벤트들을 처리하기 용이하고 보다 쉽게 구현할 수 있다.
물론 JSX를 쓰지 않으면 리액트를 쓸 수 없다 이런 것은 아니지만, JSX를 쓰지 않으면 일일이 ReactDOM을 렌더 해줘야 된다.
그냥 대충 말하자면 React.createElement로 형태/속성/내용을 할당한 후 렌더를 또 해야 한다.
이런 식으로...
<script>
const root = document.getElementById("root");
const h3 = React.createElement( //형태, 속성, 내용
"h3",
{
onMouseEnter: () => console.log("mouse eneter"),
},
"Hello, I'm a span"
);
const btn = React.createElement(
"button",
{
onClick: () => console.log("i'm clicked"),
style: {
backgroundColor: "tomato",
},
},
"Click me"
);
const container = React.createElement("div", null, [h3, btn]);
ReactDOM.render(container, root);
</script>
노마드코더 때 리액트 JSX 없이 한 번 해봤는데 그게 무슨 말인지 이제 좀 알겠다 :)
아무튼 그래서 JSX는 어떻게 쓰는 거냐?
일단 컴포넌트에 여러 엘리먼트 요소가 있다면 반드시 부모 요소로 감싸주어야 한다.
아직 자세한 문법은 나도 공식 문서를 못 봤고 강의를 들으며 따라간 상태라 찾아보겠다.
대충 생긴 건 HTML 인데 사실은 JS지롱~ 이런 느낌이다.
JSX 소개 – React
A JavaScript library for building user interfaces
ko.legacy.reactjs.org
2. to do 앱 만들기
이번 수업에서는 클래스형 컴포넌트를 만들어보았다.
먼저 react 라이브러리에서 Component를 가져오고, 사용하겠다고 extends 해주어야 한다.
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
render() {
return (
<div className="container">
<div className="todo-block">
<div className="title">
<h1> 할 일 목록</h1>
</div>
</div>
</div>
);
}
}
이런 UI를 만들기 위해서는 [체크박스] [할 일 목록]을 만들어야 한다.
1. 체크박스
- input type="checkbox" defaultChecked={false}로 만들어준다.
- 버튼은 btnStyle이라는 객체를 만들어 this.btnStyle로 style 속성을 부여한다. (this에 대해서도 공부해야 한다.)
2. 할 일 목록
- div에 style 속성을 주되 getStyle이라는 함수를 통해 준다.
- getStyle 역시 배열 형태로 패딩값, 보더바텀이나 텍스트데코레이션 등을 준다.
text-decoration - CSS: Cascading Style Sheets | MDN
The text-decoration shorthand CSS property sets the appearance of decorative lines on text. It is a shorthand for text-decoration-line, text-decoration-color, text-decoration-style, and the newer text-decoration-thickness property.
developer.mozilla.org
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
btnStyle = {
color: "#fff",
border: "none",
padding: "5px 9px",
borderRadius: "50%",
cursor: "pointer",
float: "right",
};
getStyle = () => {
return {
padding: "10px",
borderBottom: "1px #ccc dotted",
textDecoration: "none",
};
};
render() {
return (
<div className="container">
<div className="todo-block">
<div className="title">
<h1> 할 일 목록</h1>
</div>
<div style={this.getStyle()}>
<input type="checkbox" defaultChecked={false} />
공부하기
<button style={this.btnStyle}> ❌ </button>
</div>
</div>
</div>
);
}
}
3. to do 추가하기
Map 메소드를 사용해 할 일들을 추가해보자.
[todoData 배열 만들기 (id, title, completed 여부) ➡️ map 메서드로 객체를 꺼내어 새 배열을 리턴]
이 과정에서 id는 독립적인 요소를 위해 필요하다.
JSX에서는 어떤 값들을 나열할 때 반드시 key 값을 넣어주어야 한다.
이 key는 리액트가 변경, 추가, 제거된 항목을 식별하는데에 사용되기 때문이다.
이전 게시글을 보면 리액트는 가상 돔을 이용한다고 말했는데,
이 키를 이용해서 어떤 부분이 바뀌었는지 인지해 실제 돔에 적용할 수 있다.
따라서 key값은 유일한 값이어야 하며, index도 사용하긴 하지만 리스트가 추가/제거되면 index 역시 변경되므로 지양하는 게 좋다.
또 안정적인 id를 사용하려면 그냥 편하게 배열 내부의 요소에 key를 만들어두는 것이 효율적이다.
todoData = [
{
id: "1",
title: "공부하기",
completed: true,
},
{
id: "2",
title: "청소하기",
completed: false,
},
];
render() {
return (
<div className="container">
<div className="todo-block">
<div className="title">
<h1> 할 일 목록</h1>
</div>
{this.todoData.map((data) => (
<div style={this.getStyle()} key={data.id}>
<input type="checkbox" defaultChecked={false} />
{data.title}
<button style={this.btnStyle}> ❌ </button>
</div>
))}
</div>
</div>
);
}
이렇게 key에 data.id를 사용해 독립적인 값을 주었고, todoData에 map 메소드를 사용해 할 일 목록을 꺼내주었다.
4. 할 일을 체크해 지우고 다시 렌더링 해보자
체크 박스에 클릭 이벤트가 발생하면 할 일 목록을 지우게 한다.
onClick일 때 data.id를 매개변수로 하는 handleClick 함수를 생성하고, data.id !== id인 경우만 보이게 하려고 한다.
즉 클릭한 할 일의 아이디가 아닌 아이디만 보여지게 하는 것이다.
하지만 이 체크하고 할 일이 지워지는 모습은 UI에서 그려지지 않는다.
그 이유는 리액트에서는 데이터가 변할 때 화면을 다시 렌더링 해줘야 하기 때문이다.
이 때 State가 사용된다.
이 놈은 컴포넌트의 렌더링 결과물에 영향을 주는 데이터를 갖고 있는 객체다.
말이 뭔가 어려운데 간단히 말해서 이 녀석이 변경되면 컴포넌트는 다시 렌더링 되고 UI에 그려진다.
그래서 어떻게 다시 렌더링을 하냐면, state라는 객체를 만들고 그 안에 todoData 배열을 넣는다.
handleClick 함수는 id를 매개변수로 작동하며, 그 안의 newTodoData는 id가 같지 않은 녀석들만 filter로 걸러낸 배열이다.
setState로 todoData : newTodoData의 키, 값을 가지고 todoData에 newTodoData만 보여지게 다시 렌더링한다.
말이 어려운 것 같은데 아래 코드를 보자.
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
state = {
todoData: [
{
id: "1",
title: "공부하기",
completed: true,
},
{
id: "2",
title: "청소하기",
completed: false,
},
],
};
btnStyle = {
color: "#fff",
border: "none",
padding: "5px 9px",
borderRadius: "50%",
cursor: "pointer",
float: "right",
};
getStyle = () => {
return {
padding: "10px",
borderBottom: "1px #ccc dotted",
textDecoration: "none",
};
};
handleClick = (id) => {
let newTodoData = this.state.todoData.filter((data) => data.id !== id);
this.setState({ todoData: newTodoData });
};
render() {
return (
<div className="container">
<div className="todo-block">
<div className="title">
<h1> 할 일 목록</h1>
</div>
{this.state.todoData.map((data) => (
<div style={this.getStyle()} key={data.id}>
<input type="checkbox" defaultChecked={false} />
{data.title}
<button
style={this.btnStyle}
onClick={() => this.handleClick(data.id)}
>
{" "}
❌{" "}
</button>
</div>
))}
</div>
</div>
);
}
}
handleClick 부분을 보면 클릭이 일어난 곳의 id를 매개변수로 어디서 클릭이 일어났는지 filter를 통해 거르고,
걸러진 할 일만이 보여질 수 있도록 다시 렌더링 한 것이다.
5. 할 일을 추가해보자
form으로 감싼 할 일을 입력할 input box와 입력 버튼을 구현하고 실제로 입력해본다.
<form style={{ display: "flex" }} onSubmit={this.handleSubmit}>
<input
type="text"
name="value"
style={{ flex: "10", padding: "5px" }}
placeholder="해야 할 일을 입력하세요."
value={this.state.value}
onChange={this.handleChange}
></input>
<input
type="submit"
value="입력"
className="btn"
style={{ flex: "1" }}
></input>
</form>
input box의 onChange 이벤트를 매개변수로 setState에 value를 event.target.value로 입력한 값을 렌더링 한다.
입력 버튼을 누르면 submit 되는데, 이 때 새로고침이 되므로 default 를 해서 새로고침을 방지해준다.
입력을 하면 newTodo 라는 객체에 똑같이 id (유일한 값이어야 하므로 Date.now를 사용했다) 와 title (title은 state의 value이자 인풋 박스에 작성한 내용), completed 여부를 담게 된다.
새로운 할 일을 더해줄 때는 js처럼 push를 하는 게 아니라 다른 함수를 이용해 값을 변경해주어야 한다.
전개구문을 통해 기존의 state에 있는 todoData와 새로 만든 newTodo를 합쳐주고, value (입력 받아야 하는 곳)은 다시 빈칸으로 돌려준다.
참고로 전개 구문은 ES6에서 추가되었는데 특정 객체 또는 배열의 값을 다른 객체나 배열로 복제 / 옮길 때 사용한다.
그대로 복사하는 느낌으로 ... 을 적어주면 되고 기존 배열을 보존해서 원본은 바뀌지 않는다.
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
state = {
todoData: [
{
id: "1",
title: "공부하기",
completed: true,
},
{
id: "2",
title: "청소하기",
completed: false,
},
],
value: "",
};
btnStyle = {
color: "#fff",
border: "none",
padding: "5px 9px",
borderRadius: "50%",
cursor: "pointer",
float: "right",
};
getStyle = () => {
return {
padding: "10px",
borderBottom: "1px #ccc dotted",
textDecoration: "none",
};
};
handleClick = (id) => {
let newTodoData = this.state.todoData.filter((data) => data.id !== id);
this.setState({ todoData: newTodoData });
};
handleChange = (event) => {
this.setState({ value: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
//새로운 할 일 데이터
let newTodo = {
id: Date.now(),
title: this.state.value,
completed: false,
};
//새로운 할 일 더해주기
this.setState({ todoData: [...this.state.todoData, newTodo], value: "" });
};
render() {
return (
<div className="container">
<div className="todo-block">
<div className="title">
<h1> 할 일 목록</h1>
</div>
{this.state.todoData.map((data) => (
<div style={this.getStyle()} key={data.id}>
<input type="checkbox" defaultChecked={false} />
{data.title}
<button
style={this.btnStyle}
onClick={() => this.handleClick(data.id)}
>
{" "}
❌{" "}
</button>
</div>
))}
<form style={{ display: "flex" }} onSubmit={this.handleSubmit}>
<input
type="text"
name="value"
style={{ flex: "10", padding: "5px" }}
placeholder="해야 할 일을 입력하세요."
value={this.state.value}
onChange={this.handleChange}
></input>
<input
type="submit"
value="입력"
className="btn"
style={{ flex: "1" }}
></input>
</form>
</div>
</div>
);
}
}
6. 마무리한 할 일은 지워주자
data.completed를 인자로 받아 getStyle에 영향을 주자.
textDecoration에서 completed 가 true라면 line-throug를, false라면 none을 주는 삼항 연산자를 사용한다.
체크박스를 클릭했을 때 변화를 감지하는 handleCompletedChange 함수에 id를 인자로 주고, data.id와 같다면 completed에 준 값을 반대로 주면 된다.
즉 체크박스를 누르고 해제할 때마다 어떤 체크박스가 눌렸는지 확인하고 그것의 원래 값의 반대를 주면 되는 것이다.
말로는 복잡하니 코드로 확인해보자.
참고로 state는 이전처럼 id, title, completed 여부를 담은 키 값이 없어도 돼서 빈 배열(todoData)과 빈 문자열(value)로 두었다.
handleCompleteChange = (id) => {
let newTodoData = this.state.todoData.map((data) => {
if (data.id === id) {
data.completed = !data.completed;
}
return data;
});
this.setState({ todoData: newTodoData });
};
이렇게 data.id === id라면 data.completed를 !로 뒤집어주고 그 data를 반환했다.
해서 전체 코드는 아래와 같다.
import React, { Component } from "react";
import "./App.css";
export default class App extends Component {
state = {
todoData: [],
value: "",
};
btnStyle = {
color: "#fff",
border: "none",
padding: "5px 9px",
borderRadius: "50%",
cursor: "pointer",
float: "right",
};
getStyle = (completed) => {
return {
padding: "10px",
borderBottom: "1px #ccc dotted",
textDecoration: completed ? "line-through" : "none",
};
};
handleClick = (id) => {
let newTodoData = this.state.todoData.filter((data) => data.id !== id);
this.setState({ todoData: newTodoData });
};
handleChange = (event) => {
this.setState({ value: event.target.value });
};
handleSubmit = (event) => {
event.preventDefault();
//새로운 할 일 데이터
let newTodo = {
id: Date.now(),
title: this.state.value,
completed: false,
};
//새로운 할 일 더해주기
this.setState({ todoData: [...this.state.todoData, newTodo], value: "" });
};
handleCompleteChange = (id) => {
let newTodoData = this.state.todoData.map((data) => {
if (data.id === id) {
data.completed = !data.completed;
}
return data;
});
this.setState({ todoData: newTodoData });
};
render() {
return (
<div className="container">
<div className="todo-block">
<div className="title">
<h1> 할 일 목록</h1>
</div>
{this.state.todoData.map((data) => (
<div style={this.getStyle(data.completed)} key={data.id}>
<input
type="checkbox"
defaultChecked={false}
onChange={() => this.handleCompleteChange(data.id)}
/>
{data.title}
<button
style={this.btnStyle}
onClick={() => this.handleClick(data.id)}
>
{" "}
❌{" "}
</button>
</div>
))}
<form style={{ display: "flex" }} onSubmit={this.handleSubmit}>
<input
type="text"
name="value"
style={{ flex: "10", padding: "5px" }}
placeholder="해야 할 일을 입력하세요."
value={this.state.value}
onChange={this.handleChange}
></input>
<input
type="submit"
value="입력"
className="btn"
style={{ flex: "1" }}
></input>
</form>
</div>
</div>
);
}
}
완성된 할 일 목록
React로 to do 앱 만드는 게 엄청나게 어렵진 않은데 아직 문법이 익숙하지 않아서 헤매고 있다.
역시 자기가 이런저런 거 만들어 보는 게 최선이려나... 주말에 다시 만들어봐야겠다.
'⚛️ React > 💭 따라하며 배우는 React A-Z' 카테고리의 다른 글
[React] 넷플릭스 웹페이지의 모달을 구현해보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
---|---|
[React] 넷플릭스 웹페이지의 영화를 나열하고 footer를 만들자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.06.02 |
[React] 넷플릭스 웹페이지의 배너를 클론해보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.05.30 |
[React] Hooks / TailWindCSS / 라이브러리를 활용해 메모 앱을 업그레이드 해보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.05.25 |
[React] 리액트에 대해 알아보자 (feat. 따라하며 배우는 리액트 A-Z) (0) | 2023.05.24 |