유닛 테스트 기초 (Unit Testing Basics)

유닛 테스트 기초 (Unit Testing Basics)

자바스크립트에서 코드를 작성할 때, 제대로 작동하는지 확인하는 방법 중 하나로 유닛 테스트가 있다. 유닛 테스트는 코드의 작은 단위를 독립적으로 검증하며, 전체 애플리케이션의 안정성을 높여준다. 이번에는 유닛 테스트의 기본 개념부터 심화된 활용까지 코드와 함께 자세히 알아보려고 한다.


유닛 테스트를 잘 다루면 코드의 신뢰성을 높이고, 수정할 때도 안심할 수 있다. 하나씩 단계별로 풀어보자.


유닛 테스트란 무엇인가

유닛 테스트는 함수나 모듈 같은 코드의 개별 단위를 테스트하는 방법이다. 전체 시스템을 한 번에 확인하는 대신, 작은 조각들을 따로 검증한다:

function add(a, b) {
    return a + b;
}

console.log(add(2, 3)); // 5

위 함수를 테스트하려면 입력값을 넣고 출력값이 예상과 같은지 확인하면 된다. 이 과정을 자동화하는 도구를 사용하면 편리하다.


1. 간단한 유닛 테스트 작성

자바스크립트에서 유닛 테스트를 위해 assert 모듈을 활용해보자:

const assert = require("assert");

function add(a, b) {
    return a + b;
}

assert.strictEqual(add(2, 3), 5, "2 + 3은 5여야 한다");
assert.strictEqual(add(0, 0), 0, "0 + 0은 0이어야 한다");
console.log("모든 테스트 통과!");

assert.strictEqual로 결과값과 기대값을 비교했다. 오류가 없으면 메시지가 출력된다.


2. 테스트 프레임워크 사용: Mocha

수동으로 assert를 쓰는 대신, Mocha 같은 테스트 프레임워크를 사용하면 더 체계적으로 관리할 수 있다:

// 설치: npm install mocha
const assert = require("assert");

describe("add 함수 테스트", () => {
    it("2와 3을 더하면 5가 된다", () => {
        function add(a, b) {
            return a + b;
        }
        assert.strictEqual(add(2, 3), 5);
    });

    it("0과 0을 더하면 0이 된다", () => {
        function add(a, b) {
            return a + b;
        }
        assert.strictEqual(add(0, 0), 0);
    });
});

describe로 테스트 그룹을 만들고, it으로 개별 테스트를 정의했다. npm test로 실행하면 결과를 볼 수 있다.


3. 비동기 함수 테스트

비동기 코드를 테스트하려면 약간의 추가 작업이 필요하다:

const assert = require("assert");

function fetchData() {
    return new Promise((resolve) => {
        setTimeout(() => resolve("데이터"), 1000);
    });
}

describe("비동기 함수 테스트", () => {
    it("1초 후 데이터를 반환한다", async () => {
        const result = await fetchData();
        assert.strictEqual(result, "데이터");
    });
});

asyncawait를 사용해서 비동기 결과를 기다린 후 검증했다.


4. 모킹(Mocking)으로 의존성 관리

외부 의존성이 있는 코드를 테스트할 때, sinon 같은 라이브러리로 모킹을 해보자:

// 설치: npm install sinon mocha --save-dev
const assert = require("assert");
const sinon = require("sinon");

function getUserData(fetch) {
    return fetch("https://api.example.com/user");
}

describe("getUserData 테스트", () => {
    it("API 호출 결과를 반환한다", async () => {
        const fakeFetch = sinon.stub().resolves("유저 데이터");
        const result = await getUserData(fakeFetch);
        assert.strictEqual(result, "유저 데이터");
    });
});

sinon.stub으로 가짜 함수를 만들어 실제 API 호출 없이 테스트했다.


5. 에러 상황 테스트

함수가 에러를 잘 처리하는지 확인해보자:

const assert = require("assert");

function divide(a, b) {
    if (b === 0) throw new Error("0으로 나눌 수 없다");
    return a / b;
}

describe("divide 함수 테스트", () => {
    it("0으로 나누면 에러가 발생한다", () => {
        assert.throws(() => divide(5, 0), {
            message: "0으로 나눌 수 없다"
        });
    });

    it("정상적인 나눗셈은 결과를 반환한다", () => {
        assert.strictEqual(divide(6, 2), 3);
    });
});

assert.throws로 에러를 검증하고, 정상 동작도 확인했다.


6. 복잡한 객체 테스트

객체를 다룰 때는 깊은 비교가 필요하다:

const assert = require("assert");

function createUser(name, age) {
    return { name, age };
}

describe("createUser 테스트", () => {
    it("이름과 나이를 포함한 객체를 반환한다", () => {
        const expected = { name: "홍길동", age: 30 };
        const result = createUser("홍길동", 30);
        assert.deepStrictEqual(result, expected);
    });
});

deepStrictEqual로 객체의 속성을 모두 비교했다.


7. 테스트 더블(Test Double) 활용

테스트 더블로 복잡한 의존성을 대체해보자:

const assert = require("assert");

class Database {
    getData() {
        return "실제 데이터";
    }
}

class Service {
    constructor(db) {
        this.db = db;
    }
    fetch() {
        return this.db.getData();
    }
}

describe("Service 테스트", () => {
    it("가짜 DB로 데이터 가져오기", () => {
        const fakeDb = { getData: () => "가짜 데이터" };
        const service = new Service(fakeDb);
        assert.strictEqual(service.fetch(), "가짜 데이터");
    });
});

가짜 객체를 만들어 실제 데이터베이스 없이 테스트를 진행했다.


8. 반복 테스트 자동화

여러 입력값을 한 번에 테스트하려면 반복문을 활용한다:

const assert = require("assert");

function multiply(a, b) {
    return a * b;
}

describe("multiply 함수 테스트", () => {
    const cases = [
        [2, 3, 6],
        [0, 5, 0],
        [1, 1, 1]
    ];
    cases.forEach(([a, b, expected]) => {
        it(`${a}와 ${b}를 곱하면 ${expected}가 된다`, () => {
            assert.strictEqual(multiply(a, b), expected);
        });
    });
});

배열을 순회하며 여러 경우를 자동으로 테스트했다.


9. 성능과 유지보수성에 미치는 영향

유닛 테스트가 코드에 어떤 영향을 주는지 살펴보자:

- 성능: 테스트 실행에 시간이 걸릴 수 있지만, 버그를 줄여 전체 개발 속도를 높인다.

- 유지보수성: 코드 변경 후에도 테스트로 바로 확인할 수 있어 안정성이 유지된다.

작은 단위를 테스트하며 전체 시스템의 신뢰성을 확보하는 점이 핵심이다.


마무리

유닛 테스트는 코드의 작은 부분을 검증하며 전체 품질을 높여준다. 기본적인 함수 테스트부터 비동기, 모킹, 에러 처리까지 다양한 상황에서 활용할 수 있다. 꾸준히 작성하면 코드에 대한 자신감이 생길 것이다.


Angular 기초 (Angular Basics)

Angular 기초 (Angular Basics)

Angular는 강력한 풀스택 프론트엔드 프레임워크다. 타입스크립트와 컴포넌트 기반 구조로 대규모 애플리케이션에 적합하다. 이번에는 Angular의 기본부터 심화까지 코드와 함께 자세히 풀어보려고 한다.


Angular를 잘 이해하면 구조화된 웹 앱을 효율적으로 만들 수 있다. 하나씩 단계별로 알아보자.


Angular 시작하기

Angular를 사용하려면 먼저 Angular CLI로 프로젝트를 생성한다:

# Angular CLI 설치
npm install -g @angular/cli

# 새 프로젝트 생성
ng new my-app
cd my-app
ng serve

// src/app/app.component.html
<h1>안녕, Angular!h1>

ng serve로 앱을 실행하면 기본 템플릿이 표시된다.


1. 컴포넌트 생성

Angular는 컴포넌트로 UI를 구성한다. 새 컴포넌트를 만들어보자:

# 컴포넌트 생성
ng generate component greeting

// src/app/greeting/greeting.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app-greeting',
    template: `

안녕, {{ name }}!

`
}) export class GreetingComponent { name = '홍길동'; } // src/app/app.component.html <app-greeting>app-greeting>

@Component 데코레이터로 컴포넌트를 정의하고 사용했다.


2. 데이터 바인딩

Angular는 다양한 데이터 바인딩을 제공한다:

// src/app/app.component.ts
import { Component } from '@angular/core';

@Component({
    selector: 'app-root',
    template: `
        
        

입력값: {{ text }}

`
}) export class AppComponent { text = ''; } // src/app/app.module.ts import { FormsModule } from '@angular/forms'; @NgModule({ imports: [FormsModule] })

[(ngModel)]로 양방향 바인딩을 구현했다.


3. 디렉티브 활용

Angular의 디렉티브로 동적인 UI를 만든다:

// src/app/app.component.ts
@Component({
    template: `
        
        

보이는 상태

`
}) export class AppComponent { isVisible = false; toggle() { this.isVisible = !this.isVisible; } }

*ngIf로 조건을, (click)으로 이벤트를 처리했다.


4. 리스트 렌더링

*ngFor로 리스트를 렌더링한다:

// src/app/app.component.ts
@Component({
    template: `
        
  • {{ item }}
`
}) export class AppComponent { items = ['사과', '바나나', '오렌지']; }

*ngFor로 배열을 순회하며 표시했다.


5. 서비스와 의존성 주입

서비스로 데이터를 관리한다:

# 서비스 생성
ng generate service data

// src/app/data.service.ts
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class DataService {
    getItems() {
        return ['사과', '바나나'];
    }
}

// src/app/app.component.ts
import { DataService } from './data.service';

@Component({
    template: `
  • {{ item }}
`
}) export class AppComponent { items: string[]; constructor(private dataService: DataService) { this.items = dataService.getItems(); } }

의존성 주입으로 서비스를 컴포넌트에 연결했다.


6. HTTP 요청

API 데이터를 가져와보자:

// src/app/app.module.ts
import { HttpClientModule } from '@angular/common/http';
@NgModule({
    imports: [HttpClientModule]
})

// src/app/app.component.ts
import { HttpClient } from '@angular/common/http';

@Component({
    template: `
  • {{ post.title }}
`
}) export class AppComponent { posts: any[] = []; constructor(private http: HttpClient) { this.http.get('https://jsonplaceholder.typicode.com/posts') .subscribe(data => this.posts = (data as any).slice(0, 5)); } }

HttpClient로 데이터를 가져와 렌더링했다.


7. 성능과 구조에 미치는 영향

Angular가 앱에 어떤 영향을 주는지 보자:

- 성능: 변경 감지로 효율적이지만, 초기 로드가 약간 무거울 수 있다.

- 구조: 타입스크립트와 모듈로 대규모 앱에 강하다.

컴포넌트서비스가 Angular의 강력함을 보여준다.


마무리

Angular는 컴포넌트, 바인딩, 서비스를 통해 구조화된 웹 개발을 가능하게 한다. 기본부터 HTTP 요청까지 다채롭게 활용할 수 있다.


Vue.js 기초 (Vue.js Basics)

Vue.js 기초 (Vue.js Basics)

Vue.js는 간단하면서도 강력한 프론트엔드 프레임워크다. 데이터 바인딩과 컴포넌트 구조로 UI를 쉽게 만들어준다. 이번에는 Vue.js의 기본부터 심화까지 코드와 함께 자세히 풀어보려고 한다.


Vue.js를 잘 이해하면 반응형 인터페이스를 빠르게 구현할 수 있다. 하나씩 단계별로 알아보자.


Vue.js 시작하기

Vue.js를 사용하려면 먼저 CDN으로 추가하거나 프로젝트를 설정한다. 간단히 CDN으로 시작해보자:

<html>
<head>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js">script>
head>
<body>
    <div id="app">
        {{ message }}
    div>
    <script>
        const { createApp } = Vue;
        createApp({
            data() {
                return { message: "안녕, Vue.js!" };
            }
        }).mount("#app");
    script>
body>
html>
// "안녕, Vue.js!"

{{ }}로 데이터를 화면에 바로 표시했다.


1. 데이터 바인딩

Vue.js의 핵심은 양방향 데이터 바인딩이다:

<div id="app">
    <input v-model="text">
    <p>입력값: {{ text }}p>
div>

<script>
    const { createApp } = Vue;
    createApp({
        data() {
            return { text: "" };
        }
    }).mount("#app");
script>

v-model로 입력값과 데이터를 실시간으로 연결했다.


2. 디렉티브 사용

Vue.js는 디렉티브로 동적인 UI를 만든다:

<div id="app">
    <button v-on:click="toggle">토글button>
    <p v-if="isVisible">보이는 상태p>
div>

<script>
    const { createApp } = Vue;
    createApp({
        data() {
            return { isVisible: false };
        },
        methods: {
            toggle() {
                this.isVisible = !this.isVisible;
            }
        }
    }).mount("#app");
script>

v-if로 조건을, v-on으로 이벤트를 처리했다.


3. 리스트 렌더링

v-for로 배열을 렌더링할 수 있다:

<div id="app">
    <ul>
        <li v-for="item in items" :key="item">{{ item }}li>
    ul>
div>

<script>
    const { createApp } = Vue;
    createApp({
        data() {
            return { items: ["사과", "바나나", "오렌지"] };
        }
    }).mount("#app");
script>
// 
  • 사과
  • 바나나
  • 오렌지

:key로 각 항목을 고유하게 식별했다.


4. 컴포넌트 만들기

Vue.js에서도 컴포넌트로 UI를 분리할 수 있다:

<div id="app">
    <item-card v-for="item in items" :key="item" :name="item">item-card>
div>

<script>
    const { createApp } = Vue;
    const app = createApp({
        data() {
            return { items: ["사과", "바나나"] };
        }
    });

    app.component("item-card", {
        props: ["name"],
        template: `

항목: {{ name }}

`
}); app.mount("#app"); script>

props로 데이터를 전달해 컴포넌트를 재사용했다.


5. 반응형 상태

Vue 3에서는 refreactive로 반응형 데이터를 만든다:

<div id="app">
    <p>카운트: {{ count }}p>
    <button v-on:click="increment">증가button>
div>

<script>
    const { createApp, ref } = Vue;
    createApp({
        setup() {
            const count = ref(0);
            const increment = () => { count.value++; };
            return { count, increment };
        }
    }).mount("#app");
script>

ref로 반응형 변수를 정의하고 버튼으로 값을 변경했다.


6. 데이터 가져오기

API 데이터를 가져와 렌더링해보자:

<div id="app">
    <ul>
        <li v-for="post in posts" :key="post.id">{{ post.title }}li>
    ul>
div>

<script>
    const { createApp, ref, onMounted } = Vue;
    createApp({
        setup() {
            const posts = ref([]);
            onMounted(async () => {
                const res = await fetch("https://jsonplaceholder.typicode.com/posts");
                posts.value = (await res.json()).slice(0, 5);
            });
            return { posts };
        }
    }).mount("#app");
script>

onMounted로 마운트 시 데이터를 가져왔다.


7. 성능과 구조에 미치는 영향

Vue.js가 앱에 어떤 영향을 주는지 보자:

- 성능: 반응형 시스템으로 필요한 부분만 업데이트된다.

- 구조: 직관적인 디렉티브와 컴포넌트로 코드가 깔끔해진다.

데이터 바인딩디렉티브가 Vue.js의 강점이다.


마무리

Vue.js는 데이터 바인딩, 디렉티브, 컴포넌트를 통해 UI 개발을 단순화한다. 기본부터 데이터 가져오기까지 유연하게 활용할 수 있다.


Node.js 기초 (Node.js Basics)

Node.js 기초 (Node.js Basics)

Node.js는 자바스크립트를 브라우저 밖에서 실행할 수 있게 해주는 런타임이다. 비동기 I/O와 이벤트 기반 아키텍처로 서버 개발에 강력한 도구로 자리잡았다. 이번에는 Node.js의 기본부터 심화까지 코드와 함께 자세히 풀어보려고 한다.


Node.js를 잘 다루면 서버와 백엔드 로직을 효율적으로 구현할 수 있다. 하나씩 단계별로 알아보자.


Node.js 시작하기

Node.js를 사용하려면 먼저 설치해야 한다. 설치 후 간단한 코드를 실행해보자:

// hello.js
console.log("안녕, Node.js!");

# 터미널에서 실행
node hello.js
// "안녕, Node.js!"

Node.js가 설치되어 있다면 node 명령어로 파일을 바로 실행할 수 있다.


1. 모듈 시스템

Node.js는 모듈로 코드를 분리해 관리한다. 기본 모듈을 사용해보자:

// math.js
function add(a, b) {
    return a + b;
}

module.exports = { add };

// main.js
const math = require("./math");
console.log(math.add(2, 3));
// 5

module.exports로 함수를 내보내고, require로 불러왔다.


2. 파일 읽고 쓰기

fs 모듈로 파일 시스템을 다룰 수 있다:

const fs = require("fs");

// 파일 쓰기
fs.writeFileSync("test.txt", "Hello, Node.js!");

// 파일 읽기
const data = fs.readFileSync("test.txt", "utf8");
console.log(data);
// "Hello, Node.js!"

동기 방식으로 파일을 읽고 썼다. 비동기 방식도 가능하다.


3. 비동기 처리

Node.js의 강점은 비동기 처리다. fs 모듈의 비동기 메서드를 사용해보자:

const fs = require("fs");

fs.readFile("test.txt", "utf8", (err, data) => {
    if (err) {
        console.log("에러: " + err);
        return;
    }
    console.log(data);
});
// "Hello, Node.js!"

콜백으로 비동기 작업 결과를 처리했다.


4. 간단한 서버 만들기

http 모듈로 웹 서버를 구축할 수 있다:

const http = require("http");

const server = http.createServer((req, res) => {
    res.writeHead(200, { "Content-Type": "text/plain" });
    res.end("안녕, Node.js 서버!");
});

server.listen(3000, () => {
    console.log("서버가 3000번 포트에서 실행 중");
});

3000번 포트에서 실행하면 브라우저에서 응답을 확인할 수 있다.


5. Express로 서버 개선

express를 사용하면 서버 개발이 더 간편해진다:

# 설치
npm install express

// app.js
const express = require("express");
const app = express();

app.get("/", (req, res) => {
    res.send("안녕, Express!");
});

app.listen(3000, () => {
    console.log("Express 서버가 3000번 포트에서 실행 중");
});

Express로 간단한 GET 요청을 처리하는 서버를 만들었다.


6. 미들웨어 사용

Express에서 미들웨어로 요청을 가공할 수 있다:

const express = require("express");
const app = express();

app.use((req, res, next) => {
    console.log("요청 시간: " + new Date());
    next();
});

app.get("/", (req, res) => {
    res.send("미들웨어 테스트");
});

app.listen(3000);

미들웨어로 요청 시 시간을 로그에 남기고 다음 단계로 넘어갔다.


7. REST API 구축

간단한 REST API를 만들어 보자:

const express = require("express");
const app = express();
app.use(express.json());

let items = ["사과", "바나나"];

app.get("/items", (req, res) => {
    res.json(items);
});

app.post("/items", (req, res) => {
    items.push(req.body.name);
    res.json(items);
});

app.listen(3000);

GET으로 리스트를 조회하고, POST로 새 항목을 추가했다.


8. 성능과 활용성

Node.js가 코드에 어떤 영향을 주는지 보자:

- 성능: 비동기 처리로 I/O 작업이 많은 환경에서 뛰어나다.

- 활용성: 자바스크립트로 풀스택 개발이 가능해진다.

비동기모듈 시스템이 Node.js의 핵심이다.


마무리

Node.js는 모듈, 비동기, 서버 구축을 통해 백엔드 개발을 단순화한다. 기본 파일 작업부터 REST API까지 다채롭게 활용할 수 있다.


React 기초 (React Basics)

React 기초 (React Basics)

React는 UI를 컴포넌트 단위로 쌓아 올려 동적인 웹을 만들어내는 라이브러리다. 컴포넌트와 상태 관리를 중심으로 설계되어 효율적이고 유연한 개발을 가능하게 한다. 이번에는 React의 기본부터 심화까지 코드와 함께 자세히 풀어보려고 한다.


React를 잘 이해하면 복잡한 인터페이스도 깔끔하게 구성할 수 있다. 하나씩 단계별로 알아보자.


React 시작하기

React를 사용하려면 먼저 프로젝트를 설정해야 한다. create-react-app을 활용하면 간단하다:

# 터미널에서 실행
npx create-react-app my-app
cd my-app
npm start

// src/App.js 기본 구조
import React from 'react';

function App() {
    return (
        <div>
            <h1>안녕, React!h1>
        div>
    );
}

export default App;

위 코드는 React 앱의 기본 컴포넌트를 보여준다. JSX를 사용해 HTML처럼 작성할 수 있다.


1. 컴포넌트 만들기

React는 컴포넌트를 기반으로 동작한다. 간단한 컴포넌트를 만들어보자:

import React from 'react';

function Greeting(props) {
    return <p>안녕, {props.name}!p>;
}

function App() {
    return (
        <div>
            <Greeting name="홍길동" />
            <Greeting name="김영희" />
        div>
    );
}

export default App;
// "안녕, 홍길동!"
// "안녕, 김영희!"

props로 데이터를 전달해 재사용 가능한 컴포넌트를 만들었다.


2. 상태 관리

React에서 동적인 UI를 위해 useState 훅을 사용한다:

import React, { useState } from 'react';

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

    return (
        <div>
            <p>클릭 횟수: {count}p>
            <button onClick={() => setCount(count + 1)}>증가button>
        div>
    );
}

export default Counter;

useState로 상태를 정의하고, 버튼 클릭 시 상태를 업데이트했다.


3. 이벤트 처리

React에서 이벤트는 JSX 속성으로 처리된다:

import React, { useState } from 'react';

function InputForm() {
    const [text, setText] = useState("");

    const handleChange = (event) => {
        setText(event.target.value);
    };

    return (
        <div>
            <input type="text" value={text} onChange={handleChange} />
            <p>입력값: {text}p>
        div>
    );
}

export default InputForm;

onChange로 입력값을 실시간으로 상태에 반영했다.


4. 조건부 렌더링

React는 조건에 따라 UI를 다르게 렌더링할 수 있다:

import React, { useState } from 'react';

function Toggle() {
    const [isOn, setIsOn] = useState(false);

    return (
        <div>
            <button onClick={() => setIsOn(!isOn)}>토글button>
            {isOn ? <p>켜짐p> : <p>꺼짐p>}
        div>
    );
}

export default Toggle;

삼항 연산자로 상태에 따라 다른 내용을 표시했다.


5. 리스트 렌더링

배열 데이터를 렌더링할 때는 map을 활용한다:

import React from 'react';

function ItemList() {
    const items = ["사과", "바나나", "오렌지"];

    return (
        <ul>
            {items.map((item, index) => (
                <li key={index}>{item}li>
            ))}
        ul>
    );
}

export default ItemList;
// 
  • 사과
  • 바나나
  • 오렌지

key 속성을 추가해 React가 리스트를 효율적으로 관리하게 했다.


6. 효과 훅 (useEffect)

useEffect로 컴포넌트의 생명주기를 관리할 수 있다:

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

function Timer() {
    const [seconds, setSeconds] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => {
            setSeconds((prev) => prev + 1);
        }, 1000);
        return () => clearInterval(interval);
    }, []);

    return <p>초: {seconds}p>;
}

export default Timer;

마운트 시 타이머를 시작하고, 언마운트 시 정리했다.


7. 데이터 가져오기

API 데이터를 가져와 렌더링해보자:

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

function PostList() {
    const [posts, setPosts] = useState([]);

    useEffect(() => {
        fetch("https://jsonplaceholder.typicode.com/posts")
            .then(res => res.json())
            .then(data => setPosts(data.slice(0, 5)));
    }, []);

    return (
        <ul>
            {posts.map(post => (
                <li key={post.id}>{post.title}li>
            ))}
        ul>
    );
}

export default PostList;

fetch로 데이터를 가져와 상태에 저장하고 렌더링했다.


8. 성능과 구조에 미치는 영향

React가 앱에 어떤 영향을 주는지 보자:

- 성능: 가상 DOM으로 변경 사항만 업데이트해 빠르다.

- 구조: 컴포넌트 기반으로 코드가 모듈화되고 관리하기 쉬워진다.

컴포넌트으로 React의 유연성과 효율성이 돋보인다.


마무리

React는 컴포넌트와 상태, 훅을 통해 동적인 UI를 단순화한다. 기본 컴포넌트부터 데이터 가져오기까지 다양한 상황에서 강력하게 활용할 수 있다.


jQuery 기초 (jQuery Basics)

jQuery 기초 (jQuery Basics)

자바스크립트로 DOM을 다룰 때 jQuery를 활용하면 코드가 훨씬 간결하고 직관적이 된다. DOM 조작, 이벤트 처리, 애니메이션, AJAX 호출 등을 쉽게 구현할 수 있게 해주는 라이브러리다. 이번에는 jQuery의 기본부터 심화까지 코드와 함께 자세히 풀어보려고 한다.


jQuery를 잘 이해하면 웹 페이지의 동적 요소를 빠르게 다룰 수 있다. 하나씩 단계별로 알아보자.


jQuery 시작하기

jQuery를 사용하려면 먼저 라이브러리를 페이지에 추가해야 한다. CDN을 활용하는 방법이 간단하다:

<!-- jQuery CDN 추가 -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>

<!-- 기본 사용 -->
<script>
    $(document).ready(function() {
        console.log("jQuery 준비 완료!");
    });
</script>

$(document).ready는 DOM이 완전히 로드된 후 코드를 실행하게 해준다. 이제 jQuery를 본격적으로 다뤄보자.


1. 선택자와 DOM 조작

jQuery의 핵심은 강력한 선택자다. CSS 스타일 선택자를 사용해 요소를 쉽게 찾을 수 있다:

<div class="box">첫 번째 박스</div>
<div class="box">두 번째 박스</div>

<script>
    $(document).ready(function() {
        const $boxes = $(".box");
        $boxes.css("color", "blue");
        $boxes.text("jQuery로 변경됨");
    });
</script>
// 두 박스의 텍스트가 "jQuery로 변경됨"으로 바뀌고 파란색이 됨

$()로 요소를 선택하고, css()text()로 스타일과 내용을 바꿨다.


2. 이벤트 처리

jQuery로 이벤트 핸들링도 간단하다. 클릭 이벤트를 추가해보자:

<button id="myButton">클릭</button>
<p id="result"></p>

<script>
    $(document).ready(function() {
        $("#myButton").click(function() {
            $("#result").text("버튼이 클릭됐습니다!");
        });
    });
</script>

click() 메서드로 버튼 클릭 시 동작을 정의했다. 이벤트 연결이 매우 직관적이다.


3. 요소 추가와 제거

DOM에 요소를 동적으로 추가하거나 제거할 수도 있다:

<div id="container"></div>
<button id="add">추가</button>
<button id="remove">제거</button>

<script>
    $(document).ready(function() {
        $("#add").click(function() {
            $("#container").append('<p>새 요소</p>');
        });
        $("#remove").click(function() {
            $("#container p").remove();
        });
    });
</script>

append()로 요소를 추가하고, remove()로 삭제했다. DOM 조작이 훨씬 간소화된다.


4. 애니메이션 효과

jQuery는 애니메이션도 쉽게 구현할 수 있다:

<div id="box" style="width: 100px; height: 100px; background: orange;"></div>
<button id="animate">애니메이션</button>

<script>
    $(document).ready(function() {
        $("#animate").click(function() {
            $("#box").animate({
                width: "200px",
                height: "200px",
                opacity: 0.5
            }, 1000);
        });
    });
</script>

animate()로 크기와 투명도를 1초 동안 부드럽게 변화시켰다.


5. AJAX로 데이터 가져오기

jQuery를 사용하면 AJAX 요청도 간단하다:

<button id="fetch">데이터 가져오기</button>
<div id="result"></div>

<script>
    $(document).ready(function() {
        $("#fetch").click(function() {
            $.ajax({
                url: "https://jsonplaceholder.typicode.com/posts/1",
                method: "GET",
                success: function(data) {
                    $("#result").text(data.title);
                },
                error: function() {
                    $("#result").text("데이터를 가져오지 못했습니다.");
                }
            });
        });
    });
</script>

$.ajax로 외부 API에서 데이터를 가져와 화면에 표시했다.


6. 체이닝 활용

jQuery는 메서드 체이닝을 지원해 코드를 간결하게 만든다:

<div class="box">박스</div>

<script>
    $(document).ready(function() {
        $(".box")
            .css("color", "red")
            .text("체이닝 테스트")
            .animate({ fontSize: "24px" }, 1000);
    });
</script>

한 줄에 여러 메서드를 연결해서 순차적으로 적용했다.


7. 이벤트 위임

동적으로 추가된 요소에도 이벤트를 적용하려면 이벤트 위임을 사용한다:

<div id="list">
    <button class="item">항목 1</button>
</div>
<button id="add">항목 추가</button>

<script>
    $(document).ready(function() {
        $("#list").on("click", ".item", function() {
            console.log("항목 클릭됨");
        });
        $("#add").click(function() {
            $("#list").append('<button class="item">새 항목</button>');
        });
    });
</script>

on()으로 상위 요소에 이벤트를 위임해 새로 추가된 요소에도 동작이 적용된다.


8. 플러그인처럼 확장

jQuery는 사용자 정의 메서드를 추가할 수 있다:

<div class="box">테스트 박스</div>

<script>
    $.fn.highlight = function(color) {
        this.css("backgroundColor", color);
        return this;
    };

    $(document).ready(function() {
        $(".box").highlight("yellow");
    });
</script>

$.fn으로 새로운 메서드를 정의하고 호출했다.


9. 성능과 활용성

jQuery가 코드에 어떤 영향을 주는지 살펴보자:

- 성능: 선택자와 체이닝은 빠르지만, 복잡한 DOM 조작이 많아지면 약간의 부하가 생길 수 있다.

- 활용성: 직관적인 API로 빠르게 동적 웹을 만들 수 있다.

$() 선택자와 메서드 체이닝이 jQuery의 강력함을 보여준다.


마무리

jQuery는 DOM 조작, 이벤트, 애니메이션, AJAX를 단순화하며 웹 개발을 더 쉽게 만들어준다. 기본 선택자부터 이벤트 위임, 확장까지 다채롭게 활용할 수 있다.


Canvas API

Canvas API

브라우저에서 그래픽을 그리고 애니메이션을 만들 때 Canvas API는 매우 강력한 도구다. 2D 그래픽을 자유롭게 다룰 수 있어서 게임이나 데이터 시각화에 유용하다. 이번에는 Canvas API의 기본부터 심화된 활용까지 코드와 함께 자세히 알아보려고 한다.


Canvas API를 잘 다루면 복잡한 그래픽 작업도 쉽게 구현할 수 있다. 하나씩 차근차근 살펴보자.


Canvas 기본 설정

<canvas> 요소와 2D 컨텍스트를 사용해서 시작할 수 있다:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);

const ctx = canvas.getContext("2d");
ctx.fillStyle = "#ddd";
ctx.fillRect(0, 0, 400, 300);
// 400x300 크기의 회색 사각형 캔버스 생성

getContext("2d")로 2D 컨텍스트를 얻고, 기본 배경을 그렸다.


1. 기본 도형 그리기

사각형, 선, 원 같은 기본 도형을 그려보자:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

ctx.fillStyle = "red";
ctx.fillRect(50, 50, 100, 100);
// 빨간 사각형

ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.strokeRect(200, 50, 100, 100);
// 파란 테두리 사각형

ctx.beginPath();
ctx.strokeStyle = "green";
ctx.moveTo(50, 200);
ctx.lineTo(150, 250);
ctx.stroke();
// 초록 선

ctx.beginPath();
ctx.fillStyle = "purple";
ctx.arc(250, 200, 40, 0, Math.PI * 2);
ctx.fill();
// 보라색 원

fillRect, strokeRect, lineTo, arc로 다양한 도형을 그렸다.


2. 경로와 스타일 조합

복잡한 경로를 그리고 스타일을 적용해보자:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.strokeStyle = "orange";
ctx.lineWidth = 3;
ctx.moveTo(50, 50);
ctx.lineTo(150, 100);
ctx.lineTo(100, 150);
ctx.closePath();
ctx.fillStyle = "yellow";
ctx.fill();
ctx.stroke();
// 노란색 채우기와 주황색 테두리의 삼각형

beginPathclosePath로 경로를 만들고 채우기와 테두리를 함께 적용했다.


3. 텍스트와 이미지 추가

텍스트와 이미지를 캔버스에 그려보자:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

ctx.font = "30px Arial";
ctx.fillStyle = "black";
ctx.fillText("안녕, Canvas!", 50, 50);
ctx.strokeStyle = "red";
ctx.strokeText("안녕, Canvas!", 50, 100);
// 검은 채우기 텍스트와 빨간 테두리 텍스트

const img = new Image();
img.src = "https://via.placeholder.com/100";
img.onload = () => {
    ctx.drawImage(img, 50, 150);
};
// 100x100 이미지 그리기

fillText, strokeText로 텍스트를 그리고, drawImage로 이미지를 추가했다.


4. 변환과 애니메이션

회전, 이동, 애니메이션을 적용해보자:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

ctx.translate(200, 150);
ctx.rotate(Math.PI / 4);
ctx.fillStyle = "blue";
ctx.fillRect(-50, -50, 100, 100);
// 중앙에서 45도 회전한 파란 사각형

let angle = 0;
function animate() {
    ctx.clearRect(-200, -150, 400, 300);
    ctx.rotate(Math.PI / 180);
    angle += Math.PI / 180;
    ctx.fillStyle = "green";
    ctx.fillRect(-25, -25, 50, 50);
    requestAnimationFrame(animate);
}
animate();
// 계속 회전하는 초록 사각형

translate, rotate로 변환을 적용하고, 애니메이션은 requestAnimationFrame으로 구현했다.


5. 이벤트와 상호작용

마우스 이벤트를 캔버스에 연결해보자:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

canvas.addEventListener("mousemove", (event) => {
    const x = event.offsetX;
    const y = event.offsetY;
    ctx.clearRect(0, 0, 400, 300);
    ctx.beginPath();
    ctx.fillStyle = "orange";
    ctx.arc(x, y, 20, 0, Math.PI * 2);
    ctx.fill();
});
// 마우스를 따라다니는 주황색 원

mousemove 이벤트로 마우스 위치를 추적해서 원을 그렸다.


6. 그림자와 그라디언트

그림자와 그라디언트로 시각적 효과를 더해보자:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

ctx.shadowColor = "gray";
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.fillStyle = "blue";
ctx.fillRect(50, 50, 100, 100);
// 그림자가 있는 파란 사각형

const gradient = ctx.createLinearGradient(200, 50, 300, 150);
gradient.addColorStop(0, "red");
gradient.addColorStop(1, "yellow");
ctx.fillStyle = gradient;
ctx.fillRect(200, 50, 100, 100);
// 빨강에서 노랑으로 변하는 그라디언트 사각형

shadow 속성과 createLinearGradient로 효과를 추가했다.


7. 클리핑과 합성

클리핑 영역과 합성 모드를 사용해보자:

const canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 300;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

ctx.beginPath();
ctx.arc(200, 150, 50, 0, Math.PI * 2);
ctx.clip();
ctx.fillStyle = "pink";
ctx.fillRect(0, 0, 400, 300);
// 원형 클리핑 영역에 핑크색 채우기

ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "rgba(0, 0, 255, 0.5)";
ctx.fillRect(250, 200, 100, 100);
// 반투명 파란 사각형 합성

clip으로 그리기 영역을 제한하고, globalCompositeOperation으로 합성 방식을 조정했다.


8. 성능과 활용성 고려

Canvas API가 작업에 어떤 영향을 주는지 보자:

- 성능: 복잡한 그래픽은 자원을 많이 소모하지만, 애니메이션 최적화로 부담을 줄일 수 있다.

- 활용성: 도형, 텍스트, 이미지, 이벤트까지 다채롭게 조합 가능하다.

컨텍스트 설정requestAnimationFrame을 활용하면 동적인 그래픽을 효율적으로 다룰 수 있다.


마무리

Canvas API는 2D 그래픽을 그리기 위한 강력한 도구다. 기본 도형부터 애니메이션, 이벤트, 효과까지 다루며, 게임이나 시각화 작업에 큰 힘을 발휘한다.


Fetch API 심화 (Advanced Fetch API)

Fetch API 심화 (Advanced Fetch API)

자바스크립트에서 네트워크 요청을 다룰 때 Fetch API는 강력하고 유연한 도구다. 단순한 GET 요청부터 복잡한 설정까지, 다양한 상황에 맞춰 활용할 수 있다. 이번에는 Fetch API의 기본을 넘어 심화된 활용법을 코드와 함께 자세히 풀어보려고 한다.


Fetch API를 깊이 이해하면 요청 처리와 응답 관리를 훨씬 효율적으로 할 수 있다. 단계별로 차근차근 알아보자.


Fetch API 기본 복습

fetch는 Promise를 반환하며, 간단한 요청을 이렇게 처리할 수 있다:

fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then((response) => response.json())
    .then((data) => console.log(data.title))
    .catch((error) => console.log(error));
// "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

async/await를 사용하면 더 깔끔해진다:

async function getPost() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const data = await response.json();
    console.log(data.title);
}

getPost();
// "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

이제 여기서 더 나아가 보자.


1. 요청 옵션 커스터마이징

fetch는 두 번째 인자로 옵션 객체를 받아 요청을 세밀하게 조정할 수 있다:

async function createPost() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            title: "새 글",
            body: "내용입니다",
            userId: 1
        })
    });
    const data = await response.json();
    console.log(data);
}

createPost();
// { id: 101, title: "새 글", body: "내용입니다", userId: 1 }

POST 요청에 헤더와 본문을 추가해서 데이터를 전송했다.


2. 응답 상태 확인과 에러 처리

fetch는 404나 500 같은 오류 상태에서도 Promise를 거부하지 않으니, 상태를 직접 확인해야 한다:

async function fetchWithError(url) {
    const response = await fetch(url);
    if (!response.ok) {
        throw new Error(`HTTP 오류: ${response.status}`);
    }
    return response.json();
}

async function run() {
    try {
        const data = await fetchWithError("https://jsonplaceholder.typicode.com/posts/999");
        console.log(data);
    } catch (error) {
        console.log(error.message);
    }
}

run();
// "HTTP 오류: 404"

response.ok로 성공 여부를 확인하고, 실패 시 에러를 던졌다.


3. AbortController로 요청 중단

AbortController를 사용하면 요청을 중간에 취소할 수 있다:

async function fetchWithAbort() {
    const controller = new AbortController();
    const signal = controller.signal;

    setTimeout(() => controller.abort(), 1000);

    try {
        const response = await fetch(
            "https://jsonplaceholder.typicode.com/posts",
            { signal }
        );
        const data = await response.json();
        console.log(data);
    } catch (error) {
        if (error.name === "AbortError") {
            console.log("요청이 중단됨");
        } else {
            console.log(error.message);
        }
    }
}

fetchWithAbort();
// 1초 후 "요청이 중단됨"

1초 후 요청을 중단해서 불필요한 네트워크 작업을 막았다.


4. 스트림으로 응답 처리

Response.body를 사용하면 데이터를 스트림으로 받아올 수 있다:

async function streamResponse() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts");
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    let result = "";

    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        result += decoder.decode(value);
    }

    const data = JSON.parse(result);
    console.log(data[0].title);
}

streamResponse();
// "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"

대용량 데이터를 조각 단위로 처리해서 메모리를 효율적으로 사용했다.


5. 파일 업로드 구현

FormData를 활용해서 파일을 업로드할 수 있다:

async function uploadFile() {
    const formData = new FormData();
    const file = new File(["테스트 데이터"], "test.txt", {
        type: "text/plain"
    });
    formData.append("file", file);
    formData.append("userId", "1");

    const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
        method: "POST",
        body: formData
    });
    const data = await response.json();
    console.log(data);
}

uploadFile();
// { id: 101, ... } (서버 반영은 모의 데이터로 처리됨)

FormData로 파일과 추가 데이터를 함께 보냈다.


6. 캐싱과 요청 재사용

cache 옵션으로 캐싱 전략을 조정할 수 있다:

async function fetchWithCache() {
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1", {
        cache: "force-cache" // 캐시 강제 사용
    });
    const data = await response.json();
    console.log(data.title);
}

fetchWithCache();
// 캐시가 있으면 네트워크 요청 없이 캐시에서 가져옴

force-cache로 캐시를 우선 사용하도록 설정했다.


7. 요청 재시도 로직 추가

실패 시 재시도하는 로직을 구현해보자:

async function fetchWithRetry(url, retries = 3) {
    for (let i = 0; i < retries; i++) {
        try {
            const response = await fetch(url);
            if (!response.ok) {
                throw new Error(`HTTP 오류: ${response.status}`);
            }
            return response.json();
        } catch (error) {
            if (i === retries - 1) throw error;
            console.log(`재시도 ${i + 1} 실패: ${error.message}`);
            await new Promise((r) => setTimeout(r, 1000));
        }
    }
}

async function run() {
    try {
        const data = await fetchWithRetry("https://jsonplaceholder.typicode.com/posts/999");
        console.log(data);
    } catch (error) {
        console.log("최종 실패: " + error.message);
    }
}

run();
// "최종 실패: HTTP 오류: 404" (3번 재시도 후)

실패 시 1초 대기 후 최대 3번 재시도하도록 했다.


8. 성능과 안정성에 미치는 영향

Fetch API의 심화 활용이 코드에 어떤 영향을 주는지 살펴보자:

- 성능: 스트림 처리와 캐싱으로 대용량 데이터나 반복 요청을 최적화할 수 있다.

- 안정성: 에러 처리, 중단, 재시도로 네트워크 불안정성을 줄인다.

옵션 설정AbortController를 활용하면 요청 흐름을 완벽히 제어할 수 있다.


마무리

Fetch API는 단순한 요청을 넘어 스트림 처리, 파일 업로드, 재시도 등 다양한 상황에서 유연하게 동작한다. 옵션과 기능을 잘 조합하면 네트워크 작업을 안정적이고 효율적으로 관리할 수 있다.


+ Recent posts