JavaScript를 더 깊게 공부하고자 "모던 자바스크립트 Deep Dive"를 읽으며 공부하고 있습니다. 그리고 각 파트를 상세히 기록하며 정리할 예정인데 그 전에 기본적인 것들을 전체적으로 정리할 예정입니다. 드루아 시리즈는 각 언어나 개념들의 전체를 훑으며 그 흐름을 이해하는 데 도움이 되고자 작성하는 시리즈입니다. 잘 부탁드립니당 :)

 

해당 글은 코딩앙마의 자바스크립트 강좌를 기반으로 작성되었습니다. 

 

변수

먼저 변수를 선언하는 과정에서 지켜야 하는 것들이 있는데요. 이는 다음과 같습니다.

  1. 변수는 문자, 숫자, $, _만 사용
  2. 첫글자에 숫자는 올 수 없음
  3. 예약어 사용 불가
  4. 상수는 보통 대문자로 작성
  5. 읽기 쉽고, 이해 가능하게 작성

그리고 이러한 변수는 var, let, const 총 3가지를 통해 선언할 수 있습니다. 각각의 차이점을 알기 위해선 호이스팅이란 개념에 대해 이해해야 합니다.

 

호이스팅이란 코드를 실행하기 전에 함수, 변수, 클래스나 import 선언문을 최상단 스코프로 끌어올리는 것처럼 보이는 현상입니다. 이렇게 보이는 이유는 JS는 코드를 실행하기 전에 선언된 변수와 함수 등을 먼저 체크하기 때문입니다.

 

이제 var, let, const의 차이점에 대해 알아봅시다.

 

var

  • 호이스팅 시 변수의 선언과 초기화를 같이 실행 , 그렇기에 var는 선언하기 전에 사용힐 수 있습니다.(undefined로 초기화 되기 때문에 선언 전에 사용하면 undefined을 가져옵니다.)
  • 함수 스코프 : 함수를 기준으로만 어휘적 환경이 설정 (전역변수와 지역변수의 경계 모호)
  • 재할당 재선언 가능

let

  • 호이스팅이 일어나지만 초기화되지는 않습니다.
  • 블록 스코프 : 블록{}을 기준으로 어휘적 환경이 설정 (전역변수와 지역변수의 경계 확실)
  • TDZ(Temporal Death Zone) 적용 : 호이스팅이 됐지만 초기화 전까지는 해당 변수에 접근 불가
  • 위의 이유로 선언하기 전에는 사용 불가
  • 재할당만 가능

const

  • let과 모두 동일하지만 선언, 초기화, 할당이 동시에 일어나기 때문에 
  • 재선언 재할당 불가능 

선언 : 변수를 JS 엔진에 알려줌

초기화 : 선언된 변수에 기본 값을 할당(변수를 메모리에 등록하고 초기값 할당) 즉, 선언 후 첫 번째 값을 할당할 때 발생

할당 : 초기화 이후의 값 변경

 

 

생성자 함수 

JS에서는 객체를 자주 유용하게 사용합니다. 그렇기에 비슷한 객체를 여러개 만들어야 하는 상황이 생기고, 이 때 사용하는 것이 생성자 함수입니다.

객체 선언 방식은 모두 아시죠?

const user = {
	name: '홍길동',
    age: 34,
    nick_name: '동번서번',
}

위와 같은 객체를 여러개 만들어야 할 경우에 생성자 함수를 사용하는 데 보통 첫글자는 대문자로 작성합니다. 예시로 작성한 객체를 만들어낼 수 있는 생성자 함수를 작성해 보겠습니다.

function User(name, age, nick_name) {
	this.name = name;
    this.age = age;
    this.nickName = nick_name;
}

이러한 생성자 함수를 이용해 새로운 객체를 생성할려면 앞에 new를 붙여서 함수에 필요한 파라미터를 작성해야 합니다.

const hong = new User('홍길동', 34, '동번서번');
const zeon = new User('전우치', 27, '어디보자');

 

이러한 생성자 함수가 객체를 생성할 때는 보이지는 않지만, 내부에서 this에 빈 객체를 할당하고 this에 객체를 생성하기 위해 필요한 프로퍼티를 추가합니다. 그리고 this를 반환해 주는 방식으로 생성합니다.

 

그리고 원하는 메소드(프로퍼티의 값이 함수일 경우)를 추가할 수 있고, 생성된 모든 객체는 추가한 메소드를 불러올 수 있습니다. 예제를 보면서 확실하게 이해해 봅시다.

// 생성자 함수 정의
function User(name, age, nick_name) {
	this.name = name;
    this.age = age;
    this.nickName = nick_name;
    this.callName = function(){
    	console.log(this.name);
    }
    this.callNickName = function(){
    	console.log(this.nickName);
    }
}

// 객체 생성하기
const lee = new User('이순신', 77, '사즉생 생즉사');

// 함수 호출
lee.callName(); // '이순신'
lee.callNickName(); // '사즉생 생즉사'

 

 

Computed property

객체를 선언할 때, [] 안에 계산된 프로퍼티를 넣을 수 있습니다. 이를 computed property(계산된 프로퍼티)라고 합니다.

const user = {
	[4 + 5] : 9,
    ["내 이름은" + "00"] : "당동",
}

// user -> {9: 9, 내 이름은00: "당동"}

 

객체 메소드

객체에서 자주 사용하는 메소드들에 대해 알아봅시다.

 

- Object.assign()

객체를 복제하고 병합할 수 있습니다.

// 객체 복제
Object.assign({}, 객체);

// 객체 병합
Object.assign(객체, 객체1, 객체2);

객체 병합에 대한 예제를 보며 확실하게 이해해 봅시다.

 

const user = {
	name: '이름1',
    age: 33,
}
const info1 = {
	nickName: "이름아름",
}
const info2 = {
	gender: 'male',
}

const newUser = Object.assign(user, info1, info2);

// newUser
// {
// 	name: "이름1",
// 	age: 33,
// 	nickName: "이름아름",
// 	gender: "male",
// }

만약에 병합 과정에서 같은 키를 가진 프로퍼티가 있다면 초기값의 객체가 병합하는 객체에 덮어써집니다.

 

- Object.keys()

키를 배열로 반환합니다.

 

- Object.values()

값을 배열로 반환합니다.

 

- Object.entries()

[키, 값]을 가진 배열을 반환합니다.

 

- Object.fromEntries()

배열 형태로 변경된 객체를 다기 객체로 만들어줍니다.

const arr = [
	["키1", "값1"],
    ["키2", "값2"],
    ["키3", "값3"]
]

const arrToobj = Object.fromEntries(arr);

// arrToobj
// {
//	키1 : "값1",
//    키2 : "값2",
//    키3 : "값3",
//  }

 

 

'웹 풀스택 공부 과정 정리 > HTML, CSS, JS' 카테고리의 다른 글

[HTML] 드루아 ver.1.1.0  (0) 2023.08.23

 

서버와 클라이언트 폴더를 분리하고 그거에 맞춰서 만들 예정이다.

 

클라이언트 폴더는 myblog

서버 폴더는 myblog_server

이다

 

클라이언트 폴더에는 React + TypeScript를 설치하여 셋팅해준다

yarn create react-app myblog --template typescript

 

서버 폴더에는 express + mysql + sequelize 를 이용하여 데이터와 연결해 줄 것이다.

yarn add express mysql2 sequelize sequelize_cli

 

서버 폴더에서 먼저 .gitignore 파일을 만들어 node_modules 파일이 올라가지 못하게 만들어두고,

npx sequelize init을 입력하여 시퀄라이즈 사용에 필요한 폴더와 파일들을 만들어준다.

 

config,json에서 데이터베이스와 연결해주고,

//config.json
{
  "development": {
    "username": "toss1",
    "password": "12345",
    "database": "myblog",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": null,
    "database": "database_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": null,
    "database": "database_production",
    "host": "127.0.0.1",
    "dialect": "mysql"
  }
}

 

 

/models/index.js에서 필요한 부분만 남겨 만들어준다. 그리고 모델을 만들 파일을 연결해 줘야한다.

'use strict';

const Sequelize = require('sequelize');
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config); 


db.User = require(./user)(sequelize);	

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

 

 

그 후에 일전에 만들어준 ERD에 맞춰서 models 폴더에 원하는이름.js를 만들어  테이블을 정의해준다.

const {DataTypes} = require("sequelize");
const Model = (sequelize) => {
    const User = sequelize.define(
        "User",
        {
            user_id: {
                type: DataTypes.INTEGER,
                allowNull: false,
                primaryKey : true,
                autoIncrement: true,
            },
            nick_name: {
                type: DataTypes.STRING(255),
                allowNull: false,
            },
            email: {
                type: DataTypes.STRING(255),
                allowNull: false,
            },
            password: {
                type: DataTypes.STRING(255),
                allowNull: false,
            },
            name: {
                type: DataTypes.STRING(255),
                allowNull: false,
            }
        },
        {
            tableName : "User",
            timestamps: false,
        }
    );
    
    const Bloging = sequelize.define(
        "Bloging",
        {
            blog_id : {
                type: DataTypes.INTEGER,
                allowNull: false,
                primaryKey : true,
                autoIncrement: true,
            },
            title : {
                type: DataTypes.STRING(255),
                allowNull:false,
            },
            contents : {
                type: DataTypes.TEXT,
                allowNull:false,
            }
        },
        {
            tableName: "Bloging",
        }
    );

    const Img = sequelize.define(
        "Img",
        {
            image_id: {
                type: DataTypes.INTEGER,
                allowNull: false,
                primaryKey : true,
                autoIncrement: true,
            },
            blog_id: {
                type: DataTypes.INTEGER,
                allowNull: false,
            },
            image_url: {
                type: DataTypes.STRING(255),
                allowNull: false,
            }
        },
        {
            tableName: "Img",
            timestamps: false,
        }
    );
    
    const MyBloging = sequelize.define(
        "MyBloging",
        {
            user_id : {
                type: DataTypes.INTEGER,
                allowNull: false,
            },
            blog_id : {
                type: DataTypes.INTEGER,
                allowNull: false,
            }
        },
        {
            tableName: "MyBloging",
            timestamps: false,
        }
    );

    //관계 정의
    MyBloging.belongsTo(User, { foreginKey : "user_id"});
    MyBloging.belongsTo(Bloging, { foreginKey : "blog_id"});

    return {
        User,
        Bloging,
        Img,
        MyBloging,
    }
}

module.exports = Model;

 

 

이렇게 한 후에 서버를 열어줄 app.js를 만들어주고 서버를 열어주자

 

//app.js

const express = require("express");
const db = require("./models");
const path = require("path");
const jwt = require("jsonwebtoken"); //jwt 암호화에 필요

//s3에 필요
// const aws = require("aws-sdk"); // aws-sdk 모듈 추가
// const multer = require("multer"); // multer 모듈 추가
// const multerS3 = require("multer-s3");

const PORT = 8000;
const app = express();

//body-parser
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// 정적 파일을 제공할 디렉토리를 설정
app.use(express.static(path.join(__dirname, "public")));

//router
const useRouter = require('./route/router');
app.use('/', useRouter);

//404
app.use('*', (req, res)=>{
    res.render('404');
});

//서버 오픈
db.sequelize.sync({force : true}).then(()=>{
    app.listen(PORT, () => {
        console.log(`http://localhost:${PORT}`);
    });
});

 

 

만들던 과정 중에 생각이 든건 s3를 백에서 사용하는데 지금 의미가 없었다...하핳 사전 과제는 프론트인데 백에서 s3를 사용할리가 없기 때문이다. 그래서 일단 데이터 호출한 부분까지만 정리해 올리고 목업 데이터를 이용한 기능 구현에 초점을 맞춰서 프론트 단에서만 개발을 할 예정이다.

 

2시간이 좀 아깝지만 남은 시간 빡세게 해보자ㅠㅠ

 

/route/router

const express = require('express');
const router = express.Router();
const controller = require('../controller/Cblog');

router.get('/', controller.get_blog);

module.exports = router;

 

 

/controller/Cblog

const db = require('../models');
const models = db.User;

const get_blog = async(req, res) => {

    const blogContents = await models.User.findAll({});

    console.log("받아온 블로그 데이터", blogContents);
}

module.exports = {
    get_blog,
}

 

 

이렇게 하면 로컬 데이터베이스에서 데이터 가져오는 게 가능하다.

 

이 글은 여기서 마무리하고 목업 데이터를 이용한 프론트엔드 블로그 만들기를 해봐야겠다

Next.js에서는 fetch의 사용을 권장한다. 그 이유는 데이터 캐싱 기능을 자동으로 적용시켜주시 때문에 다른 데이터를 호출하는 것에 비해 성능면에서 뛰어나기 때문이다.

 

먼저 함수에 async를 걸어주고 fetch를 불러오면된다.

export default async fntion MyComponent(){
	const MyDat = await fetch("url", {option});
}

해당 방식으로 호출해주면 된다.

근데 여기서 중요한 점은 axios의 경우 JSON 처리를 해주지 않아도 되지만 fetch로 불러오는 데이터는 JSON 변환을 해주어야한다. 이는 데이터를 불러오는 과정에서 보여주는 걸로 하고 Next에서 지원하는 기능에 대한 알아보자

 

- cache 옵션 설정

1.

fetch("url", {cache: 'force-cache' | 'no-store'});

데이터 캐시에서 일치하는 요청을 찾는다

  • 일치하고 최신일 경우 > 캐시에서 반환해준다.
  • 일치 x, 오래된 경울 > 원격 서버에서 리소스를 가져오고 캐시에 저장하지 않는다

2.

fetch("url",  {next:{revalidate: false | 0 | number}});

리소스의 캐시 수명을 설정한다.

  • false : 리소를 무기한 캐시한다. HTTP 캐시는 시간이 지남에 따라 오래된 리소스를 제거할 수 있다.
  • 0 : 리소스가 캐시되는 것을 방지한다.
  • number : 리소스 캐시 수명이 최대 n초임을 지정한다.
더보기

알아둘만한 것

- 개별 fetch 요청이 경로의 기본값보다 낮은 숫자로 수명을 설정하며 전체 경로의 재검증 시간으 줄어든다.

- 동일한 경로, 동일한 url을 사용할때 revaildate 값이 더 낮은 값을 사용한다.

- 옵션이 충돌하면 에러가 발생한다. 

ex) {revaildate : 0, cache: force-cache}, {revaildate: 10, cache: no-store}

 

위의 내용에서 '리소스를 캐시한다'라는 문장에서 캐시란 무엇일까?

 

먼저 리소스에 대해 알아야한다.

리소스 : HTTP에서 리소스란 웹 브라우저가 HTTP 요청으로 가져올 수 있는 모든 종류의 파일을 말한다.

 

위와 같은 리소스를 미리 복사해 저장해 놓는 임시 저장소가 바로 cache이다. 

이러한 캐시를 통해 이전에 불러온 리소스를 다시 불러오기 전에 캐시에서 탐색하여 해당 리소스가 존재한다면 다시 불러오지 않고 캐시에서 꺼내 사용한다. 이 방식을 통해 데이터 페칭 과정을 줄여 성능 및 효율성을 높일 수 있는 것이다.

 

리액트 시작

npx create-react-app 폴더이름

타입 스크립트 시작

npx create-react-app 폴더이름 --template typescript

JSX & 문법

JSX [형태]

1. 최상위 element는 형제 요소가 없는 고유한 요소여야야한다

ex) 가능한 형태

function App() {
  return (
    <div>
      <h1>가능</h1>
    </div>
  );
}

export default App;

불가능한 형태

function App() {
  return (
    <div>
     
    </div>
    <h1>불가능</h1>
  );
}

export default App;

최상위에 아무것도 넣고 싶지 않다면 빈태그로 넣을 수 있다.

 

2. 종료 태그는 무조건 존재해야한다. ex) < h1></h1>이거나  <input />

 

 

JSX [변수 삽입]

위에서 선언한 변수를 html에 {}로 넣을 수 있다.

 

JSX [아이디와 클래스]

id 적용이 가능하며, class는 className으로 입력해야한다.

 

JSX [스타일 적용]

style 적용은 중괄호 안의 중괄호, 즉 객체 형태로 작성하여 스타일을 넣어준다.

ex) { {color : 'red', fontSize : '40px'} } // 카멜케이스로 넣어야한다.

 

문법 [map()과 filter()]

JSX에서 변수 삽입할 때 사용하는 중괄호 안에는 if문이나 for문은 들어가지 못한다. 그래서 배열을 편하게 html에 보여주고 싶을때, map 함수와 filter 함수를 이용해 편하게 보여줄 수 있다.

 

- map() 함수

예를 들어서 나열을 하고 싶은 arr이라는 배열이 있을때, for문을 사용하지 않고 map을 이용한다면 편하게 정렬할 수 있다.

let arr = [1, 2, 3, a, b, c];

arr.map((val, index, arr) => {

        retrun 보여주고 싶은 값

});

여기서 각 콜백 함수에 대해 설명하자면

val : 현재 돌고 있는 값

index : 돌고 있는 값의 인덱스값

arr : 현재 돌고 있는 배열 전체

 

그리고 보통 리액트에서 사용할때는 객체가 배열에 들어가 있는 경우가 많기 때문에 단일로 써서 각 객체에서 원하는 값을 불러낸다. 여기서 중요한 점은 key값을 설정해줘야 오류가 나지 않는다. 아래와 같은 객체를 가진 배열이 있다고 가정하자. 그럼 map은 다음과 같이 사용할 수 있다.

//리액트의 한 컴포넌트라고 가정하자

    export default function 컴포넌트이름 () {
        //여기에 옆에 있는 객체를 가진 배열도 존재한다고 가정한다.

        return (<>
            <ul>
                {list.map((listData) => {
                    return <li key={listData.id}> {listData.alpha} </li>
                })}
            </ul>
        </>)
    }

따로 아이디가 존재하지 않다면 인덱스를 가지고 오는 콜백함수를 키값으로 넣을 수 있다. 

 

- filter() 함수

 보통 map 함수를 이용해 배열을 나열하기 전에 어떠한 조건을 기준으로 배열을 가공할 때 사용한다. 즉, filter 함수로 리턴된 배열을 map함수로 나열한다고 생각하면 편하다. 왜냐하면 filter 함수는 map 함수처럼 html 자체를 반환해주지는 않고, 그냥 조건에 맞는 배열 자체를 보내주기 때문이다. 사용법은 다음과 같다.

//리턴한 배열을 받기 위해서 변수를 선언하어 사용하는 경우가 많다.
arr.filter((val, index, arr)=> {
	return 조건
})

 

 

문법 [sort함수]

sort 함수를 통해 문자나 숫자를 정렬할 수 있다.

 

문법 [구조분해할당] 

지금까지는 변수를 선언할때 보통

let num = [1, 2, 3, 4];

이런 식으로 했었는데, 선언을 할때부터 각 값을 지정할 수 있다.

let [a, b, c, d] = [1, 2, 3, 4];

이를 활용하여서 어떠한 객체 파일을 보냈을때 받은 데이터에서 원하는 값만 사용하고 싶은 경우 구조분해 할당을 통해 원하는 값만 뽑아서 사용할 수 있다.

ex)

보낸 객체

const data = {

    name: result.name,

    pw: result.pw,

}

 

받은 곳에서 이름만 사용하고 싶음

const data = req.body.data //(경우에 따라 params가 올 수도 있다.)

let { name } = data;


Component

MVC 모델의 view를 독립적으로 구성해 재사용할 수 있고, 새로운 컴포넌트도 만들 수 있다. 간단히 말하자면 하나로 이어진 html 파일은 구분하기와 읽기가 어렵기 때문에 개별적으로 컴포넌트를 만들어준 후에 App.js에서 import해서 가져올 수 있다. 이 과정에서 props를 넘겨주면 컴포넌트끼리 데이터를 간편하게 주고 받을 수 있다.

 

컴포넌트는 원래 함수형 컴포넌트롸 클래스형 컴포넌트가 있다. 예전에는 state와 라이프 사이클을 쓸 수 있는 클래스형

컴포넌트를 많이 사용했지만 현재는 Hook으로 state와 라이프사이클 등 다양한 부분들을 함수형 컴포넌트에서도 사용할 수 있게 되면서 함수형 컴포넌트를 많이 사용하게 됐다.  각각의 컴포넌트 구조를 알아보자

 

- 클래스형 컴포넌트

클래스형 컴포넌트는 리액트에서 컴포넌트를 불러오고 상속을 받아서 사용을 한다. 그래서 랜더 안에 리턴을 넣어서 사용한다.

import {Component} from "react";

class 컴포넌트이름 extends Component {
	render() {
    	//삼항연산자 한 번 써보자
        const classes = 'dd';
        
        return (
        	<>
            	<div>{classes === 'dd' ? "dd씨 반가워요" : "넌 뭐야"}
                {/* 참이라면 앞의 것을 출력해주고 거짓이라면 뒤의 것을 출력해준다. */}
                <div> 반가워!! </div>
            </>
        )
    }
    
    export default 컴포넌트이름;

}

- 함수형 컴포넌트

함수형 컴포넌트는 위에서 따로 불러올 필요 없이 바로 함수로 만들어서 사용할 수 있다. 그리고 함수 특성을 이용해 작성 동시에 앞에 export default를 적어서 바로 export 해 줄 수 있다.

export default function 컴포넌트이름() {
	
    return (<>
    	<div>함수형 컴포넌트</div>
    </>)
}

 클래스형 컴포넌트와 비교하면 정말 간단한 것을 알 수 있다. 그리고 export할때 디폴트를 쓰는 것과 았르 때 import 하는 방법이 다른데 다음과 같다.

이것도 기억하면 좋을 것 같아서 가져왔다.


Props (+ JS와 Ts 사용법 차이)

부모 컴포넌트에서 자식 컴포넌트로 정보를 보낼 때 사용한다. 보통 상위 컴포넌트에서 다른 컴포넌트를 불러올 때 아래와 같이 동시에 쓴다.

- JS 사용법

import Child from './Child';
import {useState} from 'react';


export default Parents() {
	const[name, setName] = useState('내이름');
    
    return (<>
    	<Child 보내는이름=문자열 혹은 {위에서 정의한 변수나 state}></Child>
    </>)
}

위와같이 작성하면 자식 컴포넌트에서 props로 받아서 쓸 수 있다.

 
 export default Child(props) {
 	
    return (<>
    	<div>{props}</div>//배열일 경우 {props[원하는인덱스값]}
        <div>{props.children}</div>// 위에서 보낸 값 말고 <>이사이의 값</>을 가져온다.
    </>)
 }
 
 //구조분해할당 방식
 
  export default Child({name, children}) {
 	
    return (<>
    	<div>{name}</div>
        <div>{children}</div>
    </>)
 }

- Ts 사용법

근데 이를 TypeScript에서 쓰려면 부모 컴포넌트와 자식 컴포넌트에서 타입을 모두 지정해줘야 오류가 나지 않고 잘 전달된다.

import Child from './Child';
import {useState} from 'react';


export default Parents() {
	const[name, setName] = useState<string>('내이름');//타입 지정해줘야함
    conconst[nameArr, setArr] = useState<string[]>(['내이름', '너이름', '우리이름']);
    //배열일 경우 타입지정
    
    return (<>
    	<Child name={name} setName={setName} name={nameArr} setName={setArr} >넌 할 수 있어</Child>
        //자식 컴포넌트에서 어떤 타입으로 받아야할지 모르겠다면, 
        //여기서 해당 변수위에 마우스를 올리면 어떠 타입인지 알 수 있다. 
    </>)
}

위와 같이 보내주었다면 개별로 타입을 지정해주는 것보다 interface로 만들어 지정해주는 것이 편하다.

import { Dispatch, SetStateAction } from 'react'

interface PROPS {
	name : string,
    nameArr : string[],
    setName : Dispatch<SetStateAction<string>>,  
    setArr : Dispatch<SetStateAction<string[]>>,
    //원래 ()=>{} 이 방식으로 보내도 됐었는데 계속 오류가 나서 찾아보니까 저렇게 하는거였다.
    children : React.ReactNode,
}

export default Child({name, nameArr, setName, setArr, chidren}: PROPS){
	
    const change = () => {
    	setName('너이름');
    }
    
	return (<>
    	<p>{name}</p> //이름
        <p>{nameArr.map((val. valindex) => {
        	return <p key={valindex}> {val} </p>
        })}</p> // 배열 출력
        <p> {children} </p>
        <button onClick={change}>이름변경</button>
    </>)
}

이렇게 사용할 수 있다.


 

 

 

 


props [객체를 가진 배열에 id를 추가한 후 id에 맞는 값을 가져와 모달창에 출력하는 방법](+Ts)

처음에는 객체를 가지지 않고 그냥 문자열을 가진 배열에서 인덱스를 뽑아서 보낼려고 했는데, 이유는 모르겠지만 계속 되지 않아서 객체를 가진 배열로 만들어 id와 내용을 넣어서 제목에 onClick 이벤트를 넣고 클릭을 했을때, 해당 id를 받아와서 자식 컴포넌트로 보내주는 방식을 사용했다. 이 과정에서 확실히 알게 된것은 타입 지정을 미리 해줘야한다는 것이다. 

 

배열 안에 있는 객체임으로 객체에 들어가는 변수들에 대한 타입 지정을 interface로 해준 뒤 그 인터페이스이름[] 이렇게 타입을 넣으면 된다. 실제 코드를 쳐보도록 하겠다.

 

App.js

//실제 내가 실습 중인 코드는 다른 내용들이 다 포함되어있어서
//위에서 말한 기능만 보여주는 코드를 짜보도록 하겠다.

import { useState } from 'react';
import Modal from './Modal';

//2. 만든 객체를 보고 interface를 만들어주자
interface Title {
	id: number;
    title: string;
    text: string;
}

export default Parents() {
	//1. 객체를 먼저 만들어주고 그에 맞는 interface를 만들어주자
    //3. 만든 interface를 useState에 적용해준다. Title[]
    const [title, setTitle] = useState<Title[]>([
        {
            id: 1,
            title: '제목1'
            text: '내용1'
        },
        {
            id: 2,
            title: '제목2'
            text: '내용2'
        },
        {
            id: 3,
            title: '제목3'
            text: '내용3'
        },
    ]);
    
    //4. 모달 창은 삼항연산자나 단축평가를 이용해 앞의 값이 true라면 창이 열리도록 할것이다.
    // 그리고 버튼을 눌렀을때 id 값도 같이 들어와야한다.
    // 모달을 열고 닫을 boolean을 담은 useState와 클릭이벤트가 일어났을때 값을 변경할 함수를 만들자
    // 또한 id의 값을 가져갈 useState도 만들어주자
    
    const[modal, setModal] = useState<boolean>(false);//처음엔 닫혀있음으로 false값이다.
    const[id, setId] = useState<number>(0);//값을 안써주면 오류가 난다. 0을 기본값으로 쓰자
    
    //5. 버튼을 눌렀을때 id값을 가져오는 부분을 만들고 id를 파라미터로 받아서 사용해주자
    const modalBtn = (id) => {
    	if(modal === false){
        	setModal(true);
            setId(id);
        } else {
        	setModal(false);
        }
    };
    
    return (<>
    	{/* 5. 화살표함수 방식으로 바꿔서 val.id를 하면 id를 받을 수 있다.*/}
        {title.map((val)=>{
        	return <p key={val.id} onClick={() => modalBtn(val.id)}> {val} </p>
        })}
        
        {/* 6. 가져온 모달창에 props를 보내주는 것과 
        modal state에 맞춰 열고 닫히는 코드를 짜보자 */}
    	
        //삼항연산자
        {modal ? <Modal titleObj={title} id={id}></Modal> : null}
        //단축평가
        {modal && <Modal titleObj={title} id={id}></Modal>}
        
    </>)	
    
}

위에서 업급했듯 저렇게 props를 받기 위해선 자식 컴포넌트에서 type을 다 설정해줘야한다.

 

Modal.js

//1. 부모에서 사용한 interface와 props에 적용할 interface를 만들어주자
interface Title {
	id: number;
    title: string;
    text: string;
}

interface PROPS {
	title: Title[];
    id: number; 
}

export default Modal({title, id}: PROPS){
	
    //2. 값을 받아와서 타입 설정을 해준 뒤에 인덱스 값이므로 id-1로 불러와서 넣어준다. 
    retrun (<>
    	<h4>제목 : {title[id-1].title}</h4>
        <p>날짜 : 10월 16일 발행(까먹고 추가 못함)</p>
        <p>내용 : {title[id-1].text} </p>
    </>)
}

이렇게 만들어주면 각 제목을 누를때만다 제목에 대한 내용이 나오는 모달창을 구현할 수 있다.


 

 

 


Hook

훅은 클래스형 컴포넌트에서만 가능했던 상태관리(state)와 생애주기(라이프 사이클)을 함수형 컴포넌트에서도 할 수 있도록 하는 기능이다.

훅의 중요한점은 컴포넌트 안에서만 호출이 가능하다는 것이다. 대표적인 것들을 설명하자면

- useState() : 상태관리를 해주는 가장 기본적인 훅이다.

- useEffect() : 클래스형에서 사용하는 라이프 사이클을 사용할 수 있도록 해준다.

- useContext() : 리액트에서 사용하고자 하는 데이터를 전역적으로 처리할 수 있다. 컴포넌트가 많은 큰 프로젝트에서 props를 사용하면 매우 번거로운데, 이에 대한 해결책 중 하나이다. 하지만 useContext보다는 리덕스를 더 많이 사용하는 추세이다.

- useReducer() : 복잡한 컴포넌트의 상태로직을 리듀서 함수로 관리해준다.

- useMemo() : 메모이제이션을 통해 함수의 리턴 값을 재사용할 수 있게 해주는 훅

- useCallback() : 함수를 메모이제이션하여 불필요한 렌더링을 줄이게 해주는 훅

- useRef() : 참조(reference)를 생성하고 관리할 수 있는 훅 (DOM 접근, 변수 보존 등)

 

 

Hook [useState]

- 기본

위에서 import로 선언을 해주고,

let/const [실제 값, 값변경함수] = useState(실제값 초기화);

위와 같은 방식으로 사용한다. 그리고 타입 스크립트로 지정해줄려면 

let/const [실제 값, 값변경함수] = useState<string, number 등등>(실제값 초기화);

let/const [실제 값, 값변경함수] = useState<string, number[]/ string, number{}>(실제값 초기화 >> 배열이나 객체);</string,>

 

 

이 state를 사용할 경우 state가 갱신되거나 변경될때마다 다시 랜더링 해주기 때문에 유용하게 사용할 수 있다.

즉 계속해서 변화하거나 추가되는 것에 state를 사용하면 된다.

원래는 클래스형 컴포넌트에서만 사용할 수 있었는데 useState를 통해 함수형 컴포넌트에서도 사용할 수 있게 됐다.

 

주의할 점은 무조건 값을 변경할 때는 실제 값인 state를 건들면 안되며, setState를 통해 변경해야한다.

 

- 배열을 변경할 때,

배열을 변경할 때는 직접 배열이 있는 state 가져올 수 없기 때문에. spread 연산자(...)를 사용한다.

만약에 state가 배열로 설정되어 있고, 해당 state에 새로운 값을 추가하고 싶다면 다음과 같이 작업하면 된다.

 

ex) 

- 배열 추가

const [arr, setArr] = useState<string[]>(['바보', '강아지', '야옹']);

추가

setArr([...arr], '추가할 값'); 

 

- 배열 삭제

setArr(arr.filter(e => e.id !== 삭제하고자 선택한 값의 아이디);

배열의 객체 속에 아이디가 있다고 가정한 후 위와 같이 코드를 적으면 선택된 아이디와 다른 아이디만 반환하기 때문에 

선택된 값이 삭제된 배열을 얻을 수 있다. 

 

- 배열 교체

Array.prototype.push.apply(기존 배열, 새로운 배열);

 

 

Hook [useEffect]

라이프 사이클을 리액트에서 사용할수 있도록 해주는 훅이다. 라이프 사이클은 총 3가지로 나뉘는데 

1. Mount : 처음 생성될때 === 컴포넌트를 처음 불러올때 

2. Update : state나 props가 변경되어 재랜더링 될때

3. Unmount : 컴포넌트가 사라질때

 

이렇게 3가지의 경우를 useEffect를 사용해 각 경우에 어떤 작업을 실행할 지 정해줄 수 있다. 

클래스형에서는 •  componentDidMount • componentDidUpdate • componentWillUnmount

위의 세 가지 메서드를 자주 사용하고 공식 문서에도 권장하고 있다.

 

클래스형에서 사용하는 예시를 간단히 보여주고 useEffect의 사용법을 배워보자

state에 있는 숫자를 더해주고 빼주는 기능을 하는 코드이다. 클래스형에서 state와 함수를 사용하기 위해선 this를 써야하고, props와 state는 생성자에 넣어줘야한다. 그리고 위에서 사용하는 메소드들은 그냥 적어서 바로 사용할 수 있다.

 

Hook [useEffect()]

- useEffect( () => {} ); //컴포넌트가 랜더링 될때마다 실행된다.

- useEffect( () => {}, [] ); // mount가 될때만 즉 최초로 컴포넌트를 불러올때만 실행한다.  

- useEffect( () => {}, [특정값] ); // 특정값이 변경될때만 실행한다.

 

만약에 컴포넌트가 사라지거나 특정값이 사라질때 어떤 작업을 수행하고 싶다면, 중괄호 안에 return () => {}; 를 이용해 해당 함수 안에 넣으면 수행하게 할 수 있다.

 

간단히 요약하자면 useEffect(랜더링 이후 수행할 함수, 어떤 상황에 할 것인지 나타냄(  or [] or [특정값]) )

 

 

Hook [useRef()]

훅 useRef는 html의 요소를 가지고 오기 위해 사용한다. 근데 이를 ts에서 사용하기가 진짜 빡셌다. 특정 요소에서 원하는 값을 가지고 올려고 할때 형식이 맞지 않아서 가지고 올 수 없기 때문에 요소에 적는 ref에도 형식 지정을 해줘야한다..ㅠㅠ

 

기본적인 사용법은 useRef로 myref하나 만들어주고 이를 원하는 요소에 ref={}로 넣어준뒤 myref.current로 가져올 수 있는 다. ts에서도 똑같이 해주면 되는데 형식 지정을 해줘야한다. 

 

프로젝트를 하면서 겪은 것을 올리도록 하겠다.

에러 

'MutableRefObject<HTMLTextAreaElement | undefined>' 형식은 'LegacyRef<HTMLTextAreaElement> | undefined' 형식에 할당할 수 없습니다. 'MutableRefObject<HTMLTextAreaElement | undefined>' 형식은 'RefObject<HTMLTextAreaElement>' 형식에 할당할 수 없습니다. 'current' 속성의 형식이 호환되지 않습니다. 'HTMLTextAreaElement | undefined' 형식은 'HTMLTextAreaElement | null' 형식에 할당할 수 없습니다. 'undefined' 형식은 'HTMLTextAreaElement | null' 형식에 할당할 수 없습니다.ts(2322) index.d.ts(159, 9): 필요한 형식은 여기에서 'DetailedHTMLProps<TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>' 형식에 선언된 'ref' 속성에서 가져옵니다.

 

해결법

이 에러는 TypeScript에서 useRef의 반환 값과 textarea 요소의 ref 속성의 타입이 일치하지 않아 발생합니다. TypeScript에서는 기본적으로 useRef를 사용하면 current 속성이 HTMLTextAreaElement | null 형식을 가집니다. 반면 textarea 요소의 ref 속성은 HTMLTextAreaElement | undefined일 수 있습니다.

이 문제를 해결하려면 useRef와 textarea 요소의 타입이 일치하도록 타입을 지정해야 합니다. HTMLTextAreaElement | null 형식을 HTMLTextAreaElement | undefined 형식으로 변경하거나 그 반대로 변경할 수 있습니다. 아래는 두 가지 방법 중 하나를 선택하여 문제를 해결하는 방법입니다:

1. useRef를 HTMLTextAreaElement | undefined 형식으로 변경:

const storyTextAreaRef = useRef<HTMLTextAreaElement | undefined>(undefined);

2. textarea 요소의 ref를 HTMLTextAreaElement | null로 변경:

<textarea ref={storyTextAreaRef as React.LegacyRef<HTMLTextAreaElement> | undefined} id='story'>
  {title[id-1].story}
</textarea>

 

이렇게 하면 textarea 요소의 ref 속성과 useRef의 반환 값의 타입이 일치하게 됩니다.

어느 방법을 선택하느냐는 개발 환경 및 팀의 코딩 스타일에 따라 다를 수 있습니다. 각 방법은 문제를 해결하는 데 사용될 수 있습니다.

 

위와같이 하면 가져올 수 있다. 근데 정말 저렇게 하는 것이 맍는지 의문이들긴한다.

 


 

 

 

 

 


리액트 라우터 (React Router)

사용자가 요청한 주소에 따라 원하는 페이지를 보여주기 위해 사용한다.

npm i react-router-dom@6

버전 6으로 지정해서 설치해준다. 

 

 BrowserRouter와 createBrowserRouter를 import 해서 사용할 수 있고, 최근에 사용하는 것은 유지보수하기 수월한  createBrowserRouter라고 한다. 

 

그러므로 BrowserRouter는 간단히 사용법만 알아보고 현재 진행중인 프로젝트는 createBrowserRouter를 이용할 것이다.

 

두 작업 모두 먼저 해야하는 것은 Router파일을 만들어줘야한다.

 

- BrowserRouter

먼저 위에서 import를 해준 뒤에 라우터 컴포넌트를 만든 후 그 안에 < BrowserRouter > </ BrowserRouter >를 넣어주고, Routes와 Route를 이용해 각 컴포넌트의 경로를 지정해주면된다.

import { BrowserRouter, Routes, Route } from 'react-router-dom';

import Header from './12Router/Header';
import Home from './12Router/Home';
import About from './12Router/About';
import NotFound from './12Router/404';
import User from './12Router/User';
import Redirect from './12Router/Redirect';
import UserDetail from './12Router/UserDetail';
import Error from './12Router/Error';
import Comment from './12Router/Comment';

export default function Router() {
    return (
        <>
            <BrowserRouter>
                <Header />
                <Routes>
                    <Route path="/" element={<Home />} />
                    <Route path="/about" element={<About />} />
                    <Route path="/user" element={<User />} />
                    <Route path="/user/:id" element={<UserDetail />} />
                    <Route path="/redirect" element={<Redirect />} />
                    <Route path="*" element={<NotFound />} />
                </Routes>
            </BrowserRouter>
        </>
    );
}

 path는 지정할 경로이며 elment는 보여줄 컴포넌트를 지정하는 것이다.

 

- createBrowserRouter

 크리에이트브라우저라우터는 이거 자체만 import해서 가져오면 사용가능하다.

import { createBrowserRouter } from 'react-router-dom';

import App from './App';
import NotFound from './12Router/404';
import Header from './12Router/Header';
import Student from './12Router/Student';
import Codingon from './12Router/Condingon';
import Search from './12Router/Sech';

const Router = createBrowserRouter([
	{
    	path: '/',
        element: <Header />,
        // 여기서 children : [{},{}...]를 이용해서 api/children에 지정된 경로
        // 이렇게 사용할 수 있다.
    },
    {
    	path: '/student',
        element: <App />,
        children: [
        	{
                path: 'kdt',
                element: <Student />,
            },
            {
                path: 'codingon',
                element: < Codingon/>,
            },
            {
                path: 'new',
                element: <Search />,
            },
        ],
    },
]);

export default Router

이렇게 지정한 뒤에 api에 따라 자식 컴포넌트로 원하는 컴포넌트를 불러올 수 있는 Outlet을 App.js에 적용하고, 인덱스에는 다음과 같이 적용하면된다.

App.js

import { Outlet } from 'react-router-dom';
import Header from './12Router/Header';
import Router from './Router';

function App() {
    return (
        <>
            {/* 최근 버전 */}
            <Header />{/* 계속 페이지에 고정하고 싶은 컴포넌트가 있다면 헤더처럼 여기서 적어주면된다. */}
            <Outlet />
            
            {/* 옛 버전은 라우터를 불러왔다 */}
            {/* <Router /> */}
            
        </>
    );
}

export default App;

 

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import Router from './Router';

import App from './App';
import { RouterProvider } from 'react-router-dom';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    {/* 옛 버전 */}
    {/* <App />  */}
    
    {/* 최근버전 */}
    <RouterProvider router={Router} />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals

위와 같이 라우터프로바이더를 불러오고 거기에서 라우터를 props로 보내주면된다.

 

Ts에서도 똑같이 해주면된다. 그리고 리액트 라우터를 하면서 유용하게 사용할 수 있는 것들을 설명하며 라우터 부분을 마치도록 하겠다.

이는 주소로 아이디를 보내서 아이디에 맞는 페이지를 보여주고 싶을 때 사용할 수 있다. api/:id로 해서 그 id값을 가지고 올때 useParams로 받아서 가져올 수 있다.

 

주소에 있는 ? 뒤의 값을 바꿀 수 있다.

 

 

import { useNavigate } from 'react-router-dom';

import 로 가져와서 사용할 수 있다. 이후에 위처럼 navigate('이동할 주소'); 이 방식으로 원하는 페이지로 이동할 수 있다.

 

useNavigate()와 useLocation()

네비게이트로 파일을 보내주면 uselocation으로 받아서 쓸 수 있다.

먼저 useNavigate로 원하는 데이터를 보내준다.

const navigate = useNavigate();

const gotoMain = () => {
  navigate("/main", {
    state: {
      userId: user.uid
    }
  });
};
// 두 번째 인자에 데이터를 써서 보내준다

 

그 다음 useLocation을 import 해준다.

import { useLocation } from 'react-router-dom';

그다음에 uselocation을 불러오면서 navigate로 넘어온 데이터를 받을 수 있다.

const location = useLocation();


const [userId, setUserId] = useState(
  location.state?.userId
);

위와 같이 location에 state의 정보를 담을 수 있다.


 

 

 

 

 


이벤트 리스너

 

onClick = {} : 중괄호 안에 함수를 적으면 클릭할 때, 실행된다.

원래는 함수를 적고 바인드를 해줘야하지만 화살표 함수를 쓰면 바로 사용할 수 있다 그래서 중괄호 안에 들어가는 함수는 아래와 같이 작성해주면 된다.

const 함수이름 = () => {
	원하는 기능
}

 

onChange = {}: onChange가 있는 요소의 값이 변할때 작동한다. 보통 값이 변할 때마다 그 값을 보내주는 방식으로 사용을 많이하는데 다음과 같이 사용하면된다.

onChange = {(e) => { e.target.value }
//변한 값을 받을 수 있다.

이를 함수에서 받고 싶다면 다음과 같이 사용하면 된다.

export default function Component () {
	
    const 함수이름 = (e) => {
    	consle.log('변한 값', e.target.value)
    }
    
    return (<>
    	<input onChange={함수이름}> </input>
    </>)
}

이렇게 사용하면 변한 값을 함수에서 받아서 사용할 수 있다.


 

 

 

 


부가기능

에디터를 만들어주는 Quill 사용해보기

먼저 npm으로 깔아준다. 

npm i react-quill
 

React-Quill 사용하기(1)

ReactQuill 사용하기

velog.io

 

생각해보니까 벨로그처럼 만들어야해서 퀼말고 토스트 ui를 가져다 쓰기로 했다.

 

[React] Toast UI Editor 적용기

Next.js 프로젝트에 Toast UI Editor를 적용해보자

velog.io

잠들어 버려서 프로젝트를 완성할 시간이 부족했다..ㅠ 빠르게 할려고 참고한 블로그를 올린다.

 

근데 치명적인 단점이 토스트 ui가 리액트 18버전에서는 적용이 안된다는 것이었다. 그래서 마크다운은 포기하고 Quill을 다시 이용하기로 했다.

 

지금  Ts를 같이 사용중이기 때문에 npm으로 설치할때 아래와 같이 설치해줘야한다

npm install @types/react-quill

 

 


에러관리

리액트 정리를 하면서 블로그 프로젝트를 만들고 있다. 이 과정에서 생기는 에러들을 정리하는 곳이다.

 

 

에러 1.

'App'은(는) 값을 참조하지만, 여기서는 형식으로 사용되고 있습니다. 'typeof App'을(를) 사용하시겠습니까?

이는 ts 오류인데 코드를 적고 있는 파일의 형식을 tsx로 바꾸어주면 해결된다.

 

에러 2.

Error: input is a void element tag and must neither have `children` nor use `dangerouslySetInnerHTML`.

input을 p태그 안에 넣어서 생긴오류

 

+ Recent posts