추석 동안 개인 프로젝트 과제를 주셨다. 리액트하다가 다시 express를 할려니까 기억이 잘 안난다. 그동안 했던거 찾아보면서 해야한다..ㅠㅠ 차근차근 하면서 복습하자

폴더를 새로 생성해주고 위의 폴더들을 만들어준다. 먼저 백엔드 작업을 해야하기에 server/ 폴더를 만든다.

만든 후에

npm init -y

express와 데이터베이스와 씨퀄라이즈를 사용할 것이기 때문에 함께 설치해준다.

npm i express mysql2 sequelize sequelize-cli

씨퀄라이즐를 사용하기 위해 아래 명령어도 작성해준다.

npx sequelize init

이를 설치하던 중에 

npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/seqeulize-cli - Not found
npm ERR! 404
npm ERR! 404  'seqeulize-cli@*' is not in this registry.
npm ERR! 404
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.

npm ERR! A complete log of this run can be found in: C:\Users\82103\AppData\Local\npm-cache\_logs\2023-10-02T05_39_37_969Z-debug-0.log

 

위와 같은 에러가 발생해서 찾아봤는데 오픈소스 패키지를 npm 명령어로 설치하려 할 경우, 설정되어 있는 default registry에 패키지가 존재하지 않아 404 에러가 발생되는 것이었다. 이를 해결하기 위해선  npm install 시에 --registry 옵션을 넣어주면 다른 registry에서 패키지 다운로드가 가능하다. 일반적인 npm 패키지는 --registry https://registry.npmjs.org/ 를 넣어주면 된다.

npm install --save-dev (설치하고자하는 패키지 이름) --registry https://registry.npmjs.org/

 

완료 후 상태

 

빠르게 워크밴치로 만들었다.

이제 위의 api에 맞는 백엔드 기능들과 데이터베이스를 연결해보자

 

데이터 베이스 연결하기

1. config 파일 수정

먼저 씨퀄라이즈를 설치하면서 생기 config 파일에 들어가 설정한 유저 정보를 입력하여 나의 로컬에 있는 데이터배아스와 연결해준다.

# 사용자 등록
create user '유저이름'@'%' identified by '비밀번호';
grant all privileges on *.* to '유저이름'@'%' with grant option;
FLUSH privileges;

# 사용자 허가
select host, user, plugin, authentication_string from mysql.user;
alter user '유저이름'@'%' identified WITH mysql_native_password by '비밀번호';

데이터베이스를 가져오기 위한 유저를 등록하는 방법은 위와같다. 위를 통해 유저 등록을 한 뒤에 config에 사용할 데이터베이스와 유저이름 그리고 비밀 번호를 입력하면 원하는 데이터 베이스를 사용할 수 있다.

 

2. models파일의 index.js 수정

그리고 만들어진 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.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

 

3. mysql과 연결

models 파일에 연결할 데이터베이스의 테이블과 대응되는 모델을 정의해준다.

models 파일의 todolist.js

const { DataTypes } = require('sequelize');

const Model = (sequelize) => {
    return sequelize.define(
        'todo',
        {
            id : {
                type: DataTypes.INTEGER,
                allowNull: false,
                primaryKey: true,
                autoIncrement: true,
            },
            title : {
                type: DataTypes.STRING(100),
                allowNull: false,
            },
            done : {
                type: DataTypes.TINYINT(1),
                allowNull: false,
            },
        },
        //아무것도 정의해주지 않으면 시간도 같이 들어가기 때문에 아래의 코드를 작성해 없애줌
        {
            tablename: "todo",
            timestamps: false,
        }
    );

};

module.exports = Model;

위에서는 바로 리턴을 해줬는데 더 많은 테이블이 있고, 관계 정의를 해줘야하는 경우에는 아래와 같이 작성하면 된다.

const { DataTypes } = require('sequelize');

const Model = (sequelize) => {
    const Todo = sequelize.define(
        'todo',
        {
            id : {
                type: DataTypes.INTEGER,
                allowNull: false,
                primaryKey: true,
                autoIncrement: true,
            },
            title : {
                type: DataTypes.STRING(100),
                allowNull: false,
            },
            done : {
                type: DataTypes.TINYINT(1),
                allowNull: false,
            },
        },
        //아무것도 정의해주지 않으면 시간도 같이 들어가기 때문에 아래의 코드를 작성해 없애줌
        {
            tablename: "todo",
            timestamps: false,
        }
    );
    
    관계정의
    
    return{
    	Todo,
    },

};

module.exports = Model;

 

모델을 정의 했으니 app.js를 만들어서 연결해준다. 여기서는 index.js로 사용했다.

index.js

const express = require('express');
const app = express();
const PORT = 8080;
const db = require('./models');

app.set('view engine', 'ejs'); //ejs를 사용할 때 설정

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

//router
const useRouter = require('./routes/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}`);
    });
});

- body-parser

body-parser는 미들웨어이다. 즉, 요청(request)과 응답(response) 사이에서 공통적인 기능을 수행하는 소프트웨어이고,  요청의 본문을 지정한 형태로 파싱해주는 미들웨어이다. 원래 Express는 요청을 처리할때 기본적으로 body를 undefined로 처리하기 때문에  바디파서로 요청의 body 부분을 원하는 형태로 파싱할 수 있다.

위에서는 json 파일로 파싱을 하고 있다.

 

- router 

mvc 구조로 사용하기 위해 라우터 파일을 만들어서 연결해준다.

 

- 서버오픈

force를 false로 두면 기존 데이터베이스를 연결하는 것이고 true로 한다면 서버를 실행할때마다 연결된 데이터베이스를 갱신한다.


4. todo 리스트 보여주기와 추가하는 기능 만들기

라우터와 컨트롤러에서 작업을 진행한다. 먼저 /todos를 만들어주자 현재 폴더의 구조는 다음과 같다.

여기서 라우터와 컨트롤러 그리고 모델을 연결하여 투두리스트를 가져와보자 임의의 값을 mysql워크밴치로 넣어준다.

1은 투두리스트를 했다고 체크한 것이고 0은 못한것인다.

 

a. 데이터베이스에서 투두리스트 데이터 가지고 오기

일단 데이터베이스에서 가져오기 위해서

router.js

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

router.get('/todos', controller.get_todo);

module.exports = router;

models의 index.js

'use strict';

const Sequelize = require('sequelize');

const env = process.env.NODE_ENV || 'development';
// 지정된 환경변수가 없다면 'development'로 지정한다.

const config = require(__dirname + '/../config/config.json')[env];
// config/config.json 파일에 있는 설정값들을 불러온다.
// config객체의 env변수(development)키 의 객체값들을 불러온다.
// 즉, 데이터베이스 설정을 불러오는 것이다.

const db = {};
const sequelize = new Sequelize(config.database, config.username, config.password, config);

db.User = require('./todolist')(sequelize); 
// 이렇게 정의한 모델과 연결해준다. 이를 컨트롤러로 가져와서 사용할 수 있다

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

module.exports = db;

Ctodo.js

const db = require('../models');
const models = db.User;
//위에서 만든 User를 가지고 온것이다.

const get_todo = async () => {
    const todolist = await models.Todo.findAll({});

    console.log('Ctodo에서 데이터 받아오는지 확인')
    console.log(todolist);

}

module.exports = {
    get_todo,
};

이를 node index.js로 실행했는데,

오류 내용 : The "data" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received type number (1234)

이 오류가 났었다. 이유를 알아보니까 config.json 파일에 문자열 대신 다른 것이 들어가 생기는 오류라고 한다. 

필자의 경우는 비밀번호를 문자열로 안적고 그냥 적어서 생기는 오류였다. 문자열로 처리해주니 잘 실행되었다.

 

근데 계속 테이블 이름을 todo로 정의했는데 계속 todos로 이름이 바뀌어서 정의가 됐었다 이는 시퀄라이즈가 테이블명을 자동으로 복수형으로 바꾸기 때문에 위에 timestamp를 정의한 option객체에서 tableName으로 이름을 고정해 주어야한다.

 

위의 작업을 모두 끝낸 후 node index.js를 실행하면 데이터베이스의 데이터들이 넘어오는 것을 확인할 수 있다.

이렇게 받아온 데이터를 json 형태로 변환시켜야한다.

기존에 내가 했던 방식은 빈 객체를 만든 후에 for문을 사용해 직접 데이터를 넣어서 만들었었다. 하지만 저번에 2번째 프로젝트를 하며 형들이 사용했던 forEach를 사용해서 객체에 넣은 뒤에 res.json을 통해 json  형식으로 보내주는 방식을 사용해볼 예정이다.

 

forEach문이란?

기본적으로 for문을 사용할려면 규칙을 다 적어줘야하는 번거로움이 있다. 이러한 번거로움을 해결해주는 녀석이 forEach문이다. 

for(let i=0; i<배열명.length; i++){
	console.log(i);
}

위와 같이 적는 for문을 forEach문으로 적으면 

배열명.forEach(i => {
	console.log(i);
});

이처럼 정말 간결하게 작성할 수 있다.

즉, 얼마나 반복을 돌아야하는지 정해주지 않아도 선택한 배열이 가지고 있는 요소만큼 반복을 돌아주는 것이다. 

사용예시를 통해 더 자세히 알아보자

const arr = ['a','b','c','d']; 
arr.forEach(function(item: 현재 돌고 있는 배열의 값, index: 앞의 값의 인덴스, arrnow: 현재 돌고 있는 배열 전체)
{ 
	console.log(item,index,arrnow); 
})
//출력 => a,0,['a','b','c','d'] // b,1,['a','b','c','d'] // c,2,['a','b','c','d']
// d,3,['a','b','c','d']

//응용하면 arrnow[index+1] 등을 입력하여 현재 돌고있는 값의 다음값을 출력 할 수도 있다.

이를 적용해 불러온 데이터를 객체에 넣어주었다.

 

Ctodo.js

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

let todolist;

const set = async () => {
    todolist = await models.todo.findAll({});
}

const get_todo = async (req, res) => {
    
    await set();

    const tododata = await todolist;
    const todoarr = {};

    tododata.forEach((data) => {
        todoarr[data.id - 1] = {
            id : data.id,
            title: data.title,
            done: data.done,
        }
    });

    console.log('Ctodo에서 데이터 받아오는지 확인')
    // console.log(tododata);
    console.log(todoarr);
    


}

module.exports = {
    get_todo,
};

이제 이 객체를 res.json으로 보내주면된다.

 

b. 투두리스트 업데이트하기

post로 보낸 값은 req.body로 받을 수 있다. 먼저 잘 받는지 console을 찍어보자 아직 프론트엔트 페이지를 완성하지 못했기 때문에 포스트맨을 사용하여 데이터를 보내준다.

Ctodo.js

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

let todolist;

const set = async () => {
    todolist = await models.todo.findAll({});
}

const get_todo = async (req, res) => {
    
    await set();

    const tododata = await todolist;
    const todoarr = {};

    tododata.forEach((data) => {
        todoarr[data.id - 1] = {
            id : data.id,
            title: data.title,
            done: data.done,
        }
    });

    console.log('Ctodo에서 데이터 받아오는지 확인')
    // console.log(tododata);
    console.log(todoarr);
    
    res.json(todoarr);


}

const post_todo = async (req, res) => {
    const update_todo = req.body;
    console.log(update_todo);
    res.send(update_todo);
}

module.exports = {
    get_todo,
    post_todo,
};

잘 주고받는 것을 확인했다. 이제 받은 데이터를 데이터베이스로 보내는 기능을 완성해보자

매우 간단하다. models에서 todo 데이터베이스를 불러오고 create로 받은 데이터를 보내주면 된다.

포스트맨으로 데이터를 보냈을때,

터미널에 이렇게 뜨고 워크밴치에 업데이트된 것을 확인할 수 있다.

이제 업데이트된 데이터를 바로 적용시켜줘야하기 때문에 다시 데이터 베이스를 가져와서 res.json으로 프론트엔드 페이지에 보내준다.

 

이렇게 데이터를 가져오는 것과 업데이트 하는 기능을 만들었다.

아직 프론트엔드가 완성된 것이 아니라 다한 것은 아니다. 그전에 삭제와 수정 기능도 만들어보자.

 

엥귤러 js >> 프레임워크(정의된 함수를 다 알아야 쓸 수 있음) // 거의 쓰지 않는다.

리액트 >> 라이브러리(웹과 앱의 실시간 소통이 가능하게 하도록 하기 위해 페이스북(메타)에서 만듦)

뷰 js >> 프레임워크지만 정말 간단하고 쉽게 만듦

 

셋 중에 리액트만 라이브러리이다.

 

리액트란?

특징 1)

- 단방향 O 데이터 

- 앵귤러 js 처럼 양방향 데이터 바인딩은 규모가 커질수록 데이터릐 흐름을 추적하기 힘들고 복잡해지는 경향이 있다.

 

특징 2)

- 컴포넌트 기반 구조

- view를 여러 컴포넌트를 쪼개서 만들고 이를 조립하여 화면을 구성한다.

- 이를 통해 생기는 장점은 다음과 같다

전체 코드 파악 용이/ 캡슐화 > 재사용성 ↑ / 코드 반복없이 컴포넌트 import / 코드의 유지보수와 관리 용이

 

특징 3) virtual DOM 

>> DOM 트리 구조와 같은 구조를 Virtual DOM으로 가지고 있기 때문에 전후 상태를 계속 비교하고 개선할 수 있다. 

 

특징 4)

프롭스 : : 부모 컴포넌트에서 자식 컴포넌트로 전달해주는 데이터

AND

state : 컴포넌트 내부에서 선언되고 내부에서 값을 변경한다, 클래스형 컴포넌트에서만 사용이 가능하다. (이를 함수형 컴포넌트에서 사용하기 위해 useState를 사용한다.)

 

특징 5) JSX를 사용한다.

 


본격적으로 사용하는 방법을 알아보자

node.js를 다운한 후에 npx를 통해 설치를 할 수 있다.

(npx : npm의 자식 명령어로 npm보다 가볍게 패키지를 구성한다.)

 

▶ 리액트 설치하기

npx create-react-app 앱이름

으로 설치한 후에 해당 폴더로 들어가서

npm start

를 쳐서 앱을 볼 수 있다.

 

▶ JSX 사용

1. 무조건  최상위 태그에는 형제를 가지고 있지 않는 고유한 요소로 구성되어 있어야한다.

( => 태그명을 넣고 싶지 않을 때는 빈태그를 넣을 수 있다. <></>)

 

2. 무조건 종료 태그가 존재해야한다. => 기존에 혼자 사용하던 태그들고 /나 종료 태그를 사용해야한다.

 

3. 스타일은 _를 사용하는 것이 아니라 camelCase를 이용한다.

그리고 스타일은 객체 형채로 넣어야한다.

 

4. JSX 안에서는 JS를 사용할 수 있다. 문법을 사용하기 위해서는 {}로 감싸서 사용한다.

 

5. JSX 안에서 연산자를 사용하기 위해서는 계산을 밖에서 다한 수에 변수에 담에 JSX에서 보여주거나

{}안에서 삼함 연산자를 이용하여 쓸 수 있다. 

이때, if문이나 for문은 사용 불가하다.

6. 주석의 형태는 {/* */}로 사용한다.

 


map()과 filter()

map함수와 filter함수를 간단히 말하자면 배열에 반복문을 쉽게 적용하기 위해 사용하는 함수이다.

 

map() 함수

map()의 인자로 넘겨지는 calback함수를 실행한 결과를 가지고 새로운 배열을 만들때, 사용한다.

 

기본적인 구조는 다음과 같다

arr(선택한 배열 이름).map(콜백함수, [thisArg])

콜백함수는 새로운 배열의 요소를 생성하는 함수로 currentValue, index, array 3개의 인수를 가질 수 있다.

[this.Arg]는 생략 가능한 것으로 콜백함수에서 사용할 this 객체이다.

 

간단한 예를 들어보자면 다음과 같다

위와 같이 맵을 사용한다면

첫 번째 인수는 배열을 돌며 나오는 값을 나타내고,

두 번째 인수는 첫번째 인수에 나오는 값의 인덱스,

세 번째 인수는 현재 반복을 돌고 있는 배열이 무엇인지 알려준다.

다음에 리턴을 사용하면 리넡에 들어간 값이 있는 배열을 보내준다.

 

이를 컴포넌트에도 아래와 같이 적용할 수 있다.

 

이러한 map함수를 사용할 떄 주의할 점은 꼳 키 값을 적어줘야한다는 것이다.(권장사항)

리액트가 자율적으로 업데이트 전 기존 요소와 업데이트 요소를 비교하는데 키 값을 사용하기 때문이다. 

 

filter () 함수

필터 함수의 인자로 넘겨지는 콜백함수의 조건에 맞는 요소를 모아 새로운 배열을 생성한다.

이를 이용해 배열에서 원하는 값을 삭제하는 코드를 구현할 수 있다.

 

두 가지 방식으로 사용할 수 있다. 

1.

let (생설될 배열을 담을 변수) = arr.filter((arr을 받은 인수 >> arr1)=>{return 조건(ex | arr1.lenth > 3)});

2.

let (생성될 배열을 받을 변수) = arr.filter((arr을 받을 변수>>arr2) => 조건(ex| arr2.lenth > 3));

그리고 includes('탐색하는 배열에 포함된 글자'); 등의 조건을 이용하여 원하는 배열을 뽑아낼 수 있다.

 


단축평가

논리연산자를 사용하여 특정 조건에 따라 값을 결정하거나, 조건에 따하 특정 코드를 실행하는 방법 && 연산자를 사용하거나 || 연산자를 사용한다.

- &&  연산자 둘다 참일 경우(앞에가 거짓이라면 바로 앞을 실행시킨다. 왜냐하면 앞이 거짓이라면 뒤도 거짓이기 때문이다. 이 특징을 이용해 많이 사용한다.)

- || 둘 중 하나가 참일 경우(이 경우는 앞의 값이 거짓일 경우 뒤에 값을 반환한다.)

 

리액트는 최대한 세세하게 쪼갠다 >> 컴포넌트가 꼭 필요하다.

 

컴포넌트는 '트리구조'를 가지고 있다.

 

큰 단위부터 아래로 하나하나씩 쪼갠다.

 

- 함수형 컴포넌트

짧고 직관적

바닐라 js와 같은 기본적인 함수 구조를 이용해 더 직관적이며 추상적이다.

메모리 자원을 덜 사용한다.

기본형 ▼

 

- 클래스형 컴포넌트

스테이트 라이프 사이클 기능 이용 가능

랜더 함수 필수

생성자 존재

기본형 ▼

 

>> 요즘은 다 함수형 컴포넌트를 사용한다. 

 

클래스형 컴포넌트에 스테이트등을 적용하면 매우 어려워진다.

 

컴포넌트는 대문자로 입력해야지 인식한다.

 


props

properties를 줄인 표현으로 컴포넌트 속성을 설정할 때 사용하는 요소

props는 컴포넌트끼리 값을 전달하는 수단이다.

상위 컴포넌트에서 하위 컴포넌트로 전달한다.

<컴포넌트 이름 title="제목" content = "내용"></컴포넌트 이름>

- 함수형 컴포넌트 props

- defaultprops

- props.children

부모 컴포넌트에서 자식 컴포넌트를 호출할 때 태그 사이에 작성하여 불러옴

-propsType

- 클래스형 컴포넌트 props

this를 붙여서 사용해야한다.

 

 

+ Recent posts