트위터 프로젝트(with react, firebase) 후기

바다·2022년 3월 1일
0

project

목록 보기
3/6
post-thumbnail

😀 트위터 클론 코딩 :깃허브 바로가기
😁 트위터 클론 코딩 페이지로 바로가기
: firebase와 react로 트위터를 클론 코딩한 프로젝트

                      드디어 끝낸 트위터 클론 코딩 !!!!!  

우연한 계기로 만난 강의를 들으며 실제 트위터의 기능을 만들어 볼 수 있겠다는 자신감을 시작해서 겨울 내내 붙들었던 트위터 클론 코딩의 프로젝트가 드디어 끝이 났고 프로젝트를 하며 내가 만난 오류들과 그를 통해 배운 점들을 써보려 한다.

Ⅰ. 프로젝트 이유

리액트 강의를 듣고 나서 실제 서비스를 만들어 보고 싶어서 검색하다 노마드 코드의 트위터 클론 코딩 강의의 수업을 듣게 되었다.

앞서 말했듯이 강의를 들을 수록 강의에 없는 트위터의 기능들(팔로우,rt와 heart, 답글, 프로필 수정등등)을 구현해 보고 싶은 욕심이 생겼고 강의를 중단하고 내가 직접 트위터 클론 코딩을 하기로 마음 먹게 되었고 그렇게 길면서도 빠르게 흐르는 겨울을 프로젝트와 보내게 되었다.

프로젝트 목표

  1. 상태관리 라이브러리를 사용하지 않고 react의 만으로 상태 관리해보기

Ⅱ 오류와 배운 것들

1. firebase

트위터에서 작성되고 필요한 데이터들을 저장하고 사용하기 위해서 firebase를 사용했고 firebase에서 데이터를 저장,사용,수정,삭제하는 방법을 익혀야했다.

데이터를 가져오는 방식으로는 get과 onSanpshot이 있고 해당 방식들의 차이점, 특히 단일 데이터를 가져올 때와 배열 형식의 데이터들 가져올 때의 코드의 차이점을 익히는데 초반에 애를 먹었다.

  • get과 onSanpshot 의 차이
    (이미지 열기로 이미지 확대해서 볼 수 있음)
getonSnapshot
단일
배열
차이점1문서의 수신 대기 하지 않음문서의 수신 대기를 해서 변경 시 콜백이 호출되어 이를 업데이트 함
차이점2배열 형태의 데이터의 경우 map을 사용해야함배열 형태의 데이터의 경우 map뿐 만아니라 forEach도 사용가능함

2 react-image-crop 와 Files API

이미지를 첨부할 때 Files API 를 사용했고 첨부할 이미지의 크기를 편집할때는 'react-image-crop'를 사용했다.

이 두가지를 사용하기 위해서는 어떻게 동작하고 그 안의 요소들에 대해 공부해야 했다. 특히 Files API를 공부할 때는 이미지등의 데이터가 어떠한 형식으로 저장되는지에 대해서도 알아야하는 구나를 알게 되었다는 점에서 좋았다.

Files API 에 대해 참고하기 좋은 사이트:

A. input과 FilesReader, base64


const onFileChange =(event) => {
  const {
    target:{files} // event.target의 형태는 FileList 
  } = event ;
  const theFile =files[0];
  const reader = new FileReader();
  reader.onloadend = (finishedEvent) =>{
    const { currentTarget : {result}} = finishedEvent;
    setAttachment (result);
    tweetDispatch({
      type:"WRITE",
      name:"attachmentUrl",
      value :result
    })
  };
  reader.readAsDataURL(theFile);
};

a. input 을 통해 첨부된 데이터는 File 객체

input 태그를 사용하여 이미지를 첨부하면, onChange 이벤트가 발생하고 이벤트의 target은 FilesList로 즉, 첨부된 이미지는 File객체 형태를 가지게 된다.

b. FileReader.readAsDataURL() 의 반환값은 base64 로 인코딩된다.

  • 데이터를 읽은 FileReader의 메서드
    ⓐ readerAsArrayBuffer
    ⓑ readAsBinaryString
    ⓒ readAsDataURL
    ⓓ readAsText

File을 읽기 위해서는 FileReader를 사용해야 하며 데이터를 읽는 메서드 중에서 객체 url을 반환하는 readAsDataURL을 사용했고 객체 url은 base64로 인코딩 된 스트리밍 데이터이다.

c. FileReader.onload/onloadestart/onloadend

FileReader로 읽을 데이터를 사용하고자 한다면 FileReader가 즉시 파일을 읽는 게 아니기 때문에 onload , onloadstart, onloadend 이벤트 핸들러를 사용해야 한다.

3. 자바스트립트의 비동기 작업과 state

자바스크립트는 이전 작업의 완료 여부와 상관없이 다음 순서의 작업이 진행되는 비동기적 성질을 가지고 있다. 그래서 서버에서 데이터를 가져온 후에 해당 데이터를 React의 state로 지정하고 싶다면 자바스크립트의 비동지적 처리 방식을 이용해야 한다.

  • 예시 : useState로 상태 관리시에 async와 await 사용
const [STATE, setSTATE] =useSatate();

	const getData =async()=>{
    	await dbservice
        .get() 
        .then(result => setSTATE(result)) 
        .catch(error => console.loog(error))
    }

async와 await로 서버에서 데이터를 받아오는 것에 우선 순위를 주고 , then()을 사용하여 서버에서 받아온 결과값을 상태로 지정해주면 된다.

가령 유저들이 속한 배열이 있고 해당 유저들 개개인의 프로필을 서버에서 받아와야하는 것 처럼 배열안의 요소들에 대해 비동기 처리를 실행해야하는 경우라면 Promise.all을 사용하면 된다.

async function ( ){
	const newMap = await Proomise.all(arry.map(a=> 비동기 실행함수 ) )
}

4. Context 와 비동기

A. Context 사용 이유

동일한 데이터의 반복 요청으로 인한 서버의 부하 줄이기

프로젝트의 기능들을 추가할 수록 동일하며 반복되는 데이터가 새로고침시나, 다른 페이지로 이동 시에 firebase에 또 요청되었고 그로인해 서버의 부하가 걸리는 일이 발생했다.
프로젝트를 시작할때 오로지 react로만 트위터를 만들어보자고 했고 그렇다고 반복되는 데이터를 props로 넘겨주면 코드가 복잡해지지 때문에 어떻게 이를 처리할 까 고민하다가 찾아낸 방법이 Context를 이용하는 것이였다.

내가 이해한 Context 를 설명하자면 다음과 같다.
Context는 선택 과목 시험 시간에 학생들이 각기 다른 과목을 선택하면, 첫째 줄부터 모든 과목을 포함해서 시험지를 넘기는 것이 아니라 시험 감독관이 해당 학생이 치르는 시험 과목의 문제를 해당 학생에게 넘겨주는 것과 같다. 즉 props로 일일히 넘겨주는 것이 아니라 해당 데이터가 필요한 곳에 그 데이터를 넘기는 것이다.

  • 해당 프로젝트에서의 Context

ⓐ ProfileContext : 현재 로그인한 유저의 프로필에 대한 context
ⓑ TweetContext : 현재 로그인한 유저의 트윗들에 대한 context
ⓒ UserContext: 로그인한 유저가 보려하는 다른 유저의 프로필과 트윗들에 대한 context

B. Context와 비동기 작업

비동기 작업은 context를 사용하는 곳에서 한다.

처음에는 context를 설정한 component 내에서 처음 마운트 시에 서버에서 데이터를 가져와 state를 변경하는 방식을 시도해봤지만 Context.Provider로 넘겨받은 Context의 state를 사용할 때 초기 상태 (initialState)값만 주고 비동기 작업 이후 의 값 (서버에서 받아온 데이터 값)은 넘겨 받지 못하는 오류가 발생했다.

그래서 찾은 해결방법은 context 를 사용하는 곳에서 비동기 작업을 실행하는 것이였고 route를 사용해 각각의 component들의 경로를 지정해주는 TwitterRouter.js에서 비동기 작업을 실행해서 context의 state를 업데이트 시켰다.

  • 예시: TwitterRouter.js
const TwitterRouter =()=>{
  const [tweet, setTweet]=useSatate(null);
  const [profiloe,setProfile]=useSatate(null);
  
  const location=useLocation;
  const state =location.state;
  
  const ContextRouter =()=>{
  	const setContext =async()=>{
      if(tweet ==null){ 
        /* 조건문을 사용하는 이유는 
        tweet를 읽어온 후에 또 다시 읽어오는 것을 
        막기 위해서이다.*/
        
      	//서버에서 유저의 트윗 데이터 가져오기 
        const tweets = 서버로 부터 받은 데이터 값
        setTweet(tweets);
        tweetDispatch(
        ....) // TweetContext의 상태 업데이트하는 액션 실행 
      };
      
      if(profile ==null) {
      //... tweet경우와 동일 
      }
    };
 	return(
      <div id="inner">
       <Routes>
      	//route
      </Routes>
      </div>
      
    )
  };
  //...
  useEffet(()=>{
  //...
    setContext()
   // ....
  },[])
 
  useEffect(()=>{
  /* state에서 userProfile이라는 value를 가지고 있을 경우
  	UserContext의 state를 업데이트 함 
  */
  },[state]);
  
  return (
  	 <UserContextProvider>
      <ProfileContextProvier>
        <TweetContextProvier>
          <ContextRouter/>
        </TweetContextProvier>
      </ProfileContextProvier>
    </UserContextProvider>
  )
  ];

5. localStorage

새로 고침 시 데이터 유지에 사용

현재 로그인한 유저가 아닌 다른 유저의 프로필을 보여주는 Profile page에서 새로고침시에 해당 데이터가 없어지는 오류가 발생했다. 이를 해결하기 위해서 localStorage를 사용했다.

sessionStorage를 사용하지 않은 이유는 새로 고침 시 뿐만아니라 프로필 페이지로 다시 돌아 왔을 때 데이터가 유지 되어야 했기 때문이다. 로그아웃시에 저장된 localStorage를 삭제하도록 했다.

*만약 저장하려는 데이터가 객체라면, storage에서 키와 값은 항상 DOMString 형태로 저장되기 때문에 JSON.stirngfy 로 저장하고 추후에 객체로 사용할 때 JSON.pare로 객체의 형태로 변경 하면 된다.

const id =JSON.stringify(userId);
const user = ownerProfile.userId ;
localStorage.setItem(user,JSON.stringify(ownerProfile) )

//.... 다른 .js 파일 
const userProfile =JSON.parse(localStorage.getItem(user)) ;

6. 다양한 변수와 복잡성

타입스크립트의 필요성을 느낌
                         "이 속성이 뭐더라.... ???? "

트위터의 여러 기능들을 만들고 , 그 기능에 따라 tweet, profile의 데이터를 만들고 사용자가 사이트를 이용하면서 마주치는 다양한 경우들을 테스트하고 수정하다보니 코드들이 복잡해지고 데이터의 속성들도 다양해져갔다.

그러다 보니 데이터를 따로 정리해가면 개발을 해도 가끔 이 속성의 초기 값이 뭐였더라? 이 속성이 이 상황에서 어떤 걸 의미했더라? 라며 머리 속이 복잡해 질때가 있었다. 특히 가능하면 코드를 재사용하려고 정리하고 이를 사용할 때 props를 지정할때 특히나 머리 속이 더 복잡해졌다.

그래서 "아 이래서 타입 스크립트를 사용하라 하는 구나"를 몸소 깨닫게 되었다.

Ⅲ. 프로젝트를 마치며

트위터 클론 코딩을 하며 다양한 것들을 고민해보고, 해결해보려 검색하고 공부했다. 그 과정에서 얻은 것들은 다음과 같다.

  • 구현하고자 하는 기능을 구현하기 위해 코드를 짜는 재미를 느꼈다.
  • react에 보다 친숙해졌다
  • 다양한 api를 다룬 경험이 나중에 새로운 api를 다룰 때 도움이 될 것 같다.
  • 이미 시중에 있는 것을 참고하다보디 현역에서는 레이아웃을 어떻게 잡는 가를 배웠다.
  • 생각하지 못했던 것들(file의 형식, 서버의 부하)을 생각해보고 경험해보면서 보는 시야가 넓어졌다.

이 중에 제일 값진 건 "그래도 코드를 짜고 있는 나" 인것 같다.

오류 해결하려, 코드를 보다 간결하게 효율적으로 쓰기 위해 머리를 굴려보고 답이 안나오면 좌절도 하고 꿈에서도 코드를 짜고 정말 미세한 차이로 답을 찾을 때는 짜릿하면서도 이런 걸로 고민했다니하면 조금은 허무한 기분이 드는 이 일련의 과정이 재미있었다.😁 그렇기에 일어나면 이 지긋지긋한 프로젝트 빨리 끝내야지 하면서도 코드를 짜기 시작하면 시간이 가는 줄 모르고 코드를 짤 수 있지 않았을까하는 생각이 든다.


그럼.. 이번 블로그 글을 끝으로 twitter 프로젝트 안녕 👋👋👋

profile
🐣프론트 개발 공부 중 (우테코 6기)

0개의 댓글