블로그

DK

워밍업 클럽 3주차 후기 BE

이번 주 공부한 내용은 크게 JPA 사용법과 예제 적용으로, 과제를 통해 JPA의 숙련도를 익히는 과정을 거쳤습니다.Entity를 활용해 어노테이션이 붙은 파일은 DB의 컬럼과 1:1로 매칭됩니다.위와 같이 Entity를 만들고 JpaRepository를 상속받는 인터페이스와 연결하면 쿼리를 직접 String으로 작성하지 않아도 DB에 접근할 수 있습니다.예) findAllByAgeBetween(int startAge, int endAge);이는 SELECT * FROM user WHERE age BETWEEN ? AND ?;와 같습니다.이처럼 카멜케이스의 각 단어가 쿼리로 변환되어 기존의 JdbcRepository 역할을 대체하는 점이 마법처럼 느껴졌습니다. 또한, 코드를 작성하는 것이 더 수월하고 효율적이라는 생각이 들었습니다.하지만 이번 주 학습 내용이 저에게는 가장 어려웠습니다. 도메인 클래스의 정확한 용도를 알아야 했고, JPA 방식으로 변환하면서 기존의 예제 코드가 많이 대체되어 처음 강의를 볼 때는 이해가 잘 되지 않는 부분이 많았습니다. Entity 클래스를 만들고 repository 인터페이스를 통해 함수를 만드는 것이 상당히 생소하게 느껴졌습니다.그러나 과제를 수행하며 JPA를 적용하고 요구사항을 추가해가면서 자연스럽게 이해할 수 있었습니다.다만, 과제를 만드는 과정에서 카멜케이스로 만든 클래스명과 컬럼명이 Entity 클래스와 매칭될 때 스네이크 케이스로 인식되어 오류가 발생하거나, yml 파일에서 컬럼명을 만들 때 컬럼명이 스네이크 케이스로 만들어져 업데이트되는 문제 때문에 반나절을 허비한 것이 개인적으로 아쉬웠습니다.드디어 JPA를 사용할 수 있게 되면서, Jdbc로 만들었던 코드를 JPA로 바꾸는 과정을 통해 나도 서버를 만들 수 있다는 자신감을 가질 수 있었습니다.

백엔드

DK 4일 전
코다

[인프런 워밍업 클럽 1기 FE] 3주차 발자국

3주차 발자국📍2024.05.13 ~ 2924.05.16🏁 3주차 미션😎 강의 수료강의 요약#6 React TDD 기본TDD (Test Driven Development) 란 테스트 코드를 먼저 작성하고, 테스트 코드를 Pass 할 수 있는 실제 코드를 작성하는 개발 방법이다. 안정적인 소스 코드, 디버깅 시간 및 개발 시간 감소, 깨끗한 코드의 장점이 있다.React Testing Library리액트 컴포넌트에 대한 테스트를 사용자 중심으로 작성하는데 도움을 주며 사용자의 행동에 대한 테스트를 강조하는 라이브러리이다. 리액트 구성 요소 작업을 위한 API 를 추가하여 DOM Testing Library 위에 구축된다. DOM Testing Library 는 DOM 노드를 테스트하기 위한 매우 가벼운 솔루션이다.render 함수 : DOM 에 컴포넌트를 렌더링 하는 함수이며 인자로 렌더링할 리액트 컴포넌트를 사용한다.쿼리 함수 : 페이지에서 요소를 찾기 위해 사용한다. get, find, query 유형이 있다.getBy : 쿼리에 대해 일치하는 노드를 반환하고 일치하는 요소가 없거나 둘 이상 일치하면 오류를 발생한다queryBy : 쿼리에 대해 일치하는 노드를 반환하고 일치하는 요소가 없으면 null 을 반환한다.findBy : 쿼리와 일치하는 요소가 발견되면 Promise 를 반환한다. 요소가 발견되지 않거나 기본 제한 시간 후에 둘 이상의 요소가 발견되면 프로미스가 거부된다. ( getBy + waitFor = findBy )waitFor : 일정 기간 동안 기다려야 할 때 사용하여 기대가 통과될 때까지 기다릴 수 있다.JestJest 는 Facebook 에 의해 만들어진 테스팅 프레임워크이다. 최소한의 설정으로 동작하며 테스트 케이스로 어플리케이션 코드를 확인한다. 단위테스트를 위해 이용한다.describe : 테스트를 그룹화하는 블록을 만든다it : 개별 테스트를 수행하며 각 테스트를 작은 문장처럼 설명한다.expect : 값을 테스트할 때마다 사용되며 matcher 와 함께 사용된다.matcher : 다른 방법으로 값을 테스트 하도록 할 때 사용된다.코드 형식 라이브러리Prettier : 코드 형식을 맞추는데 사용하며 코드 포맷터 역할을 한다.ESLint : 개발자들이 특정 규칙을 가지고 코드를 짤 수 있게 도와주는 라이브러리이며 코드 포맷터이기도 하지만 문법 오류를 파악하는 것이 주요 기능이다.#7 React TDD 를 이용한 간단한 앱 생성 및 배포FireEvent API : 유저가 발생시키는 이벤트에 대한 테스트를 하는 경우 사용한다.test('테스트 설명', () => { render(<App />); // 테스트를 위해 App 렌더링 const buttonElement = screen.getByTestId('plus-button'); fireEvent.click(buttonElemtn); // buttonElement 에 클릭 이벤트 발생 const counterElement = screen.getByTestId('counter'); expect(counterElement).toHaveTextContent(1); // counterElement 의 텍스트가 1 });Github Action 을 이용한 AWS S3 로 앱 자동 배포Jenkins, Circle CI, Travis CI, Github 등이 배포에 쓰인다. 강의에서는 Github 을 사용한다.순서저장소 생성 - 프로젝트와 연결 - Github 페이지에서 workflow (Node.js) 생성 - S3 버킷 생성 - 버킷 설정 변경 (정적 웹 사이트 호스팅 활성화) - 버킷 정책 변경 - Github workflow yml 파일에 앱 자동 배포 코드 추가 - IAM 사용자 추가 - Github 저장소 env 설정에 IAM 키 추가#8 Next.js 와 TypescriptNext.js리액트의 SSR (Server Side Rendering) 을 쉽게 구현할 수 있게 돕는 프레임워크이다.CSR 은 검색엔진에 최적화되어있지 않기 때문에 SSR 을 사용한다.Next.js 는 모든 페이지를 pre-render 하여 HTML 을 클라이언트에서 자바스크립트로 처리하기 전에 생성함으로써 SEO 검색엔진 최적화가 좋아진다.Next.js 에서 데이터를 가져오는 방법getStaticProps : Static Generation 으로 빌드할 때 데이터를 불러온다.페이지 렌더링 시 사용자 요청보다 먼저 필요한 데이터를 가져올 수 있을 때, 데이터를 headless CMS 에서 가져올 때, 모든 사용자에게 같은 데이터를 보여줄 때, 페이지가 미리 렌더링 되어야 하고 빨라야 할 때 사용한다.getStaticPaths : Static Generation 으로 데이터에 기반하여 pre-render 시에 특정한 동적 라우팅을 구현한다.path 는 어떤 경로가 pre-render 될 지 결정한다. params 는 페이지 이름이 pages/posts/[postId]/[commentId] 인 경우 postId, commentId 이다. fallback 이 false 이면 getStaticPaths 로 리턴되지 않는 것은 모두 404 페이지로 이동되며 true 인 경우는 fallback 페이지를 보여준다.getServerSideProps : SSR 로 요청이 있을 때 데이터를 불러온다.async 로 export 하면 Next.js 는 각 요청마다 리턴되는 데이터를 getServerSideProps 로 pre-render 한다. 따라서 다른 페이지로 이동했다가 이전 페이지로 돌아가도 최신 데이터가 반영된다. 요청할 때 데이터를 가져와야 하는 페이지를 미리 렌더링 할 때 사용한다.Static Site Generation (SSG) : 빌드 때 HTML 을 각 페이지별로 서버에 생성해 놓고 요청 시에 HTML 을 반환한다.TypeScript자바스크립트에 타입을 부여한 코드를 단순화하고 쉽게 디버그 할 수 있으며 타입 검사 및 컴파일 오류 검사의 기능을 수행하는 언어이다. 타입스크립트는 자바스크립트와 달리 브라우저 실행 시 파일을 한 번 변환하는 컴파일 과정을 거친다.타입스크립트의 타입은 자바스크립트의 타입들과 추가로 tuple, Enum, Any, Void, Never, Union 타입이 있다.any : 잘 알지 못하는 타입을 표현하며, 컴파일 때 타입 검사를 거치지 않는다. noImplicitAny 라는 옵션을 사용하면 any 타입을 썼을 때 오류를 발생시킬 수 있다.union : 둘 이상의 데이터 유형을 사용할 때 유니온 타입이라고 한다. ex. let code : (string | number);tuple : 지정한 순서와 타입만을 사용할 수 있는 특수한 형태의 배열 타입이다.enum : 열거형을 의미하며 별도의 값을 설정하지 않으면 0부터 시작한다.void : 데이터가 없고 타입이 없는 상태이다. any 와 반대의 이미이다.never : 절대 일어나지 않을 것이라고 확신할 때 사용하며 주로 함수의 리턴 타입으로 사용된다. 항상 오류를 리턴하거나 리턴 값을 절대로 내보내지 않음을 의미한다.void vs never : void 는 값으로 undefined, null 을 가질 수 있으나 never 는 어떠한 값도 가질 수 없다.type annotation : 개발자가 타입을 타입스크립트에게 직접 전달한다.type inference : 타입스크립트가 알아서 타입을 추론한다.type assertion : 값의 타입을 설정하고 컴파일러에게 유추하지 않도록 지시한다.#9 React Version 18Automatic batchingbatching 은 업데이트 대상이 되는 상태값들을 하나의 그룹으로 묶어 한 번의 리렌더링으로 모든 업데이트가 진행될 수 있게 하는 것을 말한다.적은 리렌더링, 이벤트 핸들러 밖에서도 작동하며 필요할 때 제외 가능한 것이 특징이다.Suspense on the server<Suspense /> 를 사용하여 앱을 더 작은 독립 단위로 나누어 앱 사용자가 콘텐츠를 빨리 보고 빠르게 상호작용할 수 있다.Streaming HTML : 모든 데이터를 가져오기 전에 renderToString 대신 renderToPipeableStream 을 사용하여 HTML 을 스트리밍하는 방법이다.Selective Hydration : 모든 코드가 로드되기 전에 hydrate 하는 방법이다.hydration : 자바스크립트 논리를 전체 앱에 대해 서버 생성된 HTML 에 연결하는 것이다.Transition업데이트에 urgent 를 주어 상태 업데이트 시에 우선순위를 주게 하는 기능이다.#10 Redux자바스크립트 애플리케이션을 위한 상태 관리 라이브러리이다. Redux 는 State 를 관리한다.Action -> Reducer -> Redux Store -> React Component -> (이벤트가 발생하면) -> ActionProvider : Redux Store 에 접근해야 하는 모든 중첩 구성 요소에서 Redux Store 를 사용할 수 있도록 한다.useSelector, useDispatch 를 이용해 provider 로 둘러싸인 컴포넌트에서 store 에 접근 가능하다.리덕스 미들웨어: 액션을 전달하고 리듀서에 도달하는 순간 사전에 지정된 작업을 실행할 수 있게 하는 중간자Redux Thunk : 리덕스를 사용하며 비동기 작업을 할 때 많이 사용한다.Redux Toolkit : 리덕스 로직을 작성하기 위한 공식 권장 접근 방식이다.Redux Persist : 페이지 새로고침 후에도 Redux Store 의 State 들을 유지할 수 있다.#11 도커를 이용한 리액트 실행Docker컨테이너를 사용하여 응용 프로그램을 쉽게 만들고 배포, 실행할 수 있는 컨테이너 기반의 오픈소스 가상화 플랫폼이다. 프로그램 다운로드 과정을 간단하게 만들기 위해 사용한다.컨테이너 안에 다양한 프로그램, 실행환경을 추상화하고 동일한 인터페이스를 제공해 프로그램의 배포 및 관리를 단순하게 하고 어디서든 실행 가능하게 한다.도커 클라이언트에서 커맨드 실행 - 도커 서버에서 이미지가 로컬에 캐시 되어있는지 확인 - 캐시된 이미지가 없는 경우 도커 허브에서 이미지를 가져와 로컬 캐시에 저장한다 - 이미지로 컨테이너를 생성한다 - 이미지로 생성된 컨테이너는 이미지에서 받은 설정이나 조건에 따라 프로그램을 실행한다.도커 파일, 이미지도커 파일 생성 순서 : 베이스 이미지 명시 - 추가로 필요한 파일 다운을 위한 명령어 명시 - 컨테이너 시작 시 실행될 명령어 명시베이스 이미지는 이미지의 기반으로 간단하게 OS 라고 생각하면 된다.이미지는 응용 프로그램 실행 시 필요한 모든 것 (실행 명령어, 파일 스냅샷)을 포함하고 있다.파일 스냅샷은 디렉토리나 파일을 카피한 것이다.도커 이미지 생성 순서 : 도커 파일 작성 - 도커 빌드로 도커 파일의 명령어들이 도커 클라이언트에 전달 - 도커 서버 - 이미지 생성포트 매핑이미지를 만들 때 로컬의 파일을 컨테이너에 복사하듯이 네트워크도 로컬 네트워크와 컨테이너 내부의 네트워크를 연결해줘야 한다. -p 키워드로 매핑을 할 수 있다. ex docker run -p 3000 : 3000 이미지 이름 -> 3000번 포트끼리 연결함도커 컴포즈 파일을 작성하면 도커 실행 시에 긴 명령어를 간단하게 만들 수 있다. 느낀점TDD 와 배포, 타입스크립트, 도커까지 배웠다. 테스트 코드를 직접 작성해보니 신기했다. 테스트를 용이하게 하기 위해 함수와 컴포넌트를 작게 만들어야 한다는 것을 확실히 깨달았다. 범위가 크면 테스트 코드 작성하기가 매우 복잡할 것 같다. 이전에 배포는 해본 적이 없어서 S3 에 코드 배포하는 것이 가장 흥미로웠다. 기존에 자바스크립트로 짰던 코드를 리팩토링 해볼 예정이다. 수업을 들을 때는 간단했는데 직접 하려고 했더니 많이 헷갈렸다. 시간이 부족해서 과제를 원하는 만큼 구현하지 못한 것 같아 아쉽다. 워밍업 클럽을 수료하고 나서도 남은 과제를 풀어야겠다.

프론트엔드인프런워밍업클럽

슬프구나

인프런 워밍업 클럽 스터디 1기 FE - 3주차 발자국

FE 강의 진도율 100% 달성자바스크립트 과제 완료음식 메뉴 앱가위 바위 보 앱퀴즈 앱책 리스트 나열 앱Github Finder 앱비밀번호 생성 앱타이핑 테스트 앱리액트 과제 완료예산 계산기 앱디즈니 플러스 앱포켓몬 도감 앱리덕스를 이용한 쇼핑몰 앱(Nextjs 사용) 드디어, 3주가 다 지나갔다.강의는 라디오를 청취하듯이 듣고, 과제를 진행하면서 막히는 경우에 재청취 하였다.자바스크립트 강의는 총 12시간, 리액트 강의는 총 18시간 분량이었는데 청취하면서 사실 쉽지는 않았다. 아는 내용은 가볍게 넘기기도 하였다. 개인적으로 좋았던 점은 강의에 대부분의 내용이 담겨있어서 복습하기 좋았다. 그리고 리액트 강의에서는 React v18 에 추가된 부분도 있고 Docker 를 사용하여 컨테이너 기반으로 리액트 프로젝트를 해보는 강의도 있어서 좋았다.React v18 에 제일 큰 변화는 향상된 automation batching 과 Suspense 가 SSR도 지원하여 Streaming HTML 이 아닐까 생각한다. SSR은 데이터가 다 준비가 되고 렌더링이 될 때까지 기다려야 한다. 하지만 리액트팀은 이러한 부분을 개선하여 UX를 향상 시켰다. 해당 스터디를 참여를 해야하나 조금 고민을 했었다. 혼자서 공부가 가능한 내용들이기 때문이었는데 결론적으로는 참여하길 잘 했다고 생각한다. 반 강제적이긴 하지만 과제들을 하면서 복기를 할 수 있었고 강의를 들으면서 내용을 다시 한번 흩어볼 수 있어서 유익한 시간이었다. 리액트 과제같은 경우에는 전부 TS 를 기반으로 작성 하였다. JS를 활용한 프론트엔드 또는 백엔드 개발에서 TS는 사실 이제 필수라고 생각한다. JS 자료형이 외부 도구에 의존을 해야 할 만큼 나쁜가? 라고 생각을 한다면 꼭 나쁘다고 단정 지을 수는 없다. 아무래도 동적 타이핑에서 발생하는 문제를 해결해주는 가치가 크기 때문이 아닐까 생각한다.동적 타이핑은 값이 할당 된 순간 타입이 결정이 되므로, 타입 관련 에러가 런타임 환경에 발생할 수 있다.동적 타이핑은 값이 할당 된 순간 타입이 결정이 되므로, 타입 관련 에러가 발생 했을 때 규모가 큰 프로젝트에서 이를 추적하기 어렵거나 예측하기 어렵다.위 문제를 해결하고자 우리는 TS 를 사용한다. 조기에 더 많은 오류를 포착해주기 때문이다. 그리고 정적 타이핑을 통한 코드 가독성이 향상 된다.(어떤 함수를 호출한다고 가정하면, 해당 인자가 어떤 타입인지 추론이 가능하기 때문이다)TS 를 사용함으로써 제일 큰 이점은 리팩토링이다. 예를 들어, API Response 명세가 바뀌었다고 가정하자. 프론트엔드단에서 이 명세에 맞게 모델을 바꿔야 할 거다. 해당 모델에 대한 타입이 정의가 되어있으므로 우리는 자신있게 이 부분을 리팩토링 할 수 있다.(틀리면 바로 에러로 알려주기 때문이다.)만약, 그냥 JS 환경이라면 실행이 잘 될수도 있고 놓친 부분이 있다면 런타임에서 에러가 발견이 될 거다. Angular 는 애초에 First party 로 TS 를 사용해 왔다.Vue, React 는 이후에 지원을 하기 시작했다. 이런거 보면, Angular 를 관리하는 구글이 선견지명이 있는거같다. 그렇지만, TS도 만능은 아니다.TSC 를 통해 나온 컴파일 결과물인 js 파일들을 확인해보면, TS 관련 코드들은 전부 사라져 있다. 결국에는 완벽하게 런타임 오류를 완전히 방지하지는 못한다. 그리고 TS를 사용하면 코드량이 증가하며 학습 비용이 발생하므로 이 부분도 프로젝트 규모의 따라 고민을 해보면 좋을 것 같다.(킹치만, 사용 안하면 너무 불안한걸?)

프론트엔드인프런워밍업클럽FE1기회고

강호연

[인프런 워밍업 스터디 클럽 1기_FE] 3주차 발자국

1. 강의 관련지난주에 다 들음2. 미션 관련 각 프로젝트별 상세는 각 게시물에서 확인바람.미션 3 포켓몬 도감 앱Github :https://github.com/KimPra2989/inflearn-warming-up-missions/tree/main/React/%EB%AF%B8%EC%85%98%203%20%ED%8F%AC%EC%BC%93%EB%AA%AC%20%EB%8F%84%EA%B0%90%20%EC%95%B1 인게임 느낌의 포켓몬 도감으로 디자인수정함방향키로 위아래 가능밑에서 15마리 정도 될 때 추가 데이터 페칭 (무한 스크롤)차트로 스텟 상태 나타냄상세페이지의 반짝이 버튼 누르면 사진이 이로치로 바뀜 (영상 안 찍었네;;)데이터 한글화함미션 4 퀴즈 앱Githubhttps://github.com/KimPra2989/inflearn-warming-up-missions/tree/main/React/%EB%AF%B8%EC%85%98%204%20%ED%80%B4%EC%A6%88%20%EC%95%B1JS 3미션 퀴즈 앱을 리액트로 마이그래이션했다. 견본 영상의 제목이 'Next로 만드는 퀴즈앱'이어서 next로 만들어야하나 고민을 했지만 next의 이점인, ssr이나, SEO, 이미지최적화 등 여러요소들이 굳이 필요하지 않다고 생각해서 react로 진행했다. 대부분 이전에 만든 프로젝트에서 갖고온 공통 컴포넌트와 로직을 재활용한 파트여서 그다지 어려운 점은 없었다. 회고저번주에 강의도 다 들었겠다 프로젝트에 몰입할 수 있다는 생각과 함께 자신감 넘치는 막주가 시작되....ㄹ 줄 알았으나 금요일에 시작된 그룹 프로젝트에 압도적 주니어로 들어가게 되어 과제를 더 이상할 수가 없었다....과제 3개 채우기용으로 가장 쉬운 퀴즈앱을 고르고, 완성도를 높이려했던 포켓몬 도감도 흐지부지 끝나고야 말았다. 태초의 목표가 다양한 프로젝트에 도전하는 것이었는데 그 부분은 어느정도 해결이 된 것 같아서 성취감이 없지는 않다. 다만, 깊이 있게 뭘 만들어보질 못했던 것이 후회로 남는다. 아쉽지만 프로젝트 때문에 이 정도만 쓰고 간다.ps. 디스코드 보니 중간 부터 과제결과물이 안 올라오는 것 같던데 사실 다 탈주하고 몇명만 남아있는 개꿀잼 몰카인가?... 재미없으니 다들 돌아왔으면 좋겠다.

구르밍

인프런 워밍업 클럽 BE 1기 - 3주차 발자국

조금 더 객체지향적으로 개발할 수 없을까?이전에는 User를 따로 가져와 BookService에서 UserLoanHistory를 만들어 저장하였다.조금 더 객체지향적으로 바꾸면 UserLoanHistory를 User에서 가져와 바로 대출을 처리하자!@ManyToOne : 내가 다수이고 너가 한개대출기록은 여러개이고 그 대출을 소유하고 있는 사용자는 한명이다 → N : 1 관계//private long userId; @ManyToOne private User user;@OneToMany : 나는 한개 너가 다수@OneToMany privateList<UserLoanHistory> userLoanHistoryList = new ArrayList<>();여러개의 대출기록 N개이기 때문에 List로 표현이로써 User와 UserLoanHistory는 서로 연관관계가 되었다. 그 중에 주인은 누구인가?누가 관계의 주도권을 가지고 있는가?현재는 user_loan_history가 user_id를 DB 컬럼으로 가지고 있기 때문에 주인이다.연관관계의 주인이 아닌 쪽에 mappedBy 옵션을 달아 주어야 한다.@OneToMany(mappedBy = "user") privateList<UserLoanHistory> userLoanHistoryList = new ArrayList<>(); 1:1 관계한 사람은 한 개의 실거주 주소 만을 가지고 있다.person 테이블이 address 테이블의 id를 가질 수도 있고, address테이블이 person 테이블의 id를 가질 수도 있다.@Entity public class Person{ ... @OneToOne private Address address; }@Entity public class Address{ ... @OneToOne private Person person; }이렇게 테이블을 생성한다고 가정할때 Person이 address id를 가지고 있다.create table person ( id bigint auto_increment, name varchar(255), address_id bigint, primary key (id) );create table address ( id bigint auto_increment, city varchar(255), street varchar(255), primary key (id) );→ Person이 address 주인이다! 1:1 관계지만..따라서 mappedBy 작성@Entity public class Address{ ... @OneToOne(mappedBy = "address") private Person person; }연관관계의 주인 효과 → 객체가 연결되는 기준이 된다.!@Transactional public void savePerson() { Person person = personRepository.save(new Person()); Address address = addressRepository.save(new Address()); person.setAddress(address); // setter는 임시 }person이 address의 주인이기때문에 이렇게 코드를 작성하고 실행하면 DB에서 정상으로 테이블이 연결된다. 하지만 반대로 1:1관계이더라도 address.setPerson(person);  이렇게 작성할 경우 DB에 저장되지 않는다. DB는 연결되었지만 !!! 객체끼리는 연결되지 않았다. address.getPerson(); --> 트랜잭션이 끝나기전에 get하면 Null 반환@Transactional public void savePerson() { Person person = personRepository.save(new Person()); Address address = addressRepository.save(new Address()); person.setAddress(address); // setter는 임시 address.getPerson(); --> 트랜잭션이 끝나기전에 get하면 Null 반환 }해결책으로 setter 한번에 둘을 같이 이어주면 된다.public void setAddress(Address address) { this.address = address; this.address.setPerson(this); --> 둘을 같이 이어주자 }N : 1 관계이 관계에서 주인은 무조건 숫자가 많은쪽이 주인이다.  @OneToMany를 작성하지 않고, @ManyToOne 하나만 작성해도 된다.(단방향)  @JoinColumn연관관계의 주인이 활용할 수 있는 어노테이션.필드의 이름이나 null 여부, 유일성 여부, 업데이트 여부 등을 지정@JoinColumn(nullable = false) @ManyToOne private User user; N : M 관계 - @ManyToMany학생과 동아리 관계를 생각하자학생은 여러 동아리를 가입할 수 있고, 한 동아리엔 여러 학생이 있다.→ But, 구조가 복잡하고, 테이블이 직관적으로 매핑되지 않아 사용하지 않는 것을 추천cascade 옵션 cascade : 폭포처럼 흐르다.  : 한 객체가 저장되거나 삭제될 때, 그 변경이 폭포처럼 플러 연결되어 있는 객체도 함께 저장되거나 삭제되는 기능유저가 삭제될때 유저가 연결되어 있는 UserLoanHistory도 삭제하고 싶을때 사용하면 된다.@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) privateList<UserLoanHistory> userLoanHistoryList = new ArrayList<>(); orphanRemoval 옵션: 객체간의 관계가 끊어진 데이터를 자동으로 제거하는 옵션관계가 끊어진 데이터 = orphan(고아) removal (제거)한 유저가 빌린 책1, 책2가 있다고 가정할 때, userLoanHistory에서 책1만 리스트(자바단)에서 지웠다. → DB는 아무런 변화가 없다. 이렇게 리스트에서 지우는(연결을 끊는 것 만으로도) 것으로도 DB에서 삭제가 되길바라면 이 옵션을 쓸 수 있다.@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) privateList<UserLoanHistory> userLoanHistoryList = new ArrayList<>(); @Transactional public void deleteUserHistory() { User user = userRepository.findByName("ABC") .orElseThrow(IllegalArgumentException::new); user.removeOneHistory(); } public void removeOneHistory() { userLoanHistories.removeIf(history -> "책1".equals(history.getBookName())); }  책 대출/반납 기능 리팩토링과 지연로딩User와 UserLoanHistory가 직접적으로 연결되어 있다.대출기능 리팩토링public void loanBook(String bookName) { this.userLoanHistories.add(new UserLoanHistory(this, bookName)); } User.java에서 새로운 UserLoanHistory객체를 만들고 넣어주기 때문에 service단에서는 loanBook메소드를 호출해주기만 하면 된다.@Transactional public void loanBook(BookLoanRequest request) throws IllegalAccessException { Book book = bookRespository.findByName(request.getBookName()) .orElseThrow(IllegalAccessException::new); if (userLoanHistoryRepository.existsByBookNameAndIsReturn(book.getName(), false)) { throw new IllegalAccessException("이미 대출중인 책입니다."); } User user = userRepository.findByName(request.getUserName()); if(user == null) { throw new IllegalAccessException("사용자를 찾을 수 없습니다."); } //userLoanHistoryRepository.save(new UserLoanHistory(user, book.getName())); user.loanBook(book.getName()); }반납기능 리팩토링public void returnBook(String bookName) throws IllegalAccessException { UserLoanHistory targetHistory = this.userLoanHistories.stream() .filter(history -> history.getBookName().equals(bookName)) .findFirst() .orElseThrow(IllegalAccessException::new); targetHistory.doReturn(); }함수형 프로그래밍을 할 수 있게 .stream을 작성해주고.filter를 통해 들어오는 객체들 중에 다음 조건을 충족하는 것만 필터링 한다  ..findFirst()를 통해 첫번째로 해당하는 UserLoanHistory를 찾는다.  이 결과는 Optional이기에 Optional을 제거하기 위해 없으면 예외를 던진다.  orElseThrow그렇게 찾은 UserLoanHistory를 반납처리 한다.@Transactional public void returnBook(BookReturnRequest request) throws IllegalAccessException { User user = userRepository.findByName(request.getUserName()); if(user == null) { throw new IllegalAccessException("사용자를 찾을 수 없습니다."); } // UserLoanHistory history = userLoanHistoryRepository.findByUserIdAndBookName(user.getId(), request.getBookName()) // .orElseThrow(IllegalAccessException::new); // history.doReturn(); user.returnBook(request.getBookName()); }userLoanHistoryRepository.findByUserIdAndBookName()은 사용할 일이 없어져서 삭제해었다.→ Domain 계층에 비즈니스 로직이 들어갔다.영속성 컨텍스트의 4번째 능력 - 지연 로딩 (Lazy Loading)예시 - User를 가져오는 부분과, 도메인 로직 실행 중간에 Print 출력을 해보자@Transactional public void returnBook(BookReturnRequest request) throws IllegalAccessException { User user = userRepository.findByName(request.getUserName()); if(user == null) { throw new IllegalAccessException("사용자를 찾을 수 없습니다."); } Systme.out.println("Hello"); user.returnBook(request.getBookName()); }실행결과를 확인하면 user를 가져오는 쿼리, hello 출력 후 userLoanHistory 쿼리를 출력하는 것을 확인할 수 있다. → 시작하자마다 유저와 대출기록을 다 가져올 수 있지만 그러지 않고, 꼭 필요한 순간에 데이터를 가져온다.  이러한 것을 지연 로딩이라 한다.  @OneToMany에 fetch 옵션에 기본 디폴트 값이다 LAZY만약 한번에 가지고 오고 싶다면 LAZY → EAGER를 사용하면 된다.@OneToMany(mappedBy = “user”, cascade = CadcadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)연관관계를 사용하면 무엇이 좋을까?각자의 역할에 집중하게 된다. 계층별로 응집성이 높아진다.새로운 개발자가 코드를 읽을 때 이해하기 쉬워진다. (모두 다 service단에 있다면 ?)테스트 코드 작성이 쉬워진다.연관관계를 사용하는 것이 항상 좋을까?지나치게 사용하면 성능상 문제가 생길 수 있고, 도메인 간의 복잡한 연결로 인해 시스템을 파악하기 어려워질 수 있다.너무 얽혀 있으면 A를 수정했을 때 , B ~ D까지 고쳐야할 수 있다.→ 여러부분을 생각해서 구조를 짜자!배포란 무엇인가현재 우리 컴퓨터에서만 작동할 수 있는 환경..배포란 : 최종 사용자에게 SW를 전달하는 과정 → 전용 컴퓨터에 우리의 서버를 옮겨 실행시키는 것전용 컴퓨터가 없는데..? → AWS를 빌리자!!AWS에서 컴퓨터를 빌릴 때 한 가지 알아두어야 할점!서버용 컴퓨터는 보통 리눅스를 사용한다.profile과 H2 DB똑같은 서버 코드를 실행시키지만, 우리 컴퓨터에서 실행할 때는 우리 컴퓨터의 MySQL을 사용하고,전용 컴퓨터에서 실행할 때는 전용 컴퓨터의 MySQL을 사용하고 싶다→ profile 개념 똑같은 서버 코드를 실행시키지만, 실행될 때 설정을 다르게 하고 싶을 때(DB 이외에도 다른 API, 다른 기능 등 ..)Profile을 적용해보자똑같은 서버 코드를 실행시키지만, local이라는 Profile을 입력하면, H2 DB를 사용하고 dev라는 profile을 입력하면 MySQL DB를 사용하게 바꾸자  H2 DB란 : 경량 Database로, 개발 단계에서 많이 사용하며 디스크가 아닌 메모리에 데이터를 저장할 수 있다.  메모리에 데이터를 저장하면 휘발되지만, 개발 단계에서 테이블이 계속 변경 되기 때문에 데이터가 휘발해야하고 ddl-auto 옵션을 create로 주면 테이블을 신경쓰지 않고 코드에만 집중할 수 있다.profile 설정나는 인텔리제이 유료버전이라 강사님 처럼 active profile 부분이 안뜨기 때문에 다른방법으로 진행하였다.-Dspring.profiles.active=locallocal 또는 dev로 입력해주면 된다. AWS의 EC2 사용하기AWS 가입 후 지역을 서울로 변경EC2인스턴스 - 내가 빌린 컴퓨터 (현재 0)인스턴스 시작 버튼 클릭인스턴스 유형은 컴퓨터의 사양t2.micro 선택 !회고벌써 워밍업도 마지막을 향해 달려가는 중이다!!7번째 과제에서 아직 나는 JPA에 익숙하지않아서 뭔가 직접 쿼리를 쓰는게 더 편했던 것 같다. @Query를 통해 직접 작성해주었다가 어? 이렇게하면 JPA에서 알아서 해주는구나 하고 여러번 바꾸기도 했다!근데 만약 쿼리에 조건이 너무 많을 경우에는 이름이 너무 길어지지 않을까라는 생각도 했었다 ㅎ..중간에 자꾸 테이블에 해당 컬럼이 없다고 오류가 나길래 .. 그럴리가 없는데라고 생각했지만 그럴리 있었다. 역시 컴퓨터는 거짓말을 하지 않는다 하하확실히 강의만 보는 것보다 직접 내가 생각하고 타이핑하는 것이 내가 어느정도까지 실제적으로 이해했는지 확인할 수 있는 길인 것 같다.앞으로 남은 마지막 미니 프로젝트까지 잘 마무리하면서 개념 부분을 더 공부해보자!

백엔드워밍업백엔드스프링

공부하자

[인프런 워밍업 클럽 1기 BE] 두번째 발자국

워밍업 회고록 2번쨰막연하게 알고 있던 ‘스프링빈’ 에대해서 공부할 수 있었던 주였다.먼저 스프링 빈이란?서버가 시작되면, 서버 내부에 거대한 컨테이너를 만든다. 그리고 만들어진 컨테이너 안으로 우리가 생성했던 클래스들이 들어가게 된다.이때 클래스들은 다양한 정보와 함께 컨테이너로 들어가며, 인스턴스화가 이루어진다.스프링 컨테이너 안으로 들어간 클래스를 SpringBean 이라고 한다.강의내용과 코드를 통해서@RestController 어노테이션을 이용하여 우리가 만들었던 컨트롤러를 스프링빈으로 손쉽게 등록할 수 있었다.(@RestController 이전까지 사용했던 JdbcTemplate은 JPA 의존성을 통해서 자동으로 스프링컨테이너에 등록되어 있었다.)이번 섹션을 JdbcTemplate에 의존하는 컨트롤러를 3가지 역할(Controller-Service-Repository)로 Layered Architecture 분리가 가능했다.JPA 이용SQL 문을 이용한 한계를 극복하기 위한 JPA에 대해 공부를 하였다.JPA 는 API 이기에 우리가 실제로 코드로 해당 규칙대로 동작 할 수 있게 작성해주어야 한다. 이런 JPA 에서 가장 유명한 프레임 워크가 Hibernate 이다. 출처: 서버올인원 강의 JPA를 이용하기 위해 먼저 @Entity 어노테이션을 이용하였다. 해당 어노테이션은 스프링을 User 클래스에 사용한다면 DB의 User 테이블을 같은 것으로 바라보할 수 있는 기능을 수행할 수 있게 된다.이런 JPA를 사용하여 이전에 작성했던 코드들을 전더 깔끔하게 분리가 가능하였다.첫주와 같이 역시 자율적 진도를 나가는것보다 스터디라는 강제성이 부여되어 정해진 분량을 차근차근나갈 수 있어 매우 효율적이였다.

강호연

[인프런 워밍업 스터디 클럽 1기_FE] 리액트 3번 미션

과제 git Hub링크 readMe에 더 자세히 적어둠 인게임 느낌의 포켓몬 도감으로 디자인 수정함방향키로 위아래 가능밑에서 15마리 정도 될 때 추가 데이터 페칭 (무한 스크롤)차트로 스텟 상태 나타냄상세페이지의 반짝이 버튼 누르면 사진이 이로치로 바뀜 (영상 안 찍었네;;)데이터 한글화함 스펙React, Vite, tsReact-queryEmotionRecharts  설명인게임 포켓몬 도감을 모티브로 구현을 해봤습니다.  프로젝트 주목표API를 분석하여 자원을 효율적으로 분배react-query의 useInfiniteQuery를 활용한 무한 스크롤 구현rechart 차트라이브러리를 활용한 데이터 시각화Emotion을 활용해 공통 컴포넌트를 스타일드 컴포넌트로 구현하여 활용성을 높임forwardRef를 활용해 부모요소에서 자식 요소의 스크롤 이벤트를 관리 프로젝트 목표 상세API를 분석하여 자원을 효율적으로 분배이번 미션에서 가장 오랜 시간 고민한 부분이 이 부분이다. 대부분 참가자는 무지성으로 pokeAPI에 /pokemon/id로 fetch를 보내서 이름을 받아오고, 반복문을 돌려 species에서 사진을 받아와서 메인페이지를 랜더링한 걸로 알고 있다. 결론부터 말해, 이 방식은 굉장히 비효율적이다. /pokemon/id는 건당 대략 13,000 줄의 응답이 오며, species 역시 1,300 줄 정도의 응답이 온다. 따라서, 이름이나 사진 하나 받기 위해 요청을 보내는 건 효율적이지 않다. api를 분석해본 결과 /pokemon 경로에 요청을 보내 이름만 받아오는 경우가 가장 효율적인 것을 알아냈다. 이는 20 마리의 포켓몬 이름에 대해 140 줄 정도의 간결한 응답이 오므로 위의 두 요청에 비해 굉장히 효율적이다. 또, 사진이나 포켓몬의 데이터를 찾아보면 꽤나 다양한 사이트들이 나오며, 데이터에 규칙성이 있어 외부 사이트에서 사진이나 데이터를 갖고 오는 것도 충분히 가능하다. react-query의 useInfiniteQuery를 활용한 무한 스크롤 구현useInfiniteQuery를 통해 구현했다. 페이지 당 포켓몬이 10마리 정도 등장하는 걸 감안하여 하단에서부터 15 마리 정도 위에서 추가 쿼리를 날리도록 제작했다. 쿼리의 트리거가 선택된 포켓몬이므로 스크롤을 직접 조작하는 경우에는 작동하지 않는다. (추후 수정)useEffect(() => { const handleFetchNextPage = () => { if (!pokemonList) return const idx = pokemonList.indexOf(selected) if (idx > pokemonList.length - 15) { fetchNextPage() } } //useInfiniteQuery handleFetchNextPage() }, [fetchNextPage, pokemonList, selected])rechart 차트라이브러리를 활용한 데이터 시각화 stat 데이터를 받아와서 가공한 뒤 rechart의 Radar를 활용하여 데이터를 시각화했다function StatChart({ stat, color }: StatChartProps) { const data = dataRefine(stat) return ( <Container> <RadarChart outerRadius={150} width={400} height={400} data={data}> <PolarGrid /> <PolarAngleAxis dataKey="subject" /> <PolarRadiusAxis domain={[0, 1]} angle={30} tick={false}/> <Radar dataKey="A" stroke="#8884d8" fill={Colors[color]} fillOpacity={0.7} /> </RadarChart> </Container> ) }Emotion을 활용해 공통 컴포넌트를 스타일드 컴포넌트로 구현하여 활용성을 높임이전 JS 파트와 react 미션 1에서 만든 기본 컴포넌트들을 emotion으르 수정했다. 5. forwardRef를 활용해 부모요소에서 자식 요소의 스크롤 이벤트를 관리 선택된 포켓몬을 상위 컴포넌트에서 관리하는 구조인데, 스크롤이 내려감과 동시에

코파

fetch와 axios 비교

웹 개발에서 데이터를 비동기적으로 가져오는 것은 매우 일반적인 작업이다. 이를 위해 주로 사용되는 두 가지 도구가 있는데, 바로 fetch와 axios이다. 참고로 이들은 구식 브라우저에서는 지원하지 않으므로 폴리필이 필요하다.Fetch 정의 :웹 브라우저 내장 API로, 네트워크 요청을 비동기적으로 처리하기 위해 만들어졌다. const res = await fetch(url); res.status; // response 코드 res.headers; // response 헤더 // response body await res.json(); // JSON 문자열을 파싱해서 자바스크립트 객체로 변환함. await res.text(); // 문자열을 그대로 가져옴.res.json() : 바디의 JSON 문자열을 파싱해서 자바스크립트 객체로 변환res.text() : 바디의 내용을 문자열로 그대로 가져옴.만약 body의 내용이 JSON이 아닌데 res.json으로 파싱하면 오류가 남. fetch() 옵션method (메소드)GET, POST, PATCH, DELETE지정하지 않으면 기본 값이 GETheaders (헤더)Content-TypeAuthorizationbody (바디)자바스크립트 객체는 그대로 전달할 수 없기 때문에 JSON 문자열로 바꿔줘야 함. 장점 :웹 표준:Fetch는 웹 표준으로, 모든 최신 브라우저에서 기본적으로 지원된다.웹 표준 API로서의 Fetch는 setTimeout, console 등과 함께 자주 사용된다. 단점 :추가 구현 필요:Fetch를 사용할 때는 axios와 비교하여 수동으로 구현해야 하는 기능들이 있다.예를 들어, 요청과 응답을 중간에 가로채어 처리하는 Interceptor 기능은 직접 작성해야 한다. // 요청을 보내기 전에 수행할 작업 const requestInterceptor = (url, options) => { console.log('Request Interceptor:', url, options); // 예: 인증 토큰 추가 const modifiedOptions = { ...options, headers: { ...options.headers, 'Authorization': 'Bearer YOUR_TOKEN' } }; return { url, options: modifiedOptions }; }; // 응답을 받은 후에 수행할 작업 const responseInterceptor = async (response) => { console.log('Response Interceptor:', response); if (!response.ok) { const errorData = await response.json(); throw new Error(`Error: ${response.status} - ${errorData.message}`); } return response.json(); }; const fetchWithInterceptors = async (url, options) => { const { url: interceptedUrl, options: interceptedOptions } = requestInterceptor(url, options); try { const response = await fetch(interceptedUrl, interceptedOptions); return await responseInterceptor(response); } catch (error) { // 에러 처리 console.error('Fetch Error:', error); throw error; } }; // 사용 예시 fetchWithInterceptors('https://api.example.com/data', { method: 'GET' }) .then(data => console.log('Data:', data)) .catch(error => console.error('Error:', error));응답값 파싱:Fetch는 기본적으로 응답값을 자동으로 파싱하지 않는다.응답을 JSON 형식으로 변환하려면 res.json() 메서드를 호출해야 한다. 이는 코드 작성 시 추가 단계를 필요로 한다.fetch('https://api.example.com/data') .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));에러 핸들링: HTTP 오류 상태 코드(예: 404, 500)는 Fetch에서는 자동으로 에러로 처리되지 않는다.Fetch API에서는 HTTP 상태 코드가 200-299 범위가 아니어도 네트워크 요청이 성공하면 Promise를 반환한다. 따라서 상태 코드에 따라 별도로 오류를 처리해야한다. 이를 위해 res.status를 사용하여 각 상태 코드에 대한 처리를 세분화할 수 있다.export async function getColorSurvey(id) { const res = await fetch(`https://www./${id}`); if (!res.ok) { throw new Error('데이터를 불러오는데 실패했습니다.'); } const data = await res.json(); return data; }export async function getColorSurvey(id) { const res = await fetch(`https://www.example.com/${id}`); // 상태 코드에 따라 오류 메시지를 다르게 처리 if (!res.ok) { if (res.status >= 400 && res.status < 500) { throw new Error('클라이언트 오류가 발생했습니다. 요청을 다시 확인하세요.'); } else if (res.status >= 500) { throw new Error('서버 오류가 발생했습니다. 나중에 다시 시도하세요.'); } else { throw new Error('알 수 없는 오류가 발생했습니다.'); } } const data = await res.json(); return data; }  axios 정의 :브라우저와 Node.js 환경 모두에서 동작하는 HTTP 클라이언트 라이브러리axios는 HTTP 메소드 이름과 동일한 메소드를 사용하고 리스폰스 바디를 data 프로퍼티로 접근할 수 있다.  axios 옵션GET, DELETE RequestRequest body가 필요 없기 때문에 옵션을 두 번째 아규먼트로 받는다. POST, PATCH, PUTRequestRequest에 보낼 body 내용은 두 번째 아규먼트로 받고, 옵션을 세 번째 아규먼트로 받는다.별도의 파싱과정이 없이 JSON으로 변환된다. // Axios 모듈 가져오기 import axios from 'axios'; // GET 요청 (Request Body 없음) axios.get('https://api.example.com/data', { // 옵션 params: { id: 123 }, headers: { 'Authorization': 'Bearer token123' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error('에러 발생:', error); }); // DELETE 요청 (Request Body 없음) axios.delete('https://api.example.com/data/123', { // 옵션 headers: { 'Authorization': 'Bearer token123' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error('에러 발생:', error); });// POST 요청 (Request Body 있음) axios.post('https://api.example.com/data', { name: 'John Doe', age: 30 }, { // 옵션 headers: { 'Authorization': 'Bearer token123' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error('에러 발생:', error); }); // PATCH 요청 (Request Body 있음) axios.patch('https://api.example.com/data/123', { age: 31 }, { // 옵션 headers: { 'Authorization': 'Bearer token123' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error('에러 발생:', error); }); // PUT 요청 (Request Body 있음) axios.put('https://api.example.com/data/123', { name: 'Jane Doe', age: 25 }, { // 옵션 headers: { 'Authorization': 'Bearer token123' } }) .then(response => { console.log(response.data); }) .catch(error => { console.error('에러 발생:', error); }); axios 인스턴스리퀘스트마다 공통되는 부분이 있으면 axios.create()으로 인스턴스를 생성한다.해당 인스턴스로 리퀘스트를 보내면 된다.const axios = require('axios'); // Axios 인스턴스 생성 const instance = axios.create({ baseURL: 'https://api.example.com/', // 기본 URL 설정 timeout: 5000, // 타임아웃 설정 (밀리초) headers: { 'Authorization': 'Bearer token123' // 기본 헤더 설정 } });// GET 요청 예제 instance.get('/data') .then(response => { console.log(response.data); }) .catch(error => { console.error('에러 발생:', error); }); // POST 요청 예제 instance.post('/data', { name: 'John Doe', age: 30 }) .then(response => { console.log(response.data); }) .catch(error => { console.error('에러 발생:', error); });https://axios-http.com/docs/instance  참고 :Fetch() 함수는 원래 웹 브라우저에서만 사용할 수 있었으며, Node.js에서는 사용할 수 없었다.그러나 Node.js v17.5부터 실험적인 기능으로 Fetch가 도입되었고, 이에 따라 주의 메시지가 출력되었다.이후, Node.js v18.13부터는 이 기능이 안정화되어 더 이상 주의 메시지가 출력되지 않는다. 장점 :간편한 코드 작성 : 코드 작성이 간편하다.직관적인 문법 : 문법이 직관적이다.다양한 기능 : 인터셉터, 타임아웃, 요청 취소 등의 기능을 제공한다.에러 처리 용이 : catch 블록에서 에러 처리가 가능하다.자동 JSON 파싱 : 응답 데이터(res.data)는 자동으로 JSON으로 파싱된다.(텍스트로 파싱이 필요할 경우 res.text()를 사용하면 된다.)axios.get('https://api.example.com/data') .then(response => { console.log(response.data); // JSON 데이터, response.json() 불필요 }) .catch(error => { console.error('Error:', error); });단점 :번들 사이즈 증가 : axios는 많은 기능을 제공하지만 그만큼 번들 사이즈를 증가시킨다. 특히 번들 사이즈가 커지면 로딩 시간이 길어질 수 있다. axios의 기능 3개 :1) 인터셉터axios의 인터셉터는 요청과 응답을 가로채고 수정할 수 있는 기능이다. 이를 통해 전역적으로 요청과 응답을 처리하거나, 특정 상황에 맞게 헤더를 추가하거나 변경할 수 있다. 예를 들어, 모든 요청에 인증 토큰을 추가하거나, 응답 데이터를 특정 형식으로 변환하는 등의 작업을 수행할 수 있다. 모든 요청에 대해 인증 토큰 추가하기axios.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, error => { return Promise.reject(error); }); 응답에 대한 에러 처리하기axios.interceptors.response.use(response => { // 응답이 성공인 경우 return response; }, error => { // 응답이 에러인 경우 if (error.response.status === 401) { // 인증 오류 처리 } else if (error.response.status === 404) { // 리소스를 찾을 수 없음 처리 } else { // 기타 오류 처리 } return Promise.reject(error); });  요청 또는 응답에 대한 로깅axios.interceptors.request.use(config => { console.log('요청 시작:', config); return config; }); axios.interceptors.response.use(response => { console.log('응답 받음:', response); return response; }, error => { console.error('에러 발생:', error); return Promise.reject(error); }); 2) 타임아웃axios는 요청에 대한 응답을 기다리는 시간을 설정할 수 있는 타임아웃 기능을 제공한다.이를 사용하여 서버로의 요청에 대한 응답이 지연되는 경우, 일정 시간이 지난 후에 요청을 취소하고 에러를 발생시킬 수 있다.axios.get('/api/data', { timeout: 5000 }) .then(response => { // 성공적으로 데이터를 받았을 때의 처리 }) .catch(error => { // 타임아웃 또는 다른 에러 처리 }); 3) 요청 취소 기능axios는 요청을 취소하는 기능을 제공하여, 요청이 보내진 후에도 요청을 중단시킬 수 있다.이는 사용자가 요청을 취소하고 다른 작업을 수행할 때 유용하다.const source = axios.CancelToken.source(); axios.get('/api/data', { cancelToken: source.token }) .then(response => { // 성공적으로 데이터를 받았을 때의 처리 }) .catch(error => { if (axios.isCancel(error)) { // 요청이 취소된 경우 처리 } else { // 다른 에러 처리 } }); // 요청 취소 source.cancel('요청이 사용자에 의해 취소되었습니다.')

웹 개발JavaScriptfetchaxios

Xangle*

Aptos Builder Meetup : Move언어 /코딩(NFT 마켓플레이스)/ 그랜트

📢 앱토스 빌더를 위한 Move 개발 세션 및 생태계 지원 프로그램 소개 안녕하세요! ‘크립토 데이터 인텔리전스 플랫폼 쟁글’을 통해 다양한 가상자산 서비스를 제공하고있는 Xangle입니다. 다가오는 6월에 Aptos와 밋업을 개최하게 되어 개발자분들을 모시고자 행사 관련 안내 드립니다.Move Prover의 저자로부터 Move 언어를 배우고, 앱토스에서 NFT 마켓플레이스 Dapp을 만드는 코딩 세션에 참여하세요!(노트북 지참).또한, 앱토스 빌더들이 지원받을 수 있는 그랜트 프로그램을 소개합니다. 🔎 그랜트 프로그램 : 초기 프로젝트들에 최소 50K 시작, 트랙션이 있을 경우 200k 이상까지 그랜트를 받을 수 있는 기회 📌 추천 대상# 앱토스 생태계와 그랜트 프로그램에 관심있는 개발자# 앱토스 Move 언어를 활용하고, 배우고 싶은 빌더# 좋은 아이디어는 있으나 사업자금이 필요한 개발자 일시 : 2024. 6. 7(금) 15:00~17:00장소 : HASHED Ventures 20F (서울특별시 강남구 강남대로 374 케이스퀘어 강남2, 20층) Aptos의 혁신적인 비전을 나누고, 산업에 새로운 변화를 가져오는 기회에 참여하고 싶어하는 분들을 대상으로 진행되는 만큼, 저희 밋업에 참여하시어 함께 미래를 만들어나가는 여정에 함께해주시면 감사하겠습니다. 자세한 내용은 아래 링크에서 확인해주시기 바랍니다. ⚠ 참가 신청 링크 : https://event-us.kr/bfw/event/83998⚠ NFT 마켓플레이스 Dapp을 만드는 코딩세션에 참여하기 위해 노트북을 지참하시기 바랍니다. 

NFT블록체인빌더개발자교육코딩그랜트프로그램빌더지원DappMove언어Aptos

역직렬화

[인프런 워밍업 스터디1기] 7일차 진도표

 <문제 1>public class FruitService { private final FruitRepository fruitRepository; public FruitService(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } @Transactional public void storeFruit(String name, LocalDate warehousingDate, long price) { Fruit fruit = fruitRepository.save(new Fruit(name, warehousingDate, price)); } @Transactional public void sellFruit(long fruitId) { Fruit fruit = fruitRepository.findById(fruitId) .orElseThrow(IllegalArgumentException::new); fruit.updateSold(); } @Transactional(readOnly = true) public FruitStatResponse getFruitStat(String name) { FruitStatResponse response = new FruitStatResponse(); List<Fruit> soldFruits= fruitRepository.findAllByNameAndSold(name, (byte)1); List<Fruit> notSoldFruits= fruitRepository.findAllByNameAndSold(name, (byte)0); response.setSalesAmount(soldFruits.stream().map(fruit -> fruit.getPrice()).reduce(new Long(0), (a, b) -> (a+b))); response.setNotSalesAmount(notSoldFruits.stream().map(fruit -> fruit.getPrice()).reduce(new Long(0), (a, b) -> (a+b))); return response; } ... }package com.group.libraryapp.dto.fruit.response; public class FruitCountResponse { private final long count; public FruitCountResponse(long count) { this.count = count; } public long getCount() { return count; } } controller @GetMapping("/api/v1/fruit/count") public FruitCountResponse countFruit (@RequestParam String name){ return fruitService.countFruit(name); } service@Transactional public FruitCountResponse countFruit (String name){ long countFruit = fruitJpaRepository.countByName(name); return new FruitCountResponse(countFruit); }

이용수

[인프런 워밍업 클럽 1기] BE 7일차

[인프런 워밍업 클럽 1기] BE 7일차본 게시글은 다음 강의 내용을 진행하고 있습니다.자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지] - https://inf.run/XKQg 코드SQL 문create table fruit ( id bigint auto_increment PRIMARY KEY , name varchar(25), warehousing_date date, price bigint, status varchar(10) CHECK (status IN ('SOLD', 'NOT_SOLD')) );Fruit.java@Entity public class Fruit { @Id private Long id = null; @Column(nullable = false, length = 25) private String name; private LocalDate warehousingDate; private long price; @Column(columnDefinition = "varchar(10) CHECK (status IN ('SOLD', 'NOT_SOLD'))") private String status; protected Fruit() { } public Fruit(String name, LocalDate warehousingDate, long price) { if (name == null || name.isBlank()) { throw new IllegalArgumentException("잘못된 name(%s)이 들어왔습니다, name"); } this.name = name; this.price = price; } public Long getId() { return id; } public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public long getPrice() { return price; } public String getStatus() { return status; } }FruitServiceV2.java@Service public class FruitServiceV2 { private final FruitRepository fruitRepository; private EntityManager entityManager; public FruitServiceV2(FruitRepository fruitRepository) { this.fruitRepository = fruitRepository; } public SaveFruitRequest saveFruit(SaveFruitRequest request) { fruitRepository.save(new Fruit( request.getName(), request.getWarehousingDate(), request.getPrice(), request.getStatus())); return request; } public GetFruitStatResponse getFruitStat(String name) { List<Fruit> fruit = fruitRepository.findByName(name); if (fruit == null) { throw new IllegalArgumentException("해당 과일이 없습니다."); } Long salesAmount = fruitRepository.sumPriceByNameAndStatus(name, "SOLD"); Long notSalesAmount = fruitRepository.sumPriceByNameAndStatus(name, "NOT_SOLD"); return new GetFruitStatResponse( salesAmount != null ? salesAmount : 0L, notSalesAmount != null ? notSalesAmount : 0L); } public ResponseEntity<Void> recordSoldFruit(RecordSoldFruitRequest request) { Fruit fruit = fruitRepository.findById(request.getId()) .orElseThrow(IllegalArgumentException::new); fruit.setStatus("SOLD"); fruitRepository.save(fruit); return ResponseEntity.ok().build(); } }FruitController.java@RestController public class FruitController { private final FruitServiceV2 fruitService; public FruitController(FruitServiceV2 fruitService) { this.fruitService = fruitService; } @PostMapping("/api/v1/fruit") public SaveFruitRequest saveFruit(@RequestBody SaveFruitRequest request) { return fruitService.saveFruit(request); } @PutMapping("/api/v1/fruit") public ResponseEntity<Void> recordSoldFruit(@RequestBody RecordSoldFruitRequest request){ return fruitService.recordSoldFruit(request); } @GetMapping("/api/v1/fruit/stat") public GetFruitStatResponse getFruitStat(@RequestParam String name) { return fruitService.getFruitStat(name); } }코드++FruitServiceV2.javapublic CountFruitResponse countFruit(String name) { long count = fruitRepository.countByName(name); return new CountFruitResponse(count); }CountFruitResponse.javapublic class CountFruitResponse { private long count; public CountFruitResponse(long count) { this.count = count; } public long getCount() { return count; } public void setCount(long count) { this.count = count; } }++FruitController@GetMapping("/api/v1/fruit/count") public CountFruitResponse countFruit(@RequestParam String name) { return fruitService.countFruit(name); }++FruitResponsitorylong countByName(String name);결과POST_MAN count코드FruitInfo.javapublic class FruitInfo { private String name; private long price; private LocalDate warehousingDate; public FruitInfo(String name, long price, LocalDate warehousingDate) { this.name = name; this.price = price; this.warehousingDate = warehousingDate; } public String getName() { return name; } public long getPrice() { return price; } public LocalDate getWarehousingDate() { return warehousingDate; } public void setName(String name) { this.name = name; } public void setPrice(long price) { this.price = price; } public void setWarehousingDate(LocalDate warehousingDate) { this.warehousingDate = warehousingDate; } }++FruitController.java@GetMapping("/api/v1/fruit/list") public List<FruitInfo> getFruitList(@RequestParam String option, @RequestParam long price) { return fruitService.getFruitList(option, price); }++FruitRepository.javaList<Fruit> findByPriceGreaterThanEqual(long price); List<Fruit> findByPriceLessThanEqual(long price);++FruitServiceV2.java public List<FruitInfo> getFruitList(String option, long price) { List<Fruit> fruits = getFruitsByOption(option, price); return convertToFruitInfoList(fruits); } private List<Fruit> getFruitsByOption(String option, long price) { if ("GTE".equals(option)) { return fruitRepository.findByPriceGreaterThanEqual(price); } else if ("LTE".equals(option)) { return fruitRepository.findByPriceLessThanEqual(price); } else { throw new IllegalArgumentException("GTE와 LTE 중 하나를 입력하세요"); } } private FruitInfo convertToFruitInfo(Fruit fruit) { return new FruitInfo(fruit.getName(), fruit.getPrice(), fruit.getWarehousingDate()); } private List<FruitInfo> convertToFruitInfoList(List<Fruit> fruits) { return fruits.stream() .map(this::convertToFruitInfo) .collect(Collectors.toList()); }결과POST_MAN LTE 경우POST MAN GTE 경우 

백엔드백엔드인프런워밍업

react에서 api 사용하는법! (fetch)

다음에 도전할 과제가 api를 활용하는 과제인 김에 이참에 api사용방법을 알아보기로 하였다.이번에는 fetch를 통해서 구현해볼 생각이다.fetch()란?자바스크립트 내장 객체 (API 호출하는 역할)fetch() 함수는 찾아보니 window객체에 소속되어 있다고 해서 window.fetch()로 사용되기도 한다고 한다.fetch() 사용 방법fetch(url, options) .then((response) => console.log("response:", response)) .catch((error) => console.log("error:", error));위 코드박스에 있는 방식으로 사용하는 것이 정석적인 방법인 듯 하다.안에 있는 내용들을 해석해보자면 url:api를 사용할 때 불러오는 url인 듯 하다option:불러올때 사용되는 듯 한데 아직은 어디에 어떤 방식으로 사용되는지 잘 모르겠다..then: .then이 붙어있는 부분이 작동되있거나 거부가 됐을 때 작동된다. 위 부분에서는 그냥 불러오면 사용되는 것 같다.response:불러온 값들이 들어가는 부분인 것 같다..catch: 뒤에 붙어있는 error이 나왔을 때 바로 실행하는 것 같다.아마 내용은 이정도가 다인 것 같다.실제 실행fetch(URL) .then((response) => response.json());이렇게 사용하는 것 같다. 과제에서 포켓몬 api를 사용하는 만큼 한번 PokeAPI로 테스트 해 볼 것이다.const poke = () => { fetch("https://pokeapi.co/api/v2/pokemon/ditto") .then((response) => response.json()) .then((data) => { console.log(data); }) .catch((error) => { console.error("Error:", error); }); };위의 코드를 onClick={poke}로 실행해본 결과 라는 결과가 나왔다.위와 같이 불러오는 것은 문제가 없는데 그렇다면 이 값을 어떻게 사용할 수 있을까?가져온 데이터 활용자세히 보면 크게 abilities,cries,forms,game_indices....등이 있고 그 안에 자세한 값들이 있다는 것을 알 수 있다.그런데 출력할 내용 중에 내가 지금 필요한 것은 id와 name이기에 console.log(data);부분을 console.log(data.id,data.name);으로 바꿔서 출력해보면이라는 우리가 원하는 값만 나오게 된다.활용-2(페이지 출력)아까 가져온 값에서 몇가지를 더 추가해서 출력해보았다import React, { useState } from "react"; function App() { const [pokemons, setPokemons] = useState([]); const poke = async () => { const newPokemons = []; for (let a = 1; a < 152; a++) { try { const response = await fetch( `https://pokeapi.co/api/v2/pokemon/&#36;{a}` ); const speciesResponse = await fetch( `https://pokeapi.co/api/v2/pokemon-species/&#36;{a}` ); const data = await response.json(); const speciesData = await speciesResponse.json(); const koreanNameEntry = speciesData.names.find( (name) => name.language.name === "ko" ); const koreanName = koreanNameEntry ? koreanNameEntry.name : data.name; newPokemons.push({ id: data.id, name: koreanName }); } catch (error) { console.error("Error:", error); } } setPokemons(newPokemons); }; return ( <div className="App"> <button onClick={poke}>Fetch Pokemon</button> <ul> {pokemons.map((pokemon) => ( <li key={pokemon.id}> {pokemon.id}: {pokemon.name} </li> ))} </ul> </div> ); } export default App; 1.usestate로 바뀌는 값 저장하기2.async와 await로 순차적으로 처리하기3.정보와 species받아와서 json으로 바꾸기4.정보에서 name만 빼고 species에서 language가 ko인 이름(한국어이름)가져오기5.newPokemons에 넣기6.newPokemons에 있던 값을 한번에 state에 집어넣고 출력하기순으로 작동된다.결과적으로는 이렇게 나온다.아직 css가 없어서 일자로 나오지만 이래저래 하면 도감 느낌으로 할 수 있을 듯 하다. POST,PUT,DELETE생각하보니 제목이 api인데 나머지 중요한 3개도 안해서 소개하자면 POSTfetch("URL", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ title: "Test", body: "Testing!", userId: 1, }), }) .then((response) => response.json())위 방식으로 사용하는데 url: 말 그대로 post용 url이다.body: JSON.stringify({}) :중괄호 안에 들어가는 값이 post 할 값인 것 같다.PUTpost랑 작동이 비슷한 느낌이어서 method가 "PUT"인 것 빼고는 달라지는것이 거의 없는 것 같다.DELETE어떤거 지울지만 고르는것이어서 get과 큰 차이가 없다.      도움받은 글https://velog.io/@fltxld3/React-API-%ED%98%B8%EC%B6%9C%ED%95%98%EA%B8%B0https://jaenam1212.tistory.com/22

이슬

인프런 워밍업 클럽 스터디 1기 FE 과제(10번 과제)

[10번 과제(Day11) - 포켓몬 도감 앱]따라하며 배우는 리액트 A-Z학습 범위: Section 6 ~ 7https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/pokedex-app과제 이미지알게된 것 (String, Array 타입에서의 includes 메서드)String메인페이지에서 검색어를 입력할 때, 검색어가 포함된 객체들로 이루어진 배열을 따로 searchResults 라는 상태로 저장해주었다. 이 searchResults 상태를 연관검색어로 노출시키기 위함이었다.pokemonList 배열에서 검색어(searchInput)가 포함된 name을 가진 객체만 모아서(배열을 필터링해) 반환해주는 filter, includes 메서드를 사용했다. 여기서 사용한 includes 메서드의 pokemon.name 는 String 타입이다.따라서 'h' 라는 검색어를 입력했을때, 객체의 name 문자열 중에 'h'가 부분으로 포함된 객체들도 가져올 수 있다. // Main Page // 포켓몬 이름(string)에 검색input이 포함된 것만 필터해서 반환 const matched = pokemonList.filter( (pokemon) => pokemon.name.includes(searchInput) // true인 객체들만 반환 ); // 검색input값이 있을 때에만 검색 결과 배열에 저장 if (searchInput) { setSearchResults(matched); } // String.prototype.includes const sentence = 'The quick brown fox jumps over the lazy dog.'; console.log(sentence.includes('quick')); // true console.log(sentence.includes('h')); // true console.log(sentence.includes('Quick')); // false, 대소문자 구분Array연관 검색어 배열에서 특정 검색어를 클릭 했을 때, 검색 input에는 선택된 특정 검색어를 value로 가지고, 연관 검색어 요소를 사라지게하는 기능을 구현하고자했다.선택된 특정 검색어가 searchInput가 되었을 때, 연관 검색어 배열에도 동일한 검색어를 가진다. (검색어 입력 시 필터링되게 했음으로)검색 input의 현재 value(searchInput)와 연관 검색어 목록이 정확히 동일하다면 연관 검색어를 보여주는 요소를 사라지게 하면 되었다. 연관 검색어 배열(searchResults)의 객체를 name 키만 가진 문자열 배열로 만들어 준 후(map 메서드), Array의 includes 메서드를 사용했다.따라서 'pikachu' 라는 검색어를 클릭했을때, 배열의 요소 중에 정확히 'pikachu'가 있다면 true를 반환한다.// SearchBar // 검색input 값이 검색 이름(array)에 포함된 경우 true일 때 (정확히 같아야 함) if (searchResults.map((result) => result.name).includes(searchInput)) { setSearchShow(false); // 연관 검색 숨기기 } // Array.prototype.includes const fruits = ['apple', 'banana', 'mango']; console.log(fruits.includes('banana')); // true console.log(fruits.includes('ba')); // false 정리하자면,타입 차이 Array.prototype.includes는 배열의 요소를 찾기 위해 사용된다.String.prototype.includes는 문자열 내의 부분 문자열을 찾기 위해 사용된다.비교 방식배열에서는 엄격한 동등성(===) 비교를 사용한다.문자열에서는 대소문자를 구분하는 포함 여부를 확인한다. 

웹 개발인프런워밍업클럽FE1기과제

코다

[인프런 워밍업 클럽 1기 FE] 2주차 발자국

2주차 발자국📍2024.05.03 ~ 2924.05.10🏁 2주차 미션강의 요약#7 Iterator, GeneratorSymbol 은 ES6 에서 추가된 원시 타입이며 생성자 함수를 이용해서만 생성할 수 있다.Symbol 을 사용하면 유일한 식별자를 만들 수 있다. for/in 과 getOwnPropertyNames 에서 제외된다.Iterator 는 next( )를 호출하여 두 개의 속성(value, done)을 가지는 객체를 반환하는 객체이다.Generator 는 사용자의 요구에 따라 다른 시간 간격으로 여러 값을 반환할 수 있다.yield 는 Generator 함수의 실행을 일시적으로 정지시키며 일반 함수의 return 과 유사하다.#8 Design Pattern디자인 패턴은 응용 프로그램이나 시스템을 디자인의 일반적인 문제를 해결할 때 사용할 수 있는 공식화된 모범 사례이다.최고의 솔루션, 재사용성, 풍부한 표현력, 향상된 의사 소통, 필요없는 코드 리팩토링, 코드베이스 크기 감소의 장점이 있다.싱글톤 패턴 : 클래스의 인스턴스를 한 개의 객체로 제한한다.팩토리 디자인 패턴 : 팩토리 함수를 사용하여 비슷한 객체를 여러 개 만들 수 있다.중재자 패턴 : 객체 그룹에 대한 중앙 권한을 제공한다.상태 패턴 : 특정 상태를 나타내는 객체 집합에 상태별 논리를 제공하여 객체가 내부 상태에 따라 행동을 변경할 수 있다.모듈 패턴 : 큰 파일을 여러 개의 작고 재사용 가능한 조각으로 분할할 수 있게 돕는다.관찰자 패턴 : 주체에 변경 사항이 생기면 주체를 관찰하는 관찰자가 알 수 있다.#1 리액트란?리액트는 뷰, 앵글러 프레임워크와 달리 자바스크립트 라이브러리이다.배우기 쉽고, 라이브러리 환경, 사용성이 검증됨 등의 이유로 리액트를 많이 사용한다.리액트는 실제 DOM 을 복사하여 메모리에 저장한 가상 DOM 을 사용한다. 가상 DOM 끼리의 비교하는 과정인 Diffing 을 통해 변경된 부분을 파악하고 변경된 부분을 Batch Update 로 실제 DOM 에 한 번에 적용시키는 Reconciliation 을 통해 DOM 을 조작한다.리액트 컴포넌트는 리액트로 만들어진 앱을 이루는 최소한의 단위이며 클래스형 컴포넌트, 함수형 컴포넌트가 있다.리액트를 사용하기 위해서는 웹팩, 바벨 라이브러리가 필요하다.웹팩은 여러 개의 파일을 하나의 자바스크립트 코드로 압축하고 최적화 하는 라이브러리이다.바벨은 최신 자바스크립트 문법을 지원하지 않는 구형 브라우저에서도 작동할 수 있게 변환시켜주는 라이브러리이다.#2 To-Do 앱으로 리액트 익히기packages.json 에는 해당 프로젝트의 이름, 버전, 필요한 라이브러리와 라이브러리의 버전들과 앱 시작, 빌드, 테스트할 때 사용할 스크립트가 명시되어있다.SPA란 페이지마다 html 을 가지는 MPA와 달리 웹 사이트의 전체 페이지를 하나의 페이지에 담아 동적으로 화면을 바꿔가며 표현하는 것을 말한다.SPA의 화면 변경은 History API 를 이용한다.JSX 는 자바스크립트의 확장 문법이며 UI 표현 시에 자바스크립트와 HTML 을 같이 사용할 수 있어 스크립트 내에서 UI 작업이 편리하다.리액트에서 요소의 리스트를 나열할 경우에 필요한 Key 는 리액트가 변경, 추가, 제거된 항목을 식별하는 데 사용된다. Key 를 이용해서 가상 DOM 에서 바뀐 부분을 인식할 수 있다.리액트 State 란 컴포넌트의 렌더링 결과물에 영향을 주는 데이터를 가진 객체이다. State 가 변경되면 컴포넌트가 Re-rendering 되며 State 는 컴포넌트 내에서 관리된다.#3 To-Do 앱 최적화React Hooks 란 class 없이 state 를 사용할 수 있는 새로운 기능이다.클래스 컴포넌트: 많은 기능, 긴 코드, 복잡한 코드, 더딘 성능함수형 컴포넌트: 적은 기능, 짧은 코드, 간결한 코드, 빠른 성능React Hooks 의 장점함수형 컴포넌트에서도 리액트 생명주기를 사용할 수 있다. 코드가 간결해진다.HOC (Higher Order Component) 컴포넌트를 Custom React Hooks 로 대체하여 Wrapper 컴포넌트를 줄인다. State컴포넌트 내부에서 데이터 전달 시 사용한다. 변경이 가능하다. 변경되면 리렌더링 된다.PropsProperties 의 줄임말. 부모 컴포넌트로부터 자녀 컴포넌트에 데이터를 전달할 때 사용한다. 읽기 전용이므로 자녀 컴포넌트에서는 변하지 않는다. 변경할 경우 부모 컴포넌트에서 State 를 변경시켜줘야 한다.TailWindCSSHTML 안에서 CSS 스타일을 만들 수 있게 해주는 CSS 프레임워크이다.CSS 프레임워크는 레이아웃 및 컴포넌트 구성, 브라우저 호환성을 보장하는데 소요되는 시간을 최소화하기 위해 여러 웹 프로젝트에 적용할 수 있는 CSS 파일 모음이다.장점: 빠른 스타일링, class/id 작성에 대한 어려움 감소, IntelliSense 플러그인으로 익숙해지기 쉬움리액트 불변성원시 타입: 불변성O, 고정된 크기로 call stack 메모리에 저장, 실제 데이터가 변수에 할당됨참조 타입: 불변성X, 정해지지 않은 크기로 call stack 메모리에 저장, 데이터의 값은 heap 에 저장되며 heap 메모리의 주소값이 변수에 할당됨불변성을 지켜야 프로그래밍의 복잡도가 감소하고, 리액트가 변경사항을 확인하여 업데이트할 수 있다.불변성을 지키는 방법참조 타입의 값을 바꾸는 경우 새로운 배열을 반환하는 메소드(spread operator, map, filter, slice, reduce)를 사용한다.cf. splice, push 는 원본 데이터를 변경하는 메소드이다.HTML Drag and Drop API 을 쉽게 구현할 수 있게 도와주는 모듈: react-beautiful-dndReact.memo 를 사용함으로써 불필요한 렌더링을 막을 수 있다.useCallback 을 이용하여 컴포넌트 렌더링 시 함수도 새로 만들게 되는데 이 같은 현상(리렌더링-함수 재생성-자식 컴포넌트 리렌더링) 을 막아 함수 최적화를 할 수 있다.useMemo 를 이용하여 비용이 많이 드는 함수 호출의 결과를 저장하고 동일한 입력 발생 시 캐시된 결과를 반환하여 컴퓨터의 속도를 높일 수 있는 메모이제이션을 사용할 수 있다. 컴포넌트가 리렌더링 되더라도 동일한 입력이라면 이전 렌더링 때 저장된 값을 재활용한다.localStorage 를 이용하면 페이지가 새로고침되어도 데이터가 남아있게 할 수 있다.객체나 배열을 저장할 경우 JSON.stringify 를 사용해야 한다.#4 Netflix 앱 - 1Axios 란 브라우저, node.js 를 위한 promise API 를 활용하는 HTTP 비동기 통신 라이브러리인스턴스화하면 중복된 부분을 계속 입력하지 않고 사용할 수 있다.Styled Component 란 Css-In-JS 라고 하는 자바스크립트 파일 안에서 CSS를 처리할 수 있게 해주는 라이브러리이다.Iframe 은 HTML Inline Frame 요소이다. 다른 HTML 을 해당 웹페이지 내부에 제한 없이 다른 페이지를 삽입할 수 있다.#5 Netflix 앱 - 2Element.scrollLeft 속성은 요소의 콘텐츠가 왼쪽 가장자리에서 스크롤되는 픽셀 수를 가져오거나 설정한다.innerWidth 는 브라우저 내부의 가로 길이이다.React Router DOMReact Router DOM 을 사용하면 웹 앱에서 동적 라우팅을 구현할 수 있고, 컴포넌트 기반 라우팅이 용이하다.Routes : 앱에서 생성될 모든 개별 경로에 대한 컨테이너 역할이며 가장 첫번째 Route 를 렌더링한다.Route : 단일 경로를 만드는 데 사용한다. path 와 element 속성을 가진다.path 는 원하는 컴포넌트의 URL 이고, 원하는대로 이름을 정할 수 있다.element 에는 경로에 맞게 렌더링 되어야 하는 컴포넌트를 지정한다.React Router DOM APIs중첩 라우팅 : 대부분의 레이아웃을 URL 세그먼트에 연결할 수 있다.Outlet : 자식 경로 요소를 렌더링 하기 위해서 부모 요소에서 사용해야 한다.useNavigate : 경로를 바꾼다.useParams : :style 문법을 path 경로에 사용한 경우 useParams 로 읽을 수 있다.useLocation : 현재 위치 객체를 반환한다.useRoutes : <Routes> 와 기능적으로 동일하나 <Route> 대신 자바스크립트 객체로 경로를 정의한다.useRef : 특정 DOM 을 선택할 때 사용한다.Debounce function 을 사용하면 미리 결정한 시간 동안 이벤트의 처리를 지연시킬 수 있다.swiper 모듈을 사용하면 터치 슬라이드를 쉽게 구현할 수 있다. 느낀 점예전에 다른 강의로 리액트 클론 코딩을 했었으나 자바스크립트를 잘 모를 때라 코드에 대한 이해 없이 따라쓰기를 했었다. 자바스크립트를 선행한 상태에서 리액트를 배우니 좀 더 쉽게 배우고 이해할 수 있었다. 동작의 원리가 궁금한 부분들이 있어 리액트 공식 문서를 읽어 추가로 정리할 예정이다. 아직 JSX 문법이나 state, prop 에 익숙하지 않아 미션을 수행하면서 직접 사용해봐야겠다.

인프런워밍업클럽