230517 - React(배열 렌더링, useRef), SASS/SCSS

백승연·2023년 5월 17일
1

🚩 React

배열렌더링

  • prompt로 값을 받아서 이전에 있던 값 교체

✒️ 코드 작성

입력

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} />
      ))}
    </>
  );
}



출력

  • 이미지로 대체



🔗 참고 링크 & 도움이 되는 링크






useRef

📝 설명

  • 컴포넌트의 속성만 조회 & 수정할 때 사용
  • current 속성을 가진 객체를 반환. 이 때 인자로 넘어온 초기값을 current 속성에 할당
  • 상태가 변경되어도 다시 렌더링 하지 않음. current 속성의 값이 유지됨

✒️ 코드 작성

입력

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도 자동으로 추가됨



🔗 참고 링크 & 도움이 되는 링크






🚩 sass/scss

scss 사용하여 채팅 화면 구현 완성

📝 설명

  • 카톡을 기반으로 채팅 화면 만들기

✒️ 코드 작성

입력

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>

출력

  • 이미지로 대체

🔗 참고 링크 & 도움이 되는 링크






profile
공부하는 벨로그

0개의 댓글