88일차 내용 정리

채공부·2025년 9월 25일

Spring_JWT 서버 실행

react-basic 에서 npm run dev

댓글 등록

  • Comment.jsx 에 handleSubmit 함수 추가
    • 댓글 등록 버튼을 눌렀을 때 실행할 함수
const handleSubmit = (e) => {
  	e.preventDefault();
};
  • 댓글 등록 form 에 onSubmit 이벤트 추가
<form onSubmit={handleSubmit} action="/v1/comments" method="post">
  • 댓글 수정 form 에 onSubmit 이벤트 추가
<form onSubmit={handleSubmit} action="/v1/comments" method="post">
  • handleSubmit 함수 안에 변환 후 DB에 데이터를 보내는 코드 추가
    • form 데이터 를 넣은 경우 num=1&name="kim" 이런식으로
      요청의 body에 query string 문자열 이 전달
    • object 데이터 를 넣은 경우 {"num":1, "name":"kim"} 이런식으로
      요청의 body에 json 문자열 이 전달
e.preventDefault();
//submit 이벤트가 발행한 form 의 참조값
const form=e.target;
//폼에 입력한 내용을 FormData 객체로 얻어내서 
const formData=new FormData(form);
//Object 로 변환
const obj = Object.fromEntries(formData);
try{
    await api.post("/v1/comments", obj);
    //댓글 목록을 refresh 한다
    onRefresh();
}catch(err){
  	console.log(err);
}

댓글 등록 조건 추가

  • 댓글 등록 버튼에 disabled 상태 관리 코드 추가
<button disabled={userInfo.userName ? false : true} type="submit" className="btn btn-success">등록</button>
  • 댓글 작성하는 textarea 에 disabled 상태 관리 코드 추가
<textarea id="commentContent" name="content" rows="5" className="form-control"
		placeholder={userInfo.userName ?'댓글을 입력하세요' : '댓글 작성을 위해 로그인이 필요합니다'}></textarea>
  • 답글 폼에 있는 textarea 와 버튼에 disabled 상태 관리 추가
<textarea name="content" className="form-control mb-2" rows="2" 
		placeholder={userInfo.userName ?'댓글을 입력하세요' : '댓글 작성을 위해 로그인이 필요합니다'}
		disabled={userInfo.userName ? false : true}></textarea>
<button disabled={userInfo.userName ? false : true} type="submit" className="btn btn-sm btn-success">등록</button>
<button disabled={userInfo.userName ? false : true} type="reset" className="btn btn-sm btn-secondary cancel-reply-btn">취소</button>
  • BoardController 에 savecomment 메소드 생성
    • 댓글 추가 요청 처리
@PostMapping("/comments")
public ResponseEntity<Void> savecomment(@RequestBody CommentDto dto) {
	commentService.createComment(dto);
	// 204 빈 컨텐츠 응답
	return ResponseEntity.noContent().build();
}
  • BoardDetail.jsx 에 handleRefresh 추가
    • 댓글 refresh 이벤트가 발생했을 때 실행할 함수
const handleRefresh = () => {
    // 댓글 목록을 다시 받아온다
    api.get(`/v1/board/${num}/comments`)
    .then(res => setCommentList(res.data))
    .catch(err => console.log(err));
};
  • CommentonRefresh 함수 사용
<Comment category="board"
    parentNum={dto.num} 
    parentWriter={dto.writer} 
    list={commentList}
    onRefresh={handleRefresh}/>
  • Comments.jsx 에 onRefresh props 추가
function Comment({category, parentNum, parentWriter, list, onRefresh}) {
  • handleSubmit 함수 안에 try 문onRefresh 함수 호출
    • 댓글 목록을 refresh 한다
onRefresh();
  • 댓글 수정 폼에 textarea 에 defaultValue 속성 추가
<textarea name="content" className="form-control mb-2" rows="2" defaultValue={item.content}></textarea>

Electron

  • node.js 로 만든 application 이다
  • node.js 에 gui 앱을 만들 수 있도록 라이브러리를 추가한게 electron 이다
  • UI 의 구성을 편하게 할 수 있다
  • React, Vue.js 사용 가능

  • App.tsx 에 버튼과 onClick 이벤트 추가
<button onClick={() => alert("hello electron")}>클릭해 보세요</button>

  • electron 폴더 안에 main.ts 에 코드 주석 처리
// import { createRequire } from 'node:module'

// const require = createRequire(import.meta.url)
  • cmd 창을 관리자 권한으로 실행 후 hello-electron 폴더로 이동
cd C:\playground\react\hello-electron
  • npm run build 코드 작성
C:\playground\react\hello-electron>npm run build

electron 폴더에 main.ts 파일 분석

createWindow

  • window 를 생성하는 함수

app.whenReady().then(createWindow)1

  • application 준비 완료 되었을 때 실행할 함수

5초 뒤 메세지 출력

  • main.ts 에 app.whenReady 함수 안에 코드 추가
    • 디버깅시 필요한 관리자 tools
    • 5초 뒤 이벤트 발생
app.whenReady().then(() => {
    createWindow();
    if(!app.isPackaged){
        win?.webContents.openDevTools();
    }
  
  	// 5초 후에 "greet" 이벤트를 발생시키면서 renderer 에 인삿말 전달하기
    setTimeout(() => {
        win?.webContents.send("greet", "즐거운 하루 시작 React 공부 시작");
    }, 5000);
})
  • App.tsx 에 type 코드 추가
    • electron 의 개발환경은 typescript 이다
type MemberDto = {
    num : number;
    name : string;
    addr : string;
};
  • App.tsx 에 inteface 코드 추가
    • type 작성 문법과 다르지만 거의 유사하게 동작한다
interface Member{
    num : number;
    name : string;
    addr : string;
}
  • type을 사용한 상수 m1 코드 추가
const m1:MemberDto = {num:1, name:"유재석", addr:"압구정"};
console.log(m1);
  • interface 사용한 상수 m2 코드 추가
const m2:Member = {num:2, name:"박명수", addr:"이태원"};
console.log(m2);

  • useEffect 함수 추가
    • window apigreet 이라는 이벤트가 발생하면 실행할 함수 등록
useEffect(() => {
  	window.api.onGreet(()=>{});
}, []);
  • ApiType 타입 선언 코드 추가
interface ApiType{
    onGreet:(callback:(msg:string)=>void)=>void
}
  • declare 코드 추가
    • 전역으로 Window 인터페이스를 정의해서 window.api 를 사용할 수 있도록 한다
declare global{
    interface Window{
        api:ApiType
    }
}

onGreet:() => void : object 를 만드는 걸까? 아니면 type 을 선언하는 걸까?

  • 함수 type 을 선언

  • 메세지 관련 useState 함수 추가
const [msg, setMsg] = useState("");
  • useEffect 함수 안에 setMsg 함수 사용
    • msg 에는 main 프로세스에서 전달한 메세지가 들어 있다
useEffect(() => {
	window.api.onGreet((msg)=>{
		setMsg(msg);
    });
}, []);
  • 메시지를 출력할 p 요소 추가
<p>main 프로세스에서 전달한 메세지 : <strong>{msg}</strong></p>
  • electron 폴더 안에 preload.ts 코드 전체 수정
    • main 프로세스에서 발생하는 "greet" 이벤트에 귀를 기울이기
import { ipcRenderer, contextBridge } from 'electron'

// --------- Expose some API to the Renderer process ---------
contextBridge.exposeInMainWorld('api', {

    onGreet:(callback:(msg:string)=>void)=>{
        console.log("preload.ts 에 onGreet() 함수가 호출됨");
        
        ipcRenderer.on("greet", (_event, msg)=> {
            console.log("ipcRenderer 에 greet 이벤트가 발생함");
            callback(msg);
        });
    }
})

Electron 앱 구조

  1. main.ts (Main Process)
  • 앱 실행 후 창을 생성
  • React 의 앱을 띄우거 preload.ts 를 설정해 Renderer Process와 연결
  1. preload.ts (Preload Script)
  • Main Process 와 Renderer Process 간에 안전하게 데이터를 주고받을 수 있게 도와주는 브릿지 역할
  1. App.tsx (Renderer Process)
  • 실제 UI를 그리는 곳
  • preload.ts 에서 제공하는 기능을 사용해 Main Process와 소통

Main과 Renderer 프로세스 간 데이터 전달 및 처리

preload.ts

  • renderer process 에서 실행되는 chromium javascript 실행 환경에서 여기서 설정된 함수를 호출하거나, 함수를 호출하면서 데이터를 전달하거나 여기서 리턴한 데이터를 받아갈 수 있다
.exposeInMainWorld("api", {
	함수명1 : 함수,
	함수명2 : 함수,
	.
	.
	이런 구조로 함수를 계속 추가할 수 있다
});
  • chromium 에서 이런 구조로 사용할 수 있다
window.api.함수명1(전달한 데이터);
  • preload.ts 에 contextBridge.exposeInMainWorld 함수 안에 toMain 함수 코드 추가
    • main 프로세스에 "from-react" 라는 이벤트를 발생시키면서 문자열 전달하기
toMain:(msg:string)=>{
  	ipcRenderer.send("from-react", msg);
}
  • .eslintrc.cjs 에 사용하지 않은 변수나 매개변수의 빨간 에러 표시를 없애주는 코드 추가
'pretter/pretter':[]
  • main.ts 에 ipcMain 코드 추가
    • renderer 프로세스에서 발생하는 "from-react" 이벤트에 귀를 기울이기
    • ipc : 프로세스와의 통신
ipcMain.on("from-react", (_event, msg:string)=>{
  	console.log(msg); 
});

➜ App을 실행하는 터미널에 해당 콘솔이 출력되야 한다

  • App.tsx 에 입력 텍스트 박스와 전송 버튼 추가
<input type="text" placeholder="main 프로세스에 할 말 입력..."/>
<button>전송</button>
  • handleSend 함수 추가
const handleSend = () => {

};
  • 입력 텍스트 박스에 ref 속성 추가
<input ref={inputRef} type="text" placeholder="main 프로세스에 할 말 입력..."/>
  • 전송 버튼에 onClick 이벤트 추가
<button onClick={handleSend}>전송</button>
  • useRef 함수 추가
const inputRef = useRef<HTMLInputElement>(null);
  • ApiType 인터페이스 안에 toMain 타입 추가
    • ApiType : api 에서 호출할 함수의 모양을 미리 정의해 놓고 밑에서 사용
toMain:(msg:string)=>void;
  • handleSend 함수 안에 입력한 문자열에 대한 처리 코드 추가
// 입력한 문자열 읽어오기
const msg = inputRef.current!.value;
// api 에서 제공하는 함수를 호출하면서 전달하기
window.api.toMain(msg);

main(ipcMain) 와 renderer(ipcRenderer)

  • onhandle

  • sendinvoke
    ➜ invoke 는 Promise 를 리턴

  • main.ts 에 whenReady 함수 안에 코드 3줄 주석 처리

// if(!app.isPackaged){
//     win?.webContents.openDevTools();
// }
  • main.ts 에 browser-window-focus 함수 추가
    • window 창에 포커스가 왔을 때 실행할 함수
app.on("browser-window-focus", ()=>{
	// ctrl + t 를 눌렀을 때 개발 tool 이 열리도록 한다
    globalShortcut.register("Control+t", ()=>{
        win?.webContents.openDevTools();
    })
});

➜ ctrl + t 를 해야 개발 창이

  • browser-window-blur 함수 추가
    • window 창에 포커스를 잃었을 때 실행할 함수
app.on("browser-window-blur", ()=>{
	globalShortcut.unregisterAll();
});
  • will-quit 함수 추가
    • 앱이 종료되기 직전에 실행할 함수
app.on("will-quit", ()=>{
	globalShortcut.unregisterAll();
});

버튼 클릭 시 메세지 출력

  • App.tsx 에 1번 회원의 정보만 받아오는 버튼 추가
<button>1번 회원의 정보 받아오기</button>
  • handleClick 함수 추가
const handleClick = (num:number) => {

};
  • 버튼에 onClick 이벤트 추가
<button onClick={() => handleClick(1)}>1번 회원의 정보 받아오기</button>
  • handleClick 함수에 async, await 코드 추가 및 Member 테이블의 값을 가져올 코드 추가
    • getMember 의 type : (num:number) => Promise<Member>
const handleClick = async (num:number) => {
	// api 의 getMember() 함수는 Promise<Member> 를 리턴하는 함수여야 한다
	const m:Member = await window.api.getMember(num);
};
  • ApiType 함수 안에 getMember 타입 선언
getMember:(num:number)=>Promise<Member>;
  • preload.ts 에 contextBridge.exposeInMainWorld 함수 안에 getMember 함수 추가
    • .invoke() 함수는 Promise 리턴한다
    • main 프로세스에서는 .invoke() 에 대해서 .handle() 로 처리한다
getMember:(num:number)=>{
	return ipcRenderer.invoke("get-member", num);
}
  • electron 폴더 안에 types 폴더 생성 후 그 안에 type.ts 생성
    • Member 라는 인터페이스 타입을 선언
export interface Member{
    num : number;
    name : string;
    addr : string;
};
  • main.ts 에 whenReady 함수 안에 ipcMain 코드 추가
    • DB, 혹은 api 서버에서 읽어온 회원 정보라고 가정
    • 해당 정보를 리턴해주면 내부적으로 자동으로 Promiseresolve 된다
import { Member } from './types/type'
ipcMain.handle("get-member", (_event, num:number)=>{
    const m1:Member = {num:num, name:"유재석", addr:"압구정"};
    return m1;
});
  • App.tsx 에 useState 함수 추가
    • Member를 상태값으로 관리
const [dto, setDto] =useState<Member>();
  • handleClick 함수 안에 setDto 함수 사용
setDto(m);
  • 해당 Member의 정보를 출력할 p 요소 추가
    • dto 의 초기값을 부여하지 않았기에 undefined 의 가능성이 존재하기에 ? 를 붙여준다
<p>번호 : <strong>{dto?.num}</strong></p>
<p>이름 : <strong>{dto?.name}</strong></p>
<p>주소 : <strong>{dto?.addr}</strong></p>

음식 목록 받아오기 실습

  • App.tsx 에 버튼과 해당 목록 출력하는 ul 코드 추가
<button>좋아하는 음식 목록 받아오기</button>
<ul>

</ul>
  • ApiType 인터페이스 안에 getFoods 타입 선언
getFoods:()=>Promise<string[]>;
  • preload.ts 에 contextBridge.exposeInMainWorld 함수 안에 getFoods 함수 추가
getFoods:() => ipcRenderer.invoke("get-foods")
  • main.ts 에 whenReady 함수 안에 icpMain.handle 함수 추가
    • DB 혹은 api 서버로부터 읽어온 음식 목록이라고 가정
ipcMain.handle("get-foods", (_event)=>{
    const foods:string[] = ["김밥", "떡볶이", "튀김"];
    return foods;
});
  • 음식을 상태값으로 관리하는 useState 함수 추가
const [foods, setFoods] = useState<string[]>();
  • handleClick2 함수 추가
const handleClick2 = async () => {
    const foods = await window.api.getFoods();
    setFoods(foods);
}
  • 버튼에 onClick 이벤트 추가
<button onClick={handleClick2}>좋아하는 음식 목록 받아오기</button>
  • ul 에 음식 목록 출력
<ul>
  	{foods?.map((item, i) => <li key={i}>{item}</li>)}
</ul>

Electron 프로젝트 생성

  1. playground 폴더 안에 electron_js 폴더 생성

  2. 해당 폴더에서 cmd 실행

npm create electron-vite@latest
  1. 프로젝트명 : memo-app
Project name: » memo-app
  1. 탬플릿 : React
Project template: » React
  1. memo-app 으로 위치 이동
cd memo-app
  1. 의존 dependency 설치
npm install
  1. 서버 실행
npm run dev

  1. VS CODE 에서 memo-app 으로 폴더 실행

  • package.json 에 dependencies 안에 코드 추가
"@toast-ui/editor": "^3.2.2",
"axios": "^1.11.0",
"bootstrap": "^5.3.8",
"bootstrap-icons": "^1.13.1",
"classnames": "^2.5.1",
"jwt-decode": "^4.0.0",
"react-bootstrap": "^2.10.10",
"react-redux": "^9.2.0",
"react-router-dom": "^7.8.2",
"redux": "^5.0.1"
  • 터미널 실행해서 설치
    • 일괄 설치
npm install

메모장

  • App.tsx 에 div 요소 추가
function App() {

    return <>
        <div className="container-fluid">
            
        </div>
    </>
}
  • main.tsx 에 App.css import 코드 지우기

  • App.tsx 에 bootstrap 관련 import 코드 추가

import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-icons/font/bootstrap-icons.css'
  • .eslintrc.cjs 에 사용하지 않은 변수나 매개변수의 빨간 에러 표시를 없애주는 코드 추가
'pretter/pretter':[]
  • App.tsx 에 ApiType 인터페이스 및 declare 코드 추가
interface ApiType{

}

declare global{
    interface Window{
        api:ApiType
    }
}
  • 메모장으로 사용할 textarea 와 저장 버튼 추가
<h1>메모장</h1>
<textarea rows={10} className="form-control"></textarea>
<button className="btn btn-success mt-3">저장</button>

  • ApiType 에 saveMemo 라는 함수 타입 추가
    • 저장할 내용을 string 타입 문자열로 전달
saveMemo:(content:string)=>void
  • useRef 함수 추가
const textRef = useRef<HTMLTextAreaElement>(null);
  • handleSave 함수 추가
    • 저장 버튼을 눌렀을 때 실행할 함수
const handleSave = () => {

};
  • textarea 에 ref 속성 & 버튼에 onClick 이벤트 추가
<textarea ref={textRef} rows={10} className="form-control"></textarea>
<button onClick={handleSave} className="btn btn-success mt-3">저장</button>
  • handleSave 함수 안에 입력한 내용과 관련한 코드 추가
// 입력한 내용 읽어오기
const content = textRef.current!.value;
// api 의 함수 호출하면서 전달하기
window.api.saveMemo(content);
  • preload.ts 에 api 로 수정 & saveMemo 코드 추가
contextBridge.exposeInMainWorld('api', {
    saveMemo:(content:string)=>{
        // 매개변수로 전달된 저장할 문자열을 main 프로세스에 전달한다
        ipcRenderer.send("save-memo", content);
    }
})

window.api.saveMemo() 가 renderer에서 호출되면?
➤ 그 내부에서 content를 main 프로세스에 전달하는 구조
➤ content는 ipcRenderer.send()에 의해 main 프로세스로 전송

  • main.ts 에 icpMain 코드 추가
ipcMain.on("save-memo", (_event, content:string)=>{
    console.log("save-memo!");
    // node.js 에서 __dirname 은 위에 코드에서 얻어낸 현재 실행 경로 이다
    console.log(__dirname);
});

  • main.ts 에 fs import
    • node js 에서 fileSystem 에 관련된 자겁을 할 moduel import
import fs from 'node:fs'

main.ts 에 save-memo 이벤트에 입력한 내용 파일에 저장하는 코드 추가

  • 파일을 저장할 경로 구성
const filePath = path.join(__dirname, '/files/myMemo.txt');
  • fs 객체를 이용해서 파일을 먼저 만들기
    • recursive:true 는 해당 겨올의 폴더가 존재하지 않으면 직접 만든다
fs.mkdirSync(path.dirname(filePath), {recursive:true});
  • 파일을 문자열에 출력하기
    • .writeFileSync(경로 내용, 인코딩);
fs.writeFileSync(filePath, content, "utf-8");
  • 알림 띄우기
    • .showMessageBox(알림을 띄울 window 객체, 옵션)
dialog.showMessageBox(win!, {
    type:"info",
    buttons:["확인", "취소"],
    title:"알림",
    message:"저장 완료"
});

profile
학원 공부 내용 정리

0개의 댓글