화면을 구성할 때 모듈 방식을 활용하여 만들어진 단위를 컴포넌트라고 합니다
일반적으로 <div>
나 <ul>
등으로 이루어진 형태를 사용하며
그 안에도 여러 자식 컴포넌트가 포함될 수 있습니다
그런데 div라는 이름의 컴포넌트를 만들 수 있을까요?
정답은 No입니다
컴포넌트 이름은 첫글자를 대문자로 지어야 한다는 규칙이 있기 때문입니다
리액트는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리합니다
예를 들어 <div />
는 HTML의 div 태그를 나타내지만,
(엄밀히는 리액트의 내장 컴포넌트입니다)
<Welcome />
은 컴포넌트(사용자정의 컴포넌트)를 나타내며 범위 안에 Welcome이 있어야 합니다
*추가로 우리가 흔히 써온 <div id=wrap>
과 같이
모든 컴포넌트를 감싸는 역할을 하는 부모 컴포넌트는 보통 App
이라고 이름을 짓습니다
*상태 끌어올리기에 대해서는 4번의 댓글 제작 예제 코드를 통해 알아보는 걸로...
React.createElement(App, null, "안녕하세요")
React.createElement("div", null, "안녕하세요")
<App>안녕하세요</App>
<div>안녕하세요</div>
그렇다면 여기서 props는 어떻게 될까요?
위 예제에서 두번째 인자(속성)와 세번째 인자(innerHTML)는 모두 props로 전달할 수 있습니다
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
// 컴포넌트명은 첫글자가 대문자이어야 합니다
class App extends React.Component {
constructor(props) {
super(props);
// children
// property
//props.children
console.log(props)
}
render() {
return <div>Hello world!</div>
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
// render 함수 실행은 인스턴스 생성과 유사한 역할을 합니다
root.render(<App id='hello'>안녕하세요</App>)
</script>
</body>
</html>
결과는 아래와 같습니다
// console.log(props)
{id: 'hello', children: '안녕하세요'}
<div>
, <span>
등)에서 id를 사용하면 실제 엘리먼트의 id로 사용되도록 전달됩니다<App>
)에서 사용한 id='hello'
는 실제 html의 엘리먼트에 id 속성을 부여하는 것이 아닙니다
지난 포스트에 이어서 클릭에 따라 상태(로그인/로그아웃)가 바뀌는 버튼을 만들어보겠습니다
<body>
<div id="root"></div>
<script type="text/babel">
class Word extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h2>{this.props.text}</h2>
}
}
class LoginBtn extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
}
}
render() {
return <button onClick={() => this.setState({ isLogin: !this.state.isLogin })}>
{this.state.isLogin ? '로그아웃' : "로그인"}
</button>
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Word text="Hello World!1" />
<Word text="Hello World!2" />
<Word text="Hello World!3" />
<LoginBtn />
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App></App>)
</script>
</body>
</html>
위 코드를 좀 더 직관적으로 작성하려면 어떻게 해야 할까요
우선 기능별로 함수를 쪼갤 필요가 있어보이네요
class LoginBtn extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
}
// this bind
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// 얼핏 문제가 없어보이지만 this 이슈가 발생하는 코드... 생성자 함수에서 this 바인드 처리가 필요합니다
// console.log(this) // undefined ~ 일반 함수에서 this는 동적으로 결정됩니다
this.setState({ isLogin: !this.state.isLogin });
}
render() {
return <button onClick={this.handleClick}>
{this.state.isLogin ? '로그아웃' : "로그인"}
</button>
}
}
위와 같이 this로 인한 문제는 항상 this가 가리키는 대상이 함수를 호출할 때 결정된다는 것에서 발생합니다
class LoginBtn extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
}
// this.handleClick = this.handleClick.bind(this)
}
handleClick() {
// console.log(this) // undefined
this.setState({ isLogin: !this.state.isLogin });
}
render() {
return <button onClick={() => this.handleClick()}>
{this.state.isLogin ? '로그아웃' : "로그인"}
</button>
}
}
↑ 이런 식의 해결도 가능합니다 (추천하는 방법은 아닙니다)
혹은 아래와 같이 일반 함수 대신 화살표 함수를 사용하는 것도 하나의 방법입니다
화살표 함수에서는 this를 정의한 컨텍스트의 this를 참조하기 때문입니다
handleClick = () => {
// console.log(this) // LoginBtn
this.setState({ isLogin: !this.state.isLogin });
}
다음은 완성 코드입니다
<body>
<div id="root"></div>
<script type="text/babel">
class Word extends React.Component {
constructor(props) {
super(props);
}
render() {
return <h2>{this.props.text}</h2>
}
}
class LoginText extends React.Component {
constructor(props) {
super(props);
}
render() {
// 조건부 렌더링 (if문을 통한 렌더링)
return this.props.flag ? <Word text="로그아웃" /> : <Word text="로그인" />;
}
}
class LoginBtn extends React.Component {
constructor(props) {
super(props);
this.state = {
isLogin: false,
}
this.handleClick = this.handleClick.bind(this)
}
handleClick() {
this.setState({ isLogin: !this.state.isLogin });
}
render() {
return <button onClick={this.handleClick}>
<LoginText flag={this.state.isLogin} />
</button>
}
}
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Word text="Hello World!1" />
<Word text="Hello World!2" />
<Word text="Hello World!3" />
<LoginBtn />
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector('#root'));
root.render(<App></App>)
</script>
</body>
배운 내용을 바탕으로 리액트를 통해 간단한 댓글창을 구현해보려 합니다
(CRUD 전체기능 구현은 다음 포스트에서...)
<body>
<div id="root"></div>
<script type="text/babel">
class CommentItem extends React.Component {
render() {
return (
<ul className="comment-row">
<li className="comment-id">{this.props.userid}</li>
<li className="comment-content">{this.props.content}</li>
<li className="comment-date">{this.props.date}</li>
</ul>
)
}
}
class CommentForm extends React.Component {
constructor(props) {
super(props)
// console.log(this.props.create)
this.submitHandler = this.submitHandler.bind(this)
this.changeHandler = this.changeHandler.bind(this)
// 리액트에서 input value는 전부 상태로 관리하는 것이 좋습니다
// 입력값을 실시간으로 관리하기에도 용이하고, 데이터를 다른 컴포넌트에 전달하기도 쉽습니다
this.state = {
value: ''
}
}
changeHandler(e) {
const { value } = e.target
this.setState({
...this.state,
value,
})
}
submitHandler(e) {
e.preventDefault()
this.props.create(this.state.value)
this.setState({ value: "" })
e.target.commentInput.focus()
}
render() {
return (
<li className="comment-form">
<form onSubmit={this.submitHandler}>
<h4>
댓글 쓰기 <span>({this.props.length})</span>
</h4>
<span className="ps_box">
<input type="text" onChange={this.changeHandler} className="int" id="commentInput" value={this.state.value} placeholder="댓글 내용을 입력해주세요" />
</span>
<input type="submit" value="등록" className="btn" />
</form>
</li>
)
}
}
class CommentList extends React.Component {
// this.props.items는 배열입니다
loop(val, key) {
return <CommentItem key={key} userid="web7722" content={val.content} date="2023-02-22" />
}
render() {
return (
<li>
{this.props.items.map(this.loop)}
</li>
)
}
}
class Comment extends React.Component {
constructor(props) {
super(props)
this.state = {
comment: []
}
// this 바인드처리가 싫다면 화살표 함수를 사용할 수도 있습니다
// this.create = this.create.bind(this)
}
// CommentForm > CommentList
// 상태(state) 끌어올리기가 필요합니다. 이를 위해 먼저 Comment에서 상태를 바꾸는 함수를 만들어야 합니다
// 상태를 바꿀 수 있는 메서드를 상위 컴포넌트인 Comment에서 만들어두고, 그것을 props로 CommentForm에 전달해야 합니다
create = (content) => {
this.setState({
comment: [
{
userid: 'web7722',
content,
date: '2023-02-22'
},
...this.state.comment
]
})
}
render() {
return (
// 자바스크립트이므로 이미 존재하는 class라는 예약어 대신 className으로 써야 합니다
<ul className="comment">
<CommentForm create={this.create} length={this.state.comment.length}/>
<CommentList items={this.state.comment} />
</ul>
)
}
}
class App extends React.Component {
render() {
return (
<div>
<Comment />
<Comment />
<Comment />
</div>
)
}
}
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App />)
</script>
</body>
4번 예제는 많은 복습이 필요합니다
화이팅!!
+) 보다 더 직관적이고 간결한 함수 컴포넌트를 사용한 코드
<body>
<div id="root"></div>
<script type="text/babel">
const { useState } = React;
const CommentForm = (props) => {
const [value, setValue] = useState("")
const changeHandler = (e) => {
setValue(e.target.value)
}
const submitHandler = (e) => {
e.preventDefault()
props.create(value)
setValue("")
e.target.commentInput.focus()
}
return (
<li className="comment-form">
<form onSubmit={submitHandler}>
<h4>
댓글 쓰기
<span>({props.length})</span>
</h4>
<span className="ps_box">
<input type="text" onChange={changeHandler} className='int' id='commentInput' value={value} placeholder="댓글 내용을 입력해주세요" />
</span>
<input type="submit" value="등록" className="btn" />
</form>
</li>
)
}
const CommentItem = (props) => {
return (
<ul className="comment-row">
<li className="comment-id">{props.userid}</li>
<li className="comment-content">{props.content}</li>
<li className="comment-date">{props.date}</li>
</ul>
)
}
const CommentList = (props) => {
const loop = (val, idx) => {
return <CommentItem key={idx} userid="web7722" content={val.content} date="2023-02-22" />
}
return (
<li>
{props.items.map(loop)}
</li>
)
}
const Comment = () => {
const [value, setValue] = useState([])
const create = (content) => {
const newComment = {
userid: "web7722",
content,
date: "2023-02-22",
}
setValue([newComment, ...value])
}
return (
<ul className="comment">
<CommentForm create={create} length={value.length}/>
<CommentList items={value} />
</ul>
)
}
const App = () => {
return (
<div>
<Comment />
</div>
)
}
const root = ReactDOM.createRoot(document.getElementById("root"))
root.render(<App />)
</script>
</body>