블로그

김혅

[인프런 워밍업 스터디 1기 디자인] 1주차 발자국 🐾

OT부터 1주 차 스터디, 그리고 첫 온라인 중간 점검까지 마치고 남기는 첫 발자국!역시 나는 강제성(?)같은 동기가 부여되어야 하는 사람임을 다시 느꼈다. 🙄>짜여진 커리큘럼과 그에 맞는 미션이 설계되어 있어서 따라가기 너무 좋았고,강사님께서 노션과 강의 요약본까지 정리해두셔서 더 편리하게 할 수 있었다. 쵝오...👍강의는 무작정 "자, 일단 따라 해봅시다!"가 아닌 기초 개념부터 쌓아 올려가는 방식이라 너무 좋았고, 설명에 맞는시각적 예시까지 곁들여주셔서 따라가기 너무 좋았다. 유용한 사이트와 플러그인 정보도 유익한 건 덤~(WOW)~아직 아날로그 인간이라 강의를 손으로 필기하는데, 그중 몇 개를 아래에 간단하게 적어 회고를 해보고자 한다!Chapter 1.피그마 배리어블과 디자인 토큰디자인 시스템을 만들어야 하는 이유 ✧ 커뮤니케이션(공감대 형성)스타일, 배리어블? ✧ 기본 디자인 토근 관리를 위해서는 배리어블 ✧ 다수의 디자인 요소의 결합은 스타일 Chapter 2.배리어블과 파운데이션 세팅하기✦ 색상 베리어블 세팅 ✧ border의 경우 너무 두꺼우면 텍스트와 잘 부딪히니 색을 빼주어 구분을 해주자.  ✧ icon은 텍스트와 주로 같이 간다.✦ 그리드 ✧ 4-point grid는 작은 단위로 레이아웃 정렬에 용이하다.  ✧ gutter/columns의 개념 헷갈리지 않기. ✧ fluid의 경우 margin 값을 필수로 넣어주어야 한다. ✧ fixed와 fluid의 constraits 각각의 차이 기억하기.✦ 기타 베리어블 ✧ ex)stroke 배리어블 버튼이 안보일때 우 클릭&apply variable or shift+왼쪽 클릭 ✧ opacity의 경우 fill이 아닌 layer에서 적용시키기. 미션✦ 놓치는 부분이 없는지, 적용이 잘 되었는지 더블의 더블 체크 해주기.✦ 생각보다 어려워서 난항을 겪었던 미션 4 ▸ 참고한 템플릿은 크게 두 가지 범정부UIUX_디자인스타일가이드n컴포넌트 / Wanted Design Library ▸ 컬러 부분이 상당히 오래 걸렸는데, 맘에 드는 플러그인을 찾기 어려웠고 요소가 많은데 그 안에서도 최대한 구분이 되어 보이기를 바라며 구성하였다. -Theme의 경우 각 변수의 수가 똑같기 때문에 가로 정렬 -Sementic의 경우 각 변수의 차이가 약간 있고 내용이 많아 세로 정렬로 진행해 주었다.마치며, 2주 차 다짐📍아직까지는 따라가기 바쁘다, 복습은 필수.📍커리큘럼대로만 잘 따라가자, 부지런해져라(제발🥺) 

UX/UI워밍업클럽1기피그마

ddang

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

1번 과제 (Day2) (음식 메뉴 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_2_food_menu2번 과제 (Day3) (가위 바위 보 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_3_rock_scissors_paper3번 과제 (Day4) (퀴즈 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_4_quiz 4-1번 과제 (Day5-1) (책 리스트 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_05-1_book_list   4-2번 과제 (Day5-2) (GitHub Finder 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_05-2_github_finder 5번 과제 (Day6) (비밀번호 생성 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_06_password_generator 6번 과제 (Day7) (타자 속도 측정 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_07_typing_speed_test 7번 과제 (Day9) (예산 계산기 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_09_budget_calculator 9번 과제 (Day11) (포켓몬 도감 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_11_pokedex 11번 과제 (Day13) (리덕스를 이용한 쇼핑몰 앱)깃허브 저장소 주소: https://github.com/llddang/Inflearn_WarmingUp_FE/tree/main/day_13_redux_shop_app

프론트엔드인프런_워밍업_클럽1기과제FE

희희

[인프런 워밍업 클럽 스터디 1기] BE 2주차 회고

두번째 발자국 인프런 워밍업 클럽 스터디에 참여하여 일주일을 보낸 후 쓰는 두번째 회고록. Section 3 - 역할의 분리와 스프링 컨테이너스프링 컨테이너 : 서버가 시작되면 스프링 서버 내부에 만들어지는 거대한 컨테이너이 컨테이너 안에는 (여러) 클래스가 들어가게 된다.스프링 컨테이너 안에 들어간 클래스를 스프링 빈이라 한다.스프링 빈은 (미리 작성된 프레임워크의 코드에 의해) 서버가 시작될 때 자동으로 등록되기도 하고, 코더가 @RestController 등의 어노테이션을 이용해 직접 설정하기도 한다.이때 스프링 빈 간 필요한 의존성이 자동으로 설정되고, 인스턴스화도 이루어진다. 제어의 역전(IOC, Inversion of Control)객체를 생성하는 과정에서 스프링 컨테이너가 필요한 의존성을 자동으로 선택해서 주입해주는 방식new를 이용해 어떤 구현체를 사용할지 결정하지 않고, 스프링 컨테이너가 대신 결정해주는 방식의존성 주입(DI, Dependency Injection)컨테이너가 특정 구현체를 선택해 클래스에 넣어주는 과정 (필요한 의존성을 설정해주는 과정)스프링 빈 등록 방법@RestController, @Service, @Repository@Configuration(클래스에 붙임), @Bean(메소드에 붙여 메소드에서 반환된 객체를 등록)@Component(컨트롤러, 서비스, 레포지토리 외의 클래스를 추가적으로 등록하기 위해 사용)스프링 빈 주입받는 방법생성자를 이용해 주입(권장)Setter와 @Autowired 사용필드에 직접 주입(@Autowired 사용)@Qualifier Section 4 - 생애 최초 JPA 사용하기JPA(Java Persistence API) 자바 진영 ORM 기술 표준ORM(Object-Relational Mapping) : 데이터와 관계형 DB의 테이블을 짝짓는 방법(객체와 관계형 DB를 짝지어) 데이터를 영구적으로 저장할 수 있도록 정해진 Java 진영의 규칙영속성 : 서버가 재시작되도 데이터는 영구적으로 저장되는 속성HibernateJPA를 구현해서 코드로 작성한 구현체JPA는 단순히 API이므로, 실제 동작을 위해서는 JPA 구현체가 필요함내부적으로 JDBC를 사용JPA 어노테이션@Entity : 스프링이 객체와 테이블을 같은 것으로 바라봄Entity : 관리되어야 하는 데이터@Id : 해당 필드를 primary key로 간주@GeneratedValue : primary key는 자동 생성되는 값@Column : 객체의 필드와 Table의 필드를 매핑JPA를 사용하려면 반드시 기본 생성자가 필요함!JPA 기능save : 주어지는 객체를 저장하거나 업데이트findAll : 주어지는 객체가 매핑된 테이블의 모든 데이터를 가져옴findById : id를 기준으로 특정한 1개의 데이터 가져옴Spring Data JPA : 복잡한 JPA 코드를 스프링과 함께 쉽게 사용할 수 있도록 도와주는 라이브러리By 앞에 들어갈 수 있는 구절 정리find: 1건을 가져옴. 반환 타입은 객체가 될 수도 있고 Optional<타입>이 될 수도 있음findAll: 쿼리의 결과물이 N개인 경우 사용. List<타입> 반환exists: 쿼리 결과가 존재하는지 확인. 반환 타입은 booleancount : SQL의 결과 개수를 셈. 반환 타입은 longBy 뒤에 들어갈 수 있는 기능 정리And / OrGreaterThan : 초과GreaterThanEqual : 이상LessThan : 미만LessThanEqual : 이하Between : 사이에StartsWith : ~로 시작하는EndsWith : ~로 끝나는 트랜잭션(Transaction)쪼갤 수 없는 업무의 최소 단위 트랜잭션의 모든 로직이 성공적으로 수행되면 commit 처리되고, 예외가 발생하면 rollback 처리됨영속성 : 모든 로직을 성공시키거나, 로직들 중 하나라도 실패하면 모두 없었던 일로 하는 속트랜잭션으로 처리할 메서드 앞에 @Transactional을 붙여서 구현  영속성 컨텍스트(Persistence Context)테이블과 매핑된 엔티티 객체를 관리/보관하는 역할스프링에서는 트랜잭션 수행 중 엔티티 객체를 관리/보관하는 역할을 수행트랜잭션이 수행될 때 자동으로 생성되고, 트랜잭션이 종료되면 함께 종료됨영속성 컨텍스트의 특수 기능 :변경 감지(Dirty Check) : 영속성 컨텍스트 안에서 불러와진 엔티티는 별도의 save 필요 없이 엔티티의 변경 사항을 자동으로 감지하여 저장쓰기 지연 : 모든 SQL 요청을 트랜잭션이 commit될 때 모아서 한 번만 날림1차 캐싱 : 조회된 엔티티 객체를 id를 기준으로 캐싱하여 DB 접근 횟수를 줄임   Section 5 - 책 요구사항 구현하기 책 생성 APIHTTP Method : POSTHTTP Path : /bookHTTP Body (JSON) : 결과 반환 : X (HTTP 200 OK)대출 기능HTTP Method: POSTHTTP Path: /book/loanHTTP Body(JSON) :결과 반환 X : (HTTP 200 OK)반납 기능HTTP Method: PUTHTTP Path: /book/returnHTTP Body(JSON) :결과 반환 X : (HTTP 200 OK)  과제과제 4https://shimmer-exoplanet-946.notion.site/4-0d91a864aa444cef9137e454a1c29b5d?pvs=4과일 정보를 저장하는 API, 과일이 팔리면 과일을 팔린 상태로 변경하는 API, 과일의 이름을 받으면 해당 과일을 기준으로 팔린 금액과 팔리지 않은 금액을 조회하는 API를 작성하는 과제였다.우선 과일을 저장할 테이블을 만들어야 했는데, 문제에서 요구되는 id, name, warehousingDate, price, status 등을 column으로 넣어주었다. 이때 status는 ENUM 타입으로 설정해 SOLD나 NOT_SOLD 두 상태 중 하나가 되도록 하였고, 디폴트 값은 NOT_SOLD로 설정해두었다.이후 API 작성은 수업에서 배운대로 JdbcTemplatae과 직접 작성한 SQL문을 이용해 DB에 쿼리를 날리는 방식이라 크게 어렵지 않았다. 문제 3번은 배운 내용을 조금 응용해야 했는데, 과일의 이름을 받아 해당 과일이 DB에 존재하는지 확인하여 분기 처리를 하는 부분은 기존에 배운 것과 동일했지만, 해당 과일 중 팔린 과일과 필리지 않은 과일의 총액을 조회하는 부분에서 내가 원하는 기능을 수행하는 SQL 쿼리에 대한 검색을 좀 해야 했다. 우선 name이 해당 과일의 이름이고 팔린/팔리지 않은 상태의 과일들을 조회해온 다음, SUM(price)를 이용해 조회된 과일들의 price값을 합산한 후, COALESCE 함수를 이용해 SUM의 결과가 NULL이면 0을 반환하도록 했다. 과제 5https://shimmer-exoplanet-946.notion.site/5-e7d1b6bae1fe44d5baf250c702270028?pvs=4주어진 코드를 클린 코드로 리팩토링 하는 과제였다. 클린 코드가 정확히 무엇인지 알기 위해 검색을 해서 정리했다. 의미있는 변수/함수/클래스 이름을 지정하고 코드를 기능(역할)에 따라 모듈화하여 최대한 간결하면서 의도를 명확히 전달할 수 있는 코드를 짜는 것이 핵심이었다. 우선 변수와 함수의 이름을 사용 의도가 명확히 보이도록 고쳐주었고, 기존에 반복되던 코드를 for문을 이용하여 간결하게 해주었다. 또한 기존 코드는 main 함수에서 모든 기능을 수행하고 있었기 때문에 기능별로 함수를 분리해주었다. 두 과제 모두 무난하고 재미있게 수행할 수 있었다. 과제 4를 통해서는 수업에서 배운 것 외의 SQL 문법에 대해 공부해볼 수 있었고, 과제 5는 다른 사람들이 어떻게 리팩토링했는지 보며 내가 미처 생각하지 못한 개선점들을 볼 수 있었다.  회고저번 주 강의는 기본적인 지식들을 쌓는 것이었는데, 이번주는 본격적인 내용으로 들어가면서 필기할 내용도 많아지고 배운 것들을 이해하는 데 더 많은 시간을 쏟아야 했다. 그래서 강의를 듣는 것도 좀 밀리고, 강의를 다 듣긴 했지만 배운 내용을 온전히 소화하지는 못했다. 다음주는 이번주 강의 내용을 다시 정리해보고, 시간을 좀 더 효율적으로 사용할 수 있도록 해야겠다. 그래도 JPA를 이용해 실제로 동작하는 도서 관리 어플리케이션을 개발해보는 과정은 정말 즐거웠다. 이번주 금요일에 코드 리팩토링에 관한 라이브 방송이 있었는데, 그걸 모르고 있다가 방송이 끝나갈 쯤에야 참여해서 아쉬움이 컸다. 과제 5를 수행하면서 클린 코드를 짜는 것이 생각보다 고려할 점이 많다는 것을 느껴서 더더욱 그랬다. 다행히 키워드와 코드를 남겨주셔서 혼자 공부해봐야 할 것 같다.

백엔드인프런워밍업클럽스터디1기

희희

[인프런 워밍업 클럽 스터디 1기] BE 1주차 회고

첫번째 발자국 인프런 워밍업 클럽 스터디에 참여하여 일주일을 보낸 후 쓰는 첫번째 회고록. Section 1 - 생애 최초 API 만들기JVM자바 가상 머신의 약자os 별로 존재한다.바이너리 코드들을 읽고 검증한다.JRE자바 실행 환경의 약자JRE = JVM + 자바 프로그램 실행에 필요한 라이브러리 파일 등JVM의 실행 환경을 구현JDK자바 개발 도구의 약자JDK = JRE + 개발을 위한 도구(통합 개발 도구)컴파일러, 디버그 도구 등이 포함되어 있다.여러 버전이 있고, 각 버전별로 새로운 기능이 추가되거나 기존 기능이 사라진다.여러 종류가 있고, 기능 자체는 동일하나 성능과 비용에 약간의 차이가 있을 수 있다. 빌드 : 소스코드 파일을 컴퓨터에서 실행할 수 있는 독립 SW 가공물(Artifact, 독립적인 하나의 파일)로 변환시키는 과정실행 : 작성한 코드를 컴파일을 거쳐 작동시켜보는 것(독립 SW 가공물이 나올수도 있고, 나오지 않을 수도 있음)빌드 과정 자동화와 외부 라이브러리 관리를 위해 빌드 툴이 사용되며, 자주 사용되는 자바 빌드툴에는 maven, gradle이 있다. 어노테이션자동으로 설정/기능을 동작하게 하는 것@ + (어노테이션으로 만들어둔) 클래스 이름 서버 : 어떤 기능을 제공하는 프로그램. 또는 그 프로그램을 실행시키고 있는 컴퓨터클라이언트 : 서버에 어떤 기능을 요청하는 프로그램. 또는 그 프로그램을 실행시키고 있는 컴퓨터HTTP : 컴퓨터가 네트워크를 통해 다른 컴퓨터와 데이터를 주고받을 때 사용되는 통신 규약(중 하나)HTTP Method : HTTP 요청을 받는 컴퓨터에게 요청하는 행위(GET, POST, PUT, DELETE 등)요청을 받는 컴퓨터(IP 주소 / 도메인)와 프로그램(port) 정보Path : HTTP 요청을 받는 컴퓨터에게 원하는 자원Query : 자원의 세부 조건(조건이 여러개면 &로 구분)Body(JSON) : 저장할 자원의 정보JSON : 객체 표기법. 중괄호 안에 "key": value로 표기하며, 속성 각각은 ,로 구분한다. 요청에 대한 HTTP 응답200 OK (요청이 잘 처리됨)300 Moved Permanently (다른 곳으로 옮겨가라)404 NotFound (요청한 것을 찾을 수 없음)500 Internal Server Error (우리 내부에 문제가 생김)응답에는 추가 정보(바디)를 담을 수도 있음 API정해진 약속을 하여 특정 기능을 수행하는 것클라이언트와 서버는 HTTP를 주고 받으며 기능을 동작하는데 이때 정해진 규칙API의 구성 요소(명세)HTTP MethodHTTP PathQuery(key와 value)API의 반환 결과 @RestController: 주어진 클래스를 Controller(API의 입구)로 등록한다 -> 현재 클래스를 API의 진입 지점으로 만들어줌@GetMapping("/add"): 아래 함수를 HTTP Method가 GET이고 HTTP Path가 /add인 API로 지정한다.@RequestParam: 주어지는 쿼리를 함수 파라미터에 넣는다 -> 같은 이름을 가진 쿼리의 값이 함수의 argument로 들어온다@RequestBody: HTTP Body 안에 담긴 JSON을 DTO 객체로 변환 (DTO : 정보를 전달하는 역할의 객체) Section 2 - Database 조작하기Database의 필요성: API를 통해 생성된 정보는 단기 기억장치인 RAM에 기록되는데, RAM에 기록된 정보는 서버가 종료되면 사라지기 때문에 DISK에 장기 저장하기 위해 DB를 사용한다. Database : 데이터를 구조화해서 저장하는 것RDB(Relational DB) - MySQL : 데이터를 표처럼 구조화해서 저장하는 DBSQL(Structured Query Language) : 표처럼 구조화된 데이터를 조회,저장 등 조작하는 언어-> MySQL을 사용 DDL(Data Definition Language) : 데이터를 정의하는 언어데이터베이스 만들기 :create database [데이터베이스 이름];데이터베이스 목록 보기 :show databases;데이터베이스 지우기 :drop database [데이터베이스 이름];데이터베이스 안으로 들어가기 :use [데이터베이스 이름];(특정 데이터베이스 안으로 들어간 후)테이블 목록 보기 :show tables;테이블 만들기 :create table [테이블 이름] ([필드1 이름] [타입] [부가조건],[필드2 이름] [타입] [부가조건],...primary key ([필드이름]));테이블 제거하기 :drop table [테이블 이름];DML(Data Manipulation Language) : 데이터를 조작하는 언어데이터를 넣는다 = 생성, create데이터를 조회한다 = 조회(읽기), retrieve of read데이터를 수정한다 = 업데이트, update데이터를 삭제한다 = 제거, delete-> CRUD데이터 넣기INSERT INTO [테이블 이름] (필드1이름, 필드2이름, ...)VALUDES(값1, 값2, ...)필드 이름과 값의 순서가 맞아야 한다명령어는 소문자로 써도 상관 없다auto_increment로 지정된 필드값은 지정해주지 않아도 자동으로 설정된다데이터 조회하기SELECT * FROM [테이블 이름];* 대신 필드 이름을 넣을 수 있다. 여러 개 넣는 것도 가능하다.SELECT * FROM [테이블 이름] WHERE [조건];where을 이용해 필터(조건)을 걸 수 있다.AND 또는 OR을 이용해 조건을 이어 붙일 수 있다.조건에는 =, <= 외에도 !=, <, >, >=, between, in, not in 등이 있다.BETWEEN [범위의 시작값] AND [범위의 끝값] : 양끝값을 포함한 범위 내의 값들만 가져온다.IN(조건1, 조건2) : 여러 조건을 한 번에 표시할 때 사용NOT IN() : 괄호 안 조건들에 포함되지 않는 데이터만 조회할 때 사용데이터 업데이트하기UPDATE [테이블 이름] SET 필드1이름=값, 필드2이름=값, ...WHERE [조건];where 절을 사용해 [조건]을 붙이지 않으면, 모든 데이터가 변경(업데이트)되니 주의해야 함데이터 삭제하기DELETE FROM [테이블 이름] WHERE [조건];[조건]을 붙이지 않으면 모든 데이터가 삭제되니 주의할 것!Spring에서 Database 사용하기사람이 아닌 스프링 서버가 MySQL 서버에 접근하게 하는 것application.yml 만들고 설정spring: datasource: url: "jdbc:mysql://localhost/library" username: "root" password: "5502" driver-class-name: com.mysql.cj.jdbc.DriverAPI 변경jdbcTemplate을 이용해 SQL을 날릴 수 있다. SQL을 만들어 문자열 변수로 저장한다. 이때 값이 들어가는 부분에 ?를 사용하면, 값을 유동적으로 넣을 수 있다.jdbcTemplate.update()는 INSERT, UPDATE, DELETE 쿼리에 사용할 수 있다(데이터에 변경을 주는 쿼리). 첫 파라미터로는 sql을 받고, ?를 데신할 값을 차례로 넣으면 된다.jdbcTemplate.query(sql, RowMapper 구현 익명클래스) : mapRow라는 함수를 override하고, override한 MapRow 안에서 sql의 실행 결과가 나오면 그 결과들의 데이터를 가져와 UserResponse(dto)로 바꿔준다. Section 3 - 역할의 분리와 스프링 컨테이너클린 코드가 중요한 이유 코드 : 요구사항을 표현하는 언어-> 개발자는 요구사항을 구현하기 위해 코드를 읽고 작성한다.-> 코드를 읽는 것은 필수적이고 피할 수 없으므로, 코드만 보고도 의미를 명확하게 파악할 수 있도록 코드를 작성해야 한다.클린 코드함수는 최대한 작게 만들고 한 가지 일만 수행하는 것이 좋다.클래스는 작아야 하며 하나의 책임만을 가져야 한다.Controller의 함수 1개를 역할에 따라 분리API의 진입 지점으로써 HTTP Body를 객체로 변환하고 있다. -> Controller의 역할로 남겨둠현재 유저가 있는지, 없는지 등을 확인하고 예외 처리를 해준다. -> Service의 역할로 분리SQL을 통해 실제 DB와의 통신을 담당한다. -> Repository의 역할로 분리 3가지 역할로 구분된 구조controller : API와 HTTP 역할 담당service : 분기 처리, 로직 담당repository : DB와의 접근 담당* controller는 service를 사용하고, service는 repostory를 사용함** DTO는 계층 간의 정보를 전달하는 역할(여러 계층을 왔다갔다 함)-> 이런 구조를 Layered Architecture라고 한다!과제과제3https://shimmer-exoplanet-946.notion.site/3-07f12f6102e24a509e57519f890b43ec?pvs=4자바의 람다식과 익명 클래스에 대해 공부해보는 과제였다. 강의를 들으면서 람다식이라는 개념을 처음 접했는데 과제를 통해 따로 공부할 수 있어서 좋았다. 수업을 들을 때도 익명 클래스 부분에서 버벅였는데, 과제를 진행하며 블로그 글을 읽어도 완전히 이해한 것 같지 자바에 대한 공부가 추가로 필요하겠다고 느꼈다.  회고인프런 워밍업 클럽 스터디에 참여한지 벌써 일주일이 지났다. 이번주에 다른 일들이 많이 겹쳐 내 생각보다 더 힘들었지만, 혼자서 공부하는 것보다 이점이 훨씬 많았다.진도표에 매일 공부할 분량이 체계적으로 정해져 있어 내가 별도로 학습 계획을 짜지 않아도 계획적으로 공부할 수 있었다. 여러명이 함께 강의를 수강하는 스터디 방식이라 강의 듣는 것을 미루지 않고 진도표에 맞춰 학습할 동기부여가 되었다. 추가적으로 주어지는 과제를 통해 배운 내용에 대해 스스로 학습하고 정리할 수 있는 것도 좋았다.강의 내용도 매우 흥미로웠다. 자바와 스프링에 대한 기초 지식부터, 백엔드 개발에 필요한 서버 관련 내용까지 차근차근 정리할 수 있었다. 또한 API 설계 및 개발 과정을 실습으로 진행하며 각 기능이 어떻게 동작하고 구현되는지 직접 코딩을 하며 익혔다.다른 일들이 겹쳐 과제를 많이 놓치기도 하고 실습 중 자바에 대한 이해가 부족하다는 것을 느껴 조금 아쉬운 1주차였지만, 2주차부터는 좀 더 많은 시간을 투자하고 자바에 대한 학습도 병행하며 더 충실한 일주일을 보낼 수 있도록 노력할 것이다.

백엔드인프런워밍업클럽스터디1기

이슬

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

짧지만 알찼던 3주간의 인프런 워밍업클럽 스터디 1기가 끝났다. 강의는 전부 수강완료했고, 과제는 1~10번까지 수행했다. 좋았던 점작년에 부트캠프를 참여하면서 자바스크립트를 공부한지 꽤 시간이 지났는데, 이후로는 주로 React.js나 타입스크립트, Next.js에 관심을 가지느라 오랜만에 다시 복습하게 되었다. 스터디에 참여하지 않았다면 진득히 다시 공부하기 어려웠을 것 같다. 그래도 스터디 이전에 모던자바스크립트 딥다이브 책을 읽어봤던 것이 강의를 이해하는 데에 큰 도움이 되어서 효과적으로 공부가 된 것 같다.스터디에서 과제의 완성 코드를 알려주지 않은 것이 제일 좋았다. 정답이 있는 코드가 없다는 말처럼 정답은 없고 영상만 보고 직접 고민해보고 기능을 구현했던 것이 너무 재밌었다. 정답 코드가 있었더라면 더 고민해보지 않았을 수도 있었을 것이다. (난 멍청이야..하면서) 다른 러너분들의 블로그에서 어떻게 구현했는지 살펴보며 깨달은 것들도 많았다. (다들 너무 멋져요)아쉬웠던 점강의를 수강하느라 목표였던 과제 모두 뿌시기는 성공하지 못했다. 총 14개의 과제 중에 자바스트립트 과제 7개, React.js 과제 3개를 만들어 10개를 수행했다. 막판 과제였던 4개의 리덕스와 Next.js 과제는 하지 못했는데, 그래도 열심히 했기 때문에 후회는 없다! 스터디는 끝이 났지만 5월이 다 끝나기 전에 나머지 과제도 마저 완성해보려 한다.강의를 듣고 스스로 고민하며 과제를 해본 것은 좋았지만, 그만큼 혼자 고민하는 것이 힘들기도 했다. 내 코드를 보고 다같이 씹고 뜯어줄 장이 필요했던 것 같다. 다음 기회에 용기를 내어서 같이 서로 코드를 구경하고 얘기해 줄 수 있는 환경을 만들어보거나 뛰어들어야겠다고 생각했다.보완할 점어떤 프로젝트를 만들고 싶다는 생각만 하고 행동하는 데에 시동이 오래 걸렸었다. 이번 스터디가 워밍업인 만큼, 행동에 옮기는데 많이 데워진 것 같다. 비슷한 스터디를 운영하거나 혼자서라도 계속 해보려한다. 보완할 점은 과제에 집중하느라 조금 설렁하게 들었던 강의 몇개를 다시 들어보고 내 것으로 만들고 싶다. 수행했던 과제들을 리팩토링까진 아니어도 다른 방식으로 구현할 수 있을지 생각해보는 시간도 가지고 싶다. 시간상 리액트 과제를 전부 JS로 했었는데, TS로 마이그레이션 해봐야겠다.

웹 개발인프런워밍업클럽FE1기발자국

슬프구나

인프런 워밍업 클럽 스터디 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 과제(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 과제(9번 과제)

[9번 과제(Day10) - 디즈니 플러스 앱]따라하며 배우는 리액트 A-Z학습 범위: Section 4 ~ 5https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/disney-plus-app과제 이미지- 구글 로그인, swiper, 모달- 유튜브 플레이, 검색, 상세페이지, 로그아웃고민한 것MovieModal mount될 때에는 애니메이션이 되고, unmount될 때에는 애니메이션 없이 툭 사라져버린다.내가 원하는 것은 모달이 꺼질때에도 애니메이션으로 사라지게 한 후, unmount되게 하는 것기존 코드// Row 컴포넌트 modalOpen && <MovieModal {...movieSelection} setModalOpen={setModalOpen} />이전 코드에서는 modalOpen 상태가 참일 때만 MovieModal이 렌더링되었지만, 변경된 코드에서는 MovieModal 컴포넌트 내부에서 직접적으로 modalOpen 상태를 조작할 수 있도록 했다.변경 코드 // Row 컴포넌트 <MovieModal {...movieSelection} modalOpen={modalOpen} setModalOpen={setModalOpen} /> // MovieModal 컴포넌트 const MovieModal = ({ ... modalOpen, setModalOpen, }) => { const ref = useRef(); const [showModal, setShowModal] = useState(false); const [animation, setAnimation] = useState(""); useOnClickOutside(ref, () => { // 2. 닫기 이벤트 setModalOpen(false); }); useEffect(() => { if (!modalOpen) { // 4. 사라지는 애니메이션으로 설정 setAnimation("animate-fade-down animate-reverse"); } else { setShowModal(true); setTimeout(() => { // 1. 나타나는 애니메이션으로 설정 setAnimation("animate-fade-up"); }, 10); } }, [modalOpen]); return ( showModal && ( <div className="animate-fade"> <article className={`${animation}`} ref={ref} onAnimationEnd={() => { // 3. 사라지는 애니메이션 끝이나면 showModal false if (!modalOpen) setShowModal(false); }} ></article> </div> ) ) };MovieModal 컴포넌트 내부에서는 modalOpen 상태에 따라 모달의 나타남과 사라짐을 제어하는 useEffect와 useState를 사용했다.modalOpen 상태가 변경될 때마다 useEffect가 실행되어 애니메이션 클래스를 설정하고, 애니메이션 효과를 주는 CSS 클래스를 적용시킨다.onAnimationEnd 이벤트 핸들러를 사용하여 애니메이션 종료 후에 showModal 상태를 변경하여 모달이 화면에서 완전히 사라지도록 처리했다. 추가한 것tailwind css + 스크롤바 숨기는 플러그인, 애니메이션 플러그인/** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { extend: {}, }, plugins: [ require("tailwind-scrollbar-hide"), require("tailwindcss-animated"), ], };회원만 접근 가능한 parent 컴포넌트 생성const UserGuard = () => { const navigate = useNavigate(); useEffect(() => { const profilePictureUrl = localStorage.getItem("profilePictureUrl"); // 로그인 시 저장한 구글 유저 프로필이미지가 없다면 LandingPage로 이동 if (!profilePictureUrl) { navigate("/"); } }, [navigate]); return <Outlet />; }; export default UserGuard; function App() { return ( <div className="App"> <Routes> <Route path="/" element={<Layout />}> <Route index element={<LandingPage />} /> 👇 <Route element={<UserGuard />}> <Route path="main" element={<MainPage />} /> <Route path=":movieId" element={<DetailPage />} /> <Route path="search" element={<SearchPage />} /> </Route> </Route> </Routes> </div> ); } export default App;로그인 상태일 때 랜딩 페이지 접근 불가const LandingPage = () => { const navigate = useNavigate(); useEffect(() => { const profilePictureUrl = localStorage.getItem("profilePictureUrl"); // 로그인 시 저장한 구글 유저 프로필이미지가 있다면 MainPage로 이동 if (profilePictureUrl) { navigate("/main"); } }, [navigate]); return (...) };

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

임하스

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

시작강의가 좀 밀려있긴 한데... 기간 안엔 다 들을 수 있을 거라 생각하며 차근차근 정리 중이다...정리자바스크립트 this 키워드메서드 내 this ⇒ 해당 객체(Object)함수 내 this ⇒ window 객체생성자 함수 내 this ⇒ 빈 객체(해당 인스턴스)forEach문 내 this ⇒ 두 번째 매개 변수 값, 없을 경우 window 객체 또는 해당 객체화살표 함수 내 this ⇒ 상위 스코프의 this예제메서드 내 ⇒ 해당 객체(Object)const audio = { title: 'a', play() { console.log('play this', this); }, }; audio.stop = function () { console.log('stop this', this); }; audio.play(); //play this { title: 'a', play: f } audio.stop(); //play this { title: 'a', play: f } 함수 내 ⇒ window 객체function playAudio() { console.log(this); } playAudio(); // Window 객체 생성자 함수 내 ⇒ 빈 객체(해당 인스턴스)function Audio(title) { this.title = title; // 이게 없으면 빈 객체 console.log(this); } const audio = new Audio('a'); // Audio { title: 'a' } forEach문 내 ⇒ 두 번째 매개 변수 값, 없을 경우 window 객체 또는 해당 객체const audio = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach(function (category) { console.log(`title: ${this.title}, category: ${category}`); // 함수 내에 있기 때문에 window 객체를 가리킴 }); }, }; audio.displayCategories(); const audio2 = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach( function (category) { console.log(`title: ${this.title}, category: ${category}`); }, { title: 'audio' } ); // forEach 두 번째 매개변수에 있는 값을 참조시킬 수 있음 }, }; audio2.displayCategories(); const audio3 = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach( function (category) { console.log(`title: ${this.title}, category: ${category}`); }, this // audio3 해당 객체를 참조 ); }, }; audio3.displayCategories(); 화살표 함수 내 ⇒ 상위 스코프의 thisconst audio = { title: 'audio', categories: ['rock', 'pop', 'hiphop'], displayCategories() { this.categories.forEach(function (category) { console.log(`title: ${this.title}, category: ${category}`); // 상위 스코프의 this(Lexical this) => audio { title : 'audio', categories: Array(3) } }); }, }; audio.displayCategories(); call, apply, bind함수에서 this 사용 시, window 객체가 아닌 값을 참조시키기 위해 사용함수명.call(thisArg[, arg1, arg2, ...]) : 함수를 호출하는 함수함수를 호출할 때, 첫 번째 매개 변수에 함수 내에서 사용할 this 값을 지정할 수 있음첫 번째 매개 변수로 null 또는 undefined가 전달되면 전역 객체가 this로 사용됨두 번째 ~ … 매개 변수로 호출할 함수에 전달될 인수 추가 가능const fullName = function (arg1, arg2) { console.log(`${this.firstName} ${this.lastName}`); console.log(`${arg1} ${arg2}`); }; const person = { firstName: 'Im', lastName: 'Hass', }; fullName.call(person, '매개변수1', '매개변수2'); // Im Hass // 매개변수1 매개변수2 함수명.apply(thisArg[, argArray]) : call()과 동일call()과 인수를 배열로 넣어주는 차이const fullName = function (arg1, arg2) { console.log(`${this.firstName} ${this.lastName}`); console.log(`${arg1} ${arg2}`); }; const person = { firstName: 'Im', lastName: 'Hass', }; fullName.apply(person, ['매개변수1', '매개변수2']); **함수명.bind(thisArg)**: 함수가 실행되지 않고 바인딩만 시켜줌함수를 바인딩할 때, 새로운 함수를 생성함 → 원본 함수와 동일한 코드를 가지지만 this가 바인딩된 값이 들어감 ⇒ 새로운 변수에 담거나 즉시 실행 시켜서 사용해야 함function func(language) { if (language === 'kor') { console.log(`language: ${this.korGreeting}`); } else { console.log(`language: ${this.engGreeting}`); } } const greeting = { korGreeting: '안녕', engGreeting: 'Hello', }; // 원본은 변하지 않기 때문에 undefined 출력 // func.bind(greeting); // func('kor'); // language: undefined // 1. 새 변수에 담아서 사용하기 // const boundFunc = func.bind(greeting); // boundFunc('kor'); // language: 안녕 // 2. 즉시 실행하기 func.bind(greeting)('kor'); Event Loop자바스크립트는 동기 언어지만, 다른 것의 도움(브라우저의 자바스크립트 엔진 또는 Node.js와 같은 런타임 환경)을 통해 비동기로 처리할 수 있음.ex) setTimeout(브라우저 api-window object 또는 node api-global object)setTimeout에 설정된 값이 0(즉시 실행)이어도 콜백 큐에서 호출 스택이 비어야 들어가기 때문에 순서가 다르게 출력됨자바스크립트 코드를 실행하기 위해 엔진이 필요한데, 자바스크립트 엔진에는 메모리 힙, 콜 스택 두 가지 주요 구성 요소가 있음Call Stack(호출 스택)코드 실행에 사용되는 메모리 구조함수 호출 및 반환을 추적함코드가 실행될 때 함수 호출은 스택에 추가되고, 함수가 완료되면 스택에서 제거됨Callback Queue(콜백 큐)비동기 작업의 콜백 함수들이 대기하는 대기열비동기 작업이 완료되면 해당 콜백 함수가 콜백 큐에 추가됨Event Loop(이벤트 루프)콜 스택과 콜백 큐를 계속 모니터링하여 콜 스택이 비어있을 때 콜백 큐의 첫 번째 콜백을 콜 스택으로 이동시킴내부 진행 참고 사이트 : https://kamronbekshodmonov.github.io/JELoop-Visualizer/Closure외부 함수 보다 생명 주기가 긴 내부 함수 중에서, 종료된 외부 함수의 값을 참조할 수 있는 내부 함수를 의미한다.클로저 사용 전(오류: b에 접근 불가)let a = 'a'; function funcB() { let c = 'c'; console.log(a, b, c); } function funcA() { let b = 'b'; console.log(a, b); funcB(); } funcA(); // a b // ReferenceError: b is not defined 클로저 사용 후(해결: 내부 함수로 변경)let a = 'a'; function funcA() { let b = 'b'; console.log(a, b); function funcB() { let c = 'c'; console.log(a, b, c); } funcB(); } funcA(); // a b // a b c 구조 분해 할당배열이나 객체의 속성을 해체하여 개별 변수에 담을 수 있게 하는 Javascript 표현식객체 구조 분해 할당let person = { name: 'hass', age: 30, phone: '123', address: { zipcode: 1234, street: 'rainbow', number: 42, }, }; let { address: { zipcode, street, number } } = person; console.log(zipcode, street, number); 별명 지정let people = [ { name: 'mike', age: 35, family: { mother: 'Jane', father: 'Harry', sister: 'samantha', }, }, { name: 'Tom', age: 25, family: { mother: 'Norah', father: 'Richard', brother: 'Howard', }, }, ]; for (let { name: n, family: { father: f }, } of people) { console.log(`Name : ${n}, Father: ${f}`); } // Name : mike, Father: Harry // Name : Tom, Father: Richard Map() 메서드배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환함array.map(callback(currVal[, index[, array]]])[, thisArg])첫 번째 매개 변수로 callback 함수가 들어가고, 두 번째 매개 변수에 this 바인딩 가능Filter() 메서드주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환함array.filter(callback(element[, index[, array]])[, thisArg])첫 번째 매개 변수로 callback 함수가 들어가고, 두 번째 매개 변수에 this 바인딩 가능Reduce() 메서드배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환함arr.reduce(reducerFunction(acc, curr, idx, src), initalValue)reducerFunction 4개의 인자를 가짐누산기(acc)현재 값(curr)현재 인덱스(idx)원본 배열(src)예제const result = [0, 1, 2, 3, 4].reduce(function (acc, curr, idx, src) { return acc + curr; }, 10); // console.log(result); // 20 acc curr idx src return value 1번째 호출 10 0 0 [0, 1, 2, 3, 4] 10 2번째 호출 10 1 1 [0, 1, 2, 3, 4] 11 3번째 호출 11 2 2 [0, 1, 2, 3, 4] 13 4번째 호출 13 3 3 [0, 1, 2, 3, 4] 16 5번째 호출 16 4 4 [0, 1, 2, 3, 4] 20  undefiend vs null공통점둘 다 원시 자료형undefiend의 타입은 undefiend, null 타입은 null(object로 나옴, strit 모드는 null) 유일한 타입undefined아무 값도 할당받지 않은 상태개발자의 의도 X, 자바스크립트 엔진이 초기화한 값null비어있는 값, 존재하지 않는 값개발자가 의도한 값null을 할당하면 변수가 이전에 참조하던 값을 명시적으로 참조하지 않겠다고 하는 것이므로, 자바스크립트 엔진이 변수의 메모리 공간에 대해 가비지 콜렉션을 수행함얕은 비교(Shallow Compare)숫자, 문자 등 원시 자료형은 값으로 비교배열, 객체 등 참조 자료형은 참조되는 위치를 비교IdentifiersuserName seasons isFinished enemies userCall StackAddress Value 0012ASF “Hass” 3241AF 5 416UHDI true 1235JFT HHSYDW1462 JFFI12HA KHS18205JAHeapAddress Value HHSYDW1462 ["ene1", "ene2", "ene3"] KHS18205JA {name: "hass", profession: "Drug dealer"}깊은 비교(Deep Compare)객체의 경우에도 값으로 비교Object Depth가 깊은 경우 : lodash 라이브러리의 isEqual() 사용Obejct Depth가 깊지 않은 경우: JSON.stringfy() 사용const obj1 = { a: 'a', b: 'b' }; const obj2 = { a: 'a', b: 'b' }; console.log(JSON.stringify(obj1) === JSON.stringify(obj2)); // true 얕은 복사, 얕은 동결얕은 복사 : 내부에 중첩된 값은 복사되지 않음(기존과 동일한 객체를 참조함, 하나를 변경하면 똑같이 변경됨)얕은 동결 : Object.freeze() 메서드, 객체를 동결하여 변경할 수 없도록 함.얕은 복사를 하는 연산자 : 전개 연산자, Oject.assign(), Array.from(), slice()깊은 복사내부 중첩된 값까지 복사함JSON.parse(JSON.stringify(object)) 또는 중첩된 부분에 전개 연산자 사용 { …aObj, cObj: {…aObj.cObj }}lodash 라이브러리를 활용structuredClone(object) 메서드 활용함수 선언문, 함수 표현식함수 표현식 : 함수를 만들고 변수에 할당, 익명 함수호이스팅 시, 함수 선언식만 영향을 받음함수 표현식은 인터프리터가 해당 코드 줄에 도달할 때만 로드됨함수 표현식은 정의된 로컬 변수의 복사본을 유지할 수 있도록 호이스팅 되지 않음즉시 호출(실행) 함수 표현식(IIFE(Immediately Invoked Function Expression))정의 하자마자 즉시 실행되는 함수를 의미함기본 형태 ( function () {} )()첫 번째 소괄호( ... ) : 전역 선언을 막고, IIFE 내부로 다른 변수의 접근을 막음두 번째 소괄호 …() : 즉시 실행 함수를 생성하는 괄호, 이를 통해 Javascript 엔진이 함수를 즉시 해석하고 실행함IIFE를 변수에 할당하면 IIFE 자체는 저장되지 않고, 함수가 실행된 결과만 저장할 수 있음IIFE로 Closure가 가능하게 만든 후, 내부 함수에서 외부 함수에 있는 변수에 접근되게 함사용 목적변수를 전역으로 선언하는 것을 피하기 위해IIFE 내부에 다른 변수들이 접근하는 것을 막기 위해익명 함수를 만들기 위해 (이름이 없기 위한 두 가지 조건 중 한 가지)이 함수를 할당 받을 변수를 지정해야 함이 함수를 즉시 호출해야 함// 이름은 선택 사항이라고 했지만 function 키워드로 만들 때 에러 발생 // function (a, b) { // Error: Identifier expected. // return a- b; // } // 조건1 const minus = function (a, b) { return a - b; }; // 조건2 (function (a, b) { return a - b; })(1, 2); IIFE 적용 전const score = () => { let count = 0; return { current: () => { return count; }, increment: () => { count++; }, reset: () => { count = 0; }, }; }; console.log(score); // () => {...} 함수 출력 console.log(score().current()); // 0 score.increment(); // count를 1 증가 시킴 console.log(score().current()); // 0 => score()를 다시 실행 시켜서 count가 0으로 초기화됐기 때문에 값이 증가하지 않음 IIFE 적용 후const score = (() => { let count = 0; return { current: () => { return count; }, increment: () => { count++; }, reset: () => { count = 0; }, }; })(); console.log(score); // { current: f, ... } console.log(score.current()); // 0 score.increment(); // count를 1 증가 시킴 console.log(score.current()); // 1 Intersection observer무한 스크롤, 이미지 lazy loading 등을 구현할 때 사용됨Lazy Loading 예제<!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Intersection Observer</title> <style> img { width: 400px; height: 300px; display: block; margin: 10px auto; } </style> </head> <body> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <img src="<https://via.placeholder.com/400x300>" alt="placeholder image" data-src="<https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300>"> <script> const observer = new IntersectionObserver( function (entries, observer) { console.log('entries', entries); entries.forEach((entry) => { if (entry.isIntersecting) { // 루트 요소와 타겟 요소가 교차하면 console.log('옵저버'); entry.target.src = entry.target.dataset.src; // dataset에 있는 url을 src에 넣어줌 observer.unobserve(entry.target); // Lazy loading 후에는 observer 해제 } }); }, { threshold: 1 } // 타겟 요소가 루트 요소와 100% 겹치면 콜백 함수 실행 ); const imgEls = document.querySelectorAll('img'); console.log(imgEls); imgEls.forEach((img) => { observer.observe(img); }); </script> </body> </html> 순수 함수(Pure Function)함수형 프로그래밍 패러다임의 한 부분특정 함수가 다른 함수에 미치는 예기치 못한 영향을 최소화하고, 함수를 만들고 실행할 때 어떤 결과값을 리턴할지 예측할 수 있다는 장점이 있음가능한 순수 함수를 사용하는 것이 좋음클린 코드를 위해서테스트를 쉽게 하기 위해서디버그를 쉽게하기 위해서독립적인 코드를 위해서두 가지 규칙이 있음같은 입력이 주어졌을 때, 언제나 같은 결과값을 리턴한다 (same input → same output)사이드 이펙트를 만들지 않는다 (동일한 input이지만 외부 값에 의해 변경되는 output → impure하게 만듦)커링(Curry Function)함수와 함께 사용하는 고급 기술 (다른 언어에도 존재하는 기술)f(a, b, c) 처럼 단일 호출로 처리하는 함수를 → f(a)(b)(c)와 같이 각각의 인수가 호출 가능한 프로세스로 호출된 후 병합될 수 있게 변환하는 것즉, 함수를 호출하는 것이 아닌 변환하는 것예제const sum = (x, y) => x + y; console.log(sum(10, 20)); // 30 const curriedSum = (x) => (y) => x + y; console.log(curriedSum(10)); // f() y => x + y console.log(curriedSum(10)(20)); // 30 const makeFood = (ingredient1) => { return (ingredient2) => { return (ingredient3) => { return `${ingredient1} ${ingredient2} ${ingredient3}`; }; }; }; // const hamburger = makeFood('Bread')('Ham'); // console.log(hamburger); // (ingredient3) => {...} 완성되지 않음 const hamburger = makeFood('Bread')('Ham')('Tomato'); console.log(hamburger); // Bread Ham Tomato => 3개의 인수를 다 넣어줘야 완성됨 // makeFood 축약 const cleanMakeFood = (ingredient1) => (ingredient2) => (ingredient3) => `${ingredient1} ${ingredient2} ${ingredient3}`; const newHamburger = cleanMakeFood('Bread')('Ham')('Tomato'); console.log(newHamburger); // Bread Ham Tomato // 일반 log 함수 function log(date, importance, message) { alert(`[${date.getHours()} : ${date.getMinutes()}]: [${importance} ${message}]`); } log(new Date(), 'DEBUG', 'same bug'); // 커리 함수 function curry(f) { return function (a) { return function (b) { return function (c) { return f(a, b, c); }; }; }; } const curriedLog = curry(log); curriedLog(new Date())('DEBUG')('same bug'); // 동적으로 추가되는 커리 함수 (매개 변수 개수 상관 없이 커리 함수로 변경 가능) function curry2(func) { return function curried(...args) { if (args.length >= func.length) { return func.apply(this.args); } else { return function (...args2) { return curried.apply(this, args.concat(args2)); }; } }; } // const sum = (x, y, z) => x + y + z; const sum = (x, y, z, j) => x + y + z + j; const curriedSum = curry2(sum); // console.log(curriedSum(1)(2)(3)); console.log(curriedSum(1)(2)(3)(4)); strict modeJavascript의 엄격 모드제한된 버전을 선택하여 암묵적인 “느슨한 모드(sloppy mode)”를 해제하기 위한 방법시멘틱스에 몇 가지 변경이 일어남기존에는 무시되던 에러를 throwingJavascript 엔진의 최적화 작업을 어렵게 만드는 실수를 바로 잡음. 가끔 엄격 모드의 코드가 비엄격 모드와 동일한 코드 보다 더 빠르게 작동하도록 만들어줌.적용 방법파일에 "use strict" 지시자 입력함수 안에 "use strict" 지시자 입력하여 해당 함수만 적용 가능class를 사용하면 자동으로 strict 모드 적용자바스크립트 파일의 타입을 module로 설정하면 strict 모드 적용<script src="script.js" type="module">기존과 차이점할당할 수 없는 값에 할당하려 할 때 ex) 선언되지 않은 변수, undefiend, NaN수정할 수 없는 값을 수정하려 할 때 ex) readOnly 객체동일한 이름의 파라미터가 존재할 때함수 내의 this가 undefiend가 됨

프론트엔드인프런워밍업스터디클럽FE1기발자국

이슬

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

The four Ls 🍀좋았던 것(Liked)성공적으로 진행된 부분이나 긍정적인 경험스터디 과제로 책 리스트 나열, 깃헙 파인더, 비밀번호 생성기, 타이핑 테스트 앱을 바닐라JS로 만들었고, 예산 계산기 앱을 React.js로 만들었다. 모든 과제를 클리어해보는 것이 목표 중 하나인데 이번 주에 총 5개의 과제를 완성시켜서 뿌듯했다. 배운 것(Learned)새로운 지식, 기술 또는 교훈이전에도 항상 헷갈리고 어떻게 사용해야할지 가늠이 되지않았던 최적화 부분을 여러번 학습했다.섹션3의 React.memo 를 이용한 렌더링 최적화,useCallback 을 이용한 함수 최적화,useMemo를 이용한 결과 값 최적화그 중에서 useCallback과 useMemo의 차이점은 알지만 활용하는 부분이 어렵다고 느껴졌는데, 이번 강의를 통해서 useCallback을 이해하며 과제에 적용해보았다. 부모 컴포넌트가 리렌더링될 때마다 함수가 재생성되면, 해당 함수가 자식 컴포넌트의 props로 전달되면서 자식 컴포넌트도 다시 리렌더링될 수 있다.따라서 useCallback을 사용하면 함수의 재생성을 방지하여 이러한 문제를 해결할 수 있고, 이를 통해 자식 컴포넌트의 불필요한 리렌더링을 최적화할 수 있다.부족했던 것(Lacked)부족한 정보, 기술 등 파악하고 개선할 수 있는 방안과제를 하느라 React.js강의의 Todo앱 class 컴포넌트에서 함수 컴포넌트로 변경하는 것과 드래그앤드랍 기능 부분을 실습해보지 못했는데, 추후에 실습해보려고 한다. (+ Hooks와 불변성 개념 복습해보기) 눈으로 봤을 때보다 직접 작성해보면서 느껴보는 것이 개인적으로 기억에 잘 남을 것 같다. 과제 기록을 하면서 그냥 GIF 이미지와 깃헙 링크만 올렸었는데, 작업하면서 생각했던 것들을 추가 작성해두었다. 큰 작업은 아니지만 어떤 점을 신경쓰면서 임했는지 다시 보면 좋을 듯 하다.바라는 것(Longed for)미래에 개선되었으면 하는 부분이나 바라는 상황앞으로 남은 강의도 집중해서 듣고, 남은 과제도 차근차근 해보도록 하겠다! 특히 경험해보지 않은 TDD 실습과 도커에 신경쓰며 들어보려고 한다. 화이팅!

웹 개발인프런워밍업클럽FE1기발자국

한지은

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

day6 - 6번 과제https://github.com/jjajan2/inflearn_study/tree/main/day6_password_generator처음에는 아스키코드 번호를 이용해서 만들려고했지만 특수문자의 경우 각각 적어줘야되기 때문에 한번에 문자열을 넣는 방식을 선택했다. range를 사용할 일이 많이 없어서 거의 처음으로 다뤄보았는데, 스타일의 경우 따로 div를 넣어서 변경해주는 방식으로 하는것같았다. 이 부분은 나중에 추가적으로 보고 적용해봐야겠다!day7 - 7번 과제https://github.com/jjajan2/inflearn_study/tree/main/day7_typing_speed7가지의 과제를 하면서 타이밍 스피트 테스트 과제가 생각보다 어렵게 느껴졌다. 한 문장을 완성하고 다음문장으로 넘어갈때 Error와 Accuracy가 누적되지않고 0으로 초기화되는 현상이 있었는데, totalErrors와 totalAccuracy를 추가해서 한 문장이 끝날때마다 누적하는 방식으로 해결했다! 생각해보면 별거 아니었던것같은데 바로 생각해내지 못해서 아쉽다.그리고 7번 타이핑 스피드 과제와 3번 퀴즈 과제에서 json파일로 데이터를 따로 빼서 사용했는데, 과제를 하던 도중 콘솔창에 에러가 생기는걸 발견했다.이 에러 메시지는 import 구문에서 assert 옵션을 사용하는 것이 더 이상 권장되지 않으며, 향후 V8 v12.6과 Chrome 126부터 지원이 중단될 것이라는 내용이고, 과거에 정적 분석을 위해 사용되었다고 한다. 이를 대신해 with을 사용하도록 권장되고있다. 라는것을 알게되었다. 하지만 fetch를 사용하는 방식으로 변경했다. 7번과제를 할때까지 몰랐던거 보면 3번과제를 할때 콘솔창을 제대로 확인하지 않았던것 같다ㅠㅠ 에러를 꼼꼼히 확인하는 습관을 기르자..!그리고 타이머를 체크하는 부분을 setTimeout과 반복문을 사용해서 만들었는데, 다 만들고나니까 코드가 허술하다는 생각이 들었다.. 그래서 setInterval을 사용하는 방식으로 변경했다!day9 - 8번 과제https://github.com/jjajan2/inflearn_study/tree/main/day9_budget_calculator예산 계산기 과제를 스터디 팀원분들과 함께 코드리뷰를 했다!총 합계를 계산하는 부분을 map을 사용해서 계산하는 코드를 작성했는데, 팀원분이 reduce를 사용하지 않은 이유를 물어보셨다..! 사실 reduce를 생각하지 못했어서 바로 reduce를 사용하는 방식으로 변경했다. (감사합니다! 😊)나도 팀원분들의 코드를 보면서 피드백을 해드리고 싶었는데, 아직 실력이 부족해서 딱히 해드릴수 있는게 없었다..확실한건 다른사람이 작성한 코드를 보는건 많은 도움이 되는것같다!인프런 스터디가 끝나도 다른 여러가지 스터디, 프로젝트를 해보면서 실력을 많이 키워야겠다

프론트엔드워밍업클럽FE1기

슬프구나

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

2주차는 드디어 본격적으로 리액트와 Next.js 를 하는 주간이었다! useState()useEffect()useLayoutEffect()useRef()useMemo()useCallback()useContext()useReducer()등에 다양한 훅을 복습 하였다. 리액트 훅은 16.8 에 추가가된 기능이다. 등장하면서, 클래스 컴포넌트의 시대는 저물었다. 리액트 훅 함수는 반드시 함수 컴포넌트에서만 사용해야만 한다.리액트 훅은 조건에 또는 반복문에 따라 호출이 되면 안된다.useState() 는 함수 컴포넌트내에서 상태를 관리하는 훅이다.useEffect(), useLayoutEffect 는 함수 생명주기에 대응하는 훅이다.2번째 인자로 배열을 가지는(의존성 배열) 훅들은 사용할 때 항상 주의해야한다. -> 안그러면 클로저로 인해 이전 값을 계속 참조한다.useEffect() 훅은 함수 컴포넌트가 반환하는 JSX 구문 즉 ReactElement 가 실제DOM에 렌더링 된 후 paint 후에 비동기로 동작하는 반면 useLayoutEffect() 훅은 동기적으로 페인트 이전에 실행이 된다. 리액트 함수 컴포넌트 생애주기는Render 단계와 Commit 단계로 나뉜다.Render 단계에서는 함수가 실행되어, JSX 를 반환한다. JSX 는 ReactElement 를 생성하는 함수로 변환이 되어 ReactElement 를 생성한다. 이걸 가지고 가상 DOM을 만든다. 이전에 만든 가상DOM이 있다면 이를 비교하는 작업을 한다.Commit 단계에서는 가상DOM을 실제 DOM에 반영한다.  Next.js는 React 프레임워크를 기반으로 한 웹 개발 도구로, 서버 사이드 렌더링(SSR), 정적 사이트 생성(SSG), 그리고 최근에는 클라이언트 사이드 렌더링(CSR)과 같은 다양한 렌더링 방식을 지원합니다. 각 렌더링 방식에 대한 설명과 특징을 살펴보겠습니다. 서버 사이드 렌더링 (Server-Side Rendering, SSR)개념: SSR은 각 요청이 있을 때마다 서버에서 HTML을 동적으로 생성하여 클라이언트에게 전송하는 방식입니다. 이는 초기 페이지 로딩 시 서버에서 모든 페이지를 미리 렌더링하고, 완성된 HTML을 클라이언트에게 보내줍니다.장점: 검색 엔진 최적화(SEO)에 유리하고, 초기 로딩 시 사용자에게 완성된 페이지를 보여줄 수 있어 사용자 경험이 개선됩니다.단점: 서버 부하가 늘어날 수 있으며, 렌더링 시간이 길어질 수 있습니다.정적 사이트 생성 (Static Site Generation, SSG)개념: 빌드 시 모든 페이지를 미리 HTML로 변환하여 저장합니다. 사용자의 요청에 따라 미리 생성된 HTML 파일을 그대로 전송합니다.장점: 서버 부하가 감소하고, 빠른 로딩 속도를 제공합니다. 보안이 강화되며, CDN을 통한 쉬운 배포가 가능합니다.단점: 실시간 업데이트가 필요한 경우 적합하지 않을 수 있습니다. 사이트 빌드 시간이 길어질 수 있습니다.클라이언트 사이드 렌더링 (Client-Side Rendering, CSR)개념: 초기 로딩에서는 기본적인 HTML과 JavaScript를 로드하고, 이후 모든 렌더링은 브라우저에서 JavaScript를 통해 이루어집니다.장점: 서버 부하가 줄어들고, 사용자 인터랙션에 따라 동적인 페이지 변화를 빠르게 구현할 수 있습니다.단점: 초기 로딩 시 필요한 자원이 많아져서 속도가 느려질 수 있으며, SEO에 불리할 수 있습니다.증분형 정적 재생(Incremental Static Regeneration, ISR)개념: ISR을 사용하면, 빌드 시 생성된 정적 페이지를 배포 후에도 필요에 따라 업데이트할 수 있습니다. 이는 특정 페이지를 새로운 데이터로 다시 생성하게 할 수 있는 옵션을 제공하여, 정적 사이트의 장점을 유지하면서도 동적 콘텐츠의 필요성을 충족시킬 수 있습니다. 장점성능과 캐싱: 정적 파일로 서빙되기 때문에 빠르고, CDN을 통해 캐싱될 수 있어 성능이 우수합니다.스케일링: 정적 파일을 사용하기 때문에 트래픽 증가에 따른 서버 부하가 적습니다.신선도: revalidate 옵션을 통해 정적 콘텐츠임에도 불구하고 일정 기간마다 콘텐츠를 자동으로 업데이트하여 최신 상태를 유지할 수 있습니다.단점복잡성: ISR 설정과 관리는 일반적인 SSG나 SSR에 비해 복잡할 수 있습니다.비용: 재생성 로직에 따라 추가 서버 자원이 필요할 수 있으며, 이로 인해 비용이 발생할 수 있습니다.Next.js 에서는 개발자를 위해 다양한 기능을 제공해 주고 있다.파일 기반 라우팅api 라우팅Image 최적화Metadata API등 다양해서 좋다. 그런데 프로젝트를 작성해나가다보면 예약어 파일들이 너무 많아져서 큰 회사들은 이걸 어떻게 관리하는지 궁금해진다.

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

이혜리

[인프런 워밍업 클럽 스터디1기] 백엔드 - 6차 과제

진도표 6일차와 연결됩니다우리는 스프링 컨테이너의 개념을 배우고, 기존에 작성했던 Controller 코드를 3단 분리해보았습니다. 앞으로 API를 개발할 때는 이 계층에 맞게 각 코드가 작성되어야 합니다! 🙂과제 #4 에서 만들었던 API를 분리해보며, Controller - Service - Repository 계층에 익숙해져 봅시다! 👍   controller, service, repository로 분리해보자.  파일의 폴더 구조는 아래와 같다. controller, domain, dto, repository, service 폴더로 이루어져 있으며, 각각에 해당하는 파일들이 위치한다.  FruitController.javapackage com.group.libraryapp.controller.fruit; import com.group.libraryapp.domain.Fruit; import com.group.libraryapp.dto.fruit.FruitCreateRequest; import com.group.libraryapp.dto.fruit.FruitOverviewResponse; import com.group.libraryapp.service.FruitService; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.bind.annotation.*; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @RestController public class FruitController { private final FruitService fruitService; public FruitController(FruitService fruitService) { this.fruitService = fruitService; } //문제1 @PostMapping("/api/v1/fruit") public void saveFruit(@RequestBody FruitCreateRequest request) { fruitService.saveFruit(request); } //문제2 @PutMapping("/api/v1/fruit") public void saledFruit(@RequestParam int id){ fruitService.saleFruit(id); } //문제3 @GetMapping("/api/v1/fruit/stat") public FruitOverviewResponse overviewFruit(@RequestParam String name){ return fruitService.overviewFruit(name); } } controller 파일은 심플하게 service의 메소드를 불러오는 방식으로 리팩토링했다.즉, 메소드만 명시하고, 실제적인 일은 service, controller가 한다는 뜻!위와 같이 코드를 짬으로서, api 의 진입점의 역할을 잘 하고 있다 볼 수 있다.FruitService.javapackage com.group.libraryapp.service; import com.group.libraryapp.domain.Fruit; import com.group.libraryapp.dto.fruit.FruitCreateRequest; import com.group.libraryapp.dto.fruit.FruitOverviewResponse; import com.group.libraryapp.repository.FruitRepository; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; import java.util.List; @Service public class FruitService { private final FruitRepository fruitRepository; private FruitOverviewResponse fruitOverviewResponse; public FruitService(@Qualifier("sql") FruitRepository fruitRepository, FruitOverviewResponse fruitOverviewResponse) { this.fruitRepository = fruitRepository; this.fruitOverviewResponse = fruitOverviewResponse; } public void saveFruit(FruitCreateRequest request){ fruitRepository.saveFruit(request.getName(),request.getPrice(),request.getWarehousingDate()); } public void saleFruit(int id) { fruitRepository.saleFruit(id); } public FruitOverviewResponse overviewFruit(String name) { List<Fruit> list = fruitRepository.overviewFruit(name); long notsalesamount = 0; long salesamount = 0; for(Fruit e : list){ if (e.getSaled() == 1) notsalesamount += e.getPrice(); else salesamount += e.getPrice(); } fruitOverviewResponse.setNotSalesAmount(notsalesamount); fruitOverviewResponse.setSalesAmount(salesamount); return fruitOverviewResponse; } }FruitService는 FruitRepository의 메소드를 불러오는 역할과 동시에 유저가 있는지 없는지를 확인하고 예외처리를 하는 부분이다.위 클래스의 overviewFruit 함수에서선별한 정보를 아래와 같은 HTTP 응답 Body로 반환하기 위한 처리를 하도록 수정하였다. list 형태의 반환값을 FruitRepository의 메소드로 부터 받은 후, 이를 FruitOverviewResponse 객체로 반환하는 역할을 하고 있다.  FruitRepository.javapackage com.group.libraryapp.repository; import com.group.libraryapp.domain.Fruit; import java.time.LocalDate; import java.util.List; public interface FruitRepository { public void saveFruit(String name, long price, LocalDate warehousingDate); public void saleFruit(int id); public List<Fruit> overviewFruit(String name); }  문제2의 요구사항을 위해, repository를 바꿔가며 동작시킬 수 있도록 강의에서 이 경우에 interface를 작성하였기 때문에 interface 클래스를 작성하였다. @Primary 어노테이션 대신, @Qualifer 어노테이션을 FruitService 클래스에 사용했다.FruitMemoryRepository.javapackage com.group.libraryapp.repository; import com.group.libraryapp.domain.Fruit; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.util.ArrayList; import java.util.List; @Repository public class FruitMemoryRepository implements FruitRepository{ private final JdbcTemplate jdbcTemplate; private List<Fruit> memory = new ArrayList<>(); private int num = 0; public FruitMemoryRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void saveFruit(String name, long price, LocalDate warehousingDate) { memory.add(new Fruit(num+1,name, warehousingDate,price,1)); } public void saleFruit(int id){ for (Fruit e : memory){ if (e.getId() == id) e.setSaled(0); else throw new IllegalArgumentException(); } } public List<Fruit> overviewFruit(String name){ List<Fruit> list = new ArrayList<>(); for (Fruit e : memory){ if (e.getName().equals(name)){ list.add(e); }else throw new IllegalArgumentException(); } return list; } }위 클래스는 클래스 내에 List<Fruit> 를 만들어 저장함으로써, 프로그램을 재실행시키면, 기존 정보가 없어지는 repository이다. db와 연결이 없다.FruitMySqlRepository.javapackage com.group.libraryapp.repository; import com.group.libraryapp.domain.Fruit; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import java.time.LocalDate; import java.util.List; @Repository @Qualifier("sql") public class FruitMySqlRepository implements FruitRepository{ private final JdbcTemplate jdbcTemplate; public FruitMySqlRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public void saveFruit(String name, long price, LocalDate warehousingDate) { String sql = "INSERT INTO fruits(name, warehousingDate, price,saled) Values (?,?,?,?)"; jdbcTemplate.update(sql, name, warehousingDate,price, 1); } public void saleFruit(int id){ String readSql = "SELECT * FROM fruits WHERE id = ?"; boolean fruitNotExist = jdbcTemplate.query(readSql, (rs,rowNum)-> 0,id).isEmpty(); if (fruitNotExist){ throw new IllegalArgumentException(); } //fruit exists String sql = "UPDATE fruits SET saled = ? WHERE id = ?"; jdbcTemplate.update(sql, 0,id); } public List<Fruit> overviewFruit(String name){ String readSql = "SELECT * FROM fruits WHERE name = ?"; List<Fruit> list = jdbcTemplate.query(readSql, (rs, rowNum) -> { String rs_name = rs.getString("name"); long rs_price = rs.getLong("price"); LocalDate rs_warehousingDate = rs.getDate("warehousingDate").toLocalDate(); int rs_saled = rs.getInt("saled"); return new Fruit(rs_name,rs_warehousingDate,rs_price,rs_saled); }, name); if (list.isEmpty()) throw new IllegalArgumentException(); return list; } }위 부분은 반대로 db와의 연결이 있고, 직접적인 sql 문을 사용하여 작성하였다. @Qualifier 어노테이션이 클래스 위에 붙어있는 것을 확인할 수 있다.회고controller 클래스만 쓰면, 여러 기능이 뭉쳐있어 코드의 가독성이 떨어지는데,위와 같이 3단 분리 및 interface 를 활용하니, 전보다는 클린코드로 작성이 된 것 같다.

백엔드워밍업1기6차과제백엔드

이슬

인프런 워밍업 클럽 스터디 1기 FE 과제(4번, 5번, 6번 과제)

[4번 과제(Day5) - 책 리스트 나열 앱]따라하며 배우는 자바스크립트 A-Z학습 범위: Section 5 ~ 6https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/book-list-app과제 이미지input 전체에 값의 유무에 따라 submit 버튼 활성화를 설정해줘서 정확한 제출 요청이 되도록 작업했다.setTimeout으로 추가, 삭제 알림이 3초 뒤에 사라지게 했다.리스트 그룹에 이벤트리스너를 설정해줘서 이벤트타겟의 태그가 버튼이라면 삭제버튼을 가진 부모요소를 삭제하도록 이벤트 위임으로 작업했다.[5번 과제(Day5) - Github Finder 앱]따라하며 배우는 자바스크립트 A-Z학습 범위: Section 5 ~ 6https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/github-finder-app과제 이미지import { Octokit } from "https://esm.sh/@octokit/core";위 스크립트를 import를 작동시키기위해서 html에 작성한 script태그에 type을 module로 설정하였다.유저의 프로필 정보는 고정적이라 값만 제외하고 퍼블리싱했고, 레포지토리 리스트는 script에서 innerHTML으로 한번에 삽입했다. li 태그를 계속 create하는 것보다 효율적이라고 생각했다.!username && alert("유저 이름을 입력해주세요."); // 변경 전 if (!username) return alert("유저 이름을 입력해주세요."); // 변경 후둘 다 username가 비어있거나 존재하지않을 때에만 조건이 참이 된다.변경 전 usename이 빈 칸일 경우 알림창이 뜨도록 조건식을 작성했는데, 알림창을 한번 나타나게하면 조건식이 끝나도 함수를 이어서 실행시켰다.내가 원하는 건 조건이 참이면 함수를 중지하는 로직을 원하기때문에 return문이 들어갈 수 있도록 조건식을 조건문으로 변경하였다.식과 문을 잘 구별해서 쓰자.[6번 과제(Day6) - 비밀번호 생성 앱]따라하며 배우는 자바스크립트 A-Z학습 범위: Section 7 ~ 8https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/password-generator-app과제 이미지비밀번호를 생성하는 generator 객체를 활용해보았다.generator 안에 아래 작성된 최소, 최대 길이에 맞게 참이 될때만 실행하도록 while 조건문을 설정했다.navigator.clipboard로 복사하기 기능을 구현했다.generator를 이번 강의에서 처음 배우고 사용해봤는데 목적에 맞게 가독성이 좋아보여서 흥미로웠다. 하지만 generator로 구현하는 것과 일반 함수로 구현하는 것의 차이를 아직은 잘 모르겠다.

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

이슬

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

[1번 과제(Day2) - 음식 메뉴 앱]따라하며 배우는 자바스크립트 A-Z학습 범위: Section 1 ~ 3https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/food-menu-app과제 이미지데이터를 json파일로 만들어놓고 처음 로드할 때, 불러와서 작업했다.강의에서 배운 이벤트위임을 활용해서 버튼 그룹에 이벤트리스너를 설정해주고 이벤트타겟의 태그가 버튼일 때에만 메뉴를 필터링할 수 있는 함수를 실행하게 했다.[2번 과제(Day3) - 가위 바위 보 앱]따라하며 배우는 자바스크립트 A-Z학습 범위: Section 4(1~8)https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/rock-scissors-paper-app과제 이미지남은 횟수가 끝나면 결과를 알려주는 부분을 잊고 완성했다가 다시 이어서 작업했다.다시 도전하기를 누를때 화면을 초기화하는 함수를 만들었다.[3번 과제(Day4) - 퀴즈 앱]따라하며 배우는 자바스크립트 A-Z학습 범위: Section 4(9~17)https://github.com/helloleesul/inflearn-warmup-club-study/tree/main/js-quiz-app과제 이미지1번 처럼 데이터를 json파일로 만들어놓고 처음 로드할 때, fetch로 불러와서 작업했는데 데이터 객체 안에 문제와 옵션들, 그리고 answer(정답) 키값을 넣었었다. 하지만 브라우저 네트워크에 답까지 노출이 되는 게 문제라고 생각됐다.그래서 answer(정답) 키값을 없애고, 문제와 옵션들만 데이터로 구성하고 script 안에서 정답 유무를 가릴 수 있게 바꿨다. 풀이는 문제를 배열로 해체하고 ×를 *으로, ÷를 /으로 바꾼 후(-,+는 그대로) eval()함수를 쓰지않고 new Function() 새로운 함수를 생성해서 풀이하게 했다. 

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

mingle

[인프런 워밍업 클럽 스터디 BE 1기] 첫 번째 발자국

늦었지만 올려보는 첫 번째 발자국참여 계기취업 한지 벌써 2년이 되어간다. 일을 할수록 부족한 나를 마주하는 순간이 많았다. 그래서 항상 공부하겠다고 다짐했지만, 퇴근 후 공부는 생각보다 쉽지 않았고…. ㅎㅎ.. 그런 와중에 인프런에 들어왔다가 워밍업 클럽 스터디 배너를 보고 관심이 갔다. BE 강의 커리큘럼을 보니 회사에서 사용하지 않는 기술도 있어 더 흥미가 갔고 공부하기 좋은 기회라고 생각해서 지원하게 됐다. 배운 내용환경 세팅, HTTPHTTP Method(GET, POST, PUT, DELETE)를 활용한 간단한 API 개발MySQL과 DDL, DMLSpring과 DB 연결. CRUD API 개발.관심사의 분리 가장 인상 깊은 강의는 리팩토링이다. 회사 일을 하다가 내가 짠 코드에 대해 다른 직원분으로부터 문의를 받은 적이 있다. 시간에 쫓겨 작성한 부분이었고.. 강의를 들으면서 매우 많이 찔렸다. 아무리 바빠도 이후에 내 코드를 읽을 동료, 나를 위한 개발을 해야겠다고 생각했다. 화이팅~스터디에서 가장 만족하는 점은 과제다. 과제를 하다 보면 관련된 다른 궁금한 점이 생기고, 계속 샛길로 빠지게 된다. 한 과제를 하는데 하루 종일 걸린다ㅎㅎ.. 기한이 2일이라서 다행이다. 과제 덕분에 더 많이 배울 수 있었고 재밌었다. 이번 주에 배울 내용과 할 과제도 기대가 된다. 과제

백엔드BE워밍업1기

채널톡 아이콘