1. 초기 세팅
* 파이어베이스
- 이번 장에서는 파이어베이스를 이용해서 realtime database를 만들어 볼 것이다.
- 실시간으로 글이 만들어지고 지워지는 환경을 구축하는데 있어 파이어베이스는 아주 좋은 도구이다.
- 데이터베이스를 만들어 보자.
- 데이터베이스 만들기를 클릭하고, 테스트모드를 클릭하여 다음으로 넘어간다. 파이어스토어 위치는 가까울수록 좋기때문에 asia-northeast 중에서 선택한다.
- 데이터베이스가 만들어지면 위와같이 표시된다. 직접 컬렉션을 만들 수도 있지만 우리는 코드를 통해 만들고 접근할 것이다.
- 참고로 위 DB의 구조는 컬렉션 > 다큐먼트 > 데이터 순으로 이루어진다. 컬렉션은 다큐먼트의 집합이며 하나의 폴더라고 생각하면 좋다. 다큐먼트는 데이터 하나를 갖고 있으며 하나의 id를 갖는다. 내부에는 객체형식 배열형식등으로 데이터를 담고있다.
* 규칙
- 콘솔에 있는 규칙을 클릭하여 규칙을 지정할 수 있다.
- 규칙을 확인하면 위와 같이 기본적으로 읽기와 쓰기를 날짜로 제한해 둔 것을 알 수 있다.
- 이는 테스트 용으로 만들어져 있는 것이다. 우리는 로그인 한 유저만 읽고 쓸 수 있도록 제한할 것이다.
* 파일
- 저번 장과 동일한 파일구조를 갖고 있으며, components 디렉토리에는 Post.js를 추가했다.
- App.js를 일부 수정했다. 게시글을 다룸에 있어서 유저의 정보가 필요하기 때문에 유저의 정보를 props로 넘겨주도록 했다.
// components/App.js
import React, { useState, useEffect } from "react";
import AppRouter from "./Router";
import { authService } from "fbase";
function App() {
const [init, setInit] = useState(false);
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [userObj, setUserObj] = useState(null);
useEffect(() => {
authService.onAuthStateChanged((user) => {
if (user) {
setIsLoggedIn(true);
setUserObj(user);
} else {
setIsLoggedIn(false);
}
setInit(true);
});
}, []);
return (
<>
{init ? (
<AppRouter isLoggedIn={isLoggedIn} userObj={userObj} />
) : (
"Initializing."
)}
</>
);
}
export default App;
- userObj라는 상태를 추가했으며, 지난 장에서만든 onAuthStateChanged메서드를 이용해서 user의 정보를 담는다.
- 이 porps는 라우터에 넘긴 후에 Home 에서 사용될 수 있도록 다시 넘긴다.
// components/Router.js
// ...
const AppRouter = ({ isLoggedIn, userObj }) => {
return (
<Router>
{isLoggedIn && <Navigation />}
<Switch>
{isLoggedIn ? (
<>
<Route exact path="/">
<Home userObj={userObj} />
</Route>
<Route exact path="/profile">
<Profile />
</Route>
<Redirect from="*" to="/" />
</>
) : (
<>
<Route exact path="/">
<Auth />
</Route>
<Redirect from="*" to="/" />
</>
)}
</Switch>
</Router>
);
};
// ...
- 실시간 db인 firestore을 사용하기 위해 import한 후, 다른 곳에서 쓰일 수 있도록 export하자.
// fbase.js
import firebase from 'firebase/app';
import "firebase/auth";
import "firebase/firestore";
// ...
export const firebaseInstance = firebase;
export const authService = firebase.auth()
export const dbService = firebase.firestore();
2. Create & Read
* Create
// routes/Home.js
import React, { useState, useEffect } from "react";
import { dbService } from "fbase";
const Home = ({ userObj }) => {
const [post, setPost] = useState("");
const onSubmit = async (event) => {
event.preventDefault();
await dbService
.collection("posts")
.add({ text: post, createdAt: Date.now(), creatorId: userObj.uid });
setPost("");
};
const onChange = (event) => {
const {
target: { value },
} = event;
setPost(value);
};
return (
<div>
<form onSubmit={onSubmit}>
<input
value={post}
onChange={onChange}
type="text"
placeholder="게시글을 입력하세요"
maxLength={120}
/>
<input type="submit" value="Post!!" />
</form>
</div>
);
};
export default Home;
- 글을 작성하기 위한 폼을 Home.js에 추가하였다.
- firebase.firestore() 을 사용하기 위해서 dbService를 import하였다.
- 폼 제출이 일어나면 post상태에 담긴 게시글을 db에 추가한다. 이 때 collection().add()메서드를 사용한다.
- collection의 인자에는 컬렉션 이름이 들어가는데 만일 없는 이름을 적으면 자동으로 초가된다.
- add 를 사용해서 데이터를 추가한다. 문서id는 자동으로 추가되고, 객체로 받은 데이터들을 넣게된다. userObj 내부에 유저id값을 의미하는 uid 속성이 있으므로 이 또한 넣어준다.
- 위와 같이 만들고 submit을 하면 db콘솔에서 확인 가능하다.
* Read
- 위에서 db에 게시글을 넣게 하였으므로, 이 게시글들을 웹페이지에 보이도록 해야한다.
- 게시글 하나에는 많은 정보와 메서드들이 필요하므로 별도의 컴포넌트로 분리하였다.
// components/Post.js
import React from "react";
const Post = ({ postObj }) => {
return (
<div>
<h4>{postObj.text}</h4>
</div>
);
};
export default Post;
- 위와 같이 간단하게 컴포넌트를 만들고 다시 Home을 수정한다.
// routes/Home.js
import React, { useState, useEffect } from "react";
import { dbService } from "fbase";
import Post from "components/Post";
const Home = ({ userObj }) => {
const [post, setPost] = useState("");
const [posts, setPosts] = useState([]);
useEffect(() => {
dbService.collection("posts").onSnapshot((snapshot) => {
const postArray = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setPosts(postArray);
});
}, []);
// ...
return (
<div>
<form onSubmit={onSubmit}>
<input
value={post}
onChange={onChange}
type="text"
placeholder="게시글을 작성하세요"
maxLength={120}
/>
<input type="submit" value="Post!!" />
</form>
<div>
{posts.map((post) => (
<Post
key={post.id}
postObj={post}
isOwner={post.creatorId === userObj.uid}
/>
))}
</div>
</div>
);
};
export default Home;
- useEffect를 이용해서 컴포넌트 실행시 포스트들을 읽을 수 있도록 하였다.
- onSnapshot 메서드를 이용해서 db의 데이터에 변화가 있을 때마다 감지되도록 하였다. 변화가 있으면 자동으로 snapshot을 가져온다. 이는 db의 데이터들을 의미한다.
- postArray 변수를 선언하여 데이터를 담는데, 이 때 게시글마다 id값을 할당하기 위해 파이어베이스에서 자동으로 할당된 다큐먼트의 id를 넣는다. data()메서드로 다큐먼트의 데이터들을 가져올 수 있다. 스프레드를 사용해서 id와 같은 수준에 있도록 하였다.
- Posts state에 위의 배열을 넣었으며, 이를 map을 이용해서 Post 컴포넌트에 랜더링 시켰다.
- isOwner은 뒤에 있을 삭제나 수정을 위해서 불린값을 넣을 수 있도록했다.
3. Delete & Update
* Delete
// components/Post.js
import { dbService } from "fbase";
import React, { useState } from "react";
const Post = ({ postObj, isOwner }) => {
const onDeleteClick = async () => {
const ok = window.confirm("삭제하시겠습니까?");
console.log(ok);
if (ok) {
await dbService.doc(`posts/${postObj.id}`).delete();
}
};
return (
<div>
<h4>{postObj.text}</h4>
{isOwner && (
<>
<button onClick={onDeleteClick}>삭제</button>
</>
)}
</div>
);
};
export default Post;
- 먼저 삭제 버튼이 필요하다. props로 받은 isOwner가 true일 때에만 삭제버튼이 활성화 되도록 하였다.
- 버튼을 클릭하면 window.confirm으로 얼럿창을 띄우고, 수락할 경우에 삭제를 진행하도록 하였다.
- 삭제하는 방법은 매우 간단하다. doc메서드에 인자로 디렉토리형식으로 담으면 된다. '컬렉션/다큐먼트id' 를 전달하고, delete 메서드를 호출하여 다큐먼트를 삭제하였다.
- 이 때, 데이터의 변화가 있으므로 Home에서 미리 설정해두었던 onSnapshot이 이를 감지하고 다시 렌더링이 진행된다. 즉 실시간으로 포스트가 삭제되는 것을 볼 수 있다.
* Update
// components/Post.js
// ...
const Post = ({ postObj, isOwner }) => {
const [editing, setEditing] = useState(false);
const [newPost, setNewPost] = useState(postObj.text);
//...
const toggleEditing = () => setEditing((prev) => !prev);
const onSubmit = async (event) => {
event.preventDefault();
await dbService.doc(`posts/${postObj.id}`).update({ text: newPost });
setEditing(false);
};
const onChange = (event) => {
const {
target: { value },
} = event;
setNewPost(value);
};
return (
<div>
{editing ? (
<>
<form onSubmit={onSubmit}>
<input
onChange={onChange}
type="text"
value={newPost}
required
/>
<input type="submit" value="Update" />
</form>
<button onClick={toggleEditing}>Cancel</button>
</>
) : (
<>
<h4>{postObj.text}</h4>
{isOwner && (
<>
<button onClick={onDeleteClick}>삭제</button>
<button onClick={toggleEditing}>수정</button>
</>
)}
</>
)}
</div>
);
};
export default Post;
- 수정도 비슷하다. 수정버튼을 추가하였고, 별도의 수정폼을 활성화 시키기 위해서 toggle상태를 만들어주었다.
- 삭제와 같이 doc메서드에 정확한 경로를 인자로 넣어서 update() 메서드를 호출한다. 이 때, 정확하게 키와 값을 명시해주어야 그 키에 해당하는 값이 변경된다.
참고
'Server > Firebase' 카테고리의 다른 글
<파이어베이스> 필터와 정렬 (0) | 2021.07.18 |
---|---|
<파이어베이스> 파일 업로드 (0) | 2021.07.17 |
<파이어베이스> 로그인과 로그아웃 (0) | 2021.07.14 |
<파이어베이스> 파이어베이스와 리액트 준비 (0) | 2021.07.11 |
<파이어베이스> 파이어베이스 소개 (2) | 2021.07.07 |