Merge pull request #39 from minwook-shin/ko_translation

[WIP] Add korean translations
pull/95/head
Jen Looper 4 years ago committed by GitHub
commit ef61ca00d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,192 @@
# 프로그래밍 언어 및 도구 소개
이 강의에서는 프로그래밍 언어의 기초를 다룹니다. 여기에서 다루는 주제는 오늘 날 많은 최신 프로그래밍 언어에 적용됩니다. 'Tools of the Trade' 세션에서는 개발자에게 도움이 되는 유용한 소프트웨어에 대해 알아보겠습니다.
![Intro Programming](webdev101-programming.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
## 소개
이 강의에서 다음 내용을 다룹니다.
- 프로그래밍이란?
- 프로그래밍 언어의 타입
- 프로그램의 기본 요소
- 전문적인 개발자를 위한 유용한 소프트웨어와 도구
## 프로그래밍이란?
프로그래밍(코딩)은 컴퓨터 또는 모바일에 명령을 내리는 프로세스입니다. 프로그래밍 언어로 명령어를 작성한 후 장치에서 해석합니다. 이러한 명령어 세트는 다양한 이름으로 참조 될 수 있지만 *program*, *computer program*, *application (app)*, 그리고 *executable* 로도 인기있는 이름입니다.
*program*은 코드로 작성된 모든 것이 될 수 있습니다; 웹 사이트, 게임과 전화 앱도 프로그램입니다. 코드를 작성하지 않고 프로그램을 만들 수는 있지만, 기본 로직은 장치로 해석되며 코드로 작성되었을 가능성이 높습니다. *running* 또는 *executing code* 프로그램이 명령을 수행하고 있습니다. 현재 이 강의를 읽고있는 장치는 화면에 출력하는 프로그램을 실행하고 있습니다.
✅ 약간의 조사를 해보세요: 세계 최초의 컴퓨터 프로그래머는 누구일까요?
## 프로그래밍 언어
프로그래밍 언어는 개발자가 기기에 보낼 명령어를 빌드할 때 사용됩니다. 장치는 바이너리(0과 1)만 이해할 수 있으며, *대부분* 개발자에게는 매우 효율적인 통신 방법이 아닙니다. 프로그래밍 언어는 인간과 컴퓨터의 소통을 위한 방법입니다.
프로그래밍 언어는 각자 다른 형식으로 제공되며 다른 용도로 사용될 수 있습니다. 예를 들어 JavaScript는 주로 웹 애플리케이션에 사용되지만, Bash는 주로 운영체제에서 사용됩니다.
*저레벨 언어*는 일반적으로 기기에서 명령을 해석할 때 *고수준 언어*보다 적은 단계로 할 수 있습니다. 그러나 고수준 언어가 인기있는 이유는 가독성과 지원입니다. JavaScript는 고수준 언어로 간주됩니다.
다음 코드는 JavaScript를 사용하는 고수준 언어와 ARM 어셈블리 코드를 사용하는 저수준 언어의 차이점을 보여줍니다.
```javascript
let number = 10
let n1 = 0, n2 = 1, nextTerm;
for (let i = 1; i <= number; i++) {
console.log(n1);
nextTerm = n1 + n2;
n1 = n2;
n2 = nextTerm;
}
```
```c
area ascen,code,readonly
entry
code32
adr r0,thumb+1
bx r0
code16
thumb
mov r0,#00
sub r0,r0,#01
mov r1,#01
mov r4,#10
ldr r2,=0x40000000
back add r0,r1
str r0,[r2]
add r2,#04
mov r3,r0
mov r0,r1
mov r1,r3
sub r4,#01
cmp r4,#00
bne back
end
```
안 믿어도 되지만, *두 언어는 같은 일을 하고 있습니다* : 피보나치 수열을 최대 10개까지 출력합니다.
✅ 피보나치 수열은 각 숫자가 0과 1에서 시작하는 앞의 두 숫자의 합이되는 숫자의 집합으로 [정의](https://en.wikipedia.org/wiki/Fibonacci_number)됩니다.
## 프로그램의 요소
프로그램의 단일 명령어를 *statement*라고 불리며, 일반적으로 명령어가 끝나거나 *terminates*되는 위치를 표시하는 문자 또는 줄 간격이 있습니다. 프로그램 종료 방법은 언어마다 다릅니다.
대부분의 프로그램은 명령을 수행하기 위해 명령문을 데이터에 의존 할 수 있는 사용자 또는 다른 곳의 데이터 사용에 의존합니다. 데이터는 프로그램의 작동 방식을 변경할 수 있으므로 프로그래밍 언어에서 나중에 사용할 수 있는 데이터를 임시 저장하도록 제공합니다. 이 데이터를 *변수*라고 불립니다. 변수는 메모리에 데이터를 저장하도록 장치에 지시하는 명령입니다. 프로그램의 변수는 고유 이름을 가지며, 시간이 지남에 따라 값이 변경 될 수 있는 대수학의 변수와 유사합니다.
일부 구문이 장치에서 실행되지 않을 가능성이 있습니다. 이는 일반적으로 개발자가 코드 작성할 때 의도적으로 설계되었거나, 오류가 발생할 때 우연히 발생합니다. 이러한 유형의 애플리케이션 제어는 더 강력하게 유지될 수 ​​있도록 합니다. 일반적으로 제어를 변경하려면 특정 조건이 충족되는 순간에 발생합니다. 프로그램 실행 방법을 제어하는 ​​최신 프로그래밍 언어의 일반적인 구문은`if..else` 구문입니다.
✅ 이후 강의에서 이러한 구문의 타입에 대해 자세히 알아볼 것입니다.
## Tools of the Trade
[![Tools of the Trade](https://img.youtube.com/vi/69WJeXGBdxg/0.jpg)](https://youtube.com/watch?v=69WJeXGBdxg "Tools of the Trade")
이 세션에서는 전문적인 개발 여정을 떠날 때 매우 유용할 수 있는 일부 소프트웨어에 대해 알아봅니다.
**개발 환경**은 개발자가 소프트웨어를 작성할 때 자주 사용하는 도구 및 기능 집합입니다. 이러한 도구 중 일부는 개발자의 특정 요구에 맞게 변경되었으며, 개발자가 작업 또는 개인 프로젝트에서 우선 순위를 변경하거나 다른 프로그래밍 언어를 사용할 때 시간이 지남에 따라 변경 될 수 있습니다. 개발 환경은 이를 사용하는 개발자만큼 독특합니다.
### 에디터
소프트웨어 개발을 위한 가장 중요한 도구 중 하나는 에디터입니다. 에디터는 코드를 작성하고 때로는 코드를 실행하는 곳입니다.
개발자는 몇 가지 추가 이유로 에디터에 의존합니다.
- *디버깅* 코드를 한 줄씩 단계별로 실행하여 버그와 오류를 발견합니다. 일부 에디터에는 디버깅 기능이 있거나, 특정 프로그래밍 언어에 맞게 변경하거나 추가할 수 있습니다.
- *Syntax highlighting* 코드에 색상 및 텍스트 서식을 추가하여 읽기 쉽게 만듭니다. 대부분 에디터에는 Syntax highlighting을 허용합니다.
- *확장 및 통합* 기본 에디터에는 없는 추가 도구에 접근하기 위해 개발자를 위한 전문화된 추가 기능입니다. 예를 들어, 많은 개발자는 코드를 문서화하고 작동 방식을 설명하는 방법이 필요하며 오타를 확인하기 위해 맞춤법 검사 확장 프로그램을 설치합니다. 이러한 추가 기능의 대부분은 특정 에디터 내에서 사용하기 위한 것이며, 대부분의 에디터는 사용 가능한 확장 검색을 제공합니다.
- *커스터마이즈* 대부분의 에디터는 커스터마이즈가 가능하며 각 개발자는 자신이 필요한 개발 환경을 가지게 됩니다. 또한 많은 개발자가 자신의 확장을 만들 수 있습니다.
#### 인기있는 에디터와 웹 개발 확장
- [Visual Studio Code](https://code.visualstudio.com/)
- [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
- [Live Share](https://marketplace.visualstudio.com/items?itemName=MS-vsliveshare.vsliveshare-pack)
- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- [Atom](https://atom.io/)
- [spell-check](https://atom.io/packages/spell-check)
- [teletype](https://atom.io/packages/teletype)
- [atom-beautify](https://atom.io/packages/atom-beautify)
### 브라우저
또 다른 중요 도구는 브라우저입니다. 웹 개발자는 웹에서 코드가 어떻게 실행되는지 보기 위해 브라우저에 의존하며, HTML과 같은 에디터에서 작성된 웹 페이지의 시각적 요소를 보는데도 사용됩니다.
많은 브라우저에는 개발자가 애플리케이션에 대한 중요한 인사이트를 수집하고 잡아내는 것에 도움이 되는 유용한 기능 및 정보가 포함된 *개발자 도구* (DevTools)가 함께 제공됩니다. 예시: 웹 페이지에 오류가 있는 경우, 오류가 발생한 시기를 아는 것이 도움될 때가 있습니다. 이 정보를 잡을 수 있도록 브라우저의 DevTools를 구성 할 수 있습니다.
#### 인기있는 브라우저와 DevTools
- [Edge](https://docs.microsoft.com/microsoft-edge/devtools-guide-chromium)
- [Chrome](https://developers.google.com/web/tools/chrome-devtools/)
- [Firefox](https://developer.mozilla.org/docs/Tools)
### Command Line 도구
일부 개발자는 일상적인 작업에 그래픽 작업을 덜 하기 위해 Command Line에 의존합니다. 코드를 개발하려면 상당한 양의 코드 타이핑이 필요하며, 일부 개발자는 키보드의 흐름을 방해하지 않는 것을 선호하므로 키보드 단축키를 사용하여 데스크톱 창을 전환하여 다른 파일에서 작업하거나, 도구를 사용합니다. 대부분 작업은 마우스로 완료할 수 있지만, Command Line을 사용하는 한 가지 이점은 마우스와 키보드를 서로 바꾸지 않고도 Command Line 도구로 많은 작업을 수행할 수 있다는 것입니다. Command Line의 또 다른 이점은 사용자 지정 구성을 저장하고, 나중에 변경하거나 새 컴퓨터로 개발 할 때 그대로 가져올 수도 있다는 것입니다. 개발 환경은 각 개발자마다 다르기 때문에 일부는 Command Line 사용을 피하고 일부는 전적으로 의존하며 일부는 두 가지를 혼용하여 사용하는 것을 선호합니다.
### 인기 있는 Command Line 옵션
command line 옵션은 사용하는 운영체제에 따라 다릅니다.
*💻 = 운영체제에 사전 설치되어 있습니다.*
#### 윈도우즈
- [Powershell](https://docs.microsoft.com/powershell/scripting/overview?view=powershell-7) 💻
- [Command Line](https://docs.microsoft.com/windows-server/administration/windows-commands/windows-commands) (also known as CMD) 💻
- [Windows Terminal](https://docs.microsoft.com/windows/terminal/)
- [mintty](https://mintty.github.io/)
#### 맥OS
- [Terminal](https://support.apple.com/guide/terminal/open-or-quit-terminal-apd5265185d-f365-44cb-8b09-71a064a42125/mac) 💻
- [iTerm](https://iterm2.com/)
- [Powershell](https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-macos?view=powershell-7)
#### 리눅스
- [Bash](https://www.gnu.org/software/bash/manual/html_node/index.html) 💻
- [KDE Konsole](https://docs.kde.org/trunk5/en/applications/konsole/index.html)
- [Powershell](https://docs.microsoft.com/powershell/scripting/install/installing-powershell-core-on-linux?view=powershell-7)
#### 인기있는 Command Line 도구
- [Git](https://git-scm.com/) (💻 on most operating sytems)
- [NPM](https://www.npmjs.com/)
- [Yarn](https://classic.yarnpkg.com/en/docs/cli/)
### 문서
개발자가 새로운 것을 배우고 싶거나, 방식을 알기 위해 문서를 찾을 가능성이 높습니다. 개발자는 종종 문서에 의존하여 도구와 언어를 올바르게 사용하는 방법을 안내하고, 작동 방식에 대한 더 깊은 지식을 얻습니다.
#### 웹 개발의 인기있는 문서
- [Mozilla Developer Network](https://developer.mozilla.org/docs/Web)
- [Frontend Masters](https://frontendmasters.com/learn/)
✅ 약간의 조사를 해보세요: 이제 웹 개발자 환경의 기본 사항을 알았으므로, 웹 디자이너 환경과 비교하고 대조하십시오.
---
## 🚀 도전
일부 프로그래밍 언어를 비교하십시오. JavaScript와 자바의 특징은 무엇입니까? COBOL과 Go는 어떻습니까?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
프로그래머가 사용할 수 있는 다른 언어에 대해 조금 공부하십시오. 한 언어로 한 줄을 쓴 다음, 다른 언어로 다시 실행하십시오. 무엇을 배우나요?
## 과제
[Reading the Docs](assignment.md)

@ -0,0 +1,294 @@
# GitHub 소개
이 강의에서는 코드 변경점을 호스팅하고 관리하는 플랫폼인 GitHub의 기본 사항을 다룹니다.
![Intro to GitHub](images/webdev101-github.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
## 소개
이 강의에서는 다룹니다:
- 기계에서 수행하는 작업 추적
- 다른 사람들과 프로젝트 작업
- 오픈소스 소프트웨어에 기여하는 방법
### 작업 필요
시작하기 전에 Git이 설치되어 있는지 확인해야합니다. 터미널에서 작업:
`git --version`
만약 Git이 설치되어 있지 않다면, [Git 내려받습니다](https://git-scm.com/downloads). 그리고, 터미널에서 로컬 Git 프로필을 설정합니다:
* `git config --global user.name "your-name"`
* `git config --global user.email "your-email"`
Git이 이미 구성되어 있는지 확인하려면 다음을 입력합니다:
`git config --list`
GitHub 계정, (Visual Studio Code와 같은) 코드 에디터가 필요하며, 터미널(혹은: command prompt)을 열어야 합니다.
아직 계정이 없는 경우에는 [github.com](https://github.com/)으로 이동하여 계정을 생성하거나, 로그인하여 프로필을 작성합니다.
✅ GitHub는 유일한 코드 저장소가 아닙니다. 다른 곳들도 있지만 GitHub가 가장 잘 알려져 있습니다.
### 준비
로컬 장치(노트북 또는 PC)에 코드 프로젝트 폴더와 다른 프로젝트에 기여하는 방법의 예시가 될 GitHub 공개 저장소가 모두 필요합니다.
---
## 코드 관리
일부 코드 프로젝트에 포함된 폴더가 로컬에 있고 버전 제어 시스템인 git을 사용하여 진행 상황을 추적하려고 한다고 가정해보겠습니다. 어떤 사람들은 git을 사용하여 미래의 자신에게 연애 편지를 쓰는 것과 비교합니다. 며칠, 몇 주 또는 몇 달 후에 커밋 메시지를 읽으면 그 때 결정을 한 이유를 기억하거나 변경점을 "롤백"할 수 있습니다. 즉, 좋은 "커밋 메시지"를 작성할 때입니다.
### 작업: 저장소 만들고 코드 커밋하기
1. **GitHub에 저장소 만들기**. GitHub.com에서 repositories 탭을 보거나, 우측 상단 네비케이션 바에서 **new repo** 버튼을 찾습니다.
1. 저장소(폴더)에 이름을 지정합니다
1. **create repository** 선택합니다.
1. **작업 폴더로 이동하기**. 터미널에서 추적을 시작할 폴더(디렉토리)로 이동하기 위해 입력합니다:
```bash
cd [name of your folder]
```
1. **git 저장소 초기화하기**. 프로젝트에서 입력합니다:
```bash
git init
```
1. **상태 확인하기**. 상태를 확인하려면 저장소에서 입력합니다:
```bash
git status
```
다음과 같이 출력될 수 있습니다:
```output
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: file.txt
modified: file2.txt
```
일반적으로 `git status` 명령은 어떤 파일이 저장소에 _저장_ 될 준비가 되었는 지 또는 유지하고 싶은 변경점이 있는 지 등을 알려줍니다.
1. **추적할 파일 추가하기**
```bash
git add .
```
`git add`와 같이 있는 `.` 인수는 모든 파일 및 변경점을 나타냅니다.
1. **작업 지속하기**. Git이 파일을 추적하는 곳에 _staging area_ 라는 파일을 추가했습니다. 영구적으로 변경하려면 파일을 _commit_ 해야합니다. 그렇게 하려면`git commit` 명령으로 _commit_ 을 생성합니다. _commit_ 은 저장소 기록의 저장 시점을 나타냅니다. 다음을 입력하여 _commit_ 을 생성합니다:
```bash
git commit -m "first commit"
```
이렇게하면 모든 파일이 커밋되고, "first commit" 메시지가 추가됩니다. 향후 커밋 메시지의 경우 변경점을 전달하기 위해 설명을 구체적으로 작성해야합니다.
1. **GitHub와 로컬 Git 저장소 연결하기**. Git 저장소는 장치에 존재하기에 좋지만, 어느 시점에서 파일을 어딘가에 백업하고 다른 사람이 저장소에서 함께 작업하도록 초대하고 싶습니다. 그렇게 하기에 좋은 곳 중 하나는 GitHub입니다. 이미 GitHub에 저장소를 만들었으므로 로컬 Git 저장소를 GitHub에 연결할 뿐입니다. `git remote add` 명령을 수행합니다. 다음 명령을 입력합니다:
> Note, 명령을 입력하기 전에 GitHub 저장소 페이지로 이동하여 저장소 URL을 찾아두십시오. 아래 명령에서 사용됩니다. `repository_name`을 GitHub URL로 바꿉니다.
```bash
git remote add origin https://github.com/username/repository_name.git
```
이렇게 이전에 만든 GitHub 저장소를 가리키는 "origin"이라는 _remote_ 또는 커넥션이 생성됩니다.
1. **GitHub로 로컬 파일 보내기**. 지금까지 로컬 저장소와 GitHub 저장소 사이에 _connection_ 을 생성했습니다. 다음과 같이 `git push` 명령을 사용하여 이러한 파일을 GitHub로 보냅니다:
```bash
git push -u origin main
```
"main" 브랜치는 GitHub로 커밋이 보내집니다.
1. **더 많은 변경점 추가하기**. 계속 작업하여 GitHub로 푸시하려면 다음 세 가지 명령을 사용하면됩니다:
```bash
git add .
git commit -m "type your commit message here"
git push
```
> Tip, 추적하고 싶지 않은 파일이 GitHub에 표시되는 것을 방지하기 위해 `.gitignore` 파일을 채용할 수 있습니다. 동일한 폴더에 저장하지만 공개 저장소에는 존재하지 않는 노트 파일과 같습니다. `.gitignore` 파일의 템플릿은 [.gitignore templates](github.com/github/gitignore)에서 찾을 수 있습니다.
#### 커밋 메시지
A great Git commit subject line completes the following sentence:
If applied, this commit will <your subject line here>
훌륭한 Git 커밋 제목 줄은 다음 문장을 완성합니다:
적용되면, 이 커밋은 <your subject line here>이 됩니다.
제목에 대해서는 명령문 또는 현재 시제를 사용하십시오 : "변경됨" 또는 "변경점"이 아닌 "변경".
제목과 마찬가지로 본문(선택 사항)에서도 명령문, 현재 시제를 사용합니다. 본문은 변화에 대한 동기를 포함하고 이를 이전 변경점과 대조해야 합니다. '어떻게'가 아니라 '왜'를 설명하고 있습니다.
✅ 몇 분 동안 GitHub를 둘러보세요. 정말 훌륭한 커밋 메시지를 찾을 수 있습니까? 정말 최소한의 것을 찾을 수 있습니까? 커밋 메시지에서 전달하는 데 가장 중요하고 유용한 정보는 무엇이라고 생각하십니까?
### 작업: 협업하기
GitHub에 코드를 올리는 주 이유는 다른 개발자와 협력할 수 있도록 하기 위함입니다.
## 다른 사람들과 함께 프로젝트 작업하기
저장소에서, `Insights> Community`로 이동하여 프로젝트에 권장되는 커뮤니티 표준과 어떻게 비교되는지 확인합니다.
다음은 GitHub 저장소를 개선 할 수있는 몇 가지 사항입니다:
- **설명**. 프로젝트에 설명을 추가했습니까?
- **README**. README를 추가했습니까? GitHub는 [README](https://docs.github.com/articles/about-readmes/) 작성에 대한 지침을 제공합니다.
- **기여 가이드**. [기여 가이드](https://docs.github.com/articles/setting-guidelines-for-repository-contributors/),
- **Code of Conduct**. [Code of Conduct](https://docs.github.com/articles/adding-a-code-of-conduct-to-your-project/),
- **라이선스**. 아마도, 가장 중요한 [라이선스](https://docs.github.com/articles/adding-a-license-to-a-repository/)도 가지고 있습니까?
이러한 모든 리소스는 새로운 팀원을 온보딩하는 데 도움이 됩니다. 그리고 일반적으로 새로운 기여자들이 여러분의 프로젝트가 시간을 보내기에 적합한 장소인지 확인하기 위해 코드를 보기 전 살펴 보는 것입니다.
✅ README 파일은 준비하는 데 시간이 걸리지만 바쁜 관리자들은 종종 무시합니다. 특히 설명적인 예를 찾을 수 있습니까? Note: 몇 가지 [tools to help create good READMEs](https://www.makeareadme.com/)를 시도해 볼 수 있습니다.
### 작업: 코드 병합하기
문서를 제공하면 사람들이 프로젝트에 기여하는 데 도움이 됩니다. 찾고있는 기여 유형과 프로세스 작동 방식을 설명합니다. 기여자는 GitHub의 저장소에 기여할 수 있도록 일련의 단계를 거쳐야합니다:
1. **저장소 포크하기** 아마도 사람들이 당신의 프로젝트를 _fork_ 하기를 원할 것입니다. 포크는 자신의 GitHub 프로필에 저장소 복제본을 만드는 걸 의미합니다.
1. **복제하기**. 프로젝트를 로컬 컴퓨터에 복제합니다.
1. **브랜치 생성하기**. 작업을 위해 _branch_ 를 만들도록 요청하고 싶을 것입니다.
1. **한 영역에 변화를 집중하기**. 기여자에게 한 번에 한 가지만 집중하도록 요청하세요 - 그러면 작업에 _병합_ 할 수 있는 가능성이 더 높아집니다. 그들이 버그 수정을 작성하고, 새로운 기능을 추가하고, 여러 테스트를 추가한다고 상상해보십시오. 원한다면 3개 중 2개 또는 3개 중 1개만 구현할 수 있습니까?
✅ 좋은 코드를 작성하고 전달하는 데 branches가 중요한 상황을 상상해보십시오. 어떤 사용 사례를 생각할 수 있습니까?
> Note, 모두가 보고싶은 변경점이나, 자신의 작업만을 위한 브랜치를 만들 수도 있습니다. 모든 커밋은 현재 "체크 아웃"된 브랜치에서 이루어집니다. `git status`를 사용하여 어떤 브랜치인지 확인하십시오.
기여자 워크플로우를 살펴봅니다. 기여자가 이미 저장소를 _forked_ 하거나 _cloned_ 했기 때문에 로컬 머신에서 작업할 준비가 된 Git 저장소가 있다고 가정합니다.
1. **브랜치 생성하기**. `git branch` 명령을 사용하여 기여하려는 변경 사항을 포함하는 브랜치를 만듭니다:
```bash
git branch [branch-name]
```
1. **작업 브랜치 변경하기**. 지정된 브랜치로 전환하고 `git checkout`으로 작업 디렉토리를 업데이트합니다:
```bash
git checkout [branch-name]
```
1. **일하기**. 이 시점에서 변경 사항을 추가하려고 합니다. 다음 명령을 사용하여 Git에 알리는 것을 잊지 마시기 바랍니다:
```bash
git add .
git commit -m "my changes"
```
도와주고 있는 저장소의 관리자뿐만 아니라 당신을 위해서, 커밋에 좋은 이름을 부여해야 합니다.
1. **`main` 브랜치에서 작업하기**. 어느 시점에서 작업을 마치고 `main` 브랜치의 작업과 병합하려고 합니다. 그동안 `main` 브랜치가 변경되었을 수 있으므로, 먼저 다음 명령을 사용하여 최신 버전으로 업데이트해야합니다:
```bash
git checkout main
git pull
```
이 시점에서 Git이 변경 사항을 쉽게 _결합_ 할 수 없는 _충돌_ 상황이 작업 브랜치에서 발생하는지 확인하려고합니다. 따라서 다음 명령을 실행합니다:
```bash
git checkout [branch_name]
git merge main
```
이렇게하면 `main` 에서 브랜치로 모든 변경점을 가져올 수 있으며 계속 진행할 수 있습니다. 그렇지 않은 경우에는 VS Code는 Git이 _혼란스러운_ 위치를 알려주고 영향받는 파일을 변경하여 가장 정확한 곳을 알려주면 됩니다.
1. **GitHub로 작업 보내기**. 작업을 GitHub에 보내는 것은 두 가지를 의미합니다. 브랜치를 저장소로 푸시 한 다음 PR, Pull Request를 엽니다.
```bash
git push --set-upstream origin [branch-name]
```
위의 명령은 포크된 저장소에 브랜치를 만듭니다.
1. **PR 열기**. 다음으로, PR을 열고 싶습니다. GitHub의 포크된 저장소로 이동하면 됩니다. GitHub에서 새 PR을 만들 것인지 묻는 표시가 표시되고 이를 클릭하면 커밋 메시지 제목을 변경할 수 있는 인터페이스로 이동하게 되며 더 적절한 설명을 작성할 수 있습니다. 이제 포크한 저장소의 관리자는 이 PR을 보고 _행운을 빌며_ 감사하고 _병합_ PR을 수행합니다. 당신은 이제 기여자입니다, yay :)
1. **정라하기**. 나중에 _정리_ 하는 것은 좋은 습관으로 간주됩니다. 로컬 브랜치와 GitHub에 푸시한 브랜치를 모두 정리하려고 합니다. 먼저 다음 명령을 사용하여 로컬에서 삭제하겠습니다:
```bash
git branch -d [branch-name]
```
포크된 저장소의 GitHub 페이지로 이동하여 방금 푸시한 원격 브랜치를 제거합니다.
변경점을 프로젝트에 푸시하고 싶기 때문에 `Pull request`는 어리석은 용어처럼 보입니다. 그러나 관리자(프로젝트 소유자) 또는 핵심 팀은 변경점을 프로젝트의 "main" 브랜치와 병합하기 전에 고려해야 하므로 실제로 유지 관리자에게 결정을 요청하는 것입니다.
pull request는 리뷰, 코멘트, 통합 테스트 등을 통해 브랜치에 도입된 차이점을 비교하고 논의하는 곳입니다. 좋은 pull request는 커밋 메시지와 거의 동일한 규칙을 따릅니다. 예를 들어 작업에서 문제를 해결할 때 이슈 트래커에서 이슈에 대한 참조를 추가할 수 있습니다. 이 작업은 `#` 다음에 이슈 번호를 사용하여 수행됩니다. 예를 들어 `#97` 처럼 말입니다.
🤞 모든 검사가 통과되고 프로젝트 소유자가 변경 사항을 프로젝트에 병합한다는 손가락이 교차했습니다 🤞
현재 로컬 작업 브랜치를 GitHub의 원격 브랜치의 커밋으로 모두 업데이트합니다:
`git pull`
## 오픈소스에 기여하는 방법
먼저, GitHub에서 관심있고 변경 사항을 기여할 저장소를 찾아 보겠습니다. 그 곳의 내용을 자신의 장치에 복사하고 싶을 것입니다.
✅ '입문-친화적'인 저장소를 찾는 좋은 방법은 ['good-first-issue 태그로 검새'](https://github.blog/2020-01-22-browse-good-first-issues-to-start-contributing-to-open-source/).
![Copy a repo locally](images/clone_repo.png)
코드를 복사하는 방법에는 여러 가지가 있습니다. 한 가지 방법은 HTTPS, SSH 또는 GitHub CLI (Command Line 인터페이스)를 사용하여 저장소의 내용을 "복제"하는 것입니다.
터미널을 열고 다음과 같이 저장소를 복제합니다:
`git clone https://github.com/ProjectURL`
프로젝트에서 작업하려면 올바른 폴더로 전환합니다:
`cd ProjectURL`
[Codespaces](https://github.com/features/codespaces), GitHub의 내장 코드 에디터 / 클라우드 개발 환경 또는 [GitHub Desktop](https://desktop.github.com/)을 사용하여 전체 프로젝트를 열 수 있습니다.
마지막으로 압축된 폴더로 코드를 내려받을 수 있습니다.
### GitHub에 대한 몇 가지 흥미로운 사항
GitHub의 모든 공개 저장소에 스타 표시, 워치 및/또는 "포크" 할 수 있습니다. 우측 상단 드롭다운 메뉴에서 스타 표시한 저장소를 찾을 수 있습니다. 북마크와 비슷하지만 코드를 위한 것 입니다.
프로젝트에는 달리 명시되지 않는 한 대부분 GitHub에 "이슈" 탭의 이슈 트레커가 있습니다. 그리고 Pull Requests 탭은 사람들이 진행중인 변경 사항을 논의하고 검토하는 곳입니다.
프로젝트는 포럼, 메일링 리스트 또는 Slack, Discord 또는 IRC와 같은 채팅 채널에서 토론 할 수도 있습니다.
✅ 새로운 GitHub 리포지토리를 둘러보고 설정 편집, 저장소 정보 추가, (Kanban 보드와 같은) 프로젝트 생성을 비롯한 몇 가지 작업을 시도해보시기 바랍니다. 할 수 있는 일이 많이 있습니다!
---
## 🚀 도전
친구와 페어링하여 서로의 코드를 작업하세요. 공동으로 프로젝트를 만들고, 코드를 포크하고, 브랜치를 만들고, 변경 사항을 병합합니다.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[contributing to open source software](https://opensource.guide/how-to-contribute/#how-to-submit-a-contribution)에 대해 읽습니다.
[Git cheatsheet](https://training.github.com/downloads/github-git-cheat-sheet/).
Practice, practice, practice. GitHub에는 [lab.github.com](https://lab.github.com/)을 통해 사용할 수 있는 훌륭한 학습 경로가 있습니다:
- [First Week on GitHub](https://lab.github.com/githubtraining/first-week-on-github)
더 많은 고급 실습도 찾을 수 있습니다.
## 과제
[the First Week on GitHub training lab](https://lab.github.com/githubtraining/first-week-on-github) 완료하기

@ -0,0 +1,220 @@
# 접근 가능한 웹 페이지 생성하기
![All About Accessibility](webdev101-a11y.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
> 웹의 힘은 보편성에 있습니다. 장애에 관계없이 모든 사람이 접근하는 것은 필수 요소입니다.
>
> \- Sir Timothy Berners-Lee, W3C Director and inventor of the World Wide Web
이 인용문은 접근 가능한 웹 사이트를 만든다는 것의 중요성을 완벽하게 강조합니다. 모든 사람이 접근할 수 없는 애플리케이션은 정의에 따라 제외됩니다. 웹 개발자로서 우리는 항상 접근성을 염두에 두어야합니다. 처음부터 이 곳에 초점을 두면 모든 사람이 자신이 만든 페이지에 접근할 수 있도록 하는 데 도움이 됩니다. 이 강의에서는 웹 어셋에 접근할 수 있는지 확인하는 데 도움이 되는 도구와 접근성을 염두에 두고 빌드하는 방법에 대해 알아 봅니다.
## 사용 도구
### 스크린 리더
가장 잘 알려진 접근성 도구 중 하나는 스크린 리더입니다.
[스크린 리더](https://en.wikipedia.org/wiki/Screen_reader)는 시각 장애가 있는 사람들을 위해 일반적으로 사용되는 클라이언트입니다. 브라우저에서 공유하려는 정보를 올바르게 전달하는지 확인하는 데 시간을 할애 할 때 스크린 리더도 동일한 작업을 수행하도록 해야합니다.
가장 기본적인 스크린 리더는 페이지를 위에서 아래로 소리내며 읽습니다. 페이지가 모두 텍스트인 경우 리더는 브라우저와 유사한 방식으로 정보를 전달합니다. 물론 웹 페이지는 문자 그대로가 아닌 텍스트가 있습니다. 여기에는 링크, 그래픽, 색상 및 기타 시각적 구성 요소가 포함됩니다. 스크린 리더가 정보를 올바르게 읽을 수 있도록 주의를 기울여야 합니다.
모든 웹 개발자는 스크린 리더에 익숙해야합니다. 위에서 강조한 것처럼 사용자가 활용할 클라이언트입니다. 브라우저가 작동하는 방식에 익숙한 것과 마찬가지로 스크린 리더가 작동하는 방식을 배워야합니다. 다행히 스크린 리더는 대부분의 운영체제에 내장되어 있으며, 많은 브라우저에는 스크린 리더를 모방하는 확장이 포함되어 있습니다.
✅ 스크린 리더 브라우저 확장이나 도구를 시도해보세요.[JAWS](https://webaim.org/articles/jaws/)는 윈도우에서만 작동합니다. 브라우저에는 이러한 목적으로 사용할 수 있는 내장 도구도 있습니다; [these accessibility-focused Edge browser tools](https://support.microsoft.com/en-us/help/4000734/microsoft-edge-accessibility-features) 확인합니다.
### Contrast checkers
웹 사이트의 색상은 색맹 혹은 저대비 색상을 보기 어려운 사람들의 요구에 맞게 신중하게 선택해야 합니다.
✅ [WCAG's color checker](https://microsoftedge.microsoft.com/addons/detail/wcag-color-contrast-check/idahaggnlnekelhgplklhfpchbfdmkjp?hl=en-US)와 같은 브라우저 확장 프로그램을 사용하여 어떤 색상을 쓰는 지 웹 사이트를 테스트합니다. 무엇을 배울 수 있나요?
### Lighthouse
브라우저의 개발자 도구에서 Lighthouse 도구를 찾을 수 있습니다. 이 도구는 웹 사이트의 접근성(기타 분석)을 처음으로 파악하는 데 중요합니다. Lighthouse에만 의존하지 않는 것은 중요하지만, 100점을 기준으로 보면 매우 유용합니다.
✅ 브라우저의 개발자 도구 패널에서 Lighthouse를 찾아 모든 사이트에서 분석을 실행하세요. 무엇을 발견했나요?
## 접근성을 위한 디자인
접근성은 비교적 큰 주제입니다. 도움을 주기 위한 다양한 리소스들이 있습니다.
- [Accessible U - University of Minnesota](https://accessibility.umn.edu/your-role/web-developers)
접근 가능한 사이트를 만드는 모든 사항을 다룰 수 없지만, 이는 구현하려는 핵심 원칙 중 일부입니다. 처음부터 접근 가능한 페이지를 기획하는 것은 접근할 수 있도록 기존 페이지로 돌아가는 것보다 **항상** 쉽습니다.
## 좋은 디스플레이 원칙
### 색상 안전 팔레트
사람들은 세상을 다른 방식으로 봅니다. 여기에는 색상도 포함됩니다. 사이트에 대한 색상 스키마를 선택할 때 모든 사람이 접근할 수 있는지 확인해야 합니다. [색상 팔레트를 생성하는 한 가지 훌륭한 도구는 Color Safe](http://colorsafe.co/)입니다.
✅ 색상을 사용할 때 매우 문제가 있는 웹 사이트를 식별하십시오. 왜 해야될까요?
### 텍스트를 적절하게 강조하기
색상, [font weight](https://developer.mozilla.org/docs/Web/CSS/font-weight) 또는 기타 [text decoration](https://developer.mozilla.org/docs/Web/CSS/text-decoration)으로 텍스트를 강조하는 것은 본질적으로 스크린 리더에 중요하다고 알리지 않습니다. 단어는 키워드이기 때문에 굵게 표시되었거나 첫 번째 단어 혹은 디자이너가 굵게 표시해야한다고 결정했기 때문입니다.
특정 구문을 강조 표시해야 하는 경우에는, `<strong>` 또는 `<em>` 요소를 사용하세요. 이는 스크린 리더에 해당 항목이 중요함을 나타냅니다.
### 올바른 HTML 사용하기
CSS와 JavaScript를 사용하면 많은 요소가 모든 유형를 제어할 수 있는 것처럼 보일 수 있습니다. `<span>` 은`<button>` 을 만드는 데 사용할 수 있으며, `<b>` 는 하이퍼 링크가 될 수 있습니다. 스타일링이 더 쉽다고 생각될 수 있지만, 스크린 리더에게는 당황스럽습니다. 페이지에 컨트롤을 만들 때는 적절한 HTML을 사용하십시오. 하이퍼 링크를 원하면 `<a>` 를 사용하세요. 올바른 제어를 위해 올바른 HTML을 사용하는 것은 Semantic HTML이라고 의미합니다.
✅ 웹 사이트로 이동하여 디자이너와 개발자가 HTML을 올바르게 사용하고 있는지 확인하십시오. 링크의 역할을 하는 버튼을 찾을 수 있나요? Hint: 기반 코드를 보려면 브라우저에서 마우스 우측 버튼을 클릭하고 'View Page Source'를 선택하십시오.
### 좋은 시각적 단서를 사용하기
CSS는 페이지에 있는 모든 요소의 형태를 완벽하게 제어합니다. 윤곽선없이 텍스트 상자를 만들거나 밑줄없이 하이퍼 링크를 만들 수 있습니다. 불행히도 이러한 단서를 제거하면 그 단서를 의존하는 사람이 제어 유형을 인식하는 것이 더 어려워질 수 있습니다.
## 링크 텍스트의 중요성
하이퍼 링크는 웹 탐색의 핵심 기능입니다. 결과적으로 스크린 리더가 링크를 제대로 읽을 수 있도록 한다면 모든 사용자가 사이트를 탐색할 수 있습니다.
### 스크린 리더 및 링크
예상대로 스크린 리더는 페이지 내부의 다른 텍스트를 읽는 것과 같은 방식으로 링크 텍스트를 읽습니다. 이를 염두하고 아래 설명된 텍스트는 완벽하게 수용 가능하다고 느낄 수 있습니다.
> The little penguin, sometimes known as the fairy penguin, is the smallest penguin in the world. [Click here](https://en.wikipedia.org/wiki/Little_penguin) for more information.
> The little penguin, sometimes known as the fairy penguin, is the smallest penguin in the world. Visit https://en.wikipedia.org/wiki/Little_penguin for more information.
> **NOTE** 읽으려고 한다면, 위와 같은 링크는 **절대** 만들지 않아야 합니다.
다시 말하지만, 스크린 리더는 브라우저마다 기능이 다른 인터페이스입니다.
### URL 사용 문제점
스크린 리더는 텍스트를 읽습니다. 텍스트에 URL이 표시되면, 스크린 리더가 URL을 읽습니다. 일반적으로 URL은 의미있는 정보를 전달하지 않으며, 불편하게 들릴 수 있습니다. 스마트폰에서 URL이 포함된 문자 메시지를 음성으로 읽은 적이 있다면 이 문제를 경험했을 수 있습니다.
### "click here"의 문제점
스크린 리더는 또한 시력이 있는 사람이 페이지에서 링크를 찾는 것과 동일한 방식으로 페이지의 하이퍼 링크만 읽을 수 있습니다. 링크 텍스트가 항상 "click here"인 경우 사용자는 "click here, click here, click here, click here, click here, ..." 라는 소리를 들을 수 있습니다. 이는 모든 링크를 서로 구별할 수 없습니다.
### 좋은 링크 텍스트
좋은 링크 텍스트는 링크의 다른 측면에 있는 내용을 간략하게 설명합니다. 작은 펭귄에 대해 이야기하는 예시에서 링크는 종에 대한 Wikipedia 페이지로 연결됩니다. *little penguins* 라는 문구는 누군가가 링크를 클릭하면 무엇을 배울 수 있는지를 명확하게 해주기 때문에 완벽한 링크 텍스트가 됩니다.-little penguins.
> The [little penguin](https://en.wikipedia.org/wiki/Little_penguin), sometimes known as the fairy penguin, is the smallest penguin in the world.
✅ 모호한 연결 전략을 사용하는 페이지를 찾으려면 몇 분 동안 웹을 검색하십시오. 연결이 더 좋은 다른 사이트와 비교하십시오. 무엇을 배울 수 있나요?
#### 검색 엔진 노트
모든 사람이 사이트에 접근할 수 있도록 하면 추가 보너스로 검색 엔진이 사이트를 탐색하는데도 도움이 됩니다. 검색 엔진은 링크 텍스트를 사용하여 페이지 주제를 학습합니다. 따라서 좋은 링크 텍스트를 사용하면 모두에게 도움이 됩니다!
### ARIA
다음 페이지를 상상해보시기 바랍니다:
| Product | Description | Order |
| ------------ | ------------------ | ------------ |
| Widget | [Description]('#') | [Order]('#') |
| Super widget | [Description]('#') | [Order]('#') |
이 예시에서는 설명 텍스트와 순서를 복사하는 것이 브라우저를 사용하는 사람에게 의미가 있습니다. 그러나 스크린 리더를 사용하는 사람은 문맥없이 반복되는 *설명**순서* 라는 단어만 듣게됩니다.
이러한 유형의 시나리오를 지원하기 위해 HTML은 [Accessible Rich Internet Applications (ARIA)](https://developer.mozilla.org/docs/Web/Accessibility/ARIA)라는 속성 집합을 지원합니다. 이러한 속성을 사용하면 스크린 리더에 추가 정보를 제공할 수 있습니다.
> **NOTE**: HTML의 여러 측면과 마찬가지로, 브라우저 및 스크린 리더 지원은 다를 수 있습니다. 그러나 대부분의 주요 클라이언트는 ARIA 속성을 지원합니다.
페이지 형식이 허용하지 않는 경우에는 `aria-label`을 사용하여 링크에 설명할 수 있습니다. 위젯에 대한 설명은 다음과 같이 설정할 수 있습니다
``` html
<a href="#" aria-label="Widget description">description</a>
```
✅ 일반적으로, 위에서 설명한 시맨틱 마크업을 사용하는 것은 ARIA의 사용을 대체하지만, 때때로 다양한 HTML 위젯에 해당하는 시맨틱이 없습니다. 좋은 예는 Progressbar입니다. 진행률 표시줄에 해당하는 HTML이 없으므로 적절한 역할 및 aria 값을 사용하여 일반 `<div>`를 식별합니다. [AMDN documentation on ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)에 더 유용한 정보가 포함되어 있습니다.
```html
<div id="percent-loaded" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100">
</div>
```
## 이미지
스크린 리더는 이미지에 있는 내용을 자동으로 읽을 수 없습니다. 이미지에 접근할 수 있는지 확인하는 데 많은 작업이 필요하지 않습니다. 바로 `alt` 속성이 끝입니다. 모든 이미지에는 이미지를 설명하는 `alt`가 있어야합니다.
✅ 예상대로, 검색 엔진은 이미지의 내용을 이해할 수 없습니다. 그래서 대체 텍스트를 사용합니다. 다시 한 번, 페이지에 접근할 수 있는지 확인하면 추가 보너스가 제공됩니다!
## 키보드
일부 사용자는 마우스 또는 트랙패드를 사용할 수 없으며, 대신 키보드로 의존하여 한 요소에서 다음 요소로 이동합니다. 사용자가 문서를 아래로 이동할 때 키보드가 각 요소에 접근할 수 있도록 웹 사이트에서 콘텐츠를 논리적 순서로 표시하는 것이 중요합니다. 시맨틱 마크업으로 웹 페이지를 만들고 CSS를 사용하여 시각적 레이아웃 스타일을 지정하는 경우, 사이트에서 키보드로 탐색할 수 있어야 하지만, 이 사항은 수동으로 테스트하는 것이 중요합니다. [keyboard navigation strategies](https://webaim.org/techniques/keyboard/)에 대해 자세히 알아보세요.
✅ 웹 사이트로 이동하여 탭 키만 사용하여 탐색해보십시오. 작동하는 것과 작동하지 않는 것은 무엇입니까? 왜 그럴까요?
## 요약
일부가 접근할 수 있는 웹은 진정한 'world-wide web'이 아닙니다. 만든 사이트에 접근할 수 있도록 하는 가장 좋은 방법은 처음부터 접근성 모범 사례를 통합하는 것입니다. 추가 단계가 포함되어 있지만 이러한 기술을 워크 플로우에 통합하면 만드는 모든 페이지에 접근할 수 있습니다.
---
## 🚀 도전
이 HTML을 가져와서 학습한 내용에 따라 가능한 하나의 접근을 할 수 있도록 다시 작성하십시오.
```html
<!DOCTYPE html>
<html>
<head>
<title>
Example
</title>
<link href='../assets/style.css' rel='stylesheet' type='text/css'>
</head>
<body>
<div class="site-header">
<p class="site-title">Turtle Ipsum</p>
<p class="site-subtitle">The World's Premier Turtle Fan Club</p>
</div>
<div class="main-nav">
<p class="nav-header">Resources</p>
<div class="nav-list">
<p class="nav-item nav-item-bull"><a href="https://www.youtube.com/watch?v=CMNry4PE93Y">"I like turtles"</a></p>
<p class="nav-item nav-item-bull"><a href="https://en.wikipedia.org/wiki/Turtle">Basic Turtle Info</a></p>
<p class="nav-item nav-item-bull"><a href="https://en.wikipedia.org/wiki/Turtles_(chocolate)">Chocolate Turtles</a></p>
</div>
</div>
<div class="main-content">
<div>
<p class="page-title">Welcome to Turtle Ipsum.
<a href="">Click here</a> to learn more.
</p>
<p class="article-text">
Turtle ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum
</p>
</div>
</div>
<div class="footer">
<div class="footer-section">
<span class="button">Sign up for turtle news</span>
</div><div class="footer-section">
<p class="nav-header footer-title">
Internal Pages
</p>
<div class="nav-list">
<p class="nav-item nav-item-bull"><a href="../">Index</a></p>
<p class="nav-item nav-item-bull"><a href="../semantic">Semantic Example</a></p>
</div>
</div>
<p class="footer-copyright">&copy; 2016 Instrument</span>
</div>
</body>
</html>
```
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
많은 정부 기관에는 접근성 요구사항에 관한 법률이 있습니다. 자신의 나라에 해당하는 접근성 법률을 읽어보십시오. 보장되거나 안되는 항목은 무엇입니까? [this government web site](https://accessibility.blog.gov.uk/) 예시 입니다.
## 과제
[Analyze a non-accessible web site](assignment.md)
크레딧: [Turtle Ipsum](https://github.com/Instrument/semantic-html-sample) by Instrument

@ -0,0 +1,17 @@
# 웹 개발 시작하기
커리큘럼의 해당 세션에서는 프로젝트 기반이 아닌 전문 개발자가 되는 데 중요한 개념을 소개합니다.
### 토픽
1. [프로그래밍 언어 및 도구 소개](1-intro-to-programming-languages/README.md)
2. [GitHub의 기초](2-github-basics/README.md)
3. [접근성의 기초](3-accessibility/README.md)
### 크레딧
Basics of Accessibility was written with ♥️ by [Christopher Harrison](https://twitter.com/geektrainer)
Introduction to GitHub was written with ♥️ by [Floor Drees](https://twitter.com/floordrees)
Introduction to Programming Languages and Tools of the Trade was written with ♥️ by [Jasmine Greenaway](https://twitter.com/paladique)

@ -0,0 +1,196 @@
# JavaScript 기초: 데이터 타입
![JavaScript Basics - Data types](images/webdev101-js-datatypes.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
해당 강의에서는 웹에서 상호작용을 제공하는 언어인 JavaScript의 기초를 다룹니다.
[![Data types in JavaScript](https://img.youtube.com/vi/JNIXfGiDWM8/0.jpg)](https://youtube.com/watch?v=JNIXfGiDWM8 "Data types in JavaScript")
변수와 변수를 채워주는 데이터 타입부터 시작합니다!
## 변수
변수는 코드에서 사용하며 변경할 수 있는 값을 저장합니다.
변수를 만들고 **선언** 하는 구문은 **[keyword] [name]** 입니다. 두 부분으로 이루어집니다:
- **키워드**. 키워드는 `let` 또는 `var`로 지정할 수 있습니다.
> Note, 키워드 `let`은 ES6에서 도입되었으며 변수에 _block scope_ 를 제공합니다. `var`보다` let`을 사용하는 것이 좋습니다. 향후 부분에서 블록 범위를 더 자세히 다룰 것입니다.
- **변수 이름**, 스스로 선택한 이름입니다.
### 작업 - 변수와 작업하기
1. **변수 선언하기**. `let` 키워드를 사용하여 변수를 선언해봅시다:
```javascript
let myVariable;
```
이제 `myVariable``let` 키워드를 사용하여 선언되었습니다. 현재는 값을 가지고 있지 않습니다.
1. **값 할당하기**. `=` 연산자로 변수에 값을 저장합니다, 예상 값이 따릅니다.
```javascript
myVariable = 123;
```
> Note: 이 강의에서 `=`을 사용한다는 것은 변수에 값을 지정하는 데 사용되는 "할당 연산자" 로 사용되는 것을 의미합니다. 같다는 의미가 아닙니다.
`myVariable`는 이제 123 값으로 *초기화* 되었습니다.
1. **리펙토링**. 코드를 다음 구문으로 바꿉니다.
```javascript
let myVariable = 123;
```
위는 변수가 선언되면서 동시에 값이 할당되는 순간을 _명시적 초기화_ 라고 합니다.
1. **변수 값 변경하기**. 다음과 같이 변수 값을 변경합니다:
```javascript
myVariable = 321;
```
변수가 선언되면, `=` 연산자와 새 값을 사용하여 코드에서 언제든지 값을 변경할 수 있습니다.
✅ 시도해보세요! 브라우저에서 바로 JavaScript를 작성할 수 있습니다. 브라우저 창을 열고 개발자 도구로 이동합니다. 콘솔에서, 프롬프트를 찾을 수 있습니다; `let myVariable = 123` 을 입력하고 Return 키를 누른 다음, `myVariable`을 입력합니다. 무슨 일이 일어났나요? 이후 강의에서 이러한 개념에 대해 자세히 알아볼 것 입니다.
## 상수
상수를 선언하고 초기화하는 것은 `const` 키워드를 제외하면 변수와 동일한 개념을 따릅니다. 상수는 일반적으로 모두 대문자로 선언됩니다.
```javascript
const MY_VARIABLE = 123;
```
상수는 변수와 유사하지만, 두 차이점이 있습니다:
- **반드시 값이 있어야 합니다**. 상수는 초기화를 하지 않는다면, 코드 실행 중에 오류가 발생합니다.
- **참조는 변경 불가합니다**. 상수 참조는 초기화된 후에는 변경할 수 없으며, 코드 실행 중에 오류가 발생합니다. 두 가지 예를 살펴 보겠습니다:
- **Simple value**. 허용하지 않습니다:
```javascript
const PI = 3;
PI = 4; // not allowed
```
- **객체 참고는 보호됩니다**. 허용하지 않습니다:
```javascript
const obj = { a: 3 };
obj = { b: 5 } // not allowed
```
- **객체 값은 보호되지 않습니다**. 허용합니다:
```javascript
const obj = { a: 3 };
obj.a = 5; // allowed
```
위는 객체의 값을 변경하지만 참조 자체는 변경하지 않으므로, 허용됩니다.
> Note, `const`는 참조가 다시 할당되지 않도록 보호한다는 점을 의미합니다. 값은 _immutable_ 이 아니며 객체와 같은 복잡한 구조인 경우, 변경될 수 있습니다.
## 데이터 타입
변수는 숫자 및 텍스트와 같은, 다양한 타입의 값을 저장할 수 있습니다. 이러한 다양한 타입의 값을 **데이터 타입**이라고 합니다. 데이터 타입은 개발자가 코드 작성 방법과 소프트웨어 실행 방법에 대한 결정을 내리는 데 도움이 되므로 소프트웨어 개발의 중요한 부분입니다. 또한 일부 데이터 타입에는 값에서 추가 정보를 변환하거나 추출하는 데 도움이 되는 기능이 있습니다.
✅ 데이터 타입은 언어에서 제공하는 저수준 데이터 타입이기 때문에 JavaScript data primitives 라고 합니다. 6개의 기본 데이터 유형이 있습니다: 문자열, 숫자, bigint, 논리 자료형, undefined 그리고 심볼. 몇 분을 투자하여 각 기본 요소가 무엇을 나타낼 수 있는지 시각화하십시오. `zebra`은 무엇입니까? `0`? `true`는 어떤가요?
### 숫자
이전 세션에서, `myVariable`의 값은 숫자 데이터 유형입니다.
`let myVariable = 123;`
변수는 소수 또는 음수를 포함하여 모든 유형의 숫자를 저장할 수 있습니다. 숫자는 [next section](#operators)에서 다룰 산술 연산자와 함께 사용할 수도 있습니다.
### 산술 연산자
산술 함수를 수행할 때 사용할 여러 유형의 연산자가 있으며, 일부는 다음과 같습니다:
| Symbol | Description | Example |
| ------ | ------------------------------------------------------------------------ | -------------------------------- |
| `+` | **Addition**: Calculates the sum of two numbers | `1 + 2 //expected answer is 3` |
| `-` | **Subtraction**: Calculates the difference of two numbers | `1 - 2 //expected answer is -1` |
| `*` | **Multiplication**: Calculates the product of two numbers | `1 * 2 //expected answer is 2` |
| `/` | **Division**: Calculates the quotient of two numbers | `1 / 2 //expected answer is 0.5` |
| `%` | **Remainder**: Calculates the remainder from the division of two numbers | `1 % 2 //expected answer is 1` |
✅ 시도해보세요! 브라우저의 콘솔에서 산술 연산을 시도하십시오. 놀라운 결과인가요?
### 문자열
문자열은 작은 따옴표 또는 큰 따옴표 사이에 있는 문자의 세트입니다.
- `'This is a string'`
- `"This is also a string"`
- `let myString = 'This is a string value stored in a variable';`
문자열을 만들 때 따옴표를 사용해야합니다. 그렇지 않으면 JavaScript가 변수명으로 간주합니다.
### 문자열 서식
문자열은 텍스트이며, 때때로 포맷팅이 필요합니다.
둘 이상의 문자열을 **연결**하거나 결합하려면 `+` 연산자를 사용하세요.
```javascript
let myString1 = "Hello";
let myString2 = "World";
myString1 + myString2 + "!"; //HelloWorld!
myString1 + " " + myString2 + "!"; //Hello World!
myString1 + ", " + myString2 + "!"; //Hello, World!
```
✅ 왜 JavaScript에서 `1 + 1 = 2`이지만, `'1'+ '1'= 11` 일까요? `'1' + 1`은 어떻습니까?
**템플릿 리터럴** 따옴표 대신 backtick이 사용된다는 점을 제외하면, 문자열을 형식화하는 또 다른 방법입니다. 일반 텍스트가 아닌 모든 것은 placeholders `${ }`안에 배치해야합니다. 여기에는 문자열일 수도 있는 모든 변수가 포함됩니다.
```javascript
let myString1 = "Hello";
let myString2 = "World";
`${myString1} ${myString2}!` //Hello World!
`${myString1}, ${myString2}!` //Hello World!
```
어느 방법이든지 포맷팅 목표를 달성할 수 있지만, 템플릿 리터럴은 모든 공백과 줄 바꿈을 고려합니다.
✅ 템플릿 리터럴과 일반적인 문자열은 언제 사용합니까?
### 논리 자료형
논리 자료형은 `true` 또는 `false` 두 가지 값만 가능합니다. 논리 자료형은 특정 조건이 충족되며 실행할 코드 라인을 결정하는 순간 도움이 될 수 있습니다. 대부분의 경우, [operators](#operators)는 논리 연산자 값을 설정하는 데 도움이 되며, 변수 초기화되거나 해당 값이 연산자로 업데이트되는 것을 알고 쓰는 경우가 많습니다.
- `let myTrueBool = true`
- `let myFalseBool = false`
✅ 변수가 논리 자료형이 `true`로 설정되면 '참'으로 간주될 수 있습니다. 흥미롭게도, JavaScript에서는 [거짓으로 정의되지 않는다면 모든 값은 참입니다](https://developer.mozilla.org/en-US/docs/Glossary/Truthy).
---
## 🚀 도전
JavaScript는 때때로 놀라운 방법으로 데이터 타입을 처리하는 것으로 유명합니다. 'gotchas'에 대해 약간 알아보세요. 예시: 대소문자 구분은 물릴 수 있습니다! 콘솔에서 다음을 시도하십시오: `let age = 1; let Age = 2; age == Age` (resolves `false` -- why?). 다른 문제를 찾을 수 있습니까?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[이 JavaScript 실습 목록](https://css-tricks.com/snippets/javascript/)을 살펴보고 시도해보십시오. 무엇을 배울 수 있나요?
## 과제
[Data Types Practice](assignment.md)

@ -0,0 +1,192 @@
# JavaScript 기초: 메소드와 함수
![JavaScript Basics - Functions](images/webdev101-js-functions.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
코드 작성에 대해 생각할 때 항상 코드를 읽을 수 있도록 해야합니다. 직설적이지 않지만, 코드는 작성된 것보다 더 많이 읽힙니다. 개발자의 툴 박스에서 유지관리 가능한 코드를 보장하는 핵심 도구는 **함수**입니다.
[![Methods and Functions](https://img.youtube.com/vi/XgKsD6Zwvlc/0.jpg)](https://youtube.com/watch?v=XgKsD6Zwvlc "Methods and Functions")
## 함수
핵심으로, 함수는 요청 시 실행할 수 있는 코드 블록입니다. 동일한 작업을 여러 번 수행하는 시나리오에 적합합니다; 로직을 여러 위치에 복사하는 대신(특정 시기에 업데이트하기 어렵게 만들 수 있음), 한 위치에 로직을 집중하고, 작업이 필요할 때마다 호출할 수 있습니다. 다른 함수에서 함수를 호출할 수도 있습니다!.
함수의 이름을 지정하는 능력도 중요합니다. 사소할 수 있지만, 이름은 코드 문서화를 빠르게 해줍니다. 버튼의 레이블로 생각할 수도 있습니다. "타이머 취소"라는 버튼을 클릭하면, 시계가 중지된다는 것을 알 수 있습니다.
## 함수 만들고 호출하기
함수 구문은 다음과 같습니다:
```javascript
function nameOfFunction() { // function definition
// function definition/body
}
```
인사말 출력하는 함수를 만들고 싶다면, 다음과 같이 보일 수 있습니다:
```javascript
function displayGreeting() {
console.log('Hello, world!');
}
```
함수를 호출(또는 invoke)할 때마다, 함수 이름 뒤에 `()`를 사용합니다. 함수를 호출하기 전후에 정의할 수 있다는 사실에 주목할 가치가 있습니다; JavaScript 컴파일러가 찾을 것입니다.
```javascript
// calling our function
displayGreeting();
```
> **NOTE:** 이미 사용하고 있는 **메소드**라는 특별한 타입의 함수가 있습니다! 실제로, 데모에서 `console.log`를 사용할 때 보았습니다. 메소드가 함수와 다른 점은 메소드가 객체(`console`)에 연결되어있는 반면에, 함수는 free floating 상태라는 것입니다. 많은 개발자들이 같은 의미로 사용하는 걸 듣게 될 것입니다.
### 좋은 함수 예시
함수를 만들 때 알아야 할 몇 가지 좋은 사례가 있습니다
- 항상 그렇듯이, 설명이 포함된 이름을 사용해서 함수가 수행하는 작업을 알 수 있습니다
- **camelCasing** 를 사용하여 단어 결합
- 특정 작업에 맞춘 함수 유지
## 함수에 정보 전달하기
함수를 더 재사용하기 위해 종종 정보를 전달하고 싶을 것입니다. 아래의 `displayGreeting` 예시를 고려하면 **Hello, world!** 만 출력됩니다. 만들 수 있는 가장 유용한 함수는 아닙니다. 인사할 사람의 이름을 정하는 것 처럼 더 유연하게 만들고 싶다면, 매개 변수를 추가할 수 있습니다. 매개 변수(**인수**)는 함수에 전달되는 추가 정보입니다.
매개 변수는 괄호 안에 나열되며 쉼표로 구분됩니다.
```javascript
function name(param, param2, param3) {
}
```
`displayGreeting` 함수를 업데이트해서 이름을 받아 출력할 수 있습니다.
```javascript
function displayGreeting(name) {
const message = `Hello, ${name}!`;
console.log(message);
}
```
함수를 호출하고 매개 변수를 전달하려면, 괄호 안에 지정합니다.
```javascript
displayGreeting('Christopher');
// displays "Hello, Christopher!" when run
```
## 기본 값
더 많은 매개 변수를 추가하여 힘수를 유연하게 만들 수 있습니다. 그러나 모든 값을 지정하지 않으려면 어떻게 해야할까요? 인사 예시를 유지하면서 필요하면 이름을 남길 수 있지만 (인사말하는 사람을 알아야 합니다), 원하는대로 인사를 커스터마이징할 수 있습니다. 누군가 커스터마이징을 원하지 않는 경우에는 대신 기본값을 제공합니다. 매개 변수에 기본값을 제공하기 위해서는 변수에 대한 값을 지정하는 것과 같은 방식으로 설정합니다 - `parameterName = 'defaultValue'`. 전체 예시를 보십시오:
```javascript
function displayGreeting(name, salutation='Hello') {
console.log(`${salutation}, ${name}`);
}
```
함수를 호출할 때, `salutation`에 대한 값을 설정할 것인지 결정할 수 있습니다.
```javascript
displayGreeting('Christopher');
// displays "Hello, Christopher"
displayGreeting('Christopher', 'Hi');
// displays "Hi, Christopher"
```
## 반환 값
지금까지 만든 함수는 항상 [console](https://developer.mozilla.org/en-US/docs/Web/API/console)에 출력됩니다. 특히 다른 서비스를 호출할 함수를 만들 때도 이것이 찾고 있을 수 있습니다. 하지만 계산을 하고 값을 다른 곳에 다시 제공하고자 헬퍼 함수를 만들고 싶으면 어떻게 해야합니까?
**반환 값**을 사용하면 할 수 있습니다. 반환 값은 함수에 의해 반환되며, 문자열이나 숫자와 같은 리터럴 값을 저장할 수 있고 똑같은 변수에 저장할 수 있습니다.
함수가 무언가 반환하면 `return` 키워드가 사용됩니다. `return` 키워드는 다음과 같이 반환되는 항목의 값 또는 참조를 예상합니다:
```javascript
return myVariable;
```
인사 메시지를 만들고 호출하는 곳에 값을 반환해주는 함수를 만들 수 있습니다.
```javascript
function createGreetingMessage(name) {
const message = `Hello, ${name}`;
return message;
}
```
이 함수를 호출하면 값을 변수에 저장합니다. 변수를 정적 값(`const name = 'Christopher'`)으로 지정하는 것과 거의 동일합니다.
```javascript
const greetingMessage = createGreetingMessage('Christopher');
```
## 함수의 파라미터에서 함수
프로그래밍 경력을 쌓으면서, 함수를 매개 변수로 받는 함수를 보게 될 것 입니다. 이 깔끔한 트릭은 일반적으로 어떤 일이 발생되거나 완료되는 때를 알지 못하지만, 이에 반응하여 작업해야 한다는 것을 알고있을 때 사용됩니다.
예시로, 타이머를 시작하고 완료되면 코드를 실행하는 [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout)을 고려해보세요. 실행하려는 코드를 먼저 알려줘야 합니다. 함수에 대한 완벽한 직업인 것 같습니다!
아래 코드를 실행하면, 3초 후에 **3 seconds has elapsed**는 메시지가 표시됩니다.
```javascript
function displayDone() {
console.log('3 seconds has elapsed');
}
// timer value is in milliseconds
setTimeout(3000, displayDone);
```
### 익명 함수
우리가 만든 것을 다시 보겠습니다. 한 번 사용할 이름으로 함수를 만들고 있습니다. 애플리케이션이 점점 복잡해지면서 한 번만 호출되는 함수를 많이 생성하는 것을 볼 수 있습니다. 이상적이지 않습니다. 결과적으로 항상 이름을 제공 할 필요가 없습니다!
함수를 매개 변수로 전달할 때 미리 하나를 생성하지 않고 대신 매개 변수의 일부로 만들 수 있습니다. 동일한 `function` 키워드를 사용하지만 대신 매개 변수로 작성합니다.
익명 함수를 사용하도록 위 코드를 다시 작성해 보겠습니다:
```javascript
setTimeout(3000, function() {
console.log('3 seconds has elapsed');
});
```
새 코드를 실행해도 동일한 결과를 얻을 수 있습니다. 함수를 만들었지만, 이름을 지정할 필요가 없어졌습니다!
### 화살표 함수
많은 프로그래밍 언어(JavaScript 등)에서 흔히 볼 수 있는 한 가지 단축키는 **화살표** 또는 **fat 화살표** 함수를 사용하는 능력입니다. 화살표처럼 보이는 `=>`의 특수 인디케이터를 사용합니다. `=>` 를 사용하면, `function` 키워드를 건너 뛸 수 있습니다.
화살표 함수를 사용하기 위해 코드를 다시 작성해봅니다:
```javascript
setTimeout(3000, () => {
console.log('3 seconds has elapsed');
});
```
### 각 strategy를 사용하는 경우
이제 매개 변수로 함수에 전달하는 세 가지 방법이 있으며 각자 언제 사용하는 지 궁금할 것입니다. 함수를 두번 이상 사용한다는 것을 알고 있다면 정상적으로 만들게 됩니다. 한 위치에만 사용하는 경우, 일반적으로 익명 함수를 사용하는 것이 가장 좋습니다. 화살표 함수를 사용하거나 더 전통적인 `함수` 구문을 사용하거나 안하거나 대부분의 요즘 개발자들이 `=>`를 선호한다는 것을 알게 될 것입니다.
---
## 🚀 도전
함수와 메소드의 차이점을 한 문장으로 표현할 수 있나요? 시도해보세요!
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
화살표 함수는 코드 베이스에서 점점 많이 사용되고 있으므로, [읽어 볼 가치](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)가 있습니다. 함수 작성을 연습한 다음에 syntax로 다시 작성하십시오.
## 과제
[Fun with Functions](assignment.md)

@ -0,0 +1,173 @@
# JavaScript 기초: 결정하기
![JavaScript Basics - Making decisions](images/webdev101-js-decisions.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
결정을 내리고 코드가 실행되는 순서를 제어하면 코드를 재사용하며 강력하게 만들 수 있습니다. 이 강의에서는 JavaScript에서 데이터 흐름을 제어하기 위한 구문과 논리 자료형 데이터 타입을 함께 사용하는 중요성을 다룹니다.
[![Making Decisions](https://img.youtube.com/vi/SxTp8j-fMMY/0.jpg)](https://youtube.com/watch?v=SxTp8j-fMMY "Making Decisions")
## 논리 자료형에 대한 간략한 요약
논리 자료형은 `true` 또는 `false` 두 가지 값만 가능합니다. 논리 자료형은 조건이 충족하는 순간 실행하는 코드 라인을 결정하는 데 도움 줄 수 있습니다.
이렇게 참 또는 거짓으로 논리 자료형을 지정합니다:
`let myTrueBool = true`
`let myFalseBool = false`
✅ Booleans(=논리 자료형)은 영국의 수학자, 철학자이자 논리 학자인 George Boole (1815-1864)의 이름에서 유래되었습니다.
## 비교 연산자와 논리 연산자
연산자는 논리 자료형 값을 비교하여 조건을 평가하는 데 사용합니다. 자주 사용되는 연산자 목록입니다.
| Symbol | Description | Example |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
| `<` | **Greater than**: Compares two values and returns the `true` Boolean data type if the value on the right side is larger than the left | `5 < 6 // true` |
| `<=` | **Greater than or equal to**: Compares two values and returns the `true` Boolean data type if the value on the right side is larger than or equal to the left | `5 <= 6 // true` |
| `>` | **Less than**: Compares two values and returns the `true` Boolean data type if the value on the left side is larger than the right | `5 > 6 // false` |
| `>=` | **Less than or equal to**: Compares two values and returns the `true` Boolean data type if the value on the left side is larger than or equal to the right | `5 >= 6 // false` |
| `===` | **Strict equality**: Compares two values and returns the `true` Boolean data type if values on the right and left are equal AND are the same data type. | `5 === 6 // false` |
| `!==` | **Inequality**: Compares two values and returns the opposite Boolean value of what a strict equality operator would return | `5 !== 6 // true` |
✅ 브라우저 콘솔에서 비교문을 작성하여 복습해보십시오. 반환된 데이터가 놀랍나요?
## If 문
조건이 참이면 if 문은 블록 사이에서 코드를 실행합니다.
```javascript
if (condition){
//Condition was true. Code in this block will run.
}
```
논리 연산자는 조건을 만들 때 종종 사용됩니다.
```javascript
let currentMoney;
let laptopPrice;
if (currentMoney >= laptopPrice){
//Condition was true. Code in this block will run.
console.log("Getting a new laptop!");
}
```
## IF..Else 문
`else` 문은 조건이 거짓일 때만 블록 사이에서 코드를 실행합니다. `if` 문와 함께 사용하는 것은 선택 사항입니다.
```javascript
let currentMoney;
let laptopPrice;
if (currentMoney >= laptopPrice){
//Condition was true. Code in this block will run.
console.log("Getting a new laptop!");
}
else{
//Condition was true. Code in this block will run.
console.log("Can't afford a new laptop, yet!");
}
```
✅ 위 코드들을 브라우저 콘솔에서 실행하여 이해했는지 테스트하십시오. currentMoney 및 laptopPrice 변수 값이 변경하여 반환된 `console.log ()`가 변경됩니다.
## 논리 연산자와 논리 자료형
결정에는 두개 이상의 비교문이 필요할 수 있으며, 논리 연산자과 합쳐서 논리 자료형 값을 생성할 수 있습니다.
| Symbol | Description | Example |
| ------ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| `&&` | **Logical AND**: Compares two Boolean expressions. Returns true **only** if both sides are true | `(5 > 6) && (5 < 6 ) //One side is false, other is true. Returns false` |
| `||` | **Logical OR**: Compares two Boolean expressions. Returns true if at least one side is true | `(5 > 6) || (5 < 6) //One side is false, other is true. Returns true` |
| `!` | **Logical NOT**: Returns the opposite value of a Boolean expression | `!(5 > 6) // 5 is not greater than 6, but "!" will return true` |
## 논리 연산자의 조건 및 결정
논리 연산자는 if..else 문에서 조건을 만들 때 사용됩니다.
```javascript
let currentMoney;
let laptopPrice;
let laptopDiscountPrice = laptopPrice - (laptopPrice * .20) //Laptop price at 20 percent off
if (currentMoney >= laptopPrice || currentMoney >= laptopDiscountPrice){
//Condition was true. Code in this block will run.
console.log("Getting a new laptop!");
}
else {
//Condition was true. Code in this block will run.
console.log("Can't afford a new laptop, yet!");
}
```
### 부정 연산자
`if...else` 문으로 사용하여 조건부 로직을 만드는 방법을 살펴 보았습니다. `if`에 들어가는 모든 것은 참/거짓으로 평가되어야 합니다. `!` 연산자를 사용하여 표현식을 _부정_ 할 수 있습니다. 이렇게 보입니다:
```javascript
if (!condition) {
// runs if condition is false
} else {
// runs if condition is true
}
```
### 삼항식
`if...else`가 결정 로직을 표현하는 유일한 방법은 아닙니다. 삼항 연산자라는 것을 사용할 수 있습니다. 구문은 다음과 같습니다:
```javascript
let variable = condition ? <return this if true> : <return this if false>`
```
다음은 확실한 예시입니다:
```javascript
let firstNumber = 20;
let secondNumber = 10
let biggestNumber = firstNumber > secondNumber ? firstNumber: secondNumber;
```
✅ 잠시 시간을 내서 코드를 몇 번 읽으시기 바랍니다. 연산자가 어떻게 작동하는지 이해하나요?
위의 내용은
- `firstNumber``secondNumber`보다 큰 경우
- `biggestNumber``firstNumber`를 할당하고
- 그렇지 않으면 `secondNumber`를 할당한다는 내용입니다.
삼항 표현식은 아래 코드를 간단히 작성했습니다.
```javascript
let biggestNumber;
if (firstNumber > secondNumber) {
biggestNumber = firstNumber;
} else {
biggestNumber = secondNumber;
}
```
---
## 🚀 도전
논리 연산자로 프로그램을 먼저 만든 뒤, 삼항 표현식을 사용하여 다시 작성하십시오. 어떤 구문을 선호합니까?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[MDN에서](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators) 사용할 수 있는 많은 연산자에 대해 자세히 알아보십시오.
Josh Comeau'의 wonderful [operator lookup](https://joshwcomeau.com/operator-lookup/)을 통할 수 있습니다!
## 과제
[Operators](assignment.md)

@ -0,0 +1,124 @@
# JavaScript 기초: 배열과 반복
![JavaScript Basics - Arrays](images/webdev101-js-arrays.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
이 강의에서는 웹에서 상호 작용을 제공하는 언어인 JavaScript의 기본 사항을 다룹니다. 데이터를 컨트롤하는 데 사용하는 배열과 반복문에 대해 알아 봅니다.
[![Arrays and Loops](https://img.youtube.com/vi/Q_CRM2lXXBg/0.jpg)](https://youtube.com/watch?v=Q_CRM2lXXBg "Arrays and Loops")
## 배열
데이터 작업은 모든 언어의 일반적인 작업이며, 데이터가 배열과 같은 구조으로 구성되면 훨씬 더 쉬운 작업입니다. 배열을 사용하면, 데이터가 리스트와 유사한 구조로 저장됩니다. 배열의 주요 장점 중 하나는 한 배열에 다양한 타입의 데이터를 저장할 수 있다는 것입니다.
✅ 배열은 우리 주변에 있습니다! 태양광 패널 배열과 같이 실생활의 사례를 생각할 수 있습니까?
배열 구문은 대괄호 쌍으로 이루어져 있습니다.
`let myArray = [];`
빈 배열이지만, 이미 데이터가 채워진 배열로 선언할 수 있습니다. 배열에 들어있는 여러가지 값들은 쉼표로 구분합니다.
`let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];`
배열 값에는 **index**라는 고유 값이 할당됩니다. 이 값은 배열 시작부터 측정한 길이를 기준으로 할당되는 정수입니다. 예시에서, 문자열 값 "Chocolate"의 인덱스는 0이고, "Rocky Road"의 인덱스는 4입니다. 배열 값을 검색하고, 변경하거나 넣으려면 대괄호가 있는 인덱스를 사용합니다.
✅ 배열이 0 인덱스에서 시작하는 것이 놀랍나요? 일부 프로그래밍 언어에서 인덱스는 1부터 시작합니다. 이와 관련된 흥미로운 기록이 있습니다, [Wikipedia에서 읽을 수 있습니다](https://en.wikipedia.org/wiki/Zero-based_numbering).
```javascript
let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
iceCreamFlavors[2]; //"Vanilla"
```
인덱스를 활용하여 값을 변경할 수 있습니다:
```javascript
iceCreamFlavors[4] = "Butter Pecan"; //Changed "Rocky Road" to "Butter Pecan"
```
그리고 주어진 인덱스에 새로운 값을 넣을 수 있습니다:
```javascript
iceCreamFlavors[5] = "Cookie Dough"; //Added "Cookie Dough"
```
✅ 값을 배열에 밀어 넣는 것보다 일반적인 방법은 array.push()와 같이 배열 연산자를 사용하는 것입니다.
배열에 있는 아이템 개수를 확인하려면, `length` 속성을 사용합니다.
```javascript
let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
iceCreamFlavors.length; //5
```
✅ 스스로 시도해보세요! 브라우저의 콘솔을 사용하여 직접 배열을 만들고 조작하십시오.
## 반복문
루프는 반복적이거나 **반복하는** 작업을 허용하며, 많은 시간과 코드를 절약할 수 있습니다. 각 iteration은 변수, 값 그리고 조건에서 다룰 수 있습니다. JavaScript에는 여러 유형의 반복문이 있으며, 약간의 차이가 있지만 기본적으로 동일한 작업을 수행합니다: 데이터 반복입니다.
### For 반복문
`for` 반복문으로 반복하려면 세 부분이 필요합니다:
- `카운터` 일반적으로 반복 횟수를 세는 숫자로 초기화되는 변수입니다.
- `조건` 비교 연산자를 사용하여 `true` 일 때 반복을 중단시키는 표현식
- `반복-표현식` 각 반복의 마무리에 실행되며, 일반적으로 값을 세면서 변경할 떄 사용됩니다
```javascript
//Counting up to 10
for (let i = 0; i < 10; i++) {
console.log(i);
}
```
✅ 브라우저 콘솔에서 코드를 실행하십시오. 카운터, 조건 혹은 반복 표현식을 약간 변경하면 어떻게 되나요? 돌아와서 카운트 다운할 수 있습니까?
### While 반복문
`for` 반복문과 달리 `while` 반복문은 `true`일 때 반복을 중지하는 조건만 필요합니다. 반복의 조건은 일반적으로 카운터와 같이 다른 값에 의존하며, 반복하는 도중에 관리해야 합니다. 카운터의 시작 값은 반복문 밖에서 만들어야하며, 카운터 변경을 포함하여 조건을 충족하는 모든 표현식은 반복문 안에서 유지되어야 합니다.
```javascript
//Counting up to 10
let i = 0;
while (i < 10) {
console.log(i);
i++;
}
```
✅ for 반복문과 while 반복문 중에 선택하는 이유는 무엇입니까? StackOverflow에 방문한 17K 명에게 동일한 질문을 했으며, 일부 의견은 [흥미로울 수 있습니다](https://stackoverflow.com/questions/39969145/while-loops-vs-for-loops-in-javascript).
## 반복문과 배열
대부분의 조건문에서 반복을 그만하기 위해서는 배열 길이가 필요하고, 인덱스는 카운터 값이 될 수도 있기 때문에 배열은 반복문과 자주 사용됩니다.
```javascript
let iceCreamFlavors = ["Chocolate", "Strawberry", "Vanilla", "Pistachio", "Rocky Road"];
for (let i = 0; i < iceCreamFlavors.length; i++) {
console.log(iceCreamFlavors[i]);
} //Ends when all flavors are printed
```
✅ 브라우저 콘솔에서 직접 만든 배열을 반복하여 실험해보세요.
---
## 🚀 도전
for문과 while문 외에 배열을 반복하는 다른 방법이 있습니다. [forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach), [for-of](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) 그리고 [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)도 있습니다. 해당 기술 중 하나를 사용하여 배열 반복을 다시 작성하십시오.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
JavaScript의 배열에는 많은 메서드를 가져서 데이터 조작에 매우 유용합니다. [이 방법에 대해 읽어보고](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array) 만든 배열에서 몇 가지(push, pop, slice 그리고 splice와 같은) 방식을 시도해보십시오.
## 과제
[Loop an Array](assignment.md)

@ -0,0 +1,14 @@
# JavaScript 소개하기
JavaScript는 웹의 언어입니다. 이 네 가지 강의에서 기초를 배우게됩니다.
### 주제
1. [변수와 데이터 타입](1-data-types/README.md)
2. [함수와 메소드](2-functions-methods/README.md)
3. [JavaScript로 결정하기](3-making-decisions/README.md)
4. [배열과 반복](4-arrays-loops/README.md)
### 크레딧
These lessons were written with ♥️ by [Jasmine Greenaway](https://twitter.com/paladique), [Christopher Harrison](https://twitter.com/geektrainer) and [Chris Noring](https://twitter.com/chris_noring)

@ -0,0 +1,231 @@
# Terrarium 프로젝트 파트 1: HTML 소개
![Introduction to HTML](images/webdev101-html.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
HTML, HyperText Markup Language는 웹의 '뼈대' 입니다. CSS가 HTML과 JavaScript를 '꾸미면' HTML은 웹 애플리케이션의 본체입니다. HTML의 구문은 "head", "body" 그리고 "footer" 태그를 포함하므로 이러한 상상을 반영합니다.
이 강의에서는, HTML을 사용하여 가상 terrarium 인터페이스의 '뼈대'을 구상할 것입니다. 제목과 3개의 열을 가집니다: 드래그 가능한 식물이 살고 있는 오른쪽과 왼쪽 열 그리고 유리처럼 보이는 테라리움이 될 가운데 영역이 있습니다. 이 강의가 끝나면, 열에서 식물을 볼 수 있지만, 인터페이스가 약간 이상하게 보일 것입니다; 걱정하지 마세요. 다음 강의에서는 인터페이스에 CSS 스타일을 추가하여 더 좋아 보이게 만들 것입니다.
### 작업
컴퓨터에서, 'terrarium' 이라는 폴더를 만들고 그 안에 'index.html' 파일을 만듭니다. 새 VS Code 윈도우를 열어서 'open folder'를 클릭하고, terrarium 폴더를 만들면 Visual Studio Code에서 이 작업을 할 수 있습니다. 탐색기에서 작은 'file' 버튼을 클릭하고 새 파일을 만듭니다:
![explorer in VS Code](images/vs-code-index.png)
또는
git bash에서 아래 명령어를 수행합니다:
* `mkdir terrarium`
* `cd terrarium`
* `touch index.html`
* `code index.html` 또는 `nano index.html`
> index.html 파일은 브라우저에 폴더의 기본 파일임을 나타냅니다; `https://anysite.com/test`와 같은 URL은 `index.html`이 내부에 있는 `test`라는 폴더에 포함되어 있는 폴더 구조를 사용하여 제작될 수 있습니다; `index.html`은 URL에 표시할 필요가 없습니다.
---
## DocType과 html 태그
HTML 파일의 첫 번째 줄은 doctype입니다. 파일 최상단에 줄이 존재해야 된다는 사실에 살짝 놀랐지만, 현재 html 사양에 따라 페이지를 표준 모드로 렌더링해야한다고 오래된 브라우저에 알려줍니다.
> Tip: VS Code에서는, 태그 위에 마우스를 가져가면 MDN 레퍼런스 가이드에서 정보를 얻을 수 있습니다.
두 번째 줄에는 `<html>`이 열어주는 태그이며 그 뒤에 바로 닫는 태그로 `</html>`이 와야합니다. 이러한 태그는 인터페이스의 상위 요소입니다.
### 작업
`index.html` 파일의 상단에 해당 줄들을 추가합니다:
```HTML
<!DOCTYPE html>
<html></html>
```
✅ 쿼리 문자열로 DocType을 설정하여 결정할 수 있는 몇 가지 모드가 있습니다: [Quirks Mode and Standards Mode](https://developer.mozilla.org/en-US/docs/Web/HTML/Quirks_Mode_and_Standards_Mode). 이 모드는 요즘 잘 사용하지 않는 오래된 브라우저를 지원하는 데 사용되었습니다 (Netscape Navigator 4 및 Internet Explorer 5). 표준 doctype 선언을 할 수도 있습니다.
---
## 문서의 'head'
HTML 문서의 'head' 영역에는 [metadata](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta)라고 하는 웹 페이지의 중요한 정보가 포함되어 있습니다. 우리의 경우에는, 이 페이지가 렌더링될 웹 서버에 다음 4가지를 알립니다.
- 페이지의 제목
- 페이지 메타데이터 포함:
- 페이지에서 사용되는 문자 인코딩을 알려주는, 'character set'
- IE=edge 브라우저가 지원됨을 나타내는 `x-ua-compatible`을 포함한, 브라우저 정보
- viewport가 볼러질 때 어떻게 동작해야되는 지에 대한 정보. viewport를 초기 배율 1로 설정하며 처음 불러올 때 줌 수준이 제어됩니다.
### 작업
문서의 열고 닫는 `<html>` 태그 사이에 'head' 블록을 추가합니다.
```html
<head>
<title>Welcome to my Virtual Terrarium</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
```
`<meta name="viewport" content="width=600">`과 같이 viewport 메타 태그를 설정하면 어떻게 되나요? [viewport](https://developer.mozilla.org/en-US/docs/Mozilla/Mobile/Viewport_meta_tag)에 대해 자세히 알아보세요.
---
## 문서의 `body`
### HTML 태그
HTML에서는, .html 파일에 태그를 추가하여 웹 페이지의 요소를 만듭니다. 각 태그에는 단락을 나타내기 위한 `<p>hello</p>` 같이 열고 닫는 태그가 있습니다. `<html>` 태그 쌍 안에 `<body>` 태그 세트를 추가하여 인터페이스의 본문을 만듭니다; 이제 마크업은 다음과 같습니다:
### 작업
```html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to my Virtual Terrarium</title>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body></body>
</html>
```
이제 페이지 만들 수 있습니다. 일반적으로 `<div>` 태그를 사용하여 페이지에 별도의 요소를 만듭니다. 이미지를 포함한 일련의 `<div>`요소를 만듭니다.
### 이미지
페이지가 아이템을 렌더링하면서 필요한 모든 정보가 `src` 요소에 들어있기 때문에, `<img>` 태그는 닫는 태그가 필요하지 않은 html 태그입니다.
앱에 `images` 라는 폴더를 만들고, [소스코드 폴더](../images)에 모든 이미지(식물 14개의 이미지)를 모두 추가합니다.
### 작업
식물 이미지를 `<body></body>` 태그 사이의 두 열에 추가합니다:
```html
<div id="page">
<div id="left-container" class="container">
<div class="plant-holder">
<img class="plant" alt="plant" id="plant1" src="./images/plant1.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant2" src="./images/plant2.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant3" src="./images/plant3.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant4" src="./images/plant4.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant5" src="./images/plant5.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant6" src="./images/plant6.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant7" src="./images/plant7.png" />
</div>
</div>
<div id="right-container" class="container">
<div class="plant-holder">
<img class="plant" alt="plant" id="plant8" src="./images/plant8.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant9" src="./images/plant9.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant10" src="./images/plant10.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant11" src="./images/plant11.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant12" src="./images/plant12.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant13" src="./images/plant13.png" />
</div>
<div class="plant-holder">
<img class="plant" alt="plant" id="plant14" src="./images/plant14.png" />
</div>
</div>
</div>
```
> Note: Spans vs. Divs. Div는 'block' 요소로 간주되고, Span은 'inline'입니다. 이 div를 spans으로 변환하면 어떻게 될까요?
이 마크업을 사용하면, 이제 식물이 화면에 출력됩니다. 아직 CSS로 스타일이 지정되지 않았기 때문에 안 이뻐보입니다. 다음 강의에 그렇게 할 것입니다.
각 이미지에는 이미지를 보거나 렌더링 할 수 없는 경우에 보여지는 alt 태그가 있습니다. 이건 접근성을 위해 있어야 할 중요한 요소입니다. 이후 강의에서 접근성에 대해 자세히 알아보십시오. 지금은 어떤 이유로 이미지를 볼 수 없는 경우(연결이 느리거나, src 속성에 오류가 있거나, 스크린 리더를 사용하는 경우) alt 속성이 이미지의 대체 정보를 제공한다는 점을 기억하시기 바랍니다.
✅ 각 이미지에 같은 alt 태그가 있다는 것을 알았습니까? 이건 좋은 습관일까요? 어떤 이유일까요? 이 코드를 개선할 수 있나요?
---
## 시멘틱 마크업
일반적으로, HTML을 작성할 때 'semantics'을 사용하는 것이 좋습니다. 무슨 뜻일까요? 이는 HTML 태그를 디자인된 방식으로 사용한다는 것을 의미합니다: 데이터를 표현하기 위해; 따라서 H1 태그는 항상 페이지에 존재해야 합니다
`<body>` 여는 태그 바로 아래에 다음 줄을 추가합니다:
```html
<h1>My Terrarium</h1>
```
헤더가 `<h1>`이고 순서가 지정되지 않은 목록은 `<ul>`로 렌더링하는 것처럼 마크업을 사용하면 스크린 리더가 페이지를 탐색하는 데 도움이 됩니다. 일반적으로, 버튼은 `<button>`으로, 목록은 `<li>`로 작성해야 합니다. 버튼을 구상하기 위해서 클릭 핸들러와 특별히 스타일이 지정된 `<span>` 요소를 사용 _가능하지만_ , differently-abled 사용자는 기술을 사용하여 페이지에 버튼이 있는 위치를 확인하므로 버튼으로 상호 작용하는 것이 더 좋습니다. 이러한 이유로, 가능한 시맨틱 마크업을 사용하십시오.
✅ 스크린 리더를 살펴보고 [웹 페이지와 상호 작용하는 방식](https://www.youtube.com/watch?v=OUDV1gqs9GA)을 살펴보세요. 의미 없는 마크업이 사용자를 혼란스럽게 하는 이유를 알 수 있습니까?
## terrarium
이 인터페이스의 마지막 부분은 terrarium을 만들기 위해 스타일 지정할 마크업을 만드는 것입니다.
### 작업:
마지막 `</div>` 태그 위에 이 마크업을 놓습니다:
```html
<div id="terrarium">
<div class="jar-top"></div>
<div class="jar-walls">
<div class="jar-glossy-long"></div>
<div class="jar-glossy-short"></div>
</div>
<div class="dirt"></div>
<div class="jar-bottom"></div>
</div>
```
✅ 이 마크업을 화면에 추가했지만, 전혀 렌더링이 안됩니다. 왜 그럴까요?
---
## 🚀 도전
HTML에는 여전히 재미있고 '오래된' 태그가 있지만, 마크업에서는 [이러한 태그](https://developer.mozilla.org/en-US/docs/Web/HTML/Element)처럼 더 이상 사용되지 않는 태그를 쓰면 안됩니다. 그래도, 예전 `<marquee>` 태그를 사용하여 h1 제목을 가로로 스크롤할 수 있습니까? (한다면, 나중에 제거하는 것을 잊지 마십시오)
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
HTML은 웹을 오늘 날의 웹으로 구축하는 데 도움이 준 'tried and true' 빌딩 블록 시스템입니다. 오래된 태그와 새로운 태그를 연구하여 그 역사에 대해 조금 배우십시오. 일부는 더 이상 안 쓰고 일부는 새로 만들어진 이유를 알 수 있습니까? 앞으로 어떤 태그가 도입될까요?
[Microsoft Learn](https://docs.microsoft.com/learn/modules/build-simple-website/?WT.mc_id=cxaall-4621-cxall)에서 웹과 모바일용 사이트 제작에 대해 자세히 알아보세요.
## 과제
[Practice your HTML: Build a blog mockup](assignment.md)

@ -0,0 +1,263 @@
# Terrarium 프로젝트 파트 2: CSS 소개
![Introduction to CSS](images/webdev101-css.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
CSS 또는 Cascading Style Sheets는 웹 개발의 중요한 이슈를 해결합니다: 웹 사이트를 멋지게 만드는 방법. 앱의 스타일을 지정하면 더 유용하고 보기 좋게 만들 수 있습니다; CSS를 사용하여 반응형 웹 디자인(RWD)을 만들 수도 있습니다 - 따라서 앱이 표시되는 화면 크기에 관계없이 멋지게 보일 수 있습니다. CSS는 앱을 멋지게 보이는 방법만 있는 것이 아닙니다; 사양에는 앱에 대한 정교한 상호 작용을 가능하게 하는 애니메이션 및 변환이 포함됩니다. CSS 워킹 그룹은 현재 CSS 사양을 유지하는 데 도움을 줍니다; [World Wide Web Consortium 사이트](https://www.w3.org/Style/CSS/members)에서 작업을 확인할 수 있습니다.
> Note, CSS는 웹의 모든 것과 마찬가지로, 진화하는 언어이며, 모든 브라우저가 사양의 최신 부분을 지원하는 것은 아닙니다. 항상 [CanIUse.com](caniuse.com)에 문의하여 구현을 확인하십시오.
이 강의에서는, 온라인 terrarium에 스타일을 추가하며 여러 CSS 개념에 대해 자세히 알아봅시다: 캐스케이드, 상속과 선택자 사용, 포지셔닝과 레이아웃 구축을 하기위한 CSS 사용. 이 과정에서 terrarium을 레이아웃하고 실제 terrarium 자체를 만듭니다.
### 준비물
terrarium으로 HTML이 제작되고 스타일을 지정할 준비가 되어있어야 합니다.
### 작업
terrarium 폴더에서, `style.css` 라고 불리는 파일을 만듭니다. 파일의 `<head>` 부분에 넣습니다:
```html
<link rel="stylesheet" href="./style.css" />
```
---
## Cascade
Cascade 스타일 시트는 우선 순위에 따라 스타일이 적용 되도록 'Cascade' 된다는 상상을 합칩니다. 웹 사이트 작성자가 지정한 스타일은 브라우저가 지정한 스타일보다 우선합니다. '인라인'으로 설정된 스타일은 외부 스타일 시트에 설정된 스타일보다 우선합니다.
### 작업
`<h1>` 태그에 "color: red" inline 스타일을 추가합니다:
```HTML
<h1 style="color: red">My Terrarium</h1>
```
그러고, `style.css` 파일에 다은 코드를 추가합니다:
```CSS
h1 {
color: blue;
}
```
✅ 웹 앱에는 어떤 색으로 표시되나요? 왜 그럴까요? 스타일을 오버라이드하는 방법을 찾을 수 있나요? 언제 하고 싶나요? 아니면 왜 안될까요?
---
## 상속
스타일은 상위 스타일에서 하위 요소로 상속되므로, 중첩된 요소는 상위 스타일을 상속합니다.
### 작업
본문의 폰트를 지정된 주어진 폰트로 설정하고, 중첩된 요소의 폰트를 확인합니다:
```
body {
font-family: helvetica, arial, sans-serif;
}
```
브라우저의 콘솔에서 'Elements'을 열고 H1 폰트를 보십시오. 브라우저에 기록된대로 본문에서 폰트를 상속합니다:
![inherited font](images/1.png)
✅ 중첩된 스타일이 다른 속성을 상속하도록 만들 수 있습니까?
---
## CSS 선택자
### 태그
지금까지, `style.css` 파일에는 스타일이 지정된 태그가 몇 개 뿐이며, 앱이 매우 이상하게 보입니다:
```
body {
font-family: helvetica, arial, sans-serif;
}
h1 {
color: #3a241d;
text-align: center;
}
```
이 태그 스타일링 방식을 사용하면 고유 요소를 제어할 수 있지만, terrarium에 있는 많은 식물의 스타일을 제어해야 합니다. 이를 위해서는, CSS 선택자를 활용해야 합니다.
### Id
왼쪽과 오른쪽 컨테이너 레이아웃에 스타일을 추가합니다. 왼쪽 컨테이너와 오른쪽 컨테이너가 하나만 있으므로, 마크업에 Id가 제공됩니다. 스타일을 지정하려면, `#`을 사용하세요:
```
#left-container {
background-color: #eee;
width: 15%;
left: 0px;
top: 0px;
position: absolute;
height: 100%;
padding: 10px;
}
#right-container {
background-color: #eee;
width: 15%;
right: 0px;
top: 0px;
position: absolute;
height: 100%;
padding: 10px;
}
```
여기에서는, 컨테이너를 화면의 맨 왼쪽과 오른쪽에 절대 위치로 두고, 너비에 백분율을 사용하여 작은 모바일 화면에 맞을 수 있도록 조정했습니다.
✅ 이 코드는 꽤 반복되므로, "DRY" 하지 않습니다 (반복하지 마십시오); id와 클래스를 사용하여 스타일링하는 더 좋은 방법을 찾을 수 있나요? 마크업을 변경하고 CSS를 리팩토링해야 합니다:
```html
<div id="left-container" class="container"></div>
```
### 클래스
위의 예시에서, 화면에서 두가지 고유 요소를 꾸몄습니다. 화면의 많은 요소에 꾸미려면, CSS 클래스를 사용할 수 있습니다. 왼쪽과 오른쪽 컨테이너에 식물을 둘 수 있습니다.
HTML 마크업의 각 식물에는 id와 클래스의 조합이 있습니다. id는 나중에 추가할 JavaScript에서 terrarium 식물 배치를 조작하는 데 사용됩니다. 그러나 이 강의에서는 모든 식물에 주어진 스타일로 부여합니다.
```html
<div class="plant-holder">
<img class="plant" alt="plant" id="plant1" src="./images/plant1.png" />
</div>
```
`style.css` 파일에 다음을 추가합니다:
```css
.plant-holder {
position: relative;
height: 13%;
left: -10px;
}
.plant {
position: absolute;
max-width: 150%;
max-height: 150%;
z-index: 2;
}
```
이 스니펫에서 볼 관점은 상대 위치와 절대 위치가 혼합되어 있다는 것이며, 다음 강의에서 다룰 것입니다. 높이가 백분율로 처리되는 방식을 살펴보십시오:
식물 홀더의 높이를 13%로 설정하면, 스크롤할 필요없이 모든 식물이 각 수직 컨테이너에 출력되도록 할 수 있습니다.
식물 홀더를 왼쪽으로 이동하여 식물이 컨테이너 인에서 더 가운데로 들어오도록 설정합니다. 이미지는 드래그하기 쉽게 만들기 위해 투명한 배경을 많이 가지고 있으므로, 화면에 더 잘 맞게 왼쪽으로 밀어야 합니다.
그러면, 식물 자체의 최대 너비는 150% 입니다. 브라우저가 작아지면 같이 작아질 수 있습니다. 브라우저 크기를 조절해보십시오. 식물은 컨테이너에 있지만 크기에 맞게 작아집니다.
또한 요소의 상대적 고도를 제어하는, z-index의 사용은 주목할만 합니다 (식물이 컨테이너 위에 있고 terrarium 안에 있는 것처럼 보이게).
✅ 식물 홀더와 식물 CSS 선택자가 필요한 이유는 무엇입니까?
## CSS 포지셔닝
위치 속성(static, relative, fixed, absolute 그리고 sticky 포지션)을 섞는 것은 약간 까다로울 수 있지만, 제대로 하면 페이지의 요소를 잘 제어할 수 있습니다.
절대 위치 요소는 가장 가까운 위치에 있는 최상위를 기준으로 위치하며, 없는 경우 문서 본문에 따라 위치가 지정됩니다.
상대 배치된 요소는 CSS의 방향에 따라 배치되며 처음 위치에서 떨어진 배치를 조정합니다.
예시에서, `plant-holder`는 절대 위치지만 컨테이너 안에서는 상대 위치 요소입니다. 그 결과 사이드바 컨테이너가 왼쪽과 오른쪽에 고정되고, 식물 홀더가 중첩되어 사이드바 안에서 자체적으로 조정되므로 수직 행에 식물이 배치될 공간을 제공합니다.
> 다음 강의에서 확인해보면, `plant`은 드래그 가능한 필요한 절대 위치에 있습니다.
✅ 사이드 컨테이너와 식물 홀더의 위치 타입을 전환하여 실험하십시오. 무슨 일이 생기나요?
## CSS 레이아웃
이제는 배웠던 모든 CSS를 사용하여 terrarium 자체를 만듭니다!
먼저, CSS를 사용하여 `.terrarium` div 하위를 라운딩된 사각형으로 꾸밉니다:
```css
.jar-walls {
height: 80%;
width: 60%;
background: #d1e1df;
border-radius: 10%;
position: absolute;
bottom: 0.5%;
left: 20%;
opacity: 0.5;
z-index: 1;
}
.jar-top {
width: 50%;
height: 5%;
background: #d1e1df;
position: absolute;
bottom: 80.5%;
left: 25%;
opacity: 0.7;
z-index: 1;
}
.jar-bottom {
width: 50%;
height: 1%;
background: #d1e1df;
position: absolute;
bottom: 0%;
left: 25%;
opacity: 0.7;
}
.dirt {
width: 58%;
height: 5%;
background: #3a241d;
position: absolute;
border-radius: 0 0 4rem 4rem;
bottom: 1%;
left: 21%;
opacity: 0.7;
z-index: -1;
}
```
여기서는 `border-radius`에 대해서도 백분율을 사용합니다. 브라우저를 줄이면 jar 모서리도 어떻게 변하는 지 볼 수 있습니다. 또한 jar 요소의 너비, 높이 백분율과 각 요소가 뷰포트 하단에 고정되어 중앙에 절대 배치되는 방식을 확인하십시오.
✅ jar 색상과 투명도를 흙 색과 비교해보십시오. 무슨 일인가요? 왜 그럴까요?
---
## 🚀 도전
jar 좌측 하단 부분에 'bubble' 광택을 추가하여 유리처럼 보이도록 합니다. `.jar-glossy-long``.jar-glossy-short`를 빛 반사되는 것처럼 보이도록 꾸밉니다. 다음과 같이 표시됩니다.
![finished terrarium](./images/terrarium-final.png)
강의 후 퀴즈를 완료하려면, 다음 학습 모듈을 진행하십시오: [Style your HTML app with CSS](https://docs.microsoft.com/en-us/learn/modules/build-simple-website/4-css-basics)
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
CSS는 믿을 수 없게 간단해 보이지만, 모든 브라우저와 모든 화면 크기에 대해 완벽한 스타일을 지정하려면 많이 어렵습니다. CSS-Grid 및 Flexbox는 좀 더 구조화되고 안정적으로 보이게 개발된 도구입니다. [Flexbox Froggy](https://flexboxfroggy.com/)와 [Grid Garden](https://codepip.com/games/grid-garden/)을 플레이하여 도구에 대해 알아보세요.
## 과제
[CSS Refactoring](assignment.md)

@ -0,0 +1,215 @@
# Terrarium 프로젝트 파트 3: DOM 조작과 클로저
![DOM and a closure](images/webdev101-js.png)
> Sketchnote by [Tomomi Imura](https://twitter.com/girlie_mac)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
DOM 또는 "Document Object Model"을 조작하는 것은 웹 개발의 핵심입니다. [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Introduction)에 따르면, "The Document Object Model (DOM) is the data representation of the objects that comprise the structure and content of a document on the web." 이라고 합니다. 웹의 DOM 조작과 관련하여 간혹 DOM을 관리하기 위해 순수 JavaScript 대신 JavaScript 프레임워크를 도전하게 되는 원동력이지만, 우리는 스스로 관리할 것입니다!
추가로, 이 강의에서는 [JavaScript closure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures)에 대한 아이디어를 소개합니다. 내부 함수가 외부 함수의 범위에 접근할 수 있도록 합니다.
DOM을 조작하기 위해 클로저를 사용할 예정입니다.
> DOM을 웹 페이지 문서를 제어할 수 있는 모든 방법으로 나타낼 수 있는 트리로 생각하십시오. 프로그래머가 선택한 프로그래밍 언어를 사용하여 DOM에 접근하고 편집, 변경, 재배치하며 관리할 수 있는 다양한 API(Application Program Interfaces)가 작성되어 있습니다.
![DOM tree representation](./images/dom-tree.png)
> DOM과 참조하는 HTML 마크업의 표현입니다. From [Olfa Nasraoui](https://www.researchgate.net/publication/221417012_Profile-Based_Focused_Crawler_for_Social_Media-Sharing_Websites)
이 강의에서는, 사용자가 페이지에서 식물을 조작할 수 있는 JavaScript를 작성하여 대화식 terrarium 프로젝트를 완료합니다.
### 준비물
terrarium에 대한 HTML과 CSS를 작성해두어야 합니다. 이 강의가 끝나면 식물을 드래그하여 terrarium에서 이동할 수 있습니다.
### 작업
terrarium 폴더에서, `script.js`라고 불리는 파일을 만듭니다. 파일의 `<head>` 부분에 넣습니다:
```html
<script src="./script.js" defer></script>
```
> Note: 외부 JavaScript 파일을 html 파일로 가져올 때 `defer` 를 사용하여 HTML 파일이 완전히 불러질 때만 JavaScript가 실행되도록 합니다. HTML 파일이 파싱되는 동안 스크립트를 실행할 수 있는 `async` 속성을 사용할 수 있지만, 우리는 드래그 스크립트를 실행하기 전에 HTML 요소를 완전히 드래그할 수 있어야 한다는 점이 중요합니다.
---
## DOM 요소
가장 먼저 할 일은 DOM에서 조작하려는 요소에 대한 참조를 만드는 것입니다. 우리의 경우, 현재 사이드바에서 대기하고 있는 14개의 식물입니다.
### 작업
```html
dragElement(document.getElementById('plant1'));
dragElement(document.getElementById('plant2'));
dragElement(document.getElementById('plant3'));
dragElement(document.getElementById('plant4'));
dragElement(document.getElementById('plant5'));
dragElement(document.getElementById('plant6'));
dragElement(document.getElementById('plant7'));
dragElement(document.getElementById('plant8'));
dragElement(document.getElementById('plant9'));
dragElement(document.getElementById('plant10'));
dragElement(document.getElementById('plant11'));
dragElement(document.getElementById('plant12'));
dragElement(document.getElementById('plant13'));
dragElement(document.getElementById('plant14'));
```
여기서 어떤 일이 일어나고 있나요? 특정 Id의 요소를 찾기 위해 문서를 참조하려고 해당 DOM을 봅니다. HTML에 대한 첫번째 강의에서 각 식물 이미지(`id="plant1"`)에 개별 Id를 준 것을 기억하시나요? 이제 그 노력을 쓰겠습니다. 각 요소를 식별한 후, 1분 안에 작성될 `dragElement`라고 불리는 함수에 해당 아이템을 전달합니다. 따라서 HTML의 요소는 이제 드래그를 할 수 있거나 곧 합니다.
✅ 왜 우리는 Id로 요소를 참조하나요? CSS 클래스가 아닌 이유는 무엇일까요? 이 질문에 답하기 위해 CSS에 대한 전 강의를 참조할 수 있습니다.
---
## 클로저
이제 내부 함수를 감싸는 외부 함수인 dragElement 클로저를 만들 준비가 되었습니다(이 경우에는, 3개가 있습니다).
클로저는 하나 이상의 함수가 외부 함수의 범위로 접근하는 순간 유용합니다. 예시는 다음과 같습니다:
```javascript
function displayCandy(){
let candy = ['jellybeans'];
function addCandy(candyType) {
candy.push(candyType)
}
addCandy('gumdrops');
}
displayCandy();
console.log(candy)
```
이 예제에서, displayCandy 함수는 새 캔디 타입을 이미 존재하는 배열로 푸시하는 함수를 감쌉니다. 이 코드를 실행한다면, `candy` 배열은 지역 변수(클로저 로컬)이므로 정의되지 않습니다.
`candy` 배열에 어떻게 접근할 수 있습니까? 클로저 밖으로 이동해보세요. 이 방식은 배열이 클로저의 로컬뿐만 아니라, 전역 범위에서도 사용할 수 있습니다.
### 작업
`script.js`의 요소 선언 아래, 함수를 만듭니다:
```javascript
function dragElement(terrariumElement) {
//set 4 positions for positioning on the screen
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
terrariumElement.onpointerdown = pointerDrag;
}
```
`dragElement`는 스크립트 상단의 선언에서 `terrariumElement` 객체를 가져옵니다. 그러고, 함수에 전달된 객체의 일부 로컬 위치를 `0`으로 설정합니다. 클로저 안에서 드래그 앤 드롭 기능을 각 요소에 추가할 때 각 요소에 대해 조작될 지역 변수입니다. terrarium에 드래그된 요소로 채워지므로, 애플리케이션은 배치된 위치를 추적해야 합니다.
추가로, 이 함수에 전달되는 terrariumElement에는 DOM 관리에 도움이 되도록 설계된 [web APIs](https://developer.mozilla.org/en-US/docs/Web/API)의 일부인 `pointerdown` 이벤트가 할당됩니다. `onpointerdown`은 버튼이 눌리거나 드래그 하는 요소가 터치될 때 발생합니다. 이 이벤트 핸들러는 몇 가지 예외를 제외하고, [웹과 모바일 브라우저](https://caniuse.com/?search=onpointerdown) 다 작동합니다.
✅ [event handler `onclick`](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onclick)은 크로스 브라우저 지원이 훨씬 더 많습니다. 왜 여기서 사용하지 않나요? 여기에서 만들고자 하는 정확한 화면 상호작용 타입에 대해 생각해보세요.
---
## Pointerdrag 함수
terrariumElement를 드래그할 준비가 되었습니다; `onpointerdown` 이벤트가 시작되면, pointerDrag 함수가 호출됩니다. 줄 바로 아래에 해당 함수를 추가하시기 바랍니다: `terrariumElement.onpointerdown = pointerDrag;`:
### 작업
```javascript
function pointerDrag(e) {
e.preventDefault();
console.log(e);
pos3 = e.clientX;
pos4 = e.clientY;
}
```
몇 가지 일이 발생합니다. 처음으로, `e.preventDefault();`를 사용하여 포인터 다운 시 일상적으로 발생하는 기본 이벤트가 발생하지 않도록 합니다. 이러면 인터페이스의 동작을 더 잘 제어할 수 있습니다.
> 스크립트 파일을 완전히 작성했으면 이 줄로 돌아와서 `e.preventDefault()` 없이 시도해보세요. - 어떻게 되나요?
두 번째로는, 브라우저 창에서 `index.html`을 열고, 인터페이스를 검사합니다. 식물을 클릭하면, 'e' 이벤트가 캡처되는 방식을 볼 수 있습니다. 이벤트를 뜯어보면서 한 번의 포인터 다운 이벤트로 얼마나 많은 정보가 수집되는지 확인해보시기 바랍니다!
다음으로 지역 변수 'pos3'과 'pos4'가 어떻게 e.clientX 로 설정되어 있는 지 확인합니다. 검사 창에서 `e` 값을 찾을 수 있습니다. 이 값은 식물을 클릭하거나 터치하는 순간 식물의 x 와 y 좌표를 캡처합니다. 식물을 클릭하고 드래그할 때 식물의 동작을 세밀하게 제어하여 좌표를 추적해야 합니다.
✅ 하나의 앱이 하나의 큰 클로저로 만들어진 이유가 더 분명해지나요? 아니라면, 14개의 드래그 가능한 식물을 어떻게 각각의 범위를 지킬까요?
`pos4 = e.clientY` 아래에 포인터 이벤트 조작을 2개 더 추가하여 함수 초기화를 완료합니다:
```html
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
```
제 식물을 이동할 때 포인터와 함께 식물을 끌고, 식물 선택을 취소할 때 드래그 제스처를 중지하도록 지정합니다. `onpointermove``onpointerup`은 모두 `onpointerdown`과 동일한 API의 일부입니다. 아직 `elementDrag``stopElementDrag` 함수를 정의하지 않아 인터페이스에서 오류가 발생하므로, 다음에 작성하십시오.
## elementDrag와 stopElementDrag 함수
식물을 드래그하고 멈출 때 일어나는 일을 처리하는 두 내부 함수를 더 추가하여 클로저를 완료합니다. 원하는 동작은 언제든지 식물을 드래그하여 화면의 아무 곳에 둘 수 있다는 것입니다. 이 인터페이스는 식물을 추가, 제거 그리고 재배치하여 원하는대로 terrarium을 정확하게 디자인할 수 있도록 (예를 들어 drop zone이 없음)하는 것에 의견이 없습니다.
### 작업
`pointerDrag`의 닫는 중괄호 바로 뒤에 `elementDrag` 함수를 추가합니다:
```javascript
function elementDrag(e) {
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
console.log(pos1, pos2, pos3, pos4);
terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px';
terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px';
}
```
이 함수에서는, 외부 함수에서 로컬 변수로 설정한 초기 위치 1-4를 많이 편집합니다. 여기서 어떤 일이 일어나고 있습니까?
드래그할 때, `pos1`을 현재 `e.clientX` 값을 뺀 `pos3`(이전에`e.clientX`로 설정)과 동일하게 만들어 `pos1`을 다시 할당합니다. `pos2`와 유사한 작업을 수행합니다. 그런 뒤에, `pos3``pos4`를 요소의 새로운 X 와 Y 좌표로 다시 설정합니다. 드래그하면 콘솔에서 변경 사항을 볼 수 있습니다. 그런 뒤에, 식물의 css 스타일을 조작하여 `pos1``pos2`의 새로운 위치를 기반으로 위치를 설정하고, 오프셋을 이러한 새 위치와 비교하여 식물의 위쪽과 왼쪽 XY 좌표를 계산합니다.
> `offsetTop``offsetLeft`는 상위 위치를 기준으로 요소의 위치를 설정하는 CSS 속성입니다. 상위는 `static`으로 두지 않은 모든 요소가 될 수 있습니다.
모든 위치 다시 계산하며 terrarium과 그 식물의 움직임을 미세하세 조정할 수 있습니다.
### 작업
인터페이스를 완성하기 위한 마지막 작업은 `elementDrag`의 닫는 중괄호 뒤에 `closeElementDrag` 함수를 추가하는 것입니다:
```javascript
function stopElementDrag() {
document.onpointerup = null;
document.onpointermove = null;
}
```
이 작은 함수는 `onpointerup``onpointermove` 이벤트를 다시 설정하므로 다시 드래그하여 식물의 진행 상황을 다시 시작하거나, 새 식물을 드래그할 수 있습니다.
✅ 이러한 이벤트를 null로 설정하지 않으면 어떻게 될까요?
이제 프로젝트를 완료했습니다!
🥇 축하합니다! 아름다운 terrarium을 완성했습니다. ![finished terrarium](./images/terrarium-final.png)
---
## 🚀 도전
클로저에 새로운 이벤트 핸들러를 추가하여 식물에 더 많은 일을 시킵니다. 예를 들어, 식물을 더블 클릭하여 앞으로 가져옵니다. 창의력을 발휘하십시오!
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
화면에서 요소를 드래그하는 것은 사소한 것처럼 보이지만, 원하는 효과에 따라 여러 가지 방식과 함정이 있습니다. 실제로, 시도할 수 있는 전체 [drag and drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API)가 있습니다. 우리가 원하는 효과가 다소 다르기 때문에 이 모듈에서는 사용하지 않았지만, 그러나 이 API를 자신의 프로젝트에서 시도해보고 얻을 수 있는 게 무엇인지 확인하시기 바랍니다.
포인터 이벤트에 대한 자세한 내용은 [W3C docs](https://www.w3.org/TR/pointerevents1/)와 [MDN web docs](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events)에서 확인하세요.
항상 [CanIUse.com](https://caniuse.com/)으로 브라우저 기능을 확인하십시오.
## 과제
[Work a bit more with the DOM](assignment.md)

@ -0,0 +1,31 @@
# My Terrarium : JavaScript를 사용한 HTML, CSS 및 DOM 조작에 대해 배우는 프로젝트 🌵🌱
작은 드래그 앤 드롭 코드-명상. 약간의 HTML, JS와 CSS를 사용하여 웹 인터페이스를 구축하고, 스타일을 지정하고, 상호 작용을 추가합니다.
![my terrarium](images/screenshot_gray.png)
# Lessons
1. [HTML 소개](./1-intro-to-html/README.md)
2. [CSS 소개](./2-intro-to-css/README.md)
3. [DOM 및 JS Closures](./3-intro-to-DOM-and-closures/README.md)
## 크레딧
Written with ♥️ by [Jen Looper](https://www.twitter.com/jenlooper)
The terrarium created via CSS was inspired by Jakub Mandra's glass jar [codepen](https://codepen.io/Rotarepmi/pen/rjpNZY).
The artwork was hand drawn by [Jen Looper](http://jenlooper.com) using Procreate.
## Terrarium 배포하기
Azure Static Web Apps를 사용하여 terrarium을 웹에 배포하거나 게시할 수 있습니다.
1. 저장소를 포크합니다
2. 버튼을 누릅니다
[![Deploy to Azure button](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/?feature.customportal=false&WT.mc_id=cxaall-4621-cxall#create/Microsoft.StaticApp)
3. 앱을 만드는 마법사로 안내합니다. 앱 상위를 `/solution` 또는 코드베이스의 상위로 설정했는지 확인하십시오. 이 앱에는 API가 없으므로 추가할 필요가 없습니다. Azure Static Web Apps의 빌드 서비스로 빌드하고 새 URL에 앱을 게시하는 데 도움이 되는 포크 리포지토리에 .github 폴더가 생성됩니다.

@ -0,0 +1,30 @@
# 이벤트-주도 프로그래밍 - 타이핑 게임 빌드하기
## 소개
타이핑은 개발자에게 가장 과소평가된 스킬 중 하나입니다. 생각을 머리에서 에디터에 빠르게 전달할 수 있는 능력은 창의력이 자유롭게 흐릅니다. 배울 때 가장 좋은 방법 중 하나는 게임을 직접 해보는 것입니다!
> 이제 타이핑 게임을 빌드하러 갑시다!
타이핑 게임을 만들기 위해 지금까지 배운 JavaScript, HTML 및 CSS 스킬을 사용합니다. 게임은 플레이어에게 무작위 인용문([Sherlock Holmes](https://en.wikipedia.org/wiki/Sherlock_Holmes) 인용문 사용)과 플레이어가 정확하게 입력하는 데 걸리는 시간을 제시합니다.
![demo](images/demo.gif)
## 사전 조건
이 레슨에서는 다음 개념에 익숙하다고 가정합니다:
- 텍스트 입력 및 버튼 컨트롤 생성하기
- 클래스를 사용한 CSS 및 설정 스타일하기
- JavaScript 기초
- 배열 생성하기
- 난수 생성하기
- 현재 시간 얻기
## 레슨
[이벤트 기반 프로그래밍을 사용하여 타이핑 게임 만들기](./project/README.md)
## 크레딧
Written with ♥️ by [Christopher Harrison](http://www.twitter.com/geektrainer)

@ -0,0 +1,167 @@
# 브라우저 확장 프로젝트 파트 1: 브라우저에 대한 모든 것
![Browser sketchnote](images/sketchnote.jpg)
> Sketchnote by [Wassim Chegham](https://dev.to/wassimchegham/ever-wondered-what-happens-when-you-type-in-a-url-in-an-address-bar-in-a-browser-3dob)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
브라우저 확장은 브라우저에 추가적인 기능을 추가합니다. 그러나 빌드하기 전에, 브라우저가 작동하는 방식에 대해 약간 알아야 합니다.
### 브라우저에 대하여
이 강의의 시리즈에서는, Chrome, Firefox와 Edge 브라우저에서 작동하는 브라우저 확장을 빌드하는 방법을 배웁니다. 이 부분에서는, 브라우저가 작동하는 방식을 발견하고 브라우저 확장의 요소를 발판으로 딛습니다.
그러나 브라우저는 정확히 무엇일까요? 엔드 유저가 서버에서 콘텐츠에 접근하여 웹 페이지로 볼 수 있도록 해주는 소프트웨어 애플리케이션입니다.
✅ 약간의 역사: 첫번째 브라우저는 'WorldWideWeb'라고 불렸으며 Sir Timothy Berners-Lee가 1990년에 만들었습니다.
![early browsers](images/earlybrowsers.jpg)
> Some early browsers, via [Karen McGrane](https://www.slideshare.net/KMcGrane/week-4-ixd-history-personal-computing)
사용자가 URL(Uniform Resource Locator) 주소를 사용하여 인터넷에 연결하고, `http` 혹은 `https` 주소를 통해 Hypertext Transfer Protocol을 사용하면, 브라우저가 웹 서버와 통신하여 웹 페이지를 가져옵니다.
이 시점에서, 브라우저의 렌더링 엔진은 모바일 폰, 데스크톱 혹은 노트북과 같은 사용자의 장치에 출력합니다.
또한 브라우저는 콘텐츠를 캐시할 수 있으므로 매번 서버에서 찾을 필요가 없습니다. 브라우징 활동 기록을 기록할 때 필요한 정보가 포함된 작은 데이터인 'cookies'를 저장할 수 있습니다.
브라우저에 대해 알아야 할 중요한 점은 브라우저가 모두 같지 않다는 것입니다! 각 브라우저에는 장점과 단점이 있으며, 전문 웹 개발자는 웹 페이지가 크로스 브라우저로 잘 작동하도록 만드는 방법을 이해해야 합니다. 여기에는 모바일 폰과 같은 작은 뷰포트 제어와 오프라인 처리 작업을 포함합니다.
선호하는 브라우저에 책갈피를 추가한다면 정말 유용한 웹 사이트는 [caniuse.com](https://www.caniuse.com)입니다. 웹 페이지를 만들 때, caniuse의 지원 기술 목록을 사용하면 사용자의 브라우저를 가장 잘 지원할 수 있습니다.
✅ 웹 사이트의 사용자 층에서 가장 인기있는 브라우저가 어떤 것인지 어떻게 알 수 있나요? analytics를 확인해보세요 - 웹 개발 프로세스의 일부로 다양한 분석 패키지를 설치할 수 있으며, 다양한 인기 브라우저에서 가장 많이 사용되는 브라우저를 알려줍니다.
## 브라우저 확장
브라우저 확장을 만드려는 이유는 무엇입니까? 반복 작업을 빠르게 할 때 브라우저로 연결하면 편리합니다. 예를 들어, 상호 작용하는 다양한 웹 페이지에서 색상을 확인하는 경우에는, color-picker 브라우저 확장을 설치할 수 있습니다. 비밀번호를 기억할 때 어려운 경우에는, password-management 브라우저 확장을 설치할 수 있습니다.
브라우저 확장도 재미있게 개발할 수 있습니다. 제한된 수의 작업을 잘 관리하는 경향이 있습니다.
✅ 가장 좋아하는 브라우저 확장은 무엇입니까? 어떤 작업을 수행하나요?
### 확장 설치하기
만들기 전에, 브라우저 확장을 작성하고 배포하는 프로세스를 찾아보세요. 브라우저마다 이 작업을 관리하는 방법이 다소 다르지만, 프로세스는 Chrome과 Firefox와 같이 Edge의 예제와 유사합니다:
![install a browser extension](images/install-on-edge.png)
본질적으로, 프로세스는 다음과 같습니다:
- `npm build`를 사용하여 확장을 빌드합니다
- 우측 상단의 `...` 아이콘을 사용하여 브라우저에서 extensions 패널로 이동합니다
- 새로 설치하는 경우, `load unpacked`를 선택하여 build 폴더(이 경우에는 `/dist`)에서 새 확장을 올립니다
- 또는, 이미 설치된 확장 프로그램을 다시 불러오는 경우에 `reload`를 클릭합니다
✅ 이 설명서는 직접 만든 확장과 관련이 있습니다; 각 브라우저와 관련된 브라우저 확장 저장소에 출시된 확장을 설치하려면, 해당 [stores](https://microsoftedge.microsoft.com/addons/Microsoft-Edge-Extensions-Home)로 이동하여 원하는 확장을 설치해야 합니다.
### 시작하기
지역 탄소 발자국을 표시하는 브라우저 확장을 만들어서 지역의 에너지 사용량과 소스를 표시합니다. 확장에는 CO2 Signal의 API에 접근할 수 있도록 API 키를 모으는 폼이 있습니다.
**필요합니다:**
- [an API key](https://www.co2signal.com/); 이 페이지의 상자에 이메일을 입력하면 이메일이 전송됩니다.
- [Electricity Map](https://www.electricitymap.org/map)에 해당하는 [code for your region](http://api.electricitymap.org/v3/zones) (보스턴에서 예시를 들면, 'US-NEISO'를 사용합니다).
- [starter code](../start). `start` 폴더를 다운로드하세요; 이 폴더에서 코드를 완성하게됩니다.
- [NPM](https://www.npmjs.com) - NPM은 패키지 관리 도구입니다. 로컬에 설치하고 `package.json` 파일에 나열된 패키지를 웹 어셋에서 사용하도록 설치합니다.
✅ 이 [excellent Learn module](https://docs.microsoft.com/en-us/learn/modules/create-nodejs-project-dependencies/)에서 패키지 관리에 대해 자세히 알아보세요
잠시 시간을 내어 코드베이스를 보세요:
dist
-|manifest.json (defaults set here)
-|index.html (front-end HTML markup here)
-|background.js (background JS here)
-|main.js (built JS)
src
-|index.js (your JS code goes here)
✅ API 키와 지역 코드를 가지고 있다면, 나중에 다시 사용할 수 있도록 메모하세요.
### 확장을 위한 HTML 제작하기
이 확장은 두 화면을 가집니다. 한 화면에는 API 키와 지역 코드를 수집합니다:
![extension form](images/1.png)
그리고 두번째 화면에는 지역의 탄소 사용량을 출력합니다:
![carbon usage](images/2.png)
이제 HTML 폼을 작성하고 CSS 스타일을 지정하는 것으로 시작하겠습니다.
`/dist` 폴더에서, 폼과 결과 영역을 작성합니다. `index.html` 파일에서, 구분된 폼 영역을 채웁니다:
```HTML
<form class="form-data" autocomplete="on">
<div>
<h2>New? Add your Information</h2>
</div>
<div>
<label>Region Name</label>
<input type="text" required class="region-name" />
</div>
<div>
<label>Your API Key from tmrow</label>
<input type="text" required class="api-key" />
</div>
<button class="search-btn">Submit</button>
</form>
```
저장된 정보를 입력하고 로컬 저장소에 저장하는 양식입니다.
다음, 결과 영역을 만듭니다; 마지막 폼 태그 아래에 약간의 div를 추가합니다:
```HTML
<div class="result">
<div class="loading">loading...</div>
<div class="errors"></div>
<div class="data"></div>
<div class="result-container">
<p><strong>Region: </strong><span class="my-region"></span></p>
<p><strong>Carbon Usage: </strong><span class="carbon-usage"></span></p>
<p><strong>Fossil Fuel Percentage: </strong><span class="fossil-fuel"></span></p>
</div>
<button class="clear-btn">Change region</button>
</div>
```
이 시점에서 다시 빌드할 수 있습니다. 이 확장의 패키지 의존성을 설치해야 합니다:
```
npm install
```
이 명령은 Node 패키지 관리자인 npm을 사용하여, 확장의 빌드 프로세스를 위한 webpack을 설치합니다. Webpack은 컴파일 코드를 제어하는 bundler입니다. 이 프로세스의 출력은 `/dist/main.js`에서 볼 수 있습니다. - 코드가 번들로 제공됨을 알 수 있습니다.
지금은, 확장이 빌드되어 확장으로 Edge에 배포된다면, 폼은 깔끔하게 출력됩니다.
축하합니다, 브라우저 확장을 만들기 위한 첫 단계를 수행했습니다. 이어지는 강의에서, 더 기능적이고 유용하게 만들 것입니다.
---
## 🚀 도전
브라우저 확장 저장소를 살펴보고 브라우저에 하나 설치하세요. 흥미로운 방식으로 파일을 뜯어볼 수 있습니다. 무엇을 발견할 수 있나요?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
이 강의에서는 웹 브라우저의 역사에 대해 조금 배웠습니다; 이 기회에 역사를 더 많이 읽어보면서 World Wide Web의 탐험가들이 어떻게 사용했는지에 대해 알아보십시오. 유용한 사이트는 다음과 같습니다:
[The History of Web Browsers](https://www.mozilla.org/en-US/firefox/browsers/browser-history/)
[History of the Web](https://webfoundation.org/about/vision/history-of-the-web/)
[An interview with Tim Berners-Lee](https://www.theguardian.com/technology/2019/mar/12/tim-berners-lee-on-30-years-of-the-web-if-we-dream-a-little-we-can-get-the-web-we-want)
## 과제
[Restyle your extension](assignment.md)

@ -0,0 +1,222 @@
# 브라우저 확장 프로젝트 파트 2: 로컬 저장소를 사용한, API 호출
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
이 강의에서는, 브라우저 확장의 폼을 제출하여 API를 호출하고 브라우저 확장에 결과를 출력합니다. 또한, 나중에 참조하고 사용하도록 브라우저의 로컬 저장소에 데이터를 저장하는 방법에 대해 알아봅니다.
✅ 적절한 파일 번호가 매겨진 세그먼트를 따라서 코드를 둘 위치를 알아봅니다.
### 확장에서 조작할 요소를 설정합니다:
지금까지 HTML 폼과 브라우저 확장에 대한 결과 `<div>`를 작성했습니다. 이제부터, `/src/index.js` 파일에서 작업하고 확장을 조금씩 빌드해야 합니다. 프로젝트 설정 및 빌드 프로세스에 대해서는 [이전 강의](../about-browsers/README.md)를 참조하세요.
`index.js` 파일에서 작업하면서, 다양한 필드에 값을 저장할 `const` 변수를 만드는 것으로 시작합니다:
```JavaScript
// form fields
const form = document.querySelector('.form-data');
const region = document.querySelector('.region-name');
const apiKey = document.querySelector('.api-key');
// results
const errors = document.querySelector('.errors');
const loading = document.querySelector('.loading');
const results = document.querySelector('.result-container');
const usage = document.querySelector('.carbon-usage');
const fossilfuel = document.querySelector('.fossil-fuel');
const myregion = document.querySelector('.my-region');
const clearBtn = document.querySelector('.clear-btn');
```
모든 필드는 이전 강의에서 HTML을 설정한 것처럼 CSS 클래스에서 참조됩니다.
### 리스너 추가하기
다음으로, 폼에 이벤트 리스너를 추가하고 폼을 리셋해주는 clear 버튼을 추가하여, 폼을 제출하거나 reset 버튼을 클릭하면 실행되도록, 파일 하단에 앱 초기화를 호출하도록 추가합니다:
```JavaScript
form.addEventListener('submit', (e) => handleSubmit(e));
clearBtn.addEventListener('click', (e) => reset(e));
init();
```
✅ submit 또는 click 이벤트를 받을 때 사용되는 약칭과, 이벤트가 handleSubmit 또는 reset 함수에 전달되는 방법에 유의하십시오. 이 약칭에 관련된 것을 더 긴 형식으로 바꿔 작성할 수 있나요? 어느 쪽을 선호하나요?
### init() 함수와 reset() 함수 작성하기:
이제 init()라고 불리는 확장 초기화하는 함수를 만드려고 합니다:
```JavaScript
function init() {
//if anything is in localStorage, pick it up
const storedApiKey = localStorage.getItem('apiKey');
const storedRegion = localStorage.getItem('regionName');
//set icon to be generic green
//todo
if (storedApiKey === null || storedRegion === null) {
//if we don't have the keys, show the form
form.style.display = 'block';
results.style.display = 'none';
loading.style.display = 'none';
clearBtn.style.display = 'none';
errors.textContent = '';
} else {
//if we have saved keys/regions in localStorage, show results when they load
displayCarbonUsage(storedApiKey, storedRegion);
results.style.display = 'none';
form.style.display = 'none';
clearBtn.style.display = 'block';
}
};
function reset(e) {
e.preventDefault();
//clear local storage for region only
localStorage.removeItem('regionName');
init();
}
```
함수에는, 약간의 흥미로운 로직이 있습니다. 이것을 읽어보면, 어떤 것이 보이나요?
- 사용자가 로컬 저장소에 APIKey 및 지역 코드를 저장했는지 확인하기 위해 두 'const'가 설정됩니다.
- 둘 중 하나가 null이면, 'block'으로 출력되도록 스타일을 변경하여 폼을 나타냅니다.
- 결과를 숨기거나, 불러오고, 또한 clearBtn을 숨기며 오류 텍스트를 빈 문자열로 설정합니다
- 키와 지역이 존재하는 경우에는, 다음 루틴이 시작됩니다:
- 탄소 사용량 데이터를 얻기 위한 API 호출
- 결과 영역 숨기기
- 폼 숨기기
- 리셋 버튼 보이기
계속 진행하기 전, 브라우저에서 사용할 수 있는 매우 중요한 개념: [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage)에 대해 알아 보는 것이 좋습니다. LocalStorage는 브라우저에 문자열을 `key-value` 쌍으로 저장하는 유용한 방법입니다. 이런 타입의 웹 저장소는 브라우저에서 데이터를 관리하기 위해 JavaScript로 제어할 수 있습니다. LocalStorage는 만료되지 않지만, 다른 종류의 웹 저장소인 SessionStorage는 브라우저를 닫는 즉시 지워집니다. 다양한 타입의 저장소를 사용하는 것은 장점과 단점이 존재합니다.
> Note - 이 브라우저 확장은 로컬 저장소를 가집니다; 주요 브라우저 창은 각자 다른 객체이며 별도로 동작합니다.
APIKey를 문자열 값으로 설정한다고 하면, 예를 들어 웹 페이지를 "inspecting" 하고 (브라우저를 우측 클릭하여 검사할 수 있습니다) 애플리케이션 탭으로 이동하여 저장소를 확인했을 때 Edge에 설정되어 있다는 것을 볼 수 있습니다.
![Local storage pane](images/localstorage.png)
✅ LocalStorage에 일부 데이터를 저장하기 꺼리는 상황을 생각해보세요. 일반적으로 API 키를 LocalStorage에 배치하는 것은 좋지 않은 생각입니다! 알 수 있나요? 우리 경우, 앱은 순수하게 학습을 위함이고, 앱 스토어에도 배포하지 않으므로 이 방법을 사용합니다.
Web API로 `getItem()`, `setItem()` 또는 `removeItem()`을 사용하여 LocalStorage를 제어합니다. 대부분 브라우저에서 광범위하게 지원됩니다.
`init()`에서 호출되는 `displayCarbonUsage()` 함수를 만들기 전에 초기 폼 제출을 하는 기능을 만들어 보겠습니다.
### 양식 제출 제어하기
이벤트 인자 `(e)`를 받는 `handleSubmit` 함수를 만듭니다. 이벤트 전달을 중단하고(브라우저 새로 고침을 중단) `apiKey.value``region.value` 인자를 전달하는 새로운 함수 `setUpUser` 를 호출합니다. 이런 방식으로, 적절한 필드가 채워지는 순간 초기 폼을 통해 가져온 두 값을 사용합니다.
```JavaScript
function handleSubmit(e) {
e.preventDefault();
setUpUser(apiKey.value, region.value);
}
```
✅ 메모리를 초기화합니다 - 지난 강의에서 설정한 HTML에는 파일 상단에 지정한 `const`를 통해 `values`이 잡히는 두 입력 필드가 있으며, 둘 다 `required`이므로 브라우저에서 사용자가 null 값을 입력하지 못하도록 합니다.
### 사용자 설정하기
`setUpUser` 함수로 이동하고, 여기에 apiKey와 regionName에 대한 로컬 저장소 값을 설정합니다. 새로운 함수를 추가합니다:
```JavaScript
function setUpUser(apiKey, regionName) {
localStorage.setItem('apiKey', apiKey);
localStorage.setItem('regionName', regionName);
loading.style.display = 'block';
errors.textContent = '';
clearBtn.style.display = 'block';
//make initial call
displayCarbonUsage(apiKey, regionName);
}
```
이 함수는 API가 호출되는 동안 로딩 메시지를 설정하게 합니다. 핵심으로, 이 브라우저 확장의 가장 중요한 함수를 만드는 데 도달했습니다!
### 탄소 사용량 출력하기
마지막으로 API 쿼리할 시간입니다!
계속 진행하기 전에, API에 대해 논의해야 합니다. API, 또는 [Application Programming Interfaces](https://www.webopedia.com/TERM/A/API.html)는 웹 개발자의 툴박스에서 중요한 요소입니다. 프로그램이 상호 작용하거나 할 수 있게 도와주는 표준 방법을 제공합니다. 예시로, 데이터베이스를 쿼리할 웹 사이트를 구축하는 경우, 누군가 사용할 API를 만들었을 수 있습니다. 다양한 유형의 API가 있지만 가장 인기있는 API 중 하나는 [REST API](https://www.smashingmagazine.com/2018/01/understanding-using-rest-api/)입니다.
✅ 'REST'라는 용어는 'Representational State Transfer'를 의미하고 데이터를 가져오기 위해서 다양하게-구성된 URL을 쓰는 기능입니다. 개발자가 사용할 수 있는 다양한 타입의 API에 대해 약간 알아보십시오. 어떤 포맷이 좋습니까?
이 함수에 대해 유의해야 할 중요 사항이 있습니다. 먼저 [`async` keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)를 확인합니다. 비동기로 실행되도록 함수를 작성한다는 것은 수행되기 전 데이터 반환 작업이 완료될 때를 기다린다는 점을 의미합니다.
다음은 `async`에 대한 간단한 영상입니다 :
[![Async and Await for managing promises](https://img.youtube.com/vi/YwmlRkrxvkk/0.jpg)](https://youtube.com/watch?v=YwmlRkrxvkk "Async and Await for managing promises")
C02Signal API를 쿼리할 새로운 함수를 만듭니다:
```JavaScript
import axios from '../node_modules/axios';
async function displayCarbonUsage(apiKey, region) {
try {
await axios
.get('https://api.co2signal.com/v1/latest', {
params: {
countryCode: region,
},
headers: {
'auth-token': apiKey,
},
})
.then((response) => {
let CO2 = Math.floor(response.data.data.carbonIntensity);
//calculateColor(CO2);
loading.style.display = 'none';
form.style.display = 'none';
myregion.textContent = region;
usage.textContent =
Math.round(response.data.data.carbonIntensity) + ' grams (grams C02 emitted per kilowatt hour)';
fossilfuel.textContent =
response.data.data.fossilFuelPercentage.toFixed(2) +
'% (percentage of fossil fuels used to generate electricity)';
results.style.display = 'block';
});
} catch (error) {
console.log(error);
loading.style.display = 'none';
results.style.display = 'none';
errors.textContent = 'Sorry, we have no data for the region you have requested.';
}
}
```
이는 큰 함수입니다. 여기에 어떤 일이 일어나고 있나요?
- 좋은 사례에 따라, `async` 키워드를 사용하여 이 함수가 비동기로 동작하도록 합니다. 이 함수는 API가 데이터를 반환할 때 promise를 반환하므로 `try/catch` 블록을 포함합니다. API 응답 속도를 제어할 수 없기 때문에 (전혀 응답을 못 받을 수 있습니다!) 확실하지 않은 일은 비동기로 호출하여 처리해야 합니다.
- API 키를 사용해서, 지역의 데이터를 얻기 위해 co2signal API를 쿼리하고 있습니다. 이 키를 사용하려면, 헤더 파라미터에 인증 타입을 사용해야 합니다.
- API가 응답하면, 이 데이터를 출력하도록 설정한 화면에 응답 데이터의 다양한 요소를 할당합니다.
- 오류가 있거나, 결과가 없는 경우에는, 오류 메시지가 출력됩니다.
✅ 비동기 프로그래밍 패턴을 사용하는 것은 툴박스의 다른 매우 유용한 도구입니다. [about the various ways](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)를 읽고 이런 타입의 코드를 구성할 수 있습니다.
축하합니다! 확장을 빌드하고 (`npm run build`) 확장 패널에서 새로 고치면, 작동하는 확장이 있습니다! 아이콘만 작동하지 않으며 다음 강의에서 수정할 예정입니다.
---
## 🚀 도전
지금까지 이 강의에서 여러 타입의 API에 대해 논의했습니다. 웹 API를 선택하고 제공하는 내용에 대해 자세히 알아보세요. 예시로, [HTML Drag and Drop API](https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API)와 같은 브라우저에서 쓸 수 있는 API를 보세요. 당신의 의견에 비추어 볼 때 좋은 API를 만드는 방법은 무엇일까요?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
이 강의에서는 전문 웹 개발자에게 매우 유용한, LocalStorage와 API에 대해 학습했습니다. 어떻게 두 가지가 같이 작동하는지 생각해볼 수 있나요? API에서 사용할 아이템을 저장하는 웹 사이트를 어떻게 설계할지 생각해보세요.
## 과제
[Adopt an API](assignment.md)

@ -0,0 +1,161 @@
# 브라우저 확장 프로젝트 파트 3: 백그라운드 작업과 성능 학습
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
이 모듈의 마지막 두 강의에서는, API에서 가져온 데이터에 대한 폼과 출력 영역을 만드는 방법을 배웠습니다. 웹에서 웹 프레즌스를 만드는 매우 표준적인 방법입니다. 비동기로 데이터를 가져와서 처리하는 방법도 배웠습니다. 브라우저 확장이 거의 완성되었습니다.
확장의 아이콘 색상을 새로 고치는 것부터 포함하여, 일부 백그라운드 작업을 관리하는 것도 남아있으므로, 브라우저가 이러한 종류의 작업을 관리하는 방법에 대해 이야기해 볼 수 있는 시간입니다. 웹 어셋을 구축하며 성능과 관련한 브라우저 작업에 대해 생각합시다.
## 웹 성능 기초
> "Website performance is about two things: how fast the page loads, and how fast the code on it runs." -- [Zack Grossbart](https://www.smashingmagazine.com/2012/06/javascript-profiling-chrome-developer-tools/)
모든 종류의 사용자와 상황에서의 모든 장치에서, 웹 사이트를 매우 빠르게 작동하도록 만드는 방법은 놀랍지 않을 정도로 많습니다. 표준 웹 프로젝트 혹은 브라우저 확장을 만들 때 알아두어야 할 몇 가지 사항이 있습니다.
사이트가 효율적으로 실행되는지 확인하기 위해 가장 먼저 할 일은 성능 데이터를 모으는 것입니다. 첫 번째 장소는 웹 브라우저의 개발자 도구입니다. Edge에서는, 브라우저 우측 상단에 존재하는 세 개 점을 선택한 다음, More Tools > Developer Tools로 이동하여 Performance 탭을 열 수 있습니다.
Performance 탭에는 Profiling 도구가 있습니다. 웹 사이트를 열고 (예를 들어, https://www.microsoft.com) 'Record' 버튼을 클릭한 뒤에, 사이트를 새로 고칩니다. 언제든 recording을 중단하면, 사이트를 'script', 'render', 그리고 'paint' 하려고 만든 루틴을 볼 수 있습니다:
![Edge profiler](./images/profiler.png)
✅ Edge에서 Performance 패널에서 [Microsoft Documentation](https://docs.microsoft.com/en-us/microsoft-edge/devtools-guide/performance)를 방문하세요
> Tip: 웹 사이트의 시작 시간을 순수하게 보려면, 브라우저의 캐시를 지우세요
프로필 타임라인의 요소를 선택하여 페이지가 불러지는 동안에 발생하는 이벤트를 확대합니다.
프로필 타임라인의 일부를 선택하고 요약 패널을 보게된 뒤 페이지 performance의 snapshot을 가져옵니다:
![Edge profiler snapshot](./images/snapshot.png)
이벤트 로그 패널을 확인하여 15ms 이상 넘긴 이벤트가 있는지 확인합니다:
![Edge event log](./images/log.png)
✅ 프로파일러에 대해 알아보세요! 이 사이트에서 개발자 도구를 열고 병목 현상이 있는지 확인하세요. 불러오는 속도가 가장 느린 어셋은 무엇인가요? 가장 빠른가요?
## 프로파일링 점검
일반적으로 모든 웹 개발자가 사이트를 만들 때는 주의해야 하는 몇 가지 "problem areas"가 있습니다, 따라서 프로덕션으로 배포할 때 깜짝 놀라는 것을 피할 수 있습니다.
**어셋 크기**: 웹은 지난 몇 년 동안 '무거워'지고 느려졌습니다. 이렇게 무거워지는 일부 원인은 이미지를 사용하는 것과 관련이 있습니다.
✅ 페이지 무게 등의 역사적 관점을 보려면 [Internet Archive](https://httparchive.org/reports/page-weight) 를 살펴보세요.
이미지가 최적화되어 사용자에게 적당한 크기와 해상도로 제공하고 있는지, 확인해보는 것이 좋습니다.
**DOM 순회**: 브라우저는 작성한 코드를 기반으로 Document Object Model을 작성하므로, 페이지에 필요한 것만 사용하며 스타일을 지정하고, 태그도 적게 사용하는 것이 좋은 페이지 성능을 위해 필요합니다. 이 시점에서, 페이지와 관련되어 많은 CSS를 최적화할 수 있습니다: 예를 들면, 한 페이지에서만 사용할 스타일은 main style sheet에 포함할 필요가 없습니다.
**JavaScript**: 모든 JavaScript 개발자는 나머지 DOM을 탐색하는 과정을 브라우저에 그리기 전에 불러와야 한다면 'render-blocking' 스크립트를 봐야 합니다. inline 스크립트와 (Terrarium 모듈에서 한 것처럼) `defer` 사용을 고려해보세요.
✅ [Site Speed Test website](https://www.webpagetest.org/)에서 일부 사이트를 시도해보고 사이트 성능을 결정하기 위해 수행되는 일반적인 확인 사항에 대해 알아보세요.
이제 브라우저가 전송한 어셋을 렌더링하는 방법에 대한 아이디어를 얻었으므로, 확장을 완료하고자 하는 마지막 몇 작업을 봅시다:
### 색상 계산하는 함수 생성하기
`/src/index.js`에서 작업하면서, DOM에 접근하기 위해 설정한 `const` 변수 뒤에 `calculateColor()`라는 함수를 추가합니다:
```JavaScript
function calculateColor(value) {
let co2Scale = [0, 150, 600, 750, 800];
let colors = ['#2AA364', '#F5EB4D', '#9E4229', '#381D02', '#381D02'];
let closestNum = co2Scale.sort((a, b) => {
return Math.abs(a - value) - Math.abs(b - value);
})[0];
console.log(value + ' is closest to ' + closestNum);
let num = (element) => element > closestNum;
let scaleIndex = co2Scale.findIndex(num);
let closestColor = colors[scaleIndex];
console.log(scaleIndex, closestColor);
chrome.runtime.sendMessage({ action: 'updateIcon', value: { color: closestColor } });
}
```
여기에 어떤 일이 일어나고 있나요? 지난 강의에서 완료한 API 호출에서 값(탄소 강도)을 전달한 다음에, 값이 colors 배열에 표시된 인덱스에서 얼마나 가까운 지 계산합니다. 그러고 가장 가까운 색상 값을 chrome 런타임으로 보냅니다.
hrome.runtime은 [API](https://developer.chrome.com/extensions/runtime)는 모든 종류의 백그라운드 작업을 제어하며, 확장은 다음을 활용합니다:
> "Use the chrome.runtime API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs."
✅ Edge 용 브라우저 확장을 개발하고 있다면, chrome API를 사용하고 있다는 점에 놀랄 수 있습니다. 최신 Edge 브라우저 버전은 Chromium 브라우저 엔진에서 실행되므로, 이 도구를 활용할 수 있습니다.
> 참고로 브라우저 확장을 프로파일링하려면, 이는 분리된 브라우저 인스턴스이므로 확장 자체적으로 dev tools를 시작합니다.
### 기본 아이콘 색상 지정하기
이제, `init()` 함수에서, chrome의 `updateIcon` 액션을 다시 호출하여 시작할 때의 아이콘을 평범한 녹색으로 설정합니다:
```JavaScript
chrome.runtime.sendMessage({
action: 'updateIcon',
value: {
color: 'green',
},
});
```
### 함수 호출하고, call 실행하기
다음, 방금 전에 만든 함수를 C02Signal API가 반환한 promise에 추가하여 호출합니다:
```JavaScript
//let CO2...
calculateColor(CO2);
```
그리고 마지막으로, `/dist/background.js` 에서, 백그라운드 엑션 호출하는 리스너를 추가합니다:
```JavaScript
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
if (msg.action === 'updateIcon') {
chrome.browserAction.setIcon({ imageData: drawIcon(msg.value) });
}
});
//borrowed from energy lollipop extension, nice feature!
function drawIcon(value) {
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
context.beginPath();
context.fillStyle = value.color;
context.arc(100, 100, 50, 0, 2 * Math.PI);
context.fill();
return context.getImageData(50, 50, 100, 100);
}
```
코드에서, 백엔드 작업 매니저로 들어오는 모든 메시지 리스너를 추가합니다. 'updateIcon' 이라고 불리면, 다음 코드가 수행되고, Canvas API를 사용해서 적절한 색상의 아이콘을 그립니다.
✅ [Space Game lessons](../../space-game/drawing-to-canvas/README.md)에서 Canvas API에 대해 더 배울 것 입니다.
이제, 확장을 (`npm run build`)로 다시 빌드합니다, 확장을 새로 고치고 시작한 뒤, 색상이 변하는 것을 봅니다. 심부름을 하거나 설거지를 하기에 좋을 때 인가요? 이제 압니다!
축하합니다, 유용한 브라우저 확장을 만들었고 브라우저 작동 방식과 성능 프로파일링 방식에 대해 더 배웠습니다.
---
## 🚀 도전
일부 오픈소스 웹 사이트가 옛날에도 있었는지 알아보고, GitHub 히스토리를 바탕으로, 몇 년 동안 성능을 위해 최적화되고 있었는가에 대해 확인하세요. 가장 일반적인 문제점은 무엇일까요?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[performance newsletter](https://perf.email/) 가입을 고려해보세요
웹 도구의 performance 탭을 통하여 브라우저에서 웹 성능을 측정하는 방식이 어떤 것인지 알아보세요. 큰 차이점을 찾을 수 있나요?
## 과제
[Analyze a site for performance](assignment.md)

@ -0,0 +1,28 @@
# 브라우저 확장 만들기
브라우저 확장을 만드는 것은 다른 타입의 웹 어셋을 빌드하면서 앱의 성능에 대해 생각할 수 있는 재미있고 흥미로운 방법입니다. 이 모듈에는 브라우저의 작동 방식과 브라우저 확장을 배포하는 방법, 폼을 만들고 API를 호출하며 로컬 저장소를 사용하는 방법과 웹 사이트의 성능을 측정하고 개선하는 방법에 대한 강의가 포함되어 있습니다.
Edge, Chrome과 Firefox에서 작동하는 브라우저 확장을 만듭니다. 매우 특정한 작업에 맞춰진 작은 웹 사이트와 같은 확장은 특정 지역의 전기 사용량과 탄소 강도에 대한 [C02 Signal API](https://www.co2signal.com)를 확인하고, 해당 지역의 탄소 발자국에 대한 판독 값을 반환합니다.
이 확장 기능은 API 키와 지역 코드를 양식에 입력하여 지역 전기 사용량을 결정하고 사용자의 전기 결정에 영향을 줄 수 있는 데이터를 제공하면 사용자가 임시로 호출할 수 있습니다. 예로 들면, 해당 지역에서 전기 사용량이 많은 기간에는 의류 건조기(a carbon-intense activity) 작동을 연기하는 것이 좋습니다.
### 주제
1. [브라우저에 대하여](1-about-browsers/README.md)
2. [폼과 로컬 저장소](2-forms-browsers-local-storage/README.md)
3. [백그라운드 작업과 성능](3-background-tasks-and-performance/README.md)
### 크레딧
![a green browser extension](extension-screenshot.png)
## 크레딧
The idea for this web carbon trigger was offered by Asim Hussain, lead at Microsoft of the Green Cloud Advocacy team and author of the [Green Principles](https://principles.green/). It was originally a [web site project](https://github.com/jlooper/green).
The structure of the browser extension was influenced by [Adebola Adeniran's COVID extension](https://github.com/onedebos/covtension).
The concept behind the 'dot' icon system was suggested by the icon structure of the [Energy Lollipop](https://energylollipop.com/) browser extension for California emissions.
These lessons were written with ♥️ by [Jen Looper](https://www.twitter.com/jenlooper)

@ -0,0 +1,224 @@
# Space 게임 제작하기 파트 1: 소개
![video](../images/pewpew.gif)
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 게임 개발의 상속과 구성
이전 강의에서는, 프로젝트의 범위가 매우 작았으므로, 만든 앱의 디자인 아키텍처에 대해 걱정할 필요가 없었습니다. 그러나, 애플리케이션의 크기와 범위가 커지면, 아키텍처 결정이 더 힘듭니다. JavaScript에서 더 큰 응용 프로그램을 만드는 데는 두 가지 주요 방식이 있습니다: *composition* 또는 *inheritance*. 둘 다 장점과 단점이 존재하지만 게임의 맥락에서 설명할 것 입니다.
✅ 가장 유명한 프로그래밍 책 중에는 [design patterns](https://en.wikipedia.org/wiki/Design_Patterns)과 관련이 있습니다.
게임에서는 화면에 존재하는 객체인 `game objects`가 있습니다. `x``y` 좌표를 갖는 것이 특징인, 데카르트 좌표계에 있음을 의미합니다. 게임을 개발할 때 만드는, 모든 게임 객체를 가지고 있는 게임에는 공통적인 표준 속성이 있습니다:
- **location-based** 전부는 아니지만, 대부분의 게임 요소는 위치 기반입니다. `x``y`, 위치를 가지고 있음을 의미합니다.
- **movable** 새 위치로 이동할 수 있는 객체입니다. 이는 일반적으로 영웅, 몬스터 혹은 NPC(non player character)이지만, 예를 들어, 나무와 같은 정적 객체는 아닙니다.
- **self-destructing** 이런 객체는 삭제 작업을 위해 설정되기 전 일정한 시간에만 존재합니다. 일반적으로 이 객체가 더 이상 렌더링하지 않도록 게임 엔진에 알리기 위해서는 `dead` 또는 `destroyed` 논리 자료형으로 표시됩니다.
- **cool-down** 'Cool-down'은 짧은-수명 객체의 일반적인 속성입니다. 일반적인 예시는 몇 milli 초 동안만 보이는 폭발과 같은 텍스트 또는 그래픽 이펙트입니다.
✅ Pac-Man과 같은 게임을 생각해보세요. 이 게임에서 위 나열된 4가지 객체 타입을 구별할 수 있나요?
### 행동 표현
설명한 모든 것은 게임 객체가 가질 수 있는 동작입니다. 그럼 어떻게 인코딩 될까요? 이 동작을 클래스 혹은 객체와 관련된 메소드로 나타낼 수 있습니다.
**Classes**
아이디어로는 클래스에 특정 동작을 추가하기 위해 `inheritance`과 함께 `classes`를 사용하는 것입니다.
✅ 상속은 이해해야 할 중요한 컨셉입니다. [MDN's article about inheritance](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain)로 더 배워보세요.
코드를 통해 표현되는, 게임 객체는 일반적으로 다음과 같습니다:
```javascript
//set up the class GameObject
class GameObject {
constructor(x, y, type) {
this.x = x;
this.y = y;
this.type = type;
}
}
//this class will extend the GameObject's inherent class properties
class Movable extends GameObject {
constructor(x,y, type) {
super(x,y, type)
}
//this movable object can be moved on the screen
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//this is a specific class that extends the Movable class, so it can take advantage of all the properties that it inherits
class Hero extends Movable {
constructor(x,y) {
super(x,y, 'Hero')
}
}
//this class, on the other hand, only inherits the GameObject properties
class Tree extends GameObject {
constructor(x,y) {
super(x,y, 'Tree')
}
}
//a hero can move...
const hero = new Hero();
hero.moveTo(5,5);
//but a tree cannot
const tree = new Tree();
```
✅ 잠시 시간을 내어 Pac-Man 영웅 (예를 들어, Inky, Pinky 혹은 Blinky)과 JavaScript로 작성되는 방법을 다시 구상하십시오.
**Composition**
객체 상속을 처리하는 다른 방법으로는 *Composition* 을 사용하는 것입니다. 그러면, 객체는 다음과 같이 동작을 표현합니다:
```javascript
//create a constant gameObject
const gameObject = {
x: 0,
y: 0,
type: ''
};
//...and a constant movable
const movable = {
moveTo(x, y) {
this.x = x;
this.y = y;
}
}
//then the constant movableObject is composed of the gameObject and movable constants
const movableObject = {...gameObject, ...movable};
//then create a function to create a new Hero who inherits the movableObject properties
function createHero(x, y) {
return {
...movableObject,
x,
y,
type: 'Hero'
}
}
//...and a static object that inherits only the gameObject properties
function createStatic(x, y, type) {
return {
...gameObject
x,
y,
type
}
}
//create the hero and move it
const hero = createHero(10,10);
hero.moveTo(5,5);
//and create a static tree which only stands around
const tree = createStatic(0,0, 'Tree');
```
**어떤 패턴을 사용해야 하나요?**
어떤 패턴을 선택하는 지는 여러분에게 달려 있습니다. JavaScript는 이러한 패러다임을 모두 지원합니다.
--
게임 개발에서 흔히 볼 수 있는 또 다른 패턴으로는 게임의 사용자 경험과 성능 문제를 다룹니다.
## Pub/sub 패턴
✅ Pub/Sub은 'publish-subscribe'를 의미합니다
이 패턴은 애플리케이션에서 각자 다른 부분이 서로 알고 있으면 안된다는 아이디어를 다룹니다. 왜 그럴까요? 여러 부분이 분리 되어있다면 보통 무슨 일이 일어나는지 쉽게 볼 수 있습니다. 필요한 경우에는 바로 동작을 변경하기도 더 쉽습니다. 어떻게 해야 할까요? 몇 가지 컨셉을 정하고 지킵시다.
- **message**: 메시지는 일반적으로 선택적 payload (명확한 메시지 내용 데이터 조각)와 함께 제공되는 텍스트 문자열입니다. 게임의 일반적인 메시지는 `KEY_PRESSED_ENTER` 일 수 있습니다.
- **publisher**: 이 요소는 메시지를 *publishes*하고 모든 구독자에게 보냅니다.
- **subscriber**: 이 요소는 특정 메시지를 *listens* 하고 메시지를 수신한 결과로 레이저 발사와 같은 일부 작업을 수행합니다.
implementation은 크기가 매우 작지만 매우 강한 패턴입니다. 구현 방법은 다음과 같습니다:
```javascript
//set up an EventEmitter class that contains listeners
class EventEmitter {
constructor() {
this.listeners = {};
}
//when a message is received, let the listener to handle its payload
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
//when a message is sent, send it to a listener with some payload
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach(l => l(message, payload))
}
}
}
```
위의 코드를 사용하기 위해서 매우 작은 implementation을 만들 수 있습니다:
```javascript
//set up a message structure
const Messages = {
HERO_MOVE_LEFT: 'HERO_MOVE_LEFT'
};
//invoke the eventEmitter you set up above
const eventEmitter = new EventEmitter();
//set up a hero
const hero = createHero(0,0);
//let the eventEmitter know to watch for messages pertaining to the hero moving left, and act on it
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
//set up the window to listen for the keyup event, specifically if the left arrow is hit, emit a message to move the hero left
window.addEventListener('keyup', (evt) => {
if (evt.key === 'ArrowLeft') {
eventEmitter.emit(Messages.HERO_MOVE_LEFT)
}
});
```
위에서 우리는 키보드 이벤트 `ArrowLeft`를 연결하고, `HERO_MOVE_LEFT` 메시지를 보냅니다. 우리는 그 메시지를 듣고 최종적으로 `hero`을 움직입니다. 이 패턴의 강점은 이벤트 리스너와 영웅이 서로 알지 못한다는 점입니다. `ArrowLeft``A`키로 다시 매핑할 수도 있습니다. 추가적으로 eventEmitter의 `on` 함수를 조금 수정하여 `ArrowLeft`로 완전히 다른 작업을 할 수 있습니다:
```javascript
eventEmitter.on(Messages.HERO_MOVE_LEFT, () => {
hero.move(5,0);
});
```
게임의 몸집이 커질 때 더 복잡해진다면, 이 패턴은 복잡성을 동일하면서 코드도 깨끗하게 유지합니다. 이 패턴을 채택하는 것은 정말 추천드립니다.
---
## 🚀 도전
pub-sub 패턴이 어떻게 게임을 발전시킬 수 있는지 생각해보세요. 어떤 부분이 이벤트를 어떻게 발생하고, 반응해야 하나요? 이제는 창의력을 발휘하고, 새로운 게임과 그 부분에 대해 어떻게 작동하는지 생각해볼 수 있는 기회입니다.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[reading about it](https://docs.microsoft.com/en-us/azure/architecture/patterns/publisher-subscriber)으로 Pub/Sub에 대해 조금 더 배워봅시다.
## 과제
[Mock up a game](assignment.md)

@ -0,0 +1,216 @@
# Space 게임 제작하기 파트 2: Canvas에 영웅과 몬스터 그리기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
## Canvas
canvas는 내용이 없는 게 기본인 HTML 요소입니다; 빈 상태입니다. 그리는 작업으로 추가해야 합니다.
✅ MDN애서 [Canvas API에 대하여 더](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) 읽어보세요.
보통 페이지 본문의 일부로 선언되는 방법은 다음과 같습니다:
```html
<canvas id="myCanvas" width="200" height="100"></canvas>
```
위에서 우리는 `id`, `width``height`를 설정합니다.
- `id`: 상호 작용을 해야 할 순간에 참조할 수 있도록 지정하세요.
- `width`: 요소의 너비입니다.
- `height`: 요소의 높이입니다.
## 간단한 geometry 그리기
캔버스는 데카르트 좌표계로 사물을 그립니다. 따라서 x-축과 y-축을 이용하여 무언가의 위치를 나타냅니다. 위치 `0,0`은 죄측 상단이며 우측 하단은 캔버스의 너비와 높이라고 말한 위치입니다.
![the canvas's grid](canvas_grid.png)
> Image from [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes)
캔버스 요소에 그리려면 다음 단계를 거쳐야 합니다:
1. 캔버스 요소에 **참조를 가져옵니다**.
1. 캔버스 요소에 있는 Context 요소로 **참조를 가져옵니다**.
1. 컨텍스트 요소를 사용하여 **그리는 작업을 수행**합니다.
위 단계의 코드는 일반적으로 다음과 같습니다:
```javascript
// draws a red rectangle
//1. get the canvas reference
canvas = document.getElementById("myCanvas");
//2. set the context to 2D to draw basic shapes
ctx = canvas.getContext("2d");
//3. fill it with the color red
ctx.fillStyle = 'red';
//4. and draw a rectangle with these parameters, setting location and size
ctx.fillRect(0,0, 200, 200) // x,y,width, height
```
✅ Canvas API는 2D 모양에 가장 초점이 맞추어져 있습니다, 그러나 웹사이트에서 3D 요소를 그려야 된다면, [WebGL API](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API)를 사용해야 할 수도 있습니다.
Canvas API를 사용하면 다음과 같은 모든 종류를 그릴 수 있습니다:
- **기하학 모양**, 직사각형을 그리는 방법은 이미 보여 주었지만, 더 많이 그릴 수 있습니다.
- **텍스트**, 원하는 폰트와 색상으로 텍스트를 그릴 수 있습니다.
- **이미지**, 예를 들면 .jpg 혹은 .png와 같은 이미지 어셋을 바탕으로 이미지를 그릴 수 있습니다.
✅ 시도 해보세요! 직사각형을 어떻게 그리는지 알고 있으면, 패이지에 원을 그릴 수 있나요? CodePen에서 흥미로운 캔버스 그림을 보세요. 여기 [particularly impressive example](https://codepen.io/dissimulate/pen/KrAwx)이 있습니다.
## 이미지 어셋 불러오고 그리기
`Image` 객체를 만들고 `src` 속성을 설정하여 이미지 어셋을 불러옵니다. 그런 다음 `load` 이벤트를 수신하여 사용할 준비가 되었는지 알 수 있습니다. 코드는 다음과 같습니다:
### 어셋 불러오기
```javascript
const img = new Image();
img.src = 'path/to/my/image.png';
img.onload = () => {
// image loaded and ready to be used
}
```
### Load asset 패턴
위의 내용을 이 구조로 래핑하는 것이 좋습니다, 그래서 쓰기 더 쉽고 완전히 불러올 수 있을 때만 조작하려고 시도합니다:
```javascript
async function loadAsset(path) {
return new Promise((resolve) => {
const img = new Image();
img.src = path;
img.onload = () => {
// image loaded and ready to be used
}
resolve(img);
})
}
// use like so
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
}
```
게임 어셋을 화면에 그리려면, 코드는 다음과 같습니다:
```javascript
async function run() {
const heroImg = await loadAsset('hero.png')
const monsterImg = await loadAsset('monster.png')
canvas = document.getElementById("myCanvas");
ctx = canvas.getContext("2d");
ctx.drawImage(heroImg, canvas.width/2,canvas.height/2);
ctx.drawImage(monsterImg, 0,0);
}
```
## 이제 게임 제작을 시작할 시간입니다
### 무엇을 만드나요
Canvas 요소가 있는 웹 페이지를 만듭니다. 검은 화면 `1024 * 768`을 렌더링해야 합니다. 두 가지 이미지를 제공받았습니다:
- Hero ship
![Hero ship](solution/assets/player.png)
- 5*5 monster
![Monster ship](solution/assets/enemyShip.png)
### 개발 시작하기 위한 권장 단계
`your-work` 하위 폴더에서 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
Visual Studio Code에서 폴더의 복사본을 엽니다. 로컬 개발 환경을 설정해야 합니다, NPM과 Node가 설치되어있는 Visual Studio Code를 사용하는 것이 좋습니다. 컴퓨터에 `npm`이 설정되어 있지 않은 경우에, [방법은 다음과 같습니다](https://www.npmjs.com/get-npm).
`your_work` 폴더로 이동하여 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하세요. 지금은 비어있는 페이지지만, 곧 바뀔 것 입니다
> Note: 화면에서 변경점을 보고싶다면, 브라우저를 새로 고치세요.
### 코드 추가하기
필요한 코드를 `your-work/app.js`에 추가하여 아래 문제를 해결합니다
1. 검은 바탕으로 canvas **그리기**
> tip: `/app.js`의 TODO 아래에 적절히 두 줄을 추가하며, `ctx` 요소를 검은 색으로 설정하고 상단/좌측 좌표를 0,0으로 설정하고 높이와 너비를 캔버스와 동일하게 설정합니다.
2. 텍스쳐 **불러오기**
> tip: `await loadTexture`를 사용하여 플레이어와 적 이미지를 추가하고 이미지 경로를 전달합니다. 아직 화면에서 볼 수 없습니다!
3. 화면 중앙 하단의 중간에 영웅 **그리기**
> tip: `drawImage` API 를 사용하여 화면에 heroImg를 그립니다, `canvas.width / 2 - 45``canvas.height - canvas.height / 4)`로 설정합니다.
4. 5*5 몬스터를 **그리기**
> tip: 이제 주석을 풀고 화면에 적을 그립니다. 그런 다음, `createEnemies` 함수로 가서 작성합니다.
먼저, 약간의 constants를 설정합니다:
```javascript
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
```
그런 다음, 화면에 몬스터 배열을 그리는 반복문을 만듭니다:
```javascript
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
ctx.drawImage(enemyImg, x, y);
}
}
```
## 결과
완성된 결과는 아래와 같이 보이게 됩니다:
![Black screen with a hero and 5*5 monsters](partI-solution.png)
## 솔루션
먼저 직접 해결해보고 문제가 발생한다면, [solution](solution/app.js)을 보세요
---
## 🚀 도전
2D-중심의 Canvas API로 그리는 방식에 대해 배웠습니다; [WebGL API](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API)를 살펴보고, 3D 개체를 그려보세요.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
[reading about it](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API)을 통해 Canvas API에 대해 자세히 알아보세요.
## 과제
[Play with the Canvas API](assignment.md)

@ -0,0 +1,389 @@
# Space 게임 제작하기 파트 3: 모션 추가하기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
외계인이 화면을 돌아다니기 전까지는 게임이 재미 없습니다! 이 게임에서는, 두 가지 타입의 동작을 씁니다:
- **키보드/마우스 동작**: 사용자가 키보드 또는 마우스와 상호작용하여 화면에서 개체를 움질일 때.
- **게임으로 움직이는 동작**: 게임이 일정 시간 간격으로 객체를 움직일 때.
그러면 화면에서 물건을 어떻게 움직일까요? 그것은 모두 직교 좌표에 관한 것입니다: 객체의 위치 (x, y)를 변경 한 뒤에 화면을 다시 그립니다.
일반적으로 화면에서 *이동*을 하려면 다음 단계가 필요합니다:
1. 객체의 **새로운 위치 설정하기**; 이는 객체가 움직인 것으로 인식하는 데 필요합니다.
2. **화면 비우기**, 화면은 그려지는 사이에 비워져야 합니다. 배경색으로 채운 사각형을 그려서 지울 수 있습니다.
3. 새로운 위치에서 **개체를 다시 그리기**. 최종적으로 한 위치에서 다른 위치로 객체를 이동합니다.
코드에서 다음과 같이 보일 수 있습니다:
```javascript
//set the hero's location
hero.x += 5;
// clear the rectangle that hosts the hero
ctx.clearRect(0, 0, canvas.width, canvas.height);
// redraw the game background and hero
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = "black";
ctx.drawImage(heroImg, hero.x, hero.y);
```
✅ 영웅을 초당 수 많은 프레임으로 다시 그리게 될 때 성능 비용이 발생하는 이유를 알 수 있나요? [alternatives to this pattern](https://www.html5rocks.com/en/tutorials/canvas/performance/)에 대하여 읽어보세요.
## 키보드 이벤트 제어하기
특정 이벤트를 코드에 연결하여 이벤트를 처리합니다. 키보드 이벤트는 전체 윈도우에서 연결되는 반면에 `click`과 같은 마우스 이벤트는 클릭하는 특정 요소에 연결할 수 있습니다. 이 프로젝트에서는 키보드 이벤트를 사용합니다.
이벤트를 처리하려면 윈도우의 `addEventListener ()` 메소드를 사용하고 두 개의 입력 파라미터를 제공해야 합니다. 첫 번째 파라미터는 이벤트의 이름입니다, 예시를 들자면 `keyup`과 같습니다. 두 번째 파라미터는 이벤트가 발생함에 따라 호출될 함수입니다.
여기는 예시입니다:
```javascript
window.addEventListener('keyup', (evt) => {
// `evt.key` = string representation of the key
if (evt.key === 'ArrowUp') {
// do something
}
})
```
키 이벤트의 경우에는 어떤 키를 눌렀는지 확인할 때 쓸 수 있는 이벤트로 두 가지 속성이 있습니다:
- `key`, 눌린 키의 문자열 표현입니다, 예를 들어 `ArrowUp` 입니다.
- `keyCode`, 숫자 표현입니다. 예를 들어 `37``ArrowLeft`에 해당합니다.
✅ 키 이벤트 조작은 게임 개발 외부에서 유용합니다. 이 기술의 다른 사용법은 무엇일까요?
### 특별한 키: a caveat
윈도우에 영향을 주는 몇 가지 *특별한* 키가 있습니다. 즉 `keyup` 이벤트를 듣고 이 특별한 키를 사용하면 영웅을 이동하여 가로 스크롤도 할 수 있다는 것을 의미합니다. 따라서 게임을 제작할 때는 이 내장 브라우저 동작을 *차단* 할 수 있습니다. 다음과 같은 코드가 필요합니다:
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
switch (e.keyCode) {
case 37:
case 39:
case 38:
case 40: // Arrow keys
case 32:
e.preventDefault();
break; // Space
default:
break; // do not block other keys
}
};
window.addEventListener('keydown', onKeyDown);
```
위 코드는 화살표-키와 스페이스 키의 *기본* 동작을 막습니다. *차단* 메커니즘은 `e.preventDefault()`를 호출할 때 발생합니다.
## 게임의 움직임
각 틱 또는 시간 간격에서 객체의 위치를 업데이트하는 `setTimeout()` 또는 `setInterval()` 함수 같은 타이머를 사용하여 스스로 움직일 수 있습니다. 다음과 같이 보입니다:
```javascript
let id = setInterval(() => {
//move the enemy on the y axis
enemy.y += 10;
})
```
## 게임 루프
게임 루프는 기본적으로 일정한 간격마다 호출되는 함수인 개념입니다. 사용자에게 보여줄 모든 것이 루프에 그려지므로 이것을 게임 루프라고 합니다. 게임 루프는 게임의 일부인 모든 게임 객체를 사용하여, 모종의 이유로 더 이상 게임의 일부가 아니지 않는 이상 다 그립니다. 예를 들면 객체가 레이저에 맞아 폭발한 적이 있다면 더 이상 현재 게임 루프의 일부가 아닙니다 (다음 단원에서 자세히 알아 볼 것입니다).
다음은 일반적으로 코드로 표현된 게임 루프의 모습입니다:
```javascript
let gameLoopId = setInterval(() =>
function gameLoop() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawHero();
drawEnemies();
drawStaticObjects();
}, 200);
```
캔버스를 다시 그리기 위해 위의 루프가 `200` milliseconds 마다 호출됩니다. 게임에 가장 적합한 간격을 고를 수 있습니다.
## Space 게임 계속하기
기존 코드를 가져와 확장합니다. 파트 I 에서 작성한 코드로 시작하거나 [Part II- starter](your-work)의 코드를 사용합니다.
- **영웅을 움직이기**: 화살표 키를 사용하여 영웅을 이동할 수 있도록 코드를 추가합니다.
- **적을 움직이기**: 적들이 주어진 속도로 상단에서 하단으로 이동할 수 있도록 코드를 추가합니다.
## 권장 단계
`your-work` 하위 폴더에 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| index.html
-| app.js
-| package.json
```
타이핑하여 `your_work` 폴더에서 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하면 영웅과 모든 적을 렌더링 해야하지만; 아무것도 움직이지 않습니다 - 아직!
### 코드 추가하기
1. `영웅``적` 그리고 `게임 객체`에 대한 **전용 객체를 추가합니다**. `x` 혹은 `y` 속성이 필요합니다. ([Inheritance or composition](../README.md) 파트를 기억하세요).
*힌트* `game object``x``y`가 있으면서 canvas에 그릴 수 있는 능력이 되어야 합니다.
>tip: 생성자가 아래와 같이 이루어진 새로운 GameObject 클래스를 추가하여, 시작한 뒤에 canvas로 그립니다:
```javascript
class GameObject {
constructor(x, y) {
this.x = x;
this.y = y;
this.dead = false;
this.type = "";
this.width = 0;
this.height = 0;
this.img = undefined;
}
draw(ctx) {
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
}
}
```
이제, GameObject를 확장하여 영웅과 적을 생성합니다.
```javascript
class Hero extends GameObject {
constructor(x, y) {
...it needs an x, y, type, and speed
}
}
```
```javascript
class Enemy extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 98), (this.height = 50);
this.type = "Enemy";
let id = setInterval(() => {
if (this.y < canvas.height - this.height) {
this.y += 5;
} else {
console.log('Stopped at', this.y)
clearInterval(id);
}
}, 300)
}
}
```
2. **키-이벤트 핸들러를 추가**하여 키 탐색을 처리합니다 (Hero를 상/하 좌/우로 이동).
*기억합시다* 데카르트 시스템이고, 좌측 상단은 `0,0`입니다. 또한 *기본 동작*을 중지하는 코드를 추가해야 합니다
>tip: onKeyDown 함수를 만들고 윈도우에 붙입니다.
```javascript
let onKeyDown = function (e) {
console.log(e.keyCode);
...add the code from the lesson above to stop default behavior
}
};
window.addEventListener("keydown", onKeyDown);
```
이 지점에서 브라우저 콘솔을 확인해봅니다, 그리고 로깅되는 키 입력을 봅니다.
3. [Pub sub pattern](../README.md)으로 **구현합니다**, 이는 남은 파트를 따라가면서 코드를 깨끗하게 유지할 수 있습니다.
이 마지막 파트를 진행하면, 다음을 할 수 있습니다:
1. 윈도우에 **Add an 이벤트 리스너를 추가합니다**:
```javascript
window.addEventListener("keyup", (evt) => {
if (evt.key === "ArrowUp") {
eventEmitter.emit(Messages.KEY_EVENT_UP);
} else if (evt.key === "ArrowDown") {
eventEmitter.emit(Messages.KEY_EVENT_DOWN);
} else if (evt.key === "ArrowLeft") {
eventEmitter.emit(Messages.KEY_EVENT_LEFT);
} else if (evt.key === "ArrowRight") {
eventEmitter.emit(Messages.KEY_EVENT_RIGHT);
}
});
```
1. publish하고 메시지를 subscribe할 **EventEmitter 클래스를 생성합니다**:
```javascript
class EventEmitter {
constructor() {
this.listeners = {};
}
on(message, listener) {
if (!this.listeners[message]) {
this.listeners[message] = [];
}
this.listeners[message].push(listener);
}
emit(message, payload = null) {
if (this.listeners[message]) {
this.listeners[message].forEach((l) => l(message, payload));
}
}
}
```
1. EventEmitter를 설정하고 **constants를 추가합니다**:
```javascript
const Messages = {
KEY_EVENT_UP: "KEY_EVENT_UP",
KEY_EVENT_DOWN: "KEY_EVENT_DOWN",
KEY_EVENT_LEFT: "KEY_EVENT_LEFT",
KEY_EVENT_RIGHT: "KEY_EVENT_RIGHT",
};
let heroImg,
enemyImg,
laserImg,
canvas, ctx,
gameObjects = [],
hero,
eventEmitter = new EventEmitter();
```
1. **게임을 초기화합니다**
```javascript
function initGame() {
gameObjects = [];
createEnemies();
createHero();
eventEmitter.on(Messages.KEY_EVENT_UP, () => {
hero.y -=5 ;
})
eventEmitter.on(Messages.KEY_EVENT_DOWN, () => {
hero.y += 5;
});
eventEmitter.on(Messages.KEY_EVENT_LEFT, () => {
hero.x -= 5;
});
eventEmitter.on(Messages.KEY_EVENT_RIGHT, () => {
hero.x += 5;
});
}
```
1. **게임 루프를 설정합니다**
window.onload 함수를 리팩터링하여 게임을 초기화하고 적절한 간격으로 게임 루프를 설정합니다. 레이저 빔도 추가합니다:
```javascript
window.onload = async () => {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
heroImg = await loadTexture("assets/player.png");
enemyImg = await loadTexture("assets/enemyShip.png");
laserImg = await loadTexture("assets/laserRed.png");
initGame();
let gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGameObjects(ctx);
}, 100)
};
```
5. 일정 간격으로 움직이는 적에 대한 **코드를 작성합니다**
`createEnemies()`함수를 리팩터링하여 적을 생성하고 새로운 gameObjects 클래스로 푸시합니다:
```javascript
function createEnemies() {
const MONSTER_TOTAL = 5;
const MONSTER_WIDTH = MONSTER_TOTAL * 98;
const START_X = (canvas.width - MONSTER_WIDTH) / 2;
const STOP_X = START_X + MONSTER_WIDTH;
for (let x = START_X; x < STOP_X; x += 98) {
for (let y = 0; y < 50 * 5; y += 50) {
const enemy = new Enemy(x, y);
enemy.img = enemyImg;
gameObjects.push(enemy);
}
}
}
```
그리고 비슷한 과정으로 영웅에 대한 `createHero()` 함수를 추가합니다.
```javascript
function createHero() {
hero = new Hero(
canvas.width / 2 - 45,
canvas.height - canvas.height / 4
);
hero.img = heroImg;
gameObjects.push(hero);
}
```
그리고 마지막으로, 그리기를 시작할 `drawGameObjects()` 함수를 추가합니다:
```javascript
function drawGameObjects(ctx) {
gameObjects.forEach(go => go.draw(ctx));
}
```
적들이 영웅 spaceship의 앞으로 나아가려고 합니다!
---
## 🚀 도전
보다가, 함수와 변수 및 클래스를 추가하기 시작하면 코드가 '스파게티 코드'로 변할 수 있습니다. 코드를 더 읽기 쉽게 구성하려면 어떻게 해야 될까요? 코드가 여전히 하나의 파일에 있어도, 어울리는 시스템을 기획하시기 바랍니다.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
프레임워크를 사용하지 않고 게임을 작성하는 동안, 게임 개발을 위한 JavaScript-기반 canvas 프레임워크가 많이 존재하고 있습니다. 시간을 내어 [about these](https://github.com/collections/javascript-game-engines)를 보시기 바랍니다.
## 과제
[Comment your code](assignment.md)

@ -0,0 +1,297 @@
# Space 게임 제작하기 파트 4: 레이저 추가하고 충돌 감지하기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
이 강의에서는 JavaScript로 레이저를 쏘는 방법을 배웁니다! 게임에 다음 두 가지를 추가합니다:
- **레이저**: 이 레이저는 영웅 우주선에서 수직 위쪽으로 발사되며
- **충돌 감지**, *쏘는* 기능 구현의 부분으로 몇 가지 멋진 게임 규칙을 추가할 예정입니다:
- **레이저로 적 때리기**: 레이저에 맞으면 적은 사망합니다
- **레이저로 화면 상단 도달하기**: 화면의 상단 부분을 맞으면 레이저는 파괴됩니다
- **적과 영웅 충돌하기**: 적과 영웅이 부딪히면 파괴됩니다
- **적이 화면 하단 도달하기**: 적이 화면 하단에 부딪히면 적과 영웅이 파괴됩니다
짧게 요약해보면, 여러분은 -- *영웅* -- 모든 적들이 화면 아래로 내려오기 전에 레이저로 모든 적을 때려야 합니다.
✅ 지금까지 작성된 최초의 컴퓨터 게임에 대해 약간 알아보새요. 어떻게 작동하나요?
함께 영웅이 됩시다!
## 충돌 감지하기
충돌은 어떻게 감지할까요? 게임 객체를 움직이는 직사각형으로 생각해야 합니다. 왜 물어볼까요? 게임 객체를 그리는 데 사용되는 이미지는 직사각형이기 때문입니다: `x`, `y`, `width` 그리고 `height`가 있습니다.
만약 두 직사각형이, 즉 영웅과 적이 *교차*하면, 충돌합니다. 그런 뒤에 발생할 일은 게임의 룰에 달려 있습니다. 따라서 충돌 감지를 구현하려면 다음이 필요합니다:
1. 게임 객체의 직사각형 표현을 얻는 방법은, 다음과 같습니다:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width
}
}
```
2. 비교 함수는, 다음 함수와 같습니다:
```javascript
function intersectRect(r1, r2) {
return !(r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top);
}
```
## 어떻게 파괴할까요
게임에서 물건을 파괴하려면 특정 간격으로 연결되는 게임 루프에서 이 아이템을 더 이상 그리지 않아야 한다고 게임에 알려야 합니다. 이 방법은 다음과 같이, 어떤 일이 발생했을 때 게임 객체를 *dead*으로 표시합니다:
```javascript
// collision happened
enemy.dead = true
```
그러고 다음과 같이, 화면을 다시 그리기 전에 *dead* 객체를 정렬합니다:
```javascript
gameObjects = gameObject.filter(go => !go.dead);
```
## 어떻게 레이저를 발사할까요
레이저를 발사하는 것은 키-이벤트에 반응하고 특정 방향으로 움직이는 객체를 만드는 것으로 바뀝니다. 따라서 다음 단계를 해야합니다:
1. **레이저 객체 생성**: 영웅 함선의 상단에서, 생성되고 화면 상단으로 올라가기 시작합니다.
2. **키 이벤트에 코드 첨부**: 레이저를 쏘는 플레이어로 특정할 키보드의 키를 선택해야 합니다.
3. 키를 누를 때, **레이저처럼 보이는 게임 객체를 생성합니다**.
## 레이저 쿨다운
레이저는 *space*와 같은 키를 누를 때마다 발사되어야 합니다. 게임이 짧은 시간에 너무 많은 레이저를 생성하는 것을 막으려면 이를 해결해야 합니다. 해결 방법은 레이저를 자주 발사하도록 보장하는 타이머인, *cooldown*을 구현하는 것입니다. 다음과 같이 구현할 수 있습니다:
```javascript
class Cooldown {
constructor(time) {
this.cool = false;
setTimeout(() => {
this.cool = true;
}, time)
}
}
class Weapon {
constructor {
}
fire() {
if (!this.cooldown || this.cooldown.cool) {
// produce a laser
this.cooldown = new Cooldown(500);
} else {
// do nothing - it hasn't cooled down yet.
}
}
}
```
*cooldowns*에 대해 복습하려면 space 게임 시리즈의 1강을 참조하세요.
## 무엇을 만드나요
이전 강의에 존재한 기존 코드 (정리하고 리팩토링함)를 가져와서, 확장합니다. 파트 II에서 코드를 시작하거나 [Part III- starter](/your-work) 코드를 사용합니다.
> tip: 작업할 레이저는 이미 어셋 폴더에 있으므로 코드에서 참조합니다
- **충돌 감지를 추가합니다**, 레이저가 무언가 부딪칠 때 다음 규칙이 적용되어야 합니다:
1. **레이저가 적 때리기**: 레이저에 맞으면 적은 사망합니다
2. **레이저로 화면 상단 도달하기**: 화면의 상단 부분을 맞으면 레이저는 부서집니다
3. **적과 영웅 충돌하기**: 적과 영웅이 부딪히면 파괴됩니다
4. **적이 화면 하단 도달하기**: 적이 화면 하단에 부딪히면 적과 영웅이 파괴됩니다
## 권장 단계
`your-work` 하위 폴더에 생성된 파일을 찾습니다. 이는 다음을 포함하고 있어야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
타이핑하여 `your_work` 폴더에서 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하세요, 지금 바로 영웅과 모든 적을 렌더링해야합니다, 하지만 다 움직이지 않습니다 - 아직 :).
### 코드 추가하기
1. ***충돌을 처리하기 위해 게임 객체의 사각형 표현을 설정합니다** 아래 코드를 쓰면 `GameObject`의 사각형 표현을 얻을 수 있습니다. GameObject 클래스를 편집하여 확장합니다:
```javascript
rectFromGameObject() {
return {
top: this.y,
left: this.x,
bottom: this.y + this.height,
right: this.x + this.width,
};
}
```
2. **충돌을 확인하는 코드를 추가합니다** 이것은 두 개의 직사각형이 교차되는가에 대한 여부를 테스트하는 새로운 함수입니다:
```javascript
function intersectRect(r1, r2) {
return !(
r2.left > r1.right ||
r2.right < r1.left ||
r2.top > r1.bottom ||
r2.bottom < r1.top
);
}
```
3. **레이저 발사 기능 추가**
1. **키-이벤트 메시지 추가하기**. *space* 키는 영웅 함선 바로 위에 레이저를 만들어줘야 합니다. Messages 객체에 세 개의 상수를 추가합니다:
```javascript
KEY_EVENT_SPACE: "KEY_EVENT_SPACE",
COLLISION_ENEMY_LASER: "COLLISION_ENEMY_LASER",
COLLISION_ENEMY_HERO: "COLLISION_ENEMY_HERO",
```
1. **space 키 제어하기**. `window.addEventListener` 키업 함수로 spaces를 제어합니다:
```javascript
} else if(evt.keyCode === 32) {
eventEmitter.emit(Messages.KEY_EVENT_SPACE);
}
```
1. **리스너 추가하기**. `initGame()` 함수를 편집해서 space 바를 눌렀을 때 hero가 발사할 수 있도록 합니다:
```javascript
eventEmitter.on(Messages.KEY_EVENT_SPACE, () => {
if (hero.canFire()) {
hero.fire();
}
```
새로운 `eventEmitter.on ()` 함수를 추가해서 적이 레이저와 부딪칠 때 동작하도록 합니다:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
})
```
1. **객체 움직이기**, 레이저가 화면 상단으로 조금씩 이동하고 있는지 확인합니다. 저번처럼, `GameObject`를 확장하는 새로운 Laser 클래스를 만듭니다:
```javascript
class Laser extends GameObject {
constructor(x, y) {
super(x,y);
(this.width = 9), (this.height = 33);
this.type = 'Laser';
this.img = laserImg;
let id = setInterval(() => {
if (this.y > 0) {
this.y -= 15;
} else {
this.dead = true;
clearInterval(id);
}
}, 100)
}
}
```
1. **충돌 제어하기**, 레이저에 대한 충돌 규칙을 구현합니다. 적에 충돌하는 객체를 테스트하는 `updateGameObjects ()` 함수를 추가합니다:
```javascript
function updateGameObjects() {
const enemies = gameObjects.filter(go => go.type === 'Enemy');
const lasers = gameObjects.filter((go) => go.type === "Laser");
// laser hit something
lasers.forEach((l) => {
enemies.forEach((m) => {
if (intersectRect(l.rectFromGameObject(), m.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_LASER, {
first: l,
second: m,
});
}
});
});
gameObjects = gameObjects.filter(go => !go.dead);
}
```
`window.onload`의 게임 루프에 `updateGameObjects()`를 추가해야 합니다.
4. 레이저의 **cooldown을 구현합니다**, 그래서 자주 발사할 수 있습니다.
마지막으로, cooldown을 할 수 있도록 Hero 클래스를 편집합니다:
```javascript
class Hero extends GameObject {
constructor(x, y) {
super(x, y);
(this.width = 99), (this.height = 75);
this.type = "Hero";
this.speed = { x: 0, y: 0 };
this.cooldown = 0;
}
fire() {
gameObjects.push(new Laser(this.x + 45, this.y - 10));
this.cooldown = 500;
let id = setInterval(() => {
if (this.cooldown > 0) {
this.cooldown -= 100;
} else {
clearInterval(id);
}
}, 200);
}
canFire() {
return this.cooldown === 0;
}
}
```
여기에서 핵심은, 게임이 몇 가지 기능을 가지고 있다는 사실입니다! 화살표 키로 탐색하고, 스페이스 바로 레이저를 발사할 수 있으며, 적을 치면 사라지게 합니다. 잘 하셨습니다!
---
## 🚀 도전
폭발을 추가합니다! [the Space Art repo](../solution/spaceArt/readme.txt)에서 게임 어셋을 살펴보고 레이저가 외계인을 칠 때 폭발하도록 추가해보세요
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
지금까지 게임의 간격을 실험 해보세요. 바꾸면 어떻게 되나요? [JavaScript timing events](https://www.freecodecamp.org/news/javascript-timing-events-settimeout-and-setinterval/)에 대하여 더 읽어보시기 바랍니다.
## 과제
[Explore collisions](assignment.md)

@ -0,0 +1,189 @@
# Space 게임 제작하기 파트 5: 점수내고 살기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
이 강의에서는 어떻게 게임에서 점수를 내고 생명을 구하는 가에 대하여 배웁니다.
## 화면에 텍스트 그리기
화면에 게임 점수를 표시하려면, 화면에 텍스트를 두는 방법을 알아야 합니다. 답변은 canvas 객체에 `fillText()` 메소드를 사용한다고 할 수 있습니다. 사용할 글꼴, 텍스트 색상과 정렬(왼쪽, 오른쪽, 가운데)처럼 다른 측면으로 제어할 수 있습니다. 다음은 화면에 텍스트를 그리는 코드입니다:
```javascript
ctx.font = "30px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "right";
ctx.fillText("show this on the screen", 0, 0);
```
✅ [how to add text to a canvas](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text)에 대하여 더 읽어보세요, 그리고 더 멋지게 보이도록 자유롭게 느껴보세요!
## 게임 컨셉에서 생명
게임에서 생명을 가진다는 개념은 숫자에 불과합니다. space 게임의 맥락에서는 배가 피해를 입었을 때마다 생명을 하나씩 빼는 것이 일반적입니다. 숫자 대신 miniships이나 하트와 같은 그래픽으로 표현할 수 있다면 좋습니다.
## 무엇을 만드나요
게임에 다음을 추가하겠습니다:
- **게임 점수**: 적의 배가 파괴될 때마다, 영웅은 점수를 받아야하고, 하나의 배마다 100점을 제안합니다. 게임 점수는 좌측 하단에 보여야 합니다.
- **생명**: 여러분의 배는 세 생명이 있습니다. 적의 배로 부딪칠 때마다 생명을 잃습니다. 생명 점수는 우측 하단에 보여야되고 ![life image](solution/assets/life.png)로 만들어야 합니다.
## 권장 단계
`your-work` 하위 폴더에서 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| index.html
-| app.js
-| package.json
```
타이핑해서 `your_work` 폴더에 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력하면, 바로 영웅과 모든 적을 렌더링해야하며, 왼쪽과 오른쪽 화살표를 누르면, 영웅이 움직이고 적을 격추할 수 있습니다.
### 코드 추가하기
1. `solution/assets/` 폴더에서 `your-work` 폴더로 **필요한 어셋을 복사합니다**; `life.png` 어셋을 추가합니다. window.onload 함수에 lifeImg를 추가합니다:
```javascript
lifeImg = await loadTexture("assets/life.png");
```
1. 어셋의 배열에 `lifeImg`를 추가합니다:
```javascript
let heroImg,
...
lifeImg,
...
eventEmitter = new EventEmitter();
```
2. **변수를 추가합니다**. 총 점수(0)과 남은 생명(3)을 나타내는 코드를 추가하고, 이 점수를 화면에 출력합니다.
3. **`updateGameObjects()` 함수를 확장합니다**. `updateGameObjects()` 함수를 확장하여 적 충돌을 제어합니다:
```javascript
enemies.forEach(enemy => {
const heroRect = hero.rectFromGameObject();
if (intersectRect(heroRect, enemy.rectFromGameObject())) {
eventEmitter.emit(Messages.COLLISION_ENEMY_HERO, { enemy });
}
})
```
4. **`life``points`를 추가하기**.
1. **변수를 초기화합니다**. `Hero` 클래스의 `this.cooldown = 0` 아래에 생명과 점수를 설정합니다:
```javascript
this.life = 3;
this.points = 0;
```
1. **화면에 점수를 그립니다**. 이 값을 화면에 그립니다:
```javascript
function drawLife() {
// TODO, 35, 27
const START_POS = canvas.width - 180;
for(let i=0; i < hero.life; i++ ) {
ctx.drawImage(
lifeImg,
START_POS + (45 * (i+1) ),
canvas.height - 37);
}
}
function drawPoints() {
ctx.font = "30px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "left";
drawText("Points: " + hero.points, 10, canvas.height-20);
}
function drawText(message, x, y) {
ctx.fillText(message, x, y);
}
```
1. **게임 루프에 메소드를 추가합니다**. `updateGameObjects()` 아래의 window.onload 함수에 다음 함수를 추가해야 합니다:
```javascript
drawPoints();
drawLife();
```
1. **게임 규칙을 구현합니다**. 다음 게임 규칙을 구현합니다:
1. **모든 영웅과 적의 충돌**에 대해 생명을 깍습니다.
깍기 위해서 `Hero` 클래스를 확장합니다:
```javascript
decrementLife() {
this.life--;
if (this.life === 0) {
this.dead = true;
}
}
```
2. **적을 공격하는 모든 레이저는**, 게임 점수 100점을 올립니다.
올리기 위해서 `Hero` 클래스를 확장합니다:
```javascript
incrementPoints() {
this.points += 100;
}
```
Collision Event Emitter에 다음 함수를 추가합니다:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
})
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
});
```
✅ JavaScript/Canvas를 사용하여 만든 다른 게임을 찾으려면 약간 알아보세요. 공통된 특징은 무엇일까요?
이 작업이 끝날 즈음, 우측 하단에 작은 '생명' 배, 좌측 하단에 점수를 보여줘야 하며, 적과 부딪칠 때마다 생명의 개수가 감소하고 적을 쏠 때마다 점수가 증가하는 것을 볼 수 있습니다. 잘 했습니다! 게임이 거의 완료되었습니다.
---
## 🚀 도전
코드는 거의 완성되었습니다. 다음 단계를 상상할 수 있나요?
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
게임 점수와 생명을 늘리거나 줄일 수 있는 몇 가지 방법을 조사해보세요. [PlayFab](https://playfab.com)처럼 흥미로운 게임 엔진이 있습니다. 이 중 하나를 사용하면 어떻게 게임을 향상시킬 수 있을까요?
## 과제
[Build a Scoring Game](assignment.md)

@ -0,0 +1,222 @@
# Space 게임 제작하기 파트 6: 끝과 재시작
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
게임에서 *조건을 표현하고 종료*하는 여러 방식이 있습니다. 게임이 종료된 이유를 말하는 것은 게임 크리에이터의 일입니다. 지금까지 만든 space 게임에 대해 말하고 있다고 가정하면, 몇 가지 이유가 있습니다:
- **`N`개의 적 배가 파괴되었습니다**: 게임을 여러 레벨로 나누면 레벨을 완료하기 위해 `N`개의 적 배를 부숴야하는 경우가 매우 흔합니다.
- **배가 파괴되었습니다**: 배가 부서지면 지는 게임이 분명 있습니다. 또 다른 일반적인 접근 방식은 생명의 컨셉을 가지고 있다는 점입니다. 배가 부서질 때마다 생명이 깍입니다. 모든 목숨을 잃으면 게임에서 집니다.
- **`N` 점수를 모았습니다**: 또 다른 종료 조건은 점수를 모으는 것입니다. 점수를 얻는 방법으로 각자 배를 파괴하는 것처럼 다양한 활동에 점수를 할당하거나 아이템이 부서질 때마다 *떨구는* 아이템을 수집하는 것은 매우 일반적입니다.
- **레벨을 완료했습니다**: 적 배를 `X` 번 부시거나, `Y` 점수를 수집하거나 특정 아이템을 수집하는 것처럼 여러 조건들을 여기에 포함할 수 있습니다.
## 다시 시작하기
사람들이 게임을 즐기고 있다면 다시 플레이하고 싶어합니다. 어떤 이유든지 게임이 끝나면 다시 시작할 수 있는 대안을 줘야합니다.
✅ 어떤 조건에서 게임이 끝나는 지에 대하여 찾고, 다시 시작이라는 메시지가 어떻게 보일지 생각해보세요
## 무엇을 만드나요
게임에 다음 규칙을 추가합니다:
1. **게임에 우승합니다**. 모든 적의 배가 부서지면, 게임에서 승리합니다. 추가로 일종의 승리 메시지를 출력합니다.
1. **다시 시작합니다**. 모든 생명을 잃거나 게임에서 이긴다면, 게임을 다시 시작할 방법을 제공해야 합니다. 생각해보세요! 게임을 다시 초기화하고 이전 게임 상태를 깨끗이 지워야 합니다.
## 권장 단계
`your-work` 하위 폴더에서 생성된 파일을 찾습니다. 다음을 포함해야 합니다:
```bash
-| assets
-| enemyShip.png
-| player.png
-| laserRed.png
-| life.png
-| index.html
-| app.js
-| package.json
```
타이핑해서 `your_work` 폴더에 프로젝트를 시작합니다:
```bash
cd your-work
npm start
```
위 코드는 `http://localhost:5000` 주소에서 HTTP 서버를 시작합니다. 브라우저를 열고 해당 주소를 입력합니다. 게임은 플레이 가능한 상태여야 합니다.
> tip: Visual Studio Code에서 경고를 보이지 않게 하려면, `gameLoopId`를 (`let`없이) 그대로 호출하도록 `window.onload` 함수를 편집하고, 파일 최상단에 gameLoopId를 독립적으로 선언합니다: `let gameLoopId;`
### 코드 추가하기
1. **종료 조건을 추적합니다**. 다음 두 함수를 추가하여 적의 수를 추적하거나, 영웅의 배가 부서진 경우도 추적해주는 코드를 추가합니다:
```javascript
function isHeroDead() {
return hero.life <= 0;
}
function isEnemiesDead() {
const enemies = gameObjects.filter((go) => go.type === "Enemy" && !go.dead);
return enemies.length === 0;
}
```
1. **메시지 핸들러에 로직을 추가합니다**. 이 조건을 제어하도록 `eventEmitter`를 편집합니다:
```javascript
eventEmitter.on(Messages.COLLISION_ENEMY_LASER, (_, { first, second }) => {
first.dead = true;
second.dead = true;
hero.incrementPoints();
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.COLLISION_ENEMY_HERO, (_, { enemy }) => {
enemy.dead = true;
hero.decrementLife();
if (isHeroDead()) {
eventEmitter.emit(Messages.GAME_END_LOSS);
return; // loss before victory
}
if (isEnemiesDead()) {
eventEmitter.emit(Messages.GAME_END_WIN);
}
});
eventEmitter.on(Messages.GAME_END_WIN, () => {
endGame(true);
});
eventEmitter.on(Messages.GAME_END_LOSS, () => {
endGame(false);
});
```
1. **새로운 메시지 타입을 추가합니다**. 상수 객체에 이 메시지를 추가합니다:
```javascript
GAME_END_LOSS: "GAME_END_LOSS",
GAME_END_WIN: "GAME_END_WIN",
```
2. **재시작 코드를 추가합니다** 선택한 버튼을 누르면 게임을 다시 시작하는 코드입니다.
1. **`Enter` 누를 키를 수신합니다**. 누르는 것을 수신하도록 윈도우의 이벤트 리스너를 편집합니다:
```javascript
else if(evt.key === "Enter") {
eventEmitter.emit(Messages.KEY_EVENT_ENTER);
}
```
1. **재시작 메시지 추가하기**. 메시지를 메시지 상수에 추가합니다:
```javascript
KEY_EVENT_ENTER: "KEY_EVENT_ENTER",
```
1. **게임 규칙을 구현합니다**. 다음 게임 규칙을 구현합니다:
1. **플레이어 승리 조건입니다**. 적 배가 모두 파괴되면, 승리 메시지를 출력합니다.
1. 먼저, `displayMessage()` 함수를 만듭니다:
```javascript
function displayMessage(message, color = "red") {
ctx.font = "30px Arial";
ctx.fillStyle = color;
ctx.textAlign = "center";
ctx.fillText(message, canvas.width / 2, canvas.height / 2);
}
```
1. `endGame()` 함수를 만듭니다:
```javascript
function endGame(win) {
clearInterval(gameLoopId);
// set a delay so we are sure any paints have finished
setTimeout(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (win) {
displayMessage(
"Victory!!! Pew Pew... - Press [Enter] to start a new game Captain Pew Pew",
"green"
);
} else {
displayMessage(
"You died !!! Press [Enter] to start a new game Captain Pew Pew"
);
}
}, 200)
}
```
1. **로직을 다시 시작합니다**. 모든 생명을 잃거나 플레이어가 게임에서 이긴다면, 게임을 다시 시작할 수 있다고 출력합니다. 추가로 *restart* 키를 누르면 게임을 다시 시작합니다 (다시 시작하기 위해 매핑할 키를 고를 수 있습니다).
1. `resetGame()` 함수를 만듭니다:
```javascript
function resetGame() {
if (gameLoopId) {
clearInterval(gameLoopId);
eventEmitter.clear();
initGame();
gameLoopId = setInterval(() => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawPoints();
drawLife();
updateGameObjects();
drawGameObjects(ctx);
}, 100);
}
}
```
1. `initGame()`에서 게임을 다시 설정하기 위해 `eventEmitter`에 호출하도록 추가합니다:
```javascript
eventEmitter.on(Messages.KEY_EVENT_ENTER, () => {
resetGame();
});
```
1. EventEmitter에 `clear()` 힘수를 추가합니다:
```javascript
clear() {
this.listeners = {};
}
```
👽 💥 🚀 축하합니다, 대장! 게임이 완성되었습니다! 잘 하셨습니다! 🚀 💥 👽
---
## 🚀 도전
소리를 추가해보세요! 레이저가 때리거나, 영웅이 죽고 이길 때, 소리를 추가하여 게임 플레이를 향상시킬 수 있나요? [sandbox](https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_audio_play)에서 JavaScript로 소리를 재생하는 방법에 대하여 알아보세요
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
과제는 새로운 샘플 게임을 만드는 것이므로, 어떤 타입의 게임을 만들 수 있는지 알아보고 흥미로운 게임을 찾아보세요.
## 과제
[Build a Sample Game](assignment.md)

@ -0,0 +1,31 @@
# 스페이스 게임 만들기
고급 JavaScript 기초를 가르치는 스페이스 게임
이 강의에서는 자신만의 스페이스 게임을 만드는 방법을 배웁니다. 만약 "Space Invaders" 게임을 해본 적이 있다면, 이 게임은 같은 아이디어를 가지고 있습니다: 우주선을 조종하고 위에서 내려오는 몬스터를 향해서 발사하는 것입니다. 완성된 게임의 모습은 다음과 같습니다.
![Finished game](images/pewpew.gif)
6개의 강의에서 다음을 학습합니다:
- **상호 작용** Canvas 요소를 사용한 화면 그림
- **이해** cartesian 좌표 시스템
- **학습** 유지 및 확장이 더 쉬운 건전한 게임 아키텍처를 만들기위한 Pub-Sub 패턴
- **이점** 게임 리소스를 불러오기 위한 Async/Await
- **제어** 키보드 이벤트
## 개요
- 이론
- [JavaScript를 사용한 게임 빌드 소개](1-introduction/README.md)
- 실습
- [canvas로 그리기](2-drawing-to-canvas/README.md)
- [화면에 요소 이동](3-moving-elements-around/README.md)
- [충돌 감지](4-collision-detection/README.md)
- [점수 유지](5-keeping-score/README.md)
- [게임 종료 및 재시작](6-end-condition/README.md)
## Credits
The assets used for this came from https://www.kenney.nl/.
If you are into building games, these are some seriously good assets, a lot is free and some are paid.

@ -0,0 +1,295 @@
# 은행 앱 제작하기 파트 1: 웹 앱의 HTML 템플릿과 라우터
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
브라우저에 JavaScript가 등장한 이후, 웹 사이트는 그 어느 순간보다 상호 작용하며 복잡해지고 있습니다. 웹 기술은 일반적으로 [web applications](https://en.wikipedia.org/wiki/Web_application)라고 불리는 브라우저로 직접 실행되는 완전한 기능의 애플리케이션을 만들 때 사용됩니다. 웹 앱은 매우 대화형이므로, 사용자는 작업되는 순간에 전체 페이지가 다시 불러오며 기다리는 것을 원치 않습니다. 원활한 사용자 경험을 제공하기 위해, JavaScript로 DOM을 사용하여 HTML을 직접 갱신합니다.
이번 강의에서는, 전체 HTML 페이지를 다시 불러오지 않으면서 출력하고 갱신할 여러 화면을 만들기 위해 HTML 템플릿을 사용할 것이므로, 은행 웹 앱을 만들기 위한 기초를 레이아웃합니다.
### 준비물
이 강의에서 만들 웹 앱을 테스트하려면 로컬 웹 서버가 필요합니다. 없는 경우에는, [Node.js](https://nodejs.org)를 설치하고 프로젝트 폴더에서 `npx lite-server` 명령을 수행할 수 있습니다. 로컬 웹 서버를 만들고 브라우저에서 앱을 엽니다.
### 준비
컴퓨터에서, `index.html` 파일이 있는 `bank`라는 폴더를 만듭니다. 이 HTML [boilerplate](https://en.wikipedia.org/wiki/Boilerplate_code)에서 시작할 것 입니다.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bank App</title>
</head>
<body>
<!-- This is where you'll work -->
</body>
</html>
```
---
## HTML 템플릿
웹 페이지에 여러 화면을 만드려는 경우에는, 하나의 솔루션은 출력하려는 모든 화면에 대해 각자 HTML 파일을 만드는 것입니다. 그러나, 이 솔루션에는 몇 가지 불편한 점이 있습니다:
- 화면 전환 시 전체 HTML을 다시 불러와야 하므로, 속도가 느릴 수 있습니다.
- 서로 다른 화면 간 데이터 공유가 어렵습니다.
또 다른 방법은 HTML 파일이 하나일 때, `<template>` 요소로 여러 [HTML templates](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template)을 정의하는 것입니다. 템플릿은 브라우저에 보이지 않는 재사용 가능한 HTML 블록이면서, JavaScript를 사용해서 런타임에 인스턴스화합니다.
### 작업
두 화면이 있는 은행 앱을 만들 것입니다: 로그인 페이지와 대시보드. 먼저, 앱의 다양한 화면을 인스턴스화할 때 사용할 placeholder 요소를 HTML 본문에 추가하겠습니다:
```html
<div id="app">Loading...</div>
```
나중에 JavaScript로 쉽게 찾도록 `id`를 제공합니다.
> Tip: 이 요소의 내용은 바뀌므로, 앱이 불러와지는 동안 보여지는 로딩 메시지 또는 인디케이터를 넣을 수 있습니다.
다음은, 로그인 페이지 HTML 템플릿 아래에 추가하겠습니다. 지금은 탐색하며 사용할 버튼이 포함된 제목과 섹션만 여기에 넣겠습니다.
```html
<template id="login">
<h1>Bank App</h1>
<section>
<button>Login</button>
</section>
</template>
```
그러고 대시보드 페이지에 대한 다른 HTML 템플릿을 추가합니다. 이 페이지에는 다른 섹션도 포함됩니다:
- 제목과 로그아웃 버튼이 있는 헤더
- 은행 계정의 현재 잔액
- 테이블에 표시된, 트랜잭션 목록
```html
<template id="dashboard">
<header>
<h1>Bank App</h1>
<button>Logout</button>
</header>
<section>
Balance: 100$
</section>
<section>
<h2>Transactions</h2>
<table>
<thead>
<tr>
<th>Date</th>
<th>Object</th>
<th>Amount</th>
</tr>
</thead>
<tbody></tbody>
</table>
</section>
</template>
```
> Tip: HTML 템플릿을 만들 때, 모양을 확인하려면, `<!-->`로 묶어 `<template>` 혹은 `</template>` 줄을 주석 처리할 수 있습니다.
✅ 템플릿에 `id` 속성을 사용하는 이유는 무엇일까요? 강의처럼 다른 것을 쓸 수 있나요?
## JavaScript로 템플릿 출력하기
브라우저에서 현재 HTML 파일을 시도하면, `Loading...`이 출력되는 것을 볼 수 있습니다. HTML 템플릿을 인스턴스화하고 출력하기 위해 JavaScript 코드를 추가했기 때문입니다.
템플릿 인스턴스화는 일반적으로 3 단계로 진행됩니다:
1. [`document.getElementById`](https://developer.mozilla.org/en-US/docs/Web/API/Document/getElementById)를 사용한 예시로, DOM에서 템플릿 요소 검색합니다.
2. [`cloneNode`](https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode)로, 템플릿 요소를 복제합니다.
3. [`appendChild`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild)를 사용한 예시로, 보이는 요소 아래의 DOM에 붙입니다.
✅ DOM에 붙이기 전에 템플릿을 복제해야하는 이유는 무엇일까요? 이 단계를 넘기면 어떻게 될까요?
### 작업
프로젝트 폴더에 `app.js`라는 새 파일을 만들고 HTML의 `<head>` 섹션에서 해당 파일을 가져옵니다:
```html
<script src="app.js" defer></script>
```
이제 `app.js`에서, 새로운 함수인 `updateRoute`를 만듭니다:
```js
function updateRoute(templateId) {
const template = document.getElementById(templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
우리가 하는 일은 정확히 위에서 설명한 3단계입니다. id `templateId`로 템플릿을 인스턴스화하고, 복제된 콘텐츠를 앱 placeholder에 넣습니다. 템플릿의 전체 하위 트리를 복사하려면 `cloneNode(true)`로 사용해야 합니다.
이제 템플릿 중 하나를 사용하여 이 함수를 호출하고 결과를 봅니다.
```js
updateRoute('login');
```
✅ 이 `app.innerHTML = '';` 코드의 목적은 무엇인가요? 없다면 어떻게 될까요?
## 라우터 생성하기
웹 앱에 대해 이야기할 때, **URLs**을 보여주기 위해 특정 화면에 매핑하려는 의도를 *Routing*이라고 합니다. 여러 HTML 파일에, 웹 사이트에서 파일 경로가 URL에 반영되므로 자동으로 수행됩니다. 예를 들면, 프로젝트 폴더에 다음 파일이 있습니다:
```
mywebsite/index.html
mywebsite/login.html
mywebsite/admin/index.html
```
만약 상위에 `mywebsite`로 웹 서버를 생성하면, URL 맵핑은 이렇게 이루어집니다:
```
https://site.com --> mywebsite/index.html
https://site.com/login.html --> mywebsite/login.html
https://site.com/admin/ --> mywebsite/admin/index.html
```
그러나, 웹 앱이라면 모든 화면이 포함된 단일 HTML 파일을 사용하므로 이러한 기본 동작은 도와주지 못합니다. 이 맵을 수동으로 만들고 JavaScript로 출력되는 템플릿을 갱신해야 합니다.
### 작업
간단한 객체로 URL 경로와 템플릿 사이에서 [map](https://en.wikipedia.org/wiki/Associative_array)을 구현합니다. `app.js` 파일의 상단에 객체를 추가합니다.
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard' },
};
```
이제 `updateRoute` 함수를 약간 수정합니다. `templateId`를 인수로 직접 주는 대신, 먼저 현재 URL을 보고 찾은 다음, 맵을 사용하여 해당 템플릿 ID 값을 가져오려 합니다. [`window.location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Location/pathname)으로 URL에서 경로 섹션만 가져올 수 있습니다.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
여기에서 선언한 라우터를 해당 템플릿에 매핑했습니다. 브라우저에서 수동으로 URL을 변경하여 잘 작동하는지 볼 수 있습니다.
✅ URL에 알 수 없는 경로를 입력하면 어떤 일이 벌어지나요? 어떻게 해결할 수 있나요?
## 네비게이션 추가하기
앱의 다음 단계는 URL을 수동으로 안 바꾸고 페이지 사이를 이동할 수 있도록 추가하는 것입니다. 이는 두 가지를 의미합니다:
1. 현재 URL로 갱신하기
2. 새로운 URL를 기반으로 출력된 템플릿 갱신하기
두 번째 부분은 `updateRoute` 함수로 이미 처리했으므로, 현재 URL로 갱신하는 방법을 알아냅니다.
HTML 앵커 요소 [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)를 사용하여 다른 URL에 대한 하이퍼링크를 만들 수 있지만, 여기에서 사용하면 브라우저가 HTML을 다시 불러오게 됩니다.
대신 URL을 업데이트 할 수 있는 JavaScript와 더 구체적으로 [`history.pushState`](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)를 사용해야합니다. HTML을 다시 불러오지 않고 검색 기록에 새로운 항목을 만듭니다.
### 작업
앱에서 탐색할 때 사용할 수 있는 새로운 함수를 만들어 보겠습니다:
```js
function navigate(path) {
window.history.pushState({}, path, window.location.origin + path);
updateRoute();
}
```
이 메소드는 먼저 주어진 경로에 따라 현재 URL을 갱신한 다음에, 템플릿을 업데이트합니다. `window.location.origin` 속성은 URL 최상위를 반환하므로, 주어진 경로에서 전체 URL을 다시 구성할 수 있습니다.
이제 함수가 있으므로, 경로가 정의된 라우터와 일치하지 않는 경우에 발생할 문제를 해결할 수 있습니다. 일치하는 경로를 찾을 수 없는 경우에는 기존 경로 중 하나에 fallback로 추가하고자 `updateRoute` 함수를 수정합니다.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
if (!route) {
return navigate('/login');
}
...
```
만약 라우터를 찾지 못한다면, `login` 페이지로 리다이렉트됩니다.
HTML의 *Login**Logout* 버튼에 바인딩을 추가하여 내비게이션 시스템을 완성해봅니다.
```html
<button onclick="navigate('/dashboard')">Login</button>
...
<button onclick="navigate('/login')">Logout</button>
```
[`onclick`](https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onclick) 속성을 사용하여 `click` 이벤트를 JavaScript 코드로 바인딩합니다, 여기에서 `navigate()` 함수를 호출합니다.
이 버튼들을 클릭해보세요, 이제 앱의 여러 화면들을 이동할 수 있습니다.
`history.pushState` 메소드는 HTML5 표준의 일부이며 [모든 모던 브라우저](https://caniuse.com/?search=pushState)에서 구현됩니다. 옛날 브라우저의 웹 앱을 제작하는 경우, 이 API 대신 사용할 수 있는 트릭이 있습니다: 경로 앞에 [hash (`#`)](https://en.wikipedia.org/wiki/URI_fragment)를 사용한다면 일반 앵커 탐색처럼 동작하면서 페이지를 다시 안 불러오는 라우팅을 구현할 수 있습니다, 페이지 내에 내부 링크를 만드는 것이 목적입니다.
## 브라우저의 뒤로가기와 앞으로가기 버튼 제어하기
`history.pushState`를 사용하면 브라우저의 탐색 기록에 새로운 항목이 생성됩니다. 브라우저의 *back button*을 누르고 있으면 다음처럼 내용이 표시되는지 볼 수 있습니다:
![Screenshot of navigation history](./history.png)
뒤로가기 버튼을 몇 번 클릭하면, 현재 URL이 변경되며 히스토리가 갱신되지만 동일한 템플릿이 계속 출력되는 것을 볼 수 있습니다.
히스토리가 바뀔 때마다 `updateRoute()`를 호출해야 한다는 사실을 모르기 때문입니다. [`history.pushState` documentation](https://developer.mozilla.org/en-US/docs/Web/API/History/pushState)을 살펴보면, 상태가 바뀌는 지 확인할 수 있습니다 - 다른 URL로 이동했다고 의미합니다. - [`popstate`](https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event) 이벤트가 연결됩니다. 이 이슈를 해결하는 데 사용할 것입니다.
### 작업
브라우저 히스토리가 바뀔 때마다 출력된 템플릿을 갱신하도록 `updateRoute()`를 호출하는 새 함수를 붙입니다. `app.js` 파일 하단에서 작업합니다:
```js
window.onpopstate = () => updateRoute();
updateRoute();
```
> Note: 여기서는 간결함을 위해 [arrow function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)을 사용하여 `popstate` 이벤트 핸들러를 선언했지만, 일반적인 함수와 동일하게 작동합니다.
다음은 화살표 함수에 대한 복습 동영상입니다:
[![Arrow Functions](https://img.youtube.com/vi/OP6eEbOj2sc/0.jpg)](https://youtube.com/watch?v=OP6eEbOj2sc "Arrow Functions")
이제 브라우저의 뒤로가기와 앞으로가기 버튼을 사용해보세요, 그리고 이 순간마다 올바르게 갱신되어 출력되는 지에 대하여 확인합니다.
---
## 🚀 도전
이 앱의 크레딧을 보여주는 세 번째 페이지에 새로운 템플릿과 라우터를 추가합니다.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
라우팅은 웹 개발의 놀랍고 까다로운 부분 중 하나입니다, 특히 웹의 페이지 새로고침 동작에서 단일 페이지 애플리케이션 페이지 새로고침으로 이동함에 따라 더욱 더 그렇습니다. [how the Azure Static Web App service](https://docs.microsoft.com/en-us/azure/static-web-apps/routes)의 라우터 제어에 대해 약간 봅니다. 그 문서에 기술된 몇 가지 결정이 필요한 이유를 설명할 수 있나요?
## 과제
[Improve the routing](assignment.md)

@ -0,0 +1,290 @@
# 은행 앱 제작하기 파트 2: 로그인과 가입 폼 작성하기
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
모든 모던 웹 앱에서 대부분은, 자신의 개인 공간을 가질 계정을 만들 수 있습니다. 여러 사용자가 동시에 웹 앱에 접근할 수 있으므로, 각자 사용자의 개인 데이터를 별도로 저장하고 어느 정보를 보여줄 지에 대하여 선택하는 메커니즘이 필요합니다. 자체적으로 광범위한 주제이므로 [user identity securely](https://en.wikipedia.org/wiki/Authentication) 관리하는 방법은 다루지 않지만, 각자가 앱에서 하나 (이상)의 은행 계좌를 만들 수 있는지 확인합니다.
이 파트에서는 HTML 폼으로 웹 앱에 로그인과 가입을 추가합니다. 프로그래밍 방식으로 데이터를 서버 API에 보내는 방법과, 최종적으로 사용자 입력에 대한 기본 유효성 검사 규칙을 정의하는 방법에 대해 보겠습니다.
### 준비물
이 강의를 위해 웹 앱의 [HTML templates and routing](../1-template-route/README.md)을 완료해야합니다. 또한 [Node.js](https://nodejs.org)와 [run the server API](../api/README.md)를 로컬에 설치해야 계정을 만들 데이터를 보낼 수 있습니다.
터미널에서 다음 명령을 실행하여 서버가 잘 실행되고 있는지 테스트할 수 있습니다:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## 폼과 컨트롤
`<form>` 요소는 사용자가 대화형 컨트롤을 사용하여 데이터를 입력하고 제출할 수 있는 HTML 문서의 섹션을 캡슐화합니다. 폼 내에서 쓸 수 있는 모든 종류의 사용자 인터페이스(UI) 컨트롤이 있으며, 가장 일반적인 컨트롤은 `<input>``<button>` 요소입니다.
`<input>`에는 다양한 [types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)이 많이 있습니다, 예를 들어 사용자 이름으로 입력 가능한 필드를 만들려면 다음과 같이 사용할 수 있습니다:
```html
<input name="username" type="text">
```
`name` 속성은 컨트롤을 식별하는 데 사용되고 폼 데이터를 전송할 때 속성 이름으로 사용됩니다.
> UI를 작성할 때 쓸 수 있는 모든 네이티브 UI 요소에 대한 아이디어를 얻으려면 [`<input>` types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)과 [other form controls](https://developer.mozilla.org/en-US/docs/Learn/Forms/Other_form_controls)의 전체 목록을 찾아봅시다.
`<input>`은 닫는 태그를 맞추지 *않는* [empty element](https://developer.mozilla.org/en-US/docs/Glossary/Empty_element)입니다. 자동으로-닫는 `<input/>` 표기법을 사용할 수 있지만, 필수는 아닙니다.
폼 내의 `<button>` 요소는 약간 특별합니다. `type` 속성을 지정하지 않으면, 눌렀을 때 폼 데이터가 자동으로 서버에 제출됩니다. 가능한 `type` 값은 다음과 같습니다:
- `submit`: `<form>`내의 기본값이며, 버튼은 폼 제출 작업으로 연결합니다.
- `reset`: 버튼은 모든 폼 컨트롤을 초기 값으로 다시 설정합니다.
- `button`: 버튼을 눌렀을 때 기본 동작을 지정하지 않습니다. JavaScript를 사용하여 커스텀 작업을 할당할 수 있습니다.
### 작업
`login` 템플릿에 폼을 추가하는 것으로 시작하겠습니다. *username* 필드와 *Login* 버튼이 필요합니다.
```html
<template id="login">
<h1>Bank App</h1>
<section>
<h2>Login</h2>
<form id="loginForm">
<label for="user">Username</label>
<input name="user" type="text">
<button>Login</button>
</form>
</section>
</template>
```
자세히 살펴보면, 여기에 `<label>` 요소도 추가된 것을 알 수 있습니다. `<label>`은 username 필드와 같은, UI 컨트롤의 캡션을 추가하는 데 사용됩니다. 라벨은 폼의 가독성을 위해서 중요하지만, 추가적인 장점도 제공합니다:
- 라벨을 폼 컨트롤에 연결하면, (화면 판독기와 같은) 보조 기술을 사용하는 사용자가 받는 것으로 예상되는 데이터를 이해하는 데 도움이 됩니다.
- 라벨을 클릭하여 연결된 입력에 직접 맞출 수 있으므로, 터치-스크린 기반 장치에서 더 쉽게 접근할 수 있습니다.
> 웹에서의 [Accessibility](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/What_is_accessibility)은 종종 간과되는 매우 중요한 주제입니다. [HTML5 semantic elements](https://developer.mozilla.org/en-US/docs/Learn/Accessibility/HTML) 덕분에 이를 적절하게 사용한다면 접근성 콘텐츠로 만드는 것은 어렵지 않습니다. 일반적인 실수를 피하고 책임있는 개발자가 되기 위해 [accessibility에 대하여 읽을 수](https://developer.mozilla.org/en-US/docs/Web/Accessibility) 있습니다.
이제 이전 항목의 바로 아래에, 가입을 위한 두번째 폼을 추가합니다:
```html
<hr/>
<h2>Register</h2>
<form id="registerForm">
<label for="user">Username</label>
<input name="user" type="text">
<label for="currency">Currency</label>
<input name="currency" type="text" value="$">
<label for="description">Description</label>
<input name="description" type="text">
<label for="balance">Current balance</label>
<input name="balance" type="number" value="0">
<button>Register</button>
</form>
```
`value` 속성을 사용하여 주어진 입력에 대한 기본값을 정의할 수 있습니다.
`balance`에 대한 입력에는 `number` 타입이 존재 합니다. 다른 입력과 다르게 보이나요? 상호작용 해보세요.
✅ 키보드만 사용하여 폼을 탐색하고 상호 작용할 수 있나요? 어떻게 하나요?
## 서버에 데이터 제출하기
이제 기능 UI가 있으므로, 다음 단계는 데이터를 서버로 보내는 것입니다. 현재 코드를 사용하여 간단한 테스트를 해봅시다. *Login* 혹은 *Register* 버튼을 클릭하면 어떻게 되나요?
브라우저의 URL 섹션에서 변경된 것을 알고 있나요?
![Screenshot of the browser's URL change after clicking the Register button](./images/click-register.png)
`<form>`의 기본 작업은 [GET method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3)를 사용하여 현재 서버 URL에 폼을 제출하고, 폼 데이터를 URL에 직접 추가하는 것입니다. 이 방식에는 몇 가지 단점이 있습니다:
- 전송되는 데이터는 크기가 매우 제한적입니다 (2000 자)
- 데이터가 URL에 직접 보입니다 (비밀번호에 적절하지 않습니다)
- 파일 업로드는 작동하지 않습니다
그러므로 아무런 제한없이 하려면, HTTP 요청 본문에서 폼 데이터를 서버로 보내는 [POST method](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5)를 사용하게 변경할 수 있습니다.
> POST는 데이터를 보낼 때 가장 일반적인 방식이지만, [some specific scenarios](https://www.w3.org/2001/tag/doc/whenToUseGet.html)에서 검색 필드를 구현할 때는, GET 방법을 사용하는 것이 더 좋습니다.
### 작업
가입 폼에 `action``method` 속성을 추가합니다:
```html
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
```
이제 이름으로 새로운 계정을 가입합니다. *Register* 버튼을 클릭하면 다음과 같은 내용이 표시됩니다:
![](./images/form-post.png)
모든 것이 잘 되면, 서버에 생성된 계정 데이터가 포함되어 [JSON](https://www.json.org/json-en.html)으로 응답해야 합니다.
✅ 같은 이름으로 다시 가입해보세요. 무슨 일이 일어났나요?
## 페이지를 다시 불러오지 않고 데이터 제출하기
알다시피, 사용한 접근 방식에는 약간 이슈가 있습니다: 폼을 제출할 때, 앱에서 나가면서 브라우저가 서버 URL로 리디렉션됩니다. [Single-page application (SPA)](https://en.wikipedia.org/wiki/Single-page_application)을 만들고 있으므로, 웹 앱으로 모든 페이지를 다시 불러오지 않으려 합니다.
페이지를 강제로 다시 불러오지 않고 폼 데이터를 서버로 보내려면, JavaScript 코드를 사용해야 합니다. `<form>` 요소의 `action` 속성에 URL을 넣는 대신, `javascript:` 문자열이 앞에 붙은 JavaScript 코드를 사용하여 커스텀 작업을 할 수 있습니다. 이를 사용하면 이전에 끝낸 브라우저로 자동 수행한 일부 작업을 구현해야 합니다:
- 폼 데이터 검색하기
- 폼 데이터를 알맞은 포맷으로 변환하고 인코딩하기
- HTTP 요청을 생성하고 서버로 전송하기
### 작업
가입 폼 `action`을 다음으로 바꿉니다:
```html
<form id="registerForm" action="javascript:register()">
```
`app.js` 열어서 `register`라고 지어진 새로운 함수를 추가합니다:
```js
function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const data = Object.fromEntries(formData);
const jsonData = JSON.stringify(data);
}
```
여기서는 `getElementById()`를 사용하여 폼 요소를 검색하고, [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData) 헬퍼를 사용하여 키/값 쌍 집합으로 폼 컨트롤에서 값을 추출합니다. 그러고 [`Object.fromEntries()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries)를 사용하여 데이터를 일반 객체로 변환하여 최종적으로 웹에서 데이터를 교환할 때, 일반적으로 사용되는 포맷인 [JSON](https://www.json.org/json-en.html)으로 데이터를 직렬화합니다.
데이터는 이제 서버에 보낼 준비가 되었습니다. `createAccount`라고 지은 새로운 함수를 만듭니다:
```js
async function createAccount(account) {
try {
const response = await fetch('//localhost:5000/api/accounts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: account
});
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
```
이 함수는 어떤 일을 할까요? 먼저, 여기있는 `async` 키워드를 확인하세요. 이 함수는 [**asynchronously**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)하게 실행하는 코드가 포함되어 있다는 것을 의미합니다. `await` 키워드와 함께 사용하면, 비동기 코드가 실행될 때까지 기다릴 수 있습니다 - 여기에서 서버의 응답을 기다리는 것과 같습니다 - 계속하기 전에.
여기는 ``async/await` 사용 방식에 대한 간단한 영상입니다:
[![Async and Await for managing promises](https://img.youtube.com/vi/YwmlRkrxvkk/0.jpg)](https://youtube.com/watch?v=YwmlRkrxvkk "Async and Await for managing promises")
`fetch()` API를 사용하여 JSON 데이터를 서버로 보냅니다. 이 메소드는 2개의 파라미터가 사용됩니다:
- 서버의 URL이므로, 여기에 `//localhost:5000/api/accounts`를 다시 넣습니다.
- 요청의 설정입니다. 여기서 메소드를 `POST`로 설정하고 요청한 `body`를 줍니다. JSON 데이터를 서버로 보낼 때, `Content-Type` 헤더를 `application/json`으로 설정하여 서버가 인터프리터하는 방식을 알 수 있도록 합니다.
서버가 JSON으로 응답하므로, `await response.json()`을 사용하여 JSON 콘텐츠를 파싱하고 결과 객체를 반환할 수 있습니다. 이 메소드는 비동기이므로, 반환하기 전 여기에서 `await` 키워드를 사용하여 파싱하는 도중에도 오류가 발생하는지 확인합니다.
이제 `register` 함수에 코드를 추가하여 `createAccount()`를 호출합니다:
```js
const result = await createAccount(jsonData);
```
`await` 함수를 여기에서 사용하기 때문에, 가입 함수 전에 `async` 키워드를 추가해야 합니다:
```js
async function register() {
```
마지막으로, 결과를 보기 위해서 로그를 추가하겠습니다. 최종 함수은 다음과 같습니다:
```js
async function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const jsonData = JSON.stringify(Object.fromEntries(formData));
const result = await createAccount(jsonData);
if (result.error) {
return console.log('An error occured:', result.error);
}
console.log('Account created!', result);
}
```
조금 길지만 도착했습니다! [browser developer tools](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools)를 열고, 새 계정을 가입하면, 웹 페이지에 변경 사항이 표시되지 않으면서 콘솔에 작동을 확인할 메시지가 나타납니다.
![Screenshot showing log message in the browser console](./images/browser-console.png)
✅ 데이터가 안전하게 서버로 보내졌다고 생각하나요? 누군가 요청을 가져갈 수 있다면 어떤가요? 보안 데이터 통신에 대해 자세히 보려면 [HTTPS](https://en.wikipedia.org/wiki/HTTPS)를 읽어보세요.
## Data 검증하기
사용자 이름을 먼저 설정하지 않고 새 계정을 가입하려하면, 서버에서 상태 코드 [400 (Bad Request)](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).) 오류를 반환하는 것으로 볼 수 있습니다.
데이터를 서버로 보내기 전에 할 수 있다면, 유요한 요청을 보낼 수 있도록, 미리 [validate the form data](https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation)를 실습하는 것이 좋습니다. HTML5 포맷 컨트롤은 다양한 속성을 사용하여 빌트인 유효성 검사를 제공합니다:
- `required`: 필드를 채워야하며 안 채운다면 폼을 제출할 수 없습니다.
- `minlength``maxlength`: 텍스트 입력의 최소 및 최대 문자 수를 정의합니다.
- `min``max`: 숫자 필드의 최소값과 최대값을 정의합니다.
- `type`: `number`, `email`, `file` 또는 [other built-in types](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input)처럼, 예상되는 데이터의 종류를 정의합니다. 이 속성은 폼 컨트롤의 비주얼 렌더링을 바꿀 수도 있습니다.
- `pattern`: 입력된 데이터가 유효한지 테스트하기 위해 [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) 패턴을 정의할 수 있습니다.
> Tip: 유효하거나 `:valid``:invalid` CSS pseudo-classes를 사용하지 않는 여부에 따라 폼 컨트롤의 모양을 커스텀할 수 있습니다.
### 작업
유효한 새로운 계정을 만들 때에 username과 currency라는 2개의 필수 필드가 필요하며, 다른 필드는 옵션입니다. HTML에서 폼을 갱신하여 다음 사항을 반영합니다:
```html
<input name="user" type="text" required>
...
<input name="currency" type="text" value="$" required>
```
이 특정 서버을 구현하는 것은 필드의 최대 길이에 제한을 걸진 않지만, 항상 사용자 텍스트 항목에 대하여 적당한 제한을 두는 것이 좋습니다.
`maxlength` 속성을 이 텍스트 필드에 추가합니다:
```html
<input name="user" type="text" maxlength="20" required>
...
<input name="currency" type="text" value="$" maxlength="5" required>
...
<input name="description" type="text" maxlength="100">
```
이제 *Register* 버튼을 누르고 정의한 유효성 검사 규칙을 필드가 따르지 않는 경우에는, 다음과 같이 보입니다:
![Screenshot showing the validation error when trying to submit the form](./images/validation-error.png)
서버에 데이터를 보내기 *전에 하는* 유효성 검사를 **client-side** 유효성 검사라고 합니다. 그러나 데이터를 보내지 않고 모든 검사를 하는 것은 항상 가능하지 않습니다. 예를 들면, 서버에 요청을 보내지 않고 동일한 사용자 이름을 가진 계정이 이미 존재하는지 확인할 수 있는 방식은 없습니다. 서버에서 수행되는 추가적인 유효성 검사를 **server-side** 유효성 검사라고합니다.
일반적으로 모두 구현해야하며, 클라이언트-측 유효성 검사를 사용하면 사용자에게 즉시 피드백을 주고 사용자 경험도 향상되지만, 서버-측 유효성 검사도 바뀌는 사용자 데이터가 건전하고 안전한지 확인하려면 중요합니다.
---
## 🚀 도전
이미 사용자가 존재한다면 HTML 오류 메시지가 나옵니다.
다음은 살짝 스타일을 적용한 뒤에 최종 로그인 페이지를 보여주는 예시입니다:
![Screenshot of the login page after adding CSS styles](./images/result.png)
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 리뷰 & 자기주도 학습
개발자는 특히 유효성 검사 전략과 관련하여, 폼을 작성하는 노력에 대해 매우 창의적으로 생각했습니다. [CodePen](https://codepen.com)으로 다양한 폼 흐름에 대해 알아보세요; 흥미롭고 영감이 생기는 폼을 찾을 수 있나요?
## 과제
[Style your bank app](assignment.md)

@ -0,0 +1,326 @@
# 은행 앱 제작하기 파트 3: 데이터를 가져오고 사용하는 방식
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
모든 웹 애플리케이션의 핵심에는 *data*가 있습니다. 데이터는 다양한 폼을 가질 수 있지만, 기본적인 목적은 항상 사용자에게 정보를 보여준다는 것입니다. 웹 앱이 점점 더 상호 작용하고 복잡해지면서, 사용자가 정보에 접근하고 상호 작용하는 방식은 이제 웹 개발에서 중요한 부분입니다.
이 강의에서는, 서버에서 비동기로 데이터를 가져오고, 이 데이터로 HTML을 다시 불러오지 않으면서 웹 페이지에 보여주는 방법으로 살펴봅니다.
### 준비물
이 강의에서 웹 앱의 [Login and Registration Form](../2-forms/README.md) 부분을 작성하는 것이 필요합니다. 또한 계정 데이터를 가져오려면 [Node.js](https://nodejs.org)와 [run the server API](../api/README.md)를 로컬에 설치해야 합니다.
터미널에서 이 명령을 실행하여 서버가 실행되고 있는지 테스트할 수 있습니다:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## AJAX와 데이터 가져오기
기존 웹 사이트는 모든 HTML 페이지를 다시 불러오기 위해서 사용자가 링크를 클릭하거나 폼으로 데이터를 제출할 때 표시되는 콘텐츠를 갱신합니다. 새로운 데이터를 불러와야 할 때마다, 웹 서버는 브라우저에서 처리하는 새 HTML 페이지를 반환하여, 현재 사용자의 액션을 중단하고 다시 불러오는 동안 상호 작용을 제한합니다. 이 과정을 *Multi-Page Application* 혹은 *MPA*라고 합니다.
![Update workflow in a multi-page application](./images/mpa.png)
웹 애플리케이션이 더 복잡해지고 상호 작용하기 시작하면서, [AJAX (Asynchronous JavaScript and XML)](https://en.wikipedia.org/wiki/Ajax_(programming))이라는 새로운 기술이 나타났습니다. 이 기술을 쓰면 웹 앱은 HTML 페이지를 다시 불러오지 않고, JavaScript를 사용하여 비동기로 서버에서 데이터를 보내고 찾을 수 있으므로, 갱신 속도가 빨라지고 사용자 상호 작용이 부드러워집니다. 서버에서 새로운 데이터를 받으면, [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) API로 현재 HTML 페이지를 JavaScript로 갱신할 수도 있습니다. 시간이 지나면서, 이 방식은 이제 [*Single-Page Application* or *SPA*](https://en.wikipedia.org/wiki/Single-page_application)라는 것으로 발전했습니다.
![Update workflow in a single-page application](./images/spa.png)
AJAX가 처음 소개되었을 때, 데이터를 비동기로 가져올 유일한 API는 [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest)였습니다. 그러나 모던 브라우저는 이제 promises를 사용하고 JSON 데이터를 조작할 때 적당하며, 더 편리하고 강력한 [`Fetch` API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)도 구현합니다.
> 모든 모던 브라우저는 `Fetch API`를 지원하지만, 웹 애플리케이션이 레거시 혹은 옛날 브라우저에서 작동하도록 하려면 항상 [compatibility table on caniuse.com](https://caniuse.com/fetch)를 먼저 보는 것이 좋습니다.
### 작업
[이전 강의](../2-forms/README.md)에서는 계정을 만들기 위해 가입 폼을 구현했습니다. 이제 이미 있는 계정으로 로그인하는 코드를 추가하고, 데이터를 가져올 것 입니다. `app.js` 파일을 열고 새로운 `login` 함수를 추가합니다:
```js
async function login() {
const loginForm = document.getElementById('loginForm')
const user = loginForm.user.value;
}
```
여기서 `getElementById()`로 폼 요소를 찾는 것으로 시작한 다음, `loginForm.user.value`로 입력에서 username을 가져옵니다. 모든 폼 컨트롤은 폼의 속성에 있는 이름(HTML에서 `name`속성으로 설정)으로 제어할 수 있습니다.
가입을 위해서 작업했던 것과 비슷한 방식으로, 서버 요청하는 또 다른 함수를 만들지만, 이번에는 계정 데이터를 찾기 위한 것입니다:
```js
async function getAccount(user) {
try {
const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user));
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
```
비동기로 서버에 데이터를 요청하기 위해서 `fetch` API를 사용하지만, 이번에는 데이터만 쿼리하므로, 호출할 URL 이외 추가 파라미터는 필요하지 않습니다. 기본적으로, `fetch`는 여기에서 찾는 것처럼 [`GET`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET) 요청을 생성합니다.
`encodeURIComponent()`는 URL에 대한 특수 문자를 이스케이프하는 함수입니다. 이 함수를 호출하지 않고 URL에서 `user` 값을 직접 사용하면 어떤 이슈가 발생할 수 있나요?
이제 `getAccount`를 사용하여 `login` 함수를 갱신합니다:
```js
async function login() {
const loginForm = document.getElementById('loginForm')
const user = loginForm.user.value;
const data = await getAccount(user);
if (data.error) {
return console.log('loginError', data.error);
}
account = data;
navigate('/dashboard');
}
```
먼저, `getAccount`는 비동기 함수이므로 서버 결과를 기다리려면 `await` 키워드로 맞춰야 합니다. 모든 서버 요청처럼, 오류 케이스도 처리해야 합니다. 지금은 오류를 보여주는 로그 메시지만 추가하고, 이 레이어로 돌아옵니다.
그러고 나중에 대시보드 정보를 보여줄 수 있도록 데이터를 어딘가 저장해야 합니다. `account` 변수가 아직 없으므로, 파일 상단에 전역 변수를 생성합니다:
```js
let account = null;
```
사용자 데이터가 변수에 저장되면 이미 있는 `navigate()` 함수를 사용하여 *login* 페이지에서 *dashboard*로 이동할 수 있습니다.
마지막으로, HTML을 수정하여 로그인 폼을 제출할 때마다 `login` 함수를 호출해야 합니다:
```html
<form id="loginForm" action="javascript:login()">
```
새로운 계정을 가입하고 같은 계정으로 로그인을 시도하여 모두 잘 작동하는지 테스트합니다.
다음 부분으로 가기 전에, 함수 하단에 다음을 추가하여 `register` 함수를 완성할 수도 있습니다:
```js
account = result;
navigate('/dashboard');
```
✅ 기본적으로, 보고있는 웹 페이지에 *동일한 도메인와 포트*에서만 서버 API를 호출할 수 있다는 사실을 알고 있었나요? 이것은 브라우저에 의해 시행되는 보안 메커니즘입니다. 하지만, 웹 앱은 `localhost:3000`에서 실행되고 서버 API가 `localhost:5000`에서 실행됩니다. 왜 작동할까요? [Cross-Origin Resource Sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)라는 기술을 사용하면 서버가 응답에 특별한 헤더를 추가하여 특정 도메인에 대한 예외를 허용하므로, cross-origin HTTP 요청을 수행 할 수 있습니다.
## 데이터를 보여주기 위해 HTML 갱신하기
이제 사용자 데이터가 있으므로, 기존 HTML을 갱신해서 보여줘야 합니다. 예시로 `document.getElementById()`를 사용하여 DOM에서 요소를 검색하는 방법은 이미 있습니다. 바탕 요소가 있으면, 수정하거나 하위 요소를 추가하는 방식으로 사용할 수 있는 몇 가지 API가 있습니다:
- [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent) 속성을 사용하여 요소의 텍스트를 바꿀 수 있습니다. 이 값을 변경하면 모든 요소의 하위가(있는 경우) 제거되고 주어진 텍스트로 대체됩니다. 따라서, 빈 문자열 `''`을 할당하여 주어진 요소의 모든 하위를 제거하는 효율적인 방법일 수도 있습니다.
- [`document.createElement()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createElement)를 [`append()`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append)메소드와 함께 사용하면 하나 이상의 새로운 하위 요소를 만들고 붙일 수 있습니다.
✅ 요소의 [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML) 속성을 사용하여 HTML 내용을 바꿀 수 있지만, [cross-site scripting (XSS)](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) 공격에 취약하므로 피해야 합니다.
### 작업
대시보드 화면으로 이동하기 전에, *login* 페이지에서 할 일이 더 있습니다. 현재, 없는 사용자 이름으로 로그인하면, 콘솔에는 메시지가 보이지만 일반적인 사용자의 경우에는 하나도 안 바뀌므로 어떤 일이 나는지 알 수 없습니다.
필요한 경우에 오류 메시지를 볼 수 있는 로그인 폼에 placeholder 요소를 추가하겠습니다. 로그인 `<button>` 바로 전에 두는 것이 좋습니다:
```html
...
<div id="loginError"></div>
<button>Login</button>
...
```
`<div>` 요소는 비어 있으므로, 내용를 더 할 때까지 화면에 아무것도 나오지 않습니다. 또한 JavaScript로 쉽게 찾을 수 있도록 `id`를 제공합니다.
`app.js` 파일로 돌아오고 새로운 헬퍼 함수인 `updateElement`를 만듭니다:
```js
function updateElement(id, text) {
const element = document.getElementById(id);
element.textContent = text;
}
```
이는 매우 간편합니다, *id**text* 요소가 주어지는 순간에 일치하는 `id`로 DOM 요소의 텍스트 내용이 갱신됩니다. `login` 함수의 이전 오류 메시지 대신에 이 방법을 사용하겠습니다:
```js
if (data.error) {
return updateElement('loginError', data.error);
}
```
이제 유효하지 않은 계정으로 로그인 시도하면, 다음과 같이 보입니다:
![Screenshot showing the error message displayed during login](./images/login-error.png)
`register` 함수 오류와 동일한 동작을 하도록 구현합니다 (HTML을 갱신하는 것을 잊지 마세요).
## 대시보드로 정보 출력하기
방금 전 같은 기술을 사용하여 대시보드 페이지에서 계정 정보를 보여주는 작업도 합니다.
서버에서 받은 계정 객체는 다음과 같습니다:
```json
{
"user": "test",
"currency": "$",
"description": "Test account",
"balance": 75,
"transactions": [
{ "id": "1", "date": "2020-10-01", "object": "Pocket money", "amount": 50 },
{ "id": "2", "date": "2020-10-03", "object": "Book", "amount": -10 },
{ "id": "3", "date": "2020-10-04", "object": "Sandwich", "amount": -5 }
],
}
```
> Note: 더 쉽게 진행하기 위해서, 이미 데이터로 채워진 기존 `test` 계정을 사용할 수 있습니다.
### 작업
HTML의 "Balance" 섹션을 교체하고 placeholder 요소를 추가하는 것으로 시작합니다:
```html
<section>
Balance: <span id="balance"></span><span id="currency"></span>
</section>
```
또한 바로 밑에 새로운 섹션을 추가하여 계정 설명을 출력합니다:
```html
<section id="description" aria-label="Account description"></section>
```
✅ 이 섹션의 내용을 설명하는 텍스트 라벨이 없기 때문에, `aria-label` 속성을 사용하여 접근성 힌트를 줍니다. 모두 웹 앱에 접근할 수 있도록 [ARIA attributes](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)에 대해 더 알아보세요.
다음으로, `app.js`에 placeholder를 채우기 위해서 새로운 함수를 만듭니다:
```js
function updateDashboard() {
if (!account) {
return navigate('/login');
}
updateElement('description', account.description);
updateElement('balance', account.balance.toFixed(2));
updateElement('currency', account.currency);
}
```
먼저, 나아가기 전 필요한 계정 데이터가 있는지 확인합니다. 그러고 일찍 만들어 둔 `updateElement()` 함수로 HTML을 업데이트합니다.
> 잔액을 더 예쁘게 보이게 만드려면, [`toFixed(2)`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed) 방법으로 소수점 이하 2자리 값을 강제로 출력합니다.
이제 대시보드를 불러올 때마다 `updateDashboard()` 함수를 호출해야 합니다. 이미 [lesson 1 assignment](../1-template-route/assignment.md)를 완료했다면 간단해야 합니다. 그렇지 않다면 이미 구현된 내용으로 쓸 수 있습니다.
`updateRoute()` 함수 끝에 이 코드를 추가합니다:
```js
if (typeof route.init === 'function') {
route.init();
}
```
그리고 라우터 정의를 업데이트합니다:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
};
```
이 변경점으로, 대시보드 페이지가 나올 때마다 `updateDashboard()` 함수가 호출됩니다. 로그인하고나서, 계정 잔액, 통화와 설명을 볼 수 있습니다.
## HTML 템플릿으로 동적 테이블 row 만들기
[first lesson](../1-template-route/README.md)에서는 HTML 템플릿과 [`appendChild()`](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild) 메소드로 앱의 탐색을 구현했습니다. 템플릿은 더 작아 질 수 있고 페이지의 반복적인 부분을 동적으로 채우는 데 쓸 수 있습니다.
유사한 접근 방식을 사용하여 HTML 테이블에 트랜잭션 목록을 출력합니다.
### 작업
HTML `<body>`에서 새로운 템플릿을 추가합니다:
```html
<template id="transaction">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</template>
```
이 템플릿은 하나의 테이블 행을 나타내고 있으며, 앞으로 채워나갈 3 개의 열이 있습니다: *date*, *object* 그리고 트랜젝션의 *amount*.
그러고, 이 `id` 속성을 대시보드 템플릿 내 테이블의 `<tbody>` 요소에 추가하여 JavaScript로 쉽게 찾을 수 있게 작성합니다:
```html
<tbody id="transactions"></tbody>
```
HTML은 준비되었습니다, JavaScript 코드로 전환하고 새로운 함수 `createTransactionRow`를 만들겠습니다:
```js
function createTransactionRow(transaction) {
const template = document.getElementById('transaction');
const transactionRow = template.content.cloneNode(true);
const tr = transactionRow.querySelector('tr');
tr.children[0].textContent = transaction.date;
tr.children[1].textContent = transaction.object;
tr.children[2].textContent = transaction.amount.toFixed(2);
return transactionRow;
}
```
이 함수는 이름이 의미한대로 정확히 수행합니다: 이전에 만든 템플릿을 사용하면서, 새 테이블 행을 만들고 트랜잭션 데이터로 내용을 채웁니다. `updateDashboard()` 함수에서 이것으로 테이블을 채울 것입니다:
```js
const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows);
```
여기서는 새로운 DOM 프래그먼트를 만들 [`document.createDocumentFragment()`](https://developer.mozilla.org/en-US/docs/Web/API/Document/createDocumentFragment) 메소드로 최종적인 HTML 테이블에 붙입니다.
현재 `updateElement()` 함수가 텍스트 내용만 지원하므로 이 코드가 실행되기 전에 할 일이 하나 더 있습니다. 코드를 약간 바꿔 보겠습니다:
```js
function updateElement(id, textOrNode) {
const element = document.getElementById(id);
element.textContent = ''; // Removes all children
element.append(textOrNode);
}
```
[`append()`](https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append)] 메소드를 사용하면 상위 요소에 텍스트 혹은 [DOM Nodes](https://developer.mozilla.org/en-US/docs/Web/API/Node)를 붙일 수 있으므로, 모든 사용 케이스에 적당합니다.
`test` 계정을 사용하여 로그인을 해보면, 지금 대시보드에 거래 목록이 보입니다 🎉.
---
## 🚀 도전
대시보드 페이지를 실제 은행 앱처럼 보이도록 함께 작업해보세요. 이미 앱 스타일한 경우, [media queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries)를 사용하여 데스크톱과 모바일 장치 다 잘 작동하는 [responsive design](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Responsive/responsive_design_building_blocks)으로 만들어보세요.
여기는 꾸며진 대시보드 페이지의 예시입니다:
![Screenshot of an example result of the dashboard after styling](../images/screen2.png)
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 과제
[Refactor and comment your code](assignment.md)

@ -0,0 +1,281 @@
# 은행 앱 제작하기 파트 4: 상태 관리의 컨셉
## 강의 전 퀴즈
[Pre-lecture quiz](.github/pre-lecture-quiz.md)
### 소개
웹 애플리케이션이 커지면서, 모든 데이터 흐름을 추적하는 것은 어렵습니다. 어떤 코드가 데이터를 가져오고, 어떤 페이지가 데이터를 사용하고, 언제 어디서 갱신해야 하는지... 관리하기 어려운 복잡한 코드로 끝날 수 있습니다. 이는 앱의 여러 페이지가 서로 데이터를 공유하는 경우에 특히 더 그렇습니다, 예시로 사용자 데이터. *상태 관리*의 컨셉은 항상 모든 종류의 프로그램에 존재했지만, 웹 앱이 계속 복잡해지면서 이제는 개발하면서 고려해야 할 키 포인트가 되었습니다.
이 최종 부분에서는, 상태 관리하는 방법을 다시 생각해보며, 언제든 브라우저 새로고침을 지원하고, 사용자 세션에서 데이터를 유지하기 위해서 작성한 앱을 살펴 보겠습니다.
### 준비물
이 강의의 웹 앱 [data fetching](../3-data/README.md) 파트를 완료해둬야 합니다. [Node.js](https://nodejs.org)와 [run the server API](../api/README.md)를 로컬에 설치해야 계정 정보를 관리할 수 있습니다.
터미널에서 다음 명령을 수행하여 서버가 잘 실행되고 있는지 테스트할 수 있습니다:
```sh
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
```
---
## 상태 관리에 대하여 다시 생각하기
[이전 강의](../3-data/README.md)에서는, 현재 로그인한 사용자의 은행 데이터를 포함하는 전역 `account` 변수를 사용하여 앱에 기초 상태 개념을 도입했습니다. 그러나, 현재 구현에는 조금 취약점이 있습니다. 대시보드에서 페이지를 새로 고쳐보기 바랍니다. 무슨 일이 일어나고 있나요?
현재 코드에는 3가지 이슈가 있습니다:
- 브라우저를 새로 고치면 로그인 페이지로 돌아가기 때문에, 상태가 유지되지 않습니다.
- 상태를 바꾸는 여러 함수들이 있습니다. 앱이 커지면서, 변경점을 추적하기 어렵고 갱신한 것을 잊어버리기 쉽습니다.
- 상태가 정리되지 않았습니다, *로그아웃*을 클릭하면 로그인 페이지에 있어도 계정 데이터가 그대로 유지됩니다.
이런 이슈를 하나씩 해결하기 위해 코드를 갱신할 수는 있지만, 코드 중복이 더 많이 발생되고 앱이 더 복잡해져서 유지 관리가 어려워집니다. 또는 몇 분 동안 잠시 멈춰서 다시 기획할 수도 있습니다.
> 여기서 우리가 실제로 해결할 문제는 무엇인가요?
[State management](https://en.wikipedia.org/wiki/State_management)는 다음 2가지 특정한 문제를 해결하기 위해 좋은 접근 방식을 찾습니다:
- 이해하기 쉽게 앱의 데이터 흐름을 유지하는 방법은 무엇인가요?
- 상태 데이터를 사용자 인터페이스와 항상 동기화하는 방법은 있나요 (혹은 그 반대로)?
이런 문제를 해결한 후에는 다른 이슈가 이미 고쳐졌거나 더 쉽게 고칠 수 있습니다. 이러한 문제를 해결하기 위한 여러 가능한 방식들이 있지만, **데이터를 중앙 집중화하고 변경하는 방법**으로 구성된 공통 솔루션을 사용합니다. 데이터 흐름은 다음과 같습니다:
![Schema showing the data flows between the HTML, user actions and state](./images/data-flow.png)
> 데이터와 뷰 갱신을 자동으로 연결하는 부분은, [Reactive Programming](https://en.wikipedia.org/wiki/Reactive_programming)의 고급 컨셉과 연결되었으므로 여기서 다루지는 않습니다. 깊게 분석한다면 좋게 팔로우 업할 주제입니다.
✅ 상태 관리에 대한 접근 방식이 다른 수 많은 라이브러리가 있으며, [Redux](https://redux.js.org)는 인기있는 옵션입니다. 큰 웹 앱에서 마주할 수 있는 잠재적 이슈와 해결 방식을 알 수 있으므로 사용된 컨셉과 패턴을 살펴보세요.
### 작업
조금 리팩토링을 해보면서 시작해봅니다. `account` 선언을 바꿉니다:
```js
let account = null;
```
With:
```js
let state = {
account: null
};
```
이 아이디어는 모든 앱 데이터를 단일 상태 개체에서 *중앙에 모으는* 것 입니다. 현재 상태에서는 `account`만 가지고 있으므로 많이 변하지 않지만, 발전을 위한 길을 닦아둡니다.
또한 그것을 사용하여 함수를 갱신해야 합니다. `register()``login()` 함수에서, `account = ...``state.account = ...`로 바꿉니다.
`updateDashboard()` 함수 상단에, 이 줄을 추가합니다:
```js
const account = state.account;
```
이 리팩토링만으로는 많은 개선이 이루어지지 않지만, 아이디어는 다음 변경점의 토대를 마련해줍니다.
## 데이터 변경 추적하기
데이터로 저장할 `state` 객체를 두었으므로, 다음 단계는 갱신 작업을 중앙 집중화하는 것입니다. 목표는 모든 변경점과 발생 시점을 쉽게 ​​추적하는 것입니다.
`state` 객체가 변경되지 않으려면, [*immutable*](https://en.wikipedia.org/wiki/Immutable_object)한 것으로 간주하는 것이 좋습니다. 즉, 전혀 수정할 수 없다는 점을 의미합니다. 또한 변경하려는 경우에는 새로운 상태 객체를 만들어야 된다는 점을 의미합니다. 이렇게 하면, 잠재적으로 원하지 않는 [side effects](https://en.wikipedia.org/wiki/Side_effect_(computer_science))에 보호하도록 만들고, undo/redo를 구현하는 것 처럼 앱의 새로운 기능에 대한 가능성을 열어 디버깅을 더 쉽게 만듭니다. 예를 들자면, 상태에 대한 모든 변경점을 남기고 유지하여 버그의 원인을 파악할 수 있습니다.
JavaScript에서, [`Object.freeze()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)를 사용하여 변경할 수 없는 버전의 객체를 만들 수 있습니다. 변경 불가능한 객체를 바꾸려고 하면 예외가 발생합니다.
*shallow**deep* 불변 객체의 차이점을 알고 계시나요? [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze#What_is_shallow_freeze)에서 읽을 수 있습니다.
### 작업
새로운 `updateState()` 함수를 만듭니다:
```js
function updateState(property, newData) {
state = Object.freeze({
...state,
[property]: newData
});
}
```
이 함수에서는, 새로운 상태 객체를 만들고 [*spread (`...`) operator*](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax#Spread_in_object_literals)로 이전 상태의 데이터를 복사합니다. 그러고 할당을 위해 [bracket notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects#Objects_and_properties) `[property]`를 사용하여 상태 객체의 특정한 속성을 새로운 데이터로 다시 정의합니다. 최종적으로, 변경되는 것을 막기 위해 `Object.freeze()`를 사용하여 객체를 잠급니다. 지금 상태에는 `account` 속성만 저장되어 있지만, 이 접근 방식으로 상태에 필요한 순간마다 많은 속성들을 추가할 수 있습니다.
또한 초기 상태가 동결되도록 `state` 초기화 작업도 갱신합니다:
```js
let state = Object.freeze({
account: null
});
```
그런 다음, `state.account = result;` 할당을 이 것으로 대체하여 `register` 함수를 갱신합니다:
```js
updateState('account', result);
```
`login` 함수에서도 동일하게 진행하고, `state.account = data;`도 이 것으로 바꿉니다:
```js
updateState('account', data);
```
이제 사용자가 *Logout*을 클릭 할 때 계정 데이터가 지워지지 않는 이슈를 해결할 수 있습니다.
새로운 함수 `logout()`을 만듭니다:
```js
function logout() {
updateState('account', null);
navigate('/login');
}
```
`updateDashboard()` 에서, 리다이렉션하는 `return navigate('/login');``return logout()`으로 바꿉니다;
새로운 계정으로 가입을 시도하면, 로그아웃하고 다시 로그인하여 모두 잘 작동하는지 확인합니다.
> Tip: `updateState()` 하단에 `console.log(state)`를 추가하고 브라우저의 개발 도구에서 콘솔을 열면 모든 상태 변경점을 볼 수 있습니다.
## 상태 유지하기
대부분 웹 앱이 잘 작동하려면 데이터를 유지할 필요가 있습니다. 모든 중요한 데이터는 일반적으로 데이터베이스에 저장되고 우리 케이스에는 사용자 계정 데이터처럼, 서버 API를 통해 접근됩니다. 그러나 때로는, 더 좋은 사용자 경험이나 로딩 퍼포먼스를 개선하기 위해서, 브라우저에서 실행중인 클라이언트 앱에 일부 데이터를 유지하는 것도 흥미롭습니다.
브라우저에서 데이터를 유지하려면, 스스로에게 몇 중요한 질문을 해야합니다:
- *민감한 데이터인가요?* 사용자 암호와 같은, 민감한 데이터는 클라이언트에 저장하지 않아야 합니다.
- *데이터를 얼마나 오래 보관해야 하나요?* 현재 세션에서만 데이터에 접근하거나 계속 저장할 계획인가요?
달성하려는 목표에 따라, 웹 앱 안에서 정보를 저장하는 방법에는 여러 가지가 있습니다. 예를 들면, URL을 사용하여 검색 쿼리를 저장하고, 사용자끼리 공유할 수 있습니다. [authentication](https://en.wikipedia.org/wiki/Authentication) 정보처럼, 데이터를 서버와 공유해야하는 경우에도 [HTTP cookies](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies)를 사용할 수 있습니다.
다른 옵션으로는 데이터 저장을 위해 여러 브라우저 API 중 하나를 사용하는 것입니다. 그 중 2가지가 특히 흥미롭습니다:
- [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage): [Key/Value store](https://en.wikipedia.org/wiki/Key%E2%80%93value_database)는 다른 세션에서 현재 웹 사이트에 대한 특정 데이터를 유지할 수 있습니다. 저장된 데이터는 만료되지 않습니다.
- [`sessionStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage): 이는 세션이 끝날 때(브라우저가 닫힐 때)에 저장된 데이터가 지워진다는 점을 제외하면 `localStorage`와 동일하게 작동합니다.
이 두 API는 모두 [strings](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)만 저장할 수 있습니다. 복잡한 객체를 저장하려면, [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify)를 사용하여 [JSON](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON) 포맷으로 직렬화해야 합니다.
✅ 서버에서 동작하지 않는 웹 앱을 만드려면, [`IndexedDB` API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)로 클라이언트에 데이터베이스를 만들 수도 있습니다. 이는 고급 사용 케이스이거나, 사용하기 복잡한 많은 양의 데이터를 저장해야 할 때에 사용하도록 되어있습니다.
### 작업
*Logout* 버튼을 명시적으로 클릭할 때까지 로그인 상태가 유지되기를 원하므로, `localStorage`로 계정 데이터를 저장합니다. 먼저, 데이터를 저장하는 데 사용할 키를 정의하겠습니다.
```js
const storageKey = 'savedAccount';
```
그러고 `updateState()` 함수의 하단에 이 줄을 추가합니다:
```js
localStorage.setItem(storageKey, JSON.stringify(state.account));
```
이를 통해, 이전의 모든 상태를 갱신하는 작업이 가운데로 모임에 따라 사용자 계정 데이터가 유지되고 항상 최신-상태를 유지합니다. 이것으로 이전 모든 리팩터링 작업의 혜택을 받기 시작했습니다 🙂.
더 많은 초기화 코드를 가지게 될 예정이므로 새로운 `init` 함수를 만드는 것이 좋습니다, 여기에는 `app.js`의 하단에 이전 코드가 포함됩니다:
```js
function init() {
const savedAccount = localStorage.getItem(storageKey);
if (savedAccount) {
updateState('account', JSON.parse(savedAccount));
}
// Our previous initialization code
window.onpopstate = () => updateRoute();
updateRoute();
}
init();
```
여기에서 저장된 데이터를 검색하고, 그에 따라서 상태를 갱신합니다. 페이지를 갱신하다가 상태에 의존하는 코드가 있을 수 있으므로, 라우터를 갱신하기 *전에* 하는 것이 중요합니다.
이제 계정 데이터를 유지하고 있으므로, *대시보드* 페이지를 애플리케이션 기본 페이지로 만들 수도 있습니다. 데이터가 없다면, 대시보드는 언제나 *로그인* 페이지로 리다이렉팅합니다. `updateRoute ()`에서, `return navigate('/login');``return navigate('dashboard');`로 바꿉니다.
이제 앱에 로그인하고 페이지를 새로 고쳐보면, 대시보드에 남아있어야 합니다. 이 업데이트로 모든 초기 이슈를 처리했습니다...
## 데이터 새로 고치기
...그러나 새로운 것을 만들 수도 있습니다. 웁스!
`test` 계정을 사용하여 대시보드로 이동하면, 터미널에서 이 명령을 실행하여 새로운 트랜잭션을 만듭니다:
```sh
curl --request POST \
--header "Content-Type: application/json" \
--data "{ \"date\": \"2020-07-24\", \"object\": \"Bought book\", \"amount\": -20 }" \
http://localhost:5000/api/accounts/test/transactions
```
지금 브라우저에서 대시보드 페이지를 새로 고쳐봅니다. 어떤 일이 일어났나요? 새로운 트랜잭션이 보이나요?
상태는 `localStorage` 덕분에 무한으로 유지하지만, 앱에서 로그아웃하고 다시 로그인할 때까지 갱신하지 않는다는 점을 의미합니다!
해결할 수 있는 한 가지 전략은 대시보드를 불러올 때마다 계정 데이터를 다시 불러와서, 데이터가 오래되는 현상을 방지하는 것 입니다.
### 작업
새로운 함수 `updateAccountData`를 만듭니다:
```js
async function updateAccountData() {
const account = state.account;
if (!account) {
return logout();
}
const data = await getAccount(account.user);
if (data.error) {
return logout();
}
updateState('account', data);
}
```
이 메소드는 현재 로그인되어 있는지 본 다음에 서버에서 계정 데이터를 다시 불러옵니다.
`refresh`라고 이름을 지은 또 다른 함수를 만듭니다:
```js
async function refresh() {
await updateAccountData();
updateDashboard();
}
```
이는 계정 데이터를 갱신하고나서, 대시보드 페이지의 HTML도 갱신하게 됩니다. 대시보드 라우터를 불러올 때마다 호출해야 합니다. 다음으로 라우터 정의를 갱신합니다:
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: refresh }
};
```
지금 대시보드를 다시 불러옵니다, 갱신된 계정 데이터를 볼 수 있어야 합니다.
---
## 🚀 도전
이제 대시보드를 불러올 때마다 계정 데이터가 다시 불러와지는데, 여전히 *모든 계정* 데이터를 유지해야 된다고 생각하나요?
앱이 동작하는 데 꼭 필요한 것만 있도록 `localStorage` 에 저장하고 불러온 항목을 함께 바꿔봅니다.
## 강의 후 퀴즈
[Post-lecture quiz](.github/post-lecture-quiz.md)
## 과제
[Implement "Add transaction" dialog](assignment.md)
다음은 과제를 완료한 뒤의 예시 결과입니다:
![Screenshot showing an example "Add transaction" dialog](./images/dialog.png)

@ -0,0 +1,20 @@
# :dollar: 은행 만들기
이 프로젝트에서는, 가상의 은행을 만드는 방법을 배웁니다. 이 강의에는 웹 앱을 레이아웃과 라우트를 제공하고, 폼을 작성하며 상태를 관리하고, 은행 데이터 API에서 데이터를 가져오는 방법에 대한 지침이 포함되어 있습니다.
<img src="images/screen1.png" width="50%" height="auto"/><img src="images/screen2.png" width="50%" height="auto"/>
## 강의
1. [웹 앱에서 HTML 템플릿과 라우트](1-template-route/README.md)
2. [로그인과 가입 폼 제작](2-forms/README.md)
3. [데이터를 가져오고 사용하는 방식](3-data/README.md)
4. [상태 관리의 개념](4-state-management/README.md)
### 크레딧
These lessons were written with :hearts: by [Yohan Lasorsa](https://twitter.com/sinedied).
If you're interested to learn how to build the [server API](./api/README) used in these lessons, you can follow [this series of videos](https://aka.ms/NodeBeginner) (in particular videos 17 through 21).
You can also take a look at [this interactive Learn tutorial](https://aka.ms/learn/express-api).
Loading…
Cancel
Save