728x90
반응형

한 달 내내 수많은 오류(Dwolla Customer error(중복된 customer ID로 인해 발생한 에러였음. email을 다르게 하면 해결됨), connect bank button error(오탈자로 인해 database에 제대로 정보 전송이 되지 않아서 발생한 에러였음, 해결됨) 등..)와 함께한 프로젝트가 거의 완성될 때 쯤 원인을 전혀 모르겠는 에러 문제에 봉착하게 된다.

 

사용된 API는 dwolla와 Plaid라는 프로그램이었는데 두 개 중, Plaid에서 Sandbox 측 오류였다.

 

 

ADDITIONAL_CONSENT_REQUIRED라는 오류로, 이는 현재 클라이언트가 거래 정보 (PRODUCT_TRANSACTIONS)에 접근할 수 있는 사용자 동의를 받지 못했음을 의미하는 오류이다.터미널 상으로 보이는 문제는

Plaid 문제였기 때문에 Plaid 설정에서 권한을 주면 되는 줄 알고 온갖 도움말을 보며 해결방안을 모색했으나 뾰족한 해결방법을 알아낼 수 없어 머리를 싸매고 고민하던 중, component 페이지 중 Transation History 페이지에서도 

 

 

map에 관한 에러가 발생한다.

Plaid 가상 데이터가 들어와야 하는데 들어오지 않아서 생긴 문제였다.

 

 

아무래도 6개월 전 영상이다보니 그 사이에 Plaid의 정책이 변경되거나 하면서 원래의 코드로는 데이터가 불러와지지 않는 모양이었다.

같은 오류를 겪고 있는 사람이 있을까 하여 서칭을 통해 알아봤더니 생각보다 간단한 해결방법이었다.

 

파일 중 user.action.ts 파일의 코드 중 createLinkToken 함수의 prodects 부분이 ["auth"] as Products[ ], 뿐인 것을

products: ["auth", "transactions"] as Products[], 로 고쳐주고 

수정 전 코드
수정 후 코드

 

 

appwrite의 auth에 저장된 가입한 회원정보를 전부 지우고 dwolla에 등록된 email 아이디를 전부 비활성화 시킨 다음,

 

appwrite auth 정보 지우기

 

dwolla 비활성화

 

새로 가입해주면,

 

 

원래 들어오지 않았던 Recent transations 데이터와 비어있던 Top categories에 데이터가 들어오는 것을 확인할 수 있다.

 

또한, 원래 run time error 창이 떴던  Transation History 페이지까지

 

제대로 동작하는 것을 확인할 수 있다.

 

 

이런 간단한 문제를 가지고 3일이나 고생했다니. 진짜 믿기지가 않는다.

그래도 수많은 에러를 거쳐 완성한 프로젝트의 끝을 봐서 기쁘고 감격스러울 따름이다.

 

728x90
반응형

Use Stack

 

cloudinary

:클라우드 기반의 이미지 및 비디오 관리 플랫폼.

-파일 업로드 : 이미지, 비디오, 오디오 파일 등을 클라우드에 업로드 할 수 있음.

-자동 변환 : 업로드된 파일의 포맷, 크기, 품질 등을 자동으로 조정하거나 변환할 수 있음.

-CDN 제공 : 파일을 전 세계에 있는 Content Delivery Network에서 제공하여 빠른 로딩 속도를 보장함.

-이미지 최적화 : 성능을 향상 시키기 위해 이미지와 비디오를 최적화 함.

-강력한 API : 업로드, 관리, 변환 및 삭제 등 다양한 기능을 API를 통해 제어할 수 있음.

 

 

 

pusher

: 실시간 애플리케이션을 쉽게 구축할 수 있도록 도와주는 서비스.

-실시간 메시징 : WebSoket을 사용하여 클라이언트와 서버 간에 실시간으로 데이터를 주고받을 수 있음

-채널 : 사용자나 그룹 간의 실시간 상호작용을 위한 채널을 지원

-푸시 알림 : 사용자에게 실시간 알림을 보낼 수 있음

-자동 재연결 : 네트워크 문제가 발생했을 때 자동으로 재연결 시도

 

 

 

prisma db

: 현대적인 ORM(객체 -관계 매핑) 도구로, 데이터베이스와의 상호작용을 간소화 해줌.

-데이터 모델링 : Prisma Schema를 사용하여 데이터베이스 모델을 정의하고 마이그레이션을 관리할 수 있음.

-타입 안정성: Type Script와 통합되어 타입 안전한 데이터베이스 쿼리를 작성할 수 있음.

-데이터베이스 클라이언트 : Prisma Client를 통해 데이터베이스 쿼리를 쉽게 작성하고 실행할 수 있음.

-데이터베이스 마이그레이션 : Prisma Migrate를 사용하여 데이터베이스 스키마의 변경 사항을 관리하고 적용할 수 있음.

 

✏ 마이그레이션

:데이터베이스 구조의 변경 작업을 관리하고 추적하는 과정. 주로 스키마의 버전 관리를 통해 데이터베이스 구조를 업데이트하고, 기존 데이터를 새로운 구조에 맞게 변환하는 작업을 말함.

 

 

zustand

: 간단하고 직관적인 상태 관리 라이브러리

-간단한 API : 사용하기 쉽고, 복잡한 설정 없이 상태를 관리할 수 있음.

-React 호환성 : React의 상태 관리를 더 간단하게 할 수 있는 훅 기반의 라이브러리.

-빠른 성능 : 상태 업데이트가 빠르고, 리렌더링 최적화가 잘 되어 있음.

-구성 가능성:  다양한 상태 관리 패턴을 지원하며, 커스터마이즈가 가능함.

 

✏ 리렌더링

: React 등의 컴포넌트의 상태나 Props가 변경될 때 해당 컴포넌트가 다시 그려지는 과정을 말함. 리렌더링은 UI가 최신 상태를 유지하도록 함.

 

 

 

React-spinners

: React 애플리케이션에서 로딩 스피너를 쉽게 사용할 수 있도록 해주는 라이브러리

- 다양한 스피너 : 여러 가지 스타일과 애니메이션 효과를 가진 스피너 컴포넌트를 제공함.

-커스터마이징 : CSS 또는 스타일 속서을 사용하여 스피너를 쉽게 커스터마이즈 할 수 있음.

-간편한 사용: React 컴포넌트 형태로 제공되어 쉽게 사용할 수 있음.

 

 

 

next-superjson-plugin

: Next.js 애플리케이션에서 JSON 직렬화 및 역직렬화를 보다 간편하게 해주는 도구

-복잡한 데이터 직렬화 : 날짜 객체, Map, Set 등 JSON으로 직렬화 하기 어려운 데이터 타입을 지원.

-성능 최적화 : JSON의 직렬화와 역직렬화 과정에서 성능을 최적화

-Next.js 통합 : Next.js의 API 라우트와 서버 사이드 렌더링(SSR)에서 JSON 데이터를 처리하는데 도움을 줌.

 

✏ 직렬화

: 데이터 구조나 객체를 네트워크 전송이나 저장을 위해 문자열이나 바이트 스트림(데이터가 연속된 바이트 형태로 전송되거나 저장되는 데이터 흐름)으로 변환하는 과정. JSON, XML, BSON 등이 직렬화의 대표적인 형식.

 

✏  역직렬화

:  직렬화 된 데이터를 원래의 객체나 데이터 구조로 복원하는 과정. 네트워크에서 받은 데이터나 파일로부터 직렬화된 문자열을 읽어들여, 원래의 데이터 구조로 변환함.

 

 

 

NextAuth.js

: Next.js 애플리케이션에서 인증을 쉽게 구현할 수 있도록 도와주는 라이브러리.

 

 

useCallback

:React의 훅으로, 특정 콜백 함수를 메모이제이션 함.

즉, 의존성 배열에 지정된 값이 변경되지 않는 한, 동일한 콜백 함수 인스턴스를 반환.

ex. 함수가 자주 렌더링 되는 컴포넌트에서 불필요한 렌더링을 방지할 때 사용.

 

✏ 메모이제이션

: 함수의 반환값을 캐싱하여 동일한 입력값에 대해 함수가 반복적으로 계산되는 것을 방지하는 기술.

 

 

useRouter

: Next.js의 훅으로 클라이언트 사이드에서 라우팅 관련 정보를 가져오거나, 라우터를 조작할 수 있게 해줌.

ex. 페이지 이동, 쿼리 파라미터 읽기, 라우터 상태 접근에 사용.

 

 

 

NextResponse

:Next.js의 app 디렉토리와 관련된 새로운 API를 통해 사용되는 객체로, 서버 사이드에서 HTTP 응답을 생성하는데 사용.

ex. API 라우트나 서버 사이드 렌더링에서 HTTP 응답을 커스터마이즈 할 때 사용.

 

 

pusher Client

:  Pusher의 클라이언트 라이브러리로, 실시간 기능을 제공하는데 사용됨. 웹소켓을 통해 서버와 클라이언트 간의 실시간 통신을 지원.

 

 

pusherServer

:Pusher의 서버 라이브러리로, 서버에서 실시간 기능을 설정하고 관리하는데 사용됨.

 

 

IParams

: 타입스크립트에서 사용하는 인터페이스로, 주로 라우트 핸들러나 함수의 파라미터를 정의할 때 사용됨.

ex. API 핸들러나 라우트에서 파라미터의 타입을 정의할 때 사용.

 

✏  파라미터

: 함수나 메서드에 입력으로 전달되는 변수

 

 

React Select

:React에서 동적인 셀렉트 박스를 구현할 수 있게 해주는 라이브러리. 다양한 스타일과 기능을 제공함.

ex. 드롭다운 선택 박스를 구현할 때 사용됨.

 

 

useActiveChannel

: Pusher 또는 비슷한 라이브러리의 훅으로, 활성화된 채널에 대한 상태를 관리하는데 사용됨. 이 훅은 특정 채널의 데이터 또는 상태를 구독함.

 

 

@headlessui/react의 Transition과 Dialog

: Transition 컴포넌트는 애니메이션과 전환 효과를 적용하는데 사용됨. 상태에 따라 요소의 등장과 퇴장을 애니메이션으로 처리할 수 잇음.

Dialog 컴포넌트는 모달 대화상자를 구현하는데 사용됨. 사용자와 상호작용할 수 있는 팝업을 쉽게 만들 수 잇음.

ex. UI 요소의 부드러운 전환 효과를 구현할 때 사용, 사용자 입력을 받거나 정보를 표시하는 모달창을 구현할 때 사용.

 

 

ClipLoader

: react-spinners 라이브러리에서 제공하는 스피너 컴포넌트로, 로딩 상태를 시각적으로 표시할 때 사용.

ex. 페이지나 데이터 로딩 중 사용자에게 로딩 상태를 표시할 때 사용.

 

 

Fragment

: React의 컴포넌트로, 여러 자식 요소를 그룹화 할 때 사용됨. DOM에 추가적인 노드를 생성하지 않고 여러 자식 요소를 반환할 수 있음.

ex. 부모 요소를 추가하지 않고 자식 요소를 반환할 때 사용함.

 

 

 

 

 

728x90
반응형

우여곡절 끝에 완성시킨 messenger cloe 프로젝트를 netlify에 빌드 시키려던 나는 어떤 오류에 가로막혔다.

처음엔 netlify에 업로드 된 프로젝트가 많아서 그런 줄 알고 vercel에 재업로드를 시켰으나,

두둥! 같은 오류가 뜨는 것이 아니겠는가.

 



유형 오류: 경로 "app/api/auth/[...nextauth]/route.ts"가 Next.js 경로의 필수 유형과 일치하지 않습니다.
  "authOptions"는 유효한 경로 내보내기 필드가 아닙니다.

 

nextAuth의 route가 경로의 필수 유형과 일치하지 않는다는 아주 당황스러운 오류였다.


영상 주인은 빌드만 잘 되더만 왜 나에게 이런 시련이 주어지는지에 대해 눈물을 좔좔 뽑으며 영상 주인의 github를 긁어 내가 오탈자를 냈나? 아니면 지운 구문을 가지고 있었나? 하는 것을 눈 빠지게 쳐다보며 고민했지만 줄바꿈 외에는 아무런 문제도 없었다. 그러면 대체 왜!!! 이런 오류가 발생하여 나를 괴롭게 한단 말인가.

하지만 여기서 포기하면 집념의 개발자라는 이름을 자소서에 함부로 쓰지 못할 터.
완성하기까지 걸린 시간이 아까워서라도 꼭 빌드 시키고야 말겠다는 의지에 불타올라 혼자 삼일 밤낮으로 고민하다가 지친 나는 gtp의 도움을 살짝쿵 받기로 했다.

맨땅에 헤딩도 흙밭에 해야 살아있지 아스팔트 바닥에다 하면 머리만 깨지고 나만 죽는다.

 

GPT의 답변은 이랬다.


이 오류는 Next.js에서 특정 경로에 맞는 내보내기(export) 필드가 잘못 정의되었기 때문에 발생하는 것입니다. 이 문제는 Next.js 13 이상 버전에서 app 디렉토리와 관련된 라우트 파일에서 흔히 발생할 수 있습니다.

NextAuth의 설정을 위한 코드는 정상으로 보이지만, Next.js 13의 새로운 app 디렉토리 기반 라우팅 시스템에서는 경로 파일에 기본 내보내기(default export)를 사용하는 것이 아니라 GET, POST와 같은 HTTP 메서드를 특정하여 내보내야 합니다.

문제점: route.ts 파일에서 authOptions를 내보내는 대신, NextAuth 핸들러를 GET과 POST로 내보내야 합니다. 이미 코드의 마지막에 handler를 GET과 POST로 내보내고 있기 때문에 authOptions를 따로 내보낼 필요가 없습니다.

authOptions가 경로 파일에서 직접 내보내지기 때문에 발생하는 오류입니다.



해결책:

authOptions를 내보내는 부분을 제거합니다.
이미 있는 handler를 GET과 POST로 내보내는 부분을 유지합니다.

 

 



GPT가 export와 몇 개의 구문을 제거한 코드를 알려줬기에 그대로 입력 후 git에 업로드 후 희망차게 다시 빌드했으나, 돌아온 것은....


또 다른 오류였다. 오 마이 갓김치. (난 파김치가 좋다.) 
어쨋든, 이번 오류는 authOptions/route.ts가 로컬에서 authOptions를 선언하긴 하지만 내보내진 않는다. 는 거였다.


그래서 GPT도 믿을 게 못 된다. 발로 뛰어보자 싶었던 나는 영상 주인의 discord 서버를 들어가게 된다.

이 무슨 행운인가. 나랑 같은 오류로 고생하는 사람을 만나게 된 것이다.
하늘에서 빛줄기가 내려와 나를 감싸는 기분이었다.

드디어 나도 이 오류를 아름답게 해결할 수 있을 것이라는 희망에 부푼 가슴을 안고 열어봤더니


코드를 새로 주셨는지 방법을 알려주셨는진 모르겠지만 (당사자끼리의 대화다보니) 코드 어딘가가 바뀌었고, 그곳에서 새로운 오류가 발생 중이라는 얘기 중이었다.

문맥 상으로는 route.ts를 libs/auth.ts로 변경하는 코드를 새로 작성하셨는데 그럼에도 불구하고 빌드 과정에서 오류가 있으셨던 모양.

근데 댓글을 달아주신 분은 어댑터에 문자열 유형을 지정해야 한다고 하셨고, 이 댓글을 본 작성자분은 코드를 보여줄 수 있냐고 여쭤보셨다.

또 그걸 본 댓글 다신 분이 나는 코드를 보여줬다고 8월 24일에 마지막 댓글을 남기신 것이 이 글의 끝이었다.
이게 한국 블로그에만 있는 비밀 댓글입니다. 하는 그런 건가? 
서양판 비밀 댓글입니다. 이런 건가? 싶었지만 해결책의 실마리라도 잡은 나는 이 얇은 실을 붙잡고 오류의 미궁을 헤쳐나가 보기로 했다.

원래 이런 건 혼자 맨 땅에 헤딩도 해보고, 삽질도 해 보고, 티스푼 같은 얇고 가는 숟가락으로 땅도 파 봐야 기억에 남고 마음에 남아 비슷한 일을 당했을 때 혼자서 해결하는 멋진 어른이로 자라는 법이다.

 

그래서 시키는 대로 libs 폴더에 nextAuthOptions.ts 라는 파일을 만든 후 원래의 route.ts 파일을 싹 다 긁어서 붙여넣은 후 이제 됐나? 하고 바로 다시 build를 시켰더니 이번에는 

authOptions의 import 경로가 잘못 되었다는 오류가 발생했다.

그렇다. 겨우 그정도로 장땡이 아니었던 것이다.

 

authOptions가 import된 모든 파일의 경로를 nextauth/route.ts에서 libs/nextAuthOptions로 변경 시키는 작업이 필요했던 것.

 

 

이게 원래 nextauth/route.ts 파일이고,

import bcrypt from "bcrypt";
import NextAuth, { AuthOptions } from "next-auth"; // 이 부분 변경됨
import CredentialsProvider from "next-auth/providers/credentials";
import GithubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@next-auth/prisma-adapter";

import prisma from "@/app/libs/prismadb";

export const authOptions: AuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID as string,
      clientSecret: process.env.GITHUB_SECRET as string,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
    CredentialsProvider({
      name: 'credentials',
      credentials: {
        email: { label: 'email', type: 'text' },
        password: { label: 'password', type: 'password' },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) {
          throw new Error('Invalid Credentials');
        }

        const user = await prisma.user.findUnique({
          where: {
            email: credentials.email
          }
        });

        if (!user || !user?.hashedPassword) {
          throw new Error('Invalid credentials');
        }

        const isCorrectPassword = await bcrypt.compare(
          credentials.password,
          user.hashedPassword
        );

        if (!isCorrectPassword) {
          throw new Error('Invalid credentials');
        }

        return user;
      }
    })
  ],
  debug: process.env.NODE_ENV === 'development',
  session: {
    strategy: "jwt",
  },
  secret: process.env.NEXTAUTH_SECRET,
};

const handler = NextAuth(authOptions); // 여기와

export { handler as GET, handler as POST }; // 여기가 사라짐

 

 

이게 바뀐 libs/nextAuthOptions.ts 파일이다.

// lib/nextAuthOptions.ts
import bcrypt from "bcrypt";
import { AuthOptions } from "next-auth";	//이 부분
import CredentialsProvider from "next-auth/providers/credentials";
import GithubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "@/app/libs/prismadb";

export const authOptions: AuthOptions = {
  adapter: PrismaAdapter(prisma),
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID as string,
      clientSecret: process.env.GITHUB_SECRET as string,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
    CredentialsProvider({
      name: "credentials",
      credentials: {
        email: { label: "email", type: "text" },
        password: { label: "password", type: "password" },
      },
      async authorize(credentials) {
        if (!credentials?.email || !credentials?.password) {
          throw new Error("Invalid Credentials");
        }

        const user = await prisma.user.findUnique({
          where: { email: credentials.email },
        });

        if (!user || !user?.hashedPassword) {
          throw new Error("Invalid credentials");
        }

        const isCorrectPassword = await bcrypt.compare(
          credentials.password,
          user.hashedPassword
        );

        if (!isCorrectPassword) {
          throw new Error("Invalid credentials");
        }

        return user;
      },
    }),
  ],
  debug: process.env.NODE_ENV === "development",
  session: { strategy: "jwt" },
  secret: process.env.NEXTAUTH_SECRET,
};

import의 NextAuth 구문이 사라지고, const hnadler = NextAuth(authOptions)와 export 구문이 사라졌다.

 

두 코드의 차이점은

1. 파일 위치 및 import

route.ts는 직접 NextAuth를 설정하고 핸들러를 내보내는 코드지만, nextAuthOptions은 anthOptions를 정의하는 설정 파일이며, 이 설정 파일은 NextAuth 인스턴스를 생성하는 메인 파일에서 import하여 사용할 수 있다는 차이점이 있다.

 

2. 코드 구조

route.ts는 authOoptions를 직접 정의하고 NextAuth 핸들러를 생성하고 export { handler as GET, handler as POST }로 핸들러를 내보낸다. 그러나 nextAuthOptions는 authOptions를 정의하게 되는데 이 authOptions는 libs/nextAuthOptions.ts에서 정의되며, NextAuth 핸들러는 이 파일을 import 하여 사용하는 다른 파일에서 생성되게 될 것이다.

 

3. 모듈화

route.ts는 authOptios와 NextAut 핸들러를 한 파일에서 정의하고 내보기에 이 파일이 NextAuth 관련 모든 기능을 포함하고 있지만, nextAuthOptions는 authOptions만 정의하고, 핸들러는 다른 파일에서 생성한다. 이로 인해 설정화 핸들러가 분리되며, 설정 파일과 핸들러를 분리하면 코드의 유지 보수성과 가독성이 높아진다.

 

정도의 차이점이 있다.

다른 건 그렇다 치더라도, 긴 코드가 줄어들어 가독성이 좋아진 건 알겠지만 코드의 유지 보수성은 체감이 잘 안 된다.

하나였던 파일이 세 개로 늘어나기 때문이다.

 

nextAuthOptions말고도 nextAuthOptions를 import해서 사용하는 다른 파일들 역시 경로를 바꿔주어야 한다.

 

getSession은  원래 authOptions이 api/auth/[...nextAuth]/route에서 import 되었는데, 

import { getServerSession } from "next-auth";

import { authOptions } from "../api/auth/[...nextauth]/route"; //route에서 import 되던 것

export default async function getSession() {
  return await getServerSession(authOptions);
}

 

이런 식으로 libs/nextAuthOptions에서 import 되도록 변경해주어야 하고,

import { getServerSession } from "next-auth";
import { authOptions } from "@/app/libs/nextAuthOptions"; 	//route에서 import 되던 게 libs로 변경됨

export default async function getSession() {
  return await getServerSession(authOptions);
}

 

 

 

 

authOptions을 import 해뒀던 pages/api/pusher 폴더에 있던 auth 파일 역시

import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";

import { pusherServer } from "@/app/libs/pusher";
import { authOptions } from "@/app/api/auth/[...nextauth]/route"; // 이 부분

export default async function handler(
  request: NextApiRequest,
  response: NextApiResponse
) {
  const session = await getServerSession(request, response, authOptions);

  if (!session?.user?.email) {
    return response.status(401);
  }

  const socketId = request.body.socket_id;
  const channel = request.body.channel_name;
  const data = {
    user_id: session.user.email
  };

  const authResponse = pusherServer.authorizeChannel(socketId, channel, data);

  return response.send(authResponse);
}

 

api/auth/[...nextauth]/route에서 import 하던 authOptions을 

 

import { NextApiRequest, NextApiResponse } from "next";
import { getServerSession } from "next-auth";

import { pusherServer } from "@/app/libs/pusher";
import { authOptions } from "@/app/libs/nextAuthOptions"; // 이 부분

export default async function handler(
  request: NextApiRequest,
  response: NextApiResponse
) {
  const session = await getServerSession(request, response, authOptions);

  if (!session?.user?.email) {
    return response.status(401);
  }

  const socketId = request.body.socket_id;
  const channel = request.body.channel_name;
  const data = {
    user_id: session.user.email,
  };

  const authResponse = pusherServer.authorizeChannel(socketId, channel, data);

  return response.send(authResponse);
}

 

 

이런 식으로 libs/nextAuthOptions 에서 import 하도록 변경해준 후, 변경된 파일을 다시 git에 업로드 해주면,

아름답게 vercel에 build 할 수 있게 되는 것이다.

 

짜잔 

보이시는가?

영롱한 vercel이 적힌 도메인의 자태가.

드디어 해결을 본 것이다.

 

고생한 거에 비해 생각보다 별 거 아닌 문제였어서 김이 좀 샜지만, 다음 번에도 비슷한 문제가 생기면 그 때는 허둥대지 않고 침착하게 차근차근 해결하는 멋진 개발자 어른이가 되길 바라며, 

나와 같은 고생을 하고 있을지도 모르는 미래의 개발자 동료들에게 도움이 되길 바라며 일지(를 가장한 하소연)을 마치겠다.

 

 

728x90
반응형

 

useSession

ex.

import { useSession, signIn, signOut } from "next-auth/react";

function MyComponent() {
  const { data: session } = useSession();

  if (session) {
    return (
      <>
        <p>Signed in as {session.user.email}</p>
        <button onClick={() => signOut()}>Sign out</button>
      </>
    );
  }
  return (
    <>
      <p>Not signed in</p>
      <button onClick={() => signIn()}>Sign in</button>
    </>
  );
}

: Next.js의 인증 라이브러리인 next-auth에서 제공하는 후긍로, 사용자의 세션 정보를 관리하고 접근할 수 있으며, 현재 로그인된 사용자의 세션 데이터를 가져올 수 있고, 로그인 상태를 확인하고 이에 따라 UI를 조건부 렌더링 할 수 있음.

로그인 상태에 따라 다른 UI를 렌더링 하며, signIn함수와  signOut함수를 통해 로그인과 로그아웃을 처리함.

 

 

 

useRouter

ex.

import { useRouter } from "next/router";

function MyComponent() {
  const router = useRouter();

  const handleClick = () => {
    router.push("/about"); // "/about" 페이지로 이동
  };

  return (
    <div>
      <p>Current path: {router.pathname}</p>
      <button onClick={handleClick}>Go to About Page</button>
    </div>
  );
}

: next.js에서 제공하는 훅으로, 라우터 객체에 접근할 수 잇게 해줌. 이를 통해 페이지 전환, 쿼리 파라미터 접근, 현재 경로 정보 등을 얻을 수 있음. useRouter로 현재 경로와 같은 라우팅 정보를 가져올 수 있으며, router.push로 프로그램적 페이지 이동이 가능함.

 

특징

1. 페이지 이동 : 프로그램적으로 페이지를 이동하거나, 쿼리 파라미터를 변경할 수 있음.

2. 라우팅 정보 접근 : 현재 경로, 쿼리 파라미터, 페이지 간 전환 등의 라우팅 정보를 얻을 수 있음.

 

✏ 쿼리 파라미터(Query Parameters)

: 웹 애플리케이션에서 URL을 통해 서버로 데이터를 전달하기 위해 사용되는 방법. 쿼리 파라미터는 주로 클라이언트가 서버에 특정 요청을 보낼 때 추가 정보를 전달하거나 필터링, 검색, 페이지네이션 등의 목적을 위해 사용됨.

 

구조

기본 구조

ex.

http://example.com/page?parameter1=value1&parameter2=value2

: URL에서 쿼리 파라미터는 물음표(?)로 시작하며, 키=값 쌍의 형태로 지정됨. 여러 개의 쿼리 파라미터는 앰퍼샌드(&)로 구분됨.

 

예시.

https://example.com/search?query=javascript&sort=asc&page=2

query=javascript : query라는 파라미터에 javascript라는 값을 전달함.

sort=asc : sort라는 파라미터에 asc라는 값을 전달하여 데이터를 오름차순으로 정렬하라는 의미.

page=2 :  page라는 파라미터에 2라는 값을 전달하여 2번째 페이지의 데이터를 요청함.

 

사용 사례

1. 검색 : 사용자가 입력한 검색어를 서버로 전달하여 관련된 데이터를 필터링함.

2. 필터링 : 특정 조건에 맞는 데이터를 필터링하기 위해 사용됨.

3. 정렬 : 데이터를 특정 기준에 따라 정렬함.

4. 페이지네이션 : 대량의 데이터를 나눠서 보여줄 때, 특정 페이지의 데이터를 요청함.

5. API 요청 : API에서 특정 데이터를 요청할 때 쿼리 파라미터를 사용하여 원하는 데이터를 가져옴.

 

 

 

clsx

: css 클래스 이름을 조건에 따라 쉽게 조합할 수 있도록 도와주는 작은 Javascript 유틸리티.

예를 들어, 여러 클래스를 조건부로 적용하거나, 배열이나 객체 형태의 클래스를 사용할 수 있음.

 

ex.

import clsx from 'clsx';

const isActive = true;
const buttonClass = clsx('btn', {
  'btn-active': isActive,
  'btn-disabled': !isActive,
});

// buttonClass는 "btn btn-active"로 설정됩니다.

 

 

 

format (date-fns)

: Javascript의 날짜를 다루기 위한 라이브러리로, format 함수는 날짜를 특정 형식으로 포맷팅하는 데 사용됨.

 

ex.

import { format } from 'date-fns';

const date = new Date();
const formattedDate = format(date, 'yyyy-MM-dd');
console.log(formattedDate); // "2024-08-09"

//여기서의 format 함수는 현재 날짜를 년-월-일 형식으로 반환

 

 

 

쿠키

:웹 브라우저에 저장되는 작은 데이터 조각으로, 클라이언트와 서버 간의 상태나 세션 정보를 저장하는데 사용됨.

쿠키는 서버가 클라이언트에 설정하고, 이후 클라이언트가 해당 서버로 요청할 때마다 자동으로 포함됨.

도메인, 경로, 만료일, 보안 플래그 등을 설정할 수 있음

 

 

 

세션

: 서버에 저장되는 사용자별 데이터의 집합. 세션은 서버에서 관리되며 ,클라이언트는 세션 ID를 통해 자신의 세션에 접근함. 세션은 일반적으로 클라이언트가 서버와 상호작용하는 동안 상태를 유지하기 위해 사용되며, 세션 ID는 쿠키로 관리되기도 함.

 

 

 

쿠키와 세션의 차이

1. 저장 위치

쿠키  : 클라이언트(브라우저) 측에 저장

세션 : 서버 측에 저장

 

2. 보안

쿠키: 클라이언트 측에 저장되므로 상대적으로 보안에 취약함. 만료 기간을 지정할 수 있음.

세션 : 서버 측에서 관리되며, 상대적으로 보안성이 높음. 일반적으로 브라우저를 닫으면 세션이 종료됨.

 

3. 사용 목적

쿠키 : 사용자의 설정이나 로그인 상태 유지 등 클라이언트 측 정보를 저장.

세션 : 사용자 로그인 정보 등 보안이 필요한 상태를 서버 측에서 유지

 

 

 

가상 DOM (Virtual DOM)이 렌더링 시 기여하는 방식

: 가상 DOM은 실제 DOM의 가벼운 사본을 메모리에 유지하여 React와 같은 프레임워크에서 빠르고 효율적인 업데이트를 가능하게 함. 

변화가 발생하면 가상 DOM은 이를 기존 가상 DOM과 비교하여 최소한의 변경사항을 실제 DOM에 반영함. 이를 diffing 알고리즘이라 하며, 실제 DOM 조작보다 훨씬 빠르게 수행됨.

가상 DOM 덕분에 React는 효율적으로 UI를 업데이트 할 수 있으며, 불필요한 DOM 변경을 줄여 성능을 최적화 함.

 

 

 

HTTP와 HTTPS

HTTP : Hyper Text Transfer Protocol의 약자로, 웹 브라우저와 서버 간에 하이퍼텍스트 문서(HTML)을 전송하는 프로토콜. HTTP는 기본적으로 텍스트를 암호화하지 않고 전송하므로 보안에 취약.

 

HTTPS : Hyper Text Transfer Protocol Secure의 약자로, HTTP에 SSL/TLS 프로토콜을 추가하여 데이터를 암호화하여 전송함. 이는 보안성과 무결성을 보장함.

 

 

✏ SSL / TLS 프로토콜

: SSL(보안 소켓 계층, Secure Sockets Layer)와 TLS(전송 계층 보안, Transport Layer Security)는 인터넷을 통해 전송되는 데이터의 보안을 강화하기 위한 암호화 프로토콜임. SSL은 원래 1990년대 중반에 넷스케이프(Netscape)에서 개발되었으며, TLS는 그 후속 버전으로 SSL의 개선된 버전임. 현재 SSL 3.0 이후의 버전은 더 이상 사용되지 않고 있으며, 대부분의 시스템에서는 TLS 프로토콜이 사용됨.

 

SSL/TLS의 주요 기능

1. 암호화 (Encryption) : 데이터를 전송하기 전에 암호화하여, 중간에 가로채어도 원래의 내용을 이해할 수 없도록 함.

2. 인증 (Authentication) : 서버와 클라이언트가 서로 신뢰할 수 있는지 확인함. 이를 위해 SSL/TLS는 디지털 인증서를 사용하여 서버의 신원을 확인함.

3. 데이터 무결성 (Integrity) : 전송 중 데이터가 변경되거나 손상되지 않았는지 확인함.

 

작동방식 (핸드셰이크 과정(Handshake)

1. 클라이언트 헬로(Client Hello) : 클라이언트가 서버에 접속을 시도하고, 사용 가능한 암호화 방식, TLS 버전, 무작위 값(Random Value) 등을 서버에 보냄

2. 서버 헬로 (Server Hello) : 서버는 클라리언트의 요청을 받아들이고, 선택한 암호화 방식, 인증서, 무작위 값 등을 클라이언트에게 보냄.

3. 서버 인증서 전송 (Server Certificate) : 서버는 자신의 공개키(Public Key)가 포함된 인증서를 클라이언트에게 보냄.

4. 세션 키 생성 (Session Key Generation) : 클라이언트는 서버의 공개키로 데이터를 암호화한 후, 이 데이터와 함께 세션 키를 생성하여 서버로 보냄. 서버는 자신의 개인키(Private Key)로 이를 복호화 함.

5. 대칭키 암호화 (Encrypted Communication) : 이후의 통신은 세션 키를 사용한 대칭 키 암호화 방식으로 진행되어, 빨고 안전하게 데이터를 전송함.

 

 

 

HTTP와 HTTPS의 차이

1. 보안

HTTP : 암호화되지 않은 텍스트 전송으로 보안에 취약

HTTPS : SSL/TLS를 통해 데이터를 암호화하여 보안성 확보.

 

2. 포트 번호

HTTP : 기본 포트는 80

HTTPS : 기본 포트는 443

 

3.  성능

HTTPS는 암호화/복호화 과정이 추가되므로 HTTP보다 약간 느릴 수는 있지만, 현대 웹에서는 이 차이가 거의 무시할 수 있을 정도로 줄어들었음.

 

 

✏ 암호화 / 복호화

: 암호화(Encryption)는 평문(Plain Text)을 암호문(Cipher Text)으로 변환하여, 인가되지 않은 사용자가 데이터를 읽응ㄹ 수 없도록 보호화는 과정. 반대로 복호화(Decryption)는 암호문을 다시 평문으로 변환하는 과정.

 

암호화의 종류

1.  대칭키 암호화(Symmetric Key Encryption)

: 하나의 키를 사용하여 데이터를 암호화하고 복호화합니다. 이 키는 암호화와 복호화에 동일하게 사용되므로, 키의 관리와 보호가 매우 중요합니다.

ex.  AES(Advanced Encryption Standard)

 

2. 비대칭키 암호화(Asymmetric Key Encryption):

서로 다른 두 개의 키(공개키와 개인키)를 사용하여 데이터를 암호화하고 복호화합니다. 공개키로 데이터를 암호화하고, 이에 대응하는 개인키로 복호화할 수 있습니다.

ex.  RSA(Rivest-Shamir-Adleman)

 

암호화/복호화의 과정 예시

대칭키 암호화

1. 데이터 "Hello World"를 대칭키로 암호화하면, 예를 들어 "aX1cE9!"와 같은 암호문이 생성됩니다.

2. 수신자는 동일한 대칭키로 "aX1cE9!"를 복호화하여 원래의 "Hello World"를 얻습니다.

 

비대칭키 암호화

1. 서버는 자신의 공개키를 클라이언트에게 제공하고, 클라이언트는 이 공개키로 데이터를 암호화합니다.

2. 암호화된 데이터는 서버의 개인키를 사용해 복호화됩니다.

 

 

프로토콜

: 컴퓨터 네트워크에서 데이터를 교환하는 규칙과 절차의 집합. 네트워크 프로토콜은 서로 다른 시스템 간의 통신을 가능하게 하며, 이를 통해 데이터를 정확하고 일관되게 전달할 수 있음.

네트워크 환경에서 다양한 종류의 통신을 관리하고 제어하는 중요한 규칙을 정의하며, 이를 통해 네트워크 내에서 일관성 있고 신뢰성 있는 데이터 전송이 가능함.

 

ex.

HTTP/HTTPS : 웹에서 하이퍼텍스트 문서를 전송하기 위한 프로토콜

FTP : 파일을 서버와 클라이언트 간에 전송하기 위한 프로토콜

SMTP : 이메일을 전송하기 위한 프로토콜

TCP/IP : 인터넷 프로토콜 스위트로, 데이터 전송을 위한 기본적인 프로토콜 집합.

728x90
반응형

bcrypt

: 비밀번호 해싱(hash) 알고리즘으로, 보안을 강화하기 위해 사용됨. 비밀번호를 직접 저장하는 대신 해시된 값을 저장하여, 데이터 유출 시에도 원래 비밀번호를 유추하기 어렵게 만듦.

 

특징

1. 가변적인 해상 라운드 : bcrypt는 해싱을 여러 번 반복하여 해시 값을 생성함. 라운드 수를 늘릴수록 연산이 복잡해지고, 해킹 시도를 더욱 어렵게 만듦.

2. 솔트(salt / 소금 아님) 추가 : 솔트는 비밀번호 해싱 과정에 추가되는 임의의 데이터로, 같은 비밀번호라도 다른 해시값을 생성하여 무차별 대입 공격을 방어함.

3. 비밀번호 검증 : bcrypt는 저장된 해시 값과 입력된 비밀번호의 해시 값을 비교하여 일치 여부를 확인함.

 

 

 

해싱(Hashing)

: 임의 길이의 데이터를 고정 길이의 해시 값으로 변환하는 과정. 이때 사용되는 함수가 해시 함수(hash function)임. 해시 함수는 입력 데이터(보통 텍스트, 파일, 또는 문자열)를 받아서 고정된 크기의 고유한 데이터 출력(해시 값 또는 해시 코드)을 생성함.

 

특징

1: 고정된 길이의 출력 : 입력 데이터의 크기와 상관없이 해시 함수는 항상 고정된 길이의 해시 값을 반환함.

2. 비가역성 : 해시 함수의 중요한 특성 중 하나는 비가역성임. 즉, 해시 값을 가지고 원래 데이터를 역으로 추측하거나 복원하는 것은 거의 불가능함. 이 때문에 해싱은 비밀번호 저장이나 데이터 무결성 검증에 유용함.

3. 빠른 연산 속도 : 해시 함수는 입력 데이터를 매우 빠르게 처리할 수 있어야 함. 이 속성 덕분에 해싱은 대량의 데이터 처리와 검색에서 효율적임.

 

용도

1: 비밀번호 저장 : 데이터베이서에 비밀번호를 평문으로 저장하는 것은 매우 위험함. 해싱을 통해 비밀번호를 고정 길이의 해시 값으로 변환해 저장하면, 데이터베이스가 해킹되더라도 원래 비밀번호를 쉽게 알 수 없음.

2. 데이터 무결성 검증 : 파일 전송이나 데이터 백업 시 해시 값을 생성해두고, 나중에 동일한 해시 값을 다시 계산해 비교함으로써 데이터가 손상되거나 변조되지 않았는지 확인할 수 있음.

3. 데이터 검색 및 인덱싱 : 해시 테이블은 키-값 쌍을 매우 효율적으로 저장하고 검색하는 자료구조로, 데이터베이스나 캐시 시스템에서 많이 사용됨.

 

bcrypt와 해싱

bcrypt는 단순한 해싱 알고리즘과는 달리, 해싱 과정에 솔트(salt)를 추가하고 여러 번의 해싱 라운드를 적용하여 보안을 덩욱 강화함. 솔트는 해시 충돌 방지와 비밀번호 해시의 유일성을 보장하기 위해 사용됨. 동일한 비밀번호라도 서로 다른 솔트를 사용하면 전혀 다른 해시 값이 생성됨.

 

비밀번호 검증

: bcrypt를 사용하면 해싱된 비밀번호와 사용자 입력 비밀번호를 비교할 때도 안전하게 비교할 수 있음. 이는 입력된 비밀번호를 동일한 방법으로 해싱하여 저장된 해시 값과 일치하는지 확인하는 방식으로 이루어짐.

 

 

 

비가역성

: 해시 함수의 중요한 특성 중 하나로, 입력 데이터를 해시 함수에 통과시켜 해시 값을 얻는 것은 쉽지만, 그 해시 값을 이용해 원래의 입력 데이터를 역으로 계산하는 것은 불가능하거나 매우 어렵다는 의미.

 

특징

한 방향성 : 해시 함수는 데이터를 한 방향으로만 처리할 수 있음. 즉, 원본 데이터에서 해시 값을 생성하는 것은 가능하지만, 생성된 해시 값만으로 원본 데이터를 복원할 수는 없음.

 

중요성

1. 비밀번호 저장 : 비가역성 덕분에 해시 함수는 비밀번호 저장에 매우 유용함. 예를 들어, 비밀번호를 해시로 변환하여 데이터베이스에 저장하면, 데이터베이스가 해킹되더라도 해시 값만으로는 원래의 비밀번호를 알아낼 수 없음.

2. 데이터 무결성 보장 : 파일이나 메시지를 전송할 때, 데이터를 해시화하여 보내면 수신자는 동일한 해시 값을 계산해 원본 데이터가 변조되지 않았는지 확인할 수 있음. 비가역성 덕분에 공격자는 해시 값을 보고 원본 데이터를 재구성하여 변조하는 것이 매우 어려움.

 

비가역성을 통한 보안 강화

1. 솔트와 해싱 : 비가역성을 강화하기 위해 솔트라는 무작위 데이터를 추가하여 해싱함. 솔트는 동일한 입력 값에도 다른 해시 값을 생성하게 만들어, 비가역성을 더욱 강화함.

2. 암호학적 해시 함수 : 비가역성을 가진 해시 함수는 암호학적 해시 함수라고 불리며, 주로 보안이 중요한 응용 프로그램에서 사용됨. 대표적인 암호학적 해시 함수로는 SHA-256, SHA-3, bcrypt 등이 있음.

 

한계

1. 충돌 공격 : 이론적으로는 두 개의 서로 다른 입력 값이 동일한 해시 값을 생성하는 경우가 있을 수 있음. 이를 해시 충돌이라 하며, 암호학적 해시 함수는 이러한 충돌이 발생할 확률을 극도로 낮추어 비가역성을 유지함.

2. 무차별 대입 공격 (Brute Force Attack) : 비가역성은 해시 값만으로 원본을 직접 복원할 수 없게 하지만, 공격자가 모든 가능한 입력 값을 해싱해보는 방식으로 원본을 찾아낼 수도 있음. 이런 공격을 방지하기 위해 bcrypt 같은 알고리즘은 해시 생성 속도를 인위적으로 느리게 만들어 공격을 어렵게 함.

 

 

 

useStat와 async, await의 차이

useStae : React 훅으로, 컴포넌트의 상태를 관리하는데 사용됨. 상태 값과 해당 값을 변경할 수 있는 함수를 반환하며, 컴포넌트의 상태가 변경될 때 컴포넌트를 다시 렌더링함.

async  : Javascript에서 비동기 작업을 간편하게 처리하기 위한 구문임. async 함수가 비동기 작업을 수행함을 명시하며 자동으로 promise를 반환함.

await : async 함수 내부에서 사용되며, promise가 해결될 때까지 함수의 사용을 일시중지 시킴.

 

차이점

1. 작동 방식

: useState는 react의 상태관리 훅이며, 값의 변경을 감지하고 컴포넌트를 재렌더링하지만, async / await는 비동기 작업을 순차적으로 처리하고, 비동기 함수의 결과를 기다릴 때까지 사용됨.

 

2. 사용 영역

: useState는 react 컴포넌트에서 상태를 관리하는데 사용되지만, async / await는 비동기 코드를 처리하는 모든 Javascript 코드에서 사용됨.

 

 

 

JavScript와 TypeScript의 차이점

 

Javascript

: 웹 개발에서 널리 사용되는 프로그래밍 언어로, 동적이며 타입 검사 없이 코드를 작성할 수 있음. 

 

특징

1. 동적 타이핑 : 변수의 타입을 미리 지정하지 않고, 런타임에 결정됨.

2. 유연성 : 코드 작성이 간단하지만, 런타임 오류 발생 가능성이 높음.

 

 

Typescript

: javascript에 정적 타입 검사를 추가한 상위 언어로 , 컴파일 타임에 오류를 잡아낼 수 있음.

 

특징

1. 정적 타이핑 : 변수와 함수의 타입을 미리 정의하여, 코드 안정성과 유지보수성을 높임.

2 . 향상된 IDE(통합 개발 환경) 지원: 코드 자동 완성, 타입 추론, 코드 리팩토링(기존 코드의 동작을 변경하지 않으면서 코드의 구조를 개선하여 가독성, 유지보수성 등을 향상시키는 작업) 등이 개선됨.

3. Javascript 호환성 : Typescript는 Javascript로 컴파일 되며, 기존 Javascript 코드와 함께 사용할 수 있음.

 

 

차이점

1. 타입 시스템 : Javascript는 동적 타이핑을 사용하는 반면, Typescript는 정적 타이핑을 사용함.

2. 개발 경험 : Typescript는 더 나은 개발자 경험을 제공하며, 특히 대규모 프로젝트에서 오류를 줄이고 코드 품질을 유지하는데 도움이 됨.

 

 

✏ IDE (통합 개발 환경)

:Integrated Development Environment의 약자로, 소프트웨어 개발을 위한 통합 도구 모음. 

코드 편집기, 컴파일러 또는 인터프리터, 디버거, 빌드 자동화 도구 등을 하나의 통합된 환겨에서 제공하여 개발자들이 효율적으로 작업할 수 있게 함.

 

주요 기능

1. 코드 작성 및 편집 : 코드 편집기에서 다양한 언어를 지원하며, 구문 강조(syntax highlighting) 및 자동 완성 기능을 제공.

2. 디버깅 : 코드에서 오류를 찾고 수정할 수 있는 디버거를 포함함.

3. 버전 관리 :Git 같은 버전 관리 시스템과 통합되어, 코드의 변경 사항을 추적하고 협업할 수 있게 해줌.

 

ex. Visual Studio Code, IntelliJ IDEA, Eclipse, PyCharm

 

 

✏ 코드 리팩토링 (Code Refactoring)

: 기존 코드의 동작을 변경하지 않으면서, 코드의 구조를 개선하여 가독성, 유지보수성, 성능을 향상 시키는 작업

 

목적

1. 가독성 향상 : 코드를 더 읽기 쉽고 이해하기 쉽게 만듦.

2. 유지보수성 개선 : 코드의 복잡도를 줄여 수정이나 추가 작업이 쉽게 이루어질 수 있도록 함.

3. 버그 감소 : 복잡한 코드를 단순화 하여 잠재적인 오류를 줄임.

 

ex. 함수 이름 변경, 코드 중복 제거, 메서드 추출

 

 

✏ 타입 추론

: 컴파일러가 명시적으로 타입을 선언하지 않아도, 변수나 함수의 타입을 자동으로 추론하는 기능. TypeScript와 같은 정적 타입 언어에서 많이 사용됨.

 

장점

1. 코드 간결화 : 개발자가 명시적으로 타입을 선언하지 않아도 되므로, 코드를 더 간결하게 작성할 수 있음.

2. 오류 방지 : 컴파일러가 타입을 추론해주므로, 잘못된 타입 사용을 방지할 수 있음.

 

ex. 

let num = 10; // 컴파일러가 num의 타입을 자동으로 number로 추론

 

 

✏ 로깅 (Logging)

: 애플리케이션의 동작이나 상태를 기록하는 작업. 로깅을 통해 시스템의 동작을 추적하고, 오류 발생 시 원인을 분석할 수 있음.

 

용도

1. 디버깅 : 프로그램이 어떻게 실행되고 있는지 확인하기 위해 로깅을 사용함.

2. 모니터링 : 시스템의 상태를 모니터링하고, 예기치 않은 동작이나 오류를 감지함.

3. 감사 로그 : 중요한 작업이나 이벤트의 기록을 남겨, 나중에 참조할 수 있게 함.

 

ex. 

console.log("User logged in:", userId);

 

 

 

React와 Redux, Next.js의 차이

 

React

: 사용자 인터페이스(UI)를 구축하기 위한 Javascript 라이브러리임. 컴포넌트를 기반으로 UI를 구성하고, 상태 관리를 통해 동적인 웹 애플리케이션을 만듦.

 

특징

1. 컴포넌트 기반 : UI를 재사용 가능한 컴포넌트로 분할하여 관리함.

2. 상태 관리 : useState, useReducer 등을 통해 상태를 관리함.

3. 가상 DOM : 변경 사항을 최소화 하여 성능을 최적화 함.

 

 

Redux

: 애플리케이션의 상태를 전역적으로 관리하기 위한 상태 관리 라이브러리임. React와 함께 자주 사용됨.

 

특징

1. 전역 상태 관리 : 모든 컴포넌트가 접근할 수 있는 단일 상태 저장소를 유지함.

2. 액션과 리듀서 : 상태 변경은 액션(action)을 통해 수행되며, 리듀서(reducer)가 새로운 상태를 반환함.

3. 미들웨어 : 비동기 작업이나 로깅 같은 추가 기능을 위해 미들웨어를 사용할 수 있음.

 

 

✏ 미들웨어 (Middleware)

: 애플리케이션의 요청(Request)과 응답(Response) 사이에 위치하여, 요청을 처리하거나 응답을 조작하는데 사용되는 함수. 주로 서버 측 애플리케이션에서 사용됨.

 

용도

1. 요청 처리 : 인증, 권한 검사, 데이터 변환, 로깅 등을 수행.

2. 응답 처리 : 응답 데이터의 변환, 캐싱, 압축 등을 수행

3. 라우팅 : 요청이 올바른 핸들러에 도달하도록 경로를 설정함.

 

 

 

Next.js

: React를 기반으로 한 프레임워크로, 서버 사이드 렌더링(SSR)과 정적 사이트 생성(SSG)를 지원함. SEO 최적화와 빠른 페이지 로딩을 위한 기능을 제공함.

 

특징

1. 서버 사이드 렌더링(SSR) : 초기 페이지를 서버에서 렌더링하여, SEO와 초기 로딩 속도를 개선함.

2. 정적 사이트 생성(SSG) : 빌드 타임에 HTML 파일을 생성하여 빠른 로딩을 지원함.

3. 파일 기반 라우팅 : 페이지 파일을 생성하는 것만으로 자동으로 라우팅이 설정됨.

 

✏ SEO (검색 엔진 최적화)

: Search Engine Optimization의 약자로, 웹사이트나 웹페이지를 검색 엔진 결과에서 더 높은 순위에 노출시키기 위한 다양한 최적화 기법을 말함.

 

목적

1. 트래픽 증가 : 검색 엔진에서 더 많은 사용자가 웹사이트를 발견할 수 있도록 하여, 트래픽을 늘림.

2. 가시성 향상 : 특정 키워드에 대해 웹 사이트의 가시성을 높여, 관련 검색에서 상위에 노출되도록 함.

 

주요 요소

1. 키워드 최적화 : 사용자가 검색할 가능성이 높은 키워드를 페이지 콘텐츠에 적절히 포함시킴.

2. 메타 태그 : 제목, 설명 등 메타 정보를 최적화하여 검색 엔진이 페이지를 올바르게 이해할 수 있도록 함.

3. 콘텐츠 품질 : 유익하고 가치있는 콘텐츠를 제공하여, 사용자의 만족도를 높임.

 

 

✏ SEO 최적화

: SEO의 여러 요소들을 체계적으로 적용하여, 검색 엔진에서 웹사이트 순위를 높이기 위한 과정을 의미.

 

방법

1. 모바일 친화성 : 모바일 사용자에게도 최적화 된 경험을 제공하기 위해 반응형 디자인을 적용함.

2. 페이지 속도 개선 : 페이지 로딩 시간을 단축하여 사용자 경험을 개선하고, 검색 엔진에서의 평가를 높임.

3. 백링크 구축 : 신뢰할 수 있는 다른 웹사이트로부터의 링크(백링크)를 얻어, 사이트의 권위를 높임.

 

 

 

React → Redux에서의 개선점

1. 전역 상태 관리의 일관성

: Redux는 애플리케이션의 전역 상태를 단일 스토어에서 관리함. 이로 인해 상태의 흐름이 일관되고 예측 가능하게 됨. 모든 상태 변경은 액션(Action)을 통해 이루지며, 이 액션이 리듀서(Reducer)를 통해 상태를 업데이트 함. 이 구조 덕분에 상태 변경이 투명하게 이루어지고, 디버깅이 용이함.

반면, React는 useState와 useReducer는 각각의 컴포넌트에 국한된 상태 관리를 제공하며, 전역 상태 관리에는 적합하지 않을 수 있음. 여러 컴포넌트 간에 상태를 공유해야하는 경우, React의 상태 관리 도구만으로는 복잡성이 증가할 수 있음.

 

2. 미들웨어 지원

: Redux는 강력한 미들웨어 생태계를 가지고 있음. Redux Thunk나 Redux Saga와 같은 미들웨어를 사용하면, 비동기 작업을 처리하거나 로깅, 분석, API 호출 등의 부가적인 작업을 상태 관리와 함께 통합적으로 처리할 수 있음. 이는 복잡한 애플리케이션에서 특히 유용함.

React의 상태 관리만으로는 이러한 미들웨어 기능을 자연스럽게 구현하기 어려울 수 있음. useEffect를 사용해 비동기 작업을 처리할 수는 있지만 Redux가 제공하는 만큼의 확장성과 일관성은 없음. 

 

3. 상태의 불변성 및 데이터 흐름

: Redux는 상태가 불변성을 유지하도록 설계되어 있음. 모든 상태 변경은 새로운 상태 객체를 반환하도록 강제됨. 이러한 불변성 원칙은 복잡한 상태 구조를 가진 애플리케이션에서 상태의 추적과 버그 예방에 도움을 줌.

 

React의 useState와 useReducer도 불변성을 유지할 수 있도록 코딩할 수는 있지만, 이는 개발자의 책임에 달려있음. 복잡한 상태를 관리할 때, Redux처럼 체계적인 방식이 없는 경우 상태 관리가 혼란스러워질 수 있음.

 

4. 대규모 애플리케이션에서의 확장성

: Redux는 대규모 애플리케이션에서 확장성이 뛰어남. 전역 상태가 증가하더라도 Redux의 아키텍처는 상태 관리가 체계적으로 이루어지도록 도와줌. 또한, 여러 개발자들이 협업할 때 상태 관리와 관련된 규칙이 명확하게 정의되어 있어 작업이 수월함.

반면, React의 상태 관리 도구만으로는 규모가 커질수록 상태 관리가 복잡해지고 관리하기 어려워질 수 있음.

 

 

 

React → Next. js에서의 개선점

1. SSR과 SSG 지원

: Next.js는 React의 클라이언트 사이드 렌더링(CSR) 외에도 서버 사이드 렌더링(SSR)과 정적 사이트 생성(SSG)을 지원하여 SEO와 성능을 크게 개선함.

 

2. 파일 기반 라우팅

: Next.js는 라우팅을 자동화하여, React의 수동 라이팅 설정보다 개발을 단순화 함.

 

3. 데이터 페칭

: Next.js는 getStaticProps, getServerSideProps와 같은 함수로 데이터 패칭을 간소화 하여, React의 데이터 패칭 로직보다 효율적이고 체계적인 방식을 제공함.

 

✏ 데이터 패칭

: 애플리케이션이 외부 소스 (ex. API, 데이터베이스, 파일 등)에서 데이터를 요청하고 받아오는 과정을 말함. 주로 웹 애플리케이션에서 서버로부터 데이터를 가져와 사용자에게 보여줄 때 사용됨.

728x90
반응형

의존성 배열

: useEffect 훅에서 특정 상태나 값이 변경될 때만 부수효과(side Effect)가 실행되도록 제어하는 배열.

이 배열은 useEffect의 두 번째 인자로 전달됨.

 

동작 방식

: 의존성 배열이 비어 있으면, useEffect는 컴포넌트가 처음 마운트 될 때 한 번만 실행됨.

특정 값들이 배열에 포함되어 있으면, 그 값들이 변경될 때마다 useEffect가 실행됨.

배열을 생략하면, useEffect는 컴포넌토가 매번 렌더링될 때마다 실행됨.

 

ex.

import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log('Count changed:', count);
  }, [count]); // count가 변경될 때만 useEffect 실행

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

이 코드에서 useEffect는 count 값이 변경될 때마다 실행됨.

 

 

 

메서드 바인딩(Method Binding)

: 객체의 메서드를 해당 객체에 묶어주는 작업을 의미.

특히 React의 클래스형 컴포넌트에서 이벤트 핸들러나 다른 메서드를 사용할 때 this 키워드를 올바르게 참조하기 위해 메서드를 바인딩 해야하는 경우가 있음.

 

바인딩의 필요성

: javascript에서 this는 호출 방식에 따라 달라짐. 클래스 메서드가 이벤트 핸들러로 사용될 때 this가 메서드를 호출한 객체(ex. 버튼 클릭 이벤트)로 설정될 수 있음. 이를 방지하고, 항상 클래스 인스턴스를 참조하게 하기 위해 바인딩이 필요함.

 

ex.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };

    // 메서드를 바인딩하지 않으면, this.increment에서 this가 undefined가 됨
    this.increment = this.increment.bind(this);
  }

  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.increment}>Increment</button>
    );
  }
}

여기서 increment 메서드(주로 카운터와 같은 기능을 구현할 때, 현재 값을 하나 증가시키는 역할을 함)는 this.increment.bind(this)를 통해 현재 인스턴스에 묶여야 함.

 

 

자동 바인딩 (화살표 함수 사용)

: 클래스 필드 문법을 사용해 메서드를 화살표 함수로 정의하면, 자동으로 바인딩 됩니다. 화살표 함수는 정의된 시점의 this를 유지함.

이 방식을 사용하면 코드가 더 간결해지고, 바인딩 실수를 줄여줌.

 

ex.

class MyComponent extends React.Component {
  state = { count: 0 };

  // 화살표 함수를 사용하면, 자동으로 바인딩됨
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <button onClick={this.increment}>Increment</button>
    );
  }
}

 

 

함수형 컴포넌트에서 바인딩

:this 키워드가 없으므로, 바인딩이 필요하지 않음. 이벤트 핸들러는 그냥 함수로 정의하고 필요에 따라 useState, useReducer, useRef 등을 사용해 상태를 관리함.

 

ex. 

import React, { useState } from 'react';

function MyComponent() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <button onClick={increment}>Increment</button>
  );
}

 

 

 

렌더링

: 컴포넌트가 화면에 표시되는 과정. React에서는 렌더링이 새로운 UI를 계산하고, 이를 실제 DOM에 반영하는 과정을 의미함. 렌더링은 컴포넌트의 상태(state)나 속성(props)이 변경될 때마다 발생함.

 

과정

1. 초기 렌더링 : 컴포넌트가 처음 화면에 나타날 때 발생함.

2. 재렌더링 : 상태나 속성이 변경될 때, 변경될 내용을 반영하기 위해 컴포넌트가 다시 렌더링 됨.

3. 정적 렌더링 :  정적인 HTML 파일을 브라우저가 로드하여 UI를 처음으로 그릴 때 발생함.

 

최적화

: 불필요한 재렌더링을 방지하기 위해 React.memo, useMemo, useCallback과  같은 최적화 기법을 사용할 수 있음.

 

 

 

useRef

: React의 훅(hook) 중 하나로, 참조(ref / 특정 DOM 요소나 값에 접근할 수 있는 방법)를 관리하기 위해 사용됨. 

useRef는 주로 직접 DOM 요소에 접근할 때 유용하며, 상태 업데이트 없이 값이 변해야 하는 경우에 사용됨.

예를 들어, 입력 필드에 포커스를 설정하거나 스크롤 위치를 제어할 때 사용할 수 있음.

 

반환값

: { current: value } 형태의 객체를 반환함. 이 객체는 컴포넌트가 렌더링 될 때마다 유지되며, 참조된 값은 current 속성을 통해 접근할 수 있음.

ex.

import React, { useRef } from 'react';

function FocusInput() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.focus(); // input 요소에 포커스 설정
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus the input</button>
    </div>
  );
}

→ useRef로 생성한 inputRef는 <input> 요소에 대한 참조를 유지하며, 버튼을 클릭하면 handleFocus 함수가 호출되고, inputRef.current.focus( )를 통해 입력 필드에 포커스가 설정 됨.

 

값 저장 및 유지

: useRef는 렌더링 간 값이 변하더라도, 컴포넌트가 다시 렌더링 되지 않도록 하는 방법으로 사용할 수 있음.

 

ex.

import React, { useRef, useState } from 'react';

function Timer() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);

  const handleClick = () => {
    countRef.current = countRef.current + 1;
    setCount(countRef.current); // 상태 업데이트
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

→ countRef.current에 값을 저장하면 상태처럼 작동하지만, 값이 업데이트되더라도 컴포넌트가 자동으로 다시 렌더링 되지 않습니다. (이 방법은 컴포넌트가 렌더링될 때마다 값을 재계산할 필요가 없을 때 유용함)

 

특성 및 주의사항

1. 렌더링과 무관한 값 관리 : useRef의 값은 컴포넌트의 렌더링과 무관하게 유지됨. 따라서 상태를 업데이트 할 필요 없이, 값만을 저장하고 싶을 때 사용할 수 있음.

2. 상태와의 차이점 : useState는 상태가 변경될 때마다 컴포넌트가 재렌더링 되는 반면, useRef는 값이 변경되어도 컴포넌트가 재렌더링 되지 않음.

3. DOM 접근의 대안 : useRef는 클래스형 컴포넌트에서 사용되던 React.createRef의 함수형 컴포넌트 대안임. 주로 DOM에 직접 접근해야 할 때 사용됨.

4. 실시간 변수 저장 : 상태를 사용하지 않고, 컴포넌트의 렌더링과 무관하게 실시간으로 값이 변경되어야 하는 경우에 유용함.

 

 

 

 

728x90
반응형

async

ex.

async function fetchData() {
  let response = await fetch('https://api.example.com/data');
  let data = await response.json();
  console.log(data);
}

: javascript에서 비동기 함수를 정의할 때 사용하는 키워드. 이 키워드가 붙은 함수는 항상 promise를 반환함.

비동기 작업을 처리하기 위해 await와 함께 사용됨.

 

✏ 비동기 작업

: 코드가 실행되는 동안 다른 작업이 완료될 때까지 기다리지 않고, 즉시 다음 작업을 처리하는 방식. 이는 코드가 멈추지 않고 계속 실행될 수 있도록 함. 비동기 작업은 주로 IO 작업 (ex. 파일 읽기/쓰기, 네트워크 요청)에서 사용됨.

 

 

 

await

ex.

async function fetchData() {
  let response = await fetch('https://api.example.com/data');
  let data = await response.json();
  console.log(data);
}

: async 함수 내부에서 사용되며, promise가 해결될 때까지 코드 실행을 잠시 멈추고 기다림. await는 promise의 결과값을 반환함.

 

✏ promise 

: javascript에서 비동기 작업을 관리하기 위한 객체. 비동기 작업이 완료되었을 때, 성공(resolve) 또는 실패(reject) 상태를 나타냄. promise는 세 가지 상태를 가짐. then, catch, finally 메서드를 사용하여 작업의 성공, 실패, 완료 상태를 처리함.

1. Pending (대기중) : 작업이 아직 완료되지 않은 상태.

2. Fulfilled (이행됨) : 작업이 성공적으로 완료된 상태.

3. Rejected (거부됨) : 작업이 실패한 상태.

 

 

 

catch

ex.

async function fetchData() {
  let response = await fetch('https://api.example.com/data');
  let data = await response.json();
  console.log(data);
}

: promise가 거부(reject) 되었을 때 실행할 콜백 함수를 지정함. 즉, 예외처리에 사용됨. promise 체인이나 try ... catch 블록에서 사용됨.

 

✏ promise 체인

: 여러 개의 비동기 작업을 순차적으로 처리하기 위해 then, catch를 연속적으로 연결하는 방식. 각 then 메서드는 이전 promise의 결과를 받아서 처리함.

 

 

 

finally

ex.

fetch('https://api.example.com/data')
  .then(response => response.json())
  .catch(error => console.error('Error:', error))
  .finally(() => console.log('Fetch attempt finished'));

:promise가 해결(resolve)되든 거부(reject)되든 상관없이 마지막에 실행할 코드를 정의함. then이나 catch 다음에 사용됨.

 

 

 

globalThis

ex.

globalThis.myVariable = 42;
console.log(window.myVariable); // 브라우저 환경에서 42 출력

: javascript의 전역 객체를 참조함. 브라우저 환경에서는 widow로, Node.js에서는 global로 매핑됨. ES2020에서 도입됨. 즉, 환경에 상관없이 전역 객체에 접근할 때 사용됨.

 

✏ 전역 객체

: javascript 환경에서 모든 전역 변수와 함수를 포함하는 특수한 객체. 전역 객체는 javascript 코드의 어디에서나 접근할 수 있으며, 환경에 따라 이름과 동작이 조금씩 다름.

 

▶ 브라우저 환경에서의 전역 객체 - window

=> 모든 전역 변수와 함수는 사실 window 객체의 속성으로 존재함.

브라우저에서 제공하는 API (ex. alert, document, loaction 등)도 모두 window 객체의 일부임. HTML 문서의 전역 변수나 함수를 선언하면, 이는 window 객체의 속성으로 자동 추가됨.

ex.

var myVar = 10;
function myFunction() {
  console.log("Hello, world!");
}

console.log(window.myVar); // 10 출력
window.myFunction(); // "Hello, world!" 출력

 

✏ 브라우저 환경

: 웹 브라우저에서 javascript 코드가 실행되는 환경을 의미함. 브라우저는 HTML, CSS, Javascript를 처리하며 window, document와 같은 전역 객체를 제공함.

 

✏ Node.js에서 global 매핑

: global 객체는 Node.js 환경에서의 전역 객체임. 브라우저 환경에서의 window 객체와 유사한 역할을 함.  Node.js에서 전역 변수나 함수를 정의할  때 사용됨.

 

 

 

then

ex.

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data));

: promise가 해결(resolve) 될 때 실행할 콜백 함수를 지정함. 비동기 작업이 성공했을 때 사용됨.

 

✏ 콜백 함수

: 다른 함수에 인수로 전달되는 함수. 특정 작업이 완료된 후에 실행되도록 설계됨. 비동기 작업이나 이벤트 처리에 자주 사용됨. 즉, 작업이 완료되었을 때 후속 작업을 처리하기 위해 사용됨.

 

 

 

throw

ex.

function checkValue(value) {
  if (value < 0) {
    throw new Error('Value cannot be negative');
  }
  return value;
}

try {
  checkValue(-1);
} catch (error) {
  console.error(error.message); // "Value cannot be negative"
}

: 예외를 발생시킬 때 사용됨. 주로 try ... catch 블록에서 사용되며, 특정 오류 조건이 발생했을 때 예외를 던짐. 즉, 원하는 조건에서 예외를 발생시킴.

 

 

 

toast

ex.

import { toast } from 'react-toastify';

function notify() {
  toast('Hello, this is a toast notification!');
}

: 주로 사용자에게 일시적으로 정보를 표시하는 UI 컴포넌트를 말함. 알림 메시지를 표시할 때 사용됨. 일반적으로 라이브러리 (ex. react-toastify)를 통해 구현됨. 즉, 사용자에게 중요한 메시지를 짧게 보여줄 때 사용됨.

 

✏ UI 컴포넌트

:웹 애플리케이션이나 모바일 애플리케이션에서 사용자 인터페이스를 구성하는 독립적인 재사용 가능 단위.

버튼, 입력 필드, 모달 창, 카드, 네비게이션 바 등이 UI 컴포넌트에 해당함.

 

▶ 특징

1. 재사용성 : 컴포넌트는 여러 번 사용될 수 있음.

2. 독립성 : 각 컴포넌트는 다른 부분과 독립적으로 작동하며, 필요한 데이터나 상태만을 관리함.

3. 조합 가능성 : 여러 개의 작은 컴포넌트를 조합하여 복잡한 UI를 구성할 수 있음.

 

 

 

try

ex. 

try {
  let result = riskyOperation();
  console.log(result);
} catch (error) {
  console.error('An error occurred:', error);
}

: 예외가 발생할 수 있는 코드 블록을 감싸는데 사용됨. 예외가 발생하면 catch 블록으로 제어가 넘어감.

 

 

 

useEffect

ex.

useEffect(() => {
  // 이 코드 블록은 컴포넌트가 처음 렌더링된 후 실행됩니다.
  console.log('Component mounted or updated');

  // 선택적으로 clean-up 함수를 반환할 수 있습니다.
  return () => {
    console.log('Component will unmount or clean up before re-running effect');
  };
}, [/* dependencies */]); // 빈 배열: 마운트/언마운트 시 한 번만 실행

: React의 함수형 컴포넌트에서 부수효과(컴포넌트의 렌더링과 직접 관련이 없는 작업)를 처리하기 위해 사용되는 훅.

▶  부수효과의 예 : 데이터 fetching, DOM 조작, 구독 관리 등이 있음.

 

▶ 특징

1. 컴포넌트가 렌더링 될 때마다 기본적으로 실행됨.

2. 의존성 배열을 통해 어떤 값이 변경될 때만 실행할지 지정할 수 있음.

3. 컴포넌트가 "언마운트" 되거나, 특정 효과가 다시 실행되기 전에 정리 작업을 수행할 수 있음.

→ 언마운트 : 컴포넌트가 DOM에서 제거되는 과정. react는 컴포넌트를 완전히 제거하고, 메모리를 해제하며 관련된 리소스나 이벤트 리스너들을 정리함.

 

의존성 배열에 값들을 전달하면, 해당 값들이 변경될 때만 useEffect가 실행됨.

빈 배열을 전달하면 해달 useEffect는 컴포넌트가 처음 마운트 될 때 한번만 실행되고 이후에는 실행되지 않음.

의존성 배열을 생략하면, 컴포넌트가 렌더링 될 때마다 useEffect가 실행됨.

 

✏ 의존성 배열

ex.

import React, { useState, useEffect } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `Count: ${count}`;

    return () => {
      console.log('Clean-up code, e.g., removing event listeners');
    };
  }, [count]); // count가 변경될 때마다 이 효과가 실행됨

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

 

: React의 useEffect 훅에서 특정 상태나 값이 변경될 때만 부수효과가 실행되도록 제어하는 배열.

이 배열은 useEffect의 두 번째 인자로 전달됨.

 

 

 

usestate

ex.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

: react의 훅(hook)으로, 함수형 컴포넌트에서 상태(state)를 관리할 수 있게 해줌. 상태값과 그 상태를 업데이트 하는 함수를 반환함. 즉, 상태 관리가 필요한 함수형 컴포넌트에서 사용됨.

 

✏ 함수형 컴포넌트

: React에서 UI를 렌더링 하기 위해 사용되는 컴포넌트 유형. 기존 클래스형 컴포넌트와 달리 상태(state)와 생명주기 메서드를 함수 내에서 사용할 수 있도록 훅을 제공함.

 

✏ 생명주기 메서드

: 컴포넌트가 생성되고 업데이트 되며, 삭제되는 과정에서 특정 시점에 호출되는 메서드들. 

컴포넌트의 상태나 UI를 변경하거나, 외부 리소스를 정리하는 등의 작업을 할 수 있음.

 

 React에서의 생명주기 메서드

: 클래스형 컴포넌트에서 생명주기 메서드를 사용함. 함수형 컴포넌트에서는 useEffect 훅을 통해 비슷한 기능을 구현할 수 있음.

주요 생명주기 메서드

- constructor : 컴포넌트가 처음 생성될 때 호출됨. 초기 상태를 설정하거나 클래스 "메서드를 바인딩"할 때 사용됨.→ 메서드 바인딩 : 객체의 메서드를 해당 객체에 묶어주는 작업을 의미함.

 

- componentDidMount : 컴포넌트가 처음 렌더링 된 후에 호출됨. API 호출, 타이머 설정 등 초기 데이터 로드와 같은 부수효과( side Effect)를 처리할 때 사용됨.

 

- componentDidUpdate : 컴포넌트가 업데이트 된 후에 호출됨. 이전 상태와 현재 상태를 비교하여 필요한 추가 작업을 할 수 있음.

 

- componentWillUnmont : 컴포넌트가 DOM에서 제거되기 전에 호출됨. 타이머나 이벤트 리스너 등 정리가 필요한 작업을 처리함.

728x90
반응형

포트폴리오 디자인을 전부 갈아엎는 과정에서 about me 파트와 modal창에 사용된 가로 차트(horizon chart)와 원형 차트(circle)가 창이 로딩된 후 숫자가 차르륵 올라가는 효과를 어떻게 구현했는지 기록하려고 한다.

 

원형 차트의 숫자가 움직이는 효과를 구현하면서 가로 차트도 이렇게 숫자가 차르륵 올라가면 예쁠텐데, 하고 생각했지만 가로 차트를 만드는 유튜브 영상을 과장 좀 보태서 수십 개를 찾아봐도 숫자가 0, 1, 2 ~ 99, 100 하고 올라가는 효과를 구현해둔 건 찾아볼 수가 없어서 가로형 차트는 css로만 구현해야 하나 싶었다.

그 때 머리를 스치는 생각이 원형 차트를 만드는 방식을 따라해서 가로형 차트의 가상 요소 after 값을 원형 차트가 작동하는 방식으로 바꿔주면 되지 않을까?  였다.

그 예상은 멋드러지게 적중하여 나는 움직이는 원형 차트와 가로형 차트를 얻은 것이다.

 

 

 

일단 원형 차트를 만든 과정부터 시작해보겠다.

 

원형 차트 완성본

 

<div class="circle" data-degree="87" data-color="#d271fe">
  <h2 class="circle_number">87<span>%</span></h2>
  <h4 class="circle_title">css</h4>
</div>
<div class="circle" data-degree="90" data-color="#d271fe">
  <h2 class="circle_number">90<span>%</span></h2>
  <h4 class="circle_title">html</h4>
</div>
<div class="circle" data-degree="75" data-color="#d271fe">
  <h2 class="circle_number">75<span>%</span></h2>
  <h4 class="circle_title">javascript</h4>
</div>
<div class="circle" data-degree="80" data-color="#d271fe">
  <h2 class="circle_number">80<span>%</span></h2>
  <h4 class="circle_title">framework</h4>
</div>

 

원형 차트 완성본 이미지를 보면 data-degree 값과 %의 숫자가 같다는 걸 알 수 있다.

즉, data-degree 값만큼만 숫자가 증가하면서 원형 차트가 차오르는 것이다.

 

숫자가 증가하는 효과는 javascript(자바스크립트)로 구현했으니 자바스크립트도 살펴보자.

 

let circle = document.querySelectorAll(".circle");
  circle.forEach(function (progress) {
    let degree = 0;
    let targetDegree = parseInt(progress.getAttribute("data-degree"));

    let color = progress.getAttribute("data-color");
    let number = progress.querySelector(".circle_number");

    let interval = setInterval(function () {
      degree += 1;

      if (degree > targetDegree) {
        clearInterval(interval);
        return;
      }

      progress.style.background = `conic-gradient(${color} ${degree}%, #222 0%)`;
      number.innerHTML = degree + `<span>%</span>`;
      number.style.color = color;
    }, 50);
  });

 

let degree = 0;

: degree 변수는 애니메이션이 진행될 때 현재 원형 차트의 배경이 얼마나 채워졌는지를 나타내는 값이다. 물론 초기값은 0이다. 

 

let targetDegree = parseInt(progess.getAttribute("data-degree")

: data-degree 라는 커스텀 데이터 속성에서 값을 가져와 숫자로 변환하여 targetDegree 변수에 저장하는 구문이다.

목적은 애니메이션이 종료될 때 배경이 채워질 목표 각도를 설정하는 것이다.

 

color는 원형 차트에 어떤 색을 적용할 것인지에 대한 hex 값을 가져오는 것이니 별로 신경 쓰지 않아도 된다.

 

✏ getAttribute 

:자바스크립트에서 DOM  요소의 특정 속성(attribute) 값을 가져오는 메서드이다.

이 메서드를 사용하면 HTML 요소에 설정된 특정 속성의 값을 읽을 수 있다.

 

let interval = setInterval(funtion () { ... } , 50);

: setInterval을 사용하여 50 밀리초마다 반복해서 실행되는 함수 즉, 애니메이션 루프를 설정하는 구문이다.

목적은 원형 배경이 채워지는 애니메이션을 구현하기 위해 주기적으로 코드를 실행하는 것이다.

 

degree += 1

: 현재의 degree 값을 1씩 증가 시키는 구문이다.

애니메이션이 진행될 수록 원형 배경을 점진적으로 채우기 위해 각도를 증가시키는 역할을 한다.

 

if (degree < targetDegree) { clearInterval(interval); return)

:현재의 degree 값이 목표 각도(targetDegree)를 초과하면 애니메이션을 멈추게 하는 구문이다.

즉, 목표 각도에 도달하면 애니메이션을 종료하게 만든다.

값과 다르게 원이 꽉꽉 들어차는 걸 보고 싶지 않다면 꼭 넣는 게 좋은 구문이다.

 

progress.style.background = \`conic-gradient(${color} ${degree}%, #222, 0%)

:CSS의 conic-gradient를 사용해 배경이 원형으로 채워지도록 설정하게 하는 구문이다.

여기서 degree의 %만큼 color 값의 색으로 채워지고 나머지는 #222(어두운 색)으로 표시된다.

즉, 원형 배경을 애니메이션에 따라 점차적으로 채워지는 효과를 시각적으로 표현하는 것이다.

 

number.innerHTML = degree + \`<span>%</span>

: 현재의 degree 값을 html로 삽입하여 원형 내부에 표시하는 구문이다. 이때 %도 함께 표시된다.

 

 

여기서 progress.style.background = \`conic-gradient(${color} ${degree}%, #222, 0%) 를 보자.

방금은 원형이라 conic-gradient를 사용했지만 가로형 차트를 구현한다면 conic-gradient를 사용할 필요가 없다.

여기만 가로형 차트의 width 값이 되는 것으로만 바꿔준다면 어디서 본 건 있는 나의 욕구를 충족 시켜줄 수 있는 코드를 구현할 수 있다는 뜻이다.

 

 

 

겨우 본론으로 돌아와서, 가로형 차트를 살펴보자.

가로 차트 완성본

 

이게 멋드러지는 가로형 차트의 완성본이다.

다시 생각해도 뿌듯해서 자꾸 보게 된다.

 

 

이제 가로형 차트의 html을 보자

<div class="chart_line" chart-data-degree="85">
  <span class="skill_name">react</span>
  <div class="chart_bar"></div>
  <span class="percent">0<span>%</span></span>
</div>
<div class="chart_line" chart-data-degree="75">
  <span class="skill_name">vite</span>
  <div class="chart_bar"></div>
  <span class="percent">0<span>%</span></span>
</div>
<div class="chart_line" chart-data-degree="78">
  <span class="skill_name">php</span>
  <div class="chart_bar"></div>
  <span class="percent">0<span>%</span></span>
</div>
<div class="chart_line" chart-data-degree="83">
  <span class="skill_name">vue</span>
  <div class="chart_bar"></div>
  <span class="percent">0<span>%</span></span>
</div>
<div class="chart_line" chart-data-degree="80">
  <span class="skill_name">next.js</span>
  <div class="chart_bar"></div>
  <span class="percent">0<span>%</span></span>
</div>
<div class="chart_line" chart-data-degree="65">
  <span class="skill_name">redux</span>
  <div class="chart_bar"></div>
  <span class="percent">0<span>%</span></span>
</div>

 

원형 차트랑 구조는 같다. (사소한 건 다르지만) 

기본적인 뼈대(틀)은 같다.

차트 전체를 구성하는 div 박스에 data-degree, 즉 차트가 채워질 만큼의 커스텀 데이터 값이 있고, 각 차트의 타이틀과 어떤 제목을 가졌고, 출력될 % 값이 있다.

 

차이점은 원형 차트에는 width 값이 필요가 없었는데 가로형 차트(horizon chart)에는 width 값이 필요하다는 것이다.

.chart_line {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-right: 10%;

    .skill_name {
      width: 10%;
      text-transform: uppercase;
      letter-spacing: 0.5vw;
    }

    .chart_bar {
      display: flex;
      width: 35vw;
      height: 1.5vw;
      background-color: rgba(255, 255, 255, 0.5);
      position: relative;

      &::after {
        content: "";
        position: absolute;
        background-color: #d271fe;
        width: var(--progress-width, 0%);
        height: inherit;
        top: 0;
        left: 0;
        z-index: 1;
        transition: width 1s ease;
      }
    }
}

 

이런 식으로 말이다.

여기서 

배경의 불투명한 부분이 chart_bar가 된다. 전체적인 가로형 차트의 배경을 그리고 가상 요소(::after)를 사용하여 그 위를 덮는 형식으로 가로형 차트를 구현했다.

그러니까 chart_bar의 width 값은 건드리지 않고 ::after의 width 값에만 변화를 주어야 하는 것이다.

그것도 값이 전부 다 다른 여섯 개의 차트를 말이다.

 

그래서 찾은 방법이 --progress-width를 사용하는 것이었다. 

css의 커스텀 속성인 --progress-width를 사용하여 프로그레스 바의 너비를 조절하는 방법이다.

이를 사용하면 차트의 chart-data-degree 값을 따라 달라지게 되는 것이다.

 

그래서 자바스크립트를 어떻게 짰냐면,

 let chartLines = document.querySelectorAll(".chart_line");
  chartLines.forEach(function (progress) {
    let degree = 0;
    let targetDegree = parseInt(progress.getAttribute("chart-data-degree"));

    let bar = progress.querySelector(".chart_bar");
    let percent = progress.querySelector(".percent");

    let interval = setInterval(function () {
      degree += 1;

      if (degree > targetDegree) {
        clearInterval(interval);
        return;
      }

      bar.style.setProperty("--progress-width", degree + "%");
      percent.innerHTML = degree + `<span>%</span>`;
    }, 50);

 

원형 차트의 자바스크립트 틀을 그대로 가져왔다.

물론 필요없는 color는 빼버렸다.

 

원형 차트와 똑같이 let chartLines를 document.quertSelectorAll을 사용하여 .chart_line이라는 클래스를 가진 모든 요소를 선택하고, 여러 개인 chartLines에 forEach를 사용하여 각 요소에 대해 순회하면서 콜백함수를 실행할 수 있도록 했다.

여기서의 progress는 각각의 .chart_line이 되겠지.

 

let degree = 0으로 degree의 초기값을 0으로 설정했다.

의외인 점은 let이 변수 선언이라지만 원형 차트도, 가로형 차트도 전부 degree라는 변수를 사용했는데 꼬이지 않고 값들이 제자리를 쏙쏙 찾아 들어갔다는 점이다.

data-degree 값을 찾아서 집어넣는 방식이라 그런가.

 

어쨌든,

let targerDegree = parseInt(progress.getAttribute("chart-data-degree")) 를 사용하여 커스텀 데이터 속성에서 값을 가져와 targetDegree 변수에 저장하게 만든다.

아, 아까는 data-degree였는데 왜 지금은 chart-data-degree를 사용하냐면, 내가 헷갈리기 때문이다. 별 이유는 없다.

 

let bar = progress.querySelector(".chart_bar)를 사용하여 .chart_bar 클래스를 가진 자식 요소를 선택하여 bar에 저장한다.

왜냐면 프로그레스 바의 시각적 요소를 조작하기 위해서이다.

 

let percent = progress.querySelector(".percent")를 사용하여 percent 클래스를 가진 자식 요소를 선택하여 percent 변수에 저장한다. 진행도를 텍스트로 표시할 요소를 선택하여 애니메이션 중 업데이트 할 수 있도록 하기 위해서이다.

대충 숫자가 차르륵 올라가려면 이 과정이 꼭 필요하다는 것만 알아두자.

 

let interval = setInteval( function ( ) { ... }, 50) 

아까처럼 setInterval을 사용해 50밀리초마다 반복해서 실행되는 함수(애니메이션 루프)를 설정한다. 프로그레스 바가 점진적으로 채워지는 애니메이션을 구현하기 위해 주기적으로 코드를 실행해야 하기 때문이다.

 

degree += 1을 사용하여 애니메이션이 진행될 수록 프로그레스 바를 점진적으로 채우기 위해 현재의 degree 값을 1씩 증가시키고, if (degree > targetDegree) {clearInterval(interval); return;)을 사용해 현재의 degree 값이 목표 진행도(targetDegree)를 초과하면 clearInterval로 애니메이션을 멈춘다.

 

bar.style.setProperty("--progress-width", degree + "%");

:CSS의 커스텀 속성(progress-width)을 설정하여 프로그레스 바의 width값을 조정하게 된다. degree 값에 따라 width가 점진적으로 증가하게 한다.

프로그레스 바의 시각적 진행 상태를 업데이트하여 애니메이션 효과를 만들게 하는 것이다.

 

✏  setProperty

: 자바스크립트의 CSS Style Declaration 객체에서 사용되는 메서드로, CSS 변수(커스텀 속성 ex.progree-width)를 설정하거나 일반 CSS 속성의 값을 변경하는데 사용됨. 이 메서드는 주로 element(요소).style을 통해 접근할 수 있으며, CSS 변수의 값을 동적으로 변경할 때  유용함.

 

percent.innerHTML = degree + \`<span>%</span>`

:현재의 degree 값을 HTML로 삽입하여 프로그레스 바  옆에 진행도를 퍼센트로 표시하는 구문이다. 숫자가 차르륵 올라가려면 이 과정이 필수이다.

 

 

이 모든 과정을 거치고 나면 숫자라 차르르르르륵 올라가는 쾌감이 엄청난 차트 바와 원형 차트를 만들 수 있는 것이다.

사실 내가 언젠가 또 써먹고 싶은데 기억 안 나서 헤맬까봐  써놓는 것이다.

뭐... 나처럼 짱 멋진 가로형 차트를 만들고 싶은데 방법을 모르겠는 사람이 보면 더 좋고.

 

728x90
반응형
import "./globals.css";

import { Footer, Navbar } from "@/components";

export const metadata = {
  title: "Car Hub",
  description: "Discover world's best car showcase application",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className="relative">
        <Navbar />
        {children}
        <Footer />
      </body>
    </html>
  );
}

layout

: 웹 애플리케이션의 기본적인 레이아웃 구조를 구성하며, 해당 레이아웃 내에 네비게이션 바(navbar), 푸터(footer) 및 기타 컨텐츠를 렌더링 합니다.
주로 JSX 문법을 사용하여 작성되었습니다.
children은 해당 레이아웃에 포함할 내용을 나타냅니다. 즉, 해당 레이아웃 내에 표시될 페이지의 본문 컨텐츠를 children을 통해 렌더링 합니다.

 

 

 

 

 

import { fetchCars } from "@/utils";
import { HomeProps } from "@/types";
import { fuels, yearsOfProduction } from "@/constants";
import { CarCard, ShowMore, SearchBar, CustomFilter, Hero } from "@/components";

export default async function Home({ searchParams }: HomeProps) {
  const allCars = await fetchCars({
    manufacturer: searchParams.manufacturer || "",
    year: searchParams.year || 2022,
    fuel: searchParams.fuel || "",
    limit: searchParams.limit || 10,
    model: searchParams.model || "",
  });

  const isDataEmpty = !Array.isArray(allCars) || allCars.length < 1 || !allCars;

  return (
    <main className="overflow-hidden">
      <Hero />

      <div className="mt-12 padding-x padding-y max-width" id="discover">
        <div className="home__text-container">
          <h1 className="text-4xl font-extrabold">Car Catalogue</h1>
          <p>Explore out cars you might like</p>
        </div>

        <div className="home__filters">
          <SearchBar />

          <div className="home__filter-container">
            <CustomFilter title="fuel" options={fuels} />
            <CustomFilter title="year" options={yearsOfProduction} />
          </div>
        </div>

        {!isDataEmpty ? (
          <section>
            <div className="home__cars-wrapper">
              {allCars?.map((car) => (
                <CarCard car={car} />
              ))}
            </div>

            <ShowMore
              pageNumber={(searchParams.limit || 10) / 10}
              isNext={(searchParams.limit || 10) > allCars.length}
            />
          </section>
        ) : (
          <div className="home__error-container">
            <h2 className="text-black text-xl font-bold">Oops, no results</h2>
            <p>{allCars?.message}</p>
          </div>
        )}
      </div>
    </main>
  );
}


page

: React를 사용하여 구현된 웹 애플리케이션의 홈페이지를 정의하는 컴포넌트 입니다.
React와 관련 라이브러리를 사용하여 동적인 웹 애플리케이션의 홈페이지를 구현하는데 사용됩니다. 사용자에게 자동차 목록을 보여주고 검색 필터를 통해 결과를 필터링하고 페이지네이션을 제공하는 역할을 합니다.

이 컴포넌트는 HomeProps를 매개변수로 받아옵니다.
또한 fetchCars 함수를 사용하여 자동차 데이터를 가져옵니다. 이 데이터는 searchParams 객체를 기반으로 가져오며, searchParams 객체에는 제조사, 연도, 연료 유형, 모델 등의 검색 매개변수가 포함됩니다.

isDateEmpty 변수는 가져온 자동차 데이터가 비어있는지 확인합니다. 데이터가 비어있다면 오류 메시지가 표시됩니다.
홈페이지의 레이아웃은 여러 부분으로 나누어져 있는데 Hero 컴포넌트가 렌더링 되며, 그 아래에는 일부 텍스트와 검색 필터(searchBar, CustomFilter)가 포함된 섹션이 있습니다.
데이터가 존재하는 경우 자동차 데이터를 Car Card 컴포넌트가 렌더링 됩니다. 이 컴포넌트는 자동차의 정보를 보여주는 카드 형식으로 화면에 표시됩니다. 또한 Show More 컴포넌트가 페이지네이션을 처리하고, 다음 페이지로 이동할 수 있는 옵션을 제공합니다.

 

 

 

 

 

"use client";

import { useState } from "react";
import Image from "next/image";

import { calculateCarRent, generateCarImageUrl } from "@/utils";
import { CarProps } from "@/types";
import CustomButton from "./CustomButton";
import CarDetails from "./CarDetails";

interface CarCardProps {
  car: CarProps;
}

const CarCard = ({ car }: CarCardProps) => {
  const { city_mpg, year, make, model, transmission, drive } = car;

  const [isOpen, setIsOpen] = useState(false);

  const carRent = calculateCarRent(city_mpg, year);

  return (
    <div className="car-card group">
      <div className="car-card__content">
        <h2 className="car-card__content-title">
          {make} {model}
        </h2>
      </div>

      <p className="flex mt-6 text-[32px] leading-[38px] font-extrabold">
        <span className="self-start text-[14px] leading-[17px] font-semibold">
          $
        </span>
        {carRent}
        <span className="self-end text-[14px] leading-[17px] font-medium">
          / day
        </span>
      </p>

      <div className="relative w-full h-40 my-3 object-contain">
        <Image
          src={generateCarImageUrl(car)}
          alt="car model"
          fill
          priority
          className="object-contain"
        />
      </div>

      <div className="relative flex w-full mt-2">
        <div className="flex group-hover:invisible w-full justify-between text-grey">
          <div className="flex flex-col justify-center items-center gap-2">
            <Image
              src="/steering-wheel.svg"
              width={20}
              height={20}
              alt="steering wheel"
            />
            <p className="text-[14px] leading-[17px]">
              {transmission === "a" ? "Automatic" : "Manual"}
            </p>
          </div>

          <div className="car-card__icon">
            <Image src="/tire.svg" width={20} height={20} alt="seat" />
            <p className="car-card__icon-text">{drive.toUpperCase()}</p>
          </div>

          <div className="car-card__icon">
            <Image src="/gas.svg" width={20} height={20} alt="seat" />
            <p className="car-card__icon-text">{city_mpg} MPG</p>
          </div>
        </div>

        <div className="car-card__btn-container">
          <CustomButton
            title="View More"
            containerStyles="w-full py-[16px] rounded-full bg-primary-blue"
            textStyles="text-white text-[14px] leading-[17px] font-bold"
            rightIcon="/right-arrow.svg"
            handleClick={() => setIsOpen(true)}
          />
        </div>
      </div>

      <CarDetails
        isOpen={isOpen}
        closeModal={() => setIsOpen(false)}
        car={car}
      />
    </div>
  );
};

export default CarCard;


Car Card

: React를 사용하여 자동차 정보를 나타내는 카드 형식의 컴포넌트인 Car Card를 정의합니다. 이 컴포넌트는 자동차의 사양 및 이미지를 표시하고 View More 버튼을 통해 자세한 자동차 정보를 볼 수 있는 모달 창을 표시합니다.

 

 

 

Detail

-CarCardProps는 car라는 이름의 자동차 정보를 받는 인터페이스를 정의합니다.
-CarCard 함수 컴포넌트는 화면에 자동차 정보를 표시하는 부분입니다. car 객체에서 자동차의 다양한 속성(ex. city_mpg, year, make, model 등)을 추출해냅니다.
-isOpen이라는 상태 변수를 사용하여 자세한 자동차 정보를 표시할 모달 창의 열림/닫힘 상태를 관리합니다.
- calculateCarRent 함수를 사용하여 자동차 대여료를 계산하고 이를 carRent 변수에 저장합니다.
-자동차는 카드 형식으로 표시되며, 이 카드에는 자동차 모델 및 대여료 정보가 포함되어 있습니다. 또한 자동차 이미지도 표시됩니다.
-View More 버튼을 클릭하면, setIsOpen(true)를 호출하여 모달 창을 엽니다.
-CarDetails 컴포넌트가 모달 창을 표시하고 자세한 자동차 정보를 표시합니다.

 

 

 

 

 

import { Fragment } from "react";
import Image from "next/image";

import { Dialog, Transition } from "@headlessui/react";

import { CarProps } from "@/types";
import { generateCarImageUrl } from "@/utils";

interface CarDetailsProps {
  isOpen: boolean;
  closeModal: () => void;
  car: CarProps;
}

const CarDetails = ({ isOpen, closeModal, car }: CarDetailsProps) => (
  <>
    <Transition appear show={isOpen} as={Fragment}>
      <Dialog as="div" className="relative z-10" onClose={closeModal}>
        <Transition.Child
          as={Fragment}
          enter="ease-out duration-300"
          enterFrom="opacity-0"
          enterTo="opacity-100"
          leave="ease-in duration-200"
          leaveFrom="opacity-100"
          leaveTo="opacity-0"
        >
          <div className="fixed inset-0 bg-black bg-opacity-25" />
        </Transition.Child>

        <div className="fixed inset-0 overflow-y-auto">
          <div className="flex min-h-full items-center justify-center p-4 text-center">
            <Transition.Child
              as={Fragment}
              enter="ease-out duration-300"
              enterFrom="opacity-0 scale-95"
              enterTo="opacity-100 scale-100"
              leave="ease-out duration-300"
              leaveFrom="opacity-100 scale-100"
              leaveTo="opacity-0 scale-95"
            >
              <Dialog.Panel
                className="relative w-full max-w-lg max-h-[90vh] overflow-y-auto 
                transform rounded-2xl bg-white p-6 text-left shadow-xl transition-all
                flex flex-col gap-5"
              >
                <button
                  type="button"
                  className="absolute top-2 right-2 z-10 w-fit p-2 bg-primary-blue-100 rounded-full"
                  onClick={closeModal}
                >
                  <Image
                    src="/close.svg"
                    alt="close"
                    width={20}
                    height={20}
                    className="object-contain"
                  />
                </button>

                <div className="flex-1 flex flex-col gap-3">
                  <div className="relative w-full h-40 bg-pattern bg-cover bg-center rounded-lg">
                    <Image
                      src={generateCarImageUrl(car)}
                      alt="car model"
                      fill
                      priority
                      className="object-contain"
                    />
                  </div>

                  <div className="flex gap-3">
                    <div className="flex-1 relative w-full h-24 bg-primary-blue-100 rounded-lg">
                      <Image
                        src={generateCarImageUrl(car, "29")}
                        alt="car model"
                        fill
                        priority
                        className="object-contain"
                      />
                    </div>
                    <div className="flex-1 relative w-full h-24 bg-primary-blue-100 rounded-lg">
                      <Image
                        src={generateCarImageUrl(car, "33")}
                        alt="car model"
                        fill
                        priority
                        className="object-contain"
                      />
                    </div>
                    <div className="flex-1 relative w-full h-24 bg-primary-blue-100 rounded-lg">
                      <Image
                        src={generateCarImageUrl(car, "13")}
                        alt="car model"
                        fill
                        priority
                        className="object-contain"
                      />
                    </div>
                  </div>
                </div>

                <div className="flex-1 flex flex-col gap-2">
                  <h2 className="font-semibold text-xl capitalize">
                    {car.make} {car.model}
                  </h2>

                  <div className="mt-3 flex flex-wrap gap-4">
                    {Object.entries(car).map(([key, value]) => (
                      <div
                        className="flex justify-between gap-5 w-full text-right"
                        key={key}
                      >
                        <h4 className="text-grey capitalize">
                          {key.split("_").join(" ")}
                        </h4>
                        <p className="text-black-100 font-semibold">{value}</p>
                      </div>
                    ))}
                  </div>
                </div>
              </Dialog.Panel>
            </Transition.Child>
          </div>
        </div>
      </Dialog>
    </Transition>
  </>
);

export default CarDetails;

Car Details

:자동차 상세 정보를 표시하기 위한 모달 창을 정의하는 React 컴포넌트인 Car Details입니다.

이 모달 창은 사용자가 자세한 자동차 정보를 볼 수 있는 모달 다이얼로그로 작동합니다.

해당 컴포넌트는 Car Card 컴포넌트에서 View More 버튼을 클릭하면 호출되어 팝업 창을 열고 자세한 자동차 정보를 표시합니다.

 

 

 

Details

-CarDetailsProps는 팝업창을 열거나 닫는데 필요한 상태와 자동차 장보를 받는 인터페이스를 정의합니다.

-CarDetails 함수 컴포넌트는 모달 창의 내용을 렌더링하는 부분입니다. isOpen, closeModal, car 객체를 받아옵니다.

-모달 창은 headlessui 라이브러리의 Transition 및 Dialog 컴포넌트를 사용하여 구현됩니다. 모달이 나타날 때와 사라질 때의 애니메이션 효과를 부여합니다.

-모달 창이 열릴 때, 화면을 어둡게 하는 배경을 생성하여, 모달 창 외부를 클릭하면 모달 창이 닫히도록 합니다. 각 이미지는 Imgage 컴포넌트를 사용하여 표시되며, 이미지 소스는 generateCarImageUrl 함수를 통하여 동적 생성됩니다.

-모달 창 오른쪽 상단에는 닫기 버튼이 있으며, 클릭하면 팝업이 닫힙니다.

-자동차 정보는 제조사, 모델, 그리고 다른 자동차 사양과 속성을 보여줍니다. 이 정보는 car 객체를 통해 동적으로 표시됩니다.

 

✏ headlessui 라이브러리

:headlessui UI는 React와 Vue 같은 프레임워크를 위한 UI 구성 요소 라이브러리.

하지만 보통의 "디자인이 포함된" UI 라이브러라와는 다름.

일반적인 UI 라이브러리는 이미 디자인과 스타일이 입혀져 있는 버튼, 모달, 드롭다운 같은 컴포넌트를 제공하나, headlessui UI는 "디자인 없이 기능만 제공"하는 컴포넌트를 제공함.

즉, 컴포넌트가 어떻게 보일지는 사용자가 직접 CSS나 Tailwind CSS 같은 스타일링 도구를 사용해 결정해야 함.

 

 

 

 

 

"use client";

import Image from "next/image";

import { CustomButtonProps } from "@/types";

const Button = ({
  isDisabled,
  btnType,
  containerStyles,
  textStyles,
  title,
  rightIcon,
  handleClick,
}: CustomButtonProps) => (
  <button
    disabled={isDisabled}
    type={btnType || "button"}
    className={`custom-btn ${containerStyles}`}
    onClick={handleClick}
  >
    <span className={`flex-1 ${textStyles}`}>{title}</span>
    {rightIcon && (
      <div className="relative w-6 h-6">
        <Image
          src={rightIcon}
          alt="arrow_left"
          fill
          className="object-contain"
        />
      </div>
    )}
  </button>
);

export default Button;

Custom Button

: React에서 재사용 가능한 버튼 컴포넌트인 Button을 정의합니다.

이 컴포넌트는 매개변수로 전달된 속성에 따라 버튼의 모양과 동작을 조절할 수 있습니다.

 

 

 

Details

-Custom Button Props는 버튼 컴포넌트에 전달되는 속성을 정의합니다. 이러한 속성에는 버튼의 텍스트, 스타일, 비활성화 상태, 클릭 핸들러 및 기타 속성이 포함됩니다.

-Button 함수 컴포넌트는 버튼을 렌더링하는 부분입니다. 이 함수는 전달된 속성을 사용하여 버튼을 동적으로 생성합니다.

-<button> HTML 요소가 생성됩니다. 이 요소의 속성은 disabled를 통해 isDisabled 속성에 따라 버튼이 활성화 또는 비활성화 되며, btnType 속성에 따라 버튼의 유형을 설정합니다. 기본값은 button이 됩니다. 

-버튼에 적용되는 css 클래스는 custom-btn 및 containerStyles 속성을 통해 지정됩니다.

-버튼 텍스트는 title 속성에 따라 동적으로 설정 됩니다.

-right Icon 속성에 아이콘 이미지 URL이 제고오디면 이미지가 표시됩니다. 이미지는 next/image를 사용하여 효율적으로 렌더링 됩니다.

-이 버튼 컴포넌트는 매개변수로 전달된 속성을 기반으로 버튼을 렌더링 하며, 다양한 컨텍스트<footnote>React의 컴포넌트 컨텍스트를 의미함. 위에서 언급한 Button 컴포넌트는 여러 부모 컴포넌트에서 사용될 수 있고, 각 부모 컴포넌트에서는 다양한 속성을 전달하여 버튼의 동작과 모양을 조절할 수 있음. 이 때, 컨텍스트는 해당 컴포넌트가 어떤 상황이나 환경에서 사용되는지에 대한 배경을 나타냄. </footnote>에서 재사용할 수 있습니다. Custom Button Props를 통해 버튼의 모양과 동작을 설정하고 필요한 곳에서 이 컴포넌트를 사용할 수 있습니다.

 

✏ disabled

HTML, CSS, Javascript에서 자주 사용되는 속성으로, 주로 사용자 인터페이스 요소를 비활성화 하는데 사용됨.

특정 요소가 비활성화 되면 사용자는 그 요소와 상호작용 할 수 없음.

 

HTML

:폼 요소에 주로 사용됨.

예를 들어, 버튼, 입력 필트, 셀렉트 박스 등에 disabled 속성을 추가하면 사용자가 해당 요소를 클릭하거나 입력할 수 없음.

 

CSS

:disabled 상태에 있는 요소에 특정 스타일을 적용할 수 있음.

예를 들어, 비활성화 된 버튼은 회색으로 표시되도록 할 수 있음.

 

Javascript

:동적으로 disabled 속성을 추가하거나 제거할 수 있음.

예를 들어 버튼을 비활성화 하거나, 버튼을 다시 활성화 하는 방식으로 사용할 수 있음.

const button = document.querySelector('button');
button.disabled = true; // 버튼을 비활성화함
button.disabled = false; // 버튼을 다시 활성화함

 

Headless UI

: headless UI 컴포넌트에서도 disable 속성이 지원됨.

<Menu.Item disabled>
   {({ active }) => (
      <a
         className={`${active ? 'bg-blue-500 text-white' : 'text-gray-900'} block px-4 py-2 text-sm`}
      >
         Disabled Item
      </a>
   )}
</Menu.Item>

이 경우, disable 속성이 적용된 메뉴 항목은 클릭할 수 없고 스타일도 달라지게 됨.

 

 

 

 

 

"use client";

import { Fragment, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { Listbox, Transition } from "@headlessui/react";

import { CustomFilterProps } from "@/types";
import { updateSearchParams } from "@/utils";

export default function CustomFilter({ title, options }: CustomFilterProps) {
  const router = useRouter();
  const [selected, setSelected] = useState(options[0]);

  const handleUpdateParams = (e: { title: string; value: string }) => {
    const newPathName = updateSearchParams(title, e.value.toLowerCase());

    router.push(newPathName);
  };

  return (
    <div className="w-fit">
      <Listbox
        value={selected}
        onChange={(e) => {
          setSelected(e);
          handleUpdateParams(e);
        }}
      >
        <div className="relative w-fit z-10">
          <Listbox.Button className="custom-filter__btn">
            <span className="block truncate">{selected.title}</span>
            <Image
              src="/chevron-up-down.svg"
              width={20}
              height={20}
              className="ml-4 object-contain"
              alt="chevron up down"
            />
          </Listbox.Button>
          <Transition
            as={Fragment}
            leave="transition ease-in duration-100"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
          >
            <Listbox.Options className="custom-filter__options">
              {options.map((option) => (
                <Listbox.Option
                  key={option.title}
                  className={({ active }) =>
                    `relative cursor-default select-none py-2 px-4 ${
                      active ? "bg-primary-blue text-white" : "text-gray-900"
                    }`
                  }
                  value={option}
                >
                  {({ selected }) => (
                    <>
                      <span
                        className={`block truncate ${
                          selected ? "font-medium" : "font-normal"
                        }`}
                      >
                        {option.title}
                      </span>
                    </>
                  )}
                </Listbox.Option>
              ))}
            </Listbox.Options>
          </Transition>
        </div>
      </Listbox>
    </div>
  );
}

Custom Filter

: Listbox를 사용하여 사용자가 특정 옵션을 선택할 수 있는 커스텀 필터를 만드는 React 컴포넌트입니다.

이 컴포넌트는 사용자에게 선택 옵션을 제공하고, 선택이 변경될 때마다 URL을 업데이트하여 페이지를 다시 로드하는 등의 기능을 수행합니다.

 

 

 

Details

-useState 훅을 사용하여 현재 선택된 옵션을 추적하며, 초깃값은 options 배열의 첫 번째 항목으로 설정됩니다.

-handleUpdateParams 함수는 옵션이 변경될 때 호출되는 함수로, 선택된 옵션을 기반으로 URL을 업데이트 합니다. 이 함수는 updateParams 유틸리티 함수<footnote>특정 작업을 수행하기 위해 만들어진 재사용 가능한 함수를 나타냄. 특정한 작업을 처리하거나 특정 값을 계산하는데 사용되며, 특정한 동작을 수행하는데 도움이 되는 함수들을 유틸리티 함수로 분리하여 작성하면 가독성이 높아지고 유지 보수가 쉬워짐.</footnote>를 사용하여 URL을 업데이트하고 페이지를 다시 로드합니다.

-Listbox 컴포넌트는 사용자에게 옵션을 선택할 수 있는 드롭다운 메뉴를 제공합니다. Headless UI<footnote>사용자 인터페이스를 만들기 위한 접근 방식 중 하나로, Headless UI에서 headless는 UI요소가 와관을 갖지 않고 스타일이나 레이아웃이 정의되어 있지 않다는 의미임. UI를 완전히 사용자 지정할 수 있도록 함. 이 패턴은 주로 React나 Vue 같은 컴포넌트 기반 라이브러리와 함께 사용됨. </footnote>에서 제공하는 컴포넌트로 외관 및 동작을 완전히 사용자 지정할 수 있습니다.

-Listbox.Button은 실제로 선택 상태를 보여주는 버튼 역할을 합니다.

-Listbox.Options는 옵션 목록을 나타냅니다. 이 목록은 선택한 옵션에 따라 동적으로 변합니다.

 

 

 

 

 

"use client";

import Image from "next/image";
import React, { useState } from "react";
import { useRouter } from "next/navigation";

import SearchManufacturer from "./SearchManufacturer";

const SearchButton = ({ otherClasses }: { otherClasses: string }) => (
  <button type="submit" className={`-ml-3 z-10 ${otherClasses}`}>
    <Image
      src={"/magnifying-glass.svg"}
      alt={"magnifying glass"}
      width={40}
      height={40}
      className="object-contain"
    />
  </button>
);

const SearchBar = () => {
  const [manufacturer, setManuFacturer] = useState("");
  const [model, setModel] = useState("");

  const router = useRouter();

  const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();

    if (manufacturer.trim() === "" && model.trim() === "") {
      return alert("Please provide some input");
    }

    updateSearchParams(model.toLowerCase(), manufacturer.toLowerCase());
  };

  const updateSearchParams = (model: string, manufacturer: string) => {
    const searchParams = new URLSearchParams(window.location.search);

    if (model) {
      searchParams.set("model", model);
    } else {
      searchParams.delete("model");
    }

    if (manufacturer) {
      searchParams.set("manufacturer", manufacturer);
    } else {
      searchParams.delete("manufacturer");
    }

    const newPathname = `${
      window.location.pathname
    }?${searchParams.toString()}`;

    router.push(newPathname);
  };

  return (
    <form className="searchbar" onSubmit={handleSearch}>
      <div className="searchbar__item">
        <SearchManufacturer
          manufacturer={manufacturer}
          setManuFacturer={setManuFacturer}
        />
        <SearchButton otherClasses="sm:hidden" />
      </div>
      <div className="searchbar__item">
        <Image
          src="/model-icon.png"
          width={25}
          height={25}
          className="absolute w-[20px] h-[20px] ml-4"
          alt="car model"
        />
        <input
          type="text"
          name="model"
          value={model}
          onChange={(e) => setModel(e.target.value)}
          placeholder="Tiguan..."
          className="searchbar__input"
        />
        <SearchButton otherClasses="sm:hidden" />
      </div>
      <SearchButton otherClasses="max-sm:hidden" />
    </form>
  );
};

export default SearchBar;

Search Bar

: 자동차 검색을 위한 검색 바를 나타냅니다.

이 컴포넌트는 자동차를 검색하는데 사용되며, 사용자가 검색어나 제조사를 입력하면 URL이 업데이트 되어 해당 검색 조건을 가진 자동차 목록을 표시합니다.

 

 

 

Details

-SearchManufacturer 컴포넌트를 통해 제조사 검색을 위한 드롭다운이나 자동완성 기능을 구현하고 있습니다. manufacturer 상태와 setManufacturer 함수로 상태를 업데이트 하고 있습니다.

-Search Button 검색 버튼 컴포넌트는 검색을 시작하는데 사용됩니다. 버튼에는 otherClasses라는 속성이 전달되며, 이는 추가적인 스타일을 적용하는데 사용됩니다.

-handleSearch 함수는 폼 제출 이벤트를 처리하며, 입력값이 유효한지 확인하며, 검색 매개변수를 업데이트한 다음 페이지를 다시 로드합니다.

-URL SearchParams는 updateParams 함수를 통해 URL 매개변수를 업데이트 합니다. 검색어와 제조사가 변경될 때마다 URL을 업데이트하여 해당 검색 조건을 반영합니다.

-Responsive Design 검색 바는 미디어 쿼리를 사용하여 화면의 크기에 따라 다르게 표시됩니다. 화면의 크기에 따라 드롭다운 기능이나 검색 창만 표시되도록 설정되어 있습니다.

 

 

 

import Image from "next/image";
import { Fragment, useState } from "react";
import { Combobox, Transition } from "@headlessui/react";

import { manufacturers } from "@/constants";
import { SearchManuFacturerProps } from "@/types";

const SearchManufacturer = ({
  manufacturer,
  setManuFacturer,
}: SearchManuFacturerProps) => {
  const [query, setQuery] = useState("");

  const filteredManufacturers =
    query === ""
      ? manufacturers
      : manufacturers.filter((item) =>
          item
            .toLowerCase()
            .replace(/\s+/g, "")
            .includes(query.toLowerCase().replace(/\s+/g, ""))
        );

  return (
    <div className="search-manufacturer">
      <Combobox value={manufacturer} onChange={setManuFacturer}>
        <div className="relative w-full">
          <Combobox.Button className="absolute top-[14px]">
            <Image
              src="/car-logo.svg"
              width={20}
              height={20}
              className="ml-4"
              alt="Car Logo"
            />
          </Combobox.Button>

          <Combobox.Input
            className="search-manufacturer__input"
            displayValue={(item: string) => item}
            onChange={(event) => setQuery(event.target.value)}
            placeholder="Volkswagen..."
          />

          <Transition
            as={Fragment}
            leave="transition ease-in duration-100"
            leaveFrom="opacity-100"
            leaveTo="opacity-0"
            afterLeave={() => setQuery("")}
          >
            <Combobox.Options
              className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
              static
            >
              {filteredManufacturers.length === 0 && query !== "" ? (
                <Combobox.Option
                  value={query}
                  className="search-manufacturer__option"
                >
                  Create "{query}"
                </Combobox.Option>
              ) : (
                filteredManufacturers.map((item) => (
                  <Combobox.Option
                    key={item}
                    className={({ active }) =>
                      `relative search-manufacturer__option ${
                        active ? "bg-primary-blue text-white" : "text-gray-900"
                      }`
                    }
                    value={item}
                  >
                    {({ selected, active }) => (
                      <>
                        <span
                          className={`block truncate ${
                            selected ? "font-medium" : "font-normal"
                          }`}
                        >
                          {item}
                        </span>
                        {selected ? (
                          <span
                            className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
                              active
                                ? "text-white"
                                : "text-pribg-primary-purple"
                            }`}
                          ></span>
                        ) : null}
                      </>
                    )}
                  </Combobox.Option>
                ))
              )}
            </Combobox.Options>
          </Transition>
        </div>
      </Combobox>
    </div>
  );
};

export default SearchManufacturer;

SearchManufacturer

: 제조사를 검색하기 위한 드롭다운 또는 자동완성을 제공합니다.

제조사 검색을 쉽게 할 수 있도록 인터랙티브 하게(사용자와 상호작용할 수 있는 기능) 구현되었습니다.

 

 

 

Details

-Headless UI의 Combobox 컴포넌트를 사용하여 검색 및 드롭다운 기능을 구현합니다. 이를 통해 제조사 목록에서 선택하거나 새로운 제조사를 추가할 수 있습니다.

-useState에서는 query라는 상태를 사용하여 검색어를 추적하고, setQuery 함수를 통해 이를 업데이트합니다. 또한 filteredManufacturers 상태를 이용하여 현재 검색어에 따라 필터링 된 제조사 목록을 저장합니다.

-Combobox.Input은 사용자가 검색어를 입력하는 입력란입니다. onChange 핸들러를 사용하여 검색어가 변경될 때마다 setQuery 함수를 호출하여 query 상태를 업데이트합니다.

 

useState

:React에서 상태(State)를 관리하기 위한 훅(hook)

 

ex.

const [state, setState] = useState(initialValue);

 state: 현재 상태 값

setState: 상태 값을 업데이트하는 함수

initiaValue: 상태의 초기값

 

useState에서 query와 setQuery의 역할

 

ex.

const [query, setQuery] = useState('');

query

: 이 변수는 검색어를 저장하는 상태 값임. 

예를 들어 사용자가 검색 입력란에 React라는 단어를 입력하면 query 상태는 이 값을 저장함. 즉, query는 현재 입력된 검색어를 추적하고 보관하는 역할을 함.

 

setQuery

:이 함수는 query 상태를 업데이트 하는데 사용됨.

사용자가 입력란에 새로운 검색어를 입력할 때마다 이 함수가 호출되어 query 상태를 최신값으로 변경함.

예를 들어, 사용자가 입력을 변경할 때마다 setQuery(newValue)를 호출하여 query의 값을 newValue(새로운 값)으로 업데이트 할 수 있음.

 

 

 

 

 

import { CarProps, FilterProps } from "@/types";

export const calculateCarRent = (city_mpg: number, year: number) => {
  const basePricePerDay = 50;
  const mileageFactor = 0.1;
  const ageFactor = 0.05;

  const mileageRate = city_mpg * mileageFactor;
  const ageRate = (new Date().getFullYear() - year) * ageFactor;

  const rentalRatePerDay = basePricePerDay + mileageRate + ageRate;

  return rentalRatePerDay.toFixed(0);
};

export const updateSearchParams = (type: string, value: string) => {
  const searchParams = new URLSearchParams(window.location.search);

  searchParams.set(type, value);

  const newPathname = `${window.location.pathname}?${searchParams.toString()}`;

  return newPathname;
};

export const deleteSearchParams = (type: string) => {
  const newSearchParams = new URLSearchParams(window.location.search);

  newSearchParams.delete(type.toLocaleLowerCase());

  const newPathname = `${
    window.location.pathname
  }?${newSearchParams.toString()}`;

  return newPathname;
};

export async function fetchCars(filters: FilterProps) {
  const { manufacturer, year, model, limit, fuel } = filters;

  const headers: HeadersInit = {
    "X-RapidAPI-Key": "7ffff88ee4msh3ef184e28e73bb3p172057jsndfdd6b4649d7",
    // "X-RapidAPI-Key": "process.env.NEXT_PUBLIC_IMAGIN_API_KEY || "", 해당 환경 변수가 설정 되어 있을 경우 해당 값 사용, 비어있는 경우 빈 문자열 사용
    "X-RapidAPI-Host": "cars-by-api-ninjas.p.rapidapi.com",
  };

  const response = await fetch(
    `https://cars-by-api-ninjas.p.rapidapi.com/v1/cars?make=${manufacturer}&year=${year}&model=${model}&limit=${limit}&fuel_type=${fuel}`,
    {
      headers: headers,
    }
  );

  const result = await response.json();

  return result;
}

export const generateCarImageUrl = (car: CarProps, angle?: string) => {
  const url = new URL("https://cdn.imagin.studio/getimage");
  const { make, model, year } = car;

  url.searchParams.append("customer", "hrjavascript-mastery");
  // url.searchParams.append(
  //   "customer",
  //   process.env.NEXT_PUBLIC_IMAGIN_API_KEY || "" 해당 환경 변수(hrjavascript-mastery)가 설정 되어 있을 경우 해당 값 사용, 비어있는 경우 빈 문자열 사용
  // );
  url.searchParams.append("make", make);
  url.searchParams.append("modelFamily", model.split(" ")[0]);
  url.searchParams.append("zoomType", "fullscreen");
  url.searchParams.append("modelYear", `${year}`);
  url.searchParams.append("angle", `${angle}`);

  return `${url}`;
};

utils - index.ts

: type script를 사용하여 React 애플리케이션에서 사용되는 여러 유틸리티 함수들을 정의하고 있습니다.

 

 

Details

-calculateCarRent

: 자동차 렌탈 요금을 계산하는 함수입니다. 주행 마일리지와 자동차 년도를 고려하여 기본 렌탈 요금을 계산합니다.

city_mpg는 주행 마일리지, year는 자동차의 생산 년도입니다. 기본 일일 렌탈 요금, 주행 마일리지에 따른 추가 비용, 그리고 자동차의 나이에 따른 추가 비용을 고려하여 총 렌탈 요금을 계산하고 그 값을 문자열로 반환합니다.

 

-updateSearchParams

:현재 URL의 쿼리 매개변수를 업데이트하는 함수입니다. 주어진 유형(type)과 값(value)을 사용하여 매개변수를 설정하고, 업데이트된 URL을 반환합니다.

type은 업데이트할 매개변수의 이름이고, value는 해당 매개변수에 설정할 값입니다. 그리고 업데이트 된 URL을 반환합니다.

 

-deleteSearchParams

:현재 URL의 특정 쿼리 매개변수를 삭제하는 함수입니다. 유형에 해당하는 매개변수를 제거하고, 업데이트된 URL을 반환합니다.

여기서의 type은 삭제할 매개변수의 이름입니다. 업데이트된 URL을 반환합니다.

 

-fetchCars

: 외부 API에서 자동차 정보를 가져오는 비동기 함수입니다. 주어진 필터를 사용하여 API에 요청하고, 결과를 반환합니다.

filters 객체는 제조사, 연도, 모델 등을 지정하는데 사용됩니다.

 

-generateCarImageUrl

:Image 스튜디오 API를 사용하여 자동차 이미지 URL을 생성하는 함수입니다. 주어진 자동차 정보와 각도(angle)을 고려하여 이미지 URL을 생성하고 반환합니다.

 

728x90
반응형

 

문제 1. 머쓱이는 직육면체 모양의 상자를 하나 가지고 있는데 이 상자에 정육면체 모양의 주사위를 최대한 많이 채우고 싶습니다. 상자의 가로, 세로, 높이가 저장되어있는 배열 box와 주사위 모서리의 길이 정수 n이 매개변수로 주어졌을 때, 상자에 들어갈 수 있는 주사위의 최대 개수를 return 하도록 solution 함수를 완성해주세요.

 

function solution(box, n) {
    let answer = 1;
    for(let i=0; i<3; i++){
        answer *= Math.floor(box[i]/n)
    }
    return answer;
}

solution 함수는 box와 n이라는 두 개의 매개변수를 입력으로 받습니다. box는 세 개의 숫자로 이루어진 배열이고, n은 나눌 수입니다.
answer 변수를 1로 초기화합니다. 이 변수는 최종 결과를 담을 변수입니다.
for 루프를 사용하여 box 배열의 각 요소에 대해 연산을 수행합니다. i 변수는 0부터 2까지 반복됩니다.
각 box[i]를 n으로 나눈 몫을 구하고, Math.floor 함수를 사용하여 정수로 변환합니다.
answer 변수에 box[i]를 n으로 나눈 몫을 곱해줍니다. 이렇게 하면 세 개의 결과가 차례대로 곱해집니다.
for 루프가 종료되면 answer 변수에는 세 개의 연산 결과가 모두 곱해진 값이 저장됩니다.
최종적으로 answer 값을 반환합니다.

 

 

 

문제 2. 약수의 개수가 세 개 이상인 수를 합성수라고 합니다. 자연수 n이 매개변수로 주어질 때 n이하의 합성수의 개수를 return하도록 solution 함수를 완성해주세요.

 

function solution(n) {
    let answer = 0;
    for(let i=n; i>=1; i--) {
        let isCount = false;
        for(let num =2; num<i-1; num++){
            if(i % num == 0) {
            isCount = true;
        }
    }
    if(isCount){
            answer += 1;
    }
}
    return answer;
}

solution 함수는 n이라는 매개변수를 입력으로 받습니다. n은 양의 정수입니다.
answer 변수를 0으로 초기화합니다. 이 변수는 소수의 개수를 담을 변수입니다.
for 루프를 사용하여 i 변수를 n부터 1까지 감소시킵니다.
isCount 변수를 false로 초기화합니다. 이 변수는 소수 여부를 나타내는 플래그입니다.
두 번째 for 루프를 사용하여 num 변수를 2부터 i - 1까지 반복합니다.
if 문을 사용하여 현재의 i가 num으로 나누어떨어지는지 확인합니다. 나누어떨어지면 isCount 변수를 true로 설정합니다.
isCount 변수가 true인 경우, 즉 i가 어떤 숫자로 나누어떨어진 경우, 소수가 아니므로 answer 변수를 1 증가시킵니다.
내부 for 루프가 종료되면, 현재의 i에 대한 소수 여부가 결정됩니다.
외부 for 루프가 종료되면, n 이하의 모든 숫자에 대한 소수 여부가 확인되고, answer 변수에 소수의 개수가 저장됩니다.
최종적으로 answer 값을 반환합니다.

 

 

 

 

문제 3. 정수 배열 numbers가 매개변수로 주어집니다. numbers의 원소 중 두 개를 곱해 만들 수 있는 최댓값을 return하도록 solution 함수를 완성해주세요.

 

function solution(numbers) {
    const sortedNumbers = numbers.sort((a,b)=>b-a);
    return sortedNumbers[0] * sortedNumbers[1];
}

solution 함수는 numbers라는 매개변수를 입력으로 받습니다. 

이 매개변수는 숫자 배열입니다.


sortedNumbers라는 변수에 numbers 배열을 내림차순으로 정렬한 결과를 저장합니다. 

sort 메서드는 비교 함수 (a, b) => b - a를 사용하여 정렬합니다. 

이 비교 함수는 a와 b를 비교하여 b - a의 결과가 양수이면 b가 a보다 큰 것으로 판단하여 정렬합니다.


정렬된 sortedNumbers 배열의 첫 번째 원소와 두 번째 원소를 곱한 결과를 반환합니다.


이 함수는 입력된 숫자 배열에서 가장 큰 두 수를 선택하여 곱한 값을 반환합니다.

sort 메서드를 사용하여 배열을 내림차순으로 정렬하므로, sortedNumbers 배열의 첫 번째 원소는 가장 큰 수이고, 두 번째 원소는 그 다음으로 큰 수입니다.

이 두 수를 곱한 결과를 반환합니다.

 

 

 

 

문제 4. i 팩토리얼 (i!)은 1부터 i까지 정수의 곱을 의미합니다. 예를들어 5! = 5 * 4 * 3 * 2 * 1 = 120 입니다. 정수 n이 주어질 때 다음 조건을 만족하는 가장 큰 정수 i를 return 하도록 solution 함수를 완성해주세요.

 

const getFactorial = (i) => {
    let result = 1;
    for(let j = 1; j<=i; j++) {
        result *= j;
    }
    return result;
}
function solution(n) {
    var answer = 0;
    
    for(let i=1; ; i++){
     const result = getFactorial(i);
        if(n<result){
            return i - 1;
        }
    }
}

getFactorial 함수는 i라는 매개변수를 입력으로 받습니다. 

이 매개변수는 팩토리얼을 계산할 숫자입니다.

result라는 변수에 1로 초기화합니다.

for 루프를 사용하여 j를 1부터 i까지 증가시키며 result에 j를 곱해 나갑니다. 

이렇게 하면 result에는 i의 팩토리얼 값이 저장됩니다.

result를 반환합니다.

solution 함수는 n이라는 매개변수를 입력으로 받습니다. 이 매개변수는 숫자입니다.

answer라는 변수를 0으로 초기화합니다.

무한루프를 돌면서 i를 1부터 증가시키며 다음을 수행합니다.

getFactorial(i)를 호출하여 i의 팩토리얼 값을 구합니다.

만약 n이 result보다 작다면, 현재 i에서 1을 빼고 반환합니다. 

이는 n보다 작은 팩토리얼 값들 중 가장 큰 값의 인덱스를 나타냅니다.

이 함수는 주어진 숫자 n보다 작은 팩토리얼 값들 중 가장 큰 값의 인덱스를 반환합니다. 

getFactorial 함수를 사용하여 각 i의 팩토리얼 값을 계산하고, n과 비교하여 n보다 작을 때까지 반복합니다. 

n보다 작아지는 순간의 i에서 1을 빼고 반환하여 가장 큰 값의 인덱스를 구합니다.

728x90
반응형

문제 1.

사분면은 한 평면을 x축과 y축을 기준으로 나눈 네 부분입니다. 사분면은 아래와 같이 1부터 4까지 번호를매깁니다.

x 좌표와 y 좌표가 모두 양수이면 제1사분면에 속합니다.
x 좌표가 음수, y 좌표가 양수이면 제2사분면에 속합니다.
x 좌표와 y 좌표가 모두 음수이면 제3사분면에 속합니다.
x 좌표가 양수, y 좌표가 음수이면 제4사분면에 속합니다.


x 좌표 (x, y)를 차례대로 담은 정수 배열 dot이 매개변수로 주어집니다. 좌표 dot이 사분면 중 어디에 속하는지 1, 2, 3, 4 중 하나를 return 하도록 solution 함수를 완성해주세요.

function solution(dot) {
  const x = dot[0];
  const y = dot[1];

  if (x > 0 && y > 0) {
    return 1;
  } else if (x < 0 && y > 0) {
    return 2;
  } else if (x < 0 && y < 0) {
    return 3;
  } else if (x > 0 && y < 0) {
    return 4;
  }
}

solution 함수는 dot라는 매개변수를 입력으로 받습니다. 이는 좌표 평면상의 점을 의미하는 배열입니다.
dot 배열의 첫 번째 요소를 x 변수에 할당합니다. 이는 점의 x 좌표를 나타냅니다.
dot 배열의 두 번째 요소를 y 변수에 할당합니다. 이는 점의 y 좌표를 나타냅니다.
조건문을 사용하여 좌표를 판별합니다.
만약 x가 양수이고 y가 양수인 경우, 이는 제 1사분면에 속하는 점입니다. 따라서 1을 반환합니다.
만약 x가 음수이고 y가 양수인 경우, 이는 제 2사분면에 속하는 점입니다. 따라서 2를 반환합니다.
만약 x가 음수이고 y가 음수인 경우, 이는 제 3사분면에 속하는 점입니다. 따라서 3을 반환합니다.
만약 x가 양수이고 y가 음수인 경우, 이는 제 4사분면에 속하는 점입니다. 따라서 4를 반환합니다.

 

 

문제 2. 정수 배열 num_list와 정수 n이 매개변수로 주어집니다. num_list를 다음 설명과 같이 2차원 배열로 바꿔 return하도록 solution 함수를 완성해주세요.

num_list가 [1, 2, 3, 4, 5, 6, 7, 8] 로 길이가 8이고 n이 2이므로 num_list를 2 * 4 배열로 다음과 같이 변경합니다. 2차원으로 바꿀 때에는 num_list의 원소들을 앞에서부터 n개씩 나눠 2차원 배열로 변경합니다.

function solution(num_list, n) {
  const result = [];
  
  for (let i = 0; i < num_list.length; i += n) {
    const chunk = num_list.slice(i, i + n);
    result.push(chunk);
  }
  
  return result;
}

solution 함수는 num_list와 n이라는 두 개의 매개변수를 입력으로 받습니다. num_list는 숫자로 이루어진 배열이고, n은 나눌 크기를 나타냅니다.
result라는 빈 배열을 생성합니다. 이 배열은 결과로 반환할 2차원 배열입니다.
for 반복문을 사용하여 배열을 n 크기로 나눕니다. 반복문의 초기값 i는 0부터 시작하고, i를 n만큼 증가시킵니다.
num_list 배열의 i부터 i + n - 1까지의 요소를 추출하여 chunk라는 배열을 생성합니다. 이는 현재 나눈 부분 배열입니다.
result 배열에 chunk 배열을 추가합니다.
반복문이 종료되면 result 배열을 반환합니다. 이는 원래 배열을 n 크기로 나눈 2차원 배열입니다.

 

 

문제 3. 머쓱이는 친구들과 동그랗게 서서 공 던지기 게임을 하고 있습니다. 공은 1번부터 던지며 오른쪽으로 한 명을 건너뛰고 그다음 사람에게만 던질 수 있습니다. 친구들의 번호가 들어있는 정수 배열 numbers와 정수 K가 주어질 때, k번째로 공을 던지는 사람의 번호는 무엇인지 return 하도록 solution 함수를 완성해보세요.

function solution(numbers, k) {
    let cnt = 1;
    for(let i = 0; ;i+=2){
      i %= numbers.length;
     const item = numbers[i];
        if(cnt === k ) {
            return item;
        }
        cnt+=1;
    }
}

solution 함수는 numbers와 k라는 두 개의 매개변수를 입력으로 받습니다. numbers는 숫자로 이루어진 배열이고, k는 선택할 요소의 위치를 나타냅니다.
cnt라는 변수를 1로 초기화합니다. 이 변수는 현재까지 선택한 요소의 개수를 나타냅니다.
for 반복문을 사용하여 배열의 요소를 선택합니다. 반복문의 초기값 i는 0부터 시작하고, 2씩 증가합니다.
i를 배열의 길이로 나눈 나머지를 구합니다. 이는 배열의 인덱스 범위를 벗어나지 않도록 합니다.
numbers 배열에서 i 위치의 요소를 item 변수에 저장합니다.
cnt가 k와 같아지면 선택한 요소를 반환합니다.
cnt를 1씩 증가시킵니다.
반복문이 종료되지 않고 계속 실행되도록 조건식을 비워둡니다.

 

 

문제 4. 정수가 담긴 배열 numbers와 문자열 direction가 매개변수로 주어집니다. 배열 numbers의 원소를 direction방향으로 한 칸씩 회전시킨 배열을 return하도록 solution 함수를 완성해주세요.

function solution(numbers, direction) {
    const answer = [];
    if(direction === 'right'){
        answer.push(numbers[numbers.length - 1]);
        for(let i=0; i<numbers.length-1; i++){
                answer.push(numbers[i]);
            }
        } else {
            for(let i=1; i<numbers.length; i++) {
                answer.push(numbers[i]);
            }
            answer.push(numbers[0]);
        }
        return answer;
    }

solution 함수는 numbers와 direction라는 두 개의 매개변수를 입력으로 받습니다. numbers는 숫자로 이루어진 배열이고, direction은 회전 방향을 나타냅니다.
answer라는 빈 배열을 선언합니다. 결과로 반환할 회전된 배열입니다.
만약 direction이 'right'인 경우, 오른쪽으로 회전시키는 로직을 수행합니다.
numbers 배열의 마지막 요소를 answer 배열에 추가합니다.
numbers 배열의 첫 번째부터 마지막 이전 요소까지를 answer 배열에 순서대로 추가합니다.
그렇지 않은 경우, 왼쪽으로 회전시키는 로직을 수행합니다.
numbers 배열의 두 번째부터 마지막 요소까지를 answer 배열에 순서대로 추가합니다.
numbers 배열의 첫 번째 요소를 answer 배열에 추가합니다.
최종적으로 answer 배열을 반환합니다.

728x90
반응형

 

 

문제 1. 개미 군단이 사냥을 나가려고 합니다. 개미군단은 사냥감의 체력에 딱 맞는 병력을 데리고 나가려고 합니다. 장군개미는 5의 공격력을, 병정개미는 3의 공격력을 일개미는 1의 공격력을 가지고 있습니다. 예를 들어 체력 23의 여치를 사냥하려고 할 때, 일개미 23마리를 데리고 가도 되지만, 장군개미 네 마리와 병정개미 한 마리를 데리고 간다면 더 적은 병력으로 사냥할 수 있습니다. 사냥감의 체력 hp가 매개변수로 주어질 때, 사냥감의 체력에 딱 맞게 최소한의 병력을 구성하려면 몇 마리의 개미가 필요한지를 return하도록 solution 함수를 완성해주세요.

function solution(hp) {
  let antCount = 0;
  
  // 장군개미, 병정개미, 일개미 중 최대한 많은 개미로 체력을 감소시킴
  while (hp > 0) {
    if (hp >= 5) {
      antCount += Math.floor(hp / 5);  // 장군개미의 개수를 추가
      hp = hp % 5;  // 남은 체력 계산
    } else if (hp >= 3) {
      antCount += Math.floor(hp / 3);  // 병정개미의 개수를 추가
      hp = hp % 3;
    } else {
      antCount += hp;  // 일개미의 개수를 추가
      hp = 0;
    }
  }
  
  return antCount;
}

solution 함수는 hp라는 매개변수를 입력으로 받습니다. 

이는 체력을 나타내는 값입니다.


antCount 변수는 사용한 개미의 총 개수를 담을 변수입니다. 

초기 값으로 0을 설정합니다.


while 반복문을 사용하여 체력(hp)이 0보다 클 때까지 반복합니다.


체력(hp)이 5 이상인 경우, 장군개미를 사용하여 체력을 감소시킵니다.


hp를 5로 나눈 몫을 antCount에 추가합니다. 이는 장군개미의 개수를 의미합니다.


남은 체력을 계산하기 위해 hp를 5로 나눈 나머지를 할당합니다.


체력(hp)이 3 이상인 경우, 병정개미를 사용하여 체력을 감소시킵니다.


hp를 3으로 나눈 몫을 antCount에 추가합니다. 이는 병정개미의 개수를 의미합니다.


남은 체력을 계산하기 위해 hp를 3으로 나눈 나머지를 할당합니다.


체력(hp)이 3 미만인 경우, 일개미를 사용하여 체력을 감소시킵니다.


hp 값을 antCount에 추가합니다. 이는 일개미의 개수를 의미합니다.


체력(hp)를 0으로 설정하여 루프를 종료합니다.


반복문을 통해 체력이 0이 될 때까지 위의 과정을 수행합니다.


마지막으로 사용한 개미의 총 개수인 antCount 값을 반환합니다.

 

 

 

문제 2. 머쓱이는 친구에게 모스부호를 이용한 편지를 받았습니다. 그냥은 읽을 수 없어 이를 해독하는 프로그램을 만들려고 합니다. 문자열 letter가 매개변수로 주어질 때, letter를 영어 소문자로 바꾼 문자열을 return 하도록 solution 함수를 완성해보세요.

function solution(letter) {
  const morse = { 
    '.-': 'a', '-...': 'b', '-.-.': 'c', '-..': 'd', '.': 'e', '..-.': 'f',
    '--.': 'g', '....': 'h', '..': 'i', '.---': 'j', '-.-': 'k', '.-..': 'l',
    '--': 'm', '-.': 'n', '---': 'o', '.--.': 'p', '--.-': 'q', '.-.': 'r',
    '...': 's', '-': 't', '..-': 'u', '...-': 'v', '.--': 'w', '-..-': 'x',
    '-.--': 'y', '--..': 'z'
  };
  
  const words = letter.split(' ');
  let result = '';
  
  for (let i = 0; i < words.length; i++) {
    const word = words[i];
    const translatedWord = morse[word];
    result += translatedWord;
  }
  
  return result;
}

solution 함수는 letter라는 매개변수를 입력으로 받습니다. 이는 모스 부호 문자열을 의미합니다.
morse 객체는 몰스 부호와 해당하는 알파벳을 매핑한 객체입니다.
words 변수는 입력된 몰스 부호 문자열을 공백을 기준으로 나눈 배열입니다.
result 변수는 변환된 알파벳을 저장할 문자열로 초기값은 빈 문자열입니다.
for 반복문을 사용하여 words 배열을 순회합니다.
각각의 몰스 부호(word)를 가져와서 morse 객체에서 해당하는 알파벳을 찾습니다.
찾은 알파벳을 translatedWord 변수에 할당합니다.
result에 translatedWord를 추가합니다.
모든 몰스 부호에 대해 위의 과정을 반복합니다.
반복문이 종료되면 변환된 알파벳이 저장된 result를 반환합니다.

 

 

 

문제 3. 가위는 2 바위는 0 보는 5로 표현합니다. 가위 바위 보를 내는 순서대로 나타낸 문자열 rsp가 매개변수로 주어질 때, rsp에 저장된 가위 바위 보를 모두 이기는 경우를 순서대로 나타낸 문자열을 return하도록 solution 함수를 완성해보세요.

function solution(rsp) {
 let answer = '';
    for(let i of rsp) {
        if(i == 0){
            answer += '5'
        } else if (i == 2) {
            answer += '0'
        } else if(i == 5) {
            answer +='2'
        }
    }
 return answer;
}

 

 

 

 

문제 4. 머쓱이는 구슬을 친구들에게 나누어주려고 합니다. 구슬은 모두 다르게 생겼습니다. 머쓱이가 갖고 있는 구슬의 개수 balls와 친구들에게 나누어 줄 구슬 개수 share이 매개변수로 주어질 때, balls개의 구슬 중 share개의 구슬을 고르는 가능한 모든 경우의 수를 return 하는 solution 함수를 완성해주세요.

function solution(balls, share) {
  let allBalls = Array.from({ length: balls }, (_, index) => index + 1).reduce(
    (a, b) => {
      return a * b;
    },
    1
  );

  let ballMinusShare = Array.from(
    { length: balls - share },
    (_, index) => index + 1
  ).reduce((a, b) => {
    return a * b;
  }, 1);

  let allShare = Array.from({ length: share }, (_, index) => index + 1).reduce(
    (a, b) => {
      return a * b;
    },
    1
  );

  return Math.round(allBalls / (ballMinusShare * allShare));
}

먼저, allBalls 변수는 1부터 balls까지의 수를 곱한 값을 계산하여 저장한다.

이는 주어진 구슬들 중 어떤 구슬이 선택되어야 하는지 고려하지 않고, 모든 구슬의 경우의 수를 나타낸다.

다음으로, ballMinusShare 변수는 balls - share까지의 수를 곱한 값을 계산하여 저장한다.

이는 주어진 구슬들 중 나누어 주지 않아도 되는 구슬들의 경우의 수를 나타낸다.

즉, 친구들에게 나누어 주어야 하는 구슬의 개수를 제외한 나머지 구슬의 경우의 수이다.

마지막으로, allShare 변수는 1부터 share까지의 수를 곱한 값을 계산하여 저장한다. 

이는 친구들에게 나누어 주어야 하는 구슬의 개수에 대한 경우의 수를 나타낸다.

 allBalls을 ballMinusShare와 allShare의 곱으로 나누어서 가능한 모든 경우의 수를 구한다.

이 값은 소수점 이하를 버린 결과로 반환된다.

소수점 이하의 값은 나누어 떨어지지 않는 경우에 발생할 수 있으므로, Math.round 함수를 사용하여 반올림 처리를 한다.

728x90
반응형

문제 1. 정수 배열 numbers와 정수 num1num2가 매개변수로 주어질 때, numbers의 num1번 째 인덱스부터 num2번째 인덱스까지 자른 정수 배열을 return 하도록 solution 함수를 완성해보세요.

function solution(numbers, num1, num2) {
  let answer = numbers.slice(num1, num2 + 1);
  return answer;
}

solution 함수는 numbers, num1, num2라는 매개변수를 입력으로 받습니다.
numbers는 원본 배열이며, num1은 시작 인덱스, num2는 종료 인덱스를 나타냅니다.
answer는 numbers 배열의 num1부터 num2까지의 부분 배열을 추출하여 저장합니다. slice 메소드를 사용하여 해당 부분 배열을 생성합니다. num1은 시작 인덱스이므로 포함되고, num2는 종료 인덱스이므로 포함됩니다.
추출된 부분 배열을 answer에 할당합니다.
최종적으로 answer를 반환하여 함수의 결과로 출력됩니다.

 

문제 2. 우주여행을 하던 머쓱이는 엔진 고장으로 PROGRAMMERS-962 행성에 불시착하게 됐습니다. 입국심사에서 나이를 말해야 하는데, PROGRAMMERS-962 행성에서는 나이를 알파벳으로 말하고 있습니다. a는 0, b는 1, c는 2, ..., j는 9입니다. 예를 들어 23살은 cd, 51살은 fb로 표현합니다. 나이 age가 매개변수로 주어질 때 PROGRAMMER-962식 나이를 return하도록 solution 함수를 완성해주세요.

function solution(age) {
  let result = '';

  while (age > 0) {
    const remainder = age % 10; // 나이의 일의 자리 수
    result = String.fromCharCode(97 + remainder) + result; // 알파벳으로 변환하여 결과에 추가
    age = Math.floor(age / 10); // 나이를 10으로 나눈 몫으로 갱신
  }

  return result;
}

solution 함수는 age라는 매개변수를 입력으로 받습니다.
result는 최종 변환 결과를 저장할 변수입니다.
while 반복문을 사용하여 age가 0보다 큰 동안 반복합니다.
remainder 변수에 age를 10으로 나눈 나머지 값을 저장합니다. 이는 age의 일의 자리 숫자를 나타냅니다.
String.fromCharCode(97 + remainder)를 사용하여 remainder에 해당하는 알파벳 문자를 생성합니다. 97은 소문자 알파벳 'a'의 유니코드 값입니다. 따라서 remainder의 값에 97을 더하면 해당 알파벳의 유니코드 값이 됩니다.
알파벳 문자열을 결과(result)의 앞쪽에 추가하기 위해 result 변수의 값을 String.fromCharCode(97 + remainder)와 결합합니다.
age를 10으로 나눈 몫을 새로운 age 값으로 갱신합니다. 이는 age의 일의 자리 숫자를 제거하는 역할을 합니다.
age가 0보다 큰 동안 위의 과정을 반복합니다.
반복이 종료되면 result를 반환하여 함수의 결과로 출력됩니다.

 

 

 

문제 3. 외과의사 머쓱이는 응급실에 온 환자의 응급도를 기준으로 진료 순서를 정하려고 합니다. 정수 배열 emergency가 매개변수로 주어질 때 응급도가 높은 순서대로 진료 순서를 정한 배열을 return하도록 solution 함수를 완성해주세요.

function solution(emergency) {
  let answer = new Array(emergency.length).fill(1);
  for (let i = 0; i < emergency.length; i++) {
    emergency.map((v) => {
      if (emergency[i] < v) answer[i]++;
    });
  }
  return answer;
}

solution 함수는 emergency라는 매개변수를 입력으로 받습니다. 이는 응급 상황에 대한 정보가 담긴 배열입니다.
answer 변수는 응급 상황의 우선순위를 담을 배열입니다. 

초기 값으로 emergency.length 길이의 배열을 생성하고 1로 채웁니다.
for 반복문을 사용하여 emergency 배열의 각 요소를 순회합니다.
내부에 있는 emergency.map 메서드는 emergency 배열을 순회하면서 현재 요소보다 큰 값이 있는지 확인합니다.
만약 현재 요소(emergency[i])보다 큰 값(v)이 있다면, 해당 우선순위(answer[i])를 1 증가시킵니다.
반복문을 통해 모든 요소에 대해 위의 과정을 수행합니다.
마지막으로 우선순위(answer) 배열을 반환합니다.

 

 

 

 

문제 4. 순서쌍이란 두 개의 숫자를 순서를 정하여 짝지어 나타낸 쌍으로 (a, b)로 표기합니다. 자연수 n이 매개변수로 주어질 때 두 숫자의 곱이 n인 자연수 순서쌍의 개수를 return하도록 solution 함수를 완성해주세요.

function solution(n) {
  let answer = 0;
  
  // 숫자 i를 1부터 n까지 증가시키면서 두 숫자의 곱이 n인 경우를 찾음
  for (let i = 1; i <= n; i++) {
    if (n % i === 0) {
      answer++;
    }
  }
  
  return answer;
}

solution 함수는 n이라는 매개변수를 입력으로 받습니다. 이는 약수를 구할 숫자입니다.
answer 변수는 약수의 개수를 담을 변수입니다. 초기 값으로 0을 설정합니다.
for 반복문을 사용하여 1부터 n까지의 숫자를 증가시키면서 약수를 찾습니다.
if 문을 사용하여 n을 현재 숫자 i로 나눈 나머지가 0인 경우를 찾습니다. 즉, i가 n의 약수인 경우입니다.
약수를 찾았을 때마다 answer 변수를 1 증가시킵니다.
반복문을 통해 모든 숫자에 대해 위의 과정을 수행합니다.
마지막으로 약수의 개수인 answer 값을 반환합니다.

728x90
반응형

 

문제 1. 문자열 my_string과 문자 letter이 매개변수로 주어집니다. my_string에서 letter를 제거한 문자열을 return하도록 solution 함수를 완성해주세요.

function solution(my_string, letter) {
  let answer = '';

  for (let i = 0; i < my_string.length; i++) {
    if (my_string[i] !== letter) {
      answer += my_string[i];
    }
  }

  return answer;
}

solution 함수는 my_string이라는 문자열과 letter라는 문자를 입력받습니다.
answer 변수를 선언하고 빈 문자열로 초기화합니다. 이 변수는 결과로 반환할 새로운 문자열입니다.
for 루프에서는 my_string의 각 문자에 접근합니다.
조건문 if를 사용하여 현재 문자가 letter와 다른 경우에만 실행합니다.
my_string[i]와 letter가 다른 경우에는 현재 문자를 answer에 추가합니다.
이렇게 하면 letter와 일치하지 않는 문자만 answer에 추가되게 됩니다.
모든 문자에 대해 위 과정을 반복하면, answer에는 letter가 제거된 문자열이 저장됩니다.
최종적으로 answer를 반환하여 함수의 결과로 출력됩니다.

 

 

문제 2. 각에서 0도 초과 90도 미만은 예각, 90도는 직각, 90도 초과 180도 미만은 둔각 180도는 평각으로 분류합니다. 각 angle이 매개변수로 주어질 때 예각일 때 1, 직각일 때 2, 둔각일 때 3, 평각일 때 4를 return하도록 solution 함수를 완성해주세요.

function solution(angle) {
  if (angle > 0 && angle < 90) {
    return 1; // 예각
  } else if (angle === 90) {
    return 2; // 직각
  } else if (angle > 90 && angle < 180) {
    return 3; // 둔각
  } else if (angle === 180) {
    return 4; // 평각
  }
}

 

 

문제 3. 머쓱이네 양꼬치 가게는 10인분을 먹으면 음료수 하나를 서비스로 줍니다. 양꼬치는 1인분에 12,000원, 음료수는 2,000원입니다. 정수 n과 k가 매개변수로 주어졌을 때, 양꼬치 n인분과 음료수 k개를 먹었다면 총얼마를 지불해야 하는지 return 하도록 solution 함수를 완성해보세요.

function solution(n, k) {
  const yakitoriPrice = 12000; // 양꼬치 가격
  const drinkPrice = 2000; // 음료수 가격

  const totalYakitoriPrice = n * yakitoriPrice; // 양꼬치 총 가격
  const freeDrinks = Math.floor(n / 10); // 무료 음료수 개수
  const totalDrinkPrice = (k - freeDrinks) * drinkPrice; // 음료수 총 가격 (무료 음료수를 제외한 개수에 대한 가격)

  const totalPrice = totalYakitoriPrice + totalDrinkPrice; // 총 결제 금액

  return totalPrice;
}

solution 함수는 n과 k라는 매개변수를 입력으로 받습니다.
yakitoriPrice는 양꼬치 1개의 가격을 나타내는 상수입니다. 여기서는 12000으로 설정되어 있습니다.
drinkPrice는 음료수 1개의 가격을 나타내는 상수입니다. 여기서는 2000으로 설정되어 있습니다.
totalYakitoriPrice는 주어진 양꼬치 개수 n과 yakitoriPrice를 곱하여 양꼬치 총 가격을 계산합니다.
freeDrinks는 양꼬치 10개당 1개의 무료 음료수가 제공되므로, n을 10으로 나눈 몫을 계산하여 무료 음료수 개수를 구합니다.
totalDrinkPrice는 주어진 음료수 개수 k에서 무료 음료수 개수 freeDrinks를 제외하고 남은 개수에 drinkPrice를 곱하여 음료수 총 가격을 계산합니다.
totalPrice는 양꼬치 총 가격과 음료수 총 가격을 합하여 총 결제 금액을 계산합니다.
최종적으로 totalPrice를 반환하여 함수의 결과로 출력됩니다.

 

 

문제 4. 정수 n이 주어질 때, n이하의 짝수를 모두 더한 값을 return 하도록 solution 함수를 작성해주세요.

function solution(n) {
    let answer = 0;
    
    for(let i=0; i<=n; i++) {
        if(i % 2 == 0) {
            answer = answer + i;
        } 
    }
    return answer;
}

 

728x90
반응형

 

문제 1. 문자열 my_string이 매개변수로 주어집니다. my_string을 거꾸로 뒤집은 문자열을 return하도록 solution 함수를 완성해주세요.

function solution(my_string) {
    let answer = '';
    for (let i = my_string.length - 1; i >= 0; i--) {
        answer += my_string[i];
    }
    return answer;
}

solution 함수는 my_string이라는 인자를 받습니다. 이 인자는 뒤집고자 하는 문자열입니다.
answer 변수는 빈 문자열로 초기화됩니다. 이 변수에 뒤집힌 문자열을 저장할 것입니다.
for 루프를 사용하여 my_string의 뒤에서부터 문자를 하나씩 가져옵니다.
i 변수는 my_string의 길이에서 1을 뺀 값부터 시작하여 0까지 감소합니다. 이렇게 함으로써 문자열을 역순으로 순회합니다.
각 문자를 answer 변수에 추가합니다. my_string[i]는 my_string의 인덱스 i에 해당하는 문자를 의미합니다. 따라서 my_string을 역순으로 순회하며 answer에 문자를 추가하게 됩니다.
최종적으로 answer 변수에 저장된 뒤집힌 문자열을 반환합니다.

 

 

문제 2. "*"의 높이와 너비를 1이라고 했을 때, "*"을 이용해 직각 이등변 삼각형을 그리려고합니다. 정수 n 이 주어지면 높이와 너비가 n 인 직각 이등변 삼각형을 출력하도록 코드를 작성해보세요.

const readline = require('readline');
const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

let input = [];

rl.on('line', function (line) {
    input = line.split(' ');
}).on('close', function () {
    const n = Number(input[0]);
    for (let i = 1; i <= n; i++) {
        let row = '';
        for (let j = 1; j <= i; j++) {
            row += '*';
        }
        console.log(row);
    }
});

readline 모듈을 사용하여 사용자로부터 입력을 받아오기 위한 준비를 합니다.
rl 객체를 생성하여 input과 output 스트림을 설정합니다. 이를 통해 사용자 입력을 받고 출력할 수 있습니다.
빈 배열 input을 선언합니다. 이 배열은 사용자 입력을 저장할 것입니다.
rl 객체의 line 이벤트 리스너를 등록합니다. 이벤트는 사용자가 한 줄을 입력할 때마다 발생합니다.
입력받은 한 줄의 문자열을 공백을 기준으로 나누어 input 배열에 저장합니다.
rl 객체의 close 이벤트 리스너를 등록합니다. 이벤트는 사용자 입력이 끝나고 입력 스트림이 닫힐 때 발생합니다.
input[0] 값을 가져와서 숫자로 변환한 후 n 변수에 저장합니다. 이 값은 출력할 삼각형의 높이를 나타냅니다.
for 루프를 사용하여 1부터 n까지 반복합니다. i는 현재 행의 번호를 나타냅니다.
row 변수를 선언하고 초기값을 빈 문자열로 설정합니다. 이 변수에 현재 행에서 출력할 별(*)을 저장할 것입니다.
두 번째 for 루프를 사용하여 1부터 i까지 반복합니다. j는 현재 행에서 출력할 별(*)의 개수를 나타냅니다.
row 변수에 별(*)을 추가합니다.
현재 행의 별(*) 패턴이 완성되면 row를 출력합니다.
n까지의 모든 행에 대해 반복하면 삼각형 모양의 별(*) 패턴이 출력됩니다.

 

결과적으로 높이와 너비가 n인 직각 이등변 삼각형을 출력합니다.

더보기

* 

** 

***

**** 

*****

 

 

 

문제 3. 정수가 담긴 리스트 num_list가 주어질 때, num_list의 원소 중 짝수와 홀수의 개수를 담은 배열을 return 하도록 solution 함수를 완성해보세요.

function solution(num_list) {
    let answer = [0, 0]; // 짝수 개수와 홀수 개수를 담을 배열 초기화

    for (let i = 0; i < num_list.length; i++) {
        if (num_list[i] % 2 === 0) {
            answer[0]++; // 짝수인 경우 짝수 개수 증가
        } else {
            answer[1]++; // 홀수인 경우 홀수 개수 증가
        }
    }

    return answer;
}

solution 함수는 num_list라는 숫자 배열을 입력받습니다.
answer 변수를 선언하고 [0, 0]으로 초기화합니다. 이 변수는 짝수 개수와 홀수 개수를 담을 배열입니다.
for 루프를 사용하여 num_list의 각 요소에 접근합니다.
현재 요소가 짝수인지 확인하기 위해 num_list[i] % 2 === 0 조건을 사용합니다. % 연산자는 나머지를 구하는 연산자이며, num_list[i]를 2로 나눈 나머지가 0이면 짝수입니다.
만약 현재 요소가 짝수라면, answer[0]을 증가시킵니다. 이는 짝수 개수를 나타내는 배열의 첫 번째 요소입니다.
현재 요소가 홀수인 경우, answer[1]을 증가시킵니다. 이는 홀수 개수를 나타내는 배열의 두 번째 요소입니다.
모든 요소에 대해 반복하면 짝수 개수와 홀수 개수가 answer 배열에 저장됩니다.
최종적으로 answer 배열을 반환하여 함수의 결과로 출력됩니다.

 

 

문제 4. 문자열 my_string과 정수 n이 매개변수로 주어질 때, my_string에 들어있는 각 문자를 n만큼 반복한 문자열을 return 하도록 solution 함수를 완성해보세요.

function solution(my_string, n) {
    let answer = '';

    for (let i = 0; i < my_string.length; i++) {
        for (let j = 0; j < n; j++) {
            answer += my_string[i];
        }
    }

    return answer;
}

solution 함수는 my_string이라는 문자열과 n이라는 숫자를 입력받습니다.
answer 변수를 선언하고 빈 문자열로 초기화합니다. 이 변수는 결과로 반환할 새로운 문자열입니다.
첫 번째 for 루프에서는 my_string의 각 문자에 접근합니다.
두 번째 for 루프에서는 n번만큼 현재 문자를 answer에 추가합니다.
j가 0부터 n-1까지 반복하면서 my_string[i]를 answer에 추가합니다.
이렇게 하면 현재 문자를 n번 반복하여 answer에 추가하는 효과를 얻을 수 있습니다.
모든 문자에 대해 위 과정을 반복하면, answer에는 문자열이 반복된 결과가 저장됩니다.
최종적으로 answer를 반환하여 함수의 결과로 출력됩니다.

728x90
반응형

 

문제 1. 머쓱이네 옷가게는 10만 원 이상 사면 5%, 30만 원 이상 사면 10%, 50만 원 이상 사면 20%를 할인해줍니다.
구매한 옷의 가격 price가 주어질 때, 지불해야 할 금액을 return 하도록 solution 함수를 완성해보세요.

function solution(price) {
  let answer = 0;
  let discountRate = 0;
  
  if (price >= 500000) {
    discountRate = 0.2; // 50만 원 이상일 때 20% 할인
  } else if (price >= 300000) {
    discountRate = 0.1; // 30만 원 이상일 때 10% 할인
  } else if (price >= 100000) {
    discountRate = 0.05; // 10만 원 이상일 때 5% 할인
  }
  
  answer = Math.floor(price - (price * discountRate));
  return answer;
}

입력받은 가격 price를 기준으로 할인율 discountRate를 계산합니다. 조건문을 사용하여 price가 일정 금액 이상인 경우에 해당하는 할인율을 적용합니다. 할인율을 적용한 후, 할인된 가격을 answer 변수에 저장하고 반환합니다.

또한 소수점 이하는 버려야 하기 때문에 Math.floor을 사용합니다.

 

 

문제 2. 머쓱이는 추운 날에도 아이스 아메리카노만 마십니다. 아이스 아메리카노는 한잔에 5,500원입니다. 머쓱이가 가지고 있는 돈 money가 매개변수로 주어질 때, 머쓱이가 최대로 마실 수 있는 아메리카노의 잔 수와 남는 돈을 순서대로 담은 배열을 return 하도록 solution 함수를 완성해보세요.

function solution(money) {
  let answer = [];
  const americanoPrice = 5500;
  let maxCups = Math.floor(money / americanoPrice); // 최대로 마실 수 있는 아메리카노의 잔 수
  let remainingMoney = money - (maxCups * americanoPrice); // 남는 돈
  
  answer.push(maxCups);
  answer.push(remainingMoney);
  
  return answer;
}

아메리카노 한 잔의 가격을 americanoPrice 변수에 저장하고, 최대로 마실 수 있는 아메리카노의 잔 수와 남는 돈을 계산하여 배열 answer에 순서대로 담습니다. Math.floor 함수를 사용하여 정수로 최대 잔 수를 구합니다.

 

 

문제 3. 머쓱이는 40살인 선생님이 몇 년도에 태어났는지 궁금해졌습니다. 나이 age가 주어질 때, 2022년을 기준 출생 연도를 return 하는 solution 함수를 완성해주세요.

function solution(age) {
  let answer = 2022 - age + 1; // 2022년을 기준으로 출생 연도 계산
  return answer;
}

주어진 나이 age를 이용하여 2022년을 기준으로 출생 연도를 계산합니다. 주어진 나이에서 1을 빼고 2022를 더하면 출생 연도가 됩니다.

 

 

 

문제 4. 정수가 들어 있는 배열 num_list가 매개변수로 주어집니다. num_list의 원소의 순서를 거꾸로 뒤집은 배열을 return하도록 solution 함수를 완성해주세요. 

function solution(num_list) {
  let answer = num_list.reverse(); // 배열의 순서를 거꾸로 뒤집음
  return answer;
}

reverse 메서드를 사용하여 주어진 배열 num_list의 순서를 거꾸로 뒤집습니다. 뒤집어진 배열을 answer 변수에 저장하고 반환합니다.

reverse() :

이 메서드는 배열의 순서를 거꾸로 뒤집습니다. 이 메서드는 원본 배열을 직접 변경하며, 뒤집힌 배열을 반환합니다.

728x90
반응형

 

문제 1. 머쓱이네 피자가게는 피자를 일곱 조각으로 잘라 줍니다. 피자를 나눠먹을 사람의 수 n이 주어질 때, 모든 사람이 피자를 한 조각 이상 먹기 위해 필요한 피자의 수를 return 하는 solution 함수를 완성해보세요.

function solution(n) {
  let answer = Math.ceil(n / 7);
  return answer;
}

solution 함수는 n을 7로 나눈 몫을 올림한 값으로 계산하여, 필요한 피자의 수를 구합니다. 이를 answer 변수에 저장하고 반환합니다.

 

 

문제 2. 머쓱이네 피자가게는 피자를 여섯 조각으로 잘라 줍니다. 피자를 나눠먹을 사람의 수 n이 매개변수로 주어질 때, n명이 주문한 피자를 남기지 않고 모두 같은 수의 피자 조각을 먹어야 한다면 최소 몇 판을 시켜야 하는지를 return 하도록 solution 함수를 완성해보세요.

function solution(n) {
  for (let i = 1; i<=n; i++) {
    if ((i * 6) % n === 0) {
      return i;
    }
  }
}

반복문에서 i는 피자 판의 수를 나타냅니다. i를 1부터 n까지 증가시키면서, (i * 6) % n을 계산하여 n명에게 피자 조각을 나누어줄 수 있는지 확인합니다. 만약 (i * 6) % n의 결과가 0이라면, i는 n명에게 피자 조각을 나누어줄 수 있는 판의 수입니다. 따라서 해당 i를 반환하고 함수를 종료합니다.

i를 1부터 n까지 증가시키기 때문에, 처음으로 (i * 6) % n이 0이 되는 i는 n명에게 피자 조각을 나누어줄 수 있는 최소한의 판의 수입니다.
(i * 6) % n의 결과가 0이 되는 조건은 i * 6이 n의 배수임을 의미합니다. 

즉, i가 n의 배수일 때 n명에게 피자 조각을 나누어줄 수 있습니다.
i가 n의 배수일 때 n명에게 피자 조각을 나누어주므로, i는 n명이 모두 같은 수의 피자 조각을 먹을 수 있는 최소한의 판의 수입니다.

 

문제 3. 머쓱이네 피자가게는 피자를 두 조각에서 열 조각까지 원하는 조각 수로 잘라줍니다. 피자 조각 수 slice와 피자를 먹는 사람의 수 n이 매개변수로 주어질 때, n명의 사람이 최소 한 조각 이상 피자를 먹으려면 최소 몇 판의 피자를 시켜야 하는지를 return 하도록 solution 함수를 완성해보세요.

function solution(slice, n) {
  return Math.ceil(n / slice);
}

slice로 나눈 몫을 올림하여 n명이 최소 한 조각 이상씩 피자를 먹을 수 있는 피자 판의 수를 반환합니다.

 

 

문제 4. 정수 배열 numbers가 매개변수로 주어집니다. numbers의 원소의 평균값을 return하도록 solution 함수를 완성해주세요.

function solution(numbers) {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum / numbers.length;
}

반복문을 사용하여 배열 numbers의 모든 원소를 더한 후, 배열의 길이로 나누어 평균값을 계산합니다. 계산된 평균값을 반환합니다.

728x90
반응형

DAY 3. 

 

문제 1. 정수 num1num2가 매개변수로 주어질 때, num1를 num2로 나눈 나머지를 return 하도록 solution 함수를 완성해주세요.

A :

function solution(num1, num2) {
    let answer = 0;
    answer = num1 % num2 
    return answer;
}

% 는 나머지를 구하는 연산자임.

 

 

문제 2. 중앙값은 어떤 주어진 값들을 크기의 순서대로 정렬했을 때 가장 중앙에 위치하는 값을 의미합니다. 예를 들어 1, 2, 7, 10, 11의 중앙값은 7입니다. 정수 배열 array가 매개변수로 주어질 때, 중앙값을 return 하도록 solution 함수를 완성해보세요.

A :

function solution(array) {
  array.sort((a, b) => a - b); // 주어진 배열을 오름차순으로 정렬
  const answer = Math.floor(array.length / 2); // 중앙 인덱스 계산

  return array[answer]; // 중앙값 반환
}

 

sort()

: JavaScript 배열의 내장 메소드로, 배열의 요소들을 정렬하는 데 사용됩니다. sort() 메소드는 원본 배열을 직접 변경하며, 변경된 배열을 반환한다.

기본적으로 sort() 메소드는 배열의 요소를 유니코드 문자열 순서로 정렬한다.

이때, 각 요소는 문자열로 변환되어 비교됩니다. 예를 들어, [3, 1, 2]라는 배열을 sort()로 정렬하면 [1, 2, 3]이 되며, 원본 배열인 [3, 1, 2]도 변경된다.

정렬 순서를 변경하거나 사용자 정의 정렬 기준을 적용하려면 비교 함수를 제공해야 한다. 

비교 함수는 선택적으로 sort() 메소드의 인자로 전달할 수 있다.

비교 함수는 두 개의 인자를 받아서 비교한 결과를 반환해야 합니다. 반환 값에 따라 요소들이 정렬된다. 예를 들어, 오름차순으로 정렬하려면 (a, b) => a - b와 같은 비교 함수를 사용할 수 있다.

 

 

문제 3. 최빈값은 주어진 값 중에서 가장 자주 나오는 값을 의미합니다. 정수 배열 array가 매개변수로 주어질 때, 최빈값을 return 하도록 solution 함수를 완성해보세요. 최빈값이 여러 개면 -1을 return 합니다.

function solution(array) {
  let countMap = new Map(); // 숫자의 등장 횟수를 저장할 맵

  // 배열의 요소들을 순회하며 등장 횟수를 카운트
  for (let num of array) {
    if (countMap.has(num)) {
      countMap.set(num, countMap.get(num) + 1); // 이미 등장한 숫자라면 등장 횟수 증가
    } else {
      countMap.set(num, 1); // 처음 등장하는 숫자라면 등장 횟수 1로 설정
    }
  }

  let maxCount = 0; // 가장 큰 등장 횟수
  let mode = []; // 최빈값을 저장할 배열

  // 맵을 순회하며 최빈값을 찾음
  for (let [num, count] of countMap) {
    if (count > maxCount) {
      maxCount = count;
      mode = [num];
    } else if (count === maxCount) {
      mode.push(num); // 최빈값이 여러 개인 경우 배열에 추가
    }
  }

  return mode.length > 1 ? -1 : mode[0]; // 최빈값이 여러 개면 -1, 그렇지 않으면 최빈값 반환
}

 

먼저, 주어진 배열 array를 순회하며 각 숫자의 등장 횟수를 카운트하기 위해 countMap이라는 Map 객체를 사용.

countMap은 숫자를 키(key)로 가지고, 해당 숫자의 등장 횟수를 값(value)로 가지게 된다.

배열 array를 순회하며 각 숫자에 대해 다음과 같은 작업을 수행합니다:

만약 countMap에 해당 숫자가 이미 존재한다면, 해당 숫자의 등장 횟수를 1 증가시킨다.
만약 countMap에 해당 숫자가 존재하지 않는다면, 해당 숫자를 새로운 키로 추가하고 등장 횟수를 1로 설정한다.
등장 횟수를 카운트한 후, 가장 큰 등장 횟수를 갖는 값(최빈값)을 찾기 위해 countMap을 순회한다. 순회하면서 다음과 같은 작업을 수행한다.

만약 현재 숫자의 등장 횟수가 maxCount(현재까지 확인한 최대 등장 횟수)보다 크다면, maxCount를 현재 숫자의 등장 횟수로 업데이트하고, mode 배열을 현재 숫자로 초기화한다.
만약 현재 숫자의 등장 횟수가 maxCount와 같다면, mode 배열에 현재 숫자를 추가한다. 이렇게 하면 최빈값이 여러 개인 경우 mode 배열에 여러 숫자가 저장된다.
mode 배열의 길이를 확인하여 최빈값이 여러 개인지 판단한다. 만약 mode 배열의 길이가 1보다 크다면, 최빈값이 여러 개이므로 -1을 반환한다. 그렇지 않으면 mode 배열의 첫 번째 요소를 반환하여 최빈값을 반환한다.

 

 

문제 4. 정수 n이 매개변수로 주어질 때, n 이하의 홀수가 오름차순으로 담긴 배열을 return하도록 solution 함수를 완성해주세요.

function solution(n) {
  let answer = [];

  for (let i = 1; i <= n; i++) {
    if (i % 2 === 1) {
      answer.push(i);
    }
  }

  return answer;
}

i가 2로 나누어서 나머지가 1일 때만, 즉 홀수 일 때만 answer 배열에 push(추가) 하도록 함.

 

728x90
반응형

완성 화면

 

 

 

 

코드 보기 / CSS

.reveal > div,
            .reveal > span {
                opacity: 0;
            }
            .reveal.show > div,
            .reveal.show > span {
                animation: opacity 1s linear forwards;
            }
            .reveal {
                position: relative;
            }
            .reveal::before {
                content: '';
                position: absolute;
                left: 0;
                top: 0;
                width: 0;
                height: 100%;
                background-color: #fff;
                z-index: 1;
            }
            .reveal.reveal-TWO::after {
                content: '';
                position: absolute;
                left: 0;
                top: 0;
                width: 0;
                height: 100%;
                z-index: 1;
                background-color: cornflowerblue;
            }
            /* before animation (1개)*/
            .reveal.show::before {
                animation:  reveal 1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-RTL.show::before {
                animation:  reveal-RTL  1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-TTB.show::before {
                animation: reveal-TTB 1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-BTT.show::before {
                animation: reveal-BTT 1s cubic-bezier(0, 0.78, 0.58, 1);
            }
            /*before,after (2개) */
            .reveal.show::after {
                animation:  reveal 1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-RTL.show::after {
                animation:  reveal-RTL  1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-TTB.show::after {
                animation: reveal-TTB 1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            .reveal.reveal-BTT.show::after {
                animation: reveal-BTT 1s 0.5s cubic-bezier(0, 0.78, 0.58, 1);
            }
            /* animation */
            @keyframes opacity {
                0%          {opacity: 0;}
                60%         {opacity: 0;}
                70%         {opacity: 1;}
                100%        {opacity: 1;}
            }
            @keyframes reveal {
                0%      {width: 0;      left: 0;}
                50%     {width: 100%;   left: 0;}
                80%     {width: 100%;   left: 0;}
                100%    {width: 0;      left: 100%;}
            }
            @keyframes reveal-RTL {
                0%      {width: 0;      left: auto;  right: 0;}
                50%     {width: 100%;   left: auto;  right: 0;}
                80%     {width: 100%;   left: auto;  right: 0;}
                100%    {width: 0;      left: auto;  right: 100%;}
            }
            @keyframes reveal-TTB {
                0%   {width: 100%; height: 0;    top: 0;}
                50%  {width: 100%; height: 100%; top: 0;}
                80%  {width: 100%; height: 100%; top: 0;}
                100% {width: 100%; height: 0;    top: 100%;}
            }
            @keyframes reveal-BTT {
                0%   {width: 100%; height: 0;    bottom:0;    top: auto;}
                50%  {width: 100%; height: 100%; bottom:0;    top: auto;}
                80%  {width: 100%; height: 100%; bottom:0;    top: auto;}
                100% {width: 100%; height: 0;    bottom:100%; top: auto;}
            }
            .parallax__item__num,
            .parallax__item__title {
                display: none;
            }

 

 

 

 

코드 보기 / HTML

<div class="parallax__wrap">
           <section id="section1" class="parallax__item">
                <span class="parallax__item__num">01</span>
                <h2 class="parallax__item__title">Section 01</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc split">좋은 책은 우리에게 생각하는 법을 가르쳐준다.</p>
           </section>
           <!--//section 01-->

 

 

 

코드 보기 / JAVASCRIPT

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.11.5/gsap.min.js"></script>
    <script>
        //reveal class 추가 시 -> 자식 요소를 span으로 감싼다.
        document.querySelectorAll("p.reveal").forEach(text => {
            text.innerHTML = `<span>${text.innerHTML}</span>`;
        })

        function scroll() {
            let scrollTop = window.scrollY || window.pageYOffset;

            const reveals = document.querySelectorAll(".reveal");

            reveals.forEach(reveal => {
                let revealOffset = reveal.offsetTop + reveal.parentElement.offsetTop;
                let revealDelay = reveal.dataset.delay;


                // if(scrollTop >= revealOffset - window.innerHeight/2) {
                //     reveal.classList.add("show");
                // }

                if(scrollTop >= revealOffset - window.innerHeight) {
                    if(revealDelay == undefined) {
                        reveal.classList.add("show");
                    } else {
                        setTimeout (() => {
                            reveal.classList.add("show")
                        }, revealDelay);
                    }
                }
            })

            document.querySelector(".scroll span").innerText = Math.round(scrollTop);
            requestAnimationFrame(scroll);

        }
        scroll();

 

document.querySelectorAll("p.reveal").forEach(text => { ... })

: 문서에서 CSS 선택자 p.reveal에 해당하는 모든 요소를 선택하고 각 요소에 대해 반복문을 실행합니다. 선택된 요소들은 "reveal" 클래스를 가지고 있는 p 요소들입니다.

 


text.innerHTML = <span>${text.innerHTML}</span>;

: 선택된 p.reveal 요소의 내용을 <span> 태그로 감싸서 변경합니다. 이를 통해 텍스트를 자식 요소로 갖는 <span> 태그가 추가됩니다.

 


function scroll() { ... }

: scroll 함수를 정의합니다. 이 함수는 스크롤 이벤트를 처리하고, 요소의 나타남을 관리합니다.

 


let scrollTop = window.scrollY || window.pageYOffset;

: scrollTop 변수를 선언하고, 현재 스크롤 위치를 저장합니다. window.scrollY 또는 window.pageYOffset 속성을 사용하여 스크롤 위치를 가져옵니다.

 


const reveals = document.querySelectorAll(".reveal");

: 문서에서 CSS 선택자 .reveal에 해당하는 모든 요소를 선택하여 reveals에 저장합니다. 선택된 요소들은 "reveal" 클래스를 가지고 있는 모든 요소입니다.



reveals.forEach(reveal => { ... })

: reveals에 포함된 각 요소에 대해 반복문을 실행합니다.



let revealOffset = reveal.offsetTop + reveal.parentElement.offsetTop;

: reveal 요소의 세로 위치를 계산하여 revealOffset 변수에 저장합니다. offsetTop 속성은 요소의 부모를 기준으로 한 상대적인 세로 위치를 반환합니다.


let revealDelay = reveal.dataset.delay;

: reveal 요소의 data-delay 속성 값을 가져와서 revealDelay 변수에 저장합니다. data-delay 속성은 요소가 나타나기까지의 지연 시간을 나타냅니다.



if(scrollTop >= revealOffset - window.innerHeight) { ... }

: 스크롤 위치가 revealOffset - window.innerHeight보다 크거나 같을 때 다음 조건을 확인합니다.



if(revealDelay == undefined) { ... } else { ... }

: revealDelay 변수가 정의되어 있는지 확인합니다. 만약 정의되어 있지 않다면 지연 없이 즉시 reveal 요소에 "show" 클래스를 추가합니다. 그렇지 않다면 setTimeout 함수를 사용하여 revealDelay 값만큼의 지연 후에 "show" 클래스를 추가합니다.



document.querySelector(".scroll span").innerText = Math.round(scrollTop);

: 문서에서 CSS 선택자 .scroll span에 해당하는 요소를 선택하고, 해당 요소의 내용을 현재 스크롤 위치인 scrollTop으로 변경합니다.



requestAnimationFrame(scroll);

: 스크롤 이벤트 처리를 위해 requestAnimationFrame 함수를 호출하여 scroll 함수를 반복적으로 실행합니다. 이를 통해 부드러운 스크롤 이벤트 처리가 가능해집니다.

728x90
반응형

 

문제 1. 정수 num1과 num2가 매개변수로 주어질 때, num1을 num2로 나눈 값에 1,000을 곱한 후 정수 부분을 return 하도록 soltuion 함수를 완성해주세요.

A :

function solution(num1, num2) {
    var answer = 0;
    answer = Math.floor((num1/num2) * 1000)
    return answer;
}

num1을 num2로 나눈 값에 : num1/num2

1000을 곱한 후 : (num1/num2) * 1000 

=> num1/num2가 먼저 되어야 하므로 소괄호( )를 사용하여 묶어줌.

정수 부분을 : Math.floor((num1/num2) * 1000) 

=> DAY 1에서 정수 부분을 반환하게 하려면 Math.round가 아닌 Math.floor을 사용하는 게 정답으로 인정되기 때문에 Math.floor 사용함.

return 하도록 : return을 사용해서 answer값이 반환(리턴) 되도록 함.

 

문제 2. 정수 num1과 num2가 매개변수로 주어집니다. 두 수가 같으면 1 다르면 -1을 retrun하도록 solution 함수를 완성해주세요.

A :

function solution(num1, num2) {
    const anwer = 0;
    if(num1 == num2) {
        answer = 1;
    } else {
        answer = -1;
    }
    return answer;
}

두 수가 같으면 1 : if(num1 == num2) {  answer = 1)

=> 수학에서의 같다 표시는 등호(=) 1개지만 javascript에서는 등호 두 개 (==)를 사용하는 것이 서로 같다는 의미임.

num1과 num2가 같을 시 asnwer가 1이 되도록 조건문을 작성함.

 

두 수가 다르면 -1 : else { answer = -1) 

=> if (조건식) { ... } 에는 조건식과 일치할 시의 반환값이 들어가고, else{ ... }는 조건문과 일치하지 않을 시의 반환값이 들어감.

 

 

 

문제 3. 첫 번째 분수의 분자와 분모를 뜻하는 numer1denom1, 두 번째 분수의 분자와 분모를 뜻하는 numer2denom2가 매개변수로 주어집니다. 두 분수를 더한 값을 기약 분수로 나타냈을 때 분자와 분모를 순서대로 담은 배열을 return 하도록 solution 함수를 완성해보세요.

A :

function solution(numer1, denom1, numer2, denom2) {
  var answer = [];

  // 분자와 분모를 더한 결과 계산
  var numerator = numer1 * denom2 + numer2 * denom1;
  var denominator = denom1 * denom2;

  // 분자와 분모의 최대공약수 계산
  var gcd = getGreatestCommonDivisor(numerator, denominator);

  // 기약 분수로 변환하여 배열에 담아 반환
  answer.push(numerator / gcd);
  answer.push(denominator / gcd);

  return answer;
}

// 최대공약수를 구하는 함수
function getGreatestCommonDivisor(a, b) {
  if (b === 0) {
    return a;
  }

  return getGreatestCommonDivisor(b, a % b);
}

 

주어진 두 분수의 분자와 분모를 가지고 덧셈을 수행하여 결과 분자(numerator)와 결과 분모(denominator)를 계산하는 식으로, 분수의 덧셈은 분모를 통분하여 분자끼리 더하고 분모는 그대로 유지하는 것을 의미함.


예를 들어, 첫 번째 분수는 numer1/denom1, 두 번째 분수는 numer2/denom2로 주어졌을 때, 결과 분자는 numer1 * denom2 + numer2 * denom1로 계산하고, 결과 분모는 denom1 * denom2로 계산함.

결과 분자와 결과 분모의 최대공약수(gcd)를 구합니다. 최대공약수는 두 수의 공통된 약수 중 가장 큰 수를 의미함. 

분자와 분모의 최대공약수를 구하는 이유는, 결과 분수를 기약 분수로 만들기 위해서임.

결과 분자와 결과 분모를 최대공약수로 나누어 기약 분수로 변환함. 

이를 위해 numerator / gcd와 denominator / gcd를 계산하여 배열 answer에 순서대로 담음.

최종적으로 기약 분수로 변환된 결과인 answer 배열을 반환함.


 

 

문제 4. 정수 배열 numbers가 매개변수로 주어집니다. numbers의 각 원소에 두배한 원소를 가진 배열을 return하도록 solution 함수를 완성해주세요.

A :

function solution(numbers) {
  let answer = [];

  for (let i = 0; i < numbers.length; i++) {
    let double = numbers[i] * 2;
    answer.push(double);
  }

  return answer;
}

answer = [ ] ; : 빈 배열 answer를 생성함, 이 배열은 결과를 담기 위한 용도로 사용됨.

for(let i = 0; i < numbers.length; i++) { .. } :반복문을 통해 주어진 배열 numbers의 각 원소를 순회함. 반복문에서 현재 원소를 가리키는 변수 i는 0부터 배열의 길이보다 작을 때까지 1씩 증가함.

let doble = numbers[i] * 2  : 각 원소의 두 배 값을 계산하여 변수 double에 저장함.  이를 위해 현재 원소 numbers[i]에 2를 곱함.

anwer.push(doble) : 변수 double에 저장된 값을 배열 answer에 추가함. 이를 위해 answer.push(double)를 사용하여 double 값을 배열의 끝에 추가함.

return answer : 반복문이 종료되면 배열 answer를 반환함. 이 배열은 주어진 배열 numbers의 각 원소를 두 배한 값을 가지고 있음.

728x90
반응형

문제 1. 정수 num1num2가 매개변수 주어집니다. num1과 num2를 곱한 값을 return 하도록 solution 함수를 완성해주세요.

function solution(num1, num2) {
    var answer = 0;
    answer = num1 * num2
    return answer;
    
}

answer를 0으로 초기화 시킨 후, answer 변수에 num1과 num2의 곱이 들어갈 수 있도록 answer = num1+num2 작성.

return으로 answer값을 불러오면 완성.

 

 

문제 2. 정수 num1과 num2가 주어질 때, num1과 num2의 합을 return하도록 soltuion 함수를 완성해주세요.

function solution(num1, num2) {
    var answer = 0;
    answer = num1+num2
    return answer;
}

같은 방법으로 answer를 0으로 초기화, answer에 num1과 num2의 합이 들어갈 수 있도록 answer = num1 + num2 작성.

return으로 answer 값을 return 시킴.

 

 

문제 3. 정수 num1과 num2가 주어질 때, num1에서 num2를 뺀 값을 return하도록 soltuion 함수를 완성해주세요.

function solution(num1, num2) {
    var answer = 0;
    answer = num1 - num2
    return answer;
}

answer에는 num1에서 num2를 뺀 값이 들어가야 하므로 answer = num1 - num2 작성.

answer 값이 return 될 수 있도록 함.

 

 

문제 4. 정수 num1num2가 매개변수로 주어질 때, num1을 num2로 나눈 몫을 return 하도록 solution 함수를 완성해주세요.

function solution(num1, num2) {
    var answer = 0;
    answer = Math.floor(num1/num2)
    return answer;
}

몫을 구하는 식이기 때문에 나누기 연산자 (/)를 사용. 

그냥 num1/num2를 넣고 코드 실행 했더니 틀렸다고 빨갛게 뜨길래  Math.round(반올림) 을 추가 해봤는데 이것도 4개 중 2개가 안 맞는다고 코드 실행 테스트가 빨갛게 뜸.

Math.floor(버림)을 사용했더니 성공.

 

728x90
반응형

 

React 컴포넌트 (함수형)

함수형 컴포넌트는 JavaScript 함수로 작성되며, React 컴포넌트를 반환합니다.
함수형 컴포넌트는 단순하고 간결하며, 최신 버전의 React에서는 훅(Hook)을 사용하여
상태 및 다른 React 기능을 활용할 수 있습니다.

 import React from 'react'
import ReactDom from 'react-dom';

function Hello() {
    return <h1>Hello, fita lux</h1>
}
const element = <Hello />;

ReactDom.render(element, document.getElementById('root'));

export default Hello

 

두 개의 함수형 컴포넌트 사용

Welcome과 App이라는 두 개의 컴포넌트를 사용하여 화면에 여러 개의 welcome과 이름을 렌더링하는 간단한 React를 구현한 것입니다.
각각의 Welcome 컴포넌트는 name 속성을 받아와서 해당 이름을 포함한 welcome을 출력합니다.
App 컴포넌트에서는 Welcome 컴포넌트를 여러 번 사용하여 세 가지 다른 이름에 대한 welcome을 출력합니다.

import React from 'react';
import ReactDom from 'react-dom/client';

function Welcome(props) {                           //Welcome이라는 함수형 컴포넌트는 props를 매개변수로 받아와서 이름(name)을 사용하여 welcome을 반환합니다.
return <h1>Hello, {props.name}</h1>
}

function App (){                                    //App이라는 함수형 컴포넌트는 Welcome 컴포넌트를 세 번 사용하여 세 가지 다른 이름을 가진 welcome을 반환합니다.
return (
    <div>
    <Welcome name = "fita lux" />
    <Welcome name = "Fita Lux" />
    <Welcome name = "FITA LUX" />
    </div>
)
}

const root = ReactDom.createRoot(document.getElementById('root'));
root.render(<App />);

 

 

 

props (속성)

props(속성)는 React 컴포넌트로 전달되는 읽기 전용 데이터이며,
부모 컴포넌트가 자식 컴포넌트에게 데이터를 전달할 때 사용됩니다.
props는 컴포넌트 내부에서 변경할 수 없으며, 컴포넌트의 상태(state)와 다릅니다.


부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달할 때, 부모 컴포넌트에서 전달된 데이터는 자식 컴포넌트의 props 객체로 접근할 수 있습니다.
props 객체는 컴포넌트 함수나 클래스의 인자로 전달되며, 키-값 쌍의 형태로 데이터를 포함합니다.
자식 컴포넌트에서는 props 객체를 사용하여 전달받은 속성에 접근할 수 있습니다.


props는 컴포넌트 간 데이터 흐름을 가능하게 하며, 컴포넌트의 재사용성을 높이는 데 도움을 줍니다.
부모 컴포넌트에서 자식 컴포넌트로 다양한 종류의 데이터를 전달할 수 있으며,
자식 컴포넌트는 props를 활용하여 전달받은 데이터를 화면에 렌더링하거나 다른 작업에 활용할 수 있습니다.

props는 함수의 매개변수 개념으로 생각하면 이해하는 데 도움이 됩니다.

 

import React from 'react';
import ReactDom from 'react-dom/client';

function Hello(props) {
return <h1>Hello, {props.name}</h1>
}

const element = <Hello name = "fita lux" />;

const root = ReactDom.createRoot(document.getElementById('root'));
root.render(element);
728x90
반응형

 

React

React는 사용자 인터페이스(UI)를 구축하기 위한 JavaScript 라이브러리입니다.

Facebook에서 개발한 React는 컴포넌트 기반 접근 방식을 사용하여 웹 및 모바일 애플리케이션의 UI를 구축하는 데 사용됩니다. React는 가상 DOM(Virtual DOM) 개념을 활용하여 효율적인 UI 업데이트를 달성합니다. 가상 DOM은 실제 DOM과 동기화되어 있지 않고, 변경 사항이 발생한 부분만 업데이트합니다. 이를 통해 React는 빠른 성능과 효율성을 제공하며, 대규모 애플리케이션에서도 잘 작동합니다. React는 컴포넌트라는 재사용 가능한 UI 조각들을 작성하고 조합하여 애플리케이션을 구축하는 방식으로 작동합니다. 각 컴포넌트는 자체적으로 상태(state)를 가질 수 있으며, 상태가 변경되면 컴포넌트는 다시 렌더링됩니다. 이러한 컴포넌트 기반 접근 방식은 코드의 재사용성과 유지 보수성을 높여줍니다.

 

 

React 설치

1. Node.js 설치
https://nodejs.org/ko

React를 사용하려면 먼저 Node.js를 설치해야 합니다.
Node.js는 JavaScript 런타임 환경으로, React 애플리케이션을 실행하고 개발하는 데 필요합니다.
Node.js를 설치하면 npm(Node Package Manager)도 함께 설치됩니다.
npm은 React를 비롯한 다양한 자바스크립트 패키지를 관리하는 도구입니다.

2. npx create-react-app react1
Compiled successfully!

    You can now view react1 in the browser.

    Local:            http://localhost:3000
    On Your Network:  http://192.168.25.199:3000

    Note that the development build is not optimized.
    To create a production build, use npm run build.

    webpack compiled successfully

 

 

React 기본 개념

1. HELLO, THE WORLD 출력

 import React from "react";
    import ReactDOM from "react-dom/client";

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(HELLO, THE WORLD);

    //HELLO THE WORLD

 

 

2. JSX

JSX는 JavaScript XML의 약어로, React 애플리케이션에서 UI를 작성하기 위해 사용되는 JavaScript의 확장 문법입니다. JSX는 React 컴포넌트의 구조를 선언적이고 가독성 있게 작성할 수 있도록 도와줍니다.

	 import React from "react";
    import ReactDOM from "react-dom/client";

    const name = "fita lux";
    const hello = <h1>hello {name} </h1>;

    const root  =  ReactDOM.createRoot(document.getElementById('root'));
    root.render(hello);

 

3. 객체와 함수 사용

 function helloName() {
    return name.nick;
    }

    const name  = {
    nick : "fita lux"
    }

    const hello = <h1>Hello, {helloName()};

    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(hello);

 

 

4. 랜더링

    function clock(){
        const element = (
            
                Hello, fitalux
                지금은 {new Date().toLocaleDateString()} 입니다.
            
        );
    
        ReactDOM.render(element, document.getElementById('root'));
    }

    export default clock
    //지금은 23. 05. 08.입니다
728x90
반응형

 

 

1. 다음의 결과값을 작성하시오.

{
    let i = 0;
    let sum = 0;
    
    while(i < 10){
        i = i+1;
        if( i % 3 == 0) sum -= i;
        if( i % 3 == 1) sum += i;
        if( i % 3 == 2) sum *= i 
    }

    console.log(sum) 
}

A : 129


반복문에서 변수 i를 초기값 0으로 설정하고 시작하며,  while 조건문에서 i가 10보다 작을 때까지 반복한다.

이 반복문에서 i는 1부터 10까지의 정수 값을 가진다.


if 조건문에서 i % 3은 i를 3으로 나눈 나머지 값을 나타냅니다. 이 값을 기준으로 다음의 작업을 수행한다.
 i % 3 == 0 : i가 3의 배수인 경우, sum에서 i를 뺍니다.
i % 3 == 1 : i를 3으로 나눈 나머지가 1인 경우, sum에 i를 더한다.
i % 3 == 2 : i를 3으로 나눈 나머지가 2인 경우, sum에 i를 곱한다.

실행 과정을 살펴보면 이렇다.


i = 1일 때, i % 3 == 1이므로 sum에 1을 더한다. (sum = 1)

i = 2일 때, i % 3 == 2이므로 sum에 2를 곱한다. (sum = 2)

i = 3일 때, i % 3 == 0이므로 sum에서 3을 뺀다. (sum = -1)

i = 4일 때, i % 3 == 1이므로 sum에 4를 더한다. (sum = 3)

i = 5일 때, i % 3 == 2이므로 sum에 5를 곱한다. (sum = 15)

i = 6일 때, i % 3 == 0이므로 sum에서 6을 뺀다. (sum = 9)

i = 7일 때, i % 3 == 1이므로 sum에 7을 더한다. (sum = 16)

i = 8일 때, i % 3 == 2이므로 sum에 8을 곱한다. (sum = 128)

i = 9일 때, i % 3 == 0이므로 sum에서 9를 뺀다. (sum = 119)

i = 10일 때, i % 3 == 1이므로 sum에 10을 더한다. (sum =129)

따라서 결과값은 129가 된다.

 


2. 결과값을 작성하시오.

{
    let num = [10, 20, 30, 40, 50];
    let i, max, min;
    max = min = num[0];

    for(i=0; i<5; i++){
        if(num[i] > max) max = num[i];
        if(num[i] < min) min = num[i];
    }

    console.log(max, min);
}

A : 50 10

1. num 배열의 첫번째 요소 num[0]은 10이다. 따라서 변수 max와 min은 모두 10으로 초기화 된다.

2. for 반복문에서 i가 0일 때, num[0]은 10이므로 max와 min은 모두 10으로 유지된다.

3. for 반복문에서 i가 1일 때, num[1]은 20이므로 min은 20으로 대체되지 않고, max는 20으로 대체된다. (if문에서 max는 num[i]값과 현재의 max을 비교해서 큰 값으로 대체되며, min은 현재 저장된 수보다 num[i]의 값이 작을 경우 대체되기 때문)

4. for 반복문에서 i가 2일 때, num[2]는 30이므로 min은 30으로 대체되지 않고, max는 30으로 대체된다.

5. for반복문에서 i가 3일 때, num[3]은 40이므로 min은 40으로 대체되지 않고, max는 40으로 대체된다.

6. for 반복문에서 i가 4일 때, num[4]는 50이므로 min은 50으로 대체되지 않지만 max는 50으로 대체된다.

 

따라서 실행 결과는 50 10이 된다.

 


3.  다음의 결과값을 작성하시오.

{
    function func(begin, diff, n){
        cnt = 1;
        ret = begin;
        while(true){
            cnt += 1;
            ret *= diff;
            if(cnt == n) return ret;
        }
    }

    console.log(func(1, 3, 4));
}

 A : 27

1. func 함수가 호출된다. 인자로 begin=1, diff=3, n=4가 전달된다.


2. cnt 변수와 ret 변수가 각각 1과 begin 값인 1로 초기화된다.


3. while 루프가 시작된다. while(true)는 항상 참이므로 무한 루프가 실행된다.


4. cnt 변수에 1이 더해지고, ret 변수에 diff 값인 3이 곱해진다. 이제 cnt는 2이고, ret은 3이 된다.


5. if 문에서 cnt와 n이 같은지 비교한다. cnt는 2이고, n은 4이므로 아직 같지 않기 때문에 while 루프가 다시 시작된다.


6. cnt 변수에 1이 더해지고, ret 변수에 diff 값인 3이 곱해집니다. 이제 cnt는 3이고, ret은 9가 된다.


7. if 문에서 cnt와 n이 같은지 비교한다. cnt는 3이고, n은 4이므로 아직 같지 않기 때문에 while 루프가 다시 시작된다.


8. cnt 변수에 1이 더해지고, ret 변수에 diff 값인 3이 곱해진다. 이제 cnt는 4이고, ret은 27이 된다.


9. if 문에서 cnt와 n이 같은지 비교한다. cnt는 4이고, n도 4이므로 같다.


10. return 문에서 ret 값을 반환합니다.


따라서 console.log(func(1, 3, 4))의 실행 결과는 27이 된다.


즉, func(1, 3, 4)는 1에서 시작해서 공차가 3인 등차수열을 4번째 항까지 계산한 결과인 27을 반환하는 함수이다.


4. 다음의 결과값을 작성하시오.

{
    let i = 0;
    let sum = 0;
    while(i < 10){
        i = i + 1;
        if(i % 2 == 0){
            continue
        }
        sum += i;
    }

    console.log(sum)
}

A : 25

 

1. i 변수와 sum 변수가 각각 0으로 초기화된다.

2.  while 루프가 시작된다.  i는 1이므로 i % 2 == 0 조건문은 거짓이 된다. 그래서 sum 변수에 i 값인 1이 더해집니다. 따라서 sum은 1이 된다.

3. i 변수에 1이 더해져서 i는 2가 된다.

4. i % 2 == 0 조건문이 참이므로 continue 문이 실행된다. continue 문은 현재 반복을 종료하고, 다음 반복으로 넘어가게 한다. 따라서 sum 변수에는 2가 더해지지 않는다.


5. while 루프가 다시 시작됩니다. 이번에는 i가 3이므로 i % 2 == 0 조건문이 거짓이 된다. 그래서 sum 변수에 i 값인 3이 더해진다. 따라서 sum은 4가 된다.

6. 이후에도 마찬가지로 i가 홀수일 때만 sum에 더해지고, i가 짝수일 때는 continue 문이 실행되어 sum에 더해지지 않는다.
7. i가 10일 때 while 루프가 종료된다. 이때까지 sum에 더해진 값은 1, 3, 5, 7, 9로 총 25이다.


따라서 console.log(sum)의 실행 결과는 25가 된다.

 


5. 다음의 결과값을 작성하시오.

{
    let num = [56, 4, 3, 65, 78];
    let temp;

    for(let i=0; i<1; i++){
        for(let j=0; j<num.length-i-1; j++){
            if(num[j]>num[j+1]){
                temp = num[j];
                num[j] = num[j+1];
                num[j+1] = temp;
            }
        }
    }

    console.log(num)
}

A : [4, 3, 56, 65, 78]

 

첫 번째 for 루프에서 i의 값은 0으로 설정되어 있으므로, 내부 루프가 한 번 실행된다.

내부 루프는 배열의 길이에서 i와 1을 뺀 만큼 반복하므로, 이 경우에는 num.length - 1 만큼 반복한다.

내부 루프에서는 인접한 두 항목을 비교하고, 만약 첫 번째 항목이 두 번째 항목보다 크다면 값을 교환한다.

그러나 첫 번째 for 루프에서 i를 0으로 설정하였기 때문에, 내부 루프는 단 한 번만 실행된다.

이렇게 되면, 배열의 첫 번째 요소와 두 번째 요소만 비교되고 나머지 요소들은 정렬되지 않은 채 그대로 남게 된다.

따라서 [4, 3, 56, 65, 78]이 된다.


6.  다음의 결과값을 작성하시오.

{
    let num = [56, 4, 3, 65, 78];
    let min = 9999;

    for(let i=0; i<10; i++){
        if(min > num[i]){
            min = num[i];
        }
    }

    console.log(min)
}

A : 3

 

먼저, 변수 min을 배열 num의 첫 번째 요소로 초기화한다.

그리고 for 루프를 사용하여 배열 num의 모든 요소를 탐색하면서 min과 현재 요소를 비교한다.

만약 현재 요소가 min보다 작으면 min 값을 현재 요소로 업데이트한다.


for 루프는 배열 num의 2번째 요소부터 마지막 요소까지 탐색한다.

첫 번째 요소는 이미 min에 할당되었기 때문에 탐색할 필요가 없기 때문에 i를 1부터 시작하여 num.length보다 작을 때까지 for 루프를 실행한다.

각 루프에서는 num[i]와 min을 비교하여 num[i]가 더 작으면 min 값을 num[i]로 업데이트한다.

이를 모든 루프에서 반복하면서, min에는 배열 num에서 가장 작은 값인 3이 할당되게 된다.

따라서 console.log(min)을 호출하면 3이 출력됩니다.

 


7. 다음의 결과값을 작성하시오.

{
    let num = [3, 4, 5, 7, 8];
    let temp;

    for(let i=0; i<=3; i++){
        for(let j=i+1; j<=4; j++){
            if(num[i] < num[j]){
                temp = num[i];
                num[i] = num[j];
                num[j] = temp;
            }
        }
    }

    console.log(num);
}

A : [3, 4, 5, 7, 8]

배열 num은 [3, 4, 5, 7, 8]으로 초기화되어 있다. 

따라서 바깥쪽 루프의 첫 번째 반복에서는 i=0일 때 안쪽 루프가 1부터 4까지 반복하게 되고, 배열 num[0]부터 num[4]까지의 요소들끼리 대소관계를 비교한다. 

이때 num[0]은 3이고, num[1]은 4이다.

 3이 4보다 작기 때문에 if문은 건너뛰고, 다음 요소인 num[2]와 비교한다.

3은 5보다 작기 때문에 if문을 건너뛰고, 3은 7보다도 작기 때문에 다시 if문을 건너뛰고, 3은 8보다도 작기 때문에 다시 if문을 건너뛰게 된다.

그러면 바깥쪽 루프의 두 번째 반복에서는 i=1일 때 안쪽 루프가 2부터 4까지 반복하게 되고, num[1]부터 num[4]까지의 요소들끼리 대소관계를 비교한다.

이때 num[1]은 4이고, num[2]는 5이다.

4가 5보다 작지 않기 때문에 if문에 들어가서 num[1]과 num[2]를 교환한다. 그러면 배열 num은 [3, 5, 4, 7, 8]이 됩니다.

이제 바깥쪽 루프의 세 번째 반복에서는 i=2일 때 안쪽 루프가 3부터 4까지 반복하게 되고, num[2]부터 num[4]까지의 요소들끼리 대소관계를 비교한다.

이때 num[2]는 4이고, num[3]은 7이다.

num[2]와 num[3]의 값을 비교하여 큰 값을 앞으로 이동시킨다.

따라서 num[2]와 num[3]의 위치가 바뀌어 [3, 4, 7, 5, 8]이 된 후, 안쪽 루프의 두 번째 반복에서 j는 4가 되고, num[4]는 8이다.

따라서 num[3]와 num[4]의 값을 비교하여 큰 값을 앞으로 이동시킨다.

따라서 num[3]와 num[4]의 위치가 바뀌어 최종적으로 [8, 7, 5, 4, 3]이 되게 되는 것이다.

 


8. 다음의 결과값을 작성하시오.

{
    let num = [1,2,3,4,5];
    let sum = 1;
    
    function func(arr){
        for(let i=0; i<arr.length; i++) {
            sum += arr[i];
        }

        for(let i=0; i<arr.length; i++){
            if(arr[i] % 2 == 0) sum -= arr[i];
        }
        return sum;            
    }

    console.log(func(num));
}

A : 10

 

num이라는 배열과 sum이라는 변수를 정의한다.

sum은 초기값으로 1을 가지고 있다.


func이라는 함수를 정의한다.

이 함수는 배열을 입력받아 배열의 합에서 짝수 요소의 값을 빼서 그 결과값을 반한다.


func 함수 내에서는 for 반복문을 사용하여 배열의 모든 요소들을 순회하면서 sum 변수에 더해준다.


그 후, 다시 for 반복문을 사용하여 배열의 모든 요소들을 순회하면서, 각 요소가 짝수인지 검사한다.

 만약 짝수라면, sum 변수에서 해당 요소의 값을 빼준다.


최종적으로 func(num) 함수를 호출하여 반환된 값을 출력한다.


따라서, num 배열의 요소들의 합은 1+2+3+4+5 = 15이고, 짝수인 요소는 2와 4이므로, 이들의 값을 sum 변수에서 빼주면 최종적으로 15-2-4 = 9가 된다. 

그러나 함수 내에서 sum 변수를 초기값으로 1을 가지고 시작했기 때문에, 최종 결과값에 1을 더해준 10이 출력되게 된다.

 


9. 다음의 결과값을 작성하시오.

{
    let num = ["0","1","2","3","4","5","6","7","8","9"];
    let sum = 100;

    for(let i=0; i<num.length; i++){
        num[i] = i+1;
    }
    for(let i=0; i<num.length; i++){
        if(i % 3 == 1){
            sum -= num[i];
        }
    }
    console.log(sum)
}

A: 85

num 배열의 요소들을 문자열에서 숫자로 변환한다.

즉, num 배열은 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]이 된다.

그 후, for 루프를 사용해서 배열의 각 요소에 접근하면서, 해당 요소의 인덱스가 3의 배수가 아닌 경우 sum 변수에서 해당 요소의 값을 뺀다.

인덱스를 0부터 시작하기 때문에, i % 3이 1인 경우는 1, 4, 7번째 요소이다. 이 경우 해당 요소의 값을 sum 변수에서 뺀다.

따라서, sum의 초기값이 100이고, 1, 4, 7번째 요소 (2, 5, 8)의 값의 합인 15를 sum에서 뺀 값이 결과값으로 출력된다.

따라서 결과값은 85가 된다.

 


10. 다음의 결과값을 작성하시오.

{
    let arr = [1,2,3,4,5];
    let temp = [1,2,4,6,8];
    let idx = 0;
    let flag = true;

    for(let i=0; i<arr.length; i++){
        flag = false;
        if(arr[i] == temp[i]){
            flag = true;
        }
        if(!flag){
            temp[idx++] = arr[i];
            console.log(temp[idx]);
        }
    }
}

A : 2 4 6

이 코드는 arr 배열과 temp 배열을 비교하여, 같은 인덱스 위치에 있는 값이 같으면 temp에 그대로 유지하고, 다르면 temp에 arr의 값을 복사하는 코드이다.

처음에 idx는 0으로 초기화되어 있습니다. 

그리고 flag는 true로 초기화되어 있습니다. 

그 후 arr 배열을 순회하면서, 같은 위치의 값이 다른 경우, temp 배열의 해당 위치에 arr의 값을 복사하고, idx를 1 증가시키고, temp[idx] 값을 출력합니다.

처음에 arr과 temp 배열이 같으므로, flag은 true이고, temp[0]의 값은 1입니다. 

그리고 arr[2]와 temp[2]의 값이 다르므로, temp[1]의 값을 arr[2]의 값인 3으로 업데이트합니다. 

이후 idx는 2가 되어 있습니다.

그리고 arr[3]과 temp[3]의 값이 다르므로, temp[2]의 값을 arr[3]의 값인 4로 업데이트합니다. 

이후 idx는 3이 되어 있습니다.

마지막으로 arr[4]와 temp[4]의 값이 다르므로, temp[3]의 값을 arr[4]의 값인 5로 업데이트합니다. 

이후 idx는 4가 되어 있습니다.

따라서 출력되는 값은 temp[1], temp[2], temp[3]의 값인 2, 4, 6이 됩니다.


11.다음은 최소값을 출력하는 예제이다. 밑줄에 알맞은 표현을 작성하시오.

    let num = [30,40,24,80,99];
    let min = 9999;

    for(let i=0; i<5; i++){
        if(min > num[i]){
            min = ________;
        }
    }

    console.log("최소값 : ", min);
}

A : num[i]

반복문을 실행하면서 배열 num에서 가장 작은 값을 찾기 위해, 현재까지 찾은 최소값 min과 비교하면서 더 작은 값을 찾습니다. 반복문이 한번 실행될 때마다 num 배열의 다음 요소를 확인하고, 해당 요소가 현재까지 찾은 최소값보다 작다면 min 값을 해당 요소의 값으로 갱신합니다. 이렇게 반복문을 실행하면서 min 변수에는 num 배열의 가장 작은 값이 할당됩니다.

따라서 min과 num[i]를 비교해야 한다.


12. 다음의 결과값을 작성하시오.

{
    let s1 = "Remember november";
    let s2 = s1.slice(0, 3) + s1.slice(12, 16);
    let s3 = "r AND you";

    console.log(s2 + s3)
}

A : Remember And you 

 

1. 문자열 "Remember november"를 변수 s1에 저장합니다.
2. s1의 첫 세 글자와 12번째부터 15번째까지의 문자열을 합쳐 새로운 문자열 s2를 만듭니다. 이 결과 s2는 3. "Rembernovem"이 됩니다.
4. 문자열 "r AND you"를 변수 s3에 저장합니다.
5. s2와 s3를 합쳐서 출력합니다. 따라서 출력 결과는 "Rembernovemr AND you"가 됩니다.

 


13. 다음의 결과값을 작성하시오.

{
    let x = 1;

    console.log( !(x>0) );
    console.log( x>0 || x<4 );
    console.log( x<<2 );
    console.log( x&2 );
    console.log( x&3 );
}

A : false true 4 0 1

!(x>0) : x가 0 이하인지 검사하여 그 결과의 반대인 false가 출력됩니다.


x>0 || x<4 : x가 0보다 크거나 4보다 작은지 검사하여 그 결과인 true가 출력됩니다. x의 값이 1이기 때문에 true가 됩니다.


x<<2 : x의 비트를 왼쪽으로 2칸 이동시킨 결과인 4가 출력됩니다. 1의 비트를 왼쪽으로 2칸 이동시키면 100이 되기 때문입니다.


x&2 : x와 2의 비트 AND 연산을 수행한 결과인 0이 출력됩니다. 1의 비트와 2의 비트를 AND 연산하면 모두 1인 비트는 없기 때문입니다.


x&3 : x와 3의 비트 AND 연산을 수행한 결과인 1이 출력됩니다. 1의 비트와 3의 비트를 AND 연산하면 둘 다 1인 비트가 있기 때문입니다.

 


14. 다음의 결과값을 작성하시오.

{
    let num = 0;
    let i = 1;

    while(i<=50){
        if(i%7 == 0){
            num += i;
        }
        i++;
    }
    console.log(num)
}

A : 196

반복문을 통해 1부터 50까지의 수를 순회하며, 각 수를 7로 나누어 떨어지는지 확인합니다. 나머지 연산자 %를 이용해 i를 7로 나누었을 때 나머지가 0이면 i는 7의 배수입니다. 이 경우, num 변수에 i를 더합니다.

따라서 7, 14, 21, 28, 35, 42, 49의 합인 196이 결과값으로 나오게 됩니다.

 


15. 다음의 결과값을 작성하시오.

{
    let result = 0;
    for(let i=1; i<999; i++){
        if(i%3==0 && i%2 !=0){
            result = i;
        }
    }
    console.log(result);
}

A : 993

처음에 result는 0으로 초기화됩니다. 

그리고 반복문에서 i가 3의 배수이면서 2의 배수가 아니라면, result에 i를 할당합니다. 

이렇게 하면, 반복문이 종료될 때까지 result는 가장 큰 3의 배수이면서 2의 배수가 아닌 값이 됩니다.

반복문에서 마지막으로 i 값이 998일 때, result에는 993이 할당됩니다. 

이는 3의 배수이면서 2의 배수가 아닌 가장 큰 값이기 때문입니다. 

따라서 console.log(result)를 실행하면 993이 출력됩니다.


16. 다음의 결과값을 작성하시오.

{
    function func(num){
        if(num <= 1){
            return 1;
        } else {
            return num + func(num - 2);
        }
    }
    function main(){
        let num = 5;
        console.log(func(num))
    }
    main();
}

 A : 9

함수 func는 인자 num이 1 이하일 때는 1을 반환하고, 그렇지 않을 경우 num에 2를 뺀 값과 num을 더한 값을 반환합니다.

이 함수가 호출되면, main 함수에서 인자 num으로 5를 넘겨줍니다. 그러면 func(5)가 호출됩니다.

func(5)는 5 + func(3)을 반환합니다. 여기서 func(3)은 3 + func(1)을 반환합니다. func(1)은 1을 반환하므로, func(3)은 4를 반환합니다. 그러므로 func(5)는 5 + 4인 9를 반환합니다.

따라서 console.log(func(num))에서 출력되는 값은 9입니다.


17. 다음의 결과값을 작성하시오.

{
    let a=1, b=1, num;

    for(let i=0; i<6; i++){
        num = a + b;
        a = b;
        b = num;
    }
    console.log(num)
}

A : 21

피보나치 수열의 7번째 값을 출력하는 코드입니다. 

피보나치 수열은 0과 1로 시작하며, 이전 두 항의 합이 다음 항이 되는 수열입니다.

따라서, 처음에 a와 b는 각각 1로 초기화 되며, for loop에서는 i가 0일 때부터 5일 때까지 num에 a와 b를 더한 값을 할당하고, a와 b를 각각 b와 num으로 업데이트합니다.

i = 0: num = 2, a = 1, b = 2

i = 1: num = 3, a = 2, b = 3


i = 2: num = 5, a = 3, b = 5


i = 3: num = 8, a = 5, b = 8


i = 4: num = 13, a = 8, b = 13


i = 5: num = 21, a = 13, b = 21


따라서, 마지막으로 num에 할당된 값인 21이 출력됩니다.


18. 다음의 결과값을 작성하시오.

{
    function func(num1, num2){
        let res = 1;
        for(let i=0; i<num2; i++){
            res *= num1;
        }
        return res;
    }
    let res = func(2, 10);

    console.log(res)
}

A : 1024

함수 func는 인자로 num1과 num2를 받습니다. num1은 밑(base)이 되는 수이고, num2는 지수(exponent)입니다. 함수 내에서 for문을 사용하여 res를 num2번 곱하게 됩니다.

따라서 func(2, 10)을 호출하면, 2의 10승인 1024를 계산하고 이를 변수 res에 할당합니다. 마지막으로 console.log(res)를 호출하면, 1024가 출력됩니다.



19. 다음의 결과값을 작성하시오.

{
    let a = func(4);

    for(let i=0; i<a.length; i++){
        console.log(a[i]);
    }

    function func(n){
        let num = [];
        for(let i=0; i<n; i++){
            num[i] = i;
        }
        return num;
    }
}

A : 0 1 2 3

주어진 코드에서 `func` 함수가 먼저 호출되는데, 이 함수는 인자로 숫자 `n`을 받아 0부터 n-1까지의 숫자를 배열에 담아 리턴합니다. 

`func(4)`를 호출하면 `[0, 1, 2, 3]`을 반환합니다.

그 다음에는 `a` 변수가 이 함수의 반환값으로 할당되는데, 따라서 `a` 변수는 `[0, 1, 2, 3]`이 됩니다.

마지막으로 `a` 배열의 요소들을 순서대로 출력하는 반복문이 실행됩니다. 

따라서 `0`, `1`, `2`, `3`이 차례로 출력되게 됩니다. 


20.  다음의 결과값을 작성하시오.

{
    let a = 3, b = 4, c = 3, d = 5;

    if( (a == 2 | a == c) & !(c > d) & (1 == b ^ c != d) ){
        a = b + c;
        if( 7 == b ^ c != a ){
            console.log(a);
        } else {
            console.log(b);
        }
    } else {
        console.log(a + b)
    }
}

A : 7

첫 번째 조건문에서는 a가 2이거나 c와 같은지 확인하고, 두 번째 조건문에서는 c가 d보다 크지 않은지 확인하고, 세 번째 조건문에서는 b와 c가 같지 않거나 c와 d가 같은지 확인합니다. 각 조건이 모두 참이면 첫 번째 if문의 코드 블록이 실행됩니다.

먼저 a에 b와 c의 합을 할당하고, 그 다음에 다시 한 번 조건문을 실행합니다. 이번에는 7과 b, c, a 중 하나와 같지 않은 것을 찾습니다. 여기서는 7과 a와 같지 않은 것을 찾습니다. 만약 이 조건이 참이면 a를 출력하고, 아니면 b를 출력합니다.

주어진 코드에서 a, b, c, d의 값에 따라 조건문이 참이 될 수도 있고 아닐 수도 있습니다. 하지만 조건문이 참이 된다면, a에 b와 c의 합인 7이 할당되고, 다시 조건문이 실행됩니다. 이번에는 7과 a가 같지 않으므로 a가 출력됩니다. 따라서 결과값은 7이 됩니다.


 

 

MYSQL  / 정보처리 기능사

01. SQL문의 빈칸을 완성하시오.

[학생] 테이블에서 전화번호가 NULL 값이 아닌 학생명을 모두 검색하기 위한 SQL문의 작성하시오.

SELECT 학생명 FROM 학생 WHERE 전화번호 _____;

 A : IS NOT NULL

(NULL 값이 아니기(NOT) 때문에 NULL 값이 아니다 라는 뜻의 IS NOT NULL)

 

02. SQL문의 빈칸을 완성하시오.

사용자 "HWANG"에게 테이블을 생성할 수 있는 권한을 부여하기 위한 SQL문을 작성하시오.

_____ CREATE TABLE _____ HWANG

A : GRANT to

(권한을 부여하는 명령어는 GRANT to이다)

 

03. SQL문의 빈칸을 완성하시오.

[성적] 테이블에서 점수가 90점 이상 95점 이하인 '컴퓨터공학과' 학생의 정보를 검색한 SQL문을 작성하시오.

SELECT * FROM 성적 WHERE (점수 _____ 90 _____ 95) AND 학과 = '컴퓨터공학과';

A : BETWEEN, AND

(90점 이상 95점 이하 즉, 90~95 사이에 있기 때문에 BETWEEN 사용)

 

04. SQL문의 빈칸을 완성하시오.

[성적] 테이블에서 점수가 높은 학생 순으로 이름(Name)과 점수(Score)가 출력되도록 SQL문을 작성하시오.

SELECT Name, Score FROM 성적 _____ BY Score _____

A : ORDER DESC

(정렬하도록 하는 구문은 ORDER이며, 최고점에서 최하점 즉, 내림차순으로 정렬하기 위해 DESC 사용)

 

05. SQL문의 빈칸을 완성하시오.

[학생] 테이블에서 '안중근' 학생의 점수를 95점으로 갱신하는 SQL문을 작성하시오.

_____ 학생 _____ 점수 = 95 WHERE 이름 = "안중근";

A : UPDATE SET

(정보를 갱신(업데이트) 하기 때문에 UPDATE SET 사용)

 

06. SQL문의 빈칸을 완성하시오.

[학생] 테이블에서 '주소' 속성을 추가하는 SQL문을 작성하시오.

_____ TABLE 학생 _____ 주소 VARCHAR(20)

A : ALTER ADD

(테이블에 속성을 추가시 ALTER와 함께 추가하다인 ADD를 사용)

 

07. SQL문의 빈칸을 완성하시오.

[학생] 테이블에서 '이'로 시작하는 학생들의 학번을 검색하여 학년이 높은 학생 순으로 출력하는 SQL문을 작성하시오.

SELECT 학번 FROM 학생 WHERE 이름 = "_____" ORDER BY 학년 _____

A : 이%, DESC

('이'로 시작하기 때문에 이%를, 내림차순이기 때문에 DESC를 사용)

 

08. SQL문의 빈칸을 완성하시오.

[학생정보] 테이블과 [학과정보] 테이블을 결합하여 학생명과 학과명을 출력하는 SQL문을 작성하시오.

SELECT A.이름, B.학과명 FROM 학생정보 A _____ 학과정보 B ON A.학과 = B.학과 

A : JOIN

(테이블 병합 시에는 JOIN을 사용)

 

09. SQL문의 빈칸을 완성하시오.

[Exam] 테이블의 모든 레코드를 지우기 위한 명령어 SQL문을 작성하시오.

_____ from Exam

A : DELETE 

(모든 레코드 지우기(DELETE)이기 때문에 DELETE 사용)

 

10. SQL문의 빈칸을 완성하시오.

[Exam] 테이블의 구조를 변화시키며 삭제하기 위한 명령어 SQL문을 작성하시오.

_____ from Exam

A : DROP

(테이블을 완전 삭제 시키기 위해서 DROP을 사용)

 

 


01. 다음 빈칸의 내용을 작성하시오.

( ① ) : 물리(Physics) 계층에서 데이터 전송을 하기 위해 사용하는 데이터 단위
( ② ) : 네트워크(Network) 계층에서 데이터 전송을 하기 위해 사용하는 데이터 단위

A : bit, 패킷

 

02. 다음이 설명하는 용어를 작성하시오.

릴레이션에서 하나의 속성이 취할 수 있는 동일한 타입의 원자값들의 집합을 의미하는 것을 무엇이라 하는지 쓰시오.

A : 도메인

 

03. 다음이 설명하는 용어를 작성하시오.

모든 창을 최소화하여 바탕화면을 보기 위한 윈도우 단축키는 무엇인지 작성하시오.

A : 윈도우 키 + D, 윈도우 키 + M

 

04. 다음이 설명하는 용어를 작성하시오.

한 릴레이션 내에 있는 후보키 중에 하나 이상의 속성들의 집합으로, 구성된 키의 모든 튜플에 대해 유일성(Unique)은 만족하지만, 최소성(Minimality)은 만족하지 못하는 것이 무엇인지 작성하시오.

A : 슈퍼키 (SUPER KEY)

 

05. 다음이 설명하는 용어를 작성하시오.

데이터베이스에서 릴에이션 내의 각 행을 레코드라고 하며, 관련 테이블에서 행한 수치 이상으로 혼합된 자료 요소를 의미, 각 개체들의 각각의 정보를 표현하는 것이 무엇인지 작성하시오.

A : 튜플

 

06. 다음이 설명하는 용어를 작성하시오.

CPU(중앙처리장치)와 입출력 장치 간의 속도 차이를 완화하기 위해 사용하는 시스템으로, 대표적으로 프린터에서 사용하는 기능은 무엇인지 쓰시오.

A : 스풀링 ( or 스풀)

(흔히 문서를 프린트 하면서 인터넷 서핑을 하거나 하는 일을 스풀링이라고 함)

 

07. 다음이 설명하는 용어를 작성하시오.

전기적인 연결, 물리적 설계, 데이터 전송 등에 이용되는 OSI 계층은 무엇인지 작성하시오.

A : 물리계층

 

08. 다음이 설명하는 용어를 작성하시오.

릴레이션 내의 데이터를 변경하거나 삭제할 때, 다른 개체가 해당 개체를 참조하고 있을 경우 변경 및 삭제를 취소하는 명령어는 무엇인지 쓰시오.

A : RESTRICT

 

09. 다음이 설명하는 용어를 작성하시오.

네트워크상에서 IP주소를 물리적 주소(MAC)로 대응시키기 위해 사용되는 프로토콜이다.
IP address를 LAN 카드의 물리적 주소인 MAC 주소로 변환한다.

A : ARP

 

10. 다음이 설명하는 용어를 작성하시오.

하나의 작업을 수행하기 위해 필요한 연산들의 집합이다.
데이터베이스에서 논리적인 작업의 단위를 의미한다.
원자성, 일관성, 독립성, 지속성 등의 특징을 있다.

A : 트랜잭션

728x90
반응형

 

완성 화면

 

 

 

 

코드 보기 / CSS

* {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'ReciaSerifDisplay';
        }

        body {
            height: 20000px;
            background: #102a46;
        }

        .scrollTop {
            position: fixed;
            left: 10px;
            top: 10px;
            z-index: 1000;
            width: 40px;
            height: 40px;
            background-color: rgba(0, 0, 0, 0.5);
            text-align: center;
            font-size: 14px;
            line-height: 40px;
            color: #fff;
        }

        .fixed {
            position: fixed;
            left: 50%;
            top: 50%;
            transform: translate(-50%, -50%);
            z-index: 1;
        }

        .s1-text1 {
            font-size: 30vw;
            color: #fff;
            text-align: center;
        }

        .s1-text2 {
            font-size: 0vw;
            line-height: 2;
            padding-top: 8vw;
            color: #fff;
            text-align: center;
        }

        .s1-text3 {
            font-size: 0vw;
            line-height: 2;
            padding-top: 8vw;
            color: #fff;
            text-align: center;
        }

        .s1-text4 {
            font-size: 0vw;
            line-height: 2;
            padding-top: 8vw;
            color: #fff;
            text-align: center;
        }

        .s1-img1 {
            width: 200vw;
            height: 100vh;
        }

        .s1-img1>div:nth-child(1) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(2) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(3) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(4) {
            padding: 0 0vh;
        }

        .s1-img1>div:nth-child(5) {
            padding: 0 0vh;
        }

        .s1-img1>div {
            height: 20vh;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }

        .s1-img1>div>div {
            width: 19vh;
            height: 19vh;
            background-position: center;
            background-size: cover;
            background-repeat: no-repeat;
            border: 5px solid #000;
        }

        .s1-img1-1>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?moon);
        }

        .s1-img1-1>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?moonlight);
        }

        .s1-img1-2>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?mikyway);
        }

        .s1-img1-2>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?galaxy);
        }

        .s1-img1-3>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?Moon);
        }

        .s1-img1-3>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?Moonlight);
        }

        .s1-img1-4>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?Milkyway);
        }

        .s1-img1-4>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?Galaxy);
        }

        .s1-img1-5>div:first-child {
            background-image: url(https://source.unsplash.com/500x500?sky);
        }

        .s1-img1-5>div:last-child {
            background-image: url(https://source.unsplash.com/500x500?Moonsky);
        }

 

scrollTop

: scrollTop 요소에 대한 스타일을 정의한다.

position: fixed는 요소가 고정된 위치에 있음을 나타내며, left: 10px와 top: 10px는 요소가 뷰포트에서 왼쪽과 위쪽에서 10픽셀 떨어져 있음을 나타낸다.z-index: 1000은 요소가 다른 요소 위에 놓이는 우선 순위를 나타내며, 숫자가 높을수록 요소가 위에 나타난다.width: 40px와 height: 40px는 요소의 너비와 높이를 40픽셀로 설정한다.
background-color: rgba(0, 0, 0, 0.5)는 요소의 배경색상을 반투명한 검정색으로 설정한다.
text-align: center는 요소 내부의 텍스트를 중앙 정렬한다.
font-size: 14px는 요소 내부의 글꼴 크기를 14픽셀로 설정한다.
line-height: 40px는 요소 내부의 줄 높이를 40픽셀로 설정한다.
color: #fff는 요소 내부의 글자색을 흰색으로 설정한다.

 


.fixed

: fixed 클래스의 요소에 대한 스타일을 정의한다.
position: fixed는 요소가 고정된 위치에 있음을 나타내며, left: 50%와 top: 50%는 요소가 뷰포트에서 가로와 세로로 각각 중앙에 위치함을 나타낸다.
transform: translate(-50%, -50%)는 요소를 가운데 정렬하기 위해 위치를 조정하는 데 사용된다.
z-index: 1은 요소가 다른 요소 위에 놓이는 우선 순위를 나타내며, 숫자가 높을수록 요소가 위에 나타낸다.

 

.s1-text1~4

: 스타일링이 적용되는 요소의 클래스명이 .s1-text1~4이다.
font-size: 30vw는 요소 내부의 글꼴 크기를 viewport width(뷰포트 너비)의 30%로 설정한다.
color: #fff는 요소 내부의 글자색을 흰색으로 설정한다.
text-align: center는 요소 내부의 텍스트를 중앙 정렬한다.

 

.s1-img1

: 스타일링이 적용되는 요소의 클래스명이 .s1-img1 이다.
width: 200vw는 요소의 너비를 viewport width(뷰포트 너비)의 200%로 설정한다. (즉, 화면보다 2배 넓다.)
height: 100vh는 요소의 높이를 viewport height(뷰포트 높이)의 100%로 설정한다. (즉, 화면 전체를 차지한다.)

 

.s1-img1>div:nth-child(1~5)

:.s1-img1 요소의 첫 번째 자식부터 다섯번째 자식 요소에 대한 스타일을 정의한다.
padding: 0 0vh는 요소의 상하좌우 여백을 모두 0으로 설정한다. (즉, 여백이 없다.)

 

 

 

코드 보기 / HTML

    <div class="scrollTop"></div>
    <section id="section1">
        <div class="s1-text1 fixed" data-0="font-size: 0vw; opacity:1" data-1000="font-size: 30vw; opacity:1"
            data-2000="font-size: 0vw; opacity:1" data-3000="font-size: 0vw; opacity:0">HELLO
        </div>

        <div class="s1-text2 fixed" data-2500="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);"
            data-3000="font-size: 30vw; transform: translate(-50%, -50%) rotate(1080deg);"
            data-8500="font-size: 0vw;">MOON
        </div>
        
        <div class="s1-img1 fixed" 
        data-3500="width: 200vw;" 
        data-4000="width: 20vw;" 
        data-5000="width: 90vw;">
            
            <div class="s1-img1-1" data-3500="transform: rotate(270deg);" data-5000="transform: rotate(0deg);">
                <div></div>
                <div></div>
            </div>

            <div class="s1-img1-2" 
                data-4000="margin:0 auto; width: 200vw;" 
                data-5000="width: 20vw; transform: rotate(360deg);"
                data-6000="width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-5000="display: block;"></div>
                <div data-3500="display: none;" data-5000="display: block;"></div>
            </div>

            <div class="s1-img1-3" data-6000="width: 20vw; transform: rotate(360deg);"
                data-7000="margin:0 auto; width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-6000="display: block;"></div>
                <div data-3500="display: none;" data-6000="display: block;"></div>
            </div>

            <div class="s1-img1-4" 
                data-7000="margin:0 auto;  width: 20vw; transform: rotate(360deg);"
                data-8000="width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-7000="display: block;"></div>
                <div data-3500="display: none;" data-7000="display: block;"></div>
            </div>

            <div class="s1-img1-5" 
                data-8000="margin:0 auto; width: 20vw; transform: rotate(360deg);"
                data-9000="width: 200vw; transform: rotate(0deg); width: 90vw;">
                <div data-3500="display: none;" data-8000="display: block;"></div>
                <div data-3500="display: none;" data-8000="display: block;"></div>
            </div> 

            <div class="s1-text3 fixed" data-8500="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);"
            data-10000="font-size: 10vw; transform: translate(-50%, -50%) rotate(1080deg);"
            data-11000="font-size: 0vw;"
            > A SKY FULL OF STARS
            </div>

            <div class="s1-text4 fixed" data-11000="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);"
            data-12000="font-size: 3vw; transform: translate(-50%, -50%) rotate(1080deg);"> 
            The moon shows us itself without changing, <br> providing us with an opportunity to search for ourselves. <br>- Hiroshi Matsumoto.
            </div>
        </div>
    </section>

 

.s1-text1 fixed 


data-0="font-size: 0vw; opacity:1" : 해당 요소가 초기에 표시될 때의 스타일을 정의한다.  글꼴 크기는 0vw (즉 보이지 않음), 투명도는 1이다.


data-1000="font-size: 30vw; opacity:1"는 스크롤을 1000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 30vw(뷰포트의 30%), 투명도는 1이다.


data-2000="font-size: 0vw; opacity:1"는 스크롤을 2000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw, 투명도는 1이다.


data-3000="font-size: 0vw; opacity:0"는 스크롤을 3000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw, 투명도는 0이다. (요소가 화면에서 사라집니다.)
HELLO는 요소 내부의 텍스트이다.

 

 

s1-text2 fixed 


data-2500="font-size: 0vw; transform: translate(-50%, -50%) rotate(0deg);": 스크롤을 2500px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw, 요소의 위치를 중앙으로 이동시키는 transform: translate(-50%, -50%), 요소의 회전 각도를 0도로 설정하는 rotate(0deg)을 적용한다.


data-3000="font-size: 30vw; transform: translate(-50%, -50%) rotate(1080deg);": 스크롤을 3000px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 30vw, 요소의 위치를 중앙으로 이동시키는 transform: translate(-50%, -50%)를 사용하여 요소를 가운데 정렬하기 위해 위치를 조정하는 데 사용된다.

data-8500="font-size: 0vw; : 스크롤을 8500px만큼 내렸을 때의 스타일을 정의한다. 글꼴 크기는 0vw으로 점점 작아지면서 요소의 텍스트가 사라지게 하는 역할을 한다.

 

 

.s1-img1 

: s1-img1이라는 선택자를 가진 div 박스 안의 모든 요소에 같은 효과를 주기 위해  CSS 선택자를 사용한다.

fixed 클래스는 이미지가 화면에 고정되도록 한다.

data-3500, data-4000, data-5000 속성은 이미지 요소가 화면에서 어떻게 변화할지를 정의한다.

data-3500은 이미지 요소가 처음에 화면에 나타나면서(화면의 3500px인 위치) 너비가 200vw로 설정된다.

data-4000은 스크롤이 4000px에 도달했을 때 이미지 요소의 너비가 20vw로 설정된다는 것을 의미한다.

data-5000은 스크롤이 5000px에 도달했을 때 이미지 요소의 너비가 90vw로 설정됩니다.

 

 

.s1-img1-1

: .s1-img1-1 클래스를 가진  요소에 대한 애니메이션을 설정하기 위해 .s1-img1-1 CSS 선택자를 사용한다.

data-3500은 이미지 요소가 처음에 화면에 나타날 때, .s1-img1-1 클래스의 회전이 270deg로 설정된다.

data-5000은 스크롤이 5000px에 도달했을 때 이미지 요소의 회전이 0deg로 설정된다.

이렇게 함으로써 이미지 요소의 자식 요소도 화면에 나타나면서 회전하게 된다.



.s1-img1-2~5

: 클래스를 사용하여 이미지 요소의 자식 요소를 정의한다.

data-nnnn 속성은 이미지 요소가 화면의 픽셀 값이 data 위에 오는 nnnn과 일치할 때 어떤 CSS 속성을 줄 지 설정하는 구문이다.

가로 길이(너비)가 200vw나 20vw, 90vw로 설정된다는 의미이다. 

200vw는 화면 밖으로 요소가 나가 보이지 않으며 20vw는 너비가 줄어들면서 요소들이 서로 가까워진다. 90vw는 화면의 양측에 요소가 위치하도록 한다.

margin: 0 auto는 이미지가 수평 중앙에 위치하도록 한다. 

tranform: rotare(nnn deg)는 일치하는 nnn값만큼 회전한다는 것을 의미한다.

 

data-3500 속성은 첫 번째와 두 번째 <div> 요소가 스크롤값이 3500px이 될 때부터  화면에서 나타나지 않도록 설정한다.

data-n000 속성은 그 값에 해당하는 픽셀 값이 되었을 때 첫 번째와 두 번째 <div> 요소가 화면에 나타나도록 display: block으로 설정한다.

 

 

 

 

 

코드 보기 / JAVASCRIPT

<script src="https://cdnjs.cloudflare.com/ajax/libs/skrollr/0.6.30/skrollr.min.js"></script>
    <script type="text/javascript">
        let s = skrollr.init();

        window.addEventListener("scroll", () => {
            let scrollTop = window.pageYOffset || window.scrollY

            document.querySelector(".scrollTop").innerText = parseInt(scrollTop);

        });

 

skrollr 라이브러리를 script src을 통해 로드하고 초기화한다.

skrollr은 스크롤 이벤트를 이용하여 웹사이트의 요소를 애니메이션화시키는 자바스크립트 라이브러리이다.


그 다음, window 객체에 scroll 이벤트 리스너를 등록한다. 

스크롤 이벤트가 발생하면 이벤트 콜백 함수가 실행됩니다.

이벤트 콜백 함수는 window.pageYOffset 또는 window.scrollY 프로퍼티를 사용하여 현재 스크롤 위치를 가져와서, document.querySelector(".scrollTop")를 사용하여 .scrollTop 클래스를 가진 요소를 찾고, 그 요소의 innerText 값을 현재 스크롤 위치로 설정한다.

따라서, 스크롤을 할 때마다 페이지의 위쪽에서 현재 위치까지의 스크롤 거리가 .scrollTop 클래스를 가진 요소의 텍스트로 출력된다.

728x90
반응형

 

 

 

JOIN 완성화면

join 화면

 

 

 

 

 

코드 보기

 <div class="intro__inner center bmstyle">
            <picture class="intro__image">
                <source srcset="../assets/img/join01-min.png, ../assets/img/join01@2x-min.png 2x, ../assets/img/join01@3x-min.png 3x"/>
                <img src="../assets/img/join01.png" alt="joinimg">
            </picture>
            <p class="intro__text">
                회원 가입 시 더 다양한 정보를 자유롭게 시청하실 수 있습니다.
            </p>
        </div>
        <!-- intro__inner -->

        <div class="join__inner container">
            <h2>회원가입</h2>
            <div class="join__form">
                <form action="joinSave.php" name="join" method="post">
                    <fieldset>
                        <legend class="blind">회원가입 폼 영역</legend>
                        <div>
                            <label for="youEmail" class="required">이메일</label>
                            <input type="email" id="youEmail" name="youEmail" class="inputStyle" placeholder="이메일을 작성해주세요." required>
                        </div>
                        <div>
                            <label for="youName" class="required" >이름</label>
                            <input type="text" id="youName" name="youName"  class="inputStyle" placeholder="이름을 입력하세요." required>
                        </div>
                        <div>
                            <label for="youPass" class="required">비밀번호</label>
                            <input type="password" id="youPass" name="youPass" class="inputStyle" placeholder="사용하실 비밀번호를 입력하세요." required>
                        </div>
                        <div>
                            <label for="youPassC" class="required">비밀번호 확인</label>
                            <input type="password" id="youPassC" name="youPassC"  class="inputStyle" placeholder="비밀번호를 한 번 더 입력하세요." required>
                        </div>
                        <div>
                            <label for="youPhone" class="required">연락처</label>
                            <input type="text" id="youPhone" name="youPhone" placeholder="연락 가능한 연락처를 입력하세요." required>
                        </div><button type="submit" class="btnStyle">회원가입</button>
                    </fieldset>
                </form>
            </div>
        </div>

 

 

<form action="joinSave.php" name="join" method="post">

: 폼 요소를 생성하는 form 태그이다.

action 속성은 회원가입 정보를 저장할 서버 측 스크립트 파일의 경로를 지정한다.

name 속성은 폼의 이름을 지정하며, method 속성은 HTTP 요청 방식을 지정하는 속성이며, 현재 코드에서는 post 방식을 사용한다.

 

 

fieldset

:폼 요소를 그룹화 하는 요소이다.

 

label의 required

:폼 요소의 필수 입력 항목임을 나타내는 클래스이다.

 

inpit의 type, id, name, class, placeholder, required

: type 속성은 폼 요소의 타입을 지정한다. email이라면 이메일을, pass라면 비밀번호를, text라면 텍스트(혹은 숫자)를 받아온다.

 

id 속성은 레이블[각주:1]과 연결되는 폼 요소의 고유한 식별자이다.

 

name 속성은 서버 측 스크립트에서 폼 요소를 참조할 때 사용된다.

 

class 속성은 CSS 스타일링을 위해 지정된 클래스 이름이다.

 

placeholder 속성은 사용자에게 폼 요소에 입력할 내용을 안내하는 텍스트를 제공한다. ex) 이메일을 작성해주세요.

 

required 속성은 input의 required와 같이 폼 요소가 필수 항목임을 나타낸다.

 

 

 

 

 

JOIN Save 완성 화면

joinSave 화면

 

 

 

 

 

코드 보기

<main id="main" class="container">
        <div class="intro__inner center bmstyle">
            <picture class="intro__image">
                <source srcset="../assets/img/joinEnd01.png"/>
                <img src="../assets/img/joinEnd01.png" alt="joinEndimg">
            </picture>
            <?php
                include "../connect/connect.php";
                $youEmail = $_POST['youEmail'];
                $youName = $_POST['youName'];
                $youPass = $_POST['youPass'];
                $youPassC = $_POST['youPassC'];
                $youPhone = $_POST['youPhone'];
                $regTime = time();
                // echo $youEmail, $youName, $youPass, $youPhone;

                

                //오류 메시지 출력 
                function msg($alert) {
                    echo "<p class='intro__text'>$alert</p>";
                }
                //유효성 검사

                //이메일
                $check_mail = preg_match("/^[a-z0-9_+.-]+@([a-z0-9-]+\.)+[a-z0-9]{2,4}$/", $youEmail);

                if($check_mail == false) {
                    msg ("이메일을 다시 한 번 확인하세요.");
                    exit;
                }

                //이름
                $check_name = preg_match("/^[가-힣a-zA-Z]+$/", $youName);

                if($check_name == false) {
                    msg("이름 작성란을 다시 확인해주세요.");
                    exit;
                }

                //비밀번호 (password)
                if($youPass !== $youPassC) {
                    msg("비밀번호가 일치하지 않습니다. 올바른 비밀번호인지 확인하세요.");
                    exit;
                }
                // $youPass = sha1($youPass);

                //연락처
                $check_number = preg_match("/^(010|011|016|017|018|019)-[0-9]{3,4}-[0-9]{4}$/", $youPhone);

                if($check_number == false) {
                    msg("입력하신 연락처가 정확하지 않습니다. 다시 확인하세요.");
                    exit;
                }
                
                //중복검사(이메일)
                $isEmailCheck = false;
                
                $sql = "SELECT youEmail FROM members WHERE youEmail = '$youEmail'";
                $result = $connect -> query($sql);

                if($result) {
                    $count = $result -> num_rows;
                    
                    if($count == 0) {
                        $isEmailCheck = true;
                    } else {
                        msg("입력하신 이메일과 일치하는 회원정보가 있습니다. 아이디 찾기를 실행해주세요.");
                        exit;
                    }
                } else {
                    msg ("error : 관리자에게 문의하여 주시길 바랍니다. 불편을 드려 죄송합니다.");
                    exit;
                }

                //중복 검사(연락처)
                $isPhoneCheck = false;
                
                $sql = "SELECT youPhone FROM members WHERE youPhone = '$youPhone'";
                $result = $connect -> query($sql);

                if($result) {
                    $count = $result -> num_rows;
                    
                    if($count == 0) {
                        $isPhoneCheck = true;
                    } else {
                        msg("입력하신 연락처와 일치하는 회원정보가 있습니다. 아이디 찾기를 실행해주세요.");
                        exit;
                    }
                } else {
                    msg ("error 2 : 관리자에게 문의하여 주시길 바랍니다. 불편을 드려 죄송합니다.");
                    exit;
                }

                //전부 이상 X -> 회원가입
                if($isEmailCheck == true && $isPhoneCheck = true) {
                    //데이터 입력
                    $sql = "INSERT INTO members(youEmail, youName, youPass, youPhone, regTime) VALUES('$youEmail', '$youName', '$youPass', '$youPhone', '$regTime')";
                    $result = $connect -> query($sql);

                    if($result) {
                        msg("회원가입이 완료 되었습니다. 로그인 하세요.<br><div class='intro__btn'><a href='../login/login.php'>로그인</a></div>");
                        exit;
                    } else {
                        msg("error 3 : 관리자에게 문의하여 주시길 바랍니다. 불편을 드려 죄송합니다.");
                        exit;
                    }
                } else {
                    msg("이미 같은 정보로 가입된 회원이 있습니다. 아이디 찾기나 비밀번호 찾기를 진행하세요.");
                    exit;
                }

            ?>
        </div>
        <!-- intro__inner -->
    </main>
    <!-- main -->

 

include "../connect/connect.php";

:데이터베이스에 연결하기 위해 connect.php 파일을 불러온다.

 

$_POST['you~']

:회원가입 폼에서 전송된 데이터를 $_POST 배열을 사용하여 변수에 저장한다.각각의 변수는 이메일, 이름, 비밀번호, 비밀번호 확인, 연락처 정보를 저장한다.

 

$regTime = time( ); 

: time( ) 함수를 사용하여 현재 시간을 $regTime 변수에 저장한다.이 변수는 회원가입을 한 시간 정보를 확인하기 위해 사용되는 것이다.이렇게 저장된 변수들은 이후에 데이터 베이스에 저장하기 위해 사용된다.

 

 

function msg($alert) { ... }

: msg 라는 이름의 함수를 정의한다. 이 함수는 $alert 라는 매개변수를 받는다.

 

echo "<p class='intro__text'>$alert</p>";

: $alert 변수에 저장된 메시지를 출력하는 역할을 한다이 메시지는 HTML의 <p> 태그 내부에 출력된다.

 

폼 데이터의 유효성 검사에서 오류 메시지를 출력할 때 사용된다.오류 메시지를 출력하는 다양한 방법 중에서도 이 함수는 출력 내용을 직접 지정하기 때문에 다양한 상황에 적용이 가능하다는 이점이  존재한다.

 

유효성 검사 - 이메일

preg_match는 정규식 패턴을 사용하여 문자열 검색을 수행하는 PHP 함수이다.preg_match 안의 소괄호에는 검사할 이메일 주소의 패턴을 정규식으로 표현한 문자열이 들어있다.이메일 주소의 유효성을 검사하는 정규식 패턴은 다양하나, 만들어 사용할 수도 있다.

 

$youEmail은 입력된 이메일 주소 값을 의미하며, if($check_mail == false)는검사한 이메일 주소가 패턴에 맞지 않을 경우 즉, 이메일 주소가 유효하지 않을 경우를 처리하기 위한 조건문이다.유효하지 않을 경우, msg 함수를 호출하여 오류 메시지를 출력한 후 exit;를 통해 프로그램을 즉시 종료한다.이 이후에 처리되어야 할 코드가 있다면 실행되지 않는다.

 

 

유효성 검사 - 이름

preg_match 함수를 사용하여 정규식 패턴을 이용, 문자열 검색을 수행한다.소괄호 안에는 검사할 이름의 패턴을 정규식으로 표현한 문자열이 들어있다.이 코드에서는 한글과 영문대소문자만 허용하도록 정의되어 있다.

 

if($check_name == false)는 검사된 이름 값이 패턴에 맞지 않을 경우를 처리하기 위한 조건문으로, msg 함수를 호출하여 오류 메시지를 출력한 후 exit;를 통해 프로그램을 즉시 종료한다. 마찬가지로 이후에 처리되어야 할 코드는 실행되지 않는다.

 

 

 

유효성 검사 - 비밀번호

if($youPass !== $youPassC)는 입력된 비밀번호 값과 비밀번호 확인 값이 일치하지 않을 경우를 처리하기 위한 조건문으로 일치하지 않을 시 msg 함수를 호출하여 오류 메시지를 출력한 후 exit;를 통하여 프로그램을 즉시 종료한다.

 

$youPass = sha1($youPass); 는 입력된 비밀번호 값을 SHA-1이라는 이름의 알고리즘을 사용하여 암호화 하는 구문이다.SHA-1은 입력된 값을 160 비트로 압축한 다음 40자리의 16진수 문자열로 변환하는 해시 함수이다.이를 통해 보안성을 높일 수 있다.

 

 

 

유효성 검사 - 연락처

$youPhone 변수에 저장된 값이 정규식에 맞는지 확인한다.이 정규식은 대한민국의 전화번호 형식을 검사하며, 전화번호의 앞 세 자리는 010, 011, 016, 017, 018, 019 중 하나로 시작하고, 그다음에는 -(하이픈)으로 구분된 3-4자리 숫자와 -로 구분된 4자리의 숫자로 이루어져 있어야 한다.

 

$check_number 변수는 preg_match( ) 함수를 통해 $youPhone 변수에 저장된 값이 정규식과 일치하는지 여부를 확인한 결과를 저장한다.$check_number 변수 값이 false 즉, $youPhone 변수에 저장된 값이 정규식과 일치하지 않는다면 msg 함수를 호출하여 오류 메시지를 출력하고 exit; 를 통해 프로그램을 즉시 종료한다.

 

 

 

이메일 중복 검사

회원가입 시 입력한 이메일이 이미 회원가입이 되어있는지 중복검사를 실행한다.$isEmailCheck 변수를 false로 초기화 한 후 입력받은 이메일을 SELECT 쿼리문을 이용해 members 테이블에서 조회한다.조회한 결과가 있다면 $count 변수에 조회된 행(row) 수를 저장한다.이후 $count 값이 0이라면 입력한 이메일이 이미 회원가입이 되어있지 않다는 뜻이므로 $isEmailCheck 값을 false에서 true로 변경한다.

 

만약 $count 값이 0이 아니라면 이미 해당 이메일로 가입한 사용자가 존재한다는 뜻이므로, 사용자에게 이를 알리고 회원가입을 중단시킨다.

 

또한 만약에 SELECT 쿼리문 실행에 실패했다면 데이터베이스와의 연결 문제가 발생한 것이므로 사용자에게 에러 메시지를 출력하여 보여주고 회원가입을 중단 시킨다.

 

 

 

연락처 중복 검사

$isPhoneCheck 변수를 초기화하여 중복되지 않은 연락처인 경우에만 값을 true로 변경한다.SQL 쿼리를 사용하여, 데이터베이스에서 해당 연락처를 가진 회원을 찾고, 결과가 반환되면 결과의 행 수를 확인하여 해당 연락처를 가진 회원이 이미 존재하는지 확인한다.중복되지 않은 경우  $isPhoneCheck를 true로 설정한다.결과가 반환되지 않거나, 에러가 발생하는 경우 msg( ) 함수를 호추랗여 에러 메시지를 출력 시켜 보여준 후 회원가입을 중단 시킨다.

 

 

 

전부 이상이 없을 시 회원가입

이 부분은 회원가입 페이지에서 사용자가 입력한 정보들을 검사하고, 문제가 없으면 회원가입을 완료하는 기능을 수행한다.사용자의 이메일 주소, 이름, 비밀번호, 비밀번호 확인, 연락처가 유효한지 여부를 정규 표현식으로 확인하고 이메일과 전화번호는 중복 검사도 수행하며if($isEmailCheck == true && $isPhoneCheck = true), 중복된 정보가 있는 경우 회원가입을 완료하지 않고 오류 메시지를 출력한다.

 

회원가입에 필요한 정보들을 검사하여 오류가 발생한 경우 msg 함수를 호출하여 오류 메시지를 출력하고 exit; 함수를 호출하여 프로그램을 종료한다.모든 검사과정이 정상적으로 진행되면, 쿼리문을 사용하여 회원정보를 데이터 베이스에 저장하고 완료 메시지를 출력한다.

($sql = "INSERT INTO members(youEmail, youName, youPass, youPhone, regTime) VALUES('$youEmail', '$youName', '$youPass', '$youPhone', '$regTime')"; $result = $connect -> query($sql); )

 

 

 

 

 

LOGIN 완성화면

login 화면

 

 

LOGIN Save 완성화면

login Save 화면

 

 

 

 

 

코드 보기

<div class="intro__inner center bmstyle">
            <picture class="intro__image">
                <source srcset="../assets/img/joinEnd01.png"/>
                <img src="../assets/img/joinEnd01.png" alt="joinEndimg">
            </picture>
<?php
    include "../connect/connect.php";
    include "../connect/session.php";

    $youEmail = $_POST['youEmail'];
    $youPass = $_POST['youPass'];
    // echo $youEmail, $youPass;

    // 데이터 출력
    function msg($alert){
        echo "<p class='intro__text'>$alert</p>";
    }
    // 데이터 조회
    $sql = "SELECT memberID, youEmail, youName, youPass FROM members WHERE youEmail = '$youEmail' AND youPass = '$youPass'";
    $result = $connect -> query($sql);
    if($result){
        $count = $result -> num_rows;
        if($count == 0){
            msg("이메일 또는 비밀번호를 다시 확인하세요. <br><div class='intro__btn'><a href='../login/login.php'>로그인</a></div>");
        } else {
            // 로그인 성공
            $memberInfo = $result -> fetch_array(MYSQLI_ASSOC);

            // echo "<pre>";
            // var_dump($memberInfo);
            // echo "</pre>";

            //세션
            $_SESSION['memberID'] = $memberInfo['memberID'];
            $_SESSION['youEmail'] = $memberInfo['youEmail'];
            $_SESSION['youName'] = $memberInfo['youName'];

            Header("Location:../main/main.php");
        }
    }
?>
        </div>
        <!-- intro__inner -->

 

include "../connect/connect.php";와 indclude "../connect/session.php";는 데이터베이스 연결과 세션을 시작하는 코드이다.

 

$_POST는 폼 데이터를 받아오는 PHP의 슈퍼 글로벌 변수이다.

이 변수를 사용하여 로그인 폼에서 입력한 이메일과 비밀번호를 받아온다.

 

$youEmail과 $youPass는 받아온 이메일과 비밀번호를 저장하는 변수로, 이후 로그인 과정에서 이 변수들을 사용한다.

메인페이지에서 $_SESSION 슈퍼 글로벌 배열[각주:2]의 내용을 출력한다.

PHP에서 $_SESSION 배열은 동일한 사용자의 다른 페이지 또는 요청 간에 세션 데이터를 저장하는데 사용된다.

var_dump( )  함수는 $_SESSION 배열의 내용을 변수의 데이터 유형과 값이 포함된 구조화된 형식으로 출력한다.

 

pre 태그를 사용하여 출력을 줄 바꿈 및 적절한 들여쓰기와 같이 더 가독성 있게 포맷한다.

 

  1. 폼 요소(input, select, textarea 등)와 연결된 설명 텍스트를 의미한다. 즉, 사용자가 입력해야 할 내용이 무엇인지 설명하는 텍스트를 의미하는 것이다. 레이블을 사용하면 사용자가 폼 요소와 관련된 정보를 더 쉽게 이해할 수 있다. [본문으로]
  2. PHP에서 미리 정의된 전역 변수이며, 어디서든지 접근 가능하므로 슈퍼라는 이름이 붙여졌다. 웹 서버와 클라이언트 간에 세션 데이터를 저장하기 위해 사용된다. 세션 데이터는 서버 측에 저장되므로 사용자가 브라우저를 닫거나 로그아웃하여 세션을 종료해도 유지된다. [본문으로]
728x90
반응형

 

완성 화면

 

 

 

 

 

코드 보기 / HTML

  <div class="parallax__wrap">
           <section id="section1" class="parallax__item">
                <span class="parallax__item__num">01</span>
                <h2 class="parallax__item__title">Section 01</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">좋은 책은 우리에게 생각하는 법을 가르쳐준다.</p>
           </section>
           <!--//section 01-->

           <section id="section2" class="parallax__item">
                <span class="parallax__item__num">02</span>
                <h2 class="parallax__item__title">Section 02</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">독서란 삶의 전체를 돌아보는 것이다.</p>
            </section>
            <!--//section 02-->

            <section id="section3" class="parallax__item">
                <span class="parallax__item__num">03</span>
                <h2 class="parallax__item__title">Section 03</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">이미 달은 충분히 밝다. 우리가 할 일은 그 빛을 받아들이는 것 뿐이다.</p>
            </section>
            <!--//section 03-->

            <section id="section4" class="parallax__item">
                <span class="parallax__item__num">04</span>
                <h2 class="parallax__item__title">Section 04</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">달은 어디서든 그 자리에 있지만, 인생에서는 다른 위치에 서 있어야 한다.</p>
            </section>
            <!--//section 04-->

            <section id="section5" class="parallax__item">
                <span class="parallax__item__num">05</span>
                <h2 class="parallax__item__title">Section 05</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">나는 항상 밤에 달을 볼 때마다, 이 작은 빛이 우리의 모든 문제를 해결할 수 있을 것 같다.</p>
            </section>
            <!--//section 05-->

            <section id="section6" class="parallax__item">
                <span class="parallax__item__num">06</span>
                <h2 class="parallax__item__title">Section 06</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">달을 보며 꿈을 꾸지 마라. 당신의 발자취를 따라가라.</p>
            </section>
            <!--//section 06-->

            <section id="section7" class="parallax__item">
                <span class="parallax__item__num">07</span>
                <h2 class="parallax__item__title">Section 07</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">책을 읽는 사람은 많은 삶을 살 수 있다.</p>
            </section>
            <!--//section 07-->

            <section id="section8" class="parallax__item">
                <span class="parallax__item__num">08</span>
                <h2 class="parallax__item__title">Section 08</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">한 사람이 책 한 권을 읽을 때마다, 세상은 한 명씩 영혼이 더 나아진다.</p>
            </section>
            <!--//section 08-->

            <section id="section9" class="parallax__item">
                <span class="parallax__item__num">09</span>
                <h2 class="parallax__item__title">Section 09</h2>
                <figure class="parallax__item__imgWrap">
                    <div class="parallax__item__img"></div>
                </figure>
                <p class="parallax__item__desc">독서는 자유로운 인간이 되는 열쇠이다.</p>
            </section>
            <!--//section 09-->
        </div>

 

 

 

 

 

 

코드 보기 / JAVASCRIPT

        window.addEventListener("scroll",()=>{
            let scrollTop = window.pageYOffset || window.scrollY || document.documentElement.scrollTop; 
            
            document.querySelectorAll(".parallax__item").forEach((item, index) => {
                if(scrollTop >= item.offsetTop - 2) {
                    document.querySelectorAll(".parallax__nav li").forEach((el, index)=>{
                        el.classList.remove("active");
                    });
                    document.querySelector(".parallax__nav li:nth-child("+(index+1)+")").classList.add("active"); 
                }
            });

            document.querySelectorAll(".parallax__nav li a").forEach(li => {
                li.addEventListener("click", (el)=>{
                    el.preventDefault();
                    document.querySelector(li.getAttribute("href")).scrollIntoView({
                        behavior: "smooth"
                    })
                })
            })


            //info scroll

            document.querySelector(".scroll span").innerText = parseInt(scrollTop);
            document.querySelector(".info .offset1").innerText = document.getElementById("section1").offsetTop;
            document.querySelector(".info .offset2").innerText = document.getElementById("section2").offsetTop;
            document.querySelector(".info .offset3").innerText = document.getElementById("section3").offsetTop;
            document.querySelector(".info .offset4").innerText = document.getElementById("section4").offsetTop;
            document.querySelector(".info .offset5").innerText = document.getElementById("section5").offsetTop;
            document.querySelector(".info .offset6").innerText = document.getElementById("section6").offsetTop;
            document.querySelector(".info .offset7").innerText = document.getElementById("section7").offsetTop;
            document.querySelector(".info .offset8").innerText = document.getElementById("section8").offsetTop;
            document.querySelector(".info .offset9").innerText = document.getElementById("section9").offsetTop;

 

window.addEventListener("scroll",()=>{ 

: window에 스크롤 이벤트를 추가한다. 스크롤이 발생하면 이벤트 핸들러 함수가 실행된다.

 


let scrollTop = window.pageYOffset || window.scrollY || document.documentElement.scrollTop; 

: 현재 스크롤 위치를 scrollTop 변수에 할당한  후, window.pageYOffset, window.scrollY, document.documentElement.scrollTop 중 어떤 값이든 존재하는 값을 가져온다.

 


document.querySelectorAll(".parallax__item").forEach((item, index) => { 

: .parallax__item 클래스를 가진 모든 요소를 반복하여 순회한다. 각 요소는 item 변수에 할당된다.

 

if(scrollTop >= item.offsetTop - 2) { 

: 현재 스크롤 위치가 item 요소의 offsetTop 값보다 2보다 크거나 같은 경우 아래 코드를 실행한다.


document.querySelectorAll(".parallax__nav li").forEach((el, index)=>{ el.classList.remove("active"); }); 

: .parallax__nav li 요소들에서 active 클래스를 모두 제거한다.

 

document.querySelector(".parallax__nav li:nth-child("+(index+1)+")").classList.add("active"); 

: 현재 item 요소의 인덱스에 해당하는 .parallax__nav li 요소에 active 클래스를 추가한다.

 


document.querySelectorAll(".parallax__nav li a").forEach(li => { 

: .parallax__nav li a 요소를 반복하여 순회합니다. 각 요소는 li 변수에 할당된다.

 


li.addEventListener("click", (el)=>{ 

: li 요소에 클릭 이벤트 리스너를 추가합니다. 클릭이 발생하면 아래 코드가 실행된다.


el.preventDefault(); 

: 이벤트의 기본 동작을 방지한다.

 

document.querySelector(li.getAttribute("href")).scrollIntoView({behavior: "smooth"}) 

: 클릭한 링크의 href 속성값으로 이동하며, 부드러운 스크롤 효과를 적용한다.

 


document.querySelector(".scroll span").innerText = parseInt(scrollTop); 

: 현재 스크롤 위치를 .scroll 요소에 표시한다.

 


document.querySelector(".info .offset1").innerText = document.getElementById("section1").offsetTop; 

: 각 섹션의 offsetTop 값을 .info 요소 내 해당 클래스를 가진 요소의 텍스트에 할당한다.

728x90
반응형

 

 

완성 화면

 

 

 

 

코드 보기 / CSS

* {
            margin: 0;
            padding: 0;
        }
        body {
            margin: 0 auto;
            background-color: #000;
            width: 100%;
            height: 100%;
        }
        .box {
            text-align: center;
            width: 1000px;
            height: 500px;
            border: 5px solid #fff;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .decs {
            text-align: center;
            font-size: 30px;
            color: #fff;
            padding: 100px;
        }
        .numberconsole {
            display: inline-block;
            color: #000;
            display: flex;
            align-items: center;
            justify-content: center;
            word-spacing: 10px;
            margin: 20px;
            background-color: #fff;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            
        }
        #randombtn {
            display: block;
            position: absolute;
            left: 45%;
            top: 70%;
            width: 100px;
            height: 50px;
            font-size: 24px;
            cursor: pointer;
            background-color: royalblue;
            border-radius: 15px;
            font-size: 18px;
            color: #fff;
            border: 2px solid #fff;
        }

 

 

 

 

 

코드 보기 / HTML

<div class="box">
        <div class="decs">랜덤 번호</div>
        <div class="numberconsole"></div>
        <div class="numberconsole"></div>
        <div class="numberconsole"></div>
        <div class="numberconsole"></div>
        <div class="numberconsole"></div>
        <div class="numberconsole"></div>
        <button id="randombtn">click</button>
    </div>

이런식으로 쓸 생각은 아니었지만 6개 밖에 안 되고 귀찮아서 그만

 

 

 

 

 

코드 보기 / JAVASCRIPT

const numberRandom = () => {
        const numberConsole = document.querySelectorAll(".numberconsole")
        const number = [];

        for(let i=0; i<numberConsole.length; i++) {
            let Randomnumber;

            do {
                Randomnumber = Math.floor(Math.random()*45)+1;
            } while (number.includes(Randomnumber));

            number.push(Randomnumber);
            numberConsole[i].textContent = Randomnumber;
        }
    };
    
    const Numbtn = document.querySelector("#randombtn");
    Numbtn.addEventListener("click", numberRandom);

 

const numberRandom = () => {...}

: 함수를 정의한다. 화살표 함수를 사용하여 함수를 정의하였고, 화살표 함수는 ES6(ECMAScript 2015) 문법으로 함수를 더 간결하게 작성할 수 있다.

이 함수에서는 HTML 문서에서 class 이름이 "numberconsole"인 모든 요소를 선택한다. 

이를 위해 document.querySelectorAll() 메소드를 사용하며, 이 메소드는 지정된 CSS 선택자에 일치하는 모든 요소를 NodeList 형식으로 반환한다.


const numberConsole = document.querySelectorAll(".numberconsole")

: 선택된 모든 요소를 변수 numberConsole에 할당한다.

다음으로 number라는 빈 배열을 생성한다.

이 배열은 무작위로 생성된 숫자들을 저장하기 위한 용도로 사용된다.

for(...) 
: for 반복문을 사용하여 numberConsole의 수만큼 모든 요소에 대해 이 아래로 서술되는 작업을 수행한다.



let Randomnumber;

: 무작위 숫자를 저장하기 위한 변수 Randomnumber를 선언한다.



do-while 반복문

:  이 반복문을 사용하여 중복되지 않는 무작위 숫자를 생성한다.

do 블록에서는 무조건 한 번은 반복이 실행되며, while 조건문이 true일 때까지 반복이 계속된다.


Randomnumber = Math.floor(Math.random()*45)+1; 

: 1부터 45 사이의 무작위 숫자를 생성한다.

Math.random() 함수는 0부터 1 사이의 난수를 생성하며, Math.floor() 함수를 사용하여 소수점 이하를 버리고 정수를 얻는다. 이렇게 얻은 값에 45를 곱하고 1을 더하여 1부터 45 사이의 정수를 얻는다.

while 조건문에서는 number 배열에 생성한 숫자가 이미 존재하는지 확인한다.

number 배열에 해당 숫자가 없을 때까지 반복하여 중복되는 숫자를 피한다.

또한 number.includes(Randomnumber)를 사용하여 배열에 생성한 숫자가 이미 존재하는지 확인한다.

number.push(Randomnumber)

: 생성한 숫자를 number 배열에 추가한다.



numberConsole[i].textContent = Randomnumber

: Randomnumber 변수에 저장된 무작위 숫자를 해당 "numberconsole" 요소의 textContent 속성에 할당한다.

이를 통해 무작위 숫자가 표시되는 것이다.

const Numbtn = document.querySelector("#randombtn")

: HTML 문서에서 "randombtn" ID를 가진 요소를 선택하고, 이를 변수 Numbtn에 할당한다.

 


Numbtn.addEventListener("click", numberRandom) 

: Numbtn 요소에 대해 "click" 이벤트 리스너를 추가한다.

즉, Numbtn 요소가 클릭될 때마다 numberRandom 함수가 실행되는 것이다.

이를 통해, 사용자가 "randombtn" 버튼을 클릭할 때마다 1부터 45 사이의 무작위 숫자가 "numberconsole" 요소에 표시되게 되는 것이다.

728x90
반응형

 

완 성 화 면

 

분명 가운데 정렬 했는데 왜째서 오른쪽에 치우쳐져 있는 것이지...?

 

 

 

문제 1번.

불러온 json 파일이 어느 형식인지 확인하기.

 

 

 

 

문제 2번 

1번에서 불러온 json 파일을 이용하여 화면에 출력되게 하는데 10초에 한 번씩 변경되도록 하기.

 

 

코드 보기 / CSS

*{
            margin: 0;
            padding: 0;
        }
        body {
            width: 1200px;
            margin: 0 auto;
            background-color: #000;
        }
        .quote {
            font-size: 24px;
            color: #fff;
            text-align: center;
            line-height: 1.5;
            padding: 20px 0 50px 0;
            font-family: 'Oswald';
            position: relative;
        } 
        .author {
            color: #fff;
            text-align: center;
            font-size: 20px;
            font-family: 'Oswald';
        }

난 억울하다 진짜 가운데로 맞췄단 말이다...

 

 

 

 

코드 보기 /  HTML

<div class="write">
        <div class="quote"></div>
        <div class="author"></div>
    </div>

 

 

 

 

코드 보기  / JAVASCRIPT

 const dummy = (value) => {
            fetch(`dummy01.json`)
            .then(res => res.json())
            .then(items => { 
                    const write = document.querySelector(".write")
                    const random = Math.round(Math.random()*30);
                    write.querySelector(".quote").innerHTML = items.quotes[random].quote;
                    write.querySelector(".author").innerHTML = ` - ${items.quotes[random].author}`;
            })
            .catch((err) => console.log(err)); 
            }
            dummy();
            setInterval(dummy,10000);

 

 

 

 

 

const dummuy :

dummy 함수를 호출합니다.
이 함수는 인자를 하나 받아오지만 현재 이 함수에서는 인자를 사용하지 않으므로 무시된다.

 

fetch(`dummy.json)`

: fetch 함수를 사용하여 "dummy01.json" 파일을 가져온다.
fetch 함수는 HTTP 요청을 보내고, 이에 대한 응답(Response)을 Promise 객체로 반환한다.
즉, 이 함수는 서버로부터 데이터를 받아오는 비동기적인 작업을 수행하는 것이다.
가져온 파일은 JSON 형식으로 변환된 후 items 변수에 저장된다.


res.json() 함수

: Promise 객체의 결과인 Response 객체를 JSON 형식으로 변환하여 Promise 객체로 반환한다.
이 Promise 객체의 결과는 items 변수에 저장된다.

 

write

: write 변수를 사용하여 HTML 문서의 .write 클래스를 가진 요소를 찾아옵니다.
document.querySelector 함수를 사용하여 HTML 문서 내에서 .write 클래스를 가진 요소를 찾아온다.
이 요소는 write 변수에 저장된다.

 

Math.round(Math.random()*30)

: Math.random() 함수는 0부터 1사이의 임의의 실수값을 반환하고, Math.random()*30을 하면 0부터 30사이의 임의의 실수값을 구할 수 있다.
Math.round 함수를 사용하여 소수점 이하를 버리고 가장 가까운 정수값으로 반올림한다.
이 값은 random 변수에 저장된다.


구한 임의의 정수값을 사용하여 items 객체 내의 랜덤한 인덱스를 선택한다.
items 객체는 JSON 파일에서 가져온 명언들이 저장된 객체이며, items.quotes는 명언들이 저장된 배열을 나타낸다.items.quotes[random]은 랜덤한 인덱스를 가진 명언 객체를 나타낸다.
이 객체 내의 quote와 author 속성값을 같이 추출한다.
선택된 인덱스의 quote와 author 속성값을 write 변수를 사용하여 HTML 문서에 출력한다.
write.querySelector(".quote")는 HTML 문서 내에서 .quote 클래스를 가진 요소를 찾아오며, write.querySelector(".author")는 HTML 문서 내에서 .author 클래스를 가진 요소를 찾아온다.
이 두 요소 내의 innerHTML 속성값을 각각 items.quotes[random].quote와 items.quotes[random].author로 변경한다.

 

그리고 만약에 에러가 발생하면 catch  블록을 사용하여 예외처리를 수행한다.

catch 블록에서는 콘솔에 에러를 출력하거나, 사용자에게 에러 메시지를보여줄 수 있다.

 

 

마지막으로 실행문에는

dummy( );와 setInterval(dummy, 10000)이 있는데 dummy( );는 화면에 바로 명언이 출력될 수 있도록 이미 dummy를 한 번 실행시키는 역할을 하고  setInterval(dummy, 10000)은 setInterval을 통해  10초에 한 번씩 dummy 함수가 실행되도록 하는 것이다.

728x90
반응형

 

 

완성 화면

 

 

 

 

코드 보기 / CSS

.slider__wrap {
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%);
        width: 800px;
        height: 450px;
        box-shadow: 0 50px 100px rgba(0,0,0,0.5);
      }
      .slider__img {
        position: relative;
        width: 100%;
        height: 100%;
        overflow: hidden;
      }
      .slider__img img {
        position: absolute;
        width: 100%;
        height: 100%;
        object-fit: cover;
        opacity: 0;
        transform: scale(1.2);
        transition: all 600ms ease-in-out;
      }
      .slider__img img.active {
        opacity: 1;
        transform: scale(1);
      }

      .slider__thumnail{
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, 240px);
        width: 100px;
        display: flex;
        justify-content: center;
        gap: 10px;
      }
      .slider__thumnail img{
        cursor: pointer;
        border: 3px solid transparent;
      }
      .slider__thumnail img.active {
        border: 3px solid #fff
      }
      .slider__btn a {
        position: absolute;
        top: 0;
        width: 100px;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
        font-size: 13px;
        background-color: rgba(0, 0, 0, 0.3);
        color: #fff;
        transition: all 400ms ease-in-out;
      }
      .slider__btn a:hover{
        background-color: rgba(255, 255, 255, 0.2);
      }
      .slider__btn a.next {
        right: 0;
    }
    .slider__btn a.previous {
        left: 0;
    }

 

 

 

 

 

코드 보기 / HTML

<main id="main">
        <div class="slider__wrap">
            <div class="slider__img"></div>
            <div class="slider__thumnail"></div>
            <div class="slider__btn">
                <a href="#" class="previous">previous (이전)</a>
                <a href="#" class="next">next (다음)</a>
            </div>
        </div>
    </main>

 

 

 

 

 

코드 보기 / JAVASCRIPT

      let images = [
            "img/sliderEffect01-min.jpg",
            "img/sliderEffect02-min.jpg",
            "img/sliderEffect03-min.jpg",
            "img/sliderEffect04-min.jpg",
            "img/sliderEffect05-min.jpg",
            "img/sliderEffect07-min.jpg",
            "img/sliderEffect08-min.jpg",
            "img/sliderEffect09-min.jpg",
            "img/sliderEffect10-min.jpg"
        ];

        function ImageSlider (parent, images) {
            let currentIndex = 0;
            //선택자
            let Slider = {

                parent : parent,
                images : parent.querySelector(".slider__img"),
                thumnail : parent.querySelector(".slider__thumnail"),
                PreviousBtn : parent.querySelector(".slider__btn .previous"),
                NextBtn : parent.querySelector(".slider__btn .next")
            }

            //이미지를 화면에 출력
            Slider.images.innerHTML = images.map((image, index)=>{
                return `<img src="${image}" alt="이미지${index}">`
            }).join("");

            //큰 활성화 시 효과
            let imageNode = Slider.images.querySelectorAll("img");
            imageNode[currentIndex].classList.add("active");
            
            //썸네일에 이미지 출력
            Slider.thumnail.innerHTML = Slider.images.innerHTML;

             //썸네일에 active 활성화
             let thumnailNode = Slider.thumnail.querySelectorAll("img");
             thumnailNode[currentIndex].classList.add("active");


            
            //썸네일 클릭 시 이동
            // for(let i=0; i<thumnailNode.length; i++){
            //     thumnailNode[i].addEventListener("click", function(){
            //         Slider.thumnail.querySelector("img.active").classList.remove("active");
            //         thumnailNode[i].classList.add("active");

            //         imageNode[currentIndex].classList.remove("active");
            //         currentIndex = i;
            //         imageNode[currentIndex].classList.add("active")
            //     });
            // }
    
            thumnailNode.forEach((el, i) => {
                el.addEventListener("click", function(){
                Slider.thumnail.querySelector("img.active").classList.remove("active");
                el.classList.add("active");

                imageNode[currentIndex].classList.remove("active");
                currentIndex = i;
                imageNode[currentIndex].classList.add("active")
                });
            });

            //왼쪽 버튼 클릭
            Slider.PreviousBtn.addEventListener("click", function(){
               imageNode[currentIndex].classList.remove("active");
               currentIndex--;

               if(currentIndex < 0) currentIndex = images.length - 1;
               imageNode[currentIndex].classList.add("active");

                //활성화 되는 이미지와 같은 썸네일에 active 활성화
                Slider.thumnail.querySelector("img.active").classList.remove("active");
                thumnailNode[currentIndex].classList.add("active");

            });

            //오른쪽 버튼 클릭
            Slider.NextBtn.addEventListener("click", function(){
                imageNode[currentIndex].classList.remove("active");

                currentIndex = (currentIndex + 1) % images.length;
                imageNode[currentIndex].classList.add("active");

                //활성화 되는 이미지와 같은 썸네일에 active 활성화
                Slider.thumnail.querySelector("img.active").classList.remove("active");
                thumnailNode[currentIndex].classList.add("active");
            });
        }
        ImageSlider(document.querySelector(".slider__wrap"),images);

 

 

 

 

parent  : 슬라이더를 감싸는 부모 요소이다.
images : 슬라이더에 들어갈 이미지의 배열이다.



ImageSlider 함수

: Slider 객체를 생성하고, 이 객체 안에 슬라이더에 필요한 요소들을 할당한다.

parent : 슬라이더를 감싸는 부모 요소입니다. Slider 객체의 parent 프로퍼티에 할당된다.
images : 슬라이더에 들어갈 이미지 배열입니다. 현재 코드에서는 images를 사용하지 않는다.
images 프로퍼티는 parent.querySelector(".slider__img")로 할당된다. 이는 슬라이더 안에 있는 이미지 요소를 선택하는선택자이다.
thumnail 프로퍼티는 parent.querySelector(".slider__thumnail")로 할당된다. 이는 슬라이더의 썸네일을 나타내는 요소를 선택하는 선택자이다.
PreviousBtn 프로퍼티는 parent.querySelector(".slider__btn .previous")로 할당된다. 이는 이전 이미지로 이동하는 버튼을 나타내는 요소를 선택하는 선택자이다.
NextBtn 프로퍼티는 parent.querySelector(".slider__btn .next")로 할당된다. 이는 다음 이미지로 이동하는 버튼을 나타내는 요소를 선택하는 선택자이다.
이렇게 생성된 Slider 객체를 통해 슬라이더에 필요한 요소들에 접근할 수 있다.



Slider.images.innerHTML에 map() 함수를 사용하여 images 배열에서 이미지를 가져와 HTML 코드로 변환한다.
join() 함수는 배열의 각 요소를 문자열로 변환하고, 배열 요소 사이에 구분자를 삽입하여 하나의 문자열로 결합한다.
따라서 Slider.images.innerHTML에는 이미지 요소를 포함한 HTML 코드가 삽입된다.
다음으로, Slider.images.querySelectorAll("img")로 이미지 요소를 선택하여 imageNode에 할당한다.

currentIndex 변수로 현재 활성화된 이미지를 지정하고, 이를 imageNode에서 선택한 이미지 요소에 classList.add("active")를 사용하여 추가한다.
이로써 현재 활성화된 이미지에 대한 효과를 적용한다.

Slider.thumnail.innerHTML에도 Slider.images.innerHTML과 같은 방식으로 HTML 코드를 삽입합니다. 따라서 썸네일 이미지 요소도 이미지 슬라이더 이미지와 동일하게 표시된다.

마지막으로, Slider.thumnail.querySelectorAll("img")로 썸네일 이미지 요소를 선택하여 thumnailNode에 할당한다. 

이전과 마찬가지로 currentIndex 변수로 현재 활성화된 이미지를 지정하고, 이를 thumnailNode에서 선택한 이미지 요소에 classList.add("active")를 사용하여 추가한다. 이로써 현재 활성화된 썸네일에 대한 효과를 적용한다.






Slider.PreviousBtn.addEventListener() 함수:  왼쪽 버튼에 클릭 이벤트를 등록한다.

이벤트 핸들러 함수에서는 현재 활성화된 이미지에서 active 클래스를 제거하고, currentIndex 변수를 1 감소시킨다.
이후, currentIndex 값이 0보다 작으면, currentIndex 값을 images.length - 1로 설정하여, 마지막 이미지를 가리키도록 한다.
다음으로, imageNode[currentIndex]로 현재 활성화된 이미지 요소를 선택하고, classList.add("active")를 사용하여 해당 이미지에 active 클래스를 추가한다.
마지막으로, 활성화된 이미지와 동일한 썸네일 이미지에 active 클래스를 추가하여, 썸네일 이미지도 함께 활성화시킨다. 
이렇게 함으로써 왼쪽 버튼을 클릭했을 때, 이미지 슬라이더의 활성화된 이미지가 왼쪽으로 이동하고, 이에 따라 썸네일 이미지도 변경된다.






Slider.NextBtn.addEventListener() 함수 : 오른쪽 버튼에 클릭 이벤트를 등록합니다.

이벤트 핸들러 함수에서는 현재 활성화된 이미지에서 active 클래스를 제거하고, currentIndex 값을 (currentIndex + 1) % images.length으로 설정한다.
이렇게 함으로써, currentIndex 값이 images.length와 같아지면, 0으로 돌아가게 된다. 

이후, imageNode[currentIndex]로 현재 활성화된 이미지 요소를 선택하고, classList.add("active")를 사용하여 해당 이미지에 active 클래스를 추가한다.
마지막으로, 활성화된 이미지와 동일한 썸네일 이미지에 active 클래스를 추가하여, 썸네일 이미지도 함께 활성화 시킨다. 이렇게 함으로써 오른쪽 버튼을 클릭했을 때, 이미지 슬라이더의 활성화된 이미지가 오른쪽으로 이동하고, 이에 따라 썸네일 이미지도 변경된다.

 


마지막으로, ImageSlider() 함수를 호출하여, 이미지 슬라이더를 초기화합니다. 
함수에는 document.querySelector(".slider__wrap")로 슬라이더를 감싸는 부모 요소와 images 배열을 인자로 전달한다.
thumnailNode.forEach() 함수를 사용하여 thumnailNode에 있는 모든 썸네일 이미지 요소에 대해 반복합니다. 
각 썸네일 이미지 요소에 addEventListener() 함수를 사용하여 클릭 이벤트를 등록한다.

이벤트 핸들러 함수에서는 먼저 Slider.thumnail.querySelector("img.active")를 사용하여 현재 활성화된 썸네일 이미지를 선택하고, classList.remove("active")를 사용하여 해당 이미지에서 active 클래스를 제거한다.
다음으로, 클릭된 썸네일 이미지 요소에 classList.add("active")를 사용하여 active 클래스를 추가한다.
그리고 currentIndex 변수를 클릭된 썸네일 이미지 요소의 인덱스 값으로 업데이트한다.
이후, imageNode[currentIndex]로 현재 활성화된 이미지 요소를 선택하고, classList.remove("active")를 사용하여 해당 이미지에서 active 클래스를 제거한다.
마지막으로, imageNode[currentIndex]에 classList.add("active")를 사용하여 클릭된 썸네일 이미지와 해당하는 이미지를 활성화한다.

이렇게 함으로써 썸네일 이미지를 클릭했을 때, 해당 이미지가 활성화되고 이에 따라 이미지 슬라이더가 변경된다.

+ Recent posts