App.js
import "./styles.css";
import UserList1 from "./UserList1";
import UserList2 from "./UserList2";
import UserList3 from "./UserList3";
export default function App() {
return (
<div className="App">
<UserList1 />
<hr />
<UserList2 />
<hr />
<UserList3 />
</div>
);
}
UserList1.jsx
import React from "react";
export default function UserList1() {
const users = [
{
id: 1,
username: "Bret",
email: "Sincere@april.biz"
},
{
id: 2,
username: "Antonette",
email: "Shanna@melissa.tv"
},
{
id: 3,
username: "Samantha",
email: "Nathan@yesenia.net"
},
{
id: 4,
username: "Karianne",
email: "Julianne.OConner@kory.org"
}
];
return (
<>
<div>
<b>{users[0].username}</b> <span>({users[0].email})</span>
</div>
<div>
<b>{users[1].username}</b> <span>({users[1].email})</span>
</div>
<div>
<b>{users[2].username}</b> <span>({users[2].email})</span>
</div>
<div>
<b>{users[3].username}</b> <span>({users[3].email})</span>
</div>
</>
);
}
UserList2.jsx
import React from "react";
//컴포넌트 추가 정의
function User({ item }) {
return (
<div>
<b>{item.username}</b> <span>({item.email})</span>
</div>
);
}
export default function UserList2() {
const users = [
{
id: 1,
username: "Bret",
email: "Sincere@april.biz"
},
{
id: 2,
username: "Antonette",
email: "Shanna@melissa.tv"
},
{
id: 3,
username: "Samantha",
email: "Nathan@yesenia.net"
},
{
id: 4,
username: "Karianne",
email: "Julianne.OConner@kory.org"
}
];
return (
<>
<User item={users[0]} />
<User item={users[3]} />
</>
);
}
UserList.jsx
import React from "react";
const users = [
{
id: 1,
username: "Bret",
email: "Sincere@april.biz"
},
{
id: 2,
username: "Antonette",
email: "Shanna@melissa.tv"
},
{
id: 3,
username: "Samantha",
email: "Nathan@yesenia.net"
},
{
id: 4,
username: "Karianne",
email: "Julianne.OConner@kory.org"
}
];
//컴포넌트 추가 정의
function User({ item }) {
return (
<div>
<b>{item.username}</b> <span>({item.email})</span>
</div>
);
}
export default function UserList3() {
return (
<>
{users.map((item) => (
<User item={item} key={item.id} />
))}
</>
);
}
App.js
import { useRef } from "react";
import Input from "./Input";
import "./styles.css";
export default function App() {
let nextNum = useRef(0); //초기값 0, 리렌더링 할 필요가 없을때
const handleClick = () => {
console.log(nextNum.current);
nextNum.current += 1; //함수가 실행될대마다 기존값에 1씩 증가,
alert("당신은 " + nextNum.current + "번 클릭했어요!!!");
};
return (
<div className="App">
<h1>==useRef -React Hooks </h1>
<p>1. 특정 Dom을 선택할때 사용</p>
<Input />
<p>2. 컴포넌트 안의 변수 관리 </p>
<button onClick={handleClick}>클릭-1씩 추가됨!</button>
<p>3. 속성값을 초기화(clear)할 필요가 있는 경우</p>
</div>
);
}
Input.jsx
import React, { useRef, useState } from "react";
export default function Input() {
const nameInput = useRef();
//useRef() 를 이용해서 객체가 만들어짐
const [inputs, setInputs] = useState({
id: "",
nickname: ""
}); //inputs의 값이 2개 -> 오브젝트로
const { id, nickname } = inputs; //비구조할당을 통해 추출
const change = (e) => {
console.log("name? ", e.target.name);
console.log("value? ", e.target.value);
//const { name, value } = e.target; //추출
const name = e.target.name; //내가 지정한거
const value = e.target.value; // 입력값(원래부터 있음)
const nextInputs = {
//js와 객체 업데이트 하는 방법이 다름
...inputs,
[name]: value
}; //기존 객체를 다 가져옴(스프레드문법), [name]에 id,nickname이 들어와서 앞을 덮어 씌움
setInputs(nextInputs);
};
const reset = () => {
setInputs({ id: "", nickname: "" });
nameInput.current.focus();
//현재지정된 Dom에 포커스가 가게 한다
};
return (
<>
<input
type="text"
name="id"
onChange={change}
placeholder="아이디"
value={id}
ref={nameInput}
/>
<input
type="text"
name="nickname"
onChange={change}
placeholder="닉네임"
value={nickname}
/>
<button onClick={reset}>초기화</button>
<div>
<b>입력값:</b>
{id}({nickname})
</div>
</>
);
}
App.js
import { useRef, useState } from "react";
import CreateUser from "./CreateUser";
import "./styles.css";
import UserList from "./UserList";
export default function App() {
const initialUsers = [
{
id: 1,
username: "Bret",
email: "Sincere@april.biz"
},
{
id: 2,
username: "Antonette",
email: "Shanna@melissa.tv"
},
{
id: 3,
username: "Samantha",
email: "Nathan@yesenia.net"
},
{
id: 4,
username: "Karianne",
email: "Julianne.OConner@kory.org"
}
];
const [users, setUsers] = useState(initialUsers); //배열전체
const [inputs, setInputs] = useState({ username: "", email: "" }); //input
// const { username, email } = inputs; -> 축약형
const username = inputs.username;
const email = inputs.email;
const onChange = (event) => {
const { name, value } = event.target; //두 인풋 중에 어디인지, 입력값
setInputs({ ...inputs, [name]: value });
console.log(inputs);
};
// useRef 오브젝트 생성. id값을 1씩 증가시켜주기 위함
const nextId = useRef(5); // 5는 초기값(시작값)
const focusIn = useRef();
const onCreate = () => {
// 새로운 원소(item)
// 제일 마지막으로 inputs에 들어간 것을 item 변수에서 받아와서 배열에 넣음
const item = {
id: nextId.current,
username, // = username: username - username으로 생략 가능
email
};
setUsers([...users, item]); // 배열에 아이템을 넣는 함수 실행
setInputs({ username: "", email: "" }); // input 초기화
nextId.current += 1; // onCreate()가 실행될 때 마다 id가 1씩 증가
focusIn.current.focus();
};
return (
<div>
<CreateUser
onChange={onChange}
username={username}
email={email}
onCreate={onCreate}
focusIn={focusIn}
/>
<UserList users={users} />
</div>
);
}
CreateUser.jsx
import React from "react";
export default function CreateUser({
onChange,
username,
email,
onCreate,
focusIn
}) {
return (
<>
<input
type="text"
name="username"
placeholder="유저네임"
onChange={onChange}
value={username}
ref={focusIn}
/>
<input
type="email"
name="email"
placeholder="이메일"
onChange={onChange}
value={email}
/>
<button onClick={onCreate}>등록</button>
</>
);
}
UserList.jsx
import React from "react";
//컴포넌트 추가 정의
function User({ item }) {
return (
<div className={item.id}>
<b>{item.username}</b> <span>({item.email})</span>
</div>
);
}
export default function UserList({ users }) {
return (
<>
{users.map((item) => (
<User item={item} key={item.id} />
))}
</>
);
}
class도 자동으로 추가됨
main.scss
@import "./mixVariables"; // 번수, 믹스인을 제일 처음에
@import "./reset";
@import "./layout";
@import "./index";
@import "./chatList";
@import "./chat";
@import "./component"
_reset.scss
@font-face {
font-family: 'SUITE-Regular';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2304-2@1.0/SUITE-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'SUITE-Bold';
src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2304-2@1.0/SUITE-Bold.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'SUITE-Regular';
}
html {
height: 100%;
}
body {
height: 100%;
min-width: 350px;
}
a {
text-decoration: none;
color: inherit;
}
li { list-style: none; }
_mixVariables.scss
$bg-color: #ffeb35;
$main-color: #381e1f;
$gray1: #666;
$gray2: #aaa;
$gray3: #eee;
$gray4: #f7f7f7;
$opacityBlack: rgba(0, 0, 0, 0.2);
// height를 필수로 넣을 수 있도록 맨 앞에
@mixin set-box($height, $width: 100%, $border-radius: 0) {
height: $height;
width: $width;
border-radius: $border-radius;
}
_layout.scss
nav {
@include set-box(100%, 70px);
background: $opacityBlack;
position: fixed;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
padding: 50px 0;
cursor: pointer;
.nav_list {
.nav_btn {
padding-bottom: 40px;
color: $gray2;
&.active {
color: #000;
}
&:hover {
color: #000;
}
}
}
.logout {
color: $gray2;
&:hover {
color: #000;
}
}
}
header {
background: #fff;
display: flex;
justify-content: space-between;
padding: 50px 20px 20px;
position: sticky; top: 0;
z-index: 1;
// box-shadow: 0 5px 10px -5px $opacityBlack;
.header_title {
font-size: 1.2rem;
span {
font-size: 0.8rem;
}
}
.header_menu {
span {
@include set-box(36px, 36px, 18px);
display: inline-block;
text-align: center;
line-height: 36px;
cursor: pointer;
&:hover {
background: $opacityBlack;
}
}
}
}
_chatList.scss
.chats{
margin-left:70px;
}
.preview_wrap {
display: block;
&:first-child > .preview {
background:#f0eded;
}
.preview {
&:hover {
background:$gray4;
}
}
}
_chat.scss
.chat {
@include set-box(100%);
}
.chat_screen {
background: #9bbbd4;
height: calc(100% - 140px);
overflow: auto;
}
.chat_header {
padding: 0;
background: #9bbbd4;
.preview:hover {
background: none;
}
.chat_header_title {
display: flex;
flex-direction: column;
justify-content: space-between;
.chat_title {
font-size: 1.2rem;
}
.chat_num i {
color: $gray2;
margin-right: 5px;
}
}
.header_menu {
display: flex;
align-items: center;
span {
margin-left: 3px;
}
}
}
.bubble_container {
.bubble_wrap {
display: flex;
flex-direction: column;
.preview_title {
width: auto;
}
.bubble_content {
display: flex;
.bubble {
width: 80%;
@include set-box(auto, auto, 6px);
position: relative;
background: #fff;
padding: 10px;
box-shadow: 2px 3px 10px -3px $opacityBlack;
&::before {
@include set-box(0, 0);
content: "";
position: absolute;
top: 10px;
left: -10px;
border-bottom: 10px solid transparent;
border-right: 10px solid #fff;
}
}
.bubble_time {
display: flex;
align-items: self-end;
font-size: 0.8rem;
padding-left: 6px;
white-space: nowrap;
}
}
}
}
.bubble_preview.my {
flex-direction: row-reverse;
.preview_pic {
display: none;
}
.preview_title {
display: none;
}
.bubble_content {
display: flex;
flex-direction: row-reverse;
.bubble {
position: relative;
background: $bg-color!important;
&::before {
top: 10px;
left: auto;
right: -10px;
border-bottom: 10px solid transparent;
border-right: 10px solid $bg-color;
transform: rotate(-90deg);
}
}
.bubble_time { padding-right: 6px; }
}
}
.chat_form {
background: #fff;
height: 140px;
padding: 10px;
.chat_form_msg {
@include set-box(70px, 100%);
resize: none; // textarea 조정x
margin-bottom: 8px;
border: none;
outline: none;
}
.chat_form_util {
display: flex;
justify-content: space-between;
.chat_form_util_plugin {
i {
margin-right: 10px;
color: $gray2;
cursor: pointer;
&:hover { color: #000; }
}
}
.chat_form_util_submit {
.chat_form_btn {
@include set-box(35px, 65px, 6px);
border: none;
color: $gray1;
}
}
}
}
_component.scss
.preview {
display:flex;
padding:16px;
position: relative;
.preview_col {
&:nth-child(1){
margin-right:16px;
}
.preview_pic{
@include set-box(50px, 50px,8px );
background:$main-color;
}
.preview_title{
width:50vw;
font-size:0.88rem;
margin-bottom:6px;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.preview_msg {
width:50vw;
font-size:0.83rem;
color:$gray1;
// 2줄만 보이게 함
display: -webkit-box;
word-wrap:break-word;
-webkit-line-clamp:2;
-webkit-box-orient: vertical;
overflow:hidden;
text-overflow:ellipsis;
}
.preview_time{
position:absolute;
right:16px;
top:16px;
font-size:0.73rem;
color:$gray2;
}
}
}
chat.html
<!DOCTYPE html>
<html lang="ko">
<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>채팅화면</title>
<link rel="stylesheet" href="./css/style.css" />
<!-- fontawesome -->
<script src="https://kit.fontawesome.com/05f6febec9.js" crossorigin="anonymous"></script>
</head>
<body>
<div class="chat">
<main class="chat_screen">
<header class="header chat_header">
<div class="preview">
<div class="preview_col">
<div class="preview_pic"></div>
</div>
<div class="preview_col chat_header_title">
<h4 class="preview_title chat_title">채팅방이름</h4>
<p class="preview_msg chat_num"><i class="fa-solid fa-user fa-xs"></i><span>5</span></p>
</div>
</div>
<div class="header_menu">
<span><i class="fa-solid fa-magnifying-glass"></i></span>
<span><i class="fa-regular fa-comments"></i></span>
<span><i class="fa-solid fa-plus"></i></span>
</div>
</header>
<section class="bubble_container">
<div class="preview bubble_preview">
<div class="preview_col">
<div class="preview_pic">
</div>
</div>
<div class="preview_col bubble_wrap">
<h4 class="preview_title">유저이름</h4>
<div class="bubble_content">
<p class="bubble">채팅내용입니다 Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam sodales, purus ut imperdiet varius, arcu arcu sagittis lectus, sit amet tempor ligula nibh nec tellus. Nam sodales sodales ex, nec pellentesque lacus pharetra vel. Nunc sed magna magna. Morbi gravida diam quis dictum mollis.</p>
<span class="bubble_time">오후 2:33</span>
</div>
</div>
</div>
<div class="preview bubble_preview">
<div class="preview_col">
<div class="preview_pic">
</div>
</div>
<div class="preview_col bubble_wrap">
<h4 class="preview_title">유저이름유저이름유저이름유저이름</h4>
<div class="bubble_content">
<p class="bubble">채팅내용입니다</p>
<span class="bubble_time">오후 2:33</span>
</div>
</div>
</div>
<div class="preview bubble_preview my">
<div class="preview_col">
<div class="preview_pic">
</div>
</div>
<div class="preview_col bubble_wrap">
<h4 class="preview_title">유저이름유저이름유저이름유저이름</h4>
<div class="bubble_content">
<p class="bubble">채팅내용입니다채팅내용입니다채팅내용입니다채팅내용입니다채팅내용입니다채팅내용입니다</p>
<span class="bubble_time">오후 2:33</span>
</div>
</div>
</div>
<div class="preview bubble_preview my">
<div class="preview_col">
<div class="preview_pic">
</div>
</div>
<div class="preview_col bubble_wrap">
<h4 class="preview_title">유저이름유저이름유저이름유저이름</h4>
<div class="bubble_content">
<p class="bubble">ㅇㅇ</p>
<span class="bubble_time">오후 2:33</span>
</div>
</div>
</div>
</section>
</main>
<form action="" class="chat_form">
<div class="chat_form_field">
<textarea name="" id="" class="chat_form_msg"></textarea>
</div>
<div class="chat_form_util">
<div class="chat_form_util_plugin">
<i class="fa-regular fa-face-smile" title="이모티콘 Ctrl+E"></i>
<i class="fa-solid fa-paperclip" title="파일 전송 Ctrl+T"></i>
<i class="fa-solid fa-crop-simple" title="캡처 Ctrl+Shift+C"></i>
</div>
<div class="chat_form_util_submit">
<input type="submit" value="전송" class="chat_form_btn">
</div>
</div>
</form>
</div>
</body>
</html>