스파르타 코딩 클럽 🤟🏻 - 5 주차
앱개발 플러스 - 5 주차
[수업 목표]
- flatList 무한 스크롤
- 댓글 및 좋아요 구현
- 내가 쓴 글 불러오기 (마이페이지)
01. 5 주차
- 게시글 무한 스크롤
- 사용자들과 상호 커뮤니케이션을 위한 댓글 기능
- 좋아요 눌렀던 게시글을 껐다 켰을 때 버튼 상태 유지
02. FlatList - 일정 개수 불러오기
무한 스크롤
- 게시글 5개 > 스크롤 하단까지 내린다 > 다음 3개 출력
앱 속도 증진
사용자 이탈 감소
Cloud Firebase에서 특정 개수 게시글 출력
MainPage.jsx
const [next, setNext] = useState(0);
const readyData = async () => {
const data = await getData(setNext);
setData(data);
};
firebaseFunctions.js
export async function getData(setNext) {
try {
let data = [];
const db = firebase.firestore();
const first = db.collection("diary").orderBy("date", "desc").limit(5);
const snapshot = await first.get();
snapshot.docs.map((doc) => {
data.push(doc.data());
});
let last;
if (snapshot.docs.length !== 0) {
last = snapshot.docs[snapshot.docs.length - 1];
}
setNext(last.data().date);
return data;
} catch (err) {
return false;
}
}
- 데이터 추출
- 다음 순서로 넘겨줄 마지막 데이터를 상태 관리에 저장
- 사용자가 스크롤을 내려 5개를 다 보면
- 상태에 저장된 마지막 데이터 출력 및 다음 5개 데이터를 가져옴
- 그 다음 데이터를 출력했을 때, 다시 마지막 데이터를 상태 관리에 저장
03. FlatList - 무한 스크롤 적용
MainPage.jsx 구조 변경
<Container>
<Header />
<Content>
<CardComponent />
</Content>
</Container>
⬇︎
<Container>
<Header/>
<FlatList
header={()=>{return (<Content></Content>)}}
renderItem={()=> { return (<CardComponent/>)}}
>
</Container>
- FlatList가 Content 영역의 역할 수행
- Content와 CardComponent 부분 분리
FlatList는 큰 ScrollView 기능의 도화지
- Header 지정
ListHeaderComponent={() => {
return (
)
}}
- renderItem : 어떤 컴포넌트를 반복해서 보여주려 하는지 지정
<View style={{ marginTop: -20 }}>
{data.map((content, i) => {
return <CardComponent content={content} key={i} navigation={navigation} />;
})}
</View>
⬇︎
renderItem={(data) => {
return (
<CardComponent navigation={navigation} content={data.item} />
);
}}
- numColumns : 한 줄에 몇 개의 컴포넌트를 보여줄 것인지 지정
- keyExtractor : map 반복문을 돌릴 때 유니크한 값을 넘겼던 key 속성과 유사한 기능
numColumns={1}
keyExtractor={(item) => item.data.toString()
- onEndReached : 바닥 근처에 갔을 때 실행될 함수 지정
- onEndReachedThreshold : 바닥에서 얼마나 이격돼야 바닥으로 간주할 것인지 수치 입력 란 (0 : 바닥에 닿았을 때 함수 실행)
onEndReachedThreshold={0}
onEndReached={async () => {
}}
- ActivityIndicator : 로딩바 구현
{data.length == 0 ? (
<ActivityIndicator size="large" />
) : (
<FlatList ...
04. FlatList - 데이터 연결
firebaseFunctions.js
export async function getNextData(nextDate, setNext) {
try {
let data = [];
const db = firebase.firestore();
const next = db
.collection("diary")
.orderBy("date", "desc")
.startAfter(nextDate)
.limit(5);
const snapshot = await next.get();
snapshot.docs.map((doc) => {
doc.data();
data.push(doc.data());
});
let last;
if (snapshot.docs.length !== 0) {
last = snapshot.docs[snapshot.docs.length - 1];
setNext(last.data().date);
return data;
} else {
return 0;
}
} catch (err) {
return false;
}
}
MainPage.jsx
onEndReached={async () => {
let nextData = await getNextData(next, setNext);
if (nextData == 0) {
Alert.alert('마지막 글입니다.');
} else {
let newData = [...data, ...nextData];
await setData(newData);
}
}}
- startAfter : 다음 데이터 가져올 준비
const next = db
.collection("diary")
.orderBy("date", "desc")
.startAfter(nextDate)
.limit(5);
- 스프레드 연산자
let newData = [...data, ...nextData];
기존 게시글이 담겨있는 상태 값 data와 새로운 게시글이 담겨있는 nextData 변수를 합칠 때 사용하는 문법
05. 댓글 DB 구상
- 다이어리 컨텐츠 문서 안에 comment 컬렉션 생성 후 게시글 고유의 댓글 date 저장 (게시글을 불러올 때 댓글 모두 출력)
- diary, users 레벨의 새로운 컬렉션 생성 (diary 유니크 문서값을 통해 댓글 데이터 출력)
1번은 댓글 양 증가 시 게시글 당 가져와야 하는 데이터 증가
06. 댓글 저장
DetailPage.jsx
const [commentInput, setCommentInput] = useState("");
const commentFunc = async () => {
let date = new Date();
let getTime = date.getTime();
const currentUser = firebase.auth().currentUser;
let comment = {
date: getTime,
comment: commentInput,
did: content.date + "D",
uid: currentUser.uid,
};
};
return (
...
<Item style={{ marginTop: 100 }}>
<Input
placeholder="Comment"
value={commentInput}
onChangeText={(text) => {
setCommentInput(text);
}}
/>
<Icon
active
name="paper-plane"
onPress={() => {
commentFunc();
}}
/>
</Item>
...
)
DetailPage -> DB(comment)
- 작성 시간 (date)
- 코멘트 (comment)
- 다이러리 아이디 (did)
- 작성자 아이디 (uid)
firebaseFunctions.js
- 댓글 컬렉션 생성 및 users 컬렉션 조회하여 사용자 닉네임 조회
export async function addComment(comment) {
try {
const db = firebase.firestore();
let userRef = await db.collection("users").doc(comment.uid);
let data = await userRef.get().then((doc) => {
return doc.data();
});
comment.author = data.nickName;
await db
.collection("comment")
.doc(comment.data + "D")
.set(comment);
return true;
} catch (err) {
Alert.alert("댓글 작성 오류 -> ", err.message);
return false;
}
}
댓글 상태 관리
firebaseFunctions.js
export async function getComment(did) {
const db = firebase.firestore();
let data = [];
let snapshot = await db.collection("comment").where("did", "==", did).get();
if (snapshot.empty) {
return 0;
} else {
snapshot.forEach((doc) => {
data.push(doc.data());
});
return data;
}
}
- firebase의 where 절 사용
// comment 컬렉션에 저장된 댓글 데이터 중 did 값이 DetailPage로부터 넘어온 값과 일치하는 데이터를 골라 조회하는 조건문
let snapshot = await db.collection("comment").where("did", "==", did).get();
DetailPage.jsx
const [comment, setComment] = useState([]);
useEffect(() => {
navigation.setOptions({
title: content.title,
headerStyle: {
backgroundColor: "white",
shadowColor: "white",
},
headerTintColor: "black",
headerShown: "true",
headerBackTitleVisible: false,
});
commentLoad(content.date);
}, []);
const commentLoad = async (did) => {
let c = await getComment(did + "D");
if (c == 0) {
} else {
setComment(c);
}
};
return (
...
<List>
{comment.map((c, i) => {
return <CommentComponent key={i} comment={c} />;
})}
</List>
...
)
- 댓글 등록 후 바로 출력
DetailPage.jsx
const commentFunc = async () => {
let date = new Date();
let getTime = date.getTime();
const currentUser = firebase.auth().currentUser;
let newComment = {
date: getTime,
comment: commentInput,
did: content.date + "D",
uid: currentUser.uid,
};
let result = await addComment(newComment);
if (result) {
Alert.alert("댓글 저장");
await setComment([...comment, newComment]);
}
};
배열 + 배열을 위해 사용했던 스프레드 연산자를 통해 배열에 새 객체 추가
기존의 상태값은 보존하면서 새로운 값 추가 (데이터 불변성)
08. 좋아요 기능 DB 구상
- diary 게시글 안에 좋아요(likes) 리스트 삽입 (비관계형 데이터베이스의 장점 : 유연함)
diary -> doc -> field
-> likes -> doc -> field
- 한 다이어리 글에 몇 명이 좋아요를 눌렀는지 축적될 내부의 likes zjffprtus
- 내가 좋아요를 누른 글은 빨간 하트가 처리되어야해 내가 likes 안에 있는지 확인
09. 좋아요 기능 DB 저장
- 로그인한 사용자의 uid를
- 해당 diary의 likes 컬렉션에 저장
MainPage.jsx
useEffect(() => {
navigation.addListener("beforeRemove", (e) => {
e.preventDefault();
});
readyData();
}, []);
const readyData = async () => {
const data = await getData(setNext);
setData(data);
};
⬇︎
useEffect(() => {
navigation.addListener("beforeRemove", (e) => {
e.preventDefault();
});
getData(setNext, setData);
}, []);
- readyData() 함수를 useEffect 안으로 넣어 getDate()에 넘겨 관리
firebaseFunctions.js
export async function doLike(uid, did) {
try {
const db = firebase.firestore();
const date = new Date();
const getTime = date.getTime();
await db.collection("diary").doc(did).collection("likes").doc(uid).set({
date: getTime,
});
return true;
} catch (error) {
return false;
}
}
- 넘겨받은 did로 다이어리 특정 게시글 탐색
- 내부 likes 컬렉션 생성 후 uid 이름으로 문서(doc) 생성
- 사용자가 좋아요를 누르면 uid 문서 이름으로 누적
- 언제 좋아요를 눌렀는지 날짜 데이터를 필드 값으로 저장
문서는 같은 값 중복 체크 기능을 제공하여 같은 게시글을 여러 번 좋아요 눌러도 딱 한 번만 저장
CardComponent.jsx
const likeFunc = () => {
const currentUser = firebase.auth().currentUser;
const uid = currentUser.uid;
const did = content.date + "D";
let result = doLike(uid, did);
if (result) {
console.log("likes");
}
};
- 어떤 사용자가 눌렀는지 :
uid
- 어떤 게시글에 좋아요를 눌렀는지 :
did
firebaseFunctions
export async function getData(setNext, setData) {
try {
let data = [];
const db = firebase.firestore();
const first = db.collection("diary").orderBy("date", "desc").limit(5);
const snapshot = await first.get();
const currentUser = firebase.auth().currentUser;
let last;
if (snapshot.docs.length !== 0) {
last = snapshot.docs[snapshot.docs.length - 1];
}
setNext(last.data().date);
let count = 0;
let limit = snapshot.docs.length;
snapshot.docs.map(async (doc) => {
let d = doc.data();
const like = await db
.collection("diary")
.doc(d.date + "D")
.collection("likes")
.doc(currentUser.uid)
.get();
if (like.data() == undefined) {
d.like = false;
} else {
d.like = true;
}
count += 1;
data.push(d);
if (count == limit) {
setData(data);
}
});
return data;
} catch (err) {
return false;
}
}
- 로그인 한 사용자가 어떤 게시글을 좋아요 눌렀는지 조회
- 어떤 문서(doc)에 likes 컬렉션의 누가 눌렀는지에 대해 게시글 출력
- 반복문을 돌린 map 함수 내부에서 또 한 번 조회
- 좋아요를 누른 적이 있다면 likes 변수에 값 저장
- 그렇지 않으면 undefined로 저장
- 밖으로 내보낼 게시글 데이터에 like 키값을 두고 눌렀으면 true, 아니면 flase
10. 좋아요 매칭 및 해제
- 좋아요 매칭
CardComponent.jsx
<Icon
name={content.like == true ? "heart" : "heart-outline"}
style={content.like == true ? styles.pink : styles.grey}
onPress={() => {
likeFunc();
}}
/>
- 좋아요 해제
CardComponent.jsx
const [like, setLike] = useState(false);
useEffect(() => {
if (content.like == true) {
setLike(true);
} else {
setLike(false);
}
}, []);
const likeFunc = () => {
const currentUser = firebase.auth().currentUser;
const uid = currentUser.uid;
const did = content.date + "D";
let result = doLike(uid, did);
if (result) {
if (like == true) {
setLike(false);
} else {
setLike(true);
}
}
};
- doLike 함수에 like 데이터를 넘겨 해제할 것인지, 좋아요 정보를 저장할 것인지 결정
- 좋아요 데이터에 변화가 있으면 상태를 변경시켜 화면에 출력
firebaseFunctions.jsx
export async function doLike(uid, did, like) {
try {
const db = firebase.firestore();
const date = new Date();
const getTime = date.getTime();
if (like == true) {
await db
.collection("diary")
.doc(did)
.collection("likes")
.doc(uid)
.delete();
} else {
await db.collection("diary").doc(did).collection("likes").doc(uid).set({
date: getTime,
});
}
return true;
} catch (error) {
return false;
}
}
Prologue - React Native Tip
- 리액트 기본 기술 공부
- 근간이 리액트이기 때문에 리액트 기본기 다지기 중요
배포하기
01. 배포 체크리스트
- 앱 로고
- 스플래스 스크린 (앱 시작 초기 화면)
- 앱 마켓에 올릴 설명 이미지
02. 스플래시 스크린 / 로고
03. 최종 앱 파일 생성
- Expo를 통한 최종 앱 파일 생성
- 구글 플레이 개발자 라이센스 가입 및 구입
- 구글 플레이 스토어에 앱 배포
- PERMISSION 문제로 배포가 안되는 현상
"android": {
"package": "",
"versionCode": 1,
"config": {
"googleMobileAdsAppId": ""
},
"permissions": ["CAMERA", "READ_EXTERNAL_STORAGE"]
},
- 안드로이드 앱 빌드(생성) 명령어
expo build:android
// ios
expo build:ios
> apk 파일 선택
> expo 사이트에서 다운
Author And Source
이 문제에 관하여(스파르타 코딩 클럽 🤟🏻 - 5 주차), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@nezhitsya/스파르타-코딩-클럽-5-주차-4wbmfzek저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)