realtor-clone 프로젝트 배운점 / 후기

Jeong seulho·2022년 10월 27일
0

프로젝트 후기

목록 보기
3/4

배운점

1. firebase 활용

📖firebase 시작하기

// Import the functions you need from the SDKs you need
import { getFirestore } from "firebase/firestore";
import { initializeApp } from "firebase/app";

// TODO: Add SDKs for Firebase products that you want to use
// https://firebase.google.com/docs/web/setup#available-libraries

// Your web app's Firebase configuration
// For Firebase JS SDK v7.20.0 and later, measurementId is optional
const firebaseConfig = {
  apiKey: "AIzaSyDAgn_TSMc3cQI9mqx-71DAU46BQm_pyFs",
  authDomain: "clone-5fcac.firebaseapp.com",
  projectId: "clone-5fcac",
  storageBucket: "clone-5fcac.appspot.com",
  messagingSenderId: "159360965313",
  appId: "1:159360965313:web:d6b59c044d816b3359d240",
  measurementId: "G-K148BD688D",
};

// Initialize Firebase
initializeApp(firebaseConfig);
export const db = getFirestore();

firebase 시작하기 위한 초기 설정을 firebase.js에 구현 firestore를 사용하기 위해 db를 export한다

📖firebase 회원가입 기능 구현

  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
  });

  const { name, email, password } = formData;
  
    async function onSubmit(e) {
    e.preventDefault();
    try {
      const auth = getAuth();
      const userCredential = await createUserWithEmailAndPassword(
        auth,
        email,
        password
      );

      updateProfile(auth.currentUser, {
        displayName: name,
      });

      const user = userCredential.user;
      const formDataCopy = { ...formData };
      delete formDataCopy.password;
      formDataCopy.timestamp = serverTimestamp();

      await setDoc(doc(db, "users", user.uid), formDataCopy);
      toast.success("Sign up was successful");

      navigate("/");
    } catch (error) {
      toast.error("Something went wrong with registration");
    }
  }

firebase auth 예제에서 creatUserWithEmailAndPassword()함수를 사용하여 유저 정보를 만들고
유저 정보에서 password를 빼고 timestamp를 추가하여 setDoc()를 사용하여 firestore database에 저장한다

 async function onGoogleClick() {
    try {
      const auth = getAuth();
      const provider = new GoogleAuthProvider();
      const result = await signInWithPopup(auth, provider);
      const user = result.user;

      //check for the user
      const docRef = doc(db, "users", user.uid);
      const docSnap = await getDoc(docRef);

      if (!docSnap.exists()) {
        await setDoc(docRef, {
          name: user.displayName,
          email: user.email,
          timestamp: serverTimestamp(),
        });
      }
      navigate("/");
    } catch (error) {
      toast.error("Could not authorize with Google");
    }
  }

위는 구글을 이용한 회원가입을 하는 함수이다

📖firebase 로그인 기능 구현


  const [formData, setFormData] = useState({
    email: "",
    password: "",
  });

  const { email, password } = formData;
  
  async function onSubmit(e) {
    e.preventDefault();
    try {
      const auth = getAuth();
      const userCredential = await signInWithEmailAndPassword(
        auth,
        email,
        password
      );
      if (userCredential.user) {
        navigate("/");
      }
    } catch (error) {
      toast.error("Bad user credentials");
    }
  }

회원가입과 유사하며 signInWithEmailAndPassword()를 사용한다

📖firestore database 활용들


firestore database는 다음과 같이 컬렉션(collection)-문서(doc)-필드(field)구조로 되어있다

//setDoc
await setDoc(doc(db, "users", user.uid), formDataCopy);

//getDocs
const q = query(
        listingRef,
        where("userRef", "==", auth.currentUser.uid),
        orderBy("timestamp", "desc")
      );
const querySnap = await getDocs(q);

//addDoc
const docRef = await addDoc(collection(db, "listings"), formDataCopy);

//updateDoc
const docRef = doc(db, "listings", params.listingId);
await updateDoc(docRef, formDataCopy);

//deleteDoc
await deleteDoc(doc(db, "listings", listingID));

setDoc() setDoc은 주어진 데이터로 doc을 통째로 다시 set해버린다. 만약 데이터가 없을경우 그냥 데이터를 추가해버린다
updateDoc() doc의 특정필드만 업데이트 할 수 있다. 만약 특정필드의 값이 없을경우 필드값을 그냥 추가해버린다
addDoc() 컬렉션 안에 document를 넣는다
getDoc() 또는 getDocs() 데이터 불러오기
deleteDoc() 데이터 삭제하기
collection() 해당 컬렉션을 참조
doc() 해당 문서를 참조

📖firestore storage 사용 이미지 저장

   async function storeImage(image) {
      return new Promise((resolve, reject) => {
        const storage = getStorage();
        const filename = `${auth.currentUser.uid}-${image.name}-${uuidv4()}`;
        const storageRef = ref(storage, filename);
        const uploadTask = uploadBytesResumable(storageRef, image);
        uploadTask.on(
          "state_changed",
          (snapshot) => {
            // Observe state change events such as progress, pause, and resume
            // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
            const progress =
              (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
            console.log("Upload is " + progress + "% done");
            switch (snapshot.state) {
              case "paused":
                console.log("Upload is paused");
                break;
              case "running":
                console.log("Upload is running");
                break;
              default:
                break;
            }
          },
          (error) => {
            // Handle unsuccessful uploads
            reject(error);
          },
          () => {
            // Handle successful uploads on complete
            // For instance, get the download URL: https://firebasestorage.googleapis.com/...
            getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
              resolve(downloadURL);
            });
          }
        );
      });
    }

2. react-router-dom 활용

📖private route 구현

개인 페이지나 내가 게시한 게시글을 등록하는 등 private한 페이지가 필요할때 구현하는 방법이다

function PrivateRoute() {
  const { loggedIn, checkingStatus } = useAuthStatus();
  if (checkingStatus) {
    return <Spinner />;
  }

  return loggedIn ? <Outlet /> : <Navigate to="/sign-in" />;
}

export default PrivateRoute;

다음과 같이 로그인 되어있으면 <Outlet />을 반환 로그인 상태가 아닐경우 sign-in페이지로 이동하는 <PrivateRoute /> 컴포넌트를 구현한다

          <Route path="/create-listing" element={<PrivateRoute />}>
            <Route path="/create-listing" element={<CreateListing />} />
          </Route>

다음과 같이 app.js에서 private하게 관리하려는 페이지를 위에서 구현한 <PrivateRoute /> 로 감싸주면 <Outlet /> 반환시 감싸고 있는 내부 페이지로 이동하게된다

📖동적 라우팅 구현

            <Link to="/category/rent">
              <p className="px-3 text-sm text-blue-600 hover:text-blue-800 transition duration-150 ease-in-out">
                Show more places for rent
              </p>
            </Link>
            
            <Link to="/category/sale">
              <p className="px-3 text-sm text-blue-600 hover:text-blue-800 transition duration-150 ease-in-out">
                Show more places for sale
              </p>
            </Link>

Home 화면에서 rent 또는 sale에 해당하는 부동산매물 페이지로 이동하는 태그이다

          <Route path="/category/:categoryName" element={<Category />} />

app.js에서 다음과 같이 :(colon)을 사용하여 동적 라우팅을 할 수 있다
여기서 categoryName에는 위의 rent 또는 sale이 담기게 된다

const params = useParams();

        const q = query(
          listingRef,
          where("type", "==", params.categoryName),
          orderBy("timestamp", "desc"),
          limit(8)
        );

해당 <Category /> 컴포넌트에서 useParams()를 사용하여 categoryName에 담긴 rent 또는 sale을 받아와서 사용할 수 있다.

이 예시 외에도

// profile.js에서 게시글 수정 페이지로 이동하는 함수
const navigate = useNavigate();
  function onEdit(listingID) {
    navigate(`/edit-listing/${listingID}`);
  }
  
// app.js 동적 라우팅
<Route path="/edit-listing" element={<PrivateRoute />}>
 <Route path="/edit-listing/:listingId" element={<EditListing />} />
</Route>

// EditListing 컴포넌트에서 해당 listingID를 받아와서 그에 맞는 doc불러오는 과정
const params = useParams();
const docRef = doc(db, "listings", params.listingId);
const docSnap = await getDoc(docRef);

다음과 같이 해당 게시글의 수정 페이지에도 사용한다

3. 옵셔널 체이닝, null 병합 연산자

📖옵셔널 체이닝

const nestedProps = obj.first && object.first.name

객체의 속성값을 찾기 위해선 이런식으로 참조를 확인하여야 했었다
논리 연산자 &&를 이용하여 좌항 연산자가 truty한 경우에만 object.first.name을 불러올 수 있었다

const nestedProps = obj.first?.name;

으로 표현하여 first의 값이 존재하면 name을 반환하고 없으면 undefined를 반환한다. 이를 풀어서 해석하면

const nestedProp = ((obj.first === null || obj.first === undefined) ? undefined : obj.first.name);

📖null 병합 연산자

a ?? b의 리턴 값은

  • a가 null도 아니고 undefined도 아니면(a에 어떤 제대로 된 값이 할당되어 있으면) a
  • 그 외의 경우는 b
let height = 0;

alert(height || 100); // 100
alert(height ?? 100); // 0

||와 차이

// height가 null이나 undefined인 경우, 100을 할당
height = height ?? 100;

활용

후기

실제 내 프로젝트에 사용할 firebase를 공부하기 위한 clone 코딩이었는데 firebase뿐만 아니라 큰 사이트의 react-router-dom 사용 방법에 더 익숙해진것을 느꼈다.
실제로 firebase를 사용하는 것에는 막히는 부분이 거의 없었다.
다음에는 이 clone 프로젝트에서 배운부분을 바탕으로 실제 내 프로젝트에 반영하려 한다.

0개의 댓글