코드스테이츠 BEB 04 Section 5 Project1 회고

‍하상우·2022년 6월 19일
1

BEB Project1

목록 보기
1/1

코드스테이츠 에서 진행되는 프로젝트 1회고를 해보려 한다.

OpenSea를 클론코딩하는게 미션이었다. 월~목까지 4일만에 만들어야하는 일정이라 전체를 개발하기 보다는 다음과 같이 개발하기로 월요일에 이야기를 나눴다.

갑자기 무슨 레퍼런스 코드같은게 없는 상태에서 진행하려다보니 무슨 기능을 구현할지 어떻게 만들지에 대해 셋팅하는게 첫번째 난관이었다.

정글 한가운데 떨어진것 같은 느낌이 들기 시작했다

깃허브 링크(나)

https://github.com/matt700395/beb-04-BCZ.git

깃허브 링크(프로젝트 공용)

https://github.com/codestates/beb-04-BCZ


배포링크

React App



팀장정하기

아름님, 세규님 그리고 나 이렇게 3명이서 팀이 되어서 진행했다.

아름님께서 흔쾌히 팀장을 맡아주셨다. 끝날시점이 되니 정말 잘 이끌어주신것 같아 감사하다는 말을 이 블로글을 통해 전해드리고 싶다. :)

내 상황 파악하기

내가 어디까지 개발가능한지 파악하고 이를 팀원들에게 알려야겠다는 생각을 했다.

개인적으로 프론트개발자로 Career path를 생각하고있지 않았기때문에 React개발에 자신이 없었다. React 개발 공부를 하는 시점에 코로나며 뭐며 몸이 너무 않좋아서 공부를 정말 대충했었다. 지금 머리에 남아있는게 0이라고 해도 무방할 정도였다.

내 부끄러운 실력을 Open하는것 자체가 꺼려지는 일이었지만 용기내서 내 현상태를 말씀드렸다.(그렇다고 개발을 대충한건 절대 아니다!!ㅋㅋㅋ)

기획

1일차 기획회의 내용 회고

기획이라고 하기엔 너무 거창했다. 창업을 시도했던 사람이라면 잘 알고있을 MVP 를 뽑아내는게 관건이었다.(사실 내가 자신있는 부분이다!)

그래서 회의를 하면 지속적으로 내가 생각하는 최소한으로 구현되어야할 기능을 어필하고 뒷받침할수있는 이전기수분들의 프로젝트 결과물들을 검색해서 공유하며 이야기를 진행했다.

기획 회의를 하다보니 이야기가 정확히 셋팅되지가 않았다. 지금 회고해보면 온라인 회의인데다가 카메라도 다 꺼져있고 ‘말’로만 모든 소통을 해서 였던것 같았다. 다행이 2일차부터는 서로의 이야기를 설명하기위해 화면 공유 등을 통해 소통이 조금더 나아졌다.

1일차 결정된 사항

일단 강의나 이전 기수분들의 레퍼런스를 보며 공부를 하고 내일 아침 다시 회의하기

소통에서 문제가 좀 있기도하고 뭘 아무것도 모르는채로 만드려다보니 뜬구름을 잡는 느낌이라 참고가될만한 프로젝트나 강의를 공부하고 내일 다시 이야기 하기로했다.

그리고 각자 프론트, 백엔드, 스마트컨트랙트 3 part로 역할을 분배하고 회의를 마무리 지었다

역할 배분

프론트: 한아름

백엔드: 박세규

스마트컨트랙트: 하상우


2일차 기획회의 내용 회고

만들어야하는 기능들에 대해 팀원분들과 일단 리스트업을 했다.

그리고 필수적으로 개발되어야 하는 기능과 있으면 좋지만 필수적이지 않은 기능들을 분리했다.(mvp 추출과정이라고 보면된다)

필수기능

  1. 메타마스크 지갑과 연결 (create, mypage에 필요)
  2. 메인 페이지가 있다 (explore, create)
  3. NFT를 탐색할 수 있다. (explore)
  4. NFT를 민팅할 수 있다. (create)
  5. 나의 소유 NFT를 볼 수 있다 (mypage)

추가기능(+)

  1. NFT를 사고팔 수 있다. (sale, transfer)
  2. NFT를 검색할 수 있다 (search)
    1. NFT 이름 → items
    2. 컨트랙트 주소 → collections

이제 업무 분담을 하려하는데 아름님이 분야별로 역할을 나누는것보다는 기능별로 역할을 나누는게 좋을것 같다는 의견을 제시해주셨다.

그리고 기반을 삼는 코드가 없는것 보다는 코드스테이츠에서 프로젝트에 참고하라고 제공해주고 1일차에 실습해본 코드를 기반으로 프로젝트를 만들어가자고 결론내었다.

모든 팀원이 동의해서 기능별로 역할을 분배하기 시작했다. 굉장히 자연스럽게 목적중심 조직으로 변화하는 순간이었다ㅋㅋㅋㅋ

이때 부터였던것 같다. 프로젝트가 재밌어지기 시작했던게…

각자의 상황 및 선호를 논의하며 다음과 같이 개발할 기능들을 셋팅하고 역할을 부여하였다

목적 중심, 기능중심의 역할 분배

  • 실습 프로젝트 beb 레포에 init

create → 박세규

explore → 한아름

profile → 하상우

개인적으로 많이 부족하다고 판단해서 상대적으로 쉽다고 모두가 판단하는 profile을 맡고 싶다고 말씀드렸다. 모든 팀원분들이 흔쾌히 OK를 해주셔서 위 처럼 역할 분배가 되었다

개발로그

2일차

솔직히 나는 2일차까지도 개발을 거의 못했다…. 스마트 컨트랙트도, React도 UrClass의 프로젝트 실습 내용도 제대로 이해하지 못한 상태였다. 그래서 2일차 내내 실습 코드와 React 복습, 다른 nft마켓플레이스 제작 무료 강의등을 보며 학습을 진행했다.

와중에 아름님과 세규님은 뚝딱뚝딱 코드를 만들고 계셨다. 조급함이 생기기 기작했다

3일차

아직 스스로 개발한 내용이 없었다. 하지만 개발시작과 결과물을 내는게 시급해졌다. 방향을 선회해서 아름님과 세규님이 만드신 코드를 빠르게 이해하는걸 목표로했다.

처음에는 머리가 깨질것 같았는데, 하나씩 머리깨져가면서 구글링하니 안갯속같았던 React와 솔리디티, 스마트컨트랙트의 지식들이 선명해지는 경험을 했다.

개발 회고

아름님이 Explore라는 기능과 페이지를 만들기로 하셨다.

Explore는 배포된 스마트 컨트랙트에 민팅된 모든 nft카드를 불러와서 로딩시키는 페이지였다.

아름님이 만드신 Explore를 재활용해서 Profile 페이지에서는 현재 로그인된 사용자의 지갑주소와 nft의 owner 지갑주소가 일치하면 보여주는 로직을 구성해서 구현하고자 했다.

처음에 상태함수를 사용하는 것에 대해 좀 어색하고 어려운점이 있었는데, 프로젝트를 구성하면서 동작 방식을 완전히 익히게 되었다.

개발을 하면서 아름님이 해결하지 못하신 문제를 내가 해결한 것도 있다!!(이게 유일하게 내가 주요하게 한일이라고 봐도 무방…)

내가 해결한 문제

처음 화면이 로딩될때 각 nft 토큰들이 2번씩 로딩되는 문제가 있었습니다. 알고보니 index.js에<React.StrictMode> 태그가 있는데, 이걸 없애야 이 문제가 해결되는 문제였습니다.

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(

  // <React.StrictMode>
  <BrowserRouter>
    <App />
  </BrowserRouter>
  // </React.StrictMode>
);

개발시에 테스트때문에 <React.StrictMode> 이 태그가 사용된다는데 아직 정확히 어떤 로직으로 진행되는지 모르긴합니다.

문제를 해결하기전에 서칭을할때는 useEffect를 사용함에 있어서 내가 모르는 동작방식이나 뭔가 있는것 같았습니다. 그래서 useEffect에 대해 찾아보다가 <React.StrictMode>가 문제라는걸 발견해서 해결하게 되었습니다.


코드 간단한 분석 내용

이번 프로젝트를 통해서 useState의 사용에 대해 다시 정리할수있었다

아래 내용은 프로젝트에 사용된 useState 들을 정리해봤다

profile 파일이 route로 받는 요소들

account, isLogin, web3, setNfts, nfts, limit, page, setPage

각각이 App.js에서 어떻게 셋팅되어서 전달되는지 보자

초기 셋팅

function App() {
  const [account, setAccount] = useState("");
  const [web3, setWeb3] = useState();
  const [nfts, setNfts] = useState([]);
  const [isLogin, setIsLogin] = useState(false); // Profile에 필요
  const [page, setPage] = useState(1); // 페이지네이션 -> Explore, Profile
  const limit = 8; // 페이지네이션 -> Explore, Profile

account : 메타마스크에 연결된 계정 정보

//App.js 중에서
// 메타마스크 지갑과 연결
  const connectWallet = async () => {
    if (isLogin) {
      alert(`연결된 계정: ${account}`);
    } else {
      let accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setAccount(accounts[0]);
      setIsLogin(true);
    }
  };

메타 마스크 지갑과 연결하면서

await window.ethereum.request({method: "eth_requestAccounts",}); 를 통해 비동기로 전달받은 계정 정보를 setAccount(); 를 통해서 account를 셋팅함

isLogin

//App.js 중에서
// 메타마스크 지갑과 연결
  const connectWallet = async () => {
    if (isLogin) {
      alert(`연결된 계정: ${account}`);
    } else {
      let accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setAccount(accounts[0]);
      setIsLogin(true);
    }
  };

메타마스크 연결 함수가 실행되면 연결되었다는것이니, 초기셋팅된 false에서 true로 변경되어 저장됨

web3

useEffect(() => {
    if (typeof window.ethereum !== "undefined") {
      // window.ethereum이 있다면
      try {
        const web = new Web3(window.ethereum); // 새로운 web3 객체를 만든다
        setWeb3(web);
      } catch (err) {
        console.log(err);
      }
    }
  }, []);

Getting Started | MetaMask Docs

위 북마크를 찾아보면 나온다

window.ethereum 은 메타마스크가 크롬에서 설치되어서 실행가능하면 동작가능한 api모듈이다

window.ethereum api 에 관해 더 알고싶다면 아래 북마크를 정독하면 좋다

Ethereum Provider API | MetaMask Docs


setNfts

Explore 함수 전체

function Explore({ web3, nfts, setNfts, limit, page, setPage }) {
  const [total, setTotal] = useState(0);

  useEffect(() => {
    const getNfts = async () => {
      try {
        const tokenContract = await new web3.eth.Contract(erc721Abi, erc721Addr);
        const totalSupply = await tokenContract.methods.totalSupply().call();
        setTotal(totalSupply);

        // 리렌더링 될 때마다 totalSupply만큼 계속 state 값에 추가 되는 오류 방지
        if (nfts.length < totalSupply) {
          let arr = [];
          for (let i = 1; i <= totalSupply; i++) {
            arr.push(i);
          }

          for (let tokenId of arr) {
            let tokenOwner = await tokenContract.methods.ownerOf(tokenId).call();
            let tokenURI = await tokenContract.methods.tokenURI(tokenId).call();
            setNfts((prevState) => {
              return [...prevState, { tokenOwner, tokenId, tokenURI }];
            });
          }
        }
      } catch (err) {
        console.log(err);
      }
    };

    if (web3) {
      getNfts();
    }
  }, [web3]);

setNfts 함수를 Explore에서 받아오는 이유는 추가되는 토큰의 정보들을 nfts에 셋팅해주기 위함입니다.

for (let tokenId of arr) {
            let tokenOwner = await tokenContract.methods.ownerOf(tokenId).call();
            let tokenURI = await tokenContract.methods.tokenURI(tokenId).call();
            setNfts((prevState) => {
              return [...prevState, { tokenOwner, tokenId, tokenURI }];
            });
          }

nfts

//App.js
function App() {
  const [account, setAccount] = useState("");
  const [web3, setWeb3] = useState();
  const [nfts, setNfts] = useState([]);
  const [isLogin, setIsLogin] = useState(false); // Profile에 필요
  const [page, setPage] = useState(1); // 페이지네이션 -> Explore, Profile
  const limit = 8; // 페이지네이션 -> Explore, Profile

App.js에서 보면 초기에 nfts는 빈 배열로 선언하였습니다.

여기에 setNfts 함수를 통해 nfts에 요소들이 추가될예정입

여기서 하나씩 뽑아내서 nft를 보여주게 됩니다.

limit

//App.js
function App() {
  const [account, setAccount] = useState("");
  const [web3, setWeb3] = useState();
  const [nfts, setNfts] = useState([]);
  const [isLogin, setIsLogin] = useState(false); // Profile에 필요
  const [page, setPage] = useState(1); // 페이지네이션 -> Explore, Profile
  const limit = 8; // 페이지네이션 -> Explore, Profile

페이지 네이션을 위한 limit입니다

한 페이지당 최대 8개의 esset을 보여줄 예정입니다

page

// src/page/Explore.js
return (
    <div id="asset-list-container">
      <div id="asset-list-body">
        <div id="asset-list-title">
          <p>Explore...</p>
        </div>
        {/* {console.log(`totalSupply: ${total}, nfts.length: ${nfts.length}`)}
        {console.log("nfts: ", nfts)} */}
        <div id="asset-totalNum">total: {total}</div>
        <div id="asset-list-content">
          <NftCardList nfts={nfts} web3={web3} limit={limit} page={page} />
        </div>
        <Pagination total={total} limit={limit} page={page} setPage={setPage} />
      </div>
    </div>
  );

nftcardList와 Pagenation 을 로딩할때 인자로 넘여주게 됩니다

Profile에서도 동일합니다

nftcardList에서 page의 이용

function NftCardList({ nfts, web3, limit, page }) {
  const offset = (page - 1) * limit;
  return (
    <div className="nft-list-container">
      <div className="nft-list">
        {nfts.slice(offset, offset + limit).map((nft) => (
          <NftCard nft={nft} key={nft.tokenId} />
        ))}
      </div>

    </div>
  );
}

export default NftCardList;

offet으로 전체 pagenation 될 페이지수를 정합니다

nfts로 전달된 전체 토큰 정보의 배열을 offet으로 나눕니다.

이후 mapping을 통해 반복적으로 nft라는 인자로 전달한다

NftCard 페이지로 nft와 nft.tokenId를 전달한다

→NftCard에 해당하는 설명 파트로 넘어가서 다음 flow를 살펴보자

Compoenets/NftCard.js 살펴보기

Pagenation에서의 page와 setPage의 이용

function Pagination({ total, limit, page, setPage }) {
  const numPages = Math.ceil(total / limit);

  return (
    <>
      {/* <p>page</p> */}
      <Nav>
        <Button onClick={() => setPage(page - 1)} disabled={page === 1}>
          &lt;
        </Button>
        {Array(numPages)
          .fill()
          .map((_, i) => (
            <Button key={i + 1} onClick={() => setPage(i + 1)} aria-current={page === i + 1 ? "page" : null}>
              {i + 1}
            </Button>
          ))}
        <Button onClick={() => setPage(page + 1)} disabled={page === numPages}>
          &gt;
        </Button>
      </Nav>
    </>
  );
}

setPage

function Pagination({ total, limit, page, setPage }) {
  const numPages = Math.ceil(total / limit);

  return (
    <>
      {/* <p>page</p> */}
      <Nav>
        <Button onClick={() => setPage(page - 1)} disabled={page === 1}>
          &lt;
        </Button>
        {Array(numPages)
          .fill()
          .map((_, i) => (
            <Button key={i + 1} onClick={() => setPage(i + 1)} aria-current={page === i + 1 ? "page" : null}>
              {i + 1}
            </Button>
          ))}
        <Button onClick={() => setPage(page + 1)} disabled={page === numPages}>
          &gt;
        </Button>
      </Nav>
    </>
  );
}

페이지 숫자를 셋팅할때 사용됩니다


Compoenets/NftCard.js 살펴보기

export default function Nft({ nft}) {
  const { tokenId, tokenURI } = nft;
  const navigate = useNavigate();
  const handleCardClick = () => {
    navigate(`/list/${tokenId}`);
  };

  return (
    <div className="nft-card" onClick={handleCardClick}>
      <div className="nft-card-title">#{tokenId} NAME : qwerty</div>
      <img className="nft-card-img" alt={`Img ${tokenId}`} src={tokenURI} />
    </div>
  );
}

배포

배포는 netlify를 통해 진행했다

쉽긴한데 내가할때는 에러가 계속 떠서 6시간넘게 낑낑댔었다.

깃헙에 올린거 netlyfi에 올리려고 했는데 계속 build.command error 뜸

setting에 들어가면 Building setting을 할수있다

원래는 Building command가 npm run build 로 되어있는데 이러면 CI=true 로 자동 설정된다

네틀리파이에서 CI=true이면 building때 생기는 waring들을 에러로 인식한다

그래서 CI=false설정을 추가적으로 해주면 배포가 정상적으로 된다

여기서 정말 고생했던건 바로 “띄어쓰기” 때문이다…

반드시 CI=false npm run build 로 써야한다.

CI =false npm run build

CI= false npm run build

CI = false npm run build

모두 안된다 반드시 CI=false npm run build 로 작성해야 제대로 명령어가 작동되어 배포가 된다…ㅠㅠ


개선할것들

<개인적으로>

개발을 하면서 사실 스마트 컨트랙트를 개발한 내용이 없다

nft로 발행해둔 컨트랙트를 이용해서 react와 web3js를 사용하는것만 진행했다

react공부가 정말 중요하다는걸 알게 되었고, js를 공부하는것도 중요하다는 생각이 매우 강하게 들었다

주말에 할것 : React 다시 정리하면 제대로 공부하기

<프로젝트적으로>

profile 개선

연결된 계정 주소 보여주거나

계정연결되면 Connected로 색바꿔서 보이게 하기?

전반적으로 보이는 문제 → 카드 로딩하는데 시간이 많이 걸림

처음 사이트 접속했을때 어느정도는 로딩 받아두면 어떨까??

프론트에서는 멀쩡하게 보이지만 → 백에서는 미리 로딩을 다 받아두면..??

미리 로딩 받아둬야할 것들

  1. Explore에서 최대 16개 카드(페이지 2개짜리)
  2. Profile에서 최대 16개 미리 로딩

logic

  1. main화면이 실행되자마자 Explore에서 최대 16개 카드 로딩
  2. 미리 Explore한 카드에서 필터링해서 보여주기

위 logic에서 추가로 개선해야할 지점

Explore가 16개가 넘어가면 추가적으로 로딩을 어떻게 할지

아직 부족한 부분이 많다. 개인적으로 창업을 위한 프로젝트도 한번 만들어보면 좋을것 같다!

profile
시골도시에서 실리콘 밸리 만들기

0개의 댓글