Backend
home

2024. 6. 26 - React

어제 작성한 코드 정리

Card.js
import React from "react"; function Card({title, backgroundColor, children}) { return ( <div style={{ margin: 10, height: 50, borderRadius: 5, boxShadow: "0px 0px 5px gray", backgroundColor: backgroundColor || "white" }}> {title && <h1>{title}</h1>} {children} </div> ) } export default Card;
JavaScript
복사
Comment.js
import React from "react"; function UserImg(props) { return ( <img className="userImg" src={props.user.imgUrl} alt={props.user.userName} /> ) } function UserInfo(props) { return( <div className = 'userInfo'> <UserImg user={props.user}/> <div className="userInfoName"> {props.user.userName} </div> </div> ) } function Comment(props) { return ( <div className='comment'> <UserInfo user={props.user} /> {/* 댓글 */} <div className="comment">{props.content}</div> {/* 작성 시간 */} <div className="replydate">{props.replydate}</div> </div> ) } export default Comment;
JavaScript
복사
Comment1. js
import React from "react"; function UserImg1(props) { return ( <img className="userImg" src={props.user.imgUrl} alt={props.user.userName} /> ) } function UserInfo1(props) { return ( <div className="userinfo"> <UserImg1 user={props.user}/> <div className="userinfoname"> {props.user.userName}</div> </div> ) } function Comment1(props){ return ( <div className="comment"> <UserInfo1 user={props.user} /> {/* reply Content */} <div className="commentContent"> {props.content} </div> {/* reply replydate */} <div className="replydate"> {props.replydate} </div> </div> ) } export default Comment1
JavaScript
복사
Comment2.js
import React from "react"; const style = { wrap: { margin: 8, padding: 8, display: 'flex', flexDirection: 'row', border: '1px solid gray' }, image: { width: 50, height: 50, borderRadius: 25 }, contentContainer: { marginLeft: 10, display: 'flex', flexDirection: 'column', justifyContent : 'center' }, commentText: { color: 'black', fontSize: 16, marginBottom: 5 }, dataText: { color: 'gray', fontSize: 10 } } function Comment2(props){ return ( <div style={style.wrap}> {/* 회원 정보 */} <div> <img src={props.user.imgUrl} alt={props.user.userName} style={style.image} /> <div style={{ color: 'blue', fontSize: 12, fontWeight: 'bold' }}>{props.user.userName} </div> </div> <div style={style.contentContainer}> {/* reply Content */} <div style={style.commentText}>{props.content} </div> {/* reply replydate */} <div style={style.dataText}> {props.replydate} </div> </div> </div> ) } export default Comment2;
JavaScript
복사
CommentList.js
import React from "react"; import Comment2 from "./Comment2"; let comments = [ { cno: 1, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '손흥민' }, content: '1등이다!!!!!!', replydate: '2024.06.20' }, { cno: 2, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '이강인' }, content: '반갑습니다.', replydate: '2024.06.21' }, { cno: 3, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '김민재' }, content: '강인이 안녕.', replydate: '2024.06.25' } ] function CommentList() { return ( <div> { comments.map((comment) => { return ( <Comment2 key={comment.cno} user={comment.user} content={comment.content} replydate={comment.replydate}></Comment2> ) }) } </div> ) } export default CommentList;
JavaScript
복사
ConfirmDialog.js
import React from "react"; import Comment2 from "./Comment2"; let comments = [ { cno: 1, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '손흥민' }, content: '1등이다!!!!!!', replydate: '2024.06.20' }, { cno: 2, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '이강인' }, content: '반갑습니다.', replydate: '2024.06.21' }, { cno: 3, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '김민재' }, content: '강인이 안녕.', replydate: '2024.06.25' } ] function CommentList() { return ( <div> { comments.map((comment) => { return ( <Comment2 key={comment.cno} user={comment.user} content={comment.content} replydate={comment.replydate}></Comment2> ) }) } </div> ) } export default CommentList;
JavaScript
복사
Dialog.js
import React from "react"; import Comment2 from "./Comment2"; let comments = [ { cno: 1, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '손흥민' }, content: '1등이다!!!!!!', replydate: '2024.06.20' }, { cno: 2, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '이강인' }, content: '반갑습니다.', replydate: '2024.06.21' }, { cno: 3, user: { imgUrl: 'https://encrypted-tbn1.gstatic.com/licensed-image?q=tbn:ANd9GcRpSo5X-9GknhLwsqbgdtHhueRB_ZitJC-6IPBvZ7KWWxgFHCl9oke9ouGB8DzTUth-fvavDDA1pSJBlc4', userName: '김민재' }, content: '강인이 안녕.', replydate: '2024.06.25' } ] function CommentList() { return ( <div> { comments.map((comment) => { return ( <Comment2 key={comment.cno} user={comment.user} content={comment.content} replydate={comment.replydate}></Comment2> ) }) } </div> ) } export default CommentList;
JavaScript
복사
FancyBorder.js
import React from "react"; function FancyBorder(props) { return ( <div className={'border-'+props.color}> {props.children} </div> ) } export default FancyBorder
JavaScript
복사
NumberList.js
import React from "react"; function NumberList(props) { const NumberList = [11, 42, 78, 32, 90] const listItem = NumberList.map((num, index) => <li key={index}>{num}</li>) return ( <ul> {listItem} </ul> ) } export default NumberList;
JavaScript
복사
Player.js
import React from "react"; function NumberList(props) { const NumberList = [11, 42, 78, 32, 90] const listItem = NumberList.map((num, index) => <li key={index}>{num}</li>) return ( <ul> {listItem} </ul> ) } export default NumberList;
JavaScript
복사
ProfileCard.js
import React from "react"; import Card from "./Card"; function ProfileCard(props) { return ( <Card title="손흥민" backgroundColor="yellow" > <p>안녕하세요. 축구선수 손흥민입니다.</p> <p>저는 축구를 잘합니다.</p> </Card> ) } export default ProfileCard
JavaScript
복사
Team.js
import React from "react"; import Player from "./Player"; // 배열 생성 let team = [ {name:'손흥민', no : '7'}, {name:'이강인', no : '19'}, {name:'김민재', no : '3'} ] function Team(props) { return ( <div> {/* <Player playerName='손흥민1' playerNumber='71'></Player> <Player playerName='손흥민2' playerNumber='72'></Player> <Player playerName='손흥민3' playerNumber='73'></Player> */} { team.map((player) => { return ( <Player playerName={player.name} playerNumber={player.no}></Player> ) }) } </div> ) } export default Team;
JavaScript
복사
WelcomeDialog.js
import React from "react"; import Dialog from "./Dialog"; function WelcomeDialog() { return ( <Dialog title="환영합니다!!!!" message="회원 정보를 확인해주세요"> {/* <button>개인정보 수정</button> */} </Dialog> ) } export default WelcomeDialog
JavaScript
복사

환경 설정

React Developer Tools 설치

Blog 제작 실습

Header - 블로그 이름, 설명, 객체로 받는다
import React from "react"; function Header({header}) { // 블로그 이름, 설명, 객체로 받는다 {title: '', description: ''} return ( <header> <h1 className="title">{header.title}</h1> <p>{header.description}</p> </header> ) } export default Header
JavaScript
복사
Article
import React from "react"; function Article({title, date, imgUrl, content}) { return ( <article> <h2> {title} </h2> <p> {date} </p> <img src={imgUrl} /> <p> {content} </p> </article> ) } export default Article
JavaScript
복사
Aside
import React from "react"; function Aside({recentPosts}) { // 최근 글의 제목 링크 // [{name: '', linkUrl: ''}, {name: '', linkUrl: ''}, {name: '', linkUrl: ''}, ...] return ( <aside> <h4>카테고리</h4> <ul> { recentPosts.map((item, index) => { return ( <li key={index}> <a href={item.linkUrl}>{item.name}</a> </li> ) }) } </ul> </aside> ) } export default Aside
JavaScript
복사
MainWrap
import React from "react"; import Article from "./Article"; import Aside from "./Aside"; function MainWrap({posts, recentPosts}) { // let posts = [ // {title: 'aaa', date: '2024.06.10', imgUrl: 'git.png', content: '안녕하세요11'}, // {title: 'bbb', date: '2024.06.20', imgUrl: 'git.png', content: '안녕하세요12'}, // {title: 'ccc', date: '2024.06.30', imgUrl: 'git.png', content: '안녕하세요13'} // ] // let recentPosts = [ // { name: '111', linkUrl: '#' }, // { name: '222', linkUrl: '#' }, // { name: '333', linkUrl: '#' }, // { name: '333', linkUrl: '#' }, // { name: '333', linkUrl: '#' }, // { name: '333', linkUrl: '#' }, // { name: '333', linkUrl: '#' } // ] return ( <div id="wrap"> <section> { posts.map((post, index) => { return ( <Article key={index} title={post.title} date={post.date} imgUrl={post.imgUrl} content={post.content} ></Article> ) }) } </section> <Aside recentPosts={recentPosts}></Aside> </div> ) } export default MainWrap
JavaScript
복사
Nav
import React from "react"; function Nav({nav}) { // 메뉴 데이터 : 배열 [{name: '', linkUrl: ''}, {}, .....] return ( <nav> <ul> { nav.map((menu, index) => { return ( <li key={index}> <a href={menu.linkUrl}>{menu.name}</a> </li> ) }) } </ul> </nav> ) } export default Nav
JavaScript
복사
Footer
import React from "react"; function Footer({ copyright }) { return ( <footer> {copyright} </footer> ) } export default Footer
JavaScript
복사
BlogMain
import React from "react"; import Header from "./Header"; import Nav from "./Nav"; import MainWrap from "./MainWrap"; import Footer from "./Footer"; // Headers // Nav // Main - Wrap => Article, Aside // Footer const blogData = { header: { title: 'React.js', description: 'react blog' }, nav: [ {name: 'html5', linkUrl: '#'}, {name: 'CSS', linkUrl: '#'}, {name: 'Javascript', linkUrl: '#'}, {name: 'jquery', linkUrl: '#' }, {name: 'react.js', linkUrl: '#'} ], mainwrap: { posts: [ {title: 'aaa', date: '2024.06.10', imgUrl: 'git.png', content: '안녕하세요11'}, {title: 'bbb', date: '2024.06.20', imgUrl: 'git.png', content: '안녕하세요12'}, {title: 'ccc', date: '2024.06.30', imgUrl: 'git.png', content: '안녕하세요13'} ], recentPosts: [ {name: '111', linkUrl: '#'}, {name: '222', linkUrl: '#'}, {name: '333', linkUrl: '#'}, ] }, copyright: 'copyright by minsung' } function BlogMain() { return ( <div> <Header header={blogData.header}></Header> <Nav nav={blogData.nav}></Nav> <MainWrap posts={blogData.mainwrap.posts} recentPosts={blogData.mainwrap.recentPosts}></MainWrap> <Footer copyright={blogData.copyright}></Footer> </div> ) } export default BlogMain
JavaScript
복사

State

State란
리액트에서 state는 컴포넌트의 상태를 의미
리액트 컴포넌트의 변경 가능한 데이터를 state라고 함
state는 사전에 미리 정해진 것이 아니라 리액트 컴포넌트를 개발하는 각 개발자가 직접 정의해서 사용
state 정의할 때 꼭 렌더링이나 데이터 흐름에 사용되는 값만 state 에 포함시켜야 함
state가 변경될 경우 컴포넌트가 재렌더링 되기에 렌더링과 데이터 흐름에 관련 없는 값을 포함하면 컴포넌트가 다시 렌더링되어 성능 저하가 발생할 수 있음

훅(hook)

함수형 컴포넌트에 이런 기능을 지원하기 위해서 나온 것이 바로 훅
훅을 사용하면 함수 컴포넌트도 클래스 컴포넌트의 기능을 모두 동일하게 구현할 수 있음 (!한번 렌더링되면 그 값은 변하지 않음)
Hook - 자주 사용되는 훅의 종류
useState()
userEffect()
userMemo() ⇒ 버전을 많이 탐
useCallback()
useRef()

useState() 실습

MyButton.js
import React from "react"; function MyButton(props) { const handlerDelete = (id, event) => { alert('안녕하세요') console.log(id, event.target) } return ( <button onClick={(event) => handlerDelete(1, event)}> 삭제하기 </button> ) } export default MyButton
JavaScript
복사
const [변수명, set 함수명] = useState()
setCount() ⇒ state 값을 변경해주는 함수, 실행 ⇒ 재 렌더링
import React from "react"; import {useState} from "react"; // let count = 0 // const countHandle = () => { // // count++ // setCount(count + 1) // console.log(count) // } function Counter() { // const [변수명, set함수명] = useState() const [count, setCount] = useState(0) // count = 0 // setCount() => state 값을 변경해주는 함수, 실행 => 재 렌더링 return ( <div> <p>{count}번 클릭!</p> <button onClick={() => setCount(count + 1)}> Click </button> </div> ) } export default Counter
JavaScript
복사
ConfirmButton.js
import React from "react"; import { useState } from "react"; function ConfirmButton(props) { const [isConfirmed, setIsConfirmed] = useState(false) const handleConfirm = () => { setIsConfirmed( (preIsConfirmed) => !preIsConfirmed) } return ( <button onClick={handleConfirm} disabled={isConfirmed}> {isConfirmed ? '확인 완료' : '확인하기'} </button> ) } export default ConfirmButton
JavaScript
복사
isLogin Toolbar ⇒ 로그인 상태 전달, 로그인 핸들러, 로그아웃 핸들러
// isLogin Toolbar => 로그인 상태 전달, 로그인 핸들러, 로그아웃 핸들러 import React from "react"; import { useState } from "react"; import Toolbar from "./ToolBar"; function MainPage(props) { // 변경값 : 로그인 여부 const [isLogin, setIsLogin] = useState(false) const onClickLogin = () => { setIsLogin(true) } const onClickLogout = () => { setIsLogin(false) } return ( <> <Toolbar isLogin={isLogin} onClickLoginHandle={onClickLogin} onClickLogoutHandle={onClickLogout} ></Toolbar> 안녕하세요. 메인페이지 입니다. </> ) } export default MainPage
JavaScript
복사
NameForm.js
import React from "react"; import { useState } from "react"; function NameForm(props) { const [value, setValue] = useState('') const handleChange = (event) => { setValue(event.target.value) console.log(event.target.value) } const handleSubmit = (event) => { event.preventDefault() alert('입력한 이름 : ' + value) } return ( <form onSubmit={handleSubmit}> <label>이름</label> <input type="text" value={value} onChange={handleChange} /> <button type="submit">제출</button> </form> ) } export default NameForm
JavaScript
복사
SignUp.js
import React, { useState } from "react"; function SignUp(props) { const [userName, setUserName] = useState('') const [gender, setGender] = useState('M') return ( <form onSubmit={(event) => { event.preventDefault() alert(`이름 : ${userName}, 성별 : ${gender}`) }}> <label>이름</label> <input type="text" value={userName} onChange={(event) => { setUserName(event.target.value) }} /> <br /> <label>성별</label> <select value={gender} onChange={(event) => { setGender(event.target.value) }} > <option value="M">남성</option> <option value="F">여성</option> </select> <br/> <button type="submit">제출</button> </form> ) } export default SignUp
JavaScript
복사
Dialog.js
import React from "react"; import FancyBorder from "./FancyBorder"; function Dialog(props) { return ( <FancyBorder color="blue"> <h1>{props.title}</h1> <p>{props.message}</p> {props.children} </FancyBorder> ) } export default Dialog
JavaScript
복사
Reservation.js (예약)
import React from "react" import { useState } from "react" // 방문객수 text, 조식여부 checkbox, 요청사항 textarea function Reservation(props) { const [guestOfNum, setGuestOfNum] = useState(1) const [haveBreakfast, setHaveBreakfast] = useState(false) const [msg, setMsg] = useState('요청사항을 입력하세요!') const handleSubmit = (event) => { event.preventDefault() alert(`방문객 수 : ${guestOfNum}명 \n조식 여부 : ${haveBreakfast} \n요청사항 : \n ${msg}`) } return ( <form onSubmit={handleSubmit}> <label>방문객 수</label> <input type="text" value={guestOfNum} onChange={(event) => { setGuestOfNum(event.target.value) }} /> <br/> <label>조식 여부</label> <input type="checkbox" checked={haveBreakfast} onChange={(event) => { setHaveBreakfast(event.target.checked) }} /> <br/> <label>요청사항</label> <br/> <textarea value={msg} onChange={(event) => { setMsg(event.target.value) }} ></textarea> <br/> <button type="submit">제출</button> </form> ) } export default Reservation
JavaScript
복사
SignUpDialog.js
import React from "react"; import { useState } from "react"; import Dialog from "./Dialog"; let room = [] function SignUpDialog(props) { const [userName, setUserName] = useState(''); const handleClick = (event) => { // 배열에 추가 room.push(userName) setUserName('') alert(`환영합니다!! ${userName}`) } return ( <Dialog title="스터디 팀 참여" message="이름을 입력하세요"> <input type="text" value={userName} onChange={(event) => { setUserName(event.target.value) }} /> <button onClick={handleClick}>참여하기</button> <div> { room.map((name, index) => { return <p key={index}>{name}</p> }) } </div> </Dialog> ) } export default SignUpDialog
JavaScript
복사