@samitouri / QOS-React-2 / commits / ac466b71d8

Translate tutorial to Korean

- Up to a19966f

Jinwoo Oh committed Feb 12, 2015 at 00:33 UTC ac466b71d8afff796490d427abb500eda8fa9e1e
1 file changed +702
docs/docs/tutorial.ko-KR.md new
+702
@@ -0,0 +1,702 @@
1 +---
2 +id: tutorial-ko-KR
3 +title: 튜토리얼
4 +prev: getting-started.ko-KR.html
5 +next: thinking-in-react.ko-KR.html
6 +---
7 +
8 +블로그에 붙일만한 간단하지만 실용적인 댓글상자를 만들어 볼 것입니다. Disqus, LiveFyre, Facebook에서 제공하는 것 같은 실시간 댓글의 간단한 버전이죠.
9 +
10 +이런 기능을 넣겠습니다:
11 +
12 +* 댓글목록
13 +* 댓글작성폼
14 +* 커스텀 백엔드를 위한 Hooks
15 +
16 +멋진 기능도 조금 넣어보겠습니다:
17 +
18 +* **낙관적 댓글 달기:** 댓글은 서버에 저장되기도 전에 목록에 나타납니다. 그래서 빠르게 느껴집니다.
19 +* **실시간 업데이트:** 다른 사용자가 남기는 댓글이 실시간으로 나타납니다.
20 +* **Markdown 지원:** 사용자는 글을 꾸미기 위해 Markdown 형식을 사용할 수 있습니다.
21 +
22 +### 그냥 다 생략하고 소스만 보고 싶나요?
23 +
24 +[GitHub에 전부 있습니다.](https://github.com/reactjs/react-tutorial)
25 +
26 +### 서버 구동하기
27 +
28 +이 튜토리얼을 시작할 때 필요한 건 아니지만, 나중에 실행 중인 서버에 `POST` 요청을 하는 기능을 추가하게 될 것입니다. 서버를 구성하는 것이 익숙하다면, 본인이 편한 방식대로 서버를 구성해 주세요. 서버사이드에 대한 고민없이 React의 학습 그 자체에 집중하고 싶은 분들을 위해서, 몇 가지 언어로 간단한 서버코드를 작성해 놓았습니다 - JavaScript (Node.js), Python, Ruby 버전이 있고, GitHub에서 찾아보실 있습니다. [소스를 확인](https://github.com/reactjs/react-tutorial/)하거나 [zip 파일을 다운로드](https://github.com/reactjs/react-tutorial/archive/master.zip)하고 시작하세요.
29 +
30 +튜토리얼을 다운로드 받아 시작한다면, `public/index.html`을 열고 바로 시작하세요.
31 +
32 +### 시작하기
33 +
34 +이 튜토리얼에서는 CDN에 있는 미리 빌드된 JavaScript 파일들을 사용합니다. 좋아하는 에디터를 열고, 새로운 HTML 문서를 만드세요:
35 +
36 +```html
37 +<!-- index.html -->
38 +<html>
39 + <head>
40 + <title>Hello React</title>
41 + <script src="http://fb.me/react-{{site.react_version}}.js"></script>
42 + <script src="http://fb.me/JSXTransformer-{{site.react_version}}.js"></script>
43 + <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
44 + </head>
45 + <body>
46 + <div id="content"></div>
47 + <script type="text/jsx">
48 + // 여기에 코드를 작성합니다
49 + </script>
50 + </body>
51 +</html>
52 +```
53 +
54 +다음 진행을 위해, 위의 스크립트 태그안에 JavaScript 코드를 작성합니다.
55 +
56 +> 주의:
57 +>
58 +> 여기서는 ajax 요청 코드를 단순화 하기 위해 jQuery를 넣었지만, 이는 React의 동작에 필수적인 것은 **아닙니다**.
59 +
60 +### 첫 번째 컴포넌트
61 +
62 +모듈화 된, 조합가능한 컴포넌트가 React의 전부입니다. 댓글상자 예제에서 우리는 다음과 같은 컴포넌트 구조를 가질 것입니다:
63 +
64 +```
65 +- CommentBox
66 + - CommentList
67 + - Comment
68 + - CommentForm
69 +```
70 +
71 +자, 이제 `CommentBox` 컴포넌트를 만들어 봅시다. `<div>` 하나로 구성되어 있습니다.
72 +
73 +```javascript
74 +// tutorial1.js
75 +var CommentBox = React.createClass({
76 + render: function() {
77 + return (
78 + <div className="commentBox">
79 + Hello, world! I am a CommentBox.
80 + </div>
81 + );
82 + }
83 +});
84 +React.render(
85 + <CommentBox />,
86 + document.getElementById('content')
87 +);
88 +```
89 +
90 +#### JSX 문법
91 +
92 +JavsScript 안의 유사 XML 구문이 먼저 눈에 띌 것입니다. 우리에겐 이를 일반 JavaScript로 변환해주는 간단한 프리컴파일러(precompiler)가 있습니다.
93 +
94 +```javascript
95 +// tutorial1-raw.js
96 +var CommentBox = React.createClass({displayName: 'CommentBox',
97 + render: function() {
98 + return (
99 + React.createElement('div', {className: "commentBox"},
100 + "Hello, world! I am a CommentBox."
101 + )
102 + );
103 + }
104 +});
105 +React.render(
106 + React.createElement(CommentBox, null),
107 + document.getElementById('content')
108 +);
109 +```
110 +
111 +JSX의 사용은 선택적이지만 JSX 문법이 일반 JavsScript보다 사용하기 쉽습니다. [JSX 문법 문서](/react/docs/jsx-in-depth.html)에서 더 알아보세요.
112 +
113 +#### 무슨 일이 일어나고 있는가
114 +
115 +우리는 새로운 React 컴포넌트를 만들기 위해 `React.createClass()`로 JavaScript 객체에 몇 개의 메소드를 담아 넘겼습니다. 이 중 가장 중요한것은 `render` 메소드인데, 이는 React 컴포넌트 트리를 반환해서 최종적으로 실제 HTML을 그려주게 됩니다.
116 +
117 +`<div>` 태그들은 실제 DOM 노드가 아니라 React `div` 컴포넌트의 인스턴스입니다. 이것은 React가 다룰 수 있는 데이터의 표지자(markers)나 조각이라 생각하셔도 됩니다. React는 **안전합니다**. 생(raw) HTML 문자열을 생성하는것이 아니기 때문에 XSS을 기본적으로 방지합니다.
118 +
119 +일반적인 HTML만 리턴할 수 있는 것은 아닙니다. 여러분이 직접 만든 (또는 다른 사람들이 만들어 놓은) 컴포넌트의 트리를 리턴할 수도 있습니다. 이것이 React를 **조합가능(composable)하게 만듭니다**: 유지보수 가능한 프론트엔드를 위한 핵심교리(key tenet)지요.
120 +
121 +`React.render()`는 최상위 컴포넌트의 인스턴스를 만들고, 두 번째 인수로 전달받은 DOM 엘리먼트에 마크업을 삽입해 프레임워크를 시작합니다.
122 +
123 +## 컴포넌트 조합하기
124 +
125 +이제 `CommentList`와 `CommentForm`을 위한 뼈대를 구축해 봅시다. 이전과 마찬가지로 단순히 `<div>` 태그 하나 입니다.
126 +
127 +```javascript
128 +// tutorial2.js
129 +var CommentList = React.createClass({
130 + render: function() {
131 + return (
132 + <div className="commentList">
133 + 안녕! 난 댓글목록이야.
134 + </div>
135 + );
136 + }
137 +});
138 +
139 +var CommentForm = React.createClass({
140 + render: function() {
141 + return (
142 + <div className="commentForm">
143 + 안녕! 난 댓글폼이야.
144 + </div>
145 + );
146 + }
147 +});
148 +```
149 +
150 +다음은 `CommentBox` 컴포넌트가 새로 만든 컴포넌트들을 사용하도록 수정합니다.
151 +
152 +```javascript{6-8}
153 +// tutorial3.js
154 +var CommentBox = React.createClass({
155 + render: function() {
156 + return (
157 + <div className="commentBox">
158 + <h1>댓글</h1>
159 + <CommentList />
160 + <CommentForm />
161 + </div>
162 + );
163 + }
164 +});
165 +```
166 +
167 +방금 만든 컴포넌트들을 어떤방식으로 HTML 태그들과 섞어 사용하는지 살펴보세요. HTML 컴포넌트들도 한가지 차이만 제외한다면 우리가 정의한 것과 같은 표준적인 React 컴포넌트입니다. JSX 컴파일러가 자동으로 HTML 태그들을 `React.createElement(tagName)` 표현식으로 재작성하고 나머지는 그대로 둘 것입니다. 이는 전역 네임스페이스가 오염되는 것을 막아줍니다.
168 +
169 +### 컴포넌트 프로퍼티 (Component Properties)
170 +
171 +이제 세 번째 컴포넌트인 `Comment`를 만들어 봅시다. 개별 댓글마다 글쓴이와 내용을 포함하게 될 것입니다. 먼저 댓글 몇 개를 `CommentList`에 추가해 봅시다:
172 +
173 +```javascript{6-7}
174 +// tutorial4.js
175 +var CommentList = React.createClass({
176 + render: function() {
177 + return (
178 + <div className="commentList">
179 + <Comment author="Pete Hunt">댓글입니다</Comment>
180 + <Comment author="Jordan Walke">*또 다른* 댓글입니다</Comment>
181 + </div>
182 + );
183 + }
184 +});
185 +```
186 +
187 +부모 컴포넌트인 `CommentList`에서 자식 컴포넌트인 `Comment`에 데이터들을 전달하고 있는것을 확인할 수 있습니다. 예를 들어, 우리는 어트리뷰트로 *Pete Hunt*를, XML 형식의 자식 노드로 *댓글입니다*를 첫 번째 `Comment`로 넘겼습니다. 부모에서 자식 컴포넌트로 전달되는 데이터는 **props**라 합니다. properties의 축약어지요.
188 +
189 +### props 사용하기
190 +
191 +Comment 컴포넌트를 만들어 봅시다. **props**를 사용해 `CommentList`에서 전달받은 데이터를 읽어들이고, 마크업을 렌더할 수 있을 것입니다.
192 +
193 +```javascript
194 +// tutorial5.js
195 +var Comment = React.createClass({
196 + render: function() {
197 + return (
198 + <div className="comment">
199 + <h2 className="commentAuthor">
200 + {this.props.author}
201 + </h2>
202 + {this.props.children}
203 + </div>
204 + );
205 + }
206 +});
207 +```
208 +
209 +JSX 내부의 중괄호로 둘러싸인 JavaScript 표현식(어트리뷰트나 엘리먼트의 자식으로 사용된)을 통해 텍스트나 React 컴포넌트를 트리에 더할 수 있습니다. `this.props`를 통해 컴포넌트에 전달된 특정한 어트리뷰트들에, `this.props.children`을 통해 중첩된 엘리먼트들에 접근할 수 있습니다.
210 +
211 +### Markdown 추가하기
212 +
213 +Markdown은 텍스트를 포맷팅하는 간단한 방식입니다. 예를 들어, 별표(`*`)로 텍스트를 둘러싸는 것은 강조의 의미입니다.
214 +
215 +먼저 서드파티 라이브러리인 **Showdown**을 애플리케이션에 추가합니다. 이 JavaScript 라이브러리는 Markdown 텍스트를 HTML 문법으로 변환해줍니다. head 태그안에 스크립트 태그를 추가해 주세요. (React playground에는 이미 포함되어 있습니다):
216 +
217 +```html{7}
218 +<!-- index.html -->
219 +<head>
220 + <title>Hello React</title>
221 + <script src="http://fb.me/react-{{site.react_version}}.js"></script>
222 + <script src="http://fb.me/JSXTransformer-{{site.react_version}}.js"></script>
223 + <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
224 + <script src="http://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script>
225 +</head>
226 +```
227 +
228 +다음은, 댓글 텍스트를 Markdown으로 전환하고 출력해 봅시다.
229 +
230 +```javascript{2,10}
231 +// tutorial6.js
232 +var converter = new Showdown.converter();
233 +var Comment = React.createClass({
234 + render: function() {
235 + return (
236 + <div className="comment">
237 + <h2 className="commentAuthor">
238 + {this.props.author}
239 + </h2>
240 + {converter.makeHtml(this.props.children.toString())}
241 + </div>
242 + );
243 + }
244 +});
245 +```
246 +
247 +우리가 한 일이라고는 Showdown 라이브러리를 호출한 것 뿐입니다. Showdown이 `this.props.children`에서 텍스트를 읽어들여 처리할 수 있도록 React 형식의 텍스트(React's wrapped text)를 단순 텍스트(raw string)으로 전환하기 위해 명시적으로 `toString()`을 호출했습니다.
248 +
249 +하지만 여기엔 문제가 있습니다! 우리는 HTML 태그들이 정상적으로 렌더되길 원하지만 브라우저에 출력된 결과물은 "`<p>``<em>`또 다른`</em>` 댓글입니다`</p>`"처럼 태그가 그대로 보일것입니다.
250 +
251 +React는 이런 식으로 XSS 공격을 예방합니다. 우회할 방법이 있긴 하지만 프레임워크는 사용하지 않도록 경고하고 있습니다:
252 +
253 +```javascript{5,11}
254 +// tutorial7.js
255 +var converter = new Showdown.converter();
256 +var Comment = React.createClass({
257 + render: function() {
258 + var rawMarkup = converter.makeHtml(this.props.children.toString());
259 + return (
260 + <div className="comment">
261 + <h2 className="commentAuthor">
262 + {this.props.author}
263 + </h2>
264 + <span dangerouslySetInnerHTML={{"{{"}}__html: rawMarkup}} />
265 + </div>
266 + );
267 + }
268 +});
269 +```
270 +
271 +이는 의도적으로 생(raw) HTML을 넣기 힘들게 하려고 만든 특별한 API지만 Showdown을 사용하기 위해 이 백도어를 활용합시다.
272 +
273 +**잊지 마세요:** 이 기능은 Showdown이 안전한 것으로 믿고 사용하는 것입니다.
274 +
275 +### 데이터 모델 연결하기
276 +
277 +지금까지는 소스코드에 직접 댓글을 넣었습니다. 이제부터는 JSON 데이터 덩어리를 댓글 목록에 렌더해보겠습니다. 최종적으로는 서버에서 데이터가 내려오겠지만, 지금은 소스에 직접 데이터를 넣어봅시다:
278 +
279 +```javascript
280 +// tutorial8.js
281 +var data = [
282 + {author: "Pete Hunt", text: "댓글입니다"},
283 + {author: "Jordan Walke", text: "*또 다른* 댓글입니다"}
284 +];
285 +```
286 +
287 +이 데이터를 모듈화된 방식으로 `CommentList`에 넣어야 합니다. props을 이용해 데이터를 넘기도록 `CommentBox`와 `React.render()` 호출을 수정합시다.
288 +
289 +```javascript{7,15}
290 +// tutorial9.js
291 +var CommentBox = React.createClass({
292 + render: function() {
293 + return (
294 + <div className="commentBox">
295 + <h1>댓글</h1>
296 + <CommentList data={this.props.data} />
297 + <CommentForm />
298 + </div>
299 + );
300 + }
301 +});
302 +
303 +React.render(
304 + <CommentBox data={data} />,
305 + document.getElementById('content')
306 +);
307 +```
308 +
309 +이제 `CommentList`에서 데이터를 다룰 수 있습니다. 댓글을 동적으로 렌더해봅시다:
310 +
311 +```javascript{4-10,13}
312 +// tutorial10.js
313 +var CommentList = React.createClass({
314 + render: function() {
315 + var commentNodes = this.props.data.map(function (comment) {
316 + return (
317 + <Comment author={comment.author}>
318 + {comment.text}
319 + </Comment>
320 + );
321 + });
322 + return (
323 + <div className="commentList">
324 + {commentNodes}
325 + </div>
326 + );
327 + }
328 +});
329 +```
330 +
331 +이게 답니다!
332 +
333 +### 서버에서 가져오기(Fetching)
334 +
335 +이제 데이터를 소스에 직접 넣는 방식에서 서버에서 동적으로 받아서 처리하는 방식으로 바꿔봅시다. 데이터 prop을 삭제하고 처리할 URL로 변경해 줍시다.
336 +
337 +```javascript{3}
338 +// tutorial11.js
339 +React.render(
340 + <CommentBox url="comments.json" />,
341 + document.getElementById('content')
342 +);
343 +```
344 +
345 +이 컴포넌트는 이전 것과 다르게, 스스로 다시 렌더링해야 합니다. 컴포넌트는 서버에서 요청이 들어올때까지는 아무 데이터도 가지고 있지 않다가, 특정한 시점에서 새로운 댓글을 렌더할 필요가 있을 것입니다.
346 +
347 +### 반응적 state
348 +
349 +지금까지, 각각의 구성요소는 props를 기반으로 한번 렌더되었습니다. `props`는 불변성을 갖습니다: 그것들은 부모에서 전달되어 부모에게 "소유" 되어 있습니다. 컴포넌트에 상호작용을 구현하기 위해서, 가변성을 갖는 **state**를 소개합니다. `this.state`는 컴포넌트에 한정(private)되며 `this.setState()`를 통해 변경할 수 있습니다. state가 업데이트 되면, 컴포넌트는 자신을 스스로 다시 렌더링합니다.
350 +
351 +`render()` 메소드는 `this.props`와 `this.state`를 위한 함수로 선언적으로 작성됩니다. 프레임워크에서 입력값에 따른 UI가 항상 일관성 있음을 보장해줍니다.
352 +
353 +서버가 데이터를 가져오면 댓글 데이터가 변경될 것입니다. 댓글 데이터의 배열을 `CommentBox`의 state로 추가해봅시다:
354 +
355 +```javascript{3-5,10}
356 +// tutorial12.js
357 +var CommentBox = React.createClass({
358 + getInitialState: function() {
359 + return {data: []};
360 + },
361 + render: function() {
362 + return (
363 + <div className="commentBox">
364 + <h1>댓글</h1>
365 + <CommentList data={this.state.data} />
366 + <CommentForm />
367 + </div>
368 + );
369 + }
370 +});
371 +```
372 +
373 +`getInitialState()` 는 컴포넌트의 생명주기동안 한 번만 실행되며 컴포넌트의 초기 state를 설정합니다.
374 +
375 +### state 업데이트하기
376 +
377 +컴포넌트의 최초 생성 시에, 서버에서 GET 방식으로 JSON을 넘겨받아 최신의 데이터가 state에 반영되길 원했습니다. 실제 애플리케이션에선 이것이 동적인 엔드포인트이지만, 이 예제에서는 정적 JSON 파일을 사용해서 간단하게 만들어보겠습니다.
378 +
379 +```javascript
380 +// tutorial13.json
381 +[
382 + {"author": "Pete Hunt", "text": "댓글입니다"},
383 + {"author": "Jordan Walke", "text": "*또 다른* 댓글입니다"}
384 +]
385 +```
386 +
387 +서버에 비동기 요청을 위해 jQuery를 사용합니다.
388 +
389 +주의: 우리의 앱이 AJAX 애플리케이션으로 변화하고 있기 때문에, 이제 파일 시스템의 파일을 참조하는 대신 웹서버를 사용하도록 앱을 개발해야 합니다. [위에서 언급한 바와 같이](#running-a-server), 우리는 튜토리얼의 나머지 부분에 필요한 기능을 제공하는 서버를 몇 가지 준비해 놓았습니다. [GitHub에 올려놓았으니](https://github.com/reactjs/react-tutorial) 확인해 보세요.
390 +
391 +```javascript{6-17}
392 +// tutorial13.js
393 +var CommentBox = React.createClass({
394 + getInitialState: function() {
395 + return {data: []};
396 + },
397 + componentDidMount: function() {
398 + $.ajax({
399 + url: this.props.url,
400 + dataType: 'json',
401 + success: function(data) {
402 + this.setState({data: data});
403 + }.bind(this),
404 + error: function(xhr, status, err) {
405 + console.error(this.props.url, status, err.toString());
406 + }.bind(this)
407 + });
408 + },
409 + render: function() {
410 + return (
411 + <div className="commentBox">
412 + <h1>댓글</h1>
413 + <CommentList data={this.state.data} />
414 + <CommentForm />
415 + </div>
416 + );
417 + }
418 +});
419 +```
420 +
421 +여기서 `componentDidMount`는 컴포넌트가 렌더링 된 다음 React에 의해 자동으로 호출되는 메소드입니다. 동적 업데이트의 핵심은 `this.setState()`의 호출입니다. 우리가 이전의 댓글 목록을 서버에서 넘어온 새로운 목록으로 변경하면 자동으로 UI가 업데이트 될 것입니다. 이 반응성 덕분에 실시간 업데이트에 아주 작은 수정만 가해집니다. 우리는 여기선 간단한 폴링을 사용할 것이지만 웹소켓등의 다른 기술도 쉽게 사용할 수 있습니다.
422 +
423 +```javascript{3,14,19-20,34}
424 +// tutorial14.js
425 +var CommentBox = React.createClass({
426 + loadCommentsFromServer: function() {
427 + $.ajax({
428 + url: this.props.url,
429 + dataType: 'json',
430 + success: function(data) {
431 + this.setState({data: data});
432 + }.bind(this),
433 + error: function(xhr, status, err) {
434 + console.error(this.props.url, status, err.toString());
435 + }.bind(this)
436 + });
437 + },
438 + getInitialState: function() {
439 + return {data: []};
440 + },
441 + componentDidMount: function() {
442 + this.loadCommentsFromServer();
443 + setInterval(this.loadCommentsFromServer, this.props.pollInterval);
444 + },
445 + render: function() {
446 + return (
447 + <div className="commentBox">
448 + <h1>댓글</h1>
449 + <CommentList data={this.state.data} />
450 + <CommentForm />
451 + </div>
452 + );
453 + }
454 +});
455 +
456 +React.render(
457 + <CommentBox url="comments.json" pollInterval={2000} />,
458 + document.getElementById('content')
459 +);
460 +
461 +```
462 +
463 +우리가 여기서 한것은 AJAX 호출을 별도의 메소드로 분리하고 컴포넌트가 처음 로드된 시점부터 2초 간격으로 계속 호출되도록 한 것입니다. 브라우저에서 직접 돌려보고 `comments.json`파일을 수정해보세요; 2초 간격으로 변화되는 모습이 보일 것입니다!
464 +
465 +### 새로운 댓글 추가하기
466 +
467 +이제 폼을 만들어볼 시간입니다. 우리의 `CommentForm` 컴포넌트는 사용자에게 이름과 내용을 입력받고 댓글을 저장하기 위해 서버에 요청을 전송해야 합니다.
468 +
469 +```javascript{5-9}
470 +// tutorial15.js
471 +var CommentForm = React.createClass({
472 + render: function() {
473 + return (
474 + <form className="commentForm">
475 + <input type="text" placeholder="이름" />
476 + <input type="text" placeholder="내용을 입력하세요..." />
477 + <input type="submit" value="올리기" />
478 + </form>
479 + );
480 + }
481 +});
482 +```
483 +
484 +이제 폼의 상호작용을 만들어 보겠습니다. 사용자가 폼을 섭밋하는 시점에 우리는 폼을 초기화하고 서버에 요청을 전송하고 댓글목록을 갱신해야 합니다. 폼의 섭밋 이벤트를 감시하고 초기화 해주는 부분부터 시작해 보죠.
485 +
486 +```javascript{3-14,17-20}
487 +// tutorial16.js
488 +var CommentForm = React.createClass({
489 + handleSubmit: function(e) {
490 + e.preventDefault();
491 + var author = this.refs.author.getDOMNode().value.trim();
492 + var text = this.refs.text.getDOMNode().value.trim();
493 + if (!text || !author) {
494 + return;
495 + }
496 + // TODO: 서버에 요청을 전송합니다
497 + this.refs.author.getDOMNode().value = '';
498 + this.refs.text.getDOMNode().value = '';
499 + return;
500 + },
501 + render: function() {
502 + return (
503 + <form className="commentForm" onSubmit={this.handleSubmit}>
504 + <input type="text" placeholder="이름" ref="author" />
505 + <input type="text" placeholder="내용을 입력하세요..." ref="text" />
506 + <input type="submit" value="올리기" />
507 + </form>
508 + );
509 + }
510 +});
511 +```
512 +
513 +##### 이벤트
514 +
515 +React는 카멜케이스 네이밍 컨벤션으로 컴포넌트에 이벤트 핸들러를 등록합니다. 폼이 유효한 값으로 섭밋되었을 때 폼필드들을 초기화하도록 `onSubmit` 핸들러를 등록합니다.
516 +
517 +폼 섭밋에 대한 브라우저의 기본동작을 막기 위해 이벤트시점에 `preventDefault()`를 호출합니다.
518 +
519 +##### Refs
520 +
521 +우리는 자식 컴포넌트의 이름을 지정하기 위해 `ref` 어트리뷰트를, 컴포넌트를 참조하기 위해 `this.refs`를 사용합니다. 고유한(native) 브라우저 DOM 엘리먼트를 얻기 위해 `getDOMNode()`를 호출할 수 있습니다.
522 +
523 +##### props으로 콜백 처리하기
524 +
525 +사용자가 댓글을 등록할 때, 새로운 댓글을 추가하기 위해 댓글목록을 갱신해주어야 합니다. `CommentBox`가 댓글목록의 state를 소유하고 있기 때문에 이 로직 또한 `CommentBox`에 있는것이 타당합니다.
526 +
527 +자식 컴포넌트가 그의 부모에게 데이터를 넘겨줄 필요가 있습니다. 부모의 `render` 메소드에서 새로운 콜백(`handleCommentSubmit`)을 자식에게 넘겨주고, 자식의 `onCommentSubmit` 이벤트에 그것을 바인딩해주는 식으로 구현합니다. 이벤트가 작동될때(triggerd)마다, 콜백이 호출됩니다:
528 +
529 +```javascript{15-17,30}
530 +// tutorial17.js
531 +var CommentBox = React.createClass({
532 + loadCommentsFromServer: function() {
533 + $.ajax({
534 + url: this.props.url,
535 + dataType: 'json',
536 + success: function(data) {
537 + this.setState({data: data});
538 + }.bind(this),
539 + error: function(xhr, status, err) {
540 + console.error(this.props.url, status, err.toString());
541 + }.bind(this)
542 + });
543 + },
544 + handleCommentSubmit: function(comment) {
545 + // TODO: 서버에 요청을 수행하고 목록을 갱신한다
546 + },
547 + getInitialState: function() {
548 + return {data: []};
549 + },
550 + componentDidMount: function() {
551 + this.loadCommentsFromServer();
552 + setInterval(this.loadCommentsFromServer, this.props.pollInterval);
553 + },
554 + render: function() {
555 + return (
556 + <div className="commentBox">
557 + <h1>댓글</h1>
558 + <CommentList data={this.state.data} />
559 + <CommentForm onCommentSubmit={this.handleCommentSubmit} />
560 + </div>
561 + );
562 + }
563 +});
564 +```
565 +
566 +사용자가 폼을 섭밋할 때, `CommentForm`에서 콜백을 호출해 봅시다:
567 +
568 +```javascript{10}
569 +// tutorial18.js
570 +var CommentForm = React.createClass({
571 + handleSubmit: function(e) {
572 + e.preventDefault();
573 + var author = this.refs.author.getDOMNode().value.trim();
574 + var text = this.refs.text.getDOMNode().value.trim();
575 + if (!text || !author) {
576 + return;
577 + }
578 + this.props.onCommentSubmit({author: author, text: text});
579 + this.refs.author.getDOMNode().value = '';
580 + this.refs.text.getDOMNode().value = '';
581 + return;
582 + },
583 + render: function() {
584 + return (
585 + <form className="commentForm" onSubmit={this.handleSubmit}>
586 + <input type="text" placeholder="이름" ref="author" />
587 + <input type="text" placeholder="이름을 입력하세요..." ref="text" />
588 + <input type="submit" value="올리기" />
589 + </form>
590 + );
591 + }
592 +});
593 +```
594 +
595 +이제 콜백이 제자리를 찾았습니다. 우리가 할일은 서버에 요청을 날리고 목록을 갱신하는 것 뿐입니다:
596 +
597 +```javascript{16-27}
598 +// tutorial19.js
599 +var CommentBox = React.createClass({
600 + loadCommentsFromServer: function() {
601 + $.ajax({
602 + url: this.props.url,
603 + dataType: 'json',
604 + success: function(data) {
605 + this.setState({data: data});
606 + }.bind(this),
607 + error: function(xhr, status, err) {
608 + console.error(this.props.url, status, err.toString());
609 + }.bind(this)
610 + });
611 + },
612 + handleCommentSubmit: function(comment) {
613 + $.ajax({
614 + url: this.props.url,
615 + dataType: 'json',
616 + type: 'POST',
617 + data: comment,
618 + success: function(data) {
619 + this.setState({data: data});
620 + }.bind(this),
621 + error: function(xhr, status, err) {
622 + console.error(this.props.url, status, err.toString());
623 + }.bind(this)
624 + });
625 + },
626 + getInitialState: function() {
627 + return {data: []};
628 + },
629 + componentDidMount: function() {
630 + this.loadCommentsFromServer();
631 + setInterval(this.loadCommentsFromServer, this.props.pollInterval);
632 + },
633 + render: function() {
634 + return (
635 + <div className="commentBox">
636 + <h1>댓글</h1>
637 + <CommentList data={this.state.data} />
638 + <CommentForm onCommentSubmit={this.handleCommentSubmit} />
639 + </div>
640 + );
641 + }
642 +});
643 +```
644 +
645 +### 최적화: 낙관적 업데이트
646 +
647 +우리의 애플리케이션은 이제 모든 기능을 갖추었습니다. 하지만 댓글이 목록에 갱신되기 전에 완료요청을 기다리는 게 조금 느린듯한 느낌이 드네요. 우리는 낙관적 업데이트를 통해 댓글이 목록에 추가되도록 함으로써 앱이 좀 더 빨라진 것처럼 느껴지도록 할 수 있습니다.
648 +
649 +```javascript{16-18}
650 +// tutorial20.js
651 +var CommentBox = React.createClass({
652 + loadCommentsFromServer: function() {
653 + $.ajax({
654 + url: this.props.url,
655 + dataType: 'json',
656 + success: function(data) {
657 + this.setState({data: data});
658 + }.bind(this),
659 + error: function(xhr, status, err) {
660 + console.error(this.props.url, status, err.toString());
661 + }.bind(this)
662 + });
663 + },
664 + handleCommentSubmit: function(comment) {
665 + var comments = this.state.data;
666 + var newComments = comments.concat([comment]);
667 + this.setState({data: newComments});
668 + $.ajax({
669 + url: this.props.url,
670 + dataType: 'json',
671 + type: 'POST',
672 + data: comment,
673 + success: function(data) {
674 + this.setState({data: data});
675 + }.bind(this),
676 + error: function(xhr, status, err) {
677 + console.error(this.props.url, status, err.toString());
678 + }.bind(this)
679 + });
680 + },
681 + getInitialState: function() {
682 + return {data: []};
683 + },
684 + componentDidMount: function() {
685 + this.loadCommentsFromServer();
686 + setInterval(this.loadCommentsFromServer, this.props.pollInterval);
687 + },
688 + render: function() {
689 + return (
690 + <div className="commentBox">
691 + <h1>댓글</h1>
692 + <CommentList data={this.state.data} />
693 + <CommentForm onCommentSubmit={this.handleCommentSubmit} />
694 + </div>
695 + );
696 + }
697 +});
698 +```
699 +
700 +### 축하합니다!
701 +
702 +몇 단계를 거쳐 간단하게 댓글상자를 만들어 보았습니다. [왜 React인가](/react/docs/why-react.html)에서 더 알아보거나, 혹은 [API 레퍼런스](/react/docs/top-level-api.html)에 뛰어들어 해킹을 시작하세요! 행운을 빕니다!