React로 작성한 이전 예제는 게시판 데이터를 배열에 저장하기 때문에 다른 사람은 볼 수 없고, 웹 브라우저를 갱신(F5)하면 초기화 되었다.

이번에는 Firebase를 이용하여 실제 게시판처럼 데이터를 서버에 저장해서 데이터가 사라지지 않고 다른 사람과 공유할 수 있도록 구현한다.


여기서 정리한 내용은 React 게시판 만들기 시리즈의 세번째 내용이다.

   1. React 게시판(CRUD) 만들기

   2. React + Redux 게시판(CRUD) 만들기

   3. React(Redux) + Firebase 게시판(CRUD) 만들기


Firebase는 구글에서 제공하는 서버 관련 기능들로 gmail계정으로 간편하게 사용할 수 있다.

NodeJS에서 Firebase를 사용하는 방법은 이 문서에 정리되어 있다.

Firebase의 콘솔 사용법도 같이 정리 되었으니 이전 문서를 확인하고,

여기서는 Firebase의 여러 서비스 중 데이터를 저장하는 Cloud FireStore를 이용한다.

여기서 만드는 게시판은 로그인기능이 없기 때문에 아무나 글을 쓰고 저장할 수 있도록 Cloud FireStore를 테스트 모드로 설정하고, 관련 설정은 이 문서를 참고하면 된다.


배열이 아닌 Cloud FireStore에 사용자가 작성한 게시물을 저장한다.

이외에 Firebase에 대한 사용법은 Firebase 문서를 참고하고, 이 문서 중에서 Cloud FireStore 관련 문서(데이터 가져오기, 데이터 저장등)를 읽어 보는 것이 좋다.


React로 작성한 이전 게시판 예제Nodejs 기반으로 작성한 Firebase 게시판 예제를 합치는 것으로,

다음과 같이 App_reduce.js 파일의 내용만 Firebase에 맞추어 조금만 수정하면 작성할 수 있다.

그전에 다음과 같이 Firebase와 미들웨어를 설치 한다.

npm install --save firebase

npm install --save redux-thunk

redux-thunk를 설치하고, redux-thunk를 사용할 수 있도록 다음과 같이 index.js 파일을 수정한다.

import thunk from 'redux-thunk';

import App from './App';
import board_reducer from './App_reducer';

let store = createStore(board_reducer
                , window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
               , applyMiddleware(thunk) );

index.js

그리고, Firestore.js 파일을 생성하여 다음과 같이 작성한다.

import firebase from 'firebase';

var config = {
    apiKey: "AIzaSyDbsU_",
    authDomain: "fir-example-.firebaseapp.com",
    databaseURL: "https://fir-example-.firebaseio.com",
    projectId: "fir-example-",
    storageBucket: "fir-example-.appspot.com",
    messagingSenderId: ""
};
firebase.initializeApp(config);
var firestore = firebase.firestore();

const settings = {timestampsInSnapshots: true};
firestore.settings(settings);

export default firestore;


Firestore.js

React에서 Firestore에 접속하기 위한 정보로 config의 내용을 채워야 한다.

config의 내용은 Firebase 콘솔에서 가져올 수 있고, 가져오는 방법은 이전 문서를 참고하면 된다.

Firestore에 접속한 연결은 firestore로 외부로 보내지고 (export ),

다음 코드에서처럼 firestore로 사용된다 [라인 2, 7, 23, 32, 40].


다음과 같이 App_reduce.js에 firebase_board_list, firebase_board_remove, firebase_board_save 3개의 함수를 추가한다.

3 개의 파일은 Firebase(FireStore)의 게시판 데이터를 가지고 오는 리스트, 선택한 게시물을 삭제하는 삭제, 게시글을 수정하거나 새로 등록하는 글 저장 함수이다.

먼저, firebase_board_list()는 FireStore에서 데이터를 가지고 와서 기존의 state.boards에 넣어주는 기능을 한다 [라인 5].

작성된 날짜(brddate)를 기준으로 최근 데이터가 먼저 오게(desc) 데이터들을 가지고 와서 (get) [라인7 ]

데이터의 개수 만큼 반복(forEach)해서 별도의 배열(rows)에 넣어주고 [라인 10],

화면에 출력하기 위해 board_list() 함수를 호출해서 Redux로 보관한다 [라인 15].

데이터의 개수 만큼 반복하는 것은 전송 받은 데이터 리스트 구조가 JSon 배열이라 필요한 정보만 추출(doc.data() – 게시물 하나)해서 보관하기 위한 것이다 [라인 11].

전송 받은 데이터를 그대로 배열에 넣어서 사용해도 된다 (대신 관련한 코드를 좀더 수정해야 한다.).

이전 예제에서는 board_list()함수를 사용하지 않았는데, 여기서는 state.boards에 데이터를 저장하는 역할을 한다.

개념상 Redux로 저장된 state.boards의 데이터를 가지고 오는 것이 되어야 할 것 같지만 반대로 저장하는 역할을 한다.

즉, firebase_board_list()로 서버에서 데이터를 가지고 오고, board_list()로 state에 저장하는 구조이다.


firebase_board_remove()는 주어진 글번호로(brdno) FireStore에 있는 데이터를 삭제(delete)하는 명령어를 실행한다 [라인 20].

삭제하고 나면(then) [라인 23],

화면에 출력하기 위해 데이터를 가지고 있는 state.boards에서 제거하기 위해 board_remove() 함수를 호출한다 [라인 24].

board_remove()는 기존 코드와 동일하다.


firebase_board_save()함수는 수정인지 신규 등록인지 구분해서 [라인 31]

수정이면 update [라인 10],

신규 등록이면 set으로 데이터를 저장한다 [라인 35].

수정이든 신규등록이든 저장하고 나면(then), board_save() 함수를 호출해서 state.boards의 값을 수정하여 화면에 반영되게 한다 [라인 37, 41].

board_save()의 코드도 기존 코드와 동일하다.


사용자가 행을 선택하면 호출하는 board_read()는 데이터 변경이 없기 때문에 수정없이 그대로 사용한다.


initialState 에서 선언했던 최대 글번호(maxNo)를 제거한다 [라인 47].

Firestore에 저장하기 때문에 Firestore에서 제공하는 문서 번호(id)를 글번호로 사용한다 [라인 33].


이상으로 App_reducer.js 파일을 이렇게 수정한 뒤,

기존에 각 컴포넌트에서 사용한 board_remove(), board_save() 함수를 사용한 각 컴포넌트에서 firebase_ 을 앞에 붙여 추가한 3개의 함수를 사용하도록 하고,

글 리스트의 경우에는 state.boards 를 사용하기 전에 firebase_board_list()를 호출하도록 수정한다.


글리스트(BoardList) 먼저 수정하면 다음과 같다.

App.js에 작성했던 리스트 코드를 분리하여 별도의 컴포넌트로 제작하였다.

이해를 위해 App.js에 그대로 작성했지만, React에서는 기능별로 세분화해서 컴포넌트로 사용하는 것이 권장된다.


글 리스트를 구현 할 것이기 때문에 Reducer에 작성해 놓은 firebase_board_list()를 사용한다고 선언한다(import) [라인 2].

컴포넌트가 생성되는 시점에 발생하는 이벤트인 componentDidMount에서 firebase_board_list()를 실행한다 [라인 6].

firebase_board_list()를 호출하면 Reducer의 state.boards에 게시글들이 채워지고

이것을 바라보고 있는 this.props.boards에 전달되면서 화면에 출력된다 [라인 9].

this.props.boards는 앞서 정리한 데로 mapStateToProps으로 연결하였다 [라인 16~23].


각 게시물을 출력하는 BoardItem은 데이터 삭제를 위해 board_remove() 대신에 firebase_board_remove()를 호출한다.

import { board_read, firebase_board_remove } from './App_reducer'

const BoardItem = ({row, inx, board_read, firebase_board_remove}) => (
    <tr>
        <td>{inx}</td>
        <td><a onClick={() => { board_read(row.brdno) } }>{row.brdtitle}</a></td>
        <td>{row.brdwriter}</td>
        <td>{row.brddate}</td>
        <td><a onClick={() => { firebase_board_remove(row.brdno) }}>X</a></td>
    </tr>
);

const mapDispatchToProps = dispatch => ({
  board_read: brdno => dispatch(board_read(brdno)),
  firebase_board_remove: brdno => dispatch(firebase_board_remove(brdno))
})

export default connect(null, mapDispatchToProps)(BoardItem)

BoardItem.js

코드의 다양성을 보여 주기 위해 BoardItem에서는 기존과 다른 방법으로 App_reducer의 함수를 호출하도록 했다.

BoardList 컴포넌트에서는 import문에서 사용할 함수를 선언하고 다음과 같이 사용했다.

         this.props.dispatch(firebase_board_list());

firebase_board_remove도 이렇게 사용해도 되지만 mapDispatchToProps 를 사용해서

App_reducer에서 사용할 함수를 다시 선언하고 BoardItem에 연결(connect)할 때 파라미터를 넘기는 방식으로 사용한다.

이렇게 사용하면 this.props.dispatch를 생략하고 firebase_board_remove() 함수만 호출해서 사용할 수 있다.


[ 참고 ] connect에서 첫번째 파라미터는 mapStateToProps로 컴포넌트에서 사용할 변수들을 선언한다. BoardItem에서는 사용할 변수가 없어서 null로 지정하였다. mapDispatchToProps 는 사용할 함수를 선언하는 것이다.


글 쓰기 폼(BoardForm)에서는 board_save() 대신에 firebase_board_save로 바꾸어 주면 된다.

import { firebase_board_save } from './App_reducer'

class BoardForm extends Component {
   
    handleSave = () => {
        this.props.dispatch(firebase_board_save(this.state));
        this.setState (this.initialSelectedBoard);
    }
}

BoardForm.js

BoardForm에서는 this.props.dispatch로 호출했다.

mapDispatchToProps와 this.props.dispatch 중 적절한 방식으로 작성하면 되지만 mapDispatchToProps가 많이 사용된다.

(개인적으로 귀찮아서...)


이상으로 간단하게 Redux 예제를 Firebase Cloud Firestore로 구현하였다.

기존 코드에 Firestore의 CRUD를 처리하는 함수를 추가하는 것으로 간단하게 구현하였다.

즉, React(Redux)로 사용자의 PC에서 화면에 데이터를 출력하고 관리하는 기능을 하도록 하고,

Firestore로 서버에 데이터를 저장하고 관리하는 기능을 하도록 서로의 역할을 나눠서 사용한다.


다른 브라우저로 접속해서 데이터가 동일하게 출력되는 것을 확인 할 수 있다.


Firestore(Cloud Firestore)는 Realtime database와 같이 실시간 데이터베이스 이다.

서버의 데이터에 변경이 생기면 현재 접속한 클라이언트에 실시간으로 데이터가 보내진다.

실시간으로 동기화 된다는 것으로, 이 기능을 이용하여 많은 예제들이 Fireabse 기반 메신저를 제작하였다.

상세한 설명은 Firebase 문서에서 확인할 수 있다.


이번에는 앞서 작성한 예제를 실시간 데이터 방식으로 작성한다.

소스는 Github의 step2 브랜치에서 받을 수 있다.

변경 방법을 간단하게 정리하면, 데이터 변경 사항을 글 리스트에서 받아서 state.boards에 반영하여 보여주는 식으로 작성한다.

코드에서 보듯이 글 리스트(firebase_board_list)에서 거의 모든 기능이 구현된다.


이전에는 get() 메소드를 사용했지만, onSnapshot()을 사용하고 [라인 4],

전송 받은 데이터의 개수(forEach) 만큼 반복했다.

이전과 가장 큰 차이는 전송된 데이터의 종류(change.type)이다 [라인 7, 11, 14].

현재 데이터(행)가 신규 추가인지(add), 수정 인지(modified), 삭제 인지를(removed) 나타낸다.

이 종류에 따라 board_save()나 [라인 9, 12] board_remove()를 호출해서[라인 15] state.boards의 데이터를 관리한다.


그리고, firebase_board_remove(), firebase_board_save()에서

처리 후 콜백(then)으로 board_save()나 board_remove()를 호출할 필요가 없어져 제거한다 [라인 25, 35, 37].

이 코드들을 모두 리스트에 작성하였다 [라인 9, 12, 15] .


그리고 onSnapshot으로 호출된 콜백 함수는 함수가 종료 되어도 계속 실행되기 때문에 서버의 데이터 변경 정보를 계속 수신해서 반영한다.

다른 사람이 작성한 것도 모두 실시간으로 제공된다.


 주의  onSnapshot으로 호출된 콜백 함수는 계속 실행되기(Listening) 때문에 필요하지 않을 경우에는 사용을 중지해야 한다.

중지 방법은 Firestore 문서에서 [리스너 분리]항목을 참조하면 된다.


이상으로 React, Redux, Firebase 순으로 기초적인 게시판 만들기를 정리하였다.

이해를 위해 Table 테그로 디자인 없이 작성하였는데,

그림과 같이 Material-ui와 같은 React용 디자인 라이브러리를 사용하면 제법 쓸만한 SPA(Single Page Application) 웹사이트를 만들 수 있다.

게시판에 대한 개념이 이해가 되었으면,

Google's Material Design을 준수하는 Material-ui등의 디자인 라이브러리를 적용하여 다음과 같이 사용하면 된다.


이상의 게시판에 기능을 보강하고

Fireabse 인증(로그인) 기능, Material-ui 등을 추가하여 Project9 템플릿을 제작하였다.

실제 프로젝트를 쉽게 할 수 있도록 만든 템플릿으로 (작성된 코드를 Copy & Paste 하여 개발하기 위한 예제들)

이상의 개념을 가지고 응용하는 방법을 이해하는데 도움을 줄 수 있으니 확인해보면 도움이 될 것이다.


Project9 템플릿을 기반으로 DirectTalk9 이라는 실시간 메신저를 웹 버전으로 제작하였고,

다시 이것을 Electron을 이용하여 PC 버전으로 제작하였다.

React Native를 쓰면 모바일까지….



'Node.js > React' 카테고리의 다른 글

React 학습자료  (0) 2019.01.23
React + Redux 게시판(CRUD) 만들기  (1) 2018.11.04
React 게시판(CRUD) 만들기 1  (4) 2018.10.28
React 게시판(CRUD) 만들기 2  (11) 2018.10.28

+ Recent posts