블로그

정지형

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

💡 1주차 회고강의를 듣기만 하고 따로 적어두진 않아서 정리하는데 오래걸렸다...! 다음주부터는 들으면서 해야겠다.미션같은 경우 순수 js로 구현해본적이 없어서 어려울거라 생각했는데 막상 해보니 괜찮았다.단 효율적인 코드로 작성한건지는 모르겠다...! 일단 기능만 구현하고 스타일은 꾸미지 않았는데 시간 나는 대로 틈틈히 스타일도 추가로 작업해야겠다.역시 스타일이 안들어가니 허전해.. 🤔 직장인이라서 진도를 따라갈 수 있을까 했는데 이번주와 다음주에 공휴일이 있어서 다행이다. 럭키비키잖아🍀다음주도 열심히 뚠뚠 🐜 가보자고!📝 강의 내용 요약 섹션 2 자바스크립트 기초console객체브라우저의 디버깅 콘솔(Firefox 웹 콘솔 등)에 접근할 수 있는 메서드를 제공log뿐만 아닌 time, table, clear...다양한 메서드가 있음.var, let, const변수 선언 방식var - 중복 선언과 재할당 가능 let(ES6) - 중복 선언 불가, 재할당 가능const(ES6) - 중복 선언과 재할당 불가유효한 참조 범위(scope)함수 레벨 스코프(function-level scope) - var블록 레벨 스코프(block-level-scope) - let, const호이스팅(Hoisting)코드가 실행되기 전 변수 및 함수 선언이 로컬 범위의 맨 위로 끌어올려지는 경우를 말함.변수생성 과정선언단계 =[ TDZ(일시적 사각지대) ] => 초기화단계(undefined값 할당) => 할당 단계 (값 할당)타입별 변수 호이스팅var - 선언, 초기화함수 선언문 : 선언, 초기화, 할당let, const - 선언 (선언 전 접근시 TDZ로 오류발생)자바스크립트 타입 (동적 타입)원시 타입 (number, string, boolean,undefined, null, symbol• bigInt)고정된 크기로 Call Stack 메모리에 저장, 실제 데이터가 변수에 할당참조 타입 (object, arrays, functions, classes, ...)데이터 크기가 정해지지 않고 Call Stack 메모리에 저장, 데이터의 값이 heap에 저장되며 변수에 heap 메모리의 주소값이 할당 타입변환js 변수는 새 변수 및 다른 데이터 유형으로 변환할 수 있습니다.1. js 함수를 사용하여,2. js 자체에 의해 자동으로 • 문자열과 숫자: + 연산 시 숫자가 문자열로 변환됨.  • 숫자 연산: -, *, /에서 문자열이 숫자로 변환됨.• 비교: == 연산 시 두 값 중 하나가 다른 타입으로 변환됨.  • Boolean 컨텍스트: if나 while 같은 논리적 평가 시 다양한 값이 true나 false로 변환됨.  • 숫자와 불리언: 숫자 연산 시 true는 1, false는 0으로 변환됨.Math객체Template Literals - backtick(`) 줄바꿈을 쉽게할 수 있고 문자열 내부에 표현식 포함할 수 있게됨.Loops루프의 종류for - 코드 블록 여러번 반복for/in - 객체의 속성을 따라 반복while - 지정된 조건이 true인 동안 코드 블록 반복do/while - 조건이 true인지 검사하기 전 코드 블록 한 번 실행 이후 조건이 true인 동안 루프 반복섹션 3 Window 객체 및 DOMwindow Object브라우저에 의해 자동으로 생성되며 웹 브라우저의 창(window)를 나타냅니다.또한 window는 브라우저의 객체이지 js의 객체가 아닙니다.이 window 객체를 이용해서 1.이 브라우저 창에 대한 정보와 제어를 할 수 있고 2.var 키워드로 변수를 선언하거나 함수를 선언하면 이 window 객체의 프로퍼티가 됨.dom(문서 객체 모델) - 메모리에 웹 페이지 문서 구조를 트리구조로 표현해서 웹 브라우저가 HTML 페이지를 인식하게 해줌.웹 페이지 빌드 과정(Critical Rendering Path CRP)웹 브라우저가 HTML 문서를 읽고, 스타일 입히고 뷰포트에 표시하는 과정document object브라우저 내에서 콘텐츠를 보여주는 웹페이지 자체객체 사용시 웹 페이지의 상태와 모든 HTML 태그에 접근 가능요소 탐색요소 생성 createElement요소 삭제 removeChild요소 교체 replaceChild섹션 4 EventEventListener - 이벤트가 발생했을때 어떠한 액션을 위한 함수를 호출. 해당 함수가 이벤트 리스너addEventListerner() - 이벤트 리스너를 객체나 요소에 등록해야 사용가능. 등록해주는 함수Event 종류UI 이벤트키보드 이벤트마우스 이벤트포커스 이벤트폼 이벤트Event Bubbling가장 깊게 중첩된 요소에 이벤트가 발생했을 때 이벤트가 위로 전달되는 것[출처 : 저자, 따라하며 배우는 자바스크립트 A-Z 강의 자료 #3 Event,인프런 강의]Event Capturing제일 상단에 있는 요소에서 아래로 이벤트가 내려오는 것[출처 : 저자, 따라하며 배우는 자바스크립트 A-Z 강의 자료 #3 Event,인프런 강의]이벤트의 3단계 흐름1. 캡처링 단계 - 이벤트가 하위 요소로 전파되는 단계2. 타깃 단계 - 이벤트가 실제 타깃 요소에 전달되는 단계3. 버블링 단계 - 이벤트가 상위 요소로 전파되는 단계[출처 : 저자, 따라하며 배우는 자바스크립트 A-Z 강의 자료 #3 Event,인프런 강의]Event Delegation - 하위요소의 이벤트를 상위 요소에 위임(제어)하는 것[출처 : 저자, 따라하며 배우는 자바스크립트 A-Z 강의 자료 #3 Event,인프런 강의]섹션 5 자바스크립트 중급자바스크립트 this함수에서 this 사용 => Window 객체를 가리킴메소드에서 this 사용=>해당 객체를 가리킴constructor 함수에서 this 사용 =>빈 객체를 가리킴forEach(callback, thisArg)화살표 함수에서 this 사용 => 상위 스코프의 this를 가리킴(Lexical this)bind, call, apply 메소드함수의 this 컨텍스트를 제어const person1 = { firstName: “John”, lastName: “Doe” } ------------------------------------- const fullName = function() { console.log(this.firstName + “ “ + this.lastName); } //call() fullName.call(person1); ------------------------------------- const fullName = function (city, country) { console.log(`${this.firstName}, ${this.lastName}, ${city}, ${country}`); } fullName.call(person1, "Oslo", "Norway"); // 인수 넣어 사용 가능 //apply() fullName.apply(person1, ["Oslo", "Norway"]); // call 메서드와 비슷하지만 인수에 배열넣어야 함. ------------------------------------- //bind() function func(language) { if (language === “kor”) { console.log(`language: ${this.korGreeting}`); } else { console.log( (`language: ${this.engGreeting}`); } } Const greeting = { korGreeting: “안녕 “, engGreeting: “Hello “, }; Const boundFunc = func.bind(greeting); boundFunc(‘kor’); //call, apply와 다른 점 직접 함수를 실행하지 않고 반환 ㅁ 조건부 삼항 연산자 (result ? result = "" : result = "")Event Loop자바 스크립트 동기 코드인데 비동기 코드를 작성하기 위해 브라우저에서 사용한다면 브라우저 api (window object)Node에서 사용한다면 Node api(global object) 사용.web API 실행시 내부 진행[출처 : 저자, 따라하며 배우는 자바스크립트 A-Z 강의 자료 #4 자바스크립트 중급, 인프런 강의]자바스크립트 엔진메모리 힙 - 메모리 할당이 발생 (변수를 정의하면 저장되는 창고)호출 스택 - 코드가 실행될 때 스택들이 이곳에 쌓임.[출처 : 저자, 따라하며 배우는 자바스크립트 A-Z 강의 자료 #4 자바스크립트 중급, 인프런 강의]undefined vs null둘다 원시 자료형undefined = 아무 값도 할당받지 않은 상태null = 비어있는, 존재하지 않는 값 Map, Filter, ReduceClosure다른 함수 내부에 정의딘 함수가 있는 경우 외부 함수가 실행을 완료하고 해당 변수가 해당 함수 외부에서 더 이상 액세스할 수 없는 경우에도 해당 내부 함수는 외부 함수의 변수 및 범위에 액세스할 수 있음.Destucturing배열이나 객체의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 js 표현식let { accessory, animal, color } = animalData;전개 연산자특정 객체 또는 배열의 값을 다른 객체, 배열로 복제하거나 옮길 때 사용.const arr = [...arr1, ...arr2];얕은 비교 vs 깊은 비교얕은 비교숫자, 문자열 등 원시 자료형은 값을 비교배열, 객체 등 참조 자료형은 값 혹은 속성을 비교하지 않고, 참조디는 위치를 비교깊은 비교객체의 경우에도 값으로 비교Object depth가 깊지 않은 경우 : JSON.stringfy() 사용Object depth가 깊은 경우 : lodash 라이브러리의 isEqual() 사용얕은 복사 vs 깊은 복사얕은 복사중첩된 배열이나 객체가 있다면 따라서 변경됨.spread operator, Object assign, Array.from(), slice, shallow copyObject.freeze() - 얕은 동결 : 객체를 동결합니다. 중첩된 구조에서 올바른 역할을 수행하지 못함.깊은 복사중첨된 곳에 따로 spread operator 사용lodash 라이브러리를 이용한 deepCopystructuredCloneconst 참조 타입 데이터 변경const array = ["A", "B", "C"];array.push("D");call stack에 reference ID는 같고 heap memory값만 바뀌어서 에러가 나지 않음.함수 표현식 vs 함수 선언문예시// 함수 표현식 function funcDeclaration() { return '함수 선언문'; } // 함수 선언문 let funcDeclaration = function () { return '함수 표현식'; }함수 선언식은 호이스팅에 영향을 받지만 표현식은 받지 안음.strict mode엄격모드는 제한된 버전을 선택하여 암묵적인 느슨한 모드를 해제하기 위한 방법적용방법파일에 "use strict" 지시자 입력함수 안에 "use strict" 사용해서 그 함수만을 위해서 strict mode를 적용class를 사용하면 자동으로 strict mode가 적용됨Intersection observer기본적으로 브라우저 뷰포트와 설정한 요소의 교차점을 관찰하며, 요소가 뷰포트에 포함되지 않는지, 더 쉽게는 사용자 화면에 지금 보이는 요소인지 아닌지 구별Pure Function순수 함수의 규칙1. 같은 입력값이 주어졌을 때, 언제나 같은 결과값을 리턴한다. (same input => same output)2. 사이드 이펙트를 만들지 않는다. (no dise effects)사용하는 이유1. 클린 코드를 위해서2. 테스트를 쉽게하기 위해서3. 디버그를 쉽게 하기 위해서4. 독립적인 코드를 위해서(Decoupled / Independent)Curry functionf(a,b,c)처럼 단일 호출로 처리하는 함수를 f(a)(b)(c)와 같이 각각의 인수가 호출 가능한 프로세스로 호출된 후 병합될 수 있게 변환하는 것자바스크립트 엔진자바스크립트를 실행하려면 자바스크립트 엔진이 필요.브라우저에는 자바스크립트 엔진이 있어서 실행 가능.IIFE(Immediately Invoked Function Expression)정의되자마자 즉시 실행되는 함수를 말한다.사용하는 주된 이유는 변수를 전역으로 선언하는 것을 피하기 위함.내부 안으로 다른 변수들이 접근하는 것을 막을 수도 있음.첫 번째() 소괄호 => 전역 선언 막고, IIFE 내부 안으로 다른 변수 접근두번 째() 소괄호 => 즉시 실행 함수를 생성하는 괄호( function() { // Do fun stuff } )() 섹션 6 OOP ( 객체 지향 프로그래밍) 하나의 문제 해결을 위한 독립된 단위 "객체"들의 모임알아보기 쉽고 재사용성이 높아짐특징자료 추상화불필요한 정보는 숨기고 중요한 정보만 표현해 프로그램 간단히 만드는 것상속기존 클래스의 자료와 연산을 이용할 수 있게 하는 기능다형성한 요소에 여러개념을 넣어 놓는 것 같은 메소드라도 각 인스턴스에 따라 다양한 형태를 가질 수 있는 것.클래스 & 오버 라이딩 - 자식 클래스의 메서드가 부모 클래스의 메서드와 다르게 동작하거나 변수가 다른 값으로 지정될 수 있음.캡슐화클래스 안에 관련 메서드, 변수를 하나로 묶어줌바깥에서의 접근을 막아 보안이 강화되고 잘 관리되는 코드 제공prototype & prototype chain자바스크립트 객체가 다른 객체로부터 메서드와 속성을 상속받는 메커니즘.sub class(Inheritance) - extends 키워드를 사용해 부모클래스 기능 그대로 자식 클래스를 만들 수 있음super() - 부모 생성자 호출ES6 classesstatic 정적 메서드 사용 - "prototype"이 아닌 클래스 함수 자체에 메서드를 설정할 수도 있습니다.사용 예시class Person { // 생성자(constructor)는 클래스의 인스턴스가 생성될 때 호출됩니다. constructor(name, age, job) { this.name = name; // 클래스의 속성 설정 this.email = email; this.birthday = new Date(birthday); } // 메서드 정의: 클래스 내의 함수 introduce() { return `Hello my name is ${this.name}`; } static multipleNumbers(x, y) { return x * y; } } console.log(Person.multipleNumbers(2, 9); 섹션 7 비동기시간이 오래 걸리는 작업을 먼저 시작하고, 그 작업이 끝나지 않아도 다음 작업을 먼저 처리.(<->) 동기 - 작업이 순차적으로 진행되어, 앞선 작업이 끝날 때까지 다음 작업을 실행하지 않음. callback, promise 그리고 Async/Await비동기 요청이 여러개 있을 때 하나의 요청이 다른 요청의 결과에 의존해야하는 경우 사용[출처 : 저자, 따라하며 배우는 자바스크립트 A-Z 강의 자료 #6 비동기,인프런 강의 ] 👩🏻‍💻 미션1번 (Day2)음식 메뉴 앱2번 (Day3)가위 바위 보 앱3번(Day4)퀴즈 앱4번(Day5)책 리스트 나열 앱 5번(Day5)Github Finder 앱

웹 개발워밍업클럽FE과제

이양구

[인프런 워밍업 클럽 FE 0기] 미션8 - 디즈니 플러스 앱

🎞 Disney Plus APP GitHub 🎞 Disney Plus APP DemoRecord by ScreenToGif  개요인프런 워밍업 클럽 FE 0기의 여덟 번째 미션인 '디즈니 플러스 앱' 입니다. 따라하며 배우는 리액트 섹션 4~5(리액트로 Netflix 앱 만들기) 목표swiper 라이브러리 커스텀해보기react-oauth/google 로 구글 로그인 연동해보기 구현swiper 라이브러리 커스텀해보기// LoginPage import "swiper/css/effect-fade"; <Swiper modules={[Autoplay, EffectFade, Pagination, A11y]} autoplay={auto} effect={"fade"} pagination={{ clickable: true, }} loop={true} fadeEffect={{ crossFade: true }} slidesPerView={1} speed={2000} > {...} </Swiper> // Row.tsx import "swiper/css/mousewheel"; <Swiper modules={[Navigation, Pagination, Scrollbar, A11y, Mousewheel]} navigation pagination={{ clickable: true }} mousewheel speed={1000} spaceBetween={10} > {...} </Swiper> 2024년 3월 10일의 디즈니 플러스 메인 페이지를 그대로 옮겨보고자 swiper 라이브러리를 커스텀해봤다.로그인 페이지에서는 좌우로 넘기는 슬라이드가 아닌 fade-in-out의 슬라이드를 구현하기 위해 swiper에 EffectFade 모듈을 추가하고 fadeEffect 속성을 추가했다.이 fadeEffect가 제대로 작동하기 위해선 반드시 해당 이펙트의 css를 추가해야 한다.다른 모듈이나 컴포넌트를 추가할 때처럼 자동으로 추가되지 않으니 주의해야 한다. (이걸 몰라서 한참을 찾았다. 😥)Row 컴포넌트는 마우스 휠에 따라 움직이는 슬라이드를 만들기 위해 Mousewheel 모듈과 속성을 이용했다.이렇게 슬라이드 속성을 정한 뒤에 swiper가 렌더링하는 요소의 class를 찾아 CSS에서 원하는 디자인으로 변경하면 된다.이때 라이브러리의 CSS와 겹치는 속성이 있을 수 있기 떄문에 '!important'를 붙이는 게 좋다. react-oauth/google 로 구글 로그인 연동해보기// index.js <GoogleOAuthProvider clientId={process.env.REACT_APP_CLIENT_ID}> <BrowserRouter> <App /> </BrowserRouter> </GoogleOAuthProvider> // App.jsx const navigate = useNavigate(); const [isLogin, setIsLogin] = useState( localStorage.getItem("user") ? true : false ); useEffect(() => { isLogin ? navigate("/") : navigate("/login"); }, [isLogin]); <Routes> {isLogin ? ( <Route path="/" element={<Layout setIsLogin={setIsLogin} />}> <Route index element={<MainPage />} /> <Route path=":movieId" element={<DetailPage />} /> <Route path="search" element={<SearchPage />} /> </Route> ) : ( <Route path="login" element={<LoginPage setIsLogin={setIsLogin} />} /> )} </Routes> react-oauth/google는 구글 로그인을 지원하는 라이브러리로, 사전에 구글의 Cloud에서 API 등록을 하고 Client ID를 발급받아야 사용할 수 있다.먼저 프로젝트의 최상위에 GoogleOAuthProvider로 감싸준다.그리고 사용자의 로그인 여부에 따라 페이지를 이동시키기 위해 라우터를 설정한 App 컴포넌트에서 관련 코드를 작성했다.페이지가 렌더링 될 때 로컬 스토리지에 저장된 유저 정보를 받아오고 만약 없다면 로그인 페이지로 보내도록 했다. // loginPage const googleLogin = async (credentialResponse) => { localStorage.setItem( "user", JSON.stringify(jwtDecode(credentialResponse.credential)) ); setIsLogin(true); }; <GoogleLogin onSuccess={(credentialResponse) => googleLogin(credentialResponse)} /> GoogleLogin 컴포넌트는 react-oauth/google 라이브러리에서 지원하는 버튼 컴포넌트로 디자인 및 로그인 관련 함수가 내장되어 있다.onSuccess는 사용자의 로그인이 성공했을 때 실행되는 콜백 함수이며, 인자로 로그인한 유저의 정보를 담은 데이터를 갖는다.여기서 credential이라는 값은 유저의 정보를 담고 있는 토큰으로 암호화되어 있기 때문에 jwt-decode 라이브러리를 이용해 디코딩하여 사용해야 한다.여기서 받은 picture는 사용자의 프로필 이미지 링크를 포함하고 있어서 Nav 컴포넌트에서 사용해 로그인한 유저의 프로필 이미지로 변경했다. 회고'Netflix 앱 만들기'를 하면서 사용했던 기술이 대부분이라 오래 걸리지 않을 것 같았지만...라이브러리 알아보고 문서 읽고 실행해보고... 하는 데 너무 오래 걸린 것 같다.배너 하단의 카테고리 부분은 이전에 같은 과제를 하셨던 분의 깃허브를 참고했다. (https://github.com/kimneighbor/clone-disney-plus-app)로그인 페이지는 따라하기 싫어서 현재 디즈니 플러스 홈페이지를 보고 참고했다.그대로 하면 얼마 안 걸릴 거라 생각했는데 생각보다 라이브러리 커스텀에서 좀 애를 먹었다. 😅with_networks: "2739" 2739는 TMDB에서 디즈니 플러스 방송사(networks) 코드라서 axios의 instance 기본 값에 추가했다.몇몇 요청은 해당 파라미터가 통하지 않거나 오류를 보내기도 해서 완벽하진 않다.디즈니 플러스에서 API를 제공했다면 더 알맞게 페이지를 구현할 수 있었을 텐데 하는 아쉬움이 남는다.한편 영화 정보 API를 제공해주는 TMDB(The Movie Database) 같은 곳이 있어 감사하고 다행이라는 생각이 들었다.프론트엔드 공부하는데 API를 제공해주는 곳이 아예 없었다면 혹은 매번 일정 비용을 지불해야 했다면 얼마나 힘들었을까로그인도 사실 좀 더 좋은 라우팅 구조나 상태 관리 라이브러리를 공부하고 사용해보고 싶었지만...계속 욕심만 커지는 것 같아 최대한 간단하게 구현하려 했다.(사실 과제 밀려서 조바심에 아무것도 못 했다... 😂) 

프론트엔드워밍업워밍업클럽프론트엔드프론트FE미션과제발자국

강지원

[인프런 워밍업 스터디 클럽 2기 FE] 강지원 과제 제출

<JavaScript 과제> Day 2 Mission (음식 메뉴 앱)github : https://github.com/noaprost/inflearn_study_js_mission/tree/main/mission1개발 시간 : 3시간개발 순서메뉴 정보를 담을 menu.json 파일을 만들었습니다.fetch를 사용하여 json 데이터를 불러오고 main 부분에 동적으로 child를 추가해주었습니다.각 카테고리 button에 onClick eventListener를 달아주고 curCategory 변수를 선언하여 현재 클릭 된 카테고리가 무엇 인지에 따라 다른 child가 main에 보여지도록 했습니다.아쉬웠던 점useEffect나 useState를 대체할 코드를 js로 짜는 것이 미숙해서 코드가 깔끔하게 짜이지 않은 것 같아 아쉽습니다.카테고리 버튼을 누를 때마다 main에 이미 있는 child를 모두 제거 후 다시 필터링해서 append 해주었는데, 코드가 비효율적인 것 같아서 아쉽습니다.느낀 점첫 과제를 무사히 끝내서 다행이다. 이번 과제는 바닐라 js로만 앱을 만든 것이 3년전 이후로 처음이라 많이 해맸지만 앞으로의 과제를 만들면서 js 실력이 많이 길러질 것 같아서 기대가 됐다. main 화면Breakfast를 클릭한 화면  Day 3 Mission (가위 바위 보 앱)github : https://github.com/noaprost/inflearn_study_js_mission/tree/main/mission2개발 시간 : 2시간구현 사항가위, 바위, 보 버튼을 누르면컴퓨터에서 랜덤 변수를 생성하고 플레이어와 승패를 비교합니다.승패에 따라 승리 횟수를 업데이트 해주고, 남은 횟수를 1 감소 시킵니다.남은 횟수가 모두 소진됐을 경우최종 승리 횟수를 비교해서 알맞는 문구를 띄워주고 replay 버튼을 보여줍니다.replay 버튼을 누를 경우모든 횟수가 초기화됩니다. 느낀 점한번 만들어봤던 앱이라서 바로 만들기부터 시작했는데, 중간에 막혀서 고생했다ㅠ 구현할 기능과 순서를 다시 쭉 적어 놓고 구현하니까 순조롭게 만들어져서 요구 사항 작성의 중요성을 다시 한번 깨달았다. main 화면 (게임 중)게임이 끝난 화면  Day 4 Mission (퀴즈 앱)github : https://github.com/noaprost/inflearn_study_js_mission/tree/main/misson3개발 시간 : 2시간구현 사항랜덤으로 1~50사이의 수를 골라서 문제를 생성합니다.랜덤으로 정답+랜덤 수 / 랜덤 수1+랜덤 수2+정답이 없습니다. 둘 중 하나를 보여줍니다.정답을 누를 경우 Next 버튼이 보여지고 버튼을 클릭하면 다음 문제로 이동합니다.문제를 틀렸을 경우 Restart 버튼이 보여지고 버튼을 클릭하면 새로운 문제를 보여줍니다. 아쉬웠던 점정답/틀린 답을 클릭했을 경우 ui를 보여줄 때 class 이름을 추가하고 삭제하는 방식으로 구현했는데, if문도 많이 중첩되어있고 긴 코드를 작성한 것 같아서 아쉽습니다. 추후에 더 나은 코드로 리팩토링을 하고 싶습니다. 만족한 점답 버튼을 최대한 랜덤으로 보이게 하고 싶어서 수를 먼저 만들고 이후에 동적으로 생성한 점이 만족스럽습니다. 느낀 점생각보다 구현할 때 신경 쓸 부분이 많았고, ui 처리가 어려웠지만 재밌게 만들었다. 영상에 요구 사항이 잘 안보여서 규칙 등을 직접 정하면서 만들었는데 그 점도 재밌었다. 문제를 띄운 화면정답을 클릭했을 경우오답을 클릭했을 경우  Day 5 Mission (책 리스트 나열 앱, Github Finder 앱) 책 리스트 나열 앱github 주소 : https://github.com/noaprost/inflearn_study_js_mission/tree/main/mission4개발 시간 : 1시간구현 순서form을 만들어서 input 값 2개를 받는다.submit 버튼을 누르면 input에 있던 값을 다른 변수에 저장해둔다.데이터를 가공해서 리스트에 책 제목과 저자, 삭제 버튼을 담은 목록을 보여준다.삭제 버튼을 누르면 해당 리스트가 삭제된다.느낀 점이번 과제는 개인적으로 정말 쉬웠다. todo 앱 강의를 먼저 듣고 나서 만들어서 그런지 가장 명확하게 구현 순서가 그려져서 재미있었다. 책 생성 화면책 삭제 화면Github Finder 앱github 주소 : https://github.com/noaprost/inflearn_study_js_mission/tree/main/mission5개발 시간 : 7시간구현 순서Github api로 user정보와 repo정보를 가져온 후, repo 정보는 최근에 작성된 것이 먼저 보여지도록 정렬한다.displayUserInfo와 displayLatestRepo 함수를 이용해서 각각의 정보를 화면에 보여준다.필요한 정보 정리// user data에서 가져올 목록 // 맨위 4개 태그 public_repos public_gists followers following // 리스트 info company blog location created_at // View Profile 버튼 html_url // 최근 리포 baseUrl repos_url // repo data에서 가져올 목록 // Latest Repos name -> html_url 이용해서 이동할 수 있도록 stargazers_count watchers_count forks_count 어려웠던 점새로운 user를 검색할 때 마다 userInfo와 latestRepo 컨테이너에 담긴 정보들을 지워주고 새로 담아야 하는데, 아래의 간단한 코드를 떠올리지 못해서 3시간이나 해맸다.document.getElementById("user-info").innerHTML = ""; document.getElementById("latest-repo").innerHTML = ""; 느낀 점처음 user의 정보를 가져오는 BaseUrl을 찾는 것이 어려웠지만 url을 찾고 나니 나머지는 데이터 가공 뿐이라서 만드는 과정은 재밌었다. main 화면검색 결과 화면 (user info)검색 결과 화면 (latest repo)  <React 과제> Day 9 Mission (예산 계산기)github 주소 : https://github.com/noaprost/inflearn_react_mission_budget_calculator개발 시간 : 4시간구현 사항예산 목록을 생성, 수정, 삭제할 수 있어야 한다.목록 지우기 버튼을 누르면 목록 전체가 삭제되어야 한다.생성, 삭제, 수정 시 맨 위에 status를 알려주는 상태 메세지를 띄워준다.총 예산을 계산해서 가장 아래쪽에 표시해준다. 느낀 점그동안 Next.js, Three.js 공부를 하느라 React 프로젝트를 오랫만에 만들었더니 간단한 CRUD 앱인데도 꽤나 어렵게 구현하였다. 역시 꾸준한 것 보다 좋은 것은 없는 것 같다ㅠㅠ 과제를 진행하면서 잠시 잊었던 감을 다시 찾아야겠다는 생각이 들었다. main 화면예산 입력 후 화면예산 수정 화면  Day 10 Mission(디즈니 플러스 앱)github 주소 : https://github.com/noaprost/disneyplus_clone개발 기간 : 약 2일페이지 별 구현 사항로그인 페이지firebase를 이용해서 구글로 로그인을 한다.로그인 버튼을 누르면 popup창이 뜨고 로그인이 되면 홈으로 이동한다.Navbaruser가 있으면 프로필 사진이 원형으로 보여지고, 없으면 LOGIN 버튼이 보여진다.disney 로고를 누르면 user가 있을 경우 "/home"으로 이동, 없을 경우 "/"로 이동한다.user 프로필 사진을 hover하면 LOGOUT 버튼이 1.5초간 보여지고 클릭 시 user 정보가 사라진다.메인 페이지axios를 이용해서 영화 목록을 받아온 뒤 영화 하나의 정보를 보여준다. (이미지, 제목, 설명)영화사 버튼을 만들고, hover 시 영상이 재생되도록 한다.인기 영화, 평점 높은 영화, 판타지 영화, 액션 영화 각 카테고리에 맞는 영화들의 이미지를 보여준다.카테고리에 보여지는 영화들은 swiper를 이용해서 감싸준다.카테고리에 보여지는 영화를 클릭할 경우 상세 정보를 담은 모달이 나오고, 화면 밖을 클릭할 경우 모달이 닫히게 해준다.Modal이미지, 개봉일, 제목, 평점, 설명을 보여준다.메인 페이지에 오면 검색 창이 나타나고, 검색어 입력 시 검색 페이지로 이동한다.검색 페이지에서 메인 페이지로 돌아오게 되면 useLocation을 이용해서 검색어를 초기화해준다.검색 페이지로 이동할 때 검색어 정보를 query에 담아 url에 파라미터로 보내준다.검색 페이지useLocation으로 받은 데이터를 가공해서 검색어를 저장한다.검색어가 변경될 때마다 검색어를 query로 받는 검색 결과를 업데이트 해준다.결과의 이미지를 목록의 형태로 보여준다.이미지를 클릭할 경우 화면에 꽉 찬 이미지가 보여진다. 느낀 점이번 프로젝트를 하면서 React, CSS, Html, JS에 대해 새로운 것을 많이 알게 되었다. 과제를 하는 동안에는 에러도 많이 나고, 데이터가 안받아지거나, 페이지가 예상한대로 동작하지 않아서 스트레스도 많이 받아가며 만들었지만, 그 과정에서 실력은 확실히 성장할 수 있었다. 끝나고 나니까 뿌듯하고 계속 보게 됐다. 아쉬운 점카테고리 별로 영화 정보를 받아올 때, 인기 영화와 평점 높은 영화를 받아오는 api 주소와 장르별 영화를 받아오는 api 주소가 달라서 서로 다른 컴포넌트에서 로직을 짰다. 이것 때문에 swiper 코드와 Modal 코드, 목록을 보여주는 코드들이 중복이 된 것 같아서 아쉽다. 로그인 페이지메인 페이지메인 페이지 (영화사 버튼 hover 시)메인 페이지 (카테고리 별 목록)메인 페이지 (영화 상세 정보 모달)검색 페이지검색 페이지 (이미지 클릭 시)  Day 12 Mission (퀴즈 앱)github 주소 : https://github.com/noaprost/quiz_app개발 시간 : 3시간라우터 구조 및 구현 사항"/"Welcome to Quiz App을 보여준다."/question"수학 문제 2개를 보여준다.라디오 버튼을 클릭하면 답변 확인 버튼이 뜬다.답변 확인 버튼을 클릭하면 정답을 클릭했을 경우와 오답을 클릭했을 경우를 나눠서 보여준다.정답을 클릭했을 경우 border color를 green으로, 오답을 클릭했을 경우 red로 보여준다.정답인 label에 v표시를 붙여주고, 오답인 label에는 x를 붙여준다."/state"math와 alphabet 중에 하나를 선택할 수 있는 select box를 보여준다.선택하면 카테고리에 맞는 문제를 보여준다.채점 방식은 question과 동일하다."/quiz"퀴즈를 보기 전math와 alphabet 중에 하나를 선택할 수 있는 select box를 보여준다.연습 테스트 시작 버튼을 보여준다. 클릭하면 퀴즈 화면으로 변경된다.퀴즈를 보는 중문제와 남은 문제 수가 보여진다.답을 선택하면 다음 버튼이 보여지고, 마지막 문제였을 경우 제출 버튼이 보여진다.다음 버튼을 선택하면 다음 문제가 보여지고, 제출 버튼을 누르면 결과 화면으로 변경된다.퀴즈를 본 후총 문제 수 중 몇 문제를 맞췄는지 보여준다. (합격했을 경우 초록색, 아닌 경우 빨간색)시험 합격/불합격 여부가 보여진다. (4개 전부 맞을 경우 합격, 아닌 경우 불합격)새로운 연습 테스트 시작 버튼이 보여진다.버튼 클릭 시, 퀴즈를 보기 전 화면으로 돌아간다. 아쉬운 점문제를 랜덤으로 동적 생성하고 퀴즈의 문제 수도 설정할 수 있도록 구현해보고 싶었는데 시간이 부족해서 구현하지 못했다. 스터디가 끝난 후에라도 혼자서 개발해보고 싶다. 느낀 점그래도 가장 최근에 개발할 때 사용했던 Next.js와 Typescript여서 비교적 편하게 개발했던 것 같다. 구조들이나 변수가 반복되는 부분이 많다 보니 코드를 가독성 있게 짜기가 쉽지 않았다. 리팩토링에 대한 공부의 필요성이 느껴졌다. home 화면question 화면 (문제를 풀기 전)state 화면 (문제를 푼 후)quiz 화면quiz 화면 (연습 테스트 시작)quiz 화면 (테스트 결과) 

JSReact인프런스터디과제미션

정지형

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

💡 2주차 회고중간 정검 라이브에 참가 하지 못했다..ㅠㅠ 아무래도 다음 워밍업 클럽에도 신청을 해야할 듯 싶다. 그래서 그런지 의욕이 떨어져버려서 과제가 밀려버렸지만 그래도 다시 의지를 다져보기로 했다! 다음에 또 참가하면 되지 한번 더 참가하면 복습도 되고 실력도 더 늘겠지? 완전 럭키비키잖아? 🍀생각보다 이번 주차 미션은 더 난이도가 있어서 아직 못했는데 강의듣고 얼른 시작해야겠다!📝 강의 내용 요약자바스크립트섹션 8 Iterator, GeneratorSymbol - ES6에서 새로 추가된 원시 타입, 유니크한 식별자를 만들기 위해 사용.const sym = Symbol('hi'); const sym2 = Symbol('hi'); console.log(sym1 === sym2); //falseIterator(반복기)Iterable - 배열은 반복 가능한 객체, 반복이 가능하다는 것을 Iterable이라 부름.for..of를 이용할 수 있거나 [Symbol.iterator]() 값을 가지면 Iterable한 것.Iterator - 반복자는 next()를 호출해서 {value:,done:}두 개 속성을 가지는 객체를 반환하는 객체Iterator 직접 생성해보기function makeIterator(array) { let index = 0; return { next: function () { if (index < array.length) { return { value: array[index++], done: false }; } else { return { value: undefined, done: true }; } } }; } const it = makeIterator([10, 20, 30]); console.log(it.next()); // { value: 10, done: false } console.log(it.next()); // { value: 20, done: false } console.log(it.next()); // { value: 30, done: false } console.log(it.next()); // { value: undefined, done: true }Generator(생성기)Generator 함수function* 키워드를 사용하여 정의되며, 사용자의 요구에 따라 시간 간격으로 여러 값을 반환할 수 있습니다.일반 함수 => 단 한 번의 실행으로 함수 끝까지 실행됩니다.제너레이터 함수 => 사용자의 요구에 따라 일시적으로 정지될 수 있고, 다시 시작될 수도 있습니다.function* generatorFunction() { yield 1; // yield - 사용한 지점에서 실행을 일시 중지합니다. yield 2; yield 3; } const number = generatorFunction(); console.log(number.next()); // { value: 1, done: false } console.log(number.next()); // { value: 2, done: false } console.log(number.next()); // { value: 3, done: false } console.log(number.next()); // { value: undefined, done: true }React (Seaction 5~6)👩🏻‍💻 미션비밀번호 생성 앱 6타이핑 테스트 앱7예산 계산기 앱 만들기 9디즈니 플러스 앱 만들기(난이도 중) 10 

웹 개발워밍업클럽FE과제

이양구

[인프런 워밍업 클럽 FE 0기] 미션7 - 예산 계산기 앱

💸 Budget Calculator APP GitHub 💸 Budget Calculator APP DemoRecord by ScreenToGif  개요인프런 워밍업 클럽 FE 0기의 일곱 번째 미션인 '예산 계산기 앱' 입니다. 따라하며 배우는 리액트 섹션 0~3(To-Do 앱) 목표의존성 배열(Dependency Array) 을 이용해 함수 실행하기state 를 전역 변수처럼(?) 사용해보기 구현구조|-- App | |-- Form | |-- Lists | | |-- List  의존성 배열(Dependency Array) 을 이용해 함수 실행하기// App.jsx const [budgetList, setBudgetList] = useState( JSON.parse(localStorage.getItem("budgetList")) || [] ); useEffect(() => { localStorage.setItem("budgetList", JSON.stringify(budgetList)); }, [budgetList]); const totalCost = useCallback(() => { return budgetList.reduce((acc, cur) => acc + cur.cost, 0); }, [budgetList]); 최상위 컴포넌트인 <App> 컴포넌트에서 만든 'budgetList'이라는 state를 useEffect와 useCallback의 의존성 배열에 추가했다.useEffect에서는 해당 state가 변경되면 로컬 스토리지의 budgetList를 최근의 리스트로 변경한다.이렇게 하면 일일이 setBudgetList가 호출되는 곳마다 함수를 사용하지 않아도 된다.다음은 예산의 총 금액을 반환하는 함수가 리스트가 변경될 때마다 실행되도록 useCallback으로 감싸고 의존성 배열에 state를 추가했다.// Form.jsx const budgetNameRef = useRef(); const [budgetName, setBudgetName] = useState(""); const [budgetCost, setBudgetCost] = useState(0); useEffect(() => { if (isEdit) { setBudgetName(budget.name); setBudgetCost(budget.cost); budgetNameRef.current.focus(); } }, [isEdit]); <Form> 컴포넌트에서는 useEffect에 'isEdit'이라는 state를 의존성 배열에 추가했다.사용자가 예산을 수정하기 위해 list의 Edit 버튼을 클릭하면 해당 budget의 name과 cost를 최근 state로 불러오고, useRef를 이용해 name을 입력하는 <input> 요소에 focus 상태가 되도록 했다.state 를 전역 변수처럼(?) 사용해보기// App.jsx const [currentBudget, setCurrentBudget] = useState({ isEdit: false, budget: {}, }); // List.jsx const handleEdit = () => { setCurrentBudget({ isEdit: true, budget: list, }); setHandleStatus({ type: "edit", message: "Editing..." }); }; // Form.jsx const handleBudgetSubmit = (e) => { const newBudget = { id: Date.now(), name: budgetName, cost: budgetCost, }; // isEdit의 값에 따라 새로 추가할지 수정할지 결정 if (isEdit) { setBudgetList((prevBudgetList) => { const newBudgetLists = [...prevBudgetList]; const index = newBudgetLists.findIndex(({ id }) => id === budget.id); newBudgetLists[index] = newBudget; return newBudgetLists; }); setCurrentBudget({ isEdit: false, budget: {} }); setHandleStatus({ type: "submit", message: "Edit Success!" }); } else { setBudgetList((prevBudgetLists) => [...prevBudgetLists, newBudget]); setHandleStatus({ type: "submit", message: "Submit Success!" }); } // submit 종료 시 input의 데이터를 초깃값으로 설정 setBudgetName(""); setBudgetCost(0); }; 배웠던 To Do 앱은 List의 Edit 버튼을 클릭했을 때 해당 List의 요소를 input 요소로 변경시키고 수정을 했다.하지만 과제는 클릭을 했을 때 List의 요소를 변경시키는 게 아니라 Form의 input에 해당 예산의 데이터를 전달해야 했다.그래서 마치 전역 변수처럼 사용할 'currentBudget'이라는 state를 생성하고 'isEdit'이라는 boolean 값과 수정할 예산의 데이터를 담을 'budget'이라는 값을 설정했다.'isEdit'의 상태 값이 true일 때 수정하기와 삭제하기 <button> 요소를 disabled로 변경한다.또한 submit 함수는 새로운 입력 값을 budgetList에 추가하지 않고 해당 예산의 index를 찾아 수정하고 리스트를 변경한다.이렇게 하니 onSubmit과 onEdit 처럼 비슷한 기능을 하는 함수를 여러 개 만들지 않아도 되었다. ⚠ setTimeout 렌더링const { type, message } = handleStatus; const handleStyle = useCallback(() => { if (type === "edit") { return "text-gray-500 block"; } else if (type === "none") { return "hidden"; } else { // 2초 뒤에 실행 --> App - Form - Status 1번 더 렌더링 setTimeout(() => { setHandleStatus({ type: "none", message: "" }); }, 2000); if (type === "submit") { return "text-green-400 block"; } else { return "text-red-400 block"; } } }, [type]); 추가, 삭제, 수정의 완료 및 진행 중 상태를 보여주는 <Status> 컴포넌트를 만들었다.App에서 만든 'handleStatus'라는 state를 전달하고 메세지가 나타난 뒤에 사라지게 만들고 싶어서 setTimeout() 메서드를 이용해 2초 뒤에 상태를 초기화했다.하지만 이 상태가 App과 Form 컴포넌트에서 참고하다 보니 나타나고 사라질 때마다 렌더링이 발생했다.CSS의 opacity로 처리하기엔 state의 값을 변경해야 했기에 알맞는 방법은 아니라 생각했다.뭔가 <Status> 컴포넌트 내부에서만 렌더링이 일어나게 하고 싶었는데 아직 다른 방법을 찾지 못했다.😢😢😢 회고다른 컴포넌트의 클릭 이벤트로 변경된 state를 이용하는 부분이 생각보다 오래 걸렸다.처음엔 콜백 함수처럼 App 컴포넌트에서 함수 만들고 prop으로 넘겨봤지만 List와 Form은 종속적인 관계가 아니라 힘들었다. 😢그래서 생각해낸 게 state를 이용해서 상태의 변경을 이벤트처럼 사용하는 것이었다.pub-sub 혹은 observer 패턴 같다는 생각도 했지만, 이렇게 최상위에서 선언한 state가 이곳저곳 돌아다니는 게 좋은 방법은 아닐 것 같다는 생각이 들었다.규모가 커지면 렌더링 관리도 힘들고 props를 쫓아다녀야 하기 때문이다.이래서 상태 관리 라이브러리가 나왔나 보다. 🤔 

프론트엔드워밍업워밍업클럽프론트엔드프론트FE미션과제발자국

이양구

[인프런 워밍업 클럽 FE 0기] 미션4-2 - GitHubFinder 앱

🔍 github-finder-app GitHub 🔍 github-finder-app 개요인프런 워밍업 클럽 FE 0기의 네 번째 미션인 'GitHubFinder 앱' 입니다.따라하며 배우는 자바스크립트 섹션 5(OOP), 섹션 6(비동기) 목표Fetch API 를 이용해 깃허브 유저 목록 불러오기Closure 를 이용해 Debounce Function 만들기 구현Fetch API 를 이용해 깃허브 유저 목록 불러오기async function loadUser(input) { prevInputValue = input; try { // const response = await fetch('./src/javascript/user.json'); const response = await fetch(`${url}/${input}`); if (!response.ok) { throw new Error('Failed to fetch user json'); } const json = await response.json(); setUserAvatar(json); setUserInfo(json); await loadUserRepos(json); } catch (error) { console.error(error); } }fetch() 메서드의 응답은 HTTP 응답 전체를 나타내는 'response' 객체를 반환한다.response의 ok 속성은 응답의 성공 여부를 불리언 값으로 가지고 있다.따라서 응답이 성공이 아닐 경우 오류 객체(new Error())를 반환하고 catch 문으로 Promise의 오류를 처리한다.응답에 성공한 response 객체를 JSON으로 사용하기 위해선 json() 메서드를 이용해 파싱해야 한다. Closure 를 이용해 Debounce Function 만들기// debounce debounceInput.addEventListener('input', debounce(loadUser, 1000)); // debounceInput.addEventListener('input', e => callback(e)); function debounce(callback, delay = 0) { // timer는 부모 함수에서 선언된 지역 변수 let timer = null; return (arg) => { // 여기서 arg는 input event if (timer) { // 이미 타이머가 있는데 또 실행되면 타이머 삭제 clearTimeout(timer); } // 변수 timer는 부모 함수에서 선언되었지만 내부 함수에서 사용(클로저) timer = setTimeout(() => { callback(arg.target.value); }, delay); }; }<input> 요소의 'input' 이벤트는 요소의 value가 변경될 때마다 발생한다.만약 사용자가 입력할 때마다 서버에 데이터를 요청한다면 서버의 부하가 커지기 때문에 좋은 방법은 아니다.이럴 때 사용자의 입력이 끝난 뒤 마지막 value를 이용해 서버로 요청하는 게 효율적인 방법이라 할 수 있다.함수의 실행 요청이 반복될 때 마지막 요청만으로 실행하는 걸 '디바운싱(debouncing)'이라고 부른다.debounce 함수는 인자로 실행할 함수를 받고 자식 함수를 반환한다.부모 함수인 debounce 함수에서 선언한 변수(timer)를 자식 함수에서 사용할 수 있는 클로저(Closure)를 이용해 자식 함수의 setTimeout() 메서드의 반환 값인 'timeoutID'를 할당한다.변수 'timer'에 할당한 timeoutID를 이용해 setTimeout() 메서드의 지연 시간(delay)이 종료되기 전에 요청이 들어왔다면 이전에 생성한 타이머를 clearTimeout() 메서드를 이용해 종료하고 다시 타이머를 할당한다. 이렇게 delay로 설정한 시간 이내에 사용자의 입력이 없을 경우 API 요청 함수를 실행하게 된다. 반복적인 함수의 실행을 다루는 방법으로 디바운싱(debouncing)와 쓰로틀링(throttling)이 있다.여러 변수를 고려해 'lodash' 라이브러리의 debounce를 많이 사용한다. 회고이번 미션은 debounce가 반환하는 자식 함수의 인자(argument)가 어떤 타입인지 알기 때문에 callback 함수에 전달하는 인자를 수정해서 미숙한 debounce 함수라고 볼 수 있다.늘 라이브러리를 통해 사용하던 함수를 만들려고 하니 모르는 것도 많고, 고려해야 할 부분이 많다는 걸 알게 됐다.자바스크립트의 기초를 잘 알아야 이런 라이브러리 메서드의 원리를 이해하기도 쉽고, 커스텀하기에 수월한 것 같다.(외의로 GitHub의 API 요청이 API key 없이도 되어서 신기했고, 그 덕에 조금은 수월했다. 아주 조금... 😵) DemoRecord by ScreenToGif

프론트엔드워밍업워밍업클럽프론트엔드프론트FE미션과제발자국

이양구

[인프런 워밍업 클럽 FE 0기] 미션1 - 음식 메뉴 앱

🍝 food-recipe-app API from TheMealDBGitHub food-recipe-app 개요인프런 워밍업 클럽 FE 0기의 첫 번째 미션인 '음식 메뉴 앱' 만들기입니다.따라하며 배우는 자바스크립트의 섹션 1~3(자바스크립트 기초, Window 객체 및 DOM, Event)를 보고 자바스크립트의 DOM 요소를 조작하는 데 중점을 두었습니다.음식 데이터는 TheMealDB의 API를 이용했습니다. 사용한 API가 '음식 레시피'라서 이름을 변경했습니다. 목표문서 객체 모델(The Document Object Model, 이하 DOM)의 메소드(methods)를 이용해 요소(element)에 접근하고 생성하고 교체하기이벤트 리스너(Event Listener) 메소드를 이용해 요소에 이벤트를 등록하고 이벤트 객체 이용하기메뉴 데이터를 Fetch API를 사용해 불러오기 구현이벤트 위임(Event Delegation)을 이용한 이벤트 생성/* <nav id="food-navigation"> <div class="food-navigation-item"> <button id="Beef"> <figure> <img src="https://www.themealdb.com/images/category/beef.png"> <figcaption> Beef </figcaption> </figure> </button> </div> // ... </nav> */ // Not Event Delegation foodNavigation.querySelectorAll('button').forEach((button) => { button.addEventListener('click', async () => { const targetId = button.id; await setFoodList(targetId); }); }); // Event Delegation foodNavigation.addEventListener('click', async (event) => { const targetElement = event.target; // closest() 메서드는 주어진 CSS 선택자와 일치하는 요소를 찾을 때까지, // 자기 자신을 포함해 위쪽(부모 방향, 문서 루트까지)으로 문서 트리를 순회합니다. const targetDiv = targetElement.closest('.food-navigation-item'); if (!targetDiv) { return; } const targetButton = targetDiv.querySelector('button'); const targetId = targetButton.id; await setFoodList(targetId); });이벤트 위임이란 '상위 요소에서 하위 요소의 이벤트를 제어하는 것'을 의미합니다.이벤트를 위임하는 이유이벤트를 하나의 핸들러로 처리함으로써 메모리 사용량을 줄이고 성능을 향상시킬 수 있다.새로운 요소가 추가되거나 제거되는 경우 이벤트 리스너는 상위 요소에 연결되어 있어 재연결의 필요성이 줄어든다.저는 nav 태그에 이벤트를 등록하고 closest 메서드를 이용해 버튼의 id를 찾는 방법을 사용했습니다. 하위 요소를 제거하고 생성한 요소를 추가하기/* <div id="food-list"> <div class="food-list-item"> <figure> <img src="img src" /> </figure> <div class="food-list-item-desc"> <p>food name</p> <hr /> <div> food recipe </div> </div> </div> </div> */ const foodList = await getFoodList(strCategory); const foodListElement = document.getElementById('food-list'); const foodListItem = document.querySelectorAll('.food-list-item'); foodListItem.forEach((item) => item.remove()); // foodListElement.innerHTML = ''; foodList.map(async (food) => { // ... const foodElement = getFoodElement( idMeal, strMeal, strMealThumb, strInstructions ); foodListElement.appendChild(foodElement); });배웠던 removeChild()와 replaceChild() 메서드를 이용하고자 했으나...'만약 해당 카테고리의 음식 리스트의 개수가 다르다면 어떻게 하지?'라는 생각에 한번에 제거하기로 결정했습니다.처음엔 innerHTML을 이용해 하위 코드를 공백으로 만들었지만, 뭔가 이건 너무 이상하다는 생각(요소의 참조나 연결 같은 게 깨지진 않을까)이 들어 찾아보았습니다.stack overflow의 Remove child nodes (or elements) or set innerHTML=""?라는 글에서는 innerHTML은 하위 요소의 이벤트 핸들러가 완전히 제거되지 않을 수도 있다고 한다.또한 Why InnerHTML Is a Bad Idea and How to Avoid It?에서는 innerHTML이 보안상 좋지 않다는 점을 말하고 있다. Stack Overflow의 글을 자세히 읽어 보니 다음과 같은 글이 있었다.What is the best way to empty a node in JavaScript그리고 MDN 문서에도 이렇게 소개하고 있다.replaceChildren() provides a very convenient mechanism for emptying a node of all its children. You call it on the parent node without any argument specified:즉 replaceChildren()메서드를 빈 인자로 실행하면 하위 자식 노드를 모두 지워준다는 것...!😅 회고빈 폴더를 놓고 코드를 작성해본 게 너무 오랜만인 것 같다.자료를 찾기 귀찮다는 마음과 첫 미션이니까 API를 써볼까 하며 자만했던 순간도 있었다.미션의 목적보다 어느새 다른 부분을 신경 쓰느라 배보다 배꼽이 점점 커지는 것 같았다.딸랑 script 태그 한 줄 작성하고 js 파일을 제대로 못 불러와서 몇 시간을 해결 방법을 찾아서 해매기도 했다.😭이벤트 위임 코드를 작성할 때 이은재 님의 시나브로 자바스크립트에서 배웠던 부분을 참고했다.음식 레시피를 불러올 때 요소를 지우고 불러와서 그런지 해당 부분이 사라지고 나타나서 페이지가 늘었다 줄었다 하는 게 눈에 띈다.이래서 가상 돔을 쓰는걸까? 아니면 태그의 속성을 하나하나 수정하면 되는걸까?일단 진도를 따라잡고 배워서 발전시켜야겠다. DemoRecord by ScreenToGif

프론트엔드워밍업워밍업클럽FE프론트프론트엔드과제미션발자국

xicodey

[인프런 워밍업 클럽 0기] BE 3일차 과제

자바의 람다식은 왜 등장했을까? 자바는 람다식 함수형 프로그램밍이 사용한 이유는 불필요한 코드를 줄이고, 가독성을 높이기 위해서다 .그리고 함수 만드는 과정없이 한번에 처리 할 수 있기 때문에 생산성이 높아진다.병령 프로그램밍에 용이하다. 람다식과 익명 클래스는 어떤 관계가 있을까? 함수형 프로그래밍이란 함수를 정의하고 이 함수를 데이터 처리부로 보내 데이터를 처리하는 기법이다.데이터 처리부는 데이터만 가지고 있을 뿐, 처리 방법이 정해져 있지 않아 외부에서 제공져 있지 않아 외부에서 제공된 함수에 의존한다.데이터 처리부는 제공된 함수의 입력값으로 데이터를 넣어 함수에 정의된 처리 내용을 실행하고, 동일한 데이터라도 함수 A를 제공한 결과 값과 함수 B를 제공하여 처리된 결과 값은 다를 수 있다 이것이 함수형 프로그램의 특징인 데이터 처이의 다형성이다.람다식은 함수를 하나의 식으로 표현하여 익명 함수를 반환한다.익명 클래스는 선언된 클래스 내에서만 한 번만사용될 경우 별도로 변수에 담을 필요가 없다.람다식을 쓰면 함수형 인터페이스로 인스턴스를 만들 수 있으며 코드를 줄 일 수 있다.메서드 매개변수와 리턴 타입, ㅌ변수로 만들어 사용도 가능하다. 람다식의 문법은 어떻게 될까? 데이터 처리부에 제공되는 함수 역활 하는 매개변수를 가진 중괄호 블록이다.{매개변수, ...} -> {처리 내용};  인터페이스가 단 하나의 추상 메소드를 가질 경우 이를 함수형 인터페이스라고 한다.public interface Runnable { void run(); }람다식() -> { ...}; @FunctionalInterface public interface Calculable { void calculate(int x, int y); }람다식(x, y ) -> { ...}@FunctionalInterface 어노테이션을 사용한 이유는 인터페이스가 함수형 인터페이스를 보장하기 위해서다.붙이는 것은 선택사항이지만, 컴파일 과정에서 추상 메소드가 하나인 검사하기 때문에 정확한 함수형 인터페이스를 작성하게 도와주는 역활을 해준다.매 매개변수가 없는 람다식실행문이 하나일 경우 중괄호를 생략 가능하고 두개 이상일 경우는 생략할 수 없다.() -> 실행문; () -> { 실행문; 실행문; } 매개변수가 있는 람다식매개변수를 선언할 때 타입은 생략할 수 있고, 구체적인 타입 대신에 var를 사용할 수 있다.(타입, 매변수, ... ) -> { 실행문; 실행문; }(타입, 매변수, ... ) -> 실행문;(var 매개변수, ...) -> { 실행문; 실행문; }(var 매개변수, ...) -> 실행문;(매개변수, ...) -> { 실행문; 실행문; }(매개변수, ...) ->실행문;매개변수 -> { 실행문; 실행문 };매개변수 -> 실행문; 리턴값이 있는 람다식return 문 하나만 있는 경우에는 중괄호와 함께 return 키워드를 생략가능하다.(매개변수, ...) -> { 실행문; return 값 )(매개변수, ...) -> 값 메소드 참조메소드를 참조하여 매개변수의 정보 및 리턴 타입을 알아내 람다식에서 불필요한 매개변수를 제거한다. (left, right) -> Math.max(left, right);Math.max() 메소드의 매개값은 전달하는 역활만 하기 때문에 다음과 같이 생략이 가능하다.Math :: max;정적 메소드를 참조할 경우에는 클래스 이름 뒤에 :: 기호를 붙이고 메소드 이름을 기술한다.클래스 :: 메서드참조변수 :: 메소드 매개변수의 메소드 참조(a, b) -> {a.instaceMethod(b);}메소드 참조는 a 클래스 이름 뒤에 :: 기호를 붙이고 매소드 이름을 기술한다.작성 방법은 메소드 참조와 동일하지만, a의 인스턴스 메소드가 사용된다는 점이 다르다.클래스 :: instaceMethod Reference이것은 자바다(책)

워밍업클럽과제

xicodey

[인프런 워밍업 클럽 0기] BE 1일차 과제

어노테이션 사용하는 이유어노테이션은 사용 용도로 3가지가 있습니다.1. 컴파일 시 사용하는 정보 전달2. 빌드 툴이 코드를 자동으로 생성할 때 사용하는 정보 전달3.실행 시 특정 기능을 처리할 때 사용하는 정보 전달컴파일 시 사용하는 정보 전달의 대표적인 예는 @Override 어노테이션입니다.컴파일러가 메소드 재정의 검사를하도록 설정합니다. 재정의되지 않았다면 컴파일러는 에러를 발생시킵니다.웹 개발에 많이 사용하는 Spring Framework 또는 Spring Boot는 다양한 종류의 어노테이션을 사용해서 웹 애플리케이션을 설정하는데 사용합니다.나만의 어노테이션은 어떻게 만들 수 있을까? 어노테이션을 정의하는 방법은 인터페이스를 정의하는것과 유사합니다.@interface 뒤에 사용할 어노테이션을 이름을 정의합니다.오노테이션은 속성을 가질 수 있으며, 속성은 타입과 이름으로 구성됩니다. 속성은 기본값 default 키워드로 지정할 수 있습니다.어떤 대상에 설정 정보를 적용할 것인지, 적용대상을 정의 해야 합니다.클래스 명위에 @Target 어노테이션을 붙어 정의 합니다.적용할 수 있는 대상의 종류는 ElememtType 열거 상수로 정의되어 있습니다.TYPE : 클래스, 인터페이스 열거타입ANOTATION_TYPE: 어노테이션FIELD: 필드CONSTERUCTOR: 생성자METHOD: 메서드LOCAL_VARIABLE: 로컬 변수PACKAGE: 패키지@Target의 기본 속성인 value는 배열을 값을 가질 수 있습니다.어노테이션을 정의할 때 한 가지 더 추가해야 할 내용은 @AnnotationNamed 언제까지 유지 할 것인지 지정하는 우지 정책을 정해야합니다.RetentionPolicy 열거 상수

워밍업클럽과제

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기 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기] 백엔드 - 7차 과제

진도표 7일차와 연결됩니다우리는 JPA라는 개념을 배우고 유저 테이블에 JPA를 적용해 보았습니다. 몇 가지 문제를 통해 JPA를 연습해 봅시다! 🔥     문제1JPA(Java Persistence API) 는 자바 객체를 관계형 데이터 베이스에 영속적으로 저장하고 조회할 수 있는 ORM 기술에 대한 표준 명세를 의미한다.JPA 를 통해 SQL 쿼리를 작성하지 않고도 객체를 통해 데이터베이스를 조작할 수 있어 보수성이 향상된다.Entity 클래스를 작성한 후 Repository 인터페이스를 생성해야하는데, JpaRepository를 상속받도록 하면, 기본적인 쿼리 추가, 조회, 수정, 삭제, findAll(), findById() 등의 메서드를 사용할 수 있다.1) Entity 클래스 정의@Entity public class Fruits { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id = null; @Column(nullable = false, length = 20 ) private String name; @Column(nullable = false) private LocalDate warehousingDate; @Column(nullable = false) private long price; @Column(nullable = false) private int saled; public Fruits(){ } public Fruits(long id, String name, LocalDate warehousingDate, long price, int saled) { this.id = id; this.name = name; this.warehousingDate = warehousingDate; this.price = price; this.saled = saled; } public Fruits(String name, LocalDate warehousingDate, long price, int saled) { this.name = name; this.warehousingDate = warehousingDate; this.price = price; this.saled = saled; } public Long getId() { return id; } public String getName() { return name; } public LocalDate getWarehousingDate() { return warehousingDate; } public long getPrice() { return price; } public int getSaled(){ return saled; } public void setSaled(int saled) { this.saled = saled; } }JPARepository를 사용하여 액세스할 엔티티 클래스를 정의했다.@Entity 어노테이션은 JPA를 사용해 테이블과 매핑할 클래스에 붙여주는 어노테이션이다. 이 어노테이션을 붙이면, JPA가 해당 클래스를 관리하게 된다.@Id 어노테이션은 특정 속성을 기본키로 설정하는 어노테이션이다.@GeneratedValue(startegy = GenerationType.IDENTITY) 은 기본키 생성을 DB에 위임한다는 것으로 위 클래스에서는 id 값이 자동 생성된다.@Column 은 객체필드를 테이블 칼럼과 매핑한다. 2) JpaRepository 인터페이스 상속받는 인터페이스 생성public interface FruitRepository extends JpaRepository<Fruits,Long> { Optional<Fruits> findById(Long id); long countByNameAndSaled(String name,int i); long countByName(String name); List<Fruits> findByPriceGreaterThan(long price); List<Fruits> findByPriceLessThan(long price); List<Fruits> findBySaled(int i); } 문제2특정 과일을 기준으로 지금까지 우리 가게를 거쳐갔던 과일 개수를 세고 싶습니다.controller 클래스에서는 아래와 같이 getmapping 부분을 만들고@GetMapping("/api/v1/fruit/count") @Description("지금까지 거쳐간 특정 과일이름의 개수를 반환(7일차-문제2)") public FruitCountResponse countName(@RequestParam String name){ return fruitService.countName(name); }service 클래스에서는  public FruitCountResponse countName(String name) { Long result = fruitRepository.countByName(name); return new FruitCountResponse(result); }위와 같이 FruitCountResponse 객체를 반환하도록 한다.FruitCountResponse  클래스는 아래와 같이 간단하게 json 형식으로 "count" : 숫자 를 반환하도록 만들었다.public class FruitCountResponse { private long count; public FruitCountResponse(long count) { this.count = count; } public long getCount() { return count; } }아래와 같이 HTTP 응답 Body 예시와 같은 형식으로 나오는 것을 볼 수 있다.  문제3 아직 판매되지 않은 특정 금액 이상 혹은 특정 금액 이하의 과일 목록을 받아보고 싶습니다.controller 클래스에서는 @GetMapping("/api/v1/fruit/list") @Description("판매되지 않은 특정 금액 이상 혹은 특정 금액 이하의 과일 목록(7일차-문제3)") public List<Fruits> getListFruit(@RequestParam String option, @RequestParam long price){ return fruitService.overviewFruitCondition(option,price); }service 클래스에서는public List<Fruits> overviewFruitCondition(String option, long price) { List<Fruits> list; List<Fruits> result = new ArrayList<>(); if (option.equals("GTE")) list = fruitRepository.findByPriceGreaterThan(price); else if (option.equals("LTE")){ list = fruitRepository.findByPriceLessThan(price); }else throw new IllegalArgumentException("GTE, LTE 로 작성해주세요."); for (Fruits e : list) if (e.getSaled() == 1) result.add(e); return result; }위와 같이 List<Fruits> 를 반환하도록 한다.   위와 같이 조건을 만족하는 (6000 이하의 과일들) 결과를 출력한다. 회고직접 JpaRepository를 상속받는 repository와 entity 를 만들어 실습해보니, 약간 익숙해진 것 같다. 위 과제를 하는 중간중간 오류(1,2) 가 났었는데,1) getter of property ‘id’ threw exceptionhttps://stackoverflow.com/questions/25234892/org-springframework-beans-invalidpropertyexception-invalid-property-id-of-bea org.springframework.beans.InvalidPropertyException: Invalid property 'id' of bean classI dont understand why I am getting this error on the save below. Any clue? org.springframework.beans.InvalidPropertyException: Invalid property 'id' of bean class [com.test.DataException]: Getter...stackoverflow.com위 오류에서는 long에서 Long으로 id 타입을 바꾸니 해결되었다.2) Unknown column 'warehousing_date' in 'field list'위 오류에서는 warehousing_date 라는 칼럼명을 못 찾아서 데이터를 save 할 수 없는 상황이였는데,변수 값을 잘못 넣어서 오류가 났던 거라 바꾸니 해결되었다.

백엔드백엔드7차과제JPAEntity

채널톡 아이콘