React를 쉽게 익히기 위해서 데이터 입출력(CRUD) 기능을 가진 게시판을 구현한다.
React 만으로 구현하기 위해 배열에 데이터를 저장하고 출력하는 방식으로 게시판을 구현한다.
여기서 정리한 내용은 React 게시판 만들기 시리즈의 첫번째 내용이다.
1. React 게시판(CRUD) 만들기
2. React + Redux 게시판(CRUD) 만들기
3. React(Redux) + Firebase 게시판(CRUD) 만들기
앞서 간단한 설치와 사용법을 정리하였고, 여기서는 다음과 같이 단계별로 제작한다.
소스는 Github에서 받을 수 있다.
1. 데이터 출력 (글 리스트)
2. 형식에 맞춰서 출력 (글 리스트)
3. 데이터 입력 (새 글 작성)
4. 데이터 수정과 삭제 (글 수정 / 삭제)
5. 기능(컴포넌트)별 파일 구성
개인적으로 React는 아주 쉽게 (?) 배울 수 있는 라이브러리 이지만
자바스크립트(JavaScript), 특히 ES5 (ECMAScript 5) 이상(ES6)의 문법을 모른다면 배우기 아주 어려운 라이브러리라고 생각한다.
ES5와 ES6의 특징 정도는 알고 있는 것이 좋으니 웹 문서들을 찾아보길 바란다.
여기에서는 별도로 정리하지 않지만 쉽게 React를 익히기 위해 단계별로 가장 쉬운 것 부터 시작한다.
먼저, 주어진 배열의 내용을 출력하는 글 리스트부터 작성한다 (App1.js, App2.js).
앞서서 create-react-app로 생성하고 정리한 예제에서 App.js 파일의 내용을 지우고 다음과 같이 작성한다.
Github에서 다운받은 파일에서는 App1.js로 작성되어 있다.
작성 과정을 각각의 예제 파일로 남기기 위해 단계별로 진행한 내용을 App1.js ~ App6.js 파일로 작성하였지만,
따라 할 때에는 App.js에 코드를 추가하는 방식으로 작성하면 된다.
코드를 살펴보면 state 변수를 가장 먼저 선언하였다 [라인 4].
state 변수는 게시판 데이터를 배열로 가지는 boards 배열을 구성원(Json)으로 가지고 있다.
boards는 글번호(brdno), 작성자(brdwriter), 글제목(brdtitle), 작성일자(brddate)로 구성되고 2개의 데이터가 등록되어 있다 [라인 7~16].
데이터 베이스로 보면 4개의 필드와 2개의 행이 있는 것이다.
이 데이터(state)를 render()에서 출력한다 [라인 21~33].
render()는 React에서 화면을 생성하기 위해 실행하는 이벤트이다.
App 컴포넌트(Component)에 있는 state [라인 4]을 render()에서 사용하기 위해 this.state로 지정한다 [라인 22].
this는 자바스크립트에서 자기 자신(Component)을 의미한다.
this.state에 있는 것 중에서 하나를 가지고 올 때 사용하는 것이 대괄호({})로state에 있는 데이터(boards)를 boards에 저장한다 [라인 22].
대괄호({})는 state에 변수가 많을 때 편하게 사용하는 코드로 다음과 같이 사용해도 된다.
const { boards } = this.state;
const boards = this.state. boards;
주의 const, let, var의 차이는 찾아보길……
가지고 온 데이터(boards)를 map() 메서드를 이용하여 2개 행의 글번호와 작성자를 묶어서 하나의 문자열(list)로 작성하였다 [라인 23~25].
이 값을 화면에 출력한다 [라인 29].
주의 값을 출력할 때 대괄호({})가 사용되었다. 대괄호는 자바스크립트에서 함수의 범위가 되기도 하고 Json이기도 하지만, 변수내의 수 많은 변수 중 일부를 빼내서 사용하거나 [라인 22], React의 문법으로 태그(div) 사이에서 값을 출력할 때에도 사용된다. 여러 가지 의미로 사용되니 잘 기억해야 한다.
실행 결과는 다음과 같다.
정리하면,
render() 시작부터 return 이전(라인 22~6)까지는 자바스크립트의 영역이고
return 내부는 HTML (React 정의라 조금 다름) 영역으로 볼 수 있다.
HTML영역에서 자바스크립트를 사용할 때에는 대괄호({})를 사용한다.
이상의 코드는 다음과 같이 작성 할 수 있다.
render() {
const { boards } = this.state;
return (
<div>
{
boards.map(function(row){
return row.brdno + row.brdwriter ;
})
}
</div>
);
}
App2.js
이전과 동일하게 글 번호와 작성자를 묶어서 출력하는 코드이다.
자바스크립트 코드 위치가 바뀌었고 대괄호({})를 사용한다.
데이터를 출력하는 코드는 render와 return 사이보다 return의 HTML 사이에서 많이 사용한다.
두번째 단계로 테이블(table) 테그로 게시판 리스트처럼 출력한다 (App3.js).
DIV 테그를 사용하는 것이 좋지만 CSS 설정 등으로 불필요한 코드 작성이 필요해, 다음과 같이 테이블 테그로 간단하게 작성하였다.
기존 코드에 테이블 테그를 사용하여 리스트 헤드 [라인 7~14]를 작성한다.
그리고, boards에 있는 데이터를 행(tr)으로 출력하도록 작성하였다 [라인 16~18].
다만, 각 행을 BoardItem이라는 컴포넌트를 이용하여 출력하게 작성한다 [라인 27].
BoardItem 컴포넌트에 row라는 변수로 boards의 행(row)을 하나씩 지정해서 넘겨주고 [라인 17]
BoardItem 컴포넌트에서는 이 row를 this.props로 받아서 사용한다.
주의 컴포넌트 자신이 사용하는 것은 state이고, 부모로부터 받은 것은 props이다. 이 개념을 잘 이해하면 React의 주요 개념 절반을 이해한 것이다.
BoardItem 컴포넌트를 사용하는 것은 React의 특징으로 React에서는 모든 기능을 컴포넌트로 구현하여 사용한다.
이상으로 글 리스트 기능을 구현하면서 React의 컴포넌트 개념을 사용하였다.
React는 기능을 세분화해서 컴포넌트로 구현하는 특징이 있다.
그리고, 부모(호출하는) 컴포넌트가 자식(호출 받는) 컴포넌트에 값을 넘겨주고 받는 방법을 정리하였다.
세번째로 값을 입력받아서 저장하는 글 쓰기 기능을 구현한다 (App4.js).
글쓰기에서는 그림과 같이 글 제목과 작성자를 입력하고 저장(save) 버튼을 누르면 입력한 내용이 baords 배열에 저장되도록 구현한다.
글쓰기 기능을 구현하기 위해서는 HTML 컨트롤과 이벤트가 React와 연동되는 방법을 알아야 한다.
React(특히 이 예제)에서 가장 어려운 부분이라고 생각하니 코드의 의미에 대해서 제대로 이해하고 넘어가야 한다.
글쓰기 기능을 구현할 BoardForm 컴포넌트를 생성하고 [라인 21],
글쓰기에서 사용할 state 변수와 handleChange, handleSubmit 이벤트 핸들러를 작성하였다.
1 라인에 선언된 state는 App 컴포넌트에서 사용하는 state 이고, 22 라인에서 선언한 state는 BoardForm내부에서 사용하는 state이다.
state는 컴포넌트 내부에서 사용할 변수로 이름이 고정되어 있다.
두 이벤트 핸들러는 화살표(=>) 함수로 작성하였다.
화살표 함수가 아닌 전통적인 함수로 작성하면 bind등의 제법 복잡한 처리를 해야 한다.
handleChange는 사용자가 값을 입력할 때 마다(onChange 이벤트) 입력하는 값을 받아서 state 변수에 각 컨트롤의 이름(brdtitle, brdwriter)으로 저장한다 [라인 24].
handleChange핸들러의 e는 자바스크립트의 change 이벤트에서 파라미터로 넘어오는 Event를 의미하고 e.target은 현재 이벤트가 발생한 개체,
즉 값을 입력하는 입력상자를 의미한다.
두 개의 입력상자가 각각 brdtitle, brdwriter로 지정되어 있기 때문에 [라인 39, 40] 각각의 이름으로 변수가 생성되어 사용자가 입력한 값이 저장된다.
입력 받는 값이 글 제목(brdtitle)과 작성자(brdwriter)이므로, brdtitle, brdwriter로 저장된다.
즉,
state = {
brdtitle: 값,
brdwriter: 값
}
으로 저장된다.
저장시 "this.state.brdwriter=값" 이나 "this.state[brdwriter]=값" 으로 저장하지 않고 setState 함수를 사용하여 저장한다 [라인 25].
React의 규칙이니 준수해야 하고, 이렇게 하지 않으면 웹 브라우저 콘솔 등에 경고가 출력된다 (다음 그림과 전후 설명 참조).
라인 39 와 40 에서 입력 상자와 handleChange를 연결하였다.
연결시 handleChange에 this를 붙여 사용하는데, 컴포넌트 내의 변수나 함수(이벤트 핸들러)를 참조할 때에는 this를 붙여야 한다.
handleSubmit은 Form 태그가 값을 서버로 전송할 때 발생하는 이벤트를 처리하기 위한 핸들러이다 [라인 38].
즉, 사용자가 값을 입력하고 저장할 때 발생한다.
실제로 서버로 보낼 것이 아니기 때문에 preventDefault로 이벤트를 중지한다 [라인 22].
그리고 onSaveData 함수를 호출하여 데이터를 저장한다.
onSaveData()는 BoardForm 컴포넌트에 있지 않고 [라인 32],
부모인 App 컴포넌트에 있기 때문에 this.props.onSaveData()로 사용한다 [라인5, 15].
onSaveData()는 부모로 부터 파라미터(this.props)로 받았다 [라인 15].
부모로부터 받은 것은 값이든 함수이든 항상 props를 사용해야 한다 [라인 32].
그리고 저장할 값은 stae에 있으니 함수를 호출하면서 this.state를 넘겨준다 [라인 32].
부모 (호출하는) App 컴포넌트에서는 값을 입력 받을 적당한 위치에 BoardForm 컴포넌트를 생성한다 [라인 15].
컴포넌트의 생성은 HTML 태그처럼 <BoardForm/>로 작성하면 된다.
BoardForm를 생성하면서 파라미터로 handleSaveData() 함수를 onSaveData()라는 이름으로 넘겨준다.
이것을 자식(호출받는) BoardForm에서는 this.props.onSaveData()로 호출한다 [라인 32].
호출 받은 부모의 handleSaveData()에서는 setState를 이용하여 [라인 6] state에 있는 baords배열에 값을 추가(concat)한다 [라인 7].
baords배열에 concat으로 추가하고 이것을 baords라는 이름으로 저장하는 방식으로 작성한다.
저장시 글 번호(brdno)와 작성일자(brddate)을 생성한다[라인 7].
작성일자는 자바스크립트 Date 클래스로 현재 날짜를 입력하고, 글 번호는 state에 추가한 변수 maxNo의[라인 2] 값을 사용한다.
기본적으로 baords에 데이터 2 건이 있으므로 maxNo은 3의 값을 가지고 있고, 글을 추가한 후에 1 증가(++) 한 값(다음 글번호)을 저장한다.
주의 웹 브라우저에서 이상의 예제인 App4를 실행하면 다음과 같은 경고를 볼 수 있다.
이 경고는 state에 값을 바로 대입한 코드를[라인 7] setState 함수로 수정하라는 것으로 다음과 같이 작성 한다.
this.setState({
maxNo: this.state.maxNo+1,
boards: boards.concat({brdno: this.state.maxNo, brddate: new Date(), ...data })
});
게시물(boards)을 추가하듯이 [라인 7],
maxNo의 값을 증가시키고, maxNo라는 이름으로 setState함수를 이용하여 저장한다.
실행해서 새로운 글이 잘 작성되는지 확인한다.
React의 값을 주고 받는 방식도 잘 기억해야 하지만,
자식(BoardForm)에서 작성한 값을 부모에게 보내어 저장하는 구조도 잘 기억해야 한다.
부모(App)의 state에 있는 boards에 모든 값을 저장하기 때문에 부모에게 사용자가 입력한 값을 전송한다.
부모의 state에 값을 저장하고,
이 state에 변경이 생기면
state(boards)의 값을 참조하는 또 다른 자식인 BoardItem에 값들이 자동으로 보내어져 추가된 글이 화면에 출력된다.
이 것이 React의 특징 중 하나이다.
일반적인 프로그래밍에서는 데이터를 추가하거나 수정하면 화면 갱신을 하도록 별도의 처리를 해야 하지만
React에서는 state에 변경사항이 생기면 관련 내용이 자동으로 반영된다.
익숙하지 않은 사람에게는 아주 헷갈리는 기능일 수 있다.
네 번째로 데이터를 수정하고 삭제하는 기능을 구현한다 (App5.js).
삭제 기능은 글을 추가하는 것과 같은 원리로 구현되니 상세한 설명은 생략하고 개념만 정리한다.
(직접 해보는 것이 실력 향상에 도움이 된다.)
삭제는 다음 그림처럼 글 리스트의 각 항목(BoardItem)에 삭제 버튼을 두고 [라인 37],
사용자가 삭제 버튼을 클릭하면 부모에 저장된 boards에서 해당 글을 삭제한다 [라인 3].
삭제 할 때는, 사용자가 선택한 글 번호(brdno)에 해당하는 글을 찾아서 삭제한다.
배열에서 값을 삭제하는 것은 fiter 사용이 권장된다 [라인 5].
다음 코드에서 handleRemove를 따라 다니면 보다 쉽게 코드를 이해 할 수 있을 것이다. (라인 37 > 24 > 26 > 17 > 3 > 5 순)
글 수정은 글 항목(행)들 중에 하나를 선택하면(handleSelectRow)
선택된 행의 값들을 사용자가 수정할 수 있도록 입력상자(BoardForm)에 뿌려주고,
사용자가 수정 후 저장 버튼을 클릭하면, 글번호의 값이 있으면 수정, 없으면 신규 등록으로 구현한다.
개념은 새글 작성과 동일하지만 글 수정은 제법 많은 변경과 추가가 이루어져 모든 코드를 글로 정리하기 어렵다. (실제로는 가장 어려운 부분이 여기다)
정리하더라도 읽으면서 혼란해지기 때문에 주요 코드와 개념만 정리한다.
나머지는 … (이 부분은 이해되지 않는 경우 넘어가도 된다. Redux에서 좀더 쉽고 간단하게 구현한다.)
handleSelectRow함수를 중심으로 살펴 보면, 그나마 보다 쉽게(?) 코드를 이해 할 수 있을 것이다.
사용자가 글 항목(행)들 중에 하나를 선택하면(handleSelectRow) [라인 58 => 50 => 38 => 25]
선택된 행의 값들을 사용자가 수정할 수 있도록 입력상자에 뿌려주어야 한다.
입력상자는 BoardForm에 있기 때문에 BoardForm을 부모(App) 컴포넌트가 알고 있어야 한다.
즉, 자식(BoardForm)의 핸들러를 가지고 있어야 한다.
각 컴포넌트의 핸들을 가지고 오는 속성 ref를 this.child에 보관하는 방법을 사용한다 [라인 34].
부모는 선택한 행의 데이터를 자식 (this.child)의 handleSelectRow를 호출하면서 파라미터로 넘겨준다 [라인 26].
입력 폼(BoardForm)에서는 부모로부터 받은 값을 그대로 state 변수에 넣어준다 [라인 76].
파라미터로 받은 값의 구조가 Json이기 때문에 이전처럼[라인 83~85] “변수: 값”의 구조로 지정하지 않고 한번에 넣어 준다 [라인 76].
이 state의 변수를 입력상자가 바로 보고 있도록 수정해서 [라인 92, 93] state 값이 변경되면 자동으로 바뀌도록 작성했다.
BoardItem과 같은 원리이다.
사용자가 수정 후 저장 버튼을 클릭하면 [라인 79]
부모로 데이터를 보내서 [라인 81]
글 번호(brdno)가 있으면 [라인 11] 수정, 없으면 신규 등록으로 구현한다.
신규 등록은 앞서 정리했고,
수정은 글 번호가 같은 행을 찾아서 (data.brdno === row.brdno) 행을 바꾸는 방식으로 구현한다 [라인 18].
마지막으로 사용자가 입력한 값은 지워 주는데
BoardForm에서 state 변수내의 값 변수들 값을 지워주면 화면에서도 지워진다.
BoardItem과 같은 원리로 입력 상자들이 state를 바라보고 있기 때문에 가능한 것이다.
React에서는 HTML 태그들이 자바스크립트에 바인드(Bind) 된 것처럼 작동한다.
이상으로 React의 기본적인 사용법을 게시판 예제로 정리하였다.
React 자체는 state, props만 알면 그렇게 어렵지 않지만
모든 기능을 세분화해서 컴포넌트로 작성하고, 값이 컴포넌트를 오가면서 코드가 아주 복잡해 지고 개념이 어려워 진다.
(App 컴포넌트의 리스트 부분도 BoardList로 별도로 제작해야 한다. 직접 해보길…)
값을 한 곳에 넣고 처리할 수 있도록 함수를 작성해 두고
이 함수만 각 컴포넌트에서 호출한다면 아주 쉽고 깔끔하게 코드를 작성 할 수 있을 것이다.
이러한 기능을 제공하는 라이브러리 중에 하나가 Redux로 Redux예제는 별도로 작성하였기 때문에 여기에 정리하지 않는다.
마지막 예제는 각 컴포넌트를 별도의 파일로 작성하는 것이다 (App6.js).
코드 양이 많기 때문에 컴포넌트는 각각의 파일로 작성하고 export 를 이용하여 다른 파일에서 사용할 수 있도록 한다.
각 컴포넌트를 사용할 컴포넌트에서는 import로 가져와서 사용한다.
메인 컴포넌트인 App을 포함해서, BoardItem(BoardRow)과 BoardForm의 총 3개의 컴포넌트가 사용되었다.
따라서, 다음과 같이 각각의 파일을 작성하였다.
import React, { Component } from 'react';
class BoardRow extends Component {
}
export default BoardRow;
App6_BoardItem.js
import React, { Component } from 'react';
class BoardForm extends Component {
}
export default BoardForm;
App6_BoardForm.js
import React, { Component } from 'react';
import BoardForm from './App6_BoardForm';
import BoardItem from './App6_BoardItem';
class App6 extends Component {
render() {
const { boards, selectedBoard } = this.state;
return (
<div>
<BoardForm selectedBoard={selectedBoard} onSaveData={this.handleSaveData}/>
<table border="1">
{
boards.map(row =>
(<BoardItem key={row.brdno} row={row} onRemove={this.handleRemove} onSelectRow={this.handleSelectRow} />)
)
}
</tbody>
</table>
</div>
);
}
}
export default App6;
App6.js
주의 - BoardItem은 파일에서는 BoardRow로 작성하고, import 할 때에는 BoardItem로 이름 붙여 사용했다. 이렇게 사용해도 된다는 의미로 사용한 것이다.
마지막 예제에는 컴포넌트 별로 각각의 파일을 작성한 것 외에 몇 가지 React 기능을 더 추가해서 작성했다.
네 번째 예제(App5)는 React의 특징을 제대로 표현하지 못하였고, 값이 여러 컴포넌트를 오고 가면서 상당히 헷갈리는 예제다.
이 예제를 state를 이용하여 구현하였다.
부모(App6)에서 수정된 코드는 다음과 같다.
class App6 extends Component {
state = {
maxNo: 3,
boards: [ ],
selectedBoard:{}
}
handleSelectRow = (row) => {
this.setState({selectedBoard:row});
}
~~ 생략 ~~
render() {
const { boards, selectedBoard } = this.state;
return (
<div>
<BoardForm selectedBoard={selectedBoard} onSaveData={this.handleSaveData}/>
~~ 생략 ~~
</div>
);
}
}
행이 선택되면 handleSelectRow에서 선택된 행의 값을 모두 state의 selectedBoard에 저장한다.
이 selectedBoard을 입력폼(BoardForm)으로 전달한다.
선택된 행(selectedBoard)을 입력폼(BoardForm)의 shouldComponentUpdate 이벤트에서 처리하는 방식으로 구현한다.
이전 방식은 React의 특징인 state를 이용하여 컨트롤에 값을 보여주고,
사용자가 입력한 값을 state에 저장해서 구현했고,
이번에는 다음과 같이 shouldComponentUpdate 이벤트와 입력 상자에 대한 컨트롤(ref)을 이용해서 구현한다.
입력폼(BoardForm)에서는 컴포넌트가 업데이트 될때 발생하는 shouldComponentUpdate 이벤트에서 전달 받은 selectedBoard의 값을 화면에 출력한다 [라인 5].
shouldComponentUpdate와 같은 React의 이벤트는 이 문서를 참조하면 된다.
shouldComponentUpdate 이벤트에서 글 번호의 값이 있으면 [라인 7]
글 수정 상황이니 selectedBoard의 값을 입력상자에 넣어주고 [라인 13, 14]
값이 없는 것은 초기화 상태이니 입력 상자에 빈 문자열을 넣어준다 [라인 8, 9].
사용자가 값을 입력하면 이전에는 Chang 이벤트에서 값을 state에 보관해서 처리했지만 이번에는 ref를 사용하여 컨트롤을 참조하는 방식으로 구현했다 [라인 35, 36].
주의 이 방식은 추천하는 방식이 아니지만 React의 이해(ref)를 위해 사용한 코드이다.
저장 버튼을 누르면 사용자가 입력한 값을 저장할 구조에 맞추어 Json 형식으로 생성한다 [라인 21~28].
글 번호(selectedBoard.brdno)가 있으면 수정이니 기존 데이터(selectedBoard)에서 글 번호와 작성일자를 가져오고 [라인 26, 27],
없으면 신규이니 사용자만 입력한 값만 Json으로 저장하면 된다.
이상으로 React를 이용한 게시판 제작을 5 단계로 구현하였다.
React는 특이한 방식으로 개발하여 익히는데 혼란을 주지만 개념을 잘 잡으면 쉽게 웹 개발을 할 수 있다.
더욱이 멀티 OS 개발을 쉽게 하는 React Native의 기본 문법이라 더욱 매력적이다.