본 글은 Road to Web3 예제를 공부하며 몰랐던 내용들을 정리한 글입니다. 잘못된 내용이 있다면 언제든지 지적해주세요 😇
Axios는 백엔드와 프론트엔드가 서로 통신하기 위한 HTTP 비동기 라이브러리다.
자바스크립트 자체에서 fetch
를 쓰기도 하지만 React나 Vue에서 ajax를 구현할 때 Axios를 사용한다. 따로 설치를 해야하는 단점이 있지만 fetch보다 다양한 기능을 제공하며 비교적 간단한 문법을 통해 사용 가능하다. 더 자세한 차이점은 여기에 잘 나와있다!
FormData는 HTML 단이 아닌 자바스크립트 단에서 폼 데이터를 다루는 객체라고 보면 된다. 보통은 HTML5의 <form>
태그를 이용해 input 값을 서버에 전송하지만, 자바스크립트에서 FormData()
클래스를 이용해 똑같이 스크립트로도 전송을 할수 있다. 그리고 HTML에서의 Submit 제출 동작은 ajax를 통해 서버에 제출한다고 보면 된다.
보통의 Ajax 통신에는 FormData를 잘 사용하지 않는다. 주로 JSON을 이용한 Key-Values 데이터 전송을 하기 때문이다. 하지만 이미지 같은 멀티미디어 파일을 페이지 전환 없이 폼 데이터를 비동기로 제출 할 경우 FormData객체를 사용한다. 또한 자바스크립트로 좀 더 타이트하게 폼 데이터를 관리하고 싶을 때 formData
객체를 이용한다.
추가적으로 formData
는 일반적인 객체와는 다르게 문자열 화할 수 없고, console.log
로 출력해볼 수 없다. 따로 확인 하기 위해서는 for문을 이용해야 한다고 한다.
//IPFS에 이미지 업로드
async function OnChangeFile(e) {
var file = e.target.files[0];
try {
const response = await uploadFileToIPFS(file);
if(response.success === true) {
console.log("Uploaded image to Pinata: ", response.pinataURL)
setFileURL(response.pinataURL);
}
}
catch(e) {
console.log("Error during file upload", e);
}
}
JS에서는 input 태그의 속성 중 type="file"
을 이용하면 파일을 업로드 할 수 있다. 사용자가 업로드 한 파일은 e.target.files
에 담겨있으며, multipl 속성이 있는 태그는 한번에 여러 개 파일을 첨부할 수 있기 때문에 [0] 번 이외에 인덱스 값을 추가로 가질 수 있다.
사실 pinata.js
에서 FormData를 사용한 이유는 사용자가 업로드한 파일을 서버에 그대로 전달할 수 없기 때문이다.
function Navbar() {
const [connected, toggleConnect] = useState(false);
const location = useLocation();
const [currAddress, updateAddress] = useState('0x');
//...
Navbar 파일 상단에 useLocation()
을 사용한 것을 볼 수 있다. 찾아보니 현재 페이지 주소를 알 수 있도록 해주는 react hook이었다. 예전 API reference를 보면 위에서 사용한 pathname
외에도 다양한 기능을 제공한다.
async function connectWebsite() {
const chainId = await window.ethereum.request({ method: 'eth_chainId' });
if(chainId !== '0x5')
{
//alert('Incorrect network! Switch your metamask network to Rinkeby');
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x5' }],
})
}
await window.ethereum.request({ method: 'eth_requestAccounts' })
.then(() => {
updateButton();
console.log("here");
getAddress();
window.location.replace(location.pathname)
});
}
컨트랙트가 잘 작동하려면 사용자 지갑의 네트워크가 제대로 설정돼있는지 확인이 필요하다. 이를 위해 메타마스크에서는 네트워크가 올바르게 되어있는지 확인하는 메소드를 제공한다. window.ethereum.request({ method: '사용하고자 하는 메소드' })
형식으로 사용되며 이 외에도 다양한 메소드를 제공한다.
ethereum.request
을 기본 형식으로 하며 파라미터만 바꿔주면 된다.
예제에서 두번째 부분에 사용된 eth_requestAccounts
는 연결된 지갑 주소를 string array 형태로 리턴한다.
각 네트워크의 Hex 값은 위와 같으며, 더 자세한 사항은 메타마스크 docs에 나와있다.
useEffect(() => {
let val = window.ethereum.isConnected();
if(val)
{
console.log("here");
getAddress();
toggleConnect(val);
updateButton();
}
window.ethereum.on('accountsChanged', function(accounts){
window.location.replace(location.pathname)
})
});
이더리움에서는 useEffect 부분에 다음과 같이 eventListener를 추가할 수 있다.
window.ethereum.on('이벤트 이름', eventListener를)
그런데 Navbar.js 에는 따로 설정한 이벤트가 없어서 찾아보니 메타마스크에서 제공하는 이벤트였다. accountsChanged 이벤트는 말 그대로 연결된 지갑의 주소가 바뀔 때마다 작동한다.
이 외에도 connect
, disconnect
, chainChanged
, message
등의 이벤트를 제공한다.
async function getAllNFTs() {
const ethers = require("ethers");
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
let contract = new ethers.Contract(MarketplaceJSON.address, MarketplaceJSON.abi, signer)
//create an NFT Token
let transaction = await contract.getAllNFTs()
//Fetch all the details of every NFT from the contract and display
const items = await Promise.all(transaction.map(async i => {
const tokenURI = await contract.tokenURI(i.tokenId);
let meta = await axios.get(tokenURI);
meta = meta.data;
let price = ethers.utils.formatUnits(i.price.toString(), 'ether');
let item = {
price,
tokenId: i.tokenId.toNumber(),
seller: i.seller,
owner: i.owner,
image: meta.image,
name: meta.name,
description: meta.description,
}
return item;
}))
updateFetched(true);
updateData(items);
}
NFT의 정보를 가져와서 화면에 보여주는 코드다.
ERC721에서 tokenURI(tokenId)
메소드를 사용할 수 있는데, tokenId를 인자로 넣어주면 해당 NFT의 tokenURI를 string 형태로 리턴해준다. OpenZeppelin docs에서 다양한 메소드를 찾아볼 수 있다.
Promise.all
은 여러 프로미스를 병렬적으로 처리할 때 사용한다. 모든 프로미스가 성공적으로 실행되고 나서 반환 값을 배열에 넣어 반환한다. 하지만 도중에 하나라도 프로미스가 실패하면 성공했던 프로미스의 결과는 무시되며 reject된다. Dev scroll 블로그에서 좋은 그림이 있어서 가져왔다.
위 코드에선 axios.get
으로 메타데이터를 가져올 때 Promise.all
을 사용했다. 그런데 여러 메타데이터를 가져오는 과정에서 잘못된 주소가 하나 껴있다면 전부 취소되기 때문에 비효율적일 수 있다. 따라서 Promise.allSettled
를 사용하는 것이 좋다는 의견이 많다. Promise.allSettled
는 마찬가지로 여러 프로미스를 병렬적으로 처리하지만 하나의 프로미스가 실패해도 계속 진행된다.
따라서 어떤 프로미스가 실패했는지 알 수 있으며, 성공한 프로미스의 결과는 그대로 가져와서 사용할 수 있으니 훨씬 괜찮다. 각자 장단점이 있기 때문에 필요한 곳에 Promise.all
과 Promise.allSettled
를 적절하게 사용하는 것이 좋을 것 같다.
axios를 이용해 get 요청을 보내면 response를 받을 수 있는데 이 때 response.data
에서 내가 원하는 정보들을 가져올 수 있다.
코드 작성 예시는 다음과 같다.
완벽한 NFT 마켓플레이스를 만든 것이 아니라 조금 아쉬웠지만 이번 예제에서는 JS에서 부족한 부분을 채울 수 있어서 좋았다. 명확하지 않았던 Axios의 개념과 사용방법, 그 동안 사용하지 않았던 FormData 등 이번에는 프론트 쪽 개념을 배울 수 있었다. 자바스크립트 공부도 소홀히 하면 안 되겠다는 느낌을 매우 강하게 받았다.