Svelte 할 일 목록 앱 시작 - Web 개발 학습하기

Svelte 할 일 목록 앱 시작
==================

* 이전
* Overview: Client-side JavaScript frameworks
* 다음

이제 Svelte에서 작동하는 방식에 대한 기본적인 이해가 있으므로 예제 앱인 할 일 목록을 구축할 수 있습니다. 이 글에서는 먼저 앱의 요구되는 기능을 살펴본 다음 `Todos.svelte` 컴포넌트를 만들고 정적 마크업과 스타일을 배치하고 이어지는 문서들에서 진행할 할 일 목록 앱의 기능 개발을 시작 할 수 있는 모든 준비를 마칩니다.

우리는 사용자가 작업을 탐색, 추가 및 삭제할 수 있고 작업을 완료로 표시할 수 있기를 바랍니다. 이것이 이 튜토리얼 시리즈에서 개발할 기본 기능이 될 것이며 그 과정에서 몇 가지 고급 개념도 살펴볼 것입니다.

|  |  |
| --- | --- |
| 전제사항: |
 적어도
 HTML,
 CSS, and
 JavaScript
 언어의 사용에 익숙하기를 권장합니다. 그리고
 터미널/커맨트 라인 (en-US)의 사용법에 대해서도 지식이 있어야합니다.

당신은 앱을 컴파일하고 빌드하기 위한 노드와 npm이 설치된 터미널이 필요합니다. |
| 목표: | 어떻게 Svelte 컴포넌트를 만드는 방법을 배우기 위해, 다른 컴포넌트 내부에 렌더링하고, props를 사용하여 데이터를 전달하고 state를 저장합니다. |

함께 코드를 작성해봅시다
-------------

### Git

Git 레포지토리를 복제하세요(당신이 이미 복제를 한게 아니라면).

```bash
git clone https://github.com/opensas/mdn-svelte-tutorial.git
```

그런 다음 현재 앱의 상태로 이동하기 위해 다음을 실행하세요.

```bash
cd mdn-svelte-tutorial/02-starting-our-todo-app
```

폴더의 내용을 직접 다운로드 할 수도 있습니다.

```bash
npx degit opensas/mdn-svelte-tutorial/02-starting-our-todo-app
```

`npm install && npm run dev` 명령어로 실행하여 개발 모드에서 앱을 실행해주세요.

### REPL

REPL을 사용하여 우리와 함께 코딩하려면 https://svelte.dev/repl/b7b831ea3a354d3789cefbc31e2ca495?version=3.23.2에서 시작하세요.

할 일 목록 앱의 기능들
-------------

할 일 목록 앱이 준비되면 다음과 같이 표시됩니다.

![일반적인 할 일 목록 앱, '해야 할 일'이라는 제목이 있고 더 많은 할일을 입력할 수 있으며 할 일이 체크 박스와 함께 배열됨](/ko/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning/01-todo-list-app.png)

이 UI를 사용하여 사용자는 다음을 수행할 수 있습니다.

* 작업 찾아보기
* 작업을 삭제하지 않고 완료/보류로 표시
* 작업 제거
* 새 작업 추가
* 상태별로 작업 필터링: 모든 작업, 활성 작업 또는 완료된 작업
* 작업 편집
* 모든 작업을 활성/완료로 표시
* 완료된 모든 작업 제거
첫 번째 컴포넌트 만들기
-------------

`Todos.svelte` 컴포넌트를 생성해 봅시다. 여기에는 할 일 목록이 포함됩니다.

1. 새 폴더 `src/components`를 만듭니다.

**참고:** `src` 폴더 안의 아무 곳에나 컴포넌트를 넣을 수 있습니다. 하지만 `components` 폴더는 따라야하는 규칙으로 인정되고 컴포넌트를 쉽게 찾을 수 있습니다.
2. 다음 내용을 포함하는 `src/components/Todos.svelte`라는 파일을 만드세요.

```html
<h1>Svelte 할 일 목록</h1>
```

3. `public/index.html`에서 `title` 요소를 변경하여 *Svelte 할 일 목록*라는 텍스트를 포함하도록 합니다.

```html
<title>Svelte 할 일 목록</title>
```

4. `src/App.svelte` 파일을 열고 다음의 내용으로 대체하세요.

```html
<script>
 import Todos from "./components/Todos.svelte";
</script>

<Todos />
```

5. 개발 모드에서 Svelte는 컴포넌트에 존재하지 않는 속성을 지정할 때 브라우저 콘솔에 경고를 표시합니다. 이 경우에는 `App` 내부에서는 사용되지 않지만 `src/main.js` 내부의 `App` 컴포넌트를 인스턴스화할 때 지정되는 `name` 속성이 있습니다. 콘솔은 현재 "<App>이 알 수 없는 속성 '이름'으로 생성되었습니다"라는 메시지를 표시해야 합니다. 이를 제거하려면 `src/main.js`에서 `name` 속성을 제거하세요. 이제 다음과 같이 표시됩니다.

```js
import App from "./App.svelte";

const app = new App({
  target: document.body,
});

export default app;
```

이제 테스트 서버 URL을 확인하면 `Todos.svelte` 컴포넌트가 렌더링되는 것을 볼 수 있습니다.

![basic component rendering which a title that says 'Svelte to-do list'](/ko/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning/02-todos-component-rendered.png)

정적 마크업 추가
---------

당장은 우리는 앱의 정적 마크업 표현으로 시작할 것이므로 앱이 어떻게 보이는지 확인할 수 있습니다. 다음을 복사하여 `Todos.svelte` 컴포넌트 파일에 붙여넣고 기존 콘텐츠를 대체합니다.

```html
<!-- Todos.svelte -->
<div class="todoapp stack-large">
  <!-- 새로운 할일 -->
  <form>
    <h2 class="label-wrapper">
      <label for="todo-0" class="label\_\_lg"> 무엇을 해야하나요? </label>
    </h2>
    <input type="text" id="todo-0" autocomplete="off" class="input input\_\_lg" />
    <button type="submit" disabled="" class="btn btn\_\_primary btn\_\_lg">
      추가
    </button>
  </form>

  <!-- 필터 -->
  <div class="filters btn-group stack-exception">
    <button class="btn toggle-btn" aria-pressed="true">
      <span class="visually-hidden">나타내기</span>
      <span>모두 보기</span>
      <span class="visually-hidden">작업</span>
    </button>
    <button class="btn toggle-btn" aria-pressed="false">
      <span class="visually-hidden">나타내기</span>
      <span>활성화</span>
      <span class="visually-hidden">작업</span>
    </button>
    <button class="btn toggle-btn" aria-pressed="false">
      <span class="visually-hidden">나타내기</span>
      <span>완료</span>
      <span class="visually-hidden">작업</span>
    </button>
  </div>

  <!-- 할 일 상태 -->
  <h2 id="list-heading">3개 항목 중 2개 완료</h2>

  <!-- 할 일들 -->
  <ul role="list" class="todo-list stack-large" aria-labelledby="list-heading">
    <!-- 할 일 1 (수정 모드) -->
    <li class="todo">
      <div class="stack-small">
        <form class="stack-small">
          <div class="form-group">
            <label for="todo-1" class="todo-label">
              'Svelte 시작 앱 만들기'의 새 이름
            </label>
            <input
 type="text"
 id="todo-1"
 autocomplete="off"
 class="todo-text" />
          </div>
          <div class="btn-group">
            <button class="btn todo-cancel" type="button">
              취소
              <span class="visually-hidden">Svelte 스타터 앱 만들기</span>
            </button>
            <button class="btn btn\_\_primary todo-edit" type="submit">
              저장
              <span class="visually-hidden"
 >Svelte 스타터 앱 만들기의 새 이름</span
 >
            </button>
          </div>
        </form>
      </div>
    </li>

    <!-- 할 일 2 -->
    <li class="todo">
      <div class="stack-small">
        <div class="c-cb">
          <input type="checkbox" id="todo-2" checked />
          <label for="todo-2" class="todo-label"> 첫 번째 컴포넌트 생성 </label>
        </div>
        <div class="btn-group">
          <button type="button" class="btn">
            수정
            <span class="visually-hidden">첫 번째 새로운 컴포넌트 생성</span>
          </button>
          <button type="button" class="btn btn\_\_danger">
            삭제
            <span class="visually-hidden">첫 번째 새로운 컴포넌트 생성</span>
          </button>
        </div>
      </div>
    </li>

    <!-- 할 일 3 -->
    <li class="todo">
      <div class="stack-small">
        <div class="c-cb">
          <input type="checkbox" id="todo-3" />
          <label for="todo-3" class="todo-label"> 나머지 튜토리얼 완료 </label>
        </div>
        <div class="btn-group">
          <button type="button" class="btn">
            수정
            <span class="visually-hidden">나머지 튜토리얼 완료</span>
          </button>
          <button type="button" class="btn btn\_\_danger">
            삭제
            <span class="visually-hidden">나머지 튜토리얼 완료</span>
          </button>
        </div>
      </div>
    </li>
  </ul>

  <hr />

  <!-- 다른 액션 -->
  <div class="btn-group">
    <button type="button" class="btn btn\_\_primary">모두 체크</button>
    <button type="button" class="btn btn\_\_primary">완전히 삭제</button>
  </div>
</div>
```

렌더링된 것을 다시 확인하면 다음과 같이 표시됩니다.

![A to-do list app, but unstyled, with a title of what needs to be done, inputs, checkboxes, etc.](/ko/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning/03-unstyled-todo-app.png)

위의 HTML 마크업은 스타일이 좋지 않으며 기능적으로도 쓸모가 없습니다. 그렇지만 마크업을 살펴보고 이것이 우리가 원하는 기능과 어떤 관련이 있는지 살펴보겠습니다.

* 새 작업을 입력하기 위한 레이블 및 텍스트 상자
* 작업 상태별로 필터링하는 세 개의 버튼
* 총 작업 수 및 완료된 작업을 표시하는 레이블
* 각 작업에 대한 항목을 포함하는 정렬되지 않은 목록
* 작업이 편집 중일 때, 각 항목에는 입력과 수정 사항을 취소하거나 저장하는 두 개의 버튼이 있습니다.
* 작업이 편집되지 않는 경우, 완료 상태를 설정하는 확인란과 작업을 편집하거나 삭제하는 두 개의 버튼이 있습니다.
* 마지막으로 모든 작업을 선택/선택 취소하고 완료된 작업을 제거하는 두 개의 버튼이 있습니다.

이어지는 문서에는 이러한 모든 기능이 작동하는 것 외에도 더 많은 기능을 얻을 수 있습니다.

### 할 일 목록의 접근성 관련 기능

여기에서 몇 가지 특이한 특성을 발견할 수 있습니다. 예를 들어

```html
<button class="btn toggle-btn" aria-pressed="true">
  <span class="visually-hidden">나타내기</span>
  <span>모두 보기</span>
  <span class="visually-hidden">작업</span>
</button>
```

여기서 'aria-pressed'는 버튼이 '누름' 또는 '누르지 않음'의 두 가지 상태 중 하나일 수 있음을 보조 기술(예: Screen Reader)에 알려줍니다. 켜짐과 꺼짐에 대한 아날로그로 생각하십시오. 값을 'true'로 설정하면 기본적으로 버튼을 누른 상태가 됩니다.

CSS를 포함하지 않았기 때문에 `visual-hidden` 클래스는 아직 효과가 없습니다. 하지만 스타일을 적용하면 이 클래스의 모든 요소는 화면을 볼 수 있는 사용자에게는 숨겨지고 Screen Reader 사용자는 계속 사용할 수 있습니다. 이는 볼 수 있는 사용자에게는 이러한 단어(클래스, 마크업)가 필요하지 않기 때문입니다. 추가 시각적 컨텍스트가 없는 Screen Reader 사용자를 위해 버튼이 수행하는 작업에 대한 자세한 정보를 제공하기 위해 존재합니다.

아래에서 다음 `<ul>` 요소를 찾을 수 있습니다.

```html
<ul
 role="list"
 className="todo-list stack-large"
 aria-labelledby="list-heading"></ul>
```

'role' 속성은 보조 기술이 요소가 어떤 의미론적 가치를 가지고 있는지 또는 그 목적이 무엇인지 설명하는 데 도움이 됩니다. `<ul>`은 기본적으로 목록처럼 취급되지만 추가하려는 스타일로 인해 해당 기능이 중단됩니다. 'role' 속성은 `<ul>` 요소에 대한 "목록" 의미를 복원합니다. 이것이 필요한 이유에 대해 자세히 알아보려면 Scott O'Hara의 기사 "Fixing Lists"를 참조하십시오.

`aria-labelledby` 속성은 보조 기술에 `id`가 `list-heading`인 `<h2>`를 아래 목록의 목적을 설명하는 레이블로 취급하고 있음을 알려줍니다. 이 연결을 만들면 목록에 더 많은 정보를 제공하여 Screen Reader 사용자가 목록의 목적을 더 잘 이해할 수 있습니다.

지금은 Svelte가 접근성을 다루는 방법에 대해 이야기하기에 좋은 시간인 것 같습니다. 지금 해봅시다.

Svelte 접근성 지원
-------------

Svelte는 접근성을 특별히 강조합니다. 그 의도는 개발자가 "기본적으로" 더 접근하기 쉬운 코드를 작성하도록 장려하는 것입니다. 컴파일러이기 때문에 Svelte는 HTML 템플릿을 정적으로 분석하여 컴포넌트가 컴파일될 때 접근성 경고를 제공할 수 있습니다.

접근성(a11y로 줄임)이 항상 올바른 것은 아니지만 접근할 수 없는 마크업을 작성하면 Svelte가 경고하여 도움을 줍니다.

예를 들어, 해당 `alt` 속성 없이 `<img>` 요소를 `todos.svelte` 컴포넌트에 추가하는 경우.

```html
<h1>스벨트 할 일 목록</h1>

<img height="32" width="88" src="https://www.w3.org/WAI/wcag2A" />
```

컴파일러는 다음 경고를 발생시킵니다.

```bash
(!) Plugin svelte: A11y: <img> element should have an alt attribute
src/components/Todos.svelte
1: <h1>Svelte To-Do list</h1>
2:
3: <img height="32" width="88" src="https://www.w3.org/WAI/wcag2A">
   ^
created public/build/bundle.js in 220ms

[2020-07-15 04:07:43] waiting for changes...
```

편집기는 컴파일러를 호출하기 전에도 이 경고를 표시할 수 있습니다.

![A code editor window showing an image tag, with a popup error message saying that the element should have an alt attribute](/ko/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning/04-svelte-accessibility-support.png)

다음과 같이 `svelte-ignore`로 시작하는 주석을 사용하여 다음 마크업 블록에 대해 이 경고를 무시하도록 Svelte에 지시할 수 있습니다.

```html
<!-- svelte-ignore a11y-missing-attribute -->
<img height="32" width="88" src="https://www.w3.org/WAI/wcag2A" />
```

**참고:** VSCode를 사용하면 *Quick fix…* 링크를 클릭하거나 `Ctrl` + `.`를 눌러 무시 주석을 자동으로 추가할 수 있습니다.

이 경고를 전체적으로 비활성화하려면 다음과 같이 `Svelte` 플러그인 구성 내부의 `rollup.config.js` 파일에 `onwarn` 핸들러를 추가할 수 있습니다.

```js
plugins: [
  svelte({
    dev: !production,
    css: (css) => {
      css.write("public/build/bundle.css");
    },
    // 경고는 일반적으로 Rollup으로 바로 전달됩니다.
    // 예를 들어 특정 코드로 경고를 억제하기 위해 여기에서 선택적으로 처리할 수 있습니다.
    onwarn: (warning, handler) => {
      // screen readers에 대해 관심이 없다면 -> 이것은 하지마세요!!!
      if (warning.code === "a11y-missing-attribute") {
        return;
      }

      // Rollup이 다른 모든 경고를 정상적으로 처리하도록 합니다.
      handler(warning);
    },
  }),

  // …
];
```

의도적으로 이러한 경고는 컴파일러 자체에서 구현되며 프로젝트에 추가하도록 선택할 수 있는 플러그인으로 구현되지 않습니다. 아이디어는 기본적으로 마크업에서 a11y 문제를 확인하고 특정 경고를 선택적으로 제외할 수 있도록 하는 것입니다.

**참고:** 예를 들어 빠른 프로토타입을 제작하는 동안과 같이 그럴 만한 이유가 있는 경우에만 이러한 경고를 비활성화해야 합니다. 훌륭한 웹 시민이 되어 최대한 광범위한 사용자 기반이 페이지에 액세스할 수 있도록 하는 것이 중요합니다.

Svelte가 확인하는 접근성 규칙은 JSX 요소에 대한 많은 접근성 규칙에 대한 정적 검사를 제공하는 ESLint용 플러그인eslint-plugin-jsx-a11y에서 가져옵니다. Svelte는 컴파일러에서 모두 구현하는 것을 목표로 하며 대부분은 이미 Svelte로 포팅되었습니다. GitHub에서 여전히 누락된 접근성 검사를 확인할 수 있습니다. 해당 링크를 클릭하여 각 규칙의 의미를 확인할 수 있습니다.

마크업 스타일링
--------

할 일 목록을 좀 더 보기 좋게 만들어 봅시다. `public/global.css` 파일의 내용을 다음으로 바꿉니다.

```css
/\* RESETS \*/
\*,
\*::before,
\*::after {
  box-sizing: border-box;
}
\*:focus {
  outline: 3px dashed #228bec;
  outline-offset: 0;
}
html {
  font: 62.5% / 1.15 sans-serif;
}
h1,
h2 {
  margin-bottom: 0;
}
ul {
  list-style: none;
  padding: 0;
}
button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: normal;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  appearance: none;
}
button::-moz-focus-inner {
  border: 0;
}
button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}
button,
input {
  overflow: visible;
}
input[type="text"] {
  border-radius: 0;
}
body {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  font:
    1.6rem/1.25 Arial,
    sans-serif;
  background-color: #f5f5f5;
  color: #4d4d4d;
}
@media screen and (min-width: 620px) {
  body {
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}
/\*END RESETS\*/

/\* GLOBAL STYLES \*/
.form-group > input[type="text"] {
  display: inline-block;
  margin-top: 0.4rem;
}
.btn {
  padding: 0.8rem 1rem 0.7rem;
  border: 0.2rem solid #4d4d4d;
  cursor: pointer;
  text-transform: capitalize;
}
.btn.toggle-btn {
  border-width: 1px;
  border-color: #d3d3d3;
}
.btn.toggle-btn[aria-pressed="true"] {
  text-decoration: underline;
  border-color: #4d4d4d;
}
.btn\_\_danger {
  color: #fff;
  background-color: #ca3c3c;
  border-color: #bd2130;
}
.btn\_\_filter {
  border-color: lightgrey;
}
.btn\_\_primary {
  color: #fff;
  background-color: #000;
}
.btn\_\_primary:disabled {
  color: darkgrey;
  background-color: #565656;
}
.btn-group {
  display: flex;
  justify-content: space-between;
}
.btn-group > \* {
  flex: 1 1 49%;
}
.btn-group > \* + \* {
  margin-left: 0.8rem;
}
.label-wrapper {
  margin: 0;
  flex: 0 0 100%;
  text-align: center;
}
.visually-hidden {
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  white-space: nowrap;
}
[class\*="stack"] > \* {
  margin-top: 0;
  margin-bottom: 0;
}
.stack-small > \* + \* {
  margin-top: 1.25rem;
}
.stack-large > \* + \* {
  margin-top: 2.5rem;
}
@media screen and (min-width: 550px) {
  .stack-small > \* + \* {
    margin-top: 1.4rem;
  }
  .stack-large > \* + \* {
    margin-top: 2.8rem;
  }
}
.stack-exception {
  margin-top: 1.2rem;
}
/\* END GLOBAL STYLES \*/

.todoapp {
  background: #fff;
  margin: 2rem 0 4rem 0;
  padding: 1rem;
  position: relative;
  box-shadow:
    0 2px 4px 0 rgba(0, 0, 0, 0.2),
    0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);
}
@media screen and (min-width: 550px) {
  .todoapp {
    padding: 4rem;
  }
}
.todoapp > \* {
  max-width: 50rem;
  margin-left: auto;
  margin-right: auto;
}
.todoapp > form {
  max-width: 100%;
}
.todoapp > h1 {
  display: block;
  max-width: 100%;
  text-align: center;
  margin: 0;
  margin-bottom: 1rem;
}
.label\_\_lg {
  line-height: 1.01567;
  font-weight: 300;
  padding: 0.8rem;
  margin-bottom: 1rem;
  text-align: center;
}
.input\_\_lg {
  padding: 2rem;
  border: 2px solid #000;
}
.input\_\_lg:focus {
  border-color: #4d4d4d;
  box-shadow: inset 0 0 0 2px;
}
[class\*="\_\_lg"] {
  display: inline-block;
  width: 100%;
  font-size: 1.9rem;
}
[class\*="\_\_lg"]:not(:last-child) {
  margin-bottom: 1rem;
}
@media screen and (min-width: 620px) {
  [class\*="\_\_lg"] {
    font-size: 2.4rem;
  }
}
.filters {
  width: 100%;
  margin: unset auto;
}
/\* Todo item styles \*/
.todo {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
.todo > \* {
  flex: 0 0 100%;
}
.todo-text {
  width: 100%;
  min-height: 4.4rem;
  padding: 0.4rem 0.8rem;
  border: 2px solid #565656;
}
.todo-text:focus {
  box-shadow: inset 0 0 0 2px;
}
/\* CHECKBOX STYLES \*/
.c-cb {
  box-sizing: border-box;
  font-family: Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-weight: 400;
  font-size: 1.6rem;
  line-height: 1.25;
  display: block;
  position: relative;
  min-height: 44px;
  padding-left: 40px;
  clear: left;
}
.c-cb > label::before,
.c-cb > input[type="checkbox"] {
  box-sizing: border-box;
  top: -2px;
  left: -2px;
  width: 44px;
  height: 44px;
}
.c-cb > input[type="checkbox"] {
  -webkit-font-smoothing: antialiased;
  cursor: pointer;
  position: absolute;
  z-index: 1;
  margin: 0;
  opacity: 0;
}
.c-cb > label {
  font-size: inherit;
  font-family: inherit;
  line-height: inherit;
  display: inline-block;
  margin-bottom: 0;
  padding: 8px 15px 5px;
  cursor: pointer;
  touch-action: manipulation;
}
.c-cb > label::before {
  content: "";
  position: absolute;
  border: 2px solid currentcolor;
  background: transparent;
}
.c-cb > input[type="checkbox"]:focus + label::before {
  border-width: 4px;
  outline: 3px dashed #228bec;
}
.c-cb > label::after {
  box-sizing: content-box;
  content: "";
  position: absolute;
  top: 11px;
  left: 9px;
  width: 18px;
  height: 7px;
  transform: rotate(-45deg);
  border: solid;
  border-width: 0 0 5px 5px;
  border-top-color: transparent;
  opacity: 0;
  background: transparent;
}
.c-cb > input[type="checkbox"]:checked + label::after {
  opacity: 1;
}
```

마크업 스타일을 지정하면 이제 모든 것이 더 좋아 보입니다.

![할 일 목록 앱 스타일 지정, '해야 할 일' 제목, 더 많은 할 일을 추가하기 위한 입력, 체크박스가 있는 할 일 목록](/ko/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Svelte_Todo_list_beginning/05-styled-todo-app.png)

지금까지의 코드
--------

### Git

이 문서의 끝에서 코드의 상태를 확인하려면 다음과 같이 저장소 사본에 접근하세요.

```bash
cd mdn-svelte-tutorial/03-adding-dynamic-behavior
```

또는 폴더의 콘텐츠를 직접 다운로드합니다.

```bash
npx degit opensas/mdn-svelte-tutorial/03-adding-dynamic-behavior
```

`npm install && npm run dev` 명령어로 실행하여 개발 모드에서 앱을 실행해주세요.

### REPL

REPL에서 코드의 현재 상태를 보려면 다음에 방문하십시오.

https://svelte.dev/repl/c862d964d48d473ca63ab91709a0a5a0?version=3.23.2

요약
--

마크업과 스타일 지정을 통해 할 일 목록 앱이 구체화되기 시작했으며 구현해야 하는 기능에 집중할 수 있도록 모든 준비가 완료되었습니다.

* 이전
* Overview: Client-side JavaScript frameworks
* 다음


Accessibility in React - Web 개발 학습하기

Accessibility in React
======================

* 이전
* Overview: Client-side JavaScript frameworks
* 다음

마지막 자습서 글에서는, 키보드만 사용하는 사용자와 스크린 리더 사용자 모두의 사용성을 개선하고 혼란을 줄일 수 있는 React의 포커스 관리를 포함하여, (말장난을 의도한) 접근성에 초점을 맞추어 보겠습니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: |
HTML,
 CSS, 및
 JavaScript 언어 핵심에 대한 친숙도,
 knowledge of the
 터미널/커멘드 라인 (en-US)에 대한 지식.
  |
| 목표: | React 키보드 접근성 구현에 대해 알아보겠습니다. |

키보드 사용자 포함하기
------------

이 시점에서, 우리는 구현하기로 한 모든 기능을 완료했습니다. 사용자는 새 할 일을 추가하고, 할 일을 선택 및 선택 취소하고, 할 일을 삭제하거나, 작업 이름을 편집할 수 있습니다. 또한, 모든 할 일, 활성화된 할 일 또는 완료된 할 일별로 할 일 목록을 필터링할 수 있습니다.

또는, 적어도, 마우스로 이러한 모든 작업을 수행할 수 있습니다. 안타깝게도, 이러한 기능은 키보드만 사용하는 사용자가 쉽게 접근할 수 없습니다. 지금부터 살펴보겠습니다.

키보드 사용성 문제 탐색
-------------

새 할 일을 추가하려는 것과 같이, 애플리케이션 상단의 입력을 클릭하여 시작합니다. 해당 입력 주위에 두꺼운 점선 윤곽선이 표시됩니다. 이 윤곽선은 브라우저가 현재 이 요소에 포커스를 맞추고 있다는 시각적 표시기입니다. `Tab` 키를 누르면, 입력 아래에 "추가" 버튼 주위에 윤곽선이 나타나는 것을 볼 수 있습니다. 이는 브라우저의 포커스가 이동되었음을 나타냅니다.

`Tab`을 몇 번 더 누르면, 이 점선 포커스 표시기가 각 필터 버튼 사이를 이동하는 것을 볼 수 있습니다. 포커스 표시기가 첫 번째 "편집" 버튼 주위에 올 때까지 계속 진행합니다. `Enter`키를 누르세요.

`<Todo />` 컴포넌트는 우리가 디자인한 대로, 템플릿을 전환하고, 할 일 이름을 편집할 수 있는 양식을 볼 수 있습니다.

그러나, 포커스 표시기는 어디로 갔나요?

`<Todo />` 컴포넌트에서 템플릿 사이를 전환할 때, 이전에 있던 요소를 완전히 제거하여 다른 요소로 교체합니다. 즉, 포커스를 맞추던 요소가 사라지고, 포커스가 전혀 맞지 않습니다. 이는 다양한 사용자, 특히 키보드에 의존하는 사용자나 스크린 리더를 사용하는 사용자에게 혼란을 줄 수 있습니다.

키보드 및 스크린 리더 사용자의 경험을 개선하려면, 브라우저의 포커스를 직접 관리해야 합니다.

템플릿 간의 포커싱
----------

사용자가 보기에서 편집으로 `<Todo />` 템플릿을 전환할 때, 이름을 바꾸는 데 사용되는 `<input />`에 포커스를 맞추어야 합니다. 그들이 편집에서 보기로 다시 전환할 때, 포커스를 다시 "편집" 버튼으로 옮겨야 합니다.

### 요소 타켓팅

DOM 요소에 포커스를 맞추려면, React에 포커스를 맞추고 싶은 요소와 찾는 방법을 알려줘야 합니다. React의 `useRef`훅은 `current`라는 단일 속성을 가지는 객체를 생성합니다. 이 속성은 우리가 원하는 모든 것에 대한 참조가 될 수 있으며, 해당 참조는 나중에 조회할 수 있습니다. 이 방법은 DOM 요소를 참조하는 데 특히 유용합니다.

`useRef`를 포함하도록 `Todo.js` 상단의 `import` 문을 변경합니다.

```jsx
import React, { useRef, useState } from "react";
```

그런 다음, `Todo()` 함수의 훅 아래에 두 개의 새로운 상수를 만듭니다. 각각은 참조여야 합니다. 하나는 보기 템플릿의 "편집" 버튼용이고, 다른 하나는 편집 템플릿의 편집 필드용입니다.

```jsx
const editFieldRef = useRef(null);
const editButtonRef = useRef(null);
```

이 ref는 각각의 요소에 연결할 때까지 값을 가지지 않기 때문에 기본값은 `null`입니다. 이를 위해, 각 요소에 `ref` 속성을 추가하고 해당 값을 적절한 이름의 `ref` 객체로 설정합니다.

편집 템플릿의 텍스트 상자 `<input>`은 다음과 같이 업데이트되어야 합니다.

```jsx
<input
 id={props.id}
 className="todo-text"
 type="text"
 value={newName}
 onChange={handleChange}
 ref={editFieldRef}
/>
```

보기 템플릿의 "편집" 버튼은 다음과 같아야 합니다.

```jsx
<button
 type="button"
 className="btn"
 onClick={() => setEditing(true)}
 ref={editButtonRef}>
 Edit <span className="visually-hidden">{props.name}</span>
</button>
```

### useEffect로 참조에 포커싱하기

ref를 의도한 목적으로 사용하려면, 다른 React 훅인 `useEffect()`를 가져와야 합니다. `useEffect()`는 React가 주어진 컴포넌트를 렌더링한 후에 실행되고, 렌더링 프로세스에 추가하고 싶은 모든 부수효과를 실행하기 때문에 이름이 붙여졌습니다. 이 부수효과는 기본 함수 본문 내에서는 실행할 수 없습니다. `useEffect()`는 `<Todo />` 컴포넌트가 렌더링되고 React가 refs가 어디에 있는지 알기 전까지는 요소에 포커싱할 수 없기 때문에 현재 상황에서 유용합니다.

`useEffect`를 추가하도록 `Todo.js`의 import 문을 다시 변경합니다.

```jsx
import React, { useEffect, useRef, useState } from "react";
```

`useEffect()`는 함수를 인수로 받아들입니다. 이 함수는 컴포넌트가 렌더링된 후에 실행됩니다. 이것이 실제로 작동하는 것을 봅시다. 다음 `useEffect()` 호출을 `Todo()` 함수 본문의 `return` 문 바로 위에 놓고, 콘솔에 "부수 효과"라는 단어를 기록하는 함수를 전달합니다.

```jsx
useEffect(() => {
  console.log("side effect");
});
```

기본 렌더링 프로세스와 `useEffect()` 내에서 실행되는 코드의 차이점을 설명하기 위해 다른 로그를 추가합니다. 이 로그를 이전 추가 항목 아래에 놓아주세요.

```jsx
console.log("main render");
```

이제, 브라우저에서 애플리케이션을 열어줍니다. 각 메시지가 세 번 반복되는, 두 메시지가 모두 콘솔에 표시되어야 합니다. "부수 효과" 로그가 코드에서 처음으로 표시되더라도, "주 렌더링"이 먼저 기록되고, "부수 효과"이 두 번째로 기록되는 방법에 유의하세요.
```

main render (3)                                     Todo.js:100
side effect (3)                                     Todo.js:98
```

지금은 실험을 위한 것입니다. 이제 `console.log("main render")`를 삭제하고, 포커스 관리 구현으로 넘어갑시다.

### 편집 필드에 포커싱하기

이제 `useEffect()` 훅이 작동하는 것을 알았으므로, 이를 사용하여 포커스를 관리할 수 있습니다. 참고로 편집 템플릿으로 전환할 때 편집 필드에 포커싱을 두어야 합니다.

다음과 같이 읽도록 기존 `useEffect()` 훅을 업데이트합니다.

```jsx
useEffect(() => {
  if (isEditing) {
    editFieldRef.current.focus();
  }
}, [isEditing]);
```

이러한 변경으로 인해 `isEditing`이 true인 경우, React는 `editFieldRef`의 현재 값을 읽고 브라우저 포커스를 해당 값으로 이동합니다. 또한 `useEffect()`에 배열을 두 번째 인수로 전달합니다. 이 배열은 `useEffect()`가 의존해야 하는 값의 목록입니다. 이 값을 포함하면 `useEffect()`는 해당 값 중 하나가 변경될 때만 실행됩니다. `isEditing` 값이 변경될 때만 포커스를 변경하려고 합니다.

지금 시도하면, "편집" 버튼을 클릭할 때 포커스가 해당 편집 `<edit>`으로 이동하는 것을 볼 수 있습니다!

### 포커스를 수정 버튼으로 다시 이동하기

언뜻 보기에, 편집 내용이 저장되거나 취소될 때 React가 포커스를 "편집" 버튼으로 다시 이동시키는 것은 믿을 수 없을 정도로 쉬워보이는 부분입니다. `isEditing`이 `false`인 경우 편집 버튼에 포커스를 맞추기 위해 `useEffect`에 조건을 추가할 수 있을까요? 지금 시도해 봅시다. `useEffect()` 호출을 다음과 같이 업데이트하세요.

```jsx
useEffect(() => {
  if (isEditing) {
    editFieldRef.current.focus();
  } else {
    editButtonRef.current.focus();
  }
}, [isEditing]);
```

대부분 다음과 같이 작동합니다. 브라우저로 돌아가면 편집을 시작하고 끝낼 때 포커스가 Edit `<input>`과 "Edit" 버튼 사이를 이동하는 것을 볼 수 있습니다. 그러나, 새로운 문제를 발견했을 수도 있습니다. 마지막 `<Todo />` 컴포넌트의 "편집" 버튼은 애플리케이션과 상호 작용하기도 전에 페이지 로드에 즉시 포커스가 맞추어집니다!

우리의 `useEffect()` 훅은 우리가 설계한 대로 정확히 작동합니다. 컴포넌트가 렌더링되자마자 실행되고, `isEditing`이 `false`임을 확인하고, "Edit" 버튼에 포커스를 맞춥니다. `<Todo />`의 세 가지 인스턴스가 있기 때문에, 마지막 "편집" 버튼에 포커스가 있는 것을 볼 수 있습니다.

`isEditing`이 한 값에서 다른 값으로 변경될 때만 포커스가 변경되도록 접근 방식을 리팩터링해야 합니다.

더 강력해진 포커스 관리
-------------

세분화된 기준을 충족하려면, `isEditing`의 값뿐만 아니라, 그 값이 변경된 시점도 알아야 합니다. 그러기 위해서는, `isEditing` 상수의 이전 값을 읽을 수 있어야 합니다. 의사 코드를 사용하면, 논리가 다음과 같아야 합니다.

```jsx
if (wasNotEditingBefore && isEditingNow) {
  focusOnEditField();
}
if (wasEditingBefore && isNotEditingNow) {
  focusOnEditButton();
}
```

React 팀은 컴포넌트의 이전 상태를 가져오는 방법에 대해 논의했으며, 작업에 사용할 수 있는 예제 사용자 지정 훅을 제공했습니다.

`Todo()` 함수 위, `Todo.js` 상단 근처에 다음 코드를 붙여넣습니다.

```jsx
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}
```

이제 `Todo()` 상단의 훅 아래에 `wasEditing` 상수를 정의합니다. 이 상수가 `isEditing`의 이전 값을 추적하기를 원하므로, `isEditing`을 인수로 사용하여 `usePrevious`를 호출합니다.

```jsx
const wasEditing = usePrevious(isEditing);
```

이 상수를 사용하여, `useEffect()` 훅을 업데이트하여 이전에 논의한 의사 코드를 구현할 수 있습니다. 다음과 같이 업데이트하세요.

```jsx
useEffect(() => {
  if (!wasEditing && isEditing) {
    editFieldRef.current.focus();
  }
  if (wasEditing && !isEditing) {
    editButtonRef.current.focus();
  }
}, [wasEditing, isEditing]);
```

`useEffect()`의 논리는 이제 `wasEditing`에 의존하므로, 종속성 배열에 제공합니다.

다시 "편집" 및 "취소" 버튼을 사용하여 `<Todo />` 컴포넌트의 템플릿 사이를 전환하세요. 이 섹션의 시작 부분에서 논의한 문제 없이, 브라우저 포커스 표시기가 적절하게 움직이는 것을 볼 수 있습니다.

사용자가 할 일을 삭제할 때 포커싱하기
---------------------

마지막 키보드 경험의 차이가 있습니다. 사용자가 목록에서 할 일을 삭제하면, 포커스가 사라집니다. 우리는 이전 변경 사항과 유사한 패턴을 따를 것입니다. 새로운 ref를 만들고, `usePrevious()` 훅을 활용하여, 사용자가 할 일을 삭제할 때마다 목록 제목에 집중할 수 있습니다.

### 왜 목록 제목인가요?

때로는, 포커스를 보내고 싶은 곳이 분명합니다. `<Todo />` 템플릿을 토글할 때, "돌아가기" 위한 원점인 "편집" 버튼이 있었습니다. 그러나 이 경우, DOM에서 요소를 완전히 제거하므로 돌아갈 곳이 없습니다. 그 다음으로 좋은 점은 근처 어딘가에 직관적인 위치가 있다는 것입니다. 목록 제목은 사용자가 삭제할 목록 항목에 가깝고, 그 부분에 포커스를 맞추면 사용자에게 남은 작업 수를 알려주기 때문에, 최선의 선택입니다.

### ref 만들기

`useRef()` 및 `useEffect()` 훅을 `App.js`로 가져옵니다. 아래에서 둘 다 필요합니다.

```jsx
import React, { useState, useRef, useEffect } from "react";
```

그런 다음 `App()` 함수 내에서 새 ref를 선언합니다. `return` 문 바로 위에 좋은 위치가 있습니다.

```jsx
const listHeadingRef = useRef(null);
```

### 제목 준비

`<h2>`와 같은 제목 요소는 일반적으로 포커스를 맞출 수 없습니다. 이는 문제가 되지 않는데, `tabindex="-1"` 속성을 추가하여 프로그래밍 방식으로 모든 요소를 포커스 가능하게 만들 수 있습니다. 이는 JavaScript로만 포커스 가능함을 의미합니다. `<button>` 또는 `<a>` 요소로 할 수 있는 것과 같은 방식으로 탭 인덱스가 `-1`인 요소에 포커스를 맞추기 위해 `Tab`을 누를 수 없습니다. (`tabindex="0"`을 사용해서 해결할 수 있지만, 이 경우에는 적절하지 않습니다.)

JSX에서 `tabIndex`로 작성된 `tabindex` 속성을 `headingRef`와 함께 할 일 목록 위 제목에 추가해 보겠습니다.

```jsx
<h2 id="list-heading" tabIndex="-1" ref={listHeadingRef}>
 {headingText}
</h2>
```

**참고:** `tabindex` 속성은 접근성 한계 사례에 적합하지만, 남용하지 않도록 **매우 주의**해야 합니다. 포커스 가능하게 만드는 것이 어떤 식으로든 사용자에게 도움이 될 것이라고 절대적으로 확신하는 경우에만 요소에 `tabindex`를 적용하세요. 대부분의 경우, 버튼, anchor태그 및 입력(input)과 같이 자연스럽게 포커스를 받을 수 있는 요소를 활용해야 합니다. `tabindex`의 무책임한 사용은 키보드 및 스크린 리더 사용자에게 심각한 부정적인 영향을 미칠 수 있습니다.

### 이전 상태를 가져오기

사용자가 목록에서 할 일을 삭제할 때만 ref(`ref` 속성을 통해)와 연결된 요소에 집중하려고 합니다. 이를 위해서는 이전에 이미 사용했던 `usePrevious()` 훅이 필요합니다. import 문들 바로 아래에 있는 `App.js` 파일의 맨 위에 추가하세요.

```jsx
function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}
```

이제 `App()` 함수 내부의 `return` 문 위에 다음을 추가합니다.

```jsx
const prevTaskLength = usePrevious(tasks.length);
```

여기서 우리는 `usePrevious()`를 호출하여 다음과 같이 할 일 상태의 길이를 추적합니다.

**참고:** 이제 두 개의 파일에서 `usePrevious()`를 활용하고 있으므로, 효율성이 좋은 리팩터는 `usePrevious()` 함수를 자체 파일로 이동하고, 해당 파일에서 내보내고, 당신이 이 사용자 지정 훅을 필요로 하는 곳에 가져오는 것입니다. 이 작업을 마치면 연습으로 해보세요

### `useEffect()`를 사용하여 제목 포커스 제어

이전에 얼마나 많은 할 일이 있었는지 저장했으므로, 이제 할 일의 수가 변경될 때 실행되도록 `useEffect()` 훅을 설정할 수 있습니다. 이 훅은 현재 가지고 있는 할 일의 수가 이전보다 적은 경우(즉, 작업을 삭제한 경우) 제목에 포커스를 설정합니다!

이전에 추가한 항목 바로 아래, `App()` 함수 본문에 다음을 추가합니다.

```jsx
useEffect(() => {
  if (tasks.length - prevTaskLength === -1) {
    listHeadingRef.current.focus();
  }
}, [tasks.length, prevTaskLength]);
```

이전보다 지금 할 일이 적은 경우에만 목록 제목에 포커싱을 두려고 합니다. 이 훅에 전달된 종속성은 해당 값(현재 할 일의 개수 또는 이전 할 일의 개수) 중 하나가 변경될 때만 다시 실행을 시도하도록 합니다.

이제, 브라우저에서 할 일을 삭제하면, 목록 위의 머리글 주위에 점선 포커스 윤곽선이 표시되는 것을 볼 수 있습니다.

완성
--

처음부터 React 애플리케이션 구축을 완료하였습니다! 축하합니다! 여기에서 배운 기술은 React를 계속 사용하면서 구축할 수 있는 훌륭한 기반이 될 것입니다.

대부분의 경우, 컴포넌트와 컴포넌트 상태 및 props에 대해 신중하게 생각하는 것만으로도 React 프로젝트에 효과적으로 기여할 수 있습니다. 항상 최고의 HTML을 작성해야 한다는 점을 기억하세요.

`useRef()` 및 `useEffect()`는 다소 고급 기능이며, 이러한 기능을 사용하는 것을 자랑스럽게 생각하세요! 더 많이 연습할 수 있는 기회를 찾으세요, 그렇게 하면 사용자를 위한 포괄적인 경험을 만들 수 있기 때문입니다. 기억하세요, 우리 애플리케이션은 키보드 없이는 키보드 사용자가 접근할 수 없었을 것입니다!

**참고:** 우리 버전과 비교하여 코드를 확인해야 하는 경우, todo-react repository에서 샘플 React 애플리케이션 코드의 완성된 버전을 찾을 수 있습니다. 실행 중인 라이브 버전은, https://mdn.github.io/todo-react을 참고하세요.

마지막 기사에서는 학습을 더 진행하는 데 사용할 수 있는 React 리소스 목록을 제공합니다.

* 이전
* Overview: Client-side JavaScript frameworks
* 다음


클라이언트 측 프레임워크 소개 - Web 개발 학습하기

클라이언트 측 프레임워크 소개
================

* Overview: Client-side JavaScript frameworks
* 다음

JavaScript와 프레임워크의 간략한 역사, 프레임워크가 존재하는 이유와 프레임워크가 우리에게 제공하는 것, 배울 프레임워크를 선택하는 방법에 대해 생각하기 시작하는 방법, 클라이언트 측 프레임워크에 대한 대안이 무엇인지 살펴보는 영역에 대한 일반적인 개요로 프레임워크를 살펴보기 시작합니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: | HTML,
 CSS,
 JavaScript 핵심에 대한 친숙도.
  |
| 목적: | 클라이언트 측 JavaScript 프레임워크가 어떻게 존재하게 되었는지, 어떤 문제를 해결하는지, 어떤 대안이 있는지, 그리고 하나를 선택하는 방법을 이해합니다. |

간략한 역사
------

JavaScript가 1969년에 등장했을 때, 그때까지 정적 문서로 구성된 웹에 간헐적인 상호 작용과 흥분을 더했습니다. 웹은 단순히 읽는 것이 아니라, 하는 것을 하는 곳이 되었습니다. JavaScript의 인기는 꾸준히 증가했습니다. JavaScript로 작업한 개발자는 직면한 문제를 해결하기 위한 도구를 작성하고 **라이브러리**라는 재사용 가능한 패키지로 패키징하여 솔루션을 다른 사람과 공유할 수 있습니다. 이 공유된 라이브러리 생태계는 웹의 성장을 형성하는 데 도움이 되었습니다.

이제 JavaScript는 모든 웹사이트의 98%에서 사용되는 웹의 필수적인 부분이며, 웹은 현대 생활의 필수적인 부분입니다. 사용자는 종이를 쓰고, 예산을 관리하고, 음악을 스트리밍하고, 영화를 보고, 원거리에 있는 다른 사람들과 텍스트, 오디오 또는 비디오 채팅을 통해 즉시 통신합니다. 웹을 사용하면 컴퓨터에 설치된 기본 애플리케이션에서만 가능했던 작업을 수행할 수 있습니다. 이러한 현대적이고 복잡한 대화형 웹사이트를 종종 **웹 애플리케이션**이라고 합니다.

최신 JavaScript 프레임워크의 출현으로 매우 동적인 대화형 애플리케이션을 훨씬 쉽게 구축할 수 있게 되었습니다. **프레임워크**는 소프트웨어 구축 방법에 대한 의견을 제공하는 라이브러리입니다. 이러한 의견은 응용 프로그램에서 예측 가능성과 동질성을 허용합니다. 예측 가능성을 통해 소프트웨어를 엄청난 크기로 확장하고 유지 관리할 수 있습니다. 예측 가능성과 유지 관리 가능성은 소프트웨어의 건강과 수명에 필수적입니다.

JavaScript 프레임워크는 매일 사용하는 많은 웹사이트를 포함하여 최신 웹에서 인상적인 소프트웨어의 대부분을 지원합니다. 현재 이 글을 읽고 있는 MDN 웹 문서는 React/ReactDOM 프레임워크를 사용하여 프런트엔드에 전원을 공급합니다.

어떤 프레임워크가 있습니까?
---------------

많은 프레임워크가 있지만, 현재 "빅 4"는 다음과 같은 것으로 간주됩니다.

### Ember

Ember는 SproutCore 프로젝트에서 시작된 작업의 연속으로 2011년 12월에 처음 출시되었습니다. React 및 Vue와 같은 최신 대안보다 사용자가 적은 오래된 프레임워크이지만 안정성, 커뮤니티 지원 및 일부 영리한 코딩 원칙으로 인해 여전히 상당한 인기를 누리고 있습니다.

Ember 학습 시작 (en-US)

### Angular

Angular는 Google의 Angular 팀과 개인 및 기업 커뮤니티가 주도하는 오픈 소스 웹 애플리케이션 프레임워크입니다. AngularJS를 빌드한 동일한 팀에서 완전히 다시 작성했습니다. Angular는 2016년 9월 14일 공식적으로 출시되었습니다.

Angular는 선언적 HTML 템플릿을 사용하는 컴포넌트 기반 프레임워크입니다. 빌드 시 개발자에게 투명하게 프레임워크의 컴파일러가 템플릿을 최적화된 JavaScript 명령으로 변환합니다. Angular는 다음 장에서 좀 더 자세히 살펴볼 JavaScript의 상위 집합인 TypeScript를 사용합니다.

Angular 학습 시작 (en-US)

### Vue

원래 AngularJS 프로젝트에서 작업하고 배운 후, Evan You는 2014년에 Vue를 출시했습니다. Vue는 빅 4중 막내이지만, 최근 인기가 높아지고 있습니다.

Vue는 AngularJS와 마찬가지로 일부 자체 코드로 HTML을 확장합니다. 그 외에도 주로 최신 표준 JavaScript에 의존합니다.

Vue 학습 시작

### React

Facebook은 2013년에 React를 출시했습니다. 이 시점에서 Facebook은 이미 내부적으로 많은 문제를 해결하기 위해 React를 사용하고 있었습니다. 기술적으로 React 자체는 프레임워크가 아닙니다. UI 컴포넌트를 렌더링하기 위한 라이브러리입니다. React는 애플리케이션을 만들기 위해 다른 라이브러리와 함께 사용됩니다. React 및 React Native는 개발자가 모바일 애플리케이션을 만들 수 있도록 합니다. React 및 ReactDOM을 사용하면 웹 애플리케이션 등을 만들 수 있습니다.

React와 ReactDOM은 함께 사용되는 경우가 많기 때문에, React는 일반적으로 JavaScript 프레임워크로 이해됩니다. 이 모듈을 읽으면서 우리는 구어체로 이해하는 작업을 할 것입니다.

React는 JSX로 알려진 HTML과 유사한 구문으로 JavaScript를 확장합니다.

React 학습 시작

프레임워크가 존재하는 이유는 무엇입니까?
----------------------

우리의 프레임워크 생성에 영감을 준 환경에 대해 논의했지만 실제로 왜 개발자가 프레임워크를 만들어야 할 필요성을 느꼈는지에 대해서는 언급하지 않았습니다. 이유를 탐색하려면 먼저 소프트웨어 개발의 과제를 검토해야 합니다.

다음 장에서 다양한 프레임워크를 사용하여 구현하는 방법을 살펴볼 할 일 목록 생성기와 같은 일반적인 종류의 애플리케이션을 고려하십시오. 이 애플리케이션은 사용자가 작업 목록을 렌더링하고, 새 작업을 추가하고, 작업을 삭제하는 것과 같은 작업을 수행할 수 있도록 해야 합니다. 애플리케이션의 기본 데이터를 안정적으로 추적하고 업데이트하면서 이를 수행해야 합니다. 소프트웨어 개발에서는 이 기본 데이터를 상태라고 합니다.

각각의 목표는 이론적으로 간단합니다. 데이터를 반복하여 렌더링할 수 있습니다. 새 작업을 만들기 위해 객체에 추가할 수 있습니다. 식별자를 사용하여 작업을 찾거나 편집하거나 삭제할 수 있습니다. 응용 프로그램이 사용자가 브라우저를 통해 이러한 모든 작업을 수행할 수 있도록 해야 한다는 점을 기억하면, 일부 균열이 나타나기 시작합니다. **진짜 문제는 애플리케이션의 상태를 변경할 때마다 일치하도록 UI를 업데이트해야 한다는 것입니다.**

할 일 목록 앱의 하나 기능인 작업 목록 렌더링을 살펴봄으로써 이 문제의 어려움을 검토할 수 있습니다.

DOM 변경에 대한 상세 설명
----------------

HTML 요소를 빌드하고 적절한 시간에 브라우저에서 렌더링하려면, 엄청난 양의 코드가 필요합니다. 상태가 다음과 같이 구성된 객체의 배열이라고 가정해 보겠습니다.

```js
const state = [
  {
    id: "todo-0",
    name: "몇 가지 프레임워크를 알아보자!",
  },
];
```

이러한 작업 중 하나를 사용자에게 어떻게 표시합니까? 각 작업을 목록 항목으로 나타내려고 합니다. 순서가 지정되지 않은 목록 요소(a `<ul>`) 안에 HTML`<li>` 요소를 어떻게 만들까요? 다음과 같이 보일 수 있습니다.

```js
function buildTodoItemEl(id, name) {
  const item = document.createElement("li");
  const span = document.createElement("span");
  const textContent = document.createTextNode(name);

  span.appendChild(textContent);

  item.id = id;
  item.appendChild(span);
  item.appendChild(buildDeleteButtonEl(id));

  return item;
}
```

여기서는, `document.createElement()` 메서드를 사용하여 `<li>`를 만들고 몇 줄의 추가 코드를 사용하여 필요한 속성과 자식을 만듭니다.

이전 코드 조각의 10번째 줄은 또 다른 빌드 함수인 `buildDeleteButtonEl()`을 참조합니다. 목록 항목 요소를 빌드하는 데 사용하는 것과 유사한 패턴을 따릅니다.

```js
function buildDeleteButtonEl(id) {
  const button = document.createElement("button");
  const textContent = document.createTextNode("Delete");

  button.setAttribute("type", "button");
  button.appendChild(textContent);

  return button;
}
```

이 버튼은 아직 아무것도 하지 않지만, 나중에 삭제 기능을 구현하기로 결정하면 됩니다. 페이지에서 항목을 렌더링하는 코드는 다음과 같을 수 있습니다.

```js
function renderTodoList() {
  const frag = document.createDocumentFragment();
  state.tasks.forEach((task) => {
    const item = buildTodoItemEl(task.id, task.name);
    frag.appendChild(item);
  });

  while (todoListEl.firstChild) {
    todoListEl.removeChild(todoListEl.firstChild);
  }
  todoListEl.appendChild(frag);
}
```

이제 UI에 30줄 이상의 코드가 단지 DOM에서 렌더링하기 위해 생겼고, 나중에 목록 항목의 스타일을 지정하는 데 사용할 수 있는 클래스를 추가하지도 않았습니다!

이 예에서와 같이, DOM으로 직접 작업하려면, DOM이 작동하는 방식에 대한 많은 것(속성을 변경하는 방법, 요소를 서로 안에 넣는 방법, 페이지에 표시하는 방법)을 이해해야 합니다. 이 코드는 실제로 사용자 상호 작용을 처리하거나 작업 추가 또는 삭제를 처리하지 않습니다. 이러한 기능을 추가하면 적시에 적절한 방식으로 UI를 업데이트 해야 한다는 점을 기억해야 합니다.

JavaScript 프레임워크는 이러한 종류의 작업을 훨씬 쉽게 하기 위해 만들어졌으며, 더 나은 개발자 경험을 제공하기 위해 존재합니다. 이들은 JavaScript에 새로운 기능을 제공하지 않고, 오늘날의 웹을 구축할 수 있도록 JavaScript의 기능에 더 쉽게 액세스하는 것을 지원합니다.

이 섹션의 코드 샘플을 실제로 보려면 CodePen에서 작동하는 앱 버전을 확인하세요. 사용자가 새 작업을 추가하고 삭제할 수도 있습니다.

이 섹션에서 사용된 JavaScript에 대해 자세히 읽어보십시오.

* `document.createElement()`
* `document.createTextNode()`
* `document.createDocumentFragment()`
* `eventTarget.addEventListener()`
* `node.appendChild()`
* `node.removeChild()` (en-US)
UI를 구축하는 또 다른 방법
----------------

모든 JavaScript 프레임워크는 사용자 인터페이스를 보다 선언적으로 작성하는 방법을 제공합니다. 즉, 그것들은 UI가 어떻게 보여야 하는지를 설명하는 코드를 작성할 수 있게 해주며, 프레임워크는 뒤에서 DOM에서 일어나도록 합니다.

반복적으로 새로운 DOM 요소를 구축하는 바닐라 JavaScript 접근 방식은 한 눈에 이해하기 어려웠습니다. 반대로, 다음 코드 블록은 Vue를 사용하여 작업 목록을 설명하는 방법을 보여줍니다.

```html
<ul>
  <li v-for="task in tasks" v-bind:key="task.id">
    <span>{{task.name}}</span>
    <button type="button">삭제</button>
  </li>
</ul>
```

그게 전부입니다. 이 코드 조각은 약 32줄의 코드를 6줄로 줄입니다. 중괄호와 `v-` 속성이 익숙하지 않더라도 괜찮습니다. 이 모듈의 뒷부분에서 Vue 관련 구문에 대해 배우게 됩니다. 여기서 알아두어야 할 점은 이 코드는 그것이 나타내는 UI처럼 보이지만, 기본 JavaScript 코드는 그렇지 않다는 것입니다.

Vue 덕분에 UI를 구축하기 위해 자체 함수를 작성할 필요가 없었습니다. 프레임워크는 이를 최적화되고 효율적인 방식으로 처리합니다. 여기서 우리의 유일한 역할은 각 항목이 어떻게 생겼는지 Vue에 설명하는 것이었습니다. Vue에 익숙한 개발자는 프로젝트에 참여하면 진행 상황을 빠르게 파악할 수 있습니다. Vue는 혼자가 아닙니다. 프레임워크를 사용하면 팀과 개인의 효율성이 향상됩니다.

바닐라 JavaScript에서 이것과 유사한 일을 하는 것이 가능합니다. 템플릿 리터럴 문자열을 사용하면 최종 요소의 모양을 나타내는 HTML 문자열을 쉽게 작성할 수 있습니다. 이는 할 일 목록 응용프로그램과 같이 단순한 것에 유용한 아이디어일 수 있지만, 수천 개의 데이터 레코드를 관리하고 사용자 인터페이스에서 많은 고유 요소를 렌더링할 수 있는 대규모 응용프로그램에서는 유지 관리할 수 있습니다.

프레임워크가 우리에게 주는 다른 것들
--------------------

프레임워크가 제공하는 다른 이점 중 일부를 살펴보겠습니다. 이전에 언급했듯이 프레임워크의 이점은 바닐라 JavaScript에서 달성할 수 있지만, 프레임워크를 사용하면 이러한 문제를 직접 해결해야 하는 모든 인지 부하가 제거됩니다.

### 도구

이 모듈의 각 프레임워크에는 대규모 활성 커뮤니티가 있으므로, 각 프레임워크의 에코시스템은 개발자 경험을 개선하는 도구를 제공합니다. 이러한 도구를 사용하면 테스트(응용 프로그램이 제대로 작동하는지 확인) 또는 Linting(코드에 오류가 없고 스타일이 일관성이 있는지 확인)과 같은 항목을 쉽게 추가할 수 있습니다.

**참고:** 웹 도구 개념에 대해 자세히 알아보려면, 클라이언트 측 도구 개요 (en-US)를 확인해주세요.

### 구획화

대부분의 주요 프레임워크는 개발자가 사용자 인터페이스의 다른 부분을 서로 통신할 수 있는 유지 관리 가능하고, 재사용 가능한 코드 덩어리인 컴포넌트로 추상화하도록 권장합니다. 주어진 컴포넌트와 관련된 모든 코드는 하나의 파일(또는 몇 개의 특정 파일)에 있을 수 있으므로 개발자는 해당 컴포넌트를 변경하기 위해 어디로 가야 하는지 정확히 알 수 있습니다. 바닐라 JavaScript 앱에서는 효율적이고, 확장 가능한 방식으로 이를 달성하기 위해 고유한 규칙 집합을 만들어야 합니다. 많은 JavaScript 개발자는 자신의 장치에 남겨두면 UI의 한 부분과 관련된 모든 코드가 파일 전체 또는 다른 파일 전체에 퍼져 있는 상태로 끝날 수 있습니다.

### 라우팅

웹의 가장 중요한 기능은 사용자가 한 페이지에서 다른 페이지로 이동할 수 있다는 것입니다. 결국 웹은 서로 연결된 문서의 네트워크입니다. 바로 이 웹사이트의 링크를 따라가면 브라우저가 서버와 통신하고 표시할 새 콘텐츠를 가져옵니다. 이렇게 하면 주소 표시줄의 URL이 변경됩니다. 이 새 URL을 저장하고 나중에 페이지로 다시 돌아오거나, 다른 사람과 공유하여 같은 페이지를 쉽게 찾을 수 있습니다. 브라우저는 탐색 기록을 기억하고 앞뒤로 탐색할 수도 있습니다. 이를 **서버 측 라우팅**이라고 합니다.

최신 웹 애플리케이션은 일반적으로 새 HTML 파일을 가져오거나 렌더링하지 않습니다. 단일 HTML 셸을 로드하고, 사용자를 웹의 새 주소로 안내하지 않고 내부 DOM(**단일 페이지 앱** 또는 **SPA**라고 합니다)을 지속적으로 업데이트 합니다. 각각의 새 의사 웹페이지는 일반적으로 시각적인 요소들라고 하며, 기본적으로 라우팅이 수행되지 않습니다.

SPA가 충분히 복잡하고, 고유한 시각적인 요소들을 충분히 렌더링하는 경우, 애플리케이션에 라우팅 기능을 가져오는 것이 중요합니다. 사람들은 애플리케이션의 특정 페이지에 연결하고, 탐색 기록에서 앞뒤로 이동할 수 있는 기능에 익숙하며, 이러한 표준 웹 기능이 손상되면 경험이 저하됩니다. 이러한 방식으로 클라이언트 애플리케이션에서 라우팅을 처리하는 경우 **클라이언트 측 라우팅**이라고 합니다.

JavaScript와 브라우저의 기본 기능을 사용하여 라우터를 만드는 것은 가능하지만, 널리 사용되고 적극적으로 개발된 프레임워크에는 라우팅을 개발 프로세스에서 보다 직관적으로 만들 수 있는 동반 라이브러리가 있습니다.

프레임워크를 사용할 때 고려해야 할 사항
----------------------

효과적인 웹 개발자가 된다는 것은 작업에 가장 적합한 도구를 사용하는 것을 의미합니다. JavaScript 프레임워크를 사용하면 프런트엔드 애플리케이션 개발이 쉬워지지만, 모든 문제를 해결할 수 있는 묘책은 아닙니다. 이 섹션에서는 프레임워크를 사용할 때 고려해야 할 몇 가지 사항에 대해 설명합니다. 프레임워크가 전혀 필요하지 않을 수도 있다는 점을 염두에 두세요. 단지 프레임워크를 위해 프레임워크를 사용하게 되는 것은 아니라는 점에 유의하세요.

### 도구에 대한 친숙도

바닐라 JavaScript와 마찬가지로 프레임워크는 학습하고 고유한 특성을 갖는 데 시간이 걸립니다. 프로젝트에 프레임워크를 사용하기로 결정하기 전에, 프레임워크가 자신에게 불리하게 작동하지 않고, 자신에게 유용하도록 기능을 충분히 익힐 시간이 있는지 확인하고, 팀원도 편안하게 사용할 수 있는지 확인하세요.

### 오버엔지니어링

웹 개발 프로젝트가 몇 페이지로 구성된 개인 포트폴리오이고, 해당 페이지에 대화형 기능이 거의 또는 전혀 없는 경우, 프레임워크(및 모든 JavaScript)가 전혀 필요하지 않을 수 있습니다. 즉, 프레임워크는 모놀리식(monolithic)이 아니며, 일부는 다른 것보다 소규모 프로젝트에 더 적합합니다. Smashing Magazine의 기사에서 Sarah Drasner는 Vue가 jQuery를 대체할 수 있는 방법이 웹 페이지의 작은 부분을 상호 작용하는 도구로 사용하는 방법에 대해 작성하였습니다.

### 더 큰 코드 기반 및 추상화

프레임워크를 사용하면 백그라운드에서 DOM 상호 작용을 처리하여, 보다 선언적인 코드를 작성할 수 있으며 때로는 전체적으로 더 적은 코드를 작성할 수 있습니다. 이 추상화는 개발자 경험에 유용하지만, 무료는 아닙니다. 작성한 내용을 DOM 변경으로 변환하기 위해, 프레임워크는 자체 코드를 실행해야 합니다. 그러면 최종 소프트웨어 조각이 더 커지고 실행 비용이 더 많이 듭니다.

일부 추가 코드는 불가피하며, 트리쉐이킹(빌드 프로세스 중에 앱에서 실제로 사용되지 않는 모든 코드 제거)을 지원하는 프레임워크를 사용하면 애플리케이션을 작게 유지할 수 있지만, 이는 특히 휴대전화와 같이 네트워크/스토리지 제약이 더 많은 기기에서 앱의 성능을 고려할 때 여전히 염두에 두어야 할 요소입니다.

프레임워크의 추상화는 JavaScript뿐만 아니라, 웹의 본질과의 관계에도 영향을 미칩니다. 웹 용으로 빌드하는 방법에 관계없이, 사용자가 궁극적으로 상호 작용하는 계층인 최종 결과는, HTML입니다. 전체 애플리케이션을 JavaScript로 작성하면 HTML과 다양한 태그의 목적을 간과하게 될 수 있으며, 의미가 없고 액세스할 수 없는 HTML 문서를 생성하게 됩니다. 실제로 JavaScript에 전적으로 의존하고 JavaScript 없이는 작동하지 않는 깨지기 쉬운 응용 프로그램을 작성할 수 있습니다.

프레임워크는 문제의 원인이 아닙니다. 우선 순위가 잘못되면, 모든 애플리케이션이 깨지기 쉽고, 비대해지며, 액세스가 불가능할 수 있습니다. 그러나 프레임워크는 개발자로서 우리의 우선 순위를 증폭시켜줍니다. 우선 순위가 복잡한 웹 앱을 만드는 것이라면, 쉽게 할 수 있습니다. 그러나 우선 순위가 성능과 접근성을 신중하게 보호하지 않으면, 프레임워크가 취약성, 코드 양의 증가 및 접근성을 증폭시킬 것입니다. 프레임워크에 의해 증폭된 최신 개발자 우선순위는 많은 곳에서 웹의 구조를 뒤집었습니다. 강력한 콘텐츠 우선 문서 네트워크 대신, 웹은 이제 종종 JavaScript를 먼저 사용하고, 사용자 경험을 마지막에 둡니다.

프레임워크 기반 웹의 접근성
---------------

이전 섹션에서 말한 내용을 기반으로, 접근성에 대해 좀 더 이야기해 봅시다. 사용자 인터페이스를 접근 가능하게 만들려면 항상 약간의 생각과 노력이 필요하며, 프레임워크는 해당 프로세스를 복잡하게 만들 수 있습니다. ARIA 라이브 영역 (en-US) 또는 포커스 관리와 같은 기본 브라우저 기능에 액세스하려면 고급 프레임워크 API를 사용해야 하는 경우가 많습니다.

경우에 따라, 프레임워크 애플리케이션은 기존 웹 사이트에 존재하지 않는 접근성 장벽을 생성합니다. 이에 대한 가장 큰 예는 앞에서 언급한 클라이언트 측 라우팅입니다.

기존(서버 측) 라우팅을 사용하면, 웹 탐색에서 예측 가능한 결과를 얻을 수 있습니다. 브라우저는 페이지 상단에 초점을 맞추는 것을 알고 있으며, 보조 기술은 페이지 제목을 알립니다. 이러한 일은 새 페이지로 이동할 때마다 발생합니다.

클라이언트 측 라우팅을 사용하면, 브라우저가 새 웹 페이지를 로드하지 않으므로, 자동으로 포커스를 조정하거나 새 페이지 제목을 알려야 한다는 것을 알 수 없습니다. 프레임워크 작성자는 이러한 기능을 다시 생성하는 JavaScript를 작성하는 데 막대한 시간과 노력을 기울였으며, 그 후에도 완벽하게 수행된 프레임워크는 없었습니다.

결론은 모든 웹 프로젝트의 맨 처음부터 접근성을 고려해야 한다는 것입니다. 그렇지 않으면 프레임워크를 사용하는 추상화된 코드베이스가 주요 접근성 문제를 겪을 가능성이 더 높다는 점을 명심하세요.

프레임워크를 선택하는 방법
--------------

이 모듈에서 설명하는 각 프레임워크는 웹 애플리케이션 개발에 대해 서로 다른 접근 방식을 사용합니다. 각각은 정기적으로 개선되거나 변경되고 있습니다. 올바른 프레임워크를 선택하는 것은 팀 및 프로젝트에 따라 달라지는 프로세스이며, 필요에 맞는 것을 찾기 위해 자체 조사를 수행해야 합니다. 즉, 보다 효과적으로 옵션을 조사하기 위해 물어볼 수 있는 몇 가지 질문을 확인했습니다.

1. 프레임워크는 어떤 브라우저를 지원합니까?
2. 프레임워크가 활용하는 도메인별 언어는 무엇입니까?
3. 프레임워크에 강력한 커뮤니티와 유용한 문서(및 기타 지원)가 있습니까?

이 섹션의 표는 각 프레임워크에서 제공하는 현재 브라우저 지원과 함께 사용할 수 있는 **도메인 별 언어**에 대한 간략한 요약을 제공합니다.

대체로, domain-specific languages (DSLs)은 소프트웨어 개발의 특정 영역과 관련된 프로그래밍 언어입니다. 프레임워크 맥락에서, DSL은 해당 프레임워크로 개발하기 쉽게 만드는 JavaScript 또는 HTML의 변형입니다. 결정적으로, 어떤 프레임워크도 개발자가 특정 DSL을 사용하도록 요구하지 않지만, 거의 모두 특정 DSL을 염두에 두고 설계되었습니다. 프레임워크의 기본 DSL을 사용하지 않기로 선택하면, 개발자 경험을 향상시킬 수 있는 기능을 놓치게 됩니다.

새 프로젝트를 선택할 때, 프레임워크의 지원 매트릭스와 DSL을 진지하게 고려해야 합니다. 일치하지 않는 브라우저 지원은 사용자에게 장벽이 될 수 있습니다. 일치하지 않는 DSL 지원은 당신과 당신의 팀원에게 장벽이 될 수 있습니다.

| 프레임워크 | 브라우저 지원 | 선호하는 DSL | 지원되는 DSL들 | 참고자료 |
| --- | --- | --- | --- | --- |
| Angular | Modern | TypeScript | HTML-based; TypeScript | 공식 문서 |
| React | Modern | JSX | JSX; TypeScript | 공식 문서 |
| Vue | Modern (IE9+ in Vue 2) | HTML-based | HTML-based, JSX, Pug | 공식 문서 |
| Ember | Modern (IE9+ in Ember version 2.18) | Handlebars | Handlebars, TypeScript | 공식 문서 |

**참고:** 우리가 "HTML-based"이라고 설명한 DSL에는 공식 이름이 없습니다. 실제 DSL은 아니지만 비표준 HTML이므로, 강조할 가치가 있다고 생각합니다.

### 프레임워크에 강력한 커뮤니티가 있습니까?

커뮤니티 규모는 접근하기 쉬운 숫자와 직접적인 상관관계가 없기 때문에 측정하기 가장 어려운 지표일 것입니다. 프로젝트의 GitHub 별 또는 주간 npm 다운로드 수를 확인하여 인기도를 파악할 수 있지만, 때로는 몇 개의 포럼을 검색하거나 다른 개발자와 대화하는 것이 가장 좋습니다. 커뮤니티의 규모뿐만 아니라, 커뮤니티가 얼마나 환영하고 포용적인지, 사용 가능한 문서가 얼마자 좋은지도 중요합니다.

### 웹상의 의견

이 문제에 대한 우리의 말을 그대로 받아들이지 마세요. 웹 전체에 토론이 있습니다. Wikimedia Foundation은 최근 Vue를 프런트 엔드로 사용하기로 선택하고 프레임워크 채택에 대한 의견 요청(RFC) 요청을 게시했습니다. RFC의 작성자인 Eric Gardner는 시간을 들여 Wikimedia 프로젝트의 요구 사항과 특정 프레임워크가 팀에 적합한 이유를 설명했습니다. 이 RFC는 프런트 엔드 프레임워크를 사용할 계획을 세울 때 스스로 수행해야 하는 조사 유형의 좋은 예입니다.

JavaScript 현황 설문조사는 JavaScript 개발자의 유용한 피드백 모음입니다. 프레임워크 사용과 프레임워크에 대한 개발자 정서에 대한 데이터를 포함하여 JavaScript와 관련된 많은 주제를 다루고 있습니다. 현재 몇 년간의 데이터를 사용할 수 있으므로 프레임워크의 인기도를 알 수 있습니다.

Vue 팀은 Vue를 다른 인기 있는 프레임워크와 철저하게 비교했습니다. 이 비교에는 약간의 편향이 있을 수 있지만(그들은 지적합니다), 그럼에도 불구하고 귀중한 리소스입니다.

클라이언트 측 프레임워크의 대안
-----------------

웹 개발 프로세스를 신속하게 처리할 도구를 찾고 있고, 프로젝트에 집중적인 클라이언트 측 JavaScript가 필요하지 않다는 것을 알고 있다면, 웹 구축을 위한 몇 가지 다른 솔루션 중 하나에 도달할 수 있습니다.

* 콘텐츠 관리 시스템
* 서버 측 렌더링
* 정적 사이트 생성기
### 콘텐츠 관리 시스템

**콘텐츠 관리 시스템**(**CMSes**)은 사용자가 직접 코드를 작성하지 않고도 웹용 콘텐츠를 만들 수 있는 도구입니다. 대규모 프로젝트, 특히 코딩 능력이 제한된 콘텐츠 작성자의 입력이 필요한 프로젝트나, 시간을 절약하려는 프로그래머에게 적합한 솔루션입니다. 그러나 설정하는 데 상당한 시간이 필요하며, CMS를 사용하면 웹 사이트의 최종 출력에 대한 제어를 최소한 어느 정도 양보해야 합니다. 예를 들어, 선택한 CMS가 기본적으로 액세스 가능한 콘텐츠를 작성하지 않는 경우, 이를 개선하기 어려운 경우가 많습니다.

인기있는 예로는 Wordpress, Joomla, 및 Drupal이 있습니다.

### 서버 측 렌더링

**서버 측 렌더링** (**SSR**)은 단일 페이지 애플리케이션을 렌더링하는 것이 서버의 작업인 애플리케이션 아키텍처입니다. 이는 JavaScript 애플리케이션을 구축하는 가장 일반적이고 가장 간단한 방법인 클라이언트측 렌더링의 반대입니다. 렌더링된 HTML 파일만 전송하기 때문에 클라이언트 장치에서 서버 측 렌더링이 더 쉽지만, 클라이언트 측에서 렌더링된 애플리케이션에 비해 설정하기 어려울 수 있습니다.

이 모듈에서 다루는 모든 프레임워크는 서버 측 렌더링과 클라이언트 측 렌더링을 지원합니다. React의 경우 Next.js, Vue의 경우 Nuxt.js(헷갈릴 수 있지만, 관련 없음!), Ember의 경우 FastBoot, Angular의 경우 Angular Universal를 확인하세요.

**참고:** 일부 SSR 솔루션은 커뮤니티에서 작성하고 유지 관리하는 반면, 일부는 프레임워크 유지 관리자가 제공하는 "공식" 솔루션입니다.

### 정적 사이트 생성기

정적 사이트 생성기는 여러 페이지로 구성된 웹사이트의 모든 웹페이지(관련 CSS 또는 JavaScript 포함)를 동적으로 생성하여 원하는 위치에 게시할 수 있도록 하는 프로그램입니다. 예를 들어, 게시 호스트는 GitHub 페이지 분기, Netlify 인스턴스 또는 선택한 개인 서버일 수 있습니다. 이 접근 방식에는 주로 성능(사용자 장치가 JavaScript로 페이지를 구축하지 않고 이미 완료됨) 및 보안(정적 페이지에는 공격 벡터가 적음)과 관련된 여러 가지 이점이 있습니다. 이러한 사이트는 여전히 필요한 경우 JavaScript를 사용할 수 있지만 그것에 의존하지는 않습니다. 정적 사이트 생성기는 다른 도구와 마찬가지로 학습하는 데 시간이 걸리므로, 개발 프로세스에 장벽이 될 수 있습니다.

정적 사이트는 고유한 페이지를 원하는 만큼 적게 또는 많이 가질 수 있습니다. 프레임워크를 통해 클라이언트 측 JavaScript 애플리케이션을 빠르게 작성할 수 있는 것처럼 정적 사이트 생성기를 사용하면 개별적으로 작성했을 HTML 파일을 빠르게 생성할 수 있습니다. 프레임워크와 마찬가지로 정적 사이트 생성기를 사용하면 개발자가 웹 페이지의 공통 부분을 정의하는 컴포넌트를 작성하고, 이러한 컴포넌트를 함께 구성하여 최종 페이지를 만들 수 있습니다. 정적 사이트 생성기의 맥락에서 이러한 컴포넌트를 **템플릿**이라고 합니다. 정적 사이트 생성기에 의해 구축된 웹 페이지는 프레임워크 애플리케이션의 홈이 될 수도 있습니다. 예를 들어, 사용자가 방문할 때 정적으로 생성된 웹 사이트의 특정 페이지에서 React 애플리케이션을 부팅하도록 하려면 그렇게 할 수 있습니다.

정적 사이트 생성기는 꽤 오랫동안 사용되어 왔으며, 최근 웹 역사에서 약간의 부활을 보았습니다. 이제 Hugo, Jekyll, Eleventy 및 Gatsby와 같은 몇 가지 강력한 옵션을 사용할 수 있습니다.

정적 사이트 생성기에 대해 전체적으로 자세히 알아보려면 Tatiana Mac의 Eleventy 초보자 가이드를 확인하세요. 시리즈의 첫 번째 기사에서, 그녀는 정적 사이트 생성기가 무엇인지, 웹 콘텐츠를 게시하는 다른 수단과 어떻게 관련되는지 설명합니다.

요약
--

이것으로 프레임워크 소개를 마치겠습니다. 아직 어떤 코드도 가르치지 않았지만, 처음에 프레임워크를 사용하는 이유와 프레임워크를 선택하는 방법에 대한 유용한 배경 정보를 제공하고, 더 많은 것을 배우고 몰입할 수 있도록 만들었습니다!

다음 기사는 더 낮은 수준으로 내려가서, 프레임워크가 제공하는 경향이 있는 특정 종류의 기능과, 프레임워크가 작동하는 이유를 살펴봅니다.

* Overview: Client-side JavaScript frameworks
* 다음


프레임워크 주요 기능 - Web 개발 학습하기

프레임워크 주요 기능
===========

* 이전
* Overview: Client-side JavaScript frameworks
* 다음

주요 JavaScript 프레임워크는 DOM 업데이트하는 방식, 브라우저 이벤트 처리하는 법 그리고 즐거운 개발자 경험 제공을 제공하는 방식에 있어 차이가 있습니다. 이 기사에서는 "빅 4" 프레임워크의 주요 기능을 살펴보고, 프레임워크가 높은 수준에서 작용하는 방식과 프레임워크 간의 차이점을 살펴봅니다.

|  |  |
| --- | --- |
| 전제조건: | HTML,
 CSS, 및
 JavaScript 언어 핵심에 대한 친숙도
  |
| 목적: | 프레임워크의 주요 코드 기능을 이해합니다. |

도메인 특정 언어
---------

이 모듈에서 설명하는 모든 프레임워크는 JavaScript로 구동되며 모두 도메인별 언어(DSL, domain-specific language)을 사용하여 애플리케이션을 구축할 수 있습니다. 특히 React는 컴포넌트 작성을 위해 **JSX** 사용을 대중화한 반면, Ember는 **Handlebars**를 활용합니다. HTML과 달리, 이러한 언어는 데이터 변수를 읽는 방법을 알고 있으며, 이 데이터를 사용하여 UI 작성 프로세스를 간소화할 수 있습니다.

Angular 앱은 종종 **TypeScript**를 많이 사용합니다. TypeScript는 사용자 인터페이스 작성과 관련이 없지만, 도메인 특정 언어이며, 일반 JavaScript와 상당한 차이가 있습니다.

DSL은 브라우저에서 직접 읽을 수 없습니다. 먼저 JavaScript 또는 HTML로 변환해야 합니다. 변환은 개발 프로세스의 추가 단계 (en-US)이지만, 일반적으로 프레임워크 도구는 이 단계를 처리하는 데 필요한 도구를 포함하거나 이 단계를 포함하도록 조정할 수 있습니다. 이러한 도메인별 언어를 사용하지 않고 프레임워크 앱을 구축할 수 있지만, 도메인별 언어를 활용하면 개발 프로세스가 간소화되고 해당 프레임워크 관련 커뮤니티에서 도움을 더 쉽게 찾을 수 있습니다.

### JSX

JavaScript 및 XML을 나타내는 JSX는 HTML과 유사한 구문을 JavaScript 환경에 제공하는 JavaScript의 확장입니다. React 팀에서 React 애플리케이션에 사용하기 위해 발명했지만, 예를 들어 Vue 앱과 같은 다른 애플리케이션을 개발하는 데 사용할 수 있습니다.

다음은 간단한 JSX 예제를 보여줍니다.

```js
const subject = "World";
const header = (
  <header>
    <h1>Hello, {subject}!</h1>
  </header>
);
```

이 표현식은 `<h1>`이 있는 HTML `<header>` 요소를 나타냅니다. 4행에서 `subject` 주변의 중괄호는 애플리케이션이 `subject` 상수의 값을 읽고 이를 `<h1>`에 삽입하도록 지시합니다.

React와 함께 사용하면, 이전 코드 조각의 JSX가 다음과 같이 컴파일됩니다.

```js
const subject = "World";
const header = React.createElement(
  "header",
  null,
  React.createElement("h1", null, "Hello, ", subject, "!"),
);
```

궁극적으로 브라우저에 의해 렌더링될 때, 위의 코드 조각은 다음과 같은 HTML을 생성합니다.

```html
<header>
  <h1>Hello, World!</h1>
</header>
```

### Handlebars

Handlebars 템플릿 언어는 Ember 애플리케이션에만 국한되지 않지만, Ember 앱에서 많이 활용됩니다. Handlebars 코드는 HTML과 유사하지만, 다른 곳에서 데이터를 가져올 수 있는 옵션이 있습니다. 이 데이터는 애플리케이션이 궁극적으로 빌드하는 HTML에 영향을 미치는 데 사용될 수 있습니다.

JSX와 마찬가지로, Handlebars는 중괄호를 사용하여 변수 값을 주입합니다. Handlebars는 한 쌍의 중괄호 대신 이중 쌍의 중괄호를 사용합니다.

이 Handlebars 템플릿이 주어지면:

```html
<header>
  <h1>Hello, {{subject}}!</h1>
</header>
```

그리고 이 데이터:

```js
{
  subject: "World";
}
```

Handlebars는 다음과 같이 HTML을 빌드합니다.

```html
<header>
  <h1>Hello, World!</h1>
</header>
```

### TypeScript

TypeScript는 JavaScript의 상위집합입니다. 즉, JavaScript를 확장합니다. 모든 JavaScript 코드는 유효한 TypeScript이지만, 그 반대는 아닙니다. TypeScript는 엄격한 방식으로 코드를 쓸 수 있어 유용합니다. 예를 들어, 정수 `a`와 `b`를 취해 그 합계를 반환하는 함수 `add()`를 생각해 보세요.

JavaScript에서, 해당 함수는 다음과 같이 작성할 수 있습니다.

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

JavaScript에 익숙한 사람에게는 사소할 점일 수 있지만, 이 코드는 더 명확하게 쓸 수 있습니다. JavaScript에서는 `+` 연산자를 사용하여 문자열을 함께 연결하므로, 이 함수는 `a`와 `b`가 문자열인 경우에도 기술적으로 여전히 작동합니다.이 경우에는 이 코드가 예상한 결과를 제공하지 않을 수 있습니다. 이 함수에 숫자만 전달되도록 하려면 어떻게 해야 할까요? TypeScript는 이를 가능하게 합니다.

```ts
function add(a: number, b: number) {
  return a + b;
}
```

여기에서 각 매개변수 뒤에 쓰여진 `: number`는 TypeScript에 `a`와 `b`가 모두 숫자여야 함을 알려줍니다. 이 함수를 사용하고 `'2'`를 인수로 전달하면, TypeScript는 컴파일 중에 오류를 발생시키고, 실수를 수정해야 합니다. 우리는 이러한 오류를 발생시키는 자체 JavaScript를 작성할 수 있지만, 소스 코드를 훨씬 더 장황하게 만듭니다. TypeScript가 이러한 검사를 처리하도록 하는 것이 더 합리적일 것입니다.

컴포넌트 작성하기
---------

이전 장에서 언급했듯이, 대부분의 프레임워크에는 일종의 컴포넌트 모델이 있습니다. React 컴포넌트는 JSX, Handlebars가 있는 Ember 컴포넌트, HTML을 약간 확장하는 템플릿 구문이 있는 Angular 및 Vue 컴포넌트로 작성할 수 있습니다.

컴포넌트를 어떻게 써야하는지와 관계없이, 각 프레임워크의 컴포넌트는 필요할 수도 있는 외부 속성, 컴포넌트가 관리해야 하는 내부 상태 및 사용자가 컴포넌트의 마크업에서 트리거할 수 있는 이벤트를 제공하는 방법을 제공합니다.

이 섹션의 나머지 코드 조각은 React를 예로 사용하며 JSX로 작성됩니다.

### 속성(프로퍼티)

속성 또는 **props**는 컴포넌트가 렌더링하기 위해 필요한 외부 데이터입니다. 온라인 잡지를 위한 웹 사이트를 구축중이고, 각각의 기고가가 자신의 작업에 대한 공로를 인정받고 있는지 확인해야 한다고 가정합니다. 각 기사와 함께 사용할 `AuthorCredit` 컴포넌트를 만들 수 있습니다. 이 컴포넌트는 작성자의 초상화와 저자에 대한 짧은 바이라인을 표시해야 합니다. 렌더링할 이미지와 인쇄할 작성자를 알기 위해 `AuthorCredit`은 일부 속성을 수락해야 합니다.

이 `AuthorCredit` 컴포넌트의 React 표현은 다음과 같을 수 있습니다.

```js
function AuthorCredit(props) {
  return (
    <figure>
      <img src={props.src} alt={props.alt} />
      <figcaption>{props.byline}</figcaption>
    </figure>
  );
}
```

`{props.src}`, `{props.alt}` 및 `{props.byline}`은 속성이 컴포넌트에 삽입되는 위치를 나타냅니다. 이 컴포넌트를 렌더링하려면, 렌더링하려는 위치(아마도 다른 컴포넌트 내부에 있을 것임)에 다음과 같은 코드를 작성합니다.

```js
<AuthorCredit
  src="./assets/zelda.png"
  alt="Zelda Schiff의 초상"
  byline="Zelda Schiff는 Library Times의 편집장입니다."
/>
```

그러면 궁극적으로 다음 `<figure>` 요소가 렌더링됩니다. 구조는 `AuthorCredit` 컴포넌트에 정의된 대로, 콘텐츠는`AuthorCredit` 컴포넌트 호출에 포함된 속성에 정의된 대로 표시됩니다.

```html
<figure>
  <img src="assets/zelda.png" alt="Portrait of Zelda Schiff" />
  <figcaption>Zelda Schiff는 Library Times의 편집장입니다.</figcaption>
</figure>
```

### 상태

이전 장에서 **상태**의 개념에 대해 이야기했습니다. 강력한 상태 처리 메커니즘은 효과적인 프레임워크의 핵심이며, 각 컴포넌트에는 상태 제어가 필요한 데이터가 있을 수 있습니다. 이 상태는 컴포넌트가 사용 중인 동안 어떤 식으로든 지속됩니다. 속성과 마찬가지로, 상태를 사용하여 컴포넌트가 렌더링되는 방식에 영향을 줄 수 있습니다.

예를 들어, 클릭한 횟수를 세는 버튼을 생각해 보십시오. 이 컴포넌트는 자체 count 상태 추적을 담당해야 하며, 다음과 같이 작성할 수 있습니다.

```js
function CounterButton() {
  const [count] = useState(0);
  return <button>{count}번 클릭됨</button>;
}
```

`useState()`는 **React 후크** 입니다. 초기 데이터 값이 주어지면, 업데이트될 때 해당 값을 추적합니다. 코드는 처음에 브라우저에서 다음과 같이 렌더링됩니다.

```html
<button>0번 클릭됨</button>
```

`useState()` 호출은 코드를 직접 작성할 필요 없이, 앱 전체에서 강력한 방식으로 `count` 값을 추적합니다.

### 이벤트

대화형이 되려면, 애플리케이션이 사용자에게 응답할 수 있도록 컴포넌트가 브라우저 이벤트에 응답하는 방법이 필요합니다. 프레임워크는 브라우저 이벤트를 수신하기 위한 자체 구문을 제공합니다. 이러한 구문은 같은 이름의 브라우저 이벤트를 참조합니다.

React에서, `click` 이벤트를 수신하려면 `onClick` 이라는 특수 속성이 필요합니다. 클릭 수를 계산할 수 있도록 위에서 `CounterButton` 코드를 업데이트 해 보겠습니다.

```js
function CounterButton() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}번 클릭됨</button>;
}
```

이 버전에서는 `count` 값을 업데이트하기 위해 호출할 수 있는 특수 `setCount()` 함수를 만들기 위해 추가 `useState()` 기능을 사용하고 있습니다. 4행에서 이 함수를 호출하고, `count`를 현재 값에 1을 더한 값으로 설정합니다.

컴포넌트 스타일링
---------

각 프레임워크는 컴포넌트 또는 애플리케이션 전체에 대한 스타일을 정의하는 방법을 제공합니다. 컴포넌트의 스타일을 정의하는 각 프레임워크의 접근 방식은 약간 다르지만, 모두 다양한 방법을 제공합니다. 일부 도우미 모듈을 추가하면 Sass 또는 Less에서 프레임워크 앱의 스타일을 지정하거나, 또는 PostCSS로 CSS 스타일시트를 트랜스파일할 수 있습니다.

의존성 처리
------

모든 주요 프레임워크는 의존성을 처리하기 위한 메커니즘을 제공합니다. 다른 컴포넌트 내부의 컴포넌트를 사용하며, 때로는 여러 계층 구조 수준이 있습니다. 다른 기능과 마찬가지로 정확한 메커니즘은 프레임워크마다 다르지만, 최종 결과는 동일합니다. 컴포넌트는 표준 JavaScript 모듈 문법 또는 적어도 유사한 것을 사용하여, 컴포넌트를 다른 컴포넌트로 가져오는 경향이 있습니다.

### 컴포넌트 안의 컴포넌트

컴포넌트 기반 UI 아키텍처의 주요 이점 중 하나는 컴포넌트를 함께 구성할 수 있다는 것입니다. 웹 사이트를 구축하기 위해 서로 내부에 HTML 태그를 작성할 수 있는 것처럼 다른 컴포넌트 내부의 컴포넌트를 사용하여 웹 애플리케이션을 구축할 수 있습니다. 각 프레임워크를 사용하면 다른 컴포넌트를 활용하는 (따라서 의존하는) 컴포넌트를 작성할 수 있습니다.

예를 들어, `AuthorCredit` React 컴포넌트는 `Article` 컴포넌트 내에서 활용될 수 있습니다. 즉, `Article`은 `AuthorCredit`을 가져와야 합니다.

```js
import AuthorCredit from "./components/AuthorCredit";
```

완료되면, 다음과 같이 `Article` 컴포넌트 내에서 `AuthorCredit`을 사용할 수 있습니다.

```js
// …

<AuthorCredit />

// …
```

### 의존성 주입

실제 응용 프로그램에는 종종 여러 단계의 중첩이 있는 컴포넌트 구조가 포함될 수 있습니다. 여러 단계로 중첩된 `AuthorCredit` 컴포넌트는 어떤 이유로 애플리케이션의 가장 루트 단계에서의 데이터가 필요할 수 있습니다.

우리가 만들고 있는 잡지 사이트가 다음과 같이 구성되어 있다고 가정해 보겠습니다.

```js
<App>
  <Home>
    <Article>
      <AuthorCredit {/\* props \*/} />
    </Article>
  </Home>
</App>
```

우리의 `App` 컴포넌트에는 `AuthorCredit` 컴포넌트에 필요한 데이터가 있습니다. 우리는 `Home`과 `Article`을 다시 작성하여 속성을 전달하는 것을 알 수 있지만, 데이터의 원본과 대상 사이에 매우 많은 단계가 있는 경우, 지루할 수 있고 과도합니다. `Home` 및 `Article`은 실제로 저자의 초상이나 작성자를 사용하지 않지만, 해당 정보를 `AuthorCredit`에 가져오려면, `Home` 및 `Article`을 변경해서 받아들여야 합니다.

많은 컴포넌트 계층을 통해 데이터를 전달하는 문제를 속성 구멍 뚫기(props drilling)라고 하며, 대규모 응용 프로그램에는 적합하지 않습니다.

속성 구멍 뚫기(props drilling)를 피하기 위해, 프레임워크는 의존성 주입이라는 기능을 제공합니다. 이 기능은 중간 단계를 거치지 않고, 특정 데이터를 필요한 컴포넌트에 직접 가져오는 방법입니다. 각 프레임워크는 다른 이름과 다른 방식으로 의존성 주입을 구현하지만, 그 효과는 궁극적으로 동일합니다.

Angular는 이 과정을 의존성 주입이라고 부릅니다. Vue에는 `provide()`와 `inject()` 컴포넌트 메서드, React에는 Context API, Ember는 서비스를 통해 상태를 공유합니다.

### 생명주기

프레임워크의 맥락에서, 컴포넌트의 **수명 주기**는 컴포넌트가 DOM에 추가된 후 브라우저에 의해 렌더링(종종 마운팅이라고 합니다)되는 시간부터 DOM에서 제거(종종 언마운팅이라고 합니다)되는 시간까지 거치는 단계의 모음입니다. 각 프레임워크는 이러한 수명 주기 단계의 이름을 다르게 지정하며, 모든 개발자가 동일한 단계에 접근할 수 있는 것은 아닙니다. 모든 프레임워크는 동일한 일반 모델을 따릅니다. 이를 통해, 개발자는 컴포넌트가 마운트될 때, 렌더링될 때, 마운트 해제될 때 및 이들 사이의 여러 단계에서 특정 작업을 수행할 수 있습니다.

렌더 단계는 사용자가 애플리케이션과 상호 작용할 때 가장 많이 반복되기 때문에, 이해하는 것이 가장 중요합니다. 새 정보가 브라우저에 있는 항목에 추가되었는지, 삭제되었는지 또는 그곳에 있는 내용을 편집했는지 여부에 관계없이 브라우저가 새로운 것을 렌더링해야 할 때마다 실행됩니다.

이 React 컴포넌트의 생명 주기 다이어그램은 개념에 대한 일반적인 개요를 제공합니다.

요소 렌더링
------

생명 주기와 마찬가지로, 프레임워크는 애플리케이션을 렌더링하는 방법에 대해 다르지만 유사한 접근 방식을 선택합니다. 이들 모두는 브라우저 DOM의 현재 렌더링된 버전을 추적하고, 응용 프로그램의 컴포넌트가 다시 렌더링될 때 DOM이 어떻게 변경되어야 하는지에 대해 각각 약간씩 다른 결정을 내립니다. 프레임워크가 이러한 결정을 내리기 때문에, 일반적으로 DOM과 직접 상호 작용하지 않습니다. DOM에서 멀어지는 이 추상화는 DOM을 직접 업데이트하는 것보다 더 복잡하고 메모리 집약적이지만, DOM이 없으면 프레임워크는 알려진 선언적 방식으로 프로그래밍할 수 없습니다.

**가상 DOM**은 브라우저의 DOM에 대한 정보가 JavaScript 메모리에 저장되는 방식입니다. 애플리케이션은 이 DOM 사본을 업데이트한 다음, 무엇을 렌더링할지 결정하기 위해 "실제" DOM(사용자를 위해 실제로 렌더링되는 DOM)과 비교합니다. 애플리케이션은 업데이트된 가상 DOM과 현재 렌더링된 DOM 간의 차이점을 비교하기 위해 "diff"를 빌드하고, 해당 diff를 사용해 업데이트를 실제 DOM에 적용합니다. React와 Vue는 모두 가상 DOM 모델을 사용하지만, 디핑(diffing) 또는 렌더링할 때 정확히 동일한 논리를 적용하지 않습니다.

React 문서에서 가상 DOM에 대해 자세히 읽어보세요.

**증분 DOM**은 무엇을 렌더링할지 결정하기 위해 DOM 비교(diff)를 구축하다는 점에서 가상 DOM과 유사하지만, JavaScript 메모리에 DOM의 완전한 복사본을 생성하지 않는다는 점에서 다릅니다. 변경할 필요가 없는 DOM 부분은 무시합니다. Angular는 증분 DOM을 사용하는 이 모듈에서 지금가지 논의된 유일한 프레임워크입니다.

Auth0 블로그에서 증분 DOM에 대한 자세한 내용을 읽을 수 있습니다.

**Glimmer VM**은 Ember만이 유일합니다. 가상 DOM도 증분 DOM도 아닙니다. Ember의 템플릿이 JavaScript보다 읽기 쉽고, 빠른 일종의 "바이트 코드"로 변환되는 별도의 프로세스입니다.

라우팅
---

이전 장에서 언급했던 라우팅은 웹 경험의 중요한 부분입니다. 많은 시각적인 요소가 포함되어 충분히 복잡한 앱에서 중단되는 경험을 방지하기 위해, 이 모듈에서 다루는 각 프레임워크는 개발자가 애플리케이션에서 클라이언트 측 라우팅을 구현하는 데 도움이 되는 라이브러리(또는 둘 이상의 라이브러리)를 제공합니다.

테스팅
---

모든 응용 프로그램은 소프트웨어가 예상대로 계속 작동하도록 보장하는 테스트 범위의 이점을 누리며, 웹 응용 프로그램도 마찬가지입니다. 각 프레임워크의 에코시스템은 테스트 작성을 용이하게 하는 도구를 제공합니다. 테스트 도구는 프레임워크 자체에 내장되어 있지 않지만, 프레임워크 앱을 생성하는 데 사용되는 명령줄 인터페이스 도구를 통해 적절한 테스트 도구에 접근할 수 있습니다.

단위 및 통합 테스트 기능이 모두 포함된 광범위한 도구가 각 프레임워크 생태계에 있습니다.

테스트 라이브러리는 React, Vue 및 Angular를 비롯한 많은 JavaScript 환경용 도구가 포함된 테스트 유틸리티 모음입니다. Ember 문서는 Ember 앱 테스트를 다룹니다.

다음은 React Testing Library의 도움으로 작성된 `CounterButton`에 대한 빠른 테스트입니다. 버튼의 존재 여부, 버튼을 0, 1, 2번 클릭한 후 올바른 텍스트가 표시되는지 여부와 같은 여러가지를 테스트합니다.

```js
import React from "react";
import { render, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";

import CounterButton from "./CounterButton";

it("Renders a semantic button with an initial state of 0", () => {
  const { getByRole } = render(<CounterButton />);
  const btn = getByRole("button");

  expect(btn).toBeInTheDocument();
  expect(btn).toHaveTextContent("Clicked 0 times");
});

it("Increments the count when clicked", () => {
  const { getByRole } = render(<CounterButton />);
  const btn = getByRole("button");

  fireEvent.click(btn);
  expect(btn).toHaveTextContent("Clicked 1 times");

  fireEvent.click(btn);
  expect(btn).toHaveTextContent("Clicked 2 times");
});
```

요약
--

프레임워크를 사용하여 애플리케이션을 만들 때, 사용할 실제 언어, 기능 및 도구에 대해 아이디어가 좀 더 떠올랐나요? 지금부터 열정적으로 시작하고 실제로 코딩해보세요. 지금 학습을 시작할 프레임워크를 선택해보세요.

* React
* Ember (en-US)
* Vue
* Svelte
* Angular (en-US)
* 이전
* Overview: Client-side JavaScript frameworks
* 다음


Vue 시작하기 - Web 개발 학습하기

Vue 시작하기
========

* 이전  (en-US)
* Overview: Client-side JavaScript frameworks
* 다음  (en-US)

이제 우리가 배울 세 번째 프레임워크인 Vue를 소개하겠습니다. 이 글에서 우리는 Vue에 대한 간단한 배경지식을 얻고, Vue를 설치하고 새로운 프로젝트를 만드는 방법을 배웁니다. 또한 전체 프로젝트와 컴포넌트의 주요 구조를 공부하고, 로컬에서 프로젝트를 실행하는 방법을 살펴본 후 예제 프로젝트를 구성해봅니다.

|  |  |
| --- | --- |
| 사전 요구 사항: |
 코어 HTML,
 CSS 및
 JavaScript 언어에 익숙함.
 터미널/커맨드라인에 대한 지식.Vue 컴포넌트는 앱의 데이터를
 관리하는 JavaScript 객체와 기본 DOM 구조에 매핑되는 HTML 기반 템플릿
 구문을 조합해 작성된다. Vue를 설치하고 고급 기능(예: 단일 파일
 컴포넌트, render 함수 등)을 사용하려면 node + npm이 설치된 터미널이
 필요하다.
  |
| 목적: |
 Vue 로컬 개발 환경을 구성한다. 스타터 앱을 만들고 기본적인 동작 원리를
 이해한다.
  |

더 선명한 Vue
---------

Vue는 최신의 JavaScript 프레임워크로 점진적 향상을 위한 유용한 기능을 제공합니다. 다른 많은 프레임워크와는 달리, Vue를 사용하면 기존의 HTML 코드를 향상시킬 수 있습니다. 이를 통해 아주 쉽게 JQuery 등의 라이브러리를 Vue로 대체할 수 있습니다.

물론 Vue를 이용해 완전한 단일 페이지 애플리케이션(SPA)을 작성할 수도 있습니다. 이렇게 하면 전체적으로 Vue가 관리하는 마크업을 만들 수 있고, 복잡한 애플리케이션에 대한 개발자 경험과 성능을 향상시킬 수 있습니다. 필요하다면 클라이언트 사이드 라우팅 및 상태 관리 라이브러리를 활용할 수 있다는 장점도 있습니다. Vue는 이와 같은 클라이언트 사이드 라우팅이나 상태 관리에 대해 "미들 그라운드" 접근 방식을 취합니다. Vue 코어 팀은 이런 기능을 위한 (vue-router, vuex 등의) 라이브러리를 유지보수하고 있지만, Vue에 직접 번들로 제공하지는 않습니다. 그러므로 우리가 만들 애플리케이션에 더 적합한 라우팅/상태 관리 라이브러리가 있다면 그것을 선택할 수 있습니다.

Vue는 애플리케이션에 Vue를 점진적으로 통합시킬 수 있도록 하며, 마크업 작성에 대해서도 점진적 접근법을 제공합니다. 다른 많은 프레임워크처럼, Vue에서도 컴포넌트를 통해 재사용 가능한 마크업 블록을 생성할 수 있습니다. 대부분의 경우 Vue 컴포넌트는 특별한 HTML 템플릿 구문으로 작성됩니다. HTML에서 가능한 것 이상의 제어가 필요하다면, JSX나 일반 JavaScript 함수를 이용해 컴포넌트를 정의할 수 있습니다.

이 튜토리얼을 진행하는 동안, 다른 탭에 Vue 가이드 와 API 문서를 열어 두고 각 항목에 대해 더 자세한 정보가 필요할 때 참조하세요. Vue와 다른 프레임워크 간의 비교(편향될 가능성 있음)는 Vue 문서: 다른 프레임워크와의 비교를 참조하세요.

설치하기
----

기존 사이트에서 Vue를 사용하려면 아래 `<script>` 요소 중 하나를 페이지에 추가하면 됩니다. 이 한 줄을 추가하는 것만으로 기존의 사이트에서 Vue를 사용할 수 있습니다. Vue가 자칭 '프로그레시브 프레임워크'임을 강조하는 이유가 바로 이것입니다. JQuery 등의 라이브러리를 사용하는 기존 프로젝트를 Vue로 마이그레이션하려고 할 때 아주 좋은 옵션입니다. 이 방식으로 속성, 커스텀 컴포넌트, 데이터 관리 등 수많은 Vue의 핵심 기능을 사용할 수 있게 됩니다.

* 개발 스크립트 (최적화되지 않음. 콘솔 경고 포함. 개발용으로 좋음.)

```markup
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
```

* 상용 스크립트 (최적화 버전. 콘솔 경고 최소화. 업데이트로 인해 사이트가 영향을 받지 않도록 버전 번호를 명시하는 것이 좋다.)

```markup
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
```

하지만 이 방식은 한계가 있습니다. 좀 더 복잡한 앱을 구성하려면 Vue NPM package를 사용하는 것이 좋습니다. Vue NPM 패키지를 사용하면 Vue의 고급 기능을 이용할 수 있고, WebPack 같은 번들 도구를 활용할 수 있습니다. 그리고 Vue CLI를 사용하면 개발 과정을 간소화해 Vue 앱을 더욱 쉽게 구성할 수 있습니다. npm 패키지와 CLI를 사용하기 위해 필요한 사항은 다음과 같습니다.

1. Node.js 8.11 이상 설치
2. npm 또는 yarn

**참고:** 아직 위 항목을 설치하지 않았다면, more about installing npm and Node.js (en-US) 를 참고하세요.

 CLI를 설치하기 위해 아래 커맨드를 터미널에서 실행하세요.
 npm을 사용하는 경우:

```bash
npm install --global @vue/cli
```

yarn을 사용하는 경우:

```bash
yarn global add @vue/cli
```

설치가 모두 완료되면, 프로젝트를 생성할 디렉토리에서 터미널을 열고 `vue create <프로젝트 이름>` 를 실행해봅시다. 그러면 Vue CLI는 우리가 사용할 수 있는 프로젝트 구성 목록을 보여줄 것입니다. 미리 설정되어 있는 몇 가지 구성 중 골라서 사용해도 되고, 모든 것을 직접 구성할 수도 있습니다. 이 옵션을 통해 우리는 TypeScript, linting, vue-router, 테스트 등의 항목을 구성할 수 있습니다.

더 자세한 내용은 아래에서 살펴보겠습니다.

새로운 프로젝트 시작하기
-------------

Vue의 다양한 기능을 알아보기 위해, 예제로 할 일 목록(todo list) 앱을 만들어보려고 합니다. Vue CLI를 사용해서 새로운 앱을 구성해봅시다. 아래 내용을 순서대로 따라해보세요:

1. 터미널에서 `cd <디렉토리>`를 입력해 샘플 앱을 만들 위치로 이동하고, `vue create moz-todo-vue`를 실행하세요.
2. 방향키와
 `Enter`
 를 사용해 "Manually select features" 옵션을 선택하세요.
3. 첫 번째 메뉴에서 프로젝트에 포함하고 싶은 기능들을 선택할 수 있습니다. "Babel"과 "Linter / Formatter"가 선택되어 있나요? 선택되어 있지 않다면 방향키와 스페이스바를 사용해 선택해주세요. 그리고
 `Enter`
 를 누릅니다.
4. 다음으로 linter / formatter 구성을 선택하겠습니다. "Eslint with error prevention only"로 이동하고 다시
 `Enter`
 를 눌러주세요. 흔히 생기는 에러를 잡는데 도움을 주면서도 너무 엄격하지는 않은 옵션을 선택했습니다.
5. 다음은 자동 lint를 어떤 방식으로 하고 싶은지 묻는 메세지가 표시될 것입니다. "Lint on save"를 선택하세요. 우리가 프로젝트 내부의 파일을 저장할 때 에러를 체크하라는 의미입니다. 이제
 `Enter`
 를 누르세요.
6. 이제 구성 파일을 어떻게 관리할지 선택해야 합니다. "In dedicated config files" 옵션을 선택하면 ESLint와 같은 것들의 구성 설정을 그들의 전용 파일에 넣게 됩니다. "In package.json" 옵션의 경우, 모든 구성 설정을 앱의 `package.json` 파일에 넣습니다. 우선 지금은 "In dedicated config files" 옵션을 선택하고
 `Enter`
 를 누릅시다.
7. 마지막으로, 지금까지 설정한 내용을 다음에 다시 사용하고 싶은지(현재 설정을 사전 설정 목록에 추가하고 싶은지) 묻는 메세지가 표시됩니다. 이 부분은 여러분이 하고 싶은대로 하시면 돼요. 만약 이 설정을 다음에도 다시 사용하고 싶다면
 `y`
 를, 아니라면
 `n`
 을 누르세요.

이제 CLI가 우리의 프로젝트를 위한 발판을 만들기 시작하고 우리의 앱을 위한 모든 디펜던시를 설치할 것입니다.

만약 이전에 한 번도 Vue CLI를 사용해본 적 없다면, 위에서 설명하지 않은 질문이 한 가지 더 표시될 것입니다. 패키지 매니저를 고르라는 메세지가 나타나면, 여러분이 선호하는 것을 고르면 됩니다. 지금부터는 여러분이 선택한 패키지 매니저(npm / yarn)가 기본값으로 설정됩니다. 만약 이 기본값을 변경하고 싶다면 `vue create` 커맨드를 실행할 때 `--packageManager=<package-manager>` 플래그를 추가하면 됩니다. 다시 말해, 만약 여러분이 이전에 yarn을 사용했지만 다음에는 npm을 사용하도록 프로젝트를 생성하고 싶다면 `vue create moz-todo-vue --packageManager=npm`을 실행하면 됩니다.

**참고:** 모든 옵션을 여기서 설명하지는 않았습니다. 자세한 내용은 find more information on the CLI 를 참고하세요.

프로젝트 구조
-------

위의 모든 내용이 성공적으로 진행됐다면, Vue CLI가 우리의 프로젝트에 사용할 디렉토리와 파일을 생성했을 것입니다. 다음은 그 중 가장 중요한 파일과 디렉토리에 대한 설명입니다.

* `.eslintrc.js`: eslint의 config 파일입니다. linting 규칙을 이 파일에서 관리할 수 있습니다.
* `babel.config.js`: Babel의 config 파일입니다. Babel은 개발용으로 사용하는 최신 JavaScript 기능을 상용 브라우저 호환이 가능하도록 구버전으로 번역하는 역할을 합니다. 대표적으로, ECMA Script 2015(비공식적으로 ES6라고도 불림)로 작성된 코드를 ES5로 변환해 IE 등의 브라우저에서 동작할 수 있도록 해주는 것이 Babel이 하는 일입니다. 이 파일에 부가적인 babel 플러그인을 등록할 수 있습니다.
* `.browserslistrc`: Browserslist의 config 파일입니다. 우리가 사용하는 도구를 어떤 브라우저에 최적화할지 이 파일에 정의할 수 있습니다.
* `public`: public 디렉토리는 Webpack의 처리를 받지 않고 퍼블리싱되는 정적 자산(static assets)을 포함합니다. (단, `index.html`의 경우 일부 Webpack의 처리를 받습니다)
    + `favicon.ico`: 앱의 대표 favicon 파일입니다. 기본값으로 Vue 로고가 들어가 있습니다.
    + `index.html`: 앱의 템플릿 파일입니다. 우리의 Vue 앱은 이 HTML 페이지로부터 실행됩니다. lodash 템플릿 구문을 사용해 보간법으로 값을 지정할 수 있습니다.

    **참고:** index.html 파일은 애플리케이션 레이아웃을 관리하는 템플릿이 아닙니다. 이 파일은 Vue 앱 외부의 정적 HTML을 제어하는 템플릿입니다. 고급 사용 사례를 제외하면 이 파일을 직접 편집할 일은 거의 없습니다.
* `src`: 우리의 Vue 애플리케이션의 핵심 내용을 포함하게 될 디렉토리입니다.
    + `main.js`: 이 파일은 애플리케이션의 진입점입니다. 현재 이 파일은 Vue 앱을 초기화하고 `index.html` 파일에서 어떤 HTML 엘리멘트를 앱에 붙여야 하는지 (예제 프로젝트에서는 #app 엘리멘트) 나타냅니다. 이 파일에 전역 컴포넌트나 부가적인 Vue 라이브러리를 등록하는 경우가 많습니다.
    + `App.vue`: Vue 애플리케이션의 최상위 컴포넌트입니다. Vue 컴포넌트에 대한 자세한 내용은 아래를 참조하세요.
    + `components`: 우리가 앞으로 만들 Vue 컴포넌트를 저장할 디렉토리입니다. 지금은 한 개의 예시 컴포넌트만을 포함하고 있습니다.
    + `assets`: CSS나 이미지 등의 정적 자산(static assets)을 저장하는 디렉토리입니다. 이 파일들은 src 디렉토리에 포함되어 있기 때문에 Webpack의 처리를 받습니다. 즉, Sass/SCSS나 Stylus와 같은 전처리 도구를 사용할 수 있다는 의미입니다.

**참고:** 새로운 프로젝트를 생성할 때 어떤 옵션을 선택했는지에 따라, 위에 소개되지 않은 디렉토리가 생길 수도 있습니다. 예를 들어 router 옵션을 선택했다면 `views`가 생성됩니다.

.vue 파일 (단일 파일 컴포넌트)
--------------------

다른 많은 프론트엔드 프레임워크와 마찬가지로, Vue 앱을 구축할 때도 컴포넌트가 중심적인 역할을 합니다. 대규모 애플리케이션을 여러 개의 개별 컴포넌트로 분리해 별도로 생성/구축하고, 필요한 경우 각 컴포넌트간에 데이터를 주고 받을 수 있습니다. 이 작은 블록들은 우리가 코드에 대해 추론하고 테스트하기 쉽도록 도와줍니다.

일부 프레임워크에서는 템플릿과 로직, 스타일링 코드를 각각 분리된 파일에 작성하기를 권장하지만, Vue는 이와 정반대의 방식을 채택합니다. 단일 파일 컴포넌트(Single File Components)를 사용하면, Vue는 템플릿과 이에 대응하는 스크립트, CSS를 모두 하나의 `.vue` 파일에 묶어서 작성하도록 합니다. 이 파일들은 Webpack과 같은 JS 빌드 도구를 통해 처리됩니다. 그러므로 프로젝트에서 빌드 타임 도구들을 사용할 수 있다는 장점이 있습니다. 즉 우리는 Bable, TypeScript, SCSS 같은 도구를 사용해 더욱 정교한 컴포넌트를 생성할 수 있습니다.

게다가 Vue CLI로 생성한 프로젝트는 즉시 Webpack과 함께 `.vue` 파일을 사용할 수 있도록 구성됩니다. 우리가 Vue CLI로 생성한 예제 프로젝트의 src 폴더를 열어 보면 `App.vue`가 생성된 것을 확인할 수 있습니다.

이제 App.vue를 좀 더 알아봅시다.

### App.vue

`App.vue` 파일을 열어보세요. 이 파일이 세 부분으로 이루어져 있다는 것을 바로 확인할 수 있습니다. 바로 컴포넌트 템플릿을 정의하는 `<template>` 파트, 스크립트를 작성하는 `<script>` 파트, 그리고 스타일링 정보를 작성하는 `<style>` 파트입니다. 모든 단일 파일 컴포넌트는 이러한 기본 구조로 이루어져 있습니다.

`<template>` 은 컴포넌트의 모든 마크업 구조와 디스플레이 로직을 포함합니다. 이 템플릿은 HTML 구문은 물론이고, 일부 Vue에 특화된 구문도 포함할 수 있습니다. Vue 관련 구문은 나중에 더 자세히 알아보겠습니다.

**참고:** `<template>` 태그에 `lang` 속성을 지정해 표준 HTML 대신 Pug 템플릿을 사용할 수 있습니다: `<template lang="pug">`. 이 튜토리얼에서는 표준 HTML을 계속 사용할 예정이지만, 이렇게 Pug 등의 다른 템플릿을 사용할 수도 있다는 걸 소개해보았습니다.

`<script>`는 컴포넌트 화면에 표시되지 않는 모든 로직을 포함하고 있습니다. 가장 중요한 것은 `<script>` 태그 안에 반드시 기본으로 export되는 (하단 코드의 export default 구문 참고) JS 오브젝트가 있어야 한다는 것입니다. 이 오브젝트에서 로컬 컴포넌트 등록, 컴포넌트 인풋(props) 정의, 로컬 상태 관리, 메서드 정의 등 작업이 이루어집니다. 빌드 단계에서 이 오브젝트가 처리되고, 템플릿과 함께 `render()` 함수를 통해 Vue 컴포넌트로 변환됩니다.

`App.vue`를 살펴보면, 현재 default export 오브젝트는 컴포넌트 이름을 `app`으로 설정하고 `components` 속성에 `HelloWorld` 컴포넌트를 등록합니다. 이런 식으로 컴포넌트를 등록하면 로컬 컴포넌트가 됩니다. 이렇게 로컬로 등록된 컴포넌트는 이 컴포넌트를 등록한 상위 컴포넌트 내부에서만 사용할 수 있으므로, 각각의 컴포넌트 파일에서 필요한 컴포넌트를 임포트하고 등록해야 합니다. 앱의 모든 페이지가 모든 컴포넌트를 사용하는 것은 아니기 때문에, 번들 분할/트리 쉐이킹에 유용한 기능입니다.

```js
import HelloWorld from "./components/HelloWorld.vue";

export default {
  name: "app",
  components: {
    //You can register components locally here.
    HelloWorld,
  },
};
```

**참고:** TypeScript 문법을 사용하고 싶다면, `<script>` 태그 안에 `lang` 속성을 설정해 컴파일러에게 TypeScript를 사용하고 있다고 알려주세요: `<script lang="ts">`

`<style>` 에는 컴포넌트에서 사용할 CSS를 포함합니다. `<style scoped>`와 같이 `scoped` 속성을 추가하면 Vue는 그 안의 내용을 SFC(단일 파일 컴포넌트) 내부 범위에서만 적용합니다. CSS-in-JS 방식과 비슷하게 동작하지만, 여기서는 일반 CSS 구문을 작성할 수 있다는 차이점이 있습니다.

**참고:** CLI로 프로젝트를 생성할 때 CSS 전처리기를 선택했다면, 빌드타임에 Webpack에서 내용을 처리할 수 있도록 `<style>` 태그에 `lang` 속성을 추가할 수 있습니다. 예를 들어 `<style lang="scss">` 라고 작성하면 스타일링 정보에 SCSS 문법을 사용할 수 있습니다.

로컬에서 앱 실행하기
-----------

Vue CLI는 개발 서버를 내장하고 있습니다. 앱을 로컬로 실행할 수 있기 때문에, 직접 서버를 구성할 필요 없이 쉽게 테스트해볼 수 있습니다. 프로젝트의 `package.json` 파일을 열어보면, CLI가 npm 스크립트로 `serve` 명령어를 추가해놓은 것을 확인할 수 있습니다.

터미널을 열고, `npm run serve` (yarn을 사용한다면 `yarn serve`)를 실행해보세요. 아래 내용이 나타날 것입니다.
```

INFO  Starting development server...
98% after emitting CopyPlugin

 DONE  Compiled successfully in 18121ms

  App running at:
  - Local:   <http://localhost:8080/>
  - Network: <http://192.168.1.9:8080/>

  Note that the development build is not optimized.
  To create a production build, run npm run build.
```

브라우저 탭을 열고 위의 "local" 주소로 (위 내용처럼 `http://localhost:8080`이거나 설정에 따라 다를 수 있음) 이동해보면, 아래 사진과 같은 앱이 나타날 것입니다. 환영 메시지, Vue 문서 링크, 우리가 선택한 CLI 플러그인 링크, 기타 유용한 Vue 커뮤니티와 생태계 링크 등이 표시되고 있습니다.

![default vue app render, with vue logo, welcome message, and some documentation links](/ko/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/Vue_getting_started/vue-default-app.png)

약간의 변경 사항 적용하기
--------------

일단 Vue 로고를 제거합시다. `App.vue` 파일을 열고, 템플릿 섹션에서 `<img>` 요소를 지워보세요.

```markup
<img alt="Vue logo" src="./assets/logo.png" />
```

서버가 아직 실행중이라면, 거의 즉시 로고가 제거되는 것을 확인할 수 있을 것입니다. 템플릿 섹션에서 `HelloWorld` 컴포넌트도 삭제해봅시다.

가장 먼저 아래 라인을 지워주세요.

```markup
<HelloWorld msg="Welcome to Your Vue.js App" />
```

이제 `App.vue` 파일을 저장해보면 `HelloWorld` 컴포넌트를 등록했으나 사용하지 않았다는 에러가 발생할 것입니다. 해당 컴포넌트를 임포트하고 등록하는 코드 역시 지워야 합니다.

이제 아래 라인도 삭제해보세요.

```js
import HelloWorld from "./components/HelloWorld.vue";
```

```js
components: {
  HelloWorld;
}
```

이제 더는 에러가 발생하지 않는 것을 확인할 수 있습니다. 다만 `<template>` 파트에 표시할 내용이 없기 때문에 빈 페이지만 보일 것입니다.

그럼 이제 `<div id="app">` 안에 `<h1>` 요소를 추가해보세요. 우리는 todo list 앱을 만들 거니까, 헤더 텍스트를 "To-Do List"라고 작성해봅시다.

```markup
<template>
  <div id="app">
    <h1>To-Do List</h1>
  </div>
</template>
```

`App.vue` 는 우리가 원했던 대로 'To-Do List'라는 내용의 헤더를 표시할 것입니다!

정리
--

우리는 이 장에서 Vue에 숨겨진 몇 가지 아이디어를 배우고, 우리의 예제 애플리케이션의 기초 구조를 만들고, 이를 살펴보고, 몇 가지 변경 사항을 적용해보았습니다.

이제 기본적인 소개는 끝났으니, 좀 더 나아가서 우리의 샘플 애플리케이션인 To-Do List 앱을 구현해봐야겠죠? 할 일 목록을 저장하고 일을 완료하면 체크하는 기능, 모든 아이템/완료된 아이템/아직 완료되지 않은 아이템을 필터링할 수 있는 기능을 구현해야 합니다.

다음 글에서는 우리의 첫 커스텀 컴포넌트를 작성하고, props 전달 및 데이터 상태 저장과 같은 몇 가지 중요한 개념을 살펴보겠습니다.

* 이전  (en-US)
* Overview: Client-side JavaScript frameworks
* 다음  (en-US)


React 상호 작용: 이벤트 및 상태 - Web 개발 학습하기

React 상호 작용: 이벤트 및 상태
=====================

* 이전
* Overview: Client-side JavaScript frameworks
* 다음

컴포넌트 계획이 완료되었으므로, 이제 애플리케이션을 완전히 정적인 UI에서 실제로 상호 작용하고 변경할 수 있는 애플리케이션으로 업데이트할 때입니다. 이 글에서는 이벤트와 상태를 파헤쳐 작업을 성공적으로 추가 및 삭제하고, 완료된 작업을 토글할 수 있는 애플리케이션으로 끝낼 것입니다.

|  |  |
| --- | --- |
| 전제조건: |
HTML,
 CSS, 및
 JavaScript 언어 핵심에 대한 친숙도,
 터미널/커멘드 라인 (en-US)에 대한 지식.
  |
| 목적: | React에서 이벤트 및 상태를 처리하는 방법을 배우고, 이를 사용해 사례 연구 애플리케이션을 대화형으로 만들기 시작합니다. |

이벤트 처리
------

지금까지 기본 JavaScript만 작성했다면, 일부 DOM 노드를 쿼리하고 여기에 이벤트 수신기를 연결하는, 별도의 JavaScript 파일이 있는 형태가 익숙할 수 있습니다.

```jsx
const btn = document.querySelector("button");

btn.addEventListener("click", () => {
  alert("hi!");
});
```

React에서는, 다음과 같이 JSX의 요소에 직접 이벤트 처리기를 작성합니다.

```jsx
<button type="button" onClick={() => alert("hi!")}>
 안녕하세요!
</button>
```

**참고:** 이는 HTML에서 인라인 이벤트 처리기를 사용하지 말라고 조언하는 경향이 있는 모범사례 조건과 관련하여 직관에 반하는 것처럼 보일 수 있지만, JSX는 실제로 JavaScript의 일부라는 점을 기억하세요.

위의 예에서는, `<button>` 요소에 `onClick` props을 추가하고 있습니다. 해당 props의 값은 간단한 경고를 트리거하는 함수입니다.

여기서 `onClick` props은 특별한 의미가 있습니다. 사용자가 버튼을 클릭할 때 주어진 기능을 실행하도록 React에 지시합니다. 참고해야 할 몇 가지 다른 사항이 있습니다.

* `onClick`의 camel-cased 특성이 중요합니다. JSX는 `onclick`을 인식하지 않습니다(다시 말하지만 JavaScript에서 특정 목적을 위해 이미 사용되고 있습니다. 표준 `onclick` 처리기 속성)과 관련은 있지만 다릅니다.
* 모든 브라우저 이벤트는 JSX에서 `on` 뒤에 이벤트 이름이 오는 형식을 따릅니다.

`Form.js` 컴포넌트에서 시작하여, 이것을 애플리케이션에 적용해 보겠습니다.

### form 제출 처리

`Form()` 컴포넌트 함수의 맨 위에, `handleSubmit()`이라는 함수를 만듭니다. 이 함수는 `submit` 이벤트의 기본 동작을 방지해야 합니다. 그런 다음 `alert()`를 트리거하여 원하는 대로 말할 수 있습니다. 결과는 다음과 같아야 합니다.

```jsx
function handleSubmit(e) {
  e.preventDefault();
  alert("Hello, world!");
}
```

이 함수를 사용하려면, `<form>` 요소에 `onSubmit` 속성을 추가하고, 해당 값을 `handleSubmit` 함수로 설정합니다.

```jsx
<form onSubmit={handleSubmit}>
```

이제 브라우저로 돌아가서 "추가" 버튼을 클릭하면, "Hello, world!", 또는 원하는 대로 입력한 내용이 경고 대화 상자에 표시될 겁니다.

콜백 props
--------

React 애플리케이션에서, 상호 작용은 하나의 컴포넌트에만 국한되는 경우가 거의 없습니다. 한 컴포넌트에서 발생하는 이벤트는 애플리케이션의 다른 부분에 영향을 미칩니다. 새로운 작업을 만들 수 있는 권한을 부여하기 시작하면 `<Form />` 컴포넌트에서 발생하는 일이 `<App />`에서 렌더링된 목록에 영향을 미칩니다.

우리는 `handleSubmit()` 함수가 궁극적으로 새 작업을 만드는 데 도움이 되기를 원하므로, `<Form />`에서 `<App />`로 정보를 전달하는 방법이 필요합니다. 표준 props를 사용하여 부모에서 자식으로 데이터를 전달하는 것과 같은 방식으로 자식에서 부모로 데이터를 전달할 수 없습니다. 대신 양식의 일부 데이터를 입력으로 기대하는 함수를 `<App />`에 작성한 다음 해당 함수를 props으로 `<Form />`에 전달할 수 있습니다. 이 props로서의 함수를 콜백 props이라고 합니다. 콜백 props이 있으면, `<Form />` 내부에서 호출하여 `<App />`에 올바른 데이터를 보낼 수 있습니다.

### 콜백을 통한 form 제출 처리

`App()` 컴포넌트 함수의 맨 위에 `name`이라는 단일 매개변수가 있는 `addTask()`라는 함수를 만듭니다.

```jsx
function addTask(name) {
  alert(name);
}
```

다음으로, `addTask()`를 `<Form />`에 prop으로 전달합니다. prop에는 원하는 이름을 지정할 수 있지만, 나중에 이해할 수 있는 이름을 선택해주세요. 함수의 이름과 함수가 수행할 작업을 일치시키는 의미에서 `addTask`도 좋습니다. `<Form />` 컴포넌트 호출은 다음과 같이 업데이트되어야 합니다.

```jsx
<Form addTask={addTask} />
```

마지막으로, `<Form />` 컴포넌트의 `handleSubmit()` 함수 내에서 이 props를 사용할 수 있습니다! 다음과 같이 업데이트하세요.

```jsx
function handleSubmit(e) {
  e.preventDefault();
  props.addTask("Say hello!");
}
```

브라우저에서 "추가" 버튼을 클릭하면 `addTask()` 콜백 함수가 작동한다는 것을 증명할 수 있지만, 입력 필드에 무엇을 입력하고 있는지 알려주는 알림을 받을 수 있다면 좋을 것입니다! 이것이 우리가 다음에 할 일입니다.

**참고:** 콜백 props의 이름을 `addTask`로 지정하여 props이 무엇을 하는지 쉽게 이해할 수 있도록 했습니다. React 코드에서 접할 수 있는 또 다른 일반적인 규칙은 콜백 props 이름에 `on`이라는 단어를 접두사로 붙인 다음, 실행시킬 이벤트 이름을 붙이는 것입니다. 예를 들어, 우리는 form에 `addTask` 값을 가진 `onSubmit` props을 줄 수 있습니다.

상태와 `useState` hook
-------------------

지금까지, 컴포넌트를 통해 데이터를 전달하기 위해 props를 사용했고 이 정도로도 충분했습니다. 그러나 이제 사용자 입력 및 데이터 업데이트를 처리해야 하기에 더 많은 것이 필요합니다.

우선, props는 컴포넌트의 부모에서 가져옵니다. 우리의 `<Form />`은 우리 작업의 새 이름을 물려받지 않을 것입니다. 우리의 `<input />` 요소는 `<Form />` 바로 안에 있으므로, `<Form />`은 새 이름을 만드는 일을 직접 담당합니다. 우리는 `<Form />`에게 자발적으로 자체 props을 생성하도록 요청할 수 없지만, 우리를 위해 자체 데이터 중 일부를 추적하도록 요청할 수 있습니다. 이와 같이, 컴포넌트 자체가 소유한 데이터를 **상태**라고 합니다. 상태는 컴포넌트가 상태를 소유할 뿐만 아니라, 나중에 업데이트할 수 있기 때문에 React의 또 다른 강력한 도구입니다. 컴포넌트가 받는 props를 업데이트하는 것은 불가능합니다. props를 읽을 수만 있습니다.

React는 상태처럼, 컴포넌트에 새로운 기능을 제공할 수 있는 다양한 특수 함수를 제공합니다. 이러한 함수를 **hook**이라고 하며, `useState` hook은 이름에서 알 수 있듯이, 컴포넌트에 상태를 부여하기 위해 정확히 필요한 것입니다.

React hook을 사용하려면, React 모듈에서 가져와야 합니다. `Form.js`에서 첫 번째 줄을 다음과 같이 변경합니다.

```jsx
import React, { useState } from "react";
```

이렇게 하면 `useState()` 함수를 자체적으로 가져올 수 있으며, 이 파일의 어디에서나 사용할 수 있습니다.

`useState()`는 컴포넌트에 대한 상태 조각을 생성하고, 유일한 매개변수는 해당 상태의 초기값을 결정합니다. 상태와 나중에 상태를 업데이트하는데 사용할 수 있는 함수, 두 개를 반환합니다.

한 번에 받아들이기에는 양이 많아, 시도해 봅시다. 우리는 자신을 `name` 상태로 만들고, `name` 상태를 업데이트하는 함수를 만들 것입니다.

`Form()` 내부의 `handleSubmit()` 함수 위에 다음을 작성합니다.

```jsx
const [name, setName] = useState("Use hooks!");
```

이 코드 줄에서 무슨 일이 일어나고 있나요?

* 초기 `name` 값을 "Use hooks!"로 설정하고 있습니다.
* `setName()`이라고 불리는 `name`을 수정하는 일을 하는 함수를 정의하고 있습니다.
* `useState()`는 이 두 가지를 반환하므로, 배열 비구조화을 사용하여 두 가지를 별도의 변수로 캡처합니다.
### 상태 읽기

바로 실행 중인 `name` 상태를 볼 수 있습니다. form 입력에 `value` props을 추가하고, 해당 값을 `name`으로 설정합니다. 브라우저는 input 안에 "Use hooks!"를 렌더링합니다.

```jsx
<input
 type="text"
 id="new-todo-input"
 className="input input\_\_lg"
 name="text"
 autoComplete="off"
 value={name}
/>
```

초기 상태는 빈 문자열이면 좋을 거 같습니다. "Use hooks!"를 빈 문자열로 변경해보세요.

```jsx
const [name, setName] = useState("");
```

### 사용자 입력 읽기

`name`의 값을 변경하기 전에, 사용자의 실시간 입력을 받아와야 합니다. 이를 위해, `onChange` 이벤트를 수신할 수 있습니다. `handleChange()` 함수를 작성하고 `<input />` 태그에서 입력을 받아봅시다.

```jsx
// `Form` 컴포넌트 상단 부근
function handleChange(e) {
  console.log("Typing!");
}

// return 문 아래로
<input
 type="text"
 id="new-todo-input"
 className="input input\_\_lg"
 name="text"
 autoComplete="off"
 value={name}
 onChange={handleChange}
/>;
```

현재, 입력값은 입력할 때 변경되지 않지만, 브라우저는 "Typing!"이라는 단어를 JavaScript 콘솔에 기록하므로, 이벤트 수신기가 입력에 연결되어 있음을 알 수 있습니다. `handleChange()` 함수를 사용하여 `name` 상태를 업데이트해야 합니다.

변경된 입력 필드의 내용을 읽으려면 입력의 `value` props에 접근하면 됩니다. `handleChange()` 내부에서도 `e.target.value`를 읽어올 수 있습니다. `e.target`은 `change` 이벤트를 발생시킨 요소를 나타냅니다. 이것이 우리의 입력입니다. 따라서 `value`는 그 안에 있는 텍스트입니다.

`console.log()`로 이 값을 브라우저의 콘솔에서 볼 수 있습니다.

```jsx
function handleChange(e) {
  console.log(e.target.value);
}
```

### 상태 업데이트

로깅만으로는 충분하지 않습니다. 입력 값이 변경될 때 이름의 업데이트된 상태를 실제로 저장하고 싶습니다! 아래와 같이 `console.log()`를 `setName()`으로 변경합니다.

```jsx
function handleChange(e) {
  setName(e.target.value);
}
```

이제 이름을 인수로 사용하여 `props.addTask`를 호출하도록 `handleSubmit()` 함수를 변경해야 합니다. 콜백 props을 기억하십니까? 이것은 작업을 `App` 컴포넌트로 다시 보내는 역할을 하므로, 나중에 작업 목록에 추가할 수 있습니다. 모범 사례로서 form이 제출된 후 입력을 지워야 하므로 빈 문자열을 사용하여 `setName()`을 다시 호출합니다.

```jsx
function handleSubmit(e) {
  e.preventDefault();
  props.addTask(name);
  setName("");
}
```

마지막으로, 브라우저의 input 필드에 무언가를 입력하고 "추가"를 클릭하면 입력한 내용이 경고 대화 상자에 나타납니다.

이제 `Form.js` 파일은 다음과 같아야 합니다.

```jsx
import React, { useState } from "react";

function Form(props) {
  const [name, setName] = useState("");

  function handleChange(e) {
    setName(e.target.value);
  }

  function handleSubmit(e) {
    e.preventDefault();
    props.addTask(name);
    setName("");
  }
  return (
    <form onSubmit={handleSubmit}>
 <h2 className="label-wrapper">
 <label htmlFor="new-todo-input" className="label\_\_lg">
 What needs to be done?
 </label>
 </h2>
 <input
 type="text"
 id="new-todo-input"
 className="input input\_\_lg"
 name="text"
 autoComplete="off"
 value={name}
 onChange={handleChange}
 />
 <button type="submit" className="btn btn\_\_primary btn\_\_lg">
 Add
 </button>
 </form>
  );
}

export default Form;
```

**참고:** 작업 이름을 입력하지 않고 추가 버튼을 누르기만 하면 비어있는 작업을 제출할 수 있다는 사실을 알게 될 것입니다. 빈 작업이 추가되지 않도록 하는 방법을 생각할 수 있나요? 힌트로, `handleSubmit()` 함수에 일종의 검사를 추가해야 할 수도 있습니다.

종합 : 작업 추가
----------

이벤트, 콜백 props 및 hook으로 연습했으므로 이제 사용자가 브라우저에서 새 작업을 추가할 수 있는 기능을 작성할 준비가 되었습니다.

### 상태로서의 작업

`useState`를 `App.js`로 가져와 작업을 상태로 저장할 수 있습니다. `React` 가져오기 라인을 다음과 같이 업데이트하세요.

```jsx
import React, { useState } from "react";
```

우리는 `props.tasks`를 `useState()` hook에 전달하려고 합니다. 이것은 초기 상태를 보존할 것입니다. `App()` 함수 정의 맨 위에 다음을 추가하세요.

```jsx
const [tasks, setTasks] = useState(props.tasks);
```

이제 `props.tasks` 대신 `tasks` 매핑의 결과가 되도록 `taskList` 매핑을 변경할 수 있습니다. 이제 `taskList` 상수 선언은 다음과 같아야 합니다.

```jsx
const taskList = tasks.map((task) => (
  <Todo
 id={task.id}
 name={task.name}
 completed={task.completed}
 key={task.id}
 />
));
```

### 작업을 추가

이제 `addTask()` 함수에서 작업 목록을 업데이트하는 데 사용할 수 있는 `setTasks` hook이 있습니다. 그러나 한 가지 문제가 있습니다. `addTask()`의 `name` 인수를 `setTasks`로 전달할 수 없습니다. `tasks`는 객체의 배열이고 `name`은 문자열이기 때문입니다. 이렇게 하면, 배열이 문자열로 대체됩니다.

먼저, 기존 작업과 동일한 구조를 가진 객체에 `name`을 넣어야 합니다. `addTask()` 함수 내부에서 `newTask` 객체를 만들어 배열에 추가합니다.

그런 다음 이 새 작업이 추가된 새 배열을 만든 다음 작업 데이터의 상태를 이 새 상태로 업데이트해야 합니다. 이를 위해, 전개 구문을 사용하여 기존 배열을 복사하고 끝에 객체를 추가할 수 있습니다. 그런 다음 이 배열을 `setTasks()`에 전달하여 상태를 업데이트합니다.

모두 종합하면, `addTask()` 함수는 다음과 같아야 합니다.

```jsx
function addTask(name) {
  const newTask = { id: "id", name, completed: false };
  setTasks([...tasks, newTask]);
}
```

이제 브라우저를 사용하여 데이터에 작업을 추가할 수 있습니다! 양식에 무엇이든 입력하고 "추가"를 클릭하면(또는 `Enter` 키를 누르면) UI에 새 할 일 항목이 표시됩니다!

**그러나, 또 다른 문제가 있습니다** `addTask()` 함수는 각 작업에 동일한 `id`를 부여합니다. 이는 접근성에 좋지 않으며, React가 `key` props으로 향후 작업을 구분하는 것을 불가능하게 만듭니다. 실제로 React는 DevToos 콘솔에 "경고: 같은 키를 가진 두 자식이 발생했습니다..."라는 경고를 표시합니다.

이 문제를 해결해야 합니다. 고유한 식별자를 만드는 것은 어려운 문제입니다. JavaScript 커뮤니티에서 유용한 라이브러리를 작성했습니다. 작고 작동하기 때문에 nanoid를 사용할 것입니다.

애플리케이션의 루트 디렉터리에 있는지 확인하고 다음 터미널 명령을 실행합니다.

```bash
npm install nanoid
```

**참고:** yarn을 사용하는 경우, 대신 `yarn add nanoid`가 필요합니다.

이제 `nanoid`를 `App.js` 상단으로 가져올 수 있으므로 이를 사용하여 새 작업에 대한 고유 ID를 만들 수 있습니다. 우선 `App.js` 상단에 다음 구문을 추가합니다.

```jsx
import { nanoid } from "nanoid";
```

이제 `addTask()`를 업데이트하여 각 작업 ID가 접두사 `todo-`와 nanoid에 의해 생성된 고유한 문자열이 되도록 합시다. `newTask` 상수 선언을 다음과 같이 업데이트하세요.

```jsx
const newTask = { id: `todo-${nanoid()}`, name, completed: false };
```

모든 것을 저장하고, 애플리케이션을 다시 시도하세요. 이제 중복 ID에 대한 경고 없이 작업을 추가할 수 있습니다.

우회: 작업 개수 세기
------------

새 작업을 추가해보면 문제가 있음을 알 수 있습니다. 실제 작업 수에 관계없이, 머리글에 남은 작업이 3개로 표시됩니다! `taskList`의 길이를 세고 이에 따라 제목의 텍스트를 변경하여 이 문제를 해결할 수 있습니다.

return 문 앞에, `App()` 정의 내부에 다음을 추가합니다.

```jsx
const headingText = `${taskList.length} tasks remaining`;
```

목록에 단일 작업이 포함된 경우, 제목에 여전히 "작업"이라는 단어가 사용된다는 점을 제외하면, 거의 맞습니다. 이것도 변수로 만들 수 있습니다. 방금 추가한 코드를 다음과 같이 업데이트합니다.

```jsx
const tasksNoun = taskList.length !== 1 ? "tasks" : "task";
const headingText = `${taskList.length} ${tasksNoun} remaining`;
```

이제 목록 제목의 텍스트 콘텐츠를 `headingText` 변수로 바꿀 수 있습니다. 다음과 같이 `<h2>`를 업데이트하세요.

```jsx
<h2 id="list-heading">{headingText}</h2>
```

작업 완료
-----

체크박스를 클릭하면, 적절하게 선택 및 선택 취소되는 것을 알 수 있습니다. HTML의 기능으로 브라우저는 우리의 도움 없이 체크박스 입력이 선택되거나 선택 해제된 것을 기억하는 방법을 알고 있습니다. 그러나 이 기능은 문제를 보이지 않게 합니다. 체크박스를 전환해도 React 응용프로그램의 상태가 변경되지 않습니다. 이는 브라우저와 애플리케이션이 이제 동기화되지 않았음을 의미합니다. 브라우저를 애플리케이션과 다시 동기화하려면 자체 코드를 작성해야 합니다.

### 버그 증명

문제를 해결하기 전에, 문제가 발생하는 것을 관찰해 봅시다.

`App()` 컴포넌트에 `toggleTaskCompleted()` 함수를 작성하는 것으로 시작하겠습니다. 이 함수에는 `id` 매개변수가 있지만, 아직 사용하지 않을 것입니다. 지금은 배열의 첫 번째 작업을 콘솔에 기록합니다. 브라우저에서 선택하거나 선택 취소할 때 어떤 일이 발생하는지 검사할 것입니다.

`taskList` 상수 선언 바로 위에 다음을 추가하세요.

```jsx
function toggleTaskCompleted(id) {
  console.log(tasks[0]);
}
```

다음으로, `taskList` 내부에 렌더링된 각 `<Todo />` 컴포넌트의 props에 `toggleTaskCompleted`를 추가합니다. 다음과 같이 업데이트하세요.

```jsx
const taskList = tasks.map((task) => (
  <Todo
 id={task.id}
 name={task.name}
 completed={task.completed}
 key={task.id}
 toggleTaskCompleted={toggleTaskCompleted}
 />
));
```

다음으로, `Todo.js` 컴포넌트로 이동하여 `<input />` 요소에 `onChange` 처리기를 추가합니다. 이 처리기는 익명 함수를 사용하여 `props.id` 매개변수로 `props.toggleTaskCompleted()`를 호출해야 합니다.

`<input />`은 이제 다음과 같아야 합니다.

```jsx
<input
 id={props.id}
 type="checkbox"
 defaultChecked={props.completed}
 onChange={() => props.toggleTaskCompleted(props.id)}
/>
```

모든 것을 저장하고 브라우저로 돌아가 첫 번째 작업인 Eat가 선택되었는지 확인합니다. JavaScript 콘솔을 연 다음 Eat 옆의 체크박스를 클릭합니다. 예상대로 체크가 해제됩니다. 그러나 JavaScript 콘솔은 다음과 같은 내용을 기록합니다.
```

Object { id: "task-0", name: "Eat", completed: true }
```

브라우저에서 확인란의 선택이 취소되지만, 콘솔에서는 Eat가 아직 완료되었다고 알려줍니다. 이제 수정해보겠습니다!

### 브라우저와 데이터 동기화

`App.js`에서 `toggleTaskCompleted()` 함수를 다시 살펴보겠습니다. 토글된 작업의 `completed` props만 변경하고, 나머지는 그대로 두기를 원합니다. 이를 위해 작업 목록에 대해 `map()`을 실행하고 완료한 작업을 변경합니다.

`toggleTaskCompleted()` 함수를 다음과 같이 업데이트합니다.

```jsx
function toggleTaskCompleted(id) {
  const updatedTasks = tasks.map((task) => {
    // if this task has the same ID as the edited task
    if (id === task.id) {
      // use object spread to make a new object
      // whose `completed` props has been inverted
      return { ...task, completed: !task.completed };
    }
    return task;
  });
  setTasks(updatedTasks);
}
```

여기에서, 원래 `tasks` 배열에 매핑되는 `updatedTasks` 상수를 정의합니다. 작업의 `id` props이 함수에 제공된 `id`와 일치하는 경우, 객체 전개 문법을 사용하여 새 객체를 만들고, 반환하기 전에 해당 객체의 `completed` props을 토글합니다. 일치하지 않으면 원래 객체를 반환합니다.

그런 다음 상태를 업데이트하기 위해 이 새로운 배열로 `setTasks()`를 호출합니다.

작업 삭제
-----

작업을 삭제하는 것은 완료된 상태를 토글하는 것과 유사한 패턴을 따릅니다. 상태를 업데이트하기 위한 함수를 정의한 다음, 해당 함수를 props으로 `<Todo />`에 전달하고 올바른 이벤트가 발생할 때 호출해야 합니다.

### `deleteTask` 콜백 props

여기서는 `App` 컴포넌트에 `deleteTask()` 함수를 작성하는 것으로 시작하겠습니다. `toggleTaskCompleted()`와 마찬가지로, 이 함수는 `id` 매개변수를 사용하며, 해당 `id`를 콘솔에 기록하여 시작할 것입니다. `toggleTaskCompleted()` 아래에 다음을 추가합니다.

```jsx
function deleteTask(id) {
  console.log(id);
}
```

다음으로, `<Todo />` 컴포넌트 배열에 다른 콜백 props을 추가합니다.

```jsx
const taskList = tasks.map((task) => (
  <Todo
 id={task.id}
 name={task.name}
 completed={task.completed}
 key={task.id}
 toggleTaskCompleted={toggleTaskCompleted}
 deleteTask={deleteTask}
 />
));
```

`Todo.js`에서 "삭제" 버튼을 눌렀을 때 `prop.deleteTask()`를 호출하려고 합니다. `deleteTask()`는 자신을 호출한 작업의 ID를 알아야, 상태에서 올바른 작업을 삭제할 수 있습니다.

다음과 같이 `Todo.js` 내부의 "삭제" 버튼을 업데이트합니다.

```jsx
<button
 type="button"
 className="btn btn\_\_danger"
 onClick={() => props.deleteTask(props.id)}>
 Delete <span className="visually-hidden">{props.name}</span>
</button>
```

이제 애플리케이션 "삭제" 버튼을 클릭하면 브라우저 콘솔이 관련 작업이 ID를 기록해야 합니다.

상태 및 UI에서 작업 삭제
---------------

이제 `deleteTask()`가 올바르게 호출되었음을 알았으므로, `deleteTask()`에서 `setTasks()` hook을 호출하여 애플리케이션 UI에서 시각적으로 뿐만 아니라 애플리케이션 상태에서 해당 작업을 실제로 삭제할 수 있습니다. `setTasks()`는 배열을 인수로 예상하므로 `deleteTask()`에 전달된 ID와 ID가 일치하는 작업을 제외하여 기존 작업을 복사하는 새 배열을 제공해야 합니다.

이것은 `Array.prototype.filter()`를 사용할 수 있는 절호의 기회입니다. 각 작업을 테스트하고 `id` props가 `deleteTask()`에 전달된 `id` 인수와 일치하는 경우 새 배열에서 작업을 제외할 수 있습니다.

다음과 같이 `App.js` 파일 내에서 `deleteTask()` 함수를 업데이트합니다.

```jsx
function deleteTask(id) {
  const remainingTasks = tasks.filter((task) => id !== task.id);
  setTasks(remainingTasks);
}
```

애플리케이션을 다시 사용해 보세요. 이제 애플리케이션에서 작업을 삭제할 수 있습니다!

요약
--

너무 길어질 수 있으니 이번 글은 여기서 마치겠습니다. 이 글을 통해 React가 이벤트를 처리하고 상태를 처리하는 방법, 작업을 추가하고 삭제하고 완료된 작업을 토글하는 기능을 구현하는 방법을 알려드렸습니다. 거의 다 왔습니다. 다음 문서에서는 기존 작업을 편집하고 모든 작업, 완료된 작업 및 완료되는 않은 작업 간에 작업 목록을 필터링하는 기능을 구현합니다. 그 과정에서 조건부 UI렌더링을 살펴보겠습니다.

* 이전
* Overview: Client-side JavaScript frameworks
* 다음


React todo list 시작하기 - Web 개발 학습하기

React todo list 시작하기
====================

* 이전
* Overview: Client-side JavaScript frameworks
* 다음

여러분이 React로 개념 증명을 만드는, 곧 사용자가 원하는 작업을 추가, 편집, 그리고 삭제할 수 있으며, 작업을 삭제하지 않고 완료로 표시할 수도 있는 앱을 만드는 작업을 맡게 되었다고 가정해 보겠습니다. 이 기사는 기본 `App` 컴포넌트 구조와 스타일링을 설정하는 방법에 대해서 다루며, 여러분은 개별 컴포넌트 정의와 상호작용성을 배울 준비를 갖추게 될 겁니다.

**참고:** 여러분의 코드를 우리의 것과 비교하고 싶다면 todo-react repository 에서 최종적인 버전의 샘플 React 앱 코드를 확인해보세요. 실행 중인 라이브 버전은 https://mdn.github.io/todo-react-build/ 에서 볼 수 있습니다.

|  |  |
| --- | --- |
| 필요한 사전 지식: | 코어 HTML, CSS, 및 JavaScript 언어에 익숙할 것. 터미널/커맨드라인 (en-US)에 대한 지식. |
| 목표: | 투두 리스트를 만들고 기본 `App` 구조와 스타일링을 설정한다. |

우리가 만들 애플리케이션의 유저 스토리
---------------------

소프트웨어 개발에서 유저 스토리(user story)는 사용자의 관점에서 실행 가능한 목표를 뜻합니다. 개발을 시작하기 전에 유저 스토리를 정의하는 것은 매우 도움이 됩니다. 우리의 애플리케이션은 다음과 같은 스토리를 만족해야 합니다:

나는 사용자로서, 다음의 것들을 할 수 있다:

* 작업 목록 읽기
* 마우스나 키보드로 작업 추가하기
* 마우스나 키보드를 사용하여 작업을 완료로 표시하기
* 마우스나 키보드를 사용하여 작업 삭제하기
* 마우스나 키보드를 사용하여 작업 편집하기
* 작업을 특정한 집합으로 나누어 보기: 전체 작업, 진행 중인 작업, 혹은 완료된 작업들.

이 스토리들을 하나하나 다뤄보겠습니다.

프로젝트를 실행하기 전 점검하기
-----------------

create-react-app은 우리의 프로젝트에서 전혀 사용하지 않을 파일을 몇 개 만듭니다.

* 컴포넌트마다 스타일 시트를 만들지 않을 것이므로, 우선 `App.js`의 상단에 `App.css`를 임포트하고 있는 문을 지웁니다.
* `logo.svg` 파일 역시 사용하지 않을 것이므로, 이것을 임포트하고 있는 문 역시 삭제합니다.

그러고 나서 필요하지 않은 파일들을 삭제하기 위해 터미널에 아래의 커맨드들을 복사, 붙여넣기 합니다. 애플리케이션의 루트 디렉토리에서 터미널을 실행하고 있는지 확인하세요!

```shell
# 프로젝트의 src 디렉토리로 이동
cd src
# 파일들을 삭제
rm -- App.test.js App.css logo.svg serviceWorker.js setupTests.js
# 프로젝트의 루트 디렉토리로 다시 돌아오기
cd ..
```

참고:

* 삭제하려는 파일 중 두 개는 애플리케이션을 테스트하기 위해 사용됩니다. 이 기사에서는 테스팅에 대해 다루지 않습니다.
* 위의 터미널 작업을 수행하기 위해 서버를 멈추었다면, `npm start`를 사용하여 다시 시작해야 합니다.
프로젝트 시작 코드
----------

프로젝트를 시작하기 위해 여러분이 지금 가지고 있는 것을 대신할 `App()` 함수와 애플리케이션을 꾸밀 CSS를 제공하겠습니다.

### JSX

아래의 스니펫을 복사하고 기존의 `App()` 함수 대신 `App.js`에 붙여넣으세요.

```js
function App(props) {
  return (
    <div className="todoapp stack-large">
      <h1>TodoMatic</h1>
      <form>
        <h2 className="label-wrapper">
          <label htmlFor="new-todo-input" className="label\_\_lg">
            What needs to be done?
          </label>
        </h2>
        <input
          type="text"
          id="new-todo-input"
          className="input input\_\_lg"
          name="text"
          autoComplete="off"
        />
        <button type="submit" className="btn btn\_\_primary btn\_\_lg">
          Add
        </button>
      </form>
      <div className="filters btn-group stack-exception">
        <button type="button" className="btn toggle-btn" aria-pressed="true">
          <span className="visually-hidden">Show </span>
          <span>all</span>
          <span className="visually-hidden"> tasks</span>
        </button>
        <button type="button" className="btn toggle-btn" aria-pressed="false">
          <span className="visually-hidden">Show </span>
          <span>Active</span>
          <span className="visually-hidden"> tasks</span>
        </button>
        <button type="button" className="btn toggle-btn" aria-pressed="false">
          <span className="visually-hidden">Show </span>
          <span>Completed</span>
          <span className="visually-hidden"> tasks</span>
        </button>
      </div>
      <h2 id="list-heading">3 tasks remaining</h2>
      <ul
        role="list"
        className="todo-list stack-large stack-exception"
        aria-labelledby="list-heading">
        <li className="todo stack-small">
          <div className="c-cb">
            <input id="todo-0" type="checkbox" defaultChecked={true} />
            <label className="todo-label" htmlFor="todo-0">
              Eat
            </label>
          </div>
          <div className="btn-group">
            <button type="button" className="btn">
              Edit <span className="visually-hidden">Eat</span>
            </button>
            <button type="button" className="btn btn\_\_danger">
              Delete <span className="visually-hidden">Eat</span>
            </button>
          </div>
        </li>
        <li className="todo stack-small">
          <div className="c-cb">
            <input id="todo-1" type="checkbox" />
            <label className="todo-label" htmlFor="todo-1">
              Sleep
            </label>
          </div>
          <div className="btn-group">
            <button type="button" className="btn">
              Edit <span className="visually-hidden">Sleep</span>
            </button>
            <button type="button" className="btn btn\_\_danger">
              Delete <span className="visually-hidden">Sleep</span>
            </button>
          </div>
        </li>
        <li className="todo stack-small">
          <div className="c-cb">
            <input id="todo-2" type="checkbox" />
            <label className="todo-label" htmlFor="todo-2">
              Repeat
            </label>
          </div>
          <div className="btn-group">
            <button type="button" className="btn">
              Edit <span className="visually-hidden">Repeat</span>
            </button>
            <button type="button" className="btn btn\_\_danger">
              Delete <span className="visually-hidden">Repeat</span>
            </button>
          </div>
        </li>
      </ul>
    </div>
  );
}
```

이제 `public/index.html`을 열고 `<title>` 요소의 텍스트를 `TodoMatic`으로 바꾸세요. 이렇게 하면 우리의 애플리케이션 상단의 `<h1>`과 일치할 겁니다.

```js
<title>TodoMatic</title>
```

브라우저를 새로고침하면, 아래 이미지처럼 보일 거예요.

![todo-matic app, unstyled, showing a jumbled mess of labels, inputs, and buttons](/ko/docs/Learn/Tools_and_testing/Client-side_JavaScript_frameworks/React_todo_list_beginning/unstyled-app.png)

예쁘지 않고 아무런 기능도 갖추지 않았지만, 괜찮습니다! 곧 꾸밀 거니까요. 우선, JSX가 유저 스토리에 어떻게 대응하는지 살펴봅시다:

* 새로운 작업을 쓰기 위한 `<input type="text">` (en-US)와 폼(form)을 제출하기 위한 버튼을 가진 `<form>` 요소가 있습니다.
* 작업을 필터링하기 위한 여러 개의 버튼이 있습니다.
* 작업이 몇 개 남아있는지 알려주는 헤딩(heading)이 있습니다.
* 순서가 매겨지지 않은 세 개의 작업이 있습니다. 각각의 작업은 리스트 아이템(`<li>`)이며, 편집하거나 삭제하기 위한 버튼과 완료를 표시할 체크 박스를 가지고 있습니다.

폼은 작업을 *만들* 수 있게 해줍니다; 버튼들은 작업들을 *필터링할* 수 있게 해줍니다; 헤딩과 리스트는 작업들을 *읽을* 수 있게 해줍니다. 작업을 *편집하는* UI는 현재는 없습니다. 이것도 괜찮습니다 – 나중에 만들 거니까요.

### 접근성 기능

익숙하지 않은 속성을 발견했을 겁니다. 예를 들어:

```js
<button type="button" className="btn toggle-btn" aria-pressed="true">
  <span className="visually-hidden">Show </span>
  <span>all</span>
  <span className="visually-hidden"> tasks</span>
</button>
```

`aria-pressed`은 보조 기술(예: 스크린 리더)에게 하나의 버튼이 두 개의 상태 `pressed`와 `unpressed`로 있을 수 있다는 것을 알려줍니다. `on`과 `off`라고 생각해보세요. 값을 `true`로 설정하는 것은 버튼을 기본적으로 눌린 상태라는 것을 의미합니다.

클래스 `visually-hidden`는 아직 CSS를 연결하지 않았으므로 아직 아무런 이펙트도 없습니다. 제대로 스타일을 설정하면 이 클래스를 가진 모든 요소가 시력이 정상인 사용자에게는 숨겨지며 스크린 리더 사용자에게만 보입니다 — 이 단어들은 시력이 정상인 사용자들에게 필요하지 않기 때문입니다; 단어들은 추가적인 시각적 맥락(visual context)을 가지고 있지 않은 스크린 리더 사용자들에게 버튼이 무엇을 하는지 더 많은 정보를 제공합니다.

아래에서 `<ul>` 요소를 찾을 수 있을 겁니다.

```js
<ul
  role="list"
  className="todo-list stack-large stack-exception"
  aria-labelledby="list-heading"
>
```

`role` 속성은 보조 기술이 태그가 나타내고 있는 요소의 종류가 무엇인지 설명하는 데 도움이 됩니다. `<ul>`은 기본적으로 리스트처럼 취급되지만, 우리가 추가할 스타일은 리스트의 기능성을 해칩니다. `role="list"`는 `<ul>` 요소가 "리스트"의 의미를 회복하도록 합니다. 이것이 왜 필요한지 알고 싶다면, Scott O'Hara's article, "Fixing Lists"을 참고하세요.

`aria-labelledby` 속성은 보조 기술에게 리스트 헤딩을 헤딩 아래의 리스트의 목적을 나타내는 라벨로 사용하고 있음을 알립니다. 이러한 연관을 만드는 것은 리스트가 더욱 정보가 많은 맥락을 가지게 하여, 스크린 리더 사용자가 리스트의 목적을 더 잘 이해할 수 있도록 합니다.

마지막으로, 리스트 항목 안의 라벨과 인풋들은 JSX에 고유한 속성을 가지고 있습니다.

```js
<input id="todo-0" type="checkbox" defaultChecked={true} />
<label className="todo-label" htmlFor="todo-0">
  Eat
</label>
```

`<input/ >`의 `defaultChecked` 속성은 React가 초기에 이 체크박스를 체크하도록 합니다. HTML에서처럼 `checked`를 사용한다면, React는 체크박스에 대한 이벤트를 다루는 것과 관련한 경고를 브라우저 콘솔에 출력할 것입니다. 지금은 크게 걱정하지 마세요 — 나중에 이벤트를 사용할 때 이야기할테니까요.

`htmlFor` 속성은 HTML에서는 `for` 속성과 대응합니다. JSX에서는 `for`을 속성으로 사용하지 않는데, `for`이 예약어(reserved word)이기 때문입니다. 그래서 React는 `htmlFor`을 대신 사용합니다.

참고:

* JSX 속성에 불리언 값들(`true`와 `false`)를 사용하기 위해서는 이 값들을 중괄호로 감싸야 합니다. `defaultChecked="true"`라고 적는다면, `defaultChecked`의 값은 문자열 리터럴인 `"true"`가 될 겁니다. 꼭 기억하세요 — JSX는 JavaScript이지, HTML이 아닙니다!
* 이전의 코드 스니펫에서 사용되었던 `aria-pressed` 속성은 `"true"`라는 값을 가지는데, 이것은 `aria-pressed`가 `checked`가 있는 방식에서 진짜 불리언 속성이 아니기 때문입니다.
### 스타일 적용하기

`src/index.css`에 기존에 있던 것 대신 아래의 CSS 코드를 붙여넣기하세요.

```css
/\* RESETS \*/
\*,
\*::before,
\*::after {
  box-sizing: border-box;
}
\*:focus {
  outline: 3px dashed #228bec;
  outline-offset: 0;
}
html {
  font: 62.5% / 1.15 sans-serif;
}
h1,
h2 {
  margin-bottom: 0;
}
ul {
  list-style: none;
  padding: 0;
}
button {
  border: none;
  margin: 0;
  padding: 0;
  width: auto;
  overflow: visible;
  background: transparent;
  color: inherit;
  font: inherit;
  line-height: normal;
  -webkit-font-smoothing: inherit;
  -moz-osx-font-smoothing: inherit;
  -webkit-appearance: none;
}
button::-moz-focus-inner {
  border: 0;
}
button,
input,
optgroup,
select,
textarea {
  font-family: inherit;
  font-size: 100%;
  line-height: 1.15;
  margin: 0;
}
button,
input {
  overflow: visible;
}
input[type="text"] {
  border-radius: 0;
}
body {
  width: 100%;
  max-width: 68rem;
  margin: 0 auto;
  font:
    1.6rem/1.25 Arial,
    sans-serif;
  background-color: #f5f5f5;
  color: #4d4d4d;
}
@media screen and (min-width: 620px) {
  body {
    font-size: 1.9rem;
    line-height: 1.31579;
  }
}
/\*END RESETS\*/
/\* GLOBAL STYLES \*/
.form-group > input[type="text"] {
  display: inline-block;
  margin-top: 0.4rem;
}
.btn {
  padding: 0.8rem 1rem 0.7rem;
  border: 0.2rem solid #4d4d4d;
  cursor: pointer;
  text-transform: capitalize;
}
.btn.toggle-btn {
  border-width: 1px;
  border-color: #d3d3d3;
}
.btn.toggle-btn[aria-pressed="true"] {
  text-decoration: underline;
  border-color: #4d4d4d;
}
.btn\_\_danger {
  color: #fff;
  background-color: #ca3c3c;
  border-color: #bd2130;
}
.btn\_\_filter {
  border-color: lightgrey;
}
.btn\_\_primary {
  color: #fff;
  background-color: #000;
}
.btn-group {
  display: flex;
  justify-content: space-between;
}
.btn-group > \* {
  flex: 1 1 49%;
}
.btn-group > \* + \* {
  margin-left: 0.8rem;
}
.label-wrapper {
  margin: 0;
  flex: 0 0 100%;
  text-align: center;
}
.visually-hidden {
  position: absolute !important;
  height: 1px;
  width: 1px;
  overflow: hidden;
  clip: rect(1px 1px 1px 1px);
  clip: rect(1px, 1px, 1px, 1px);
  white-space: nowrap;
}
[class\*="stack"] > \* {
  margin-top: 0;
  margin-bottom: 0;
}
.stack-small > \* + \* {
  margin-top: 1.25rem;
}
.stack-large > \* + \* {
  margin-top: 2.5rem;
}
@media screen and (min-width: 550px) {
  .stack-small > \* + \* {
    margin-top: 1.4rem;
  }
  .stack-large > \* + \* {
    margin-top: 2.8rem;
  }
}
.stack-exception {
  margin-top: 1.2rem;
}
/\* END GLOBAL STYLES \*/
.todoapp {
  background: #fff;
  margin: 2rem 0 4rem 0;
  padding: 1rem;
  position: relative;
  box-shadow:
    0 2px 4px 0 rgba(0, 0, 0, 0.2),
    0 2.5rem 5rem 0 rgba(0, 0, 0, 0.1);
}
@media screen and (min-width: 550px) {
  .todoapp {
    padding: 4rem;
  }
}
.todoapp > \* {
  max-width: 50rem;
  margin-left: auto;
  margin-right: auto;
}
.todoapp > form {
  max-width: 100%;
}
.todoapp > h1 {
  display: block;
  max-width: 100%;
  text-align: center;
  margin: 0;
  margin-bottom: 1rem;
}
.label\_\_lg {
  line-height: 1.01567;
  font-weight: 300;
  padding: 0.8rem;
  margin-bottom: 1rem;
  text-align: center;
}
.input\_\_lg {
  padding: 2rem;
  border: 2px solid #000;
}
.input\_\_lg:focus {
  border-color: #4d4d4d;
  box-shadow: inset 0 0 0 2px;
}
[class\*="\_\_lg"] {
  display: inline-block;
  width: 100%;
  font-size: 1.9rem;
}
[class\*="\_\_lg"]:not(:last-child) {
  margin-bottom: 1rem;
}
@media screen and (min-width: 620px) {
  [class\*="\_\_lg"] {
    font-size: 2.4rem;
  }
}
.filters {
  width: 100%;
  margin: unset auto;
}
/\* Todo item styles \*/
.todo {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
.todo > \* {
  flex: 0 0 100%;
}
.todo-text {
  width: 100%;
  min-height: 4.4rem;
  padding: 0.4rem 0.8rem;
  border: 2px solid #565656;
}
.todo-text:focus {
  box-shadow: inset 0 0 0 2px;
}
/\* CHECKBOX STYLES \*/
.c-cb {
  box-sizing: border-box;
  font-family: Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  font-weight: 400;
  font-size: 1.6rem;
  line-height: 1.25;
  display: block;
  position: relative;
  min-height: 44px;
  padding-left: 40px;
  clear: left;
}
.c-cb > label::before,
.c-cb > input[type="checkbox"] {
  box-sizing: border-box;
  top: -2px;
  left: -2px;
  width: 44px;
  height: 44px;
}
.c-cb > input[type="checkbox"] {
  -webkit-font-smoothing: antialiased;
  cursor: pointer;
  position: absolute;
  z-index: 1;
  margin: 0;
  opacity: 0;
}
.c-cb > label {
  font-size: inherit;
  font-family: inherit;
  line-height: inherit;
  display: inline-block;
  margin-bottom: 0;
  padding: 8px 15px 5px;
  cursor: pointer;
  touch-action: manipulation;
}
.c-cb > label::before {
  content: "";
  position: absolute;
  border: 2px solid currentColor;
  background: transparent;
}
.c-cb > input[type="checkbox"]:focus + label::before {
  border-width: 4px;
  outline: 3px dashed #228bec;
}
.c-cb > label::after {
  box-sizing: content-box;
  content: "";
  position: absolute;
  top: 11px;
  left: 9px;
  width: 18px;
  height: 7px;
  transform: rotate(-45deg);
  border: solid;
  border-width: 0 0 5px 5px;
  border-top-color: transparent;
  opacity: 0;
  background: transparent;
}
.c-cb > input[type="checkbox"]:checked + label::after {
  opacity: 1;
}
```

코드를 저장하고 브라우저로 되돌아오면, 이제 애플리케이션은 적당한 스타일을 가지게 될 겁니다.

요약
--

이제 우리의 투두 리스트는 조금 더 진짜 애플리케이션 같아졌습니다! 문제는, 이 앱이 실제로는 아무것도 하지 않는다는 거죠. 다음 챕터에서 해결해봅시다!

* 이전
* Overview: Client-side JavaScript frameworks
* 다음