Svelte 할 일 목록 앱 시작
이제 Svelte에서 작동하는 방식에 대한 기본적인 이해가 있으므로 예제 앱인 할 일 목록을 구축할 수 있습니다. 이 글에서는 먼저 앱의 요구되는 기능을 살펴본 다음 Todos.svelte
컴포넌트를 만들고 정적 마크업과 스타일을 배치하고 이어지는 문서들에서 진행할 할 일 목록 앱의 기능 개발을 시작 할 수 있는 모든 준비를 마칩니다.
우리는 사용자가 작업을 탐색, 추가 및 삭제할 수 있고 작업을 완료로 표시할 수 있기를 바랍니다. 이것이 이 튜토리얼 시리즈에서 개발할 기본 기능이 될 것이며 그 과정에서 몇 가지 고급 개념도 살펴볼 것입니다.
전제사항: |
적어도 HTML, CSS, and JavaScript 언어의 사용에 익숙하기를 권장합니다. 그리고 터미널/커맨트 라인의 사용법에 대해서도 지식이 있어야합니다. 당신은 앱을 컴파일하고 빌드하기 위한 노드와 npm이 설치된 터미널이 필요합니다. |
---|---|
목표: | 어떻게 Svelte 컴포넌트를 만드는 방법을 배우기 위해, 다른 컴포넌트 내부에 렌더링하고, props를 사용하여 데이터를 전달하고 state를 저장합니다. |
함께 코드를 작성해봅시다
Git
Git 레포지토리를 복제하세요(당신이 이미 복제를 한게 아니라면).
git clone https://github.com/opensas/mdn-svelte-tutorial.git
그런 다음 현재 앱의 상태로 이동하기 위해 다음을 실행하세요.
cd mdn-svelte-tutorial/02-starting-our-todo-app
폴더의 내용을 직접 다운로드 할 수도 있습니다.
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에서 시작하세요.
할 일 목록 앱의 기능들
할 일 목록 앱이 준비되면 다음과 같이 표시됩니다.
이 UI를 사용하여 사용자는 다음을 수행할 수 있습니다.
- 작업 찾아보기
- 작업을 삭제하지 않고 완료/보류로 표시
- 작업 제거
- 새 작업 추가
- 상태별로 작업 필터링: 모든 작업, 활성 작업 또는 완료된 작업
- 작업 편집
- 모든 작업을 활성/완료로 표시
- 완료된 모든 작업 제거
첫 번째 컴포넌트 만들기
Todos.svelte
컴포넌트를 생성해 봅시다. 여기에는 할 일 목록이 포함됩니다.
-
새 폴더
src/components
를 만듭니다.참고:
src
폴더 안의 아무 곳에나 컴포넌트를 넣을 수 있습니다. 하지만components
폴더는 따라야하는 규칙으로 인정되고 컴포넌트를 쉽게 찾을 수 있습니다. -
다음 내용을 포함하는
src/components/Todos.svelte
라는 파일을 만드세요.html<h1>Svelte 할 일 목록</h1>
-
public/index.html
에서title
요소를 변경하여 Svelte 할 일 목록라는 텍스트를 포함하도록 합니다.html<title>Svelte 할 일 목록</title>
-
src/App.svelte
파일을 열고 다음의 내용으로 대체하세요.html<script> import Todos from "./components/Todos.svelte"; </script> <Todos />
-
개발 모드에서 Svelte는 컴포넌트에 존재하지 않는 속성을 지정할 때 브라우저 콘솔에 경고를 표시합니다. 이 경우에는
App
내부에서는 사용되지 않지만src/main.js
내부의App
컴포넌트를 인스턴스화할 때 지정되는name
속성이 있습니다. 콘솔은 현재 "<App>이 알 수 없는 속성 '이름'으로 생성되었습니다"라는 메시지를 표시해야 합니다. 이를 제거하려면src/main.js
에서name
속성을 제거하세요. 이제 다음과 같이 표시됩니다.jsimport App from "./App.svelte"; const app = new App({ target: document.body, }); export default app;
이제 테스트 서버 URL을 확인하면 Todos.svelte
컴포넌트가 렌더링되는 것을 볼 수 있습니다.
정적 마크업 추가
당장은 우리는 앱의 정적 마크업 표현으로 시작할 것이므로 앱이 어떻게 보이는지 확인할 수 있습니다. 다음을 복사하여 Todos.svelte
컴포넌트 파일에 붙여넣고 기존 콘텐츠를 대체합니다.
<!-- 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>
렌더링된 것을 다시 확인하면 다음과 같이 표시됩니다.
위의 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>
요소를 찾을 수 있습니다.
<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
컴포넌트에 추가하는 경우.
<h1>스벨트 할 일 목록</h1>
<img height="32" width="88" src="https://www.w3.org/WAI/wcag2A" />
컴파일러는 다음 경고를 발생시킵니다.
(!) 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...
편집기는 컴파일러를 호출하기 전에도 이 경고를 표시할 수 있습니다.
다음과 같이 svelte-ignore
로 시작하는 주석을 사용하여 다음 마크업 블록에 대해 이 경고를 무시하도록 Svelte에 지시할 수 있습니다.
<!-- 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
핸들러를 추가할 수 있습니다.
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
파일의 내용을 다음으로 바꿉니다.
/* 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;
}
마크업 스타일을 지정하면 이제 모든 것이 더 좋아 보입니다.
지금까지의 코드
Git
이 문서의 끝에서 코드의 상태를 확인하려면 다음과 같이 저장소 사본에 접근하세요.
cd mdn-svelte-tutorial/03-adding-dynamic-behavior
또는 폴더의 콘텐츠를 직접 다운로드합니다.
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