SubstrateKitties-2

코와->코어·2022년 5월 10일
0

part 2

우리의 고양이 노드와 소통할 수 있는 커스텀 리액트 컴포넌트 만들기

  1. 시작하기

Substrate Front-end Template 설치하기

git clone https://github.com/substrate-developer-hub/substrate-front-end-template.git
cd substrate-front-end-template
yarn install

폴더구조
substrate-front-end-template
|
+-- public
| |
| +-- assets <-- Kitty avatar PNG files
|
+-- src <-- our React components
| |
| +-- tests
| |
| +-- config <-- where to specify our custom types
| |
| +-- substrate-lib <-- lib to give access to PolkadotJS API
| | |
| | +-- components <-- contains TxButton, used throughout our application
| |
| ...
...

다른 터미널을 열어서 Part1에서 만든 노드 시작하기

Launch node-kitties from its directory.

cd kitties/
./target/release/node-kitties --dev --tmp

프론트엔드 디렉토리로 돌아와서, 시작하기
yarn start

무엇을 만들까?
Substrate Front-end template은 폴카닷JS API와 RPC엔드포인트를 사용해 Substrate노드와 소통
그래서 스토리지 아이템도 읽고 dispatchable 함수를 호출해 extrinsic을 만들 수도 있음

  1. Kitties.js
  2. KittyCards.js
  3. KittyAvatar.js

** 폴카닷 JS API 기초 공부하기
Basics and Metadata
RPC queries
Storage methods
Extrinsics methods
The Devhub substrate-frontend-template

  1. 커스컴 컴포넌트 만들기

Kitties.js만들기
가장 top-level에 있는 컴포넌트로, Apps.js에 의해 렌더링될 것

src폴더에 Kitties.js파일 만들고 임포트

import React, { useEffect, useState } from 'react'
import { Form, Grid } from 'semantic-ui-react'

import { useSubstrateState } from './substrate-lib'
import { TxButton } from './substrate-lib/components'

import KittyCards from './KittyCards'

폴카닷 JS API 인스턴스의 래퍼인 substrate-lib를 통해서 폴카닷 JS API를 사용할 것
계정 키를 가져올 수도 있음

const parseKitty = ({ dna, price, gender, owner }) => ({
dna,
price: price.toJSON(),
gender: gender.toJSON(),
owner: owner.toJSON(),
})

export default function Kitties(props) {
const { api, keyring } = useSubstrateState()
const [kittyIds, setKittyIds] = useState([])
const [kitties, setKitties] = useState([])
const [status, setStatus] = useState('')

parseKittiy() : 고양이 데이터를 가지고 있고 오브젝트를 리턴하는 함수
Kitties : 체인의 스토리지 아이템이 실시간으로 변하는 걸 감지하고 useEffect를 써서
우리의 다른 컴포넌트의 상태를 업데이트시켜줌

실시간으로 변화를 감지해야 하는 건

  • 고양이 양에 대한 스토리지 변화
  • 고양이 오브젝트의 변화

각각 subscription 함수를 만들 것임

  • 고양이 양: 우리의 고양이 팔레트 스토리지 아이템에 CountForKitties에 쿼리 날리는
    api.query.substrateKitties.countForKitties를 쓸 것
  • 모든 고양이 데이터를 api.query.substrateKitties.kitties메소드로 가져오고 parseKittiy()함수로
    파싱할 것

// Subscription function for kitty count
const subscribeCount = () => {
let unsub = null
const asyncFetch = async () => {
unsub = await api.query.substrateKitties.countForKitties(
async count => {
// Fetch all kitty keys
const entries = await api.query.substrateKitties.kitties.entries()
const ids = entries.map(entry => entry[1].unwrap().dna)
setKittyIds(ids)
}
)
}
asyncFetch()
return () => {
unsub && unsub()
}
}

// Subscription function to construct all kitty objects
const subscribeKitties = () => {
let unsub = null
const asyncFetch = async () => {
unsub = await api.query.substrateKitties.kitties.multi(
kittyIds,
kitties => {
const kittiesMap = kitties.map(kitty => parseKitty(kitty.unwrap()))
setKitties(kittiesMap)
}
)
}
asyncFetch()
return () => {
// return the unsubscription cleanup function
unsub && unsub()
}
}

asyncFetch에서 고양이 스토리지를 구독했다.
이 컴포넌트가 끝날 때, 구독 취소를 하고 싶다.
그래서 리턴함수로 정리해줌

리액트의 useEffect()로 subscribeCount와 subscribeKitties를 넘겨주면 됨

useEffect(subscribeCount, [api, keyring])
useEffect(subscribeKitties, [api, keyring, kittyIds])

  1. src/KittyAvatar.js
    고양이의 DNA와 PNG이미지를 맵핑시킬 것

import React from 'react'

// Generate an array [start, start + 1, ..., end] inclusively
const genArray = (start, end) =>
Array.from(Array(end - start + 1).keys()).map(v => v + start)

const IMAGES = {
accessory: genArray(1, 20).map(
n => ${process.env.PUBLIC_URL}/assets/KittyAvatar/accessorie_${n}.png
),
body: genArray(1, 15).map(
n => ${process.env.PUBLIC_URL}/assets/KittyAvatar/body_${n}.png
),
eyes: genArray(1, 15).map(
n => ${process.env.PUBLIC_URL}/assets/KittyAvatar/eyes_${n}.png
),
mouth: genArray(1, 10).map(
n => ${process.env.PUBLIC_URL}/assets/KittyAvatar/mouth_${n}.png
),
fur: genArray(1, 10).map(
n => ${process.env.PUBLIC_URL}/assets/KittyAvatar/fur_${n}.png
),
}

const dnaToAttributes = dna => {
const attribute = (index, type) =>
IMAGES[type]dna[index] % IMAGES[type].length]

return {
body: attribute(0, 'body'),
eyes: attribute(1, 'eyes'),
accessory: attribute(2, 'accessory'),
fur: attribute(3, 'fur'),
mouth: attribute(4, 'mouth'),
}
}

const KittyAvatar = props => {
const outerStyle = { height: '160px', position: 'relative', width: '50%' }
const innerStyle = {
height: '150px',
position: 'absolute',
top: '3%',
left: '50%',
}
const { dna } = props

if (!dna) return null

const cat = dnaToAttributes(dna)
return (

<div style={outerStyle}>
  <img alt="body" src={cat.body} style={innerStyle} />
  <img alt="fur" src={cat.fur} style={innerStyle} />
  <img alt="mouth" src={cat.mouth} style={innerStyle} />
  <img alt="eyes" src={cat.eyes} style={innerStyle} />
  <img alt="accessory" src={cat.accessory} style={innerStyle} />
</div>

)
}

export default KittyAvatar

KittyCards.js로부터 dna 하나만 전달받음

링크에 가서 PNG다운받고 public/assets/KittyAvatar라는 새 폴더를 만들어서 PNG들 붙여넣기

  1. KittyCards.js 안에 transferModal 작성하기
    KittyCards는 세 파트로 나눠질 것임
  • TransferModal, setPrice, buyPrice : TxButton컴포넌트를 사용한 모달 다이얼로그
  • KittyCard : KittyAvatar컴포넌트를 사용해서 고양이 아바타를 보여주는 카드
  • KittyCards : KittyCard를 그리드로 보여주는 컴ㅍ넌트

먼저 src/KittyCards.js를 만들고 임포트

import React from 'react'
import {
Button,
Card,
Grid,
Message,
Modal,
Form,
Label,
} from 'semantic-ui-react'
import KittyAvatar from './KittyAvatar'
import { useSubstrateState } from './substrate-lib'
import { TxButton } from './substrate-lib/components'

먼저 TransferModal을 보자

Substrate Front-end Template이 TxButton이라는 컴포넌트를 갖고 있는데
노드와 상호작용하는 전송 버튼을 포함하는 유용한 방법이다

노드에 트랜잭션을 보내고 고양이 팔레트에 서명된 extrinsic을 유발하도록 해줌

transfer button
modal(kitty id, receiver address)
transfer, cancel button

세 파트로 구성됨

나머지도 비슷한 구성임

리액트 훅으로 props 추출하기
const TransferModal = props => {
const { kitty, accountPair, setStatus } = props;
const [open, setOpen] = React.useState(false);
const [formValue, setFormValue] = React.useState({});
const formChange = key => (ev, el) => {
setFormValue({ ...formValue, [key]: el.value });
};
const confirmAndClose = unsub => {
setOpen(false)
if (unsub && typeof unsub === 'function') unsub()
}
return (
<Modal
onClose={() => setOpen(false)}
onOpen={() => setOpen(true)}
open={open}
trigger={

Transfer

}

<Modal.Header>Kitty Transfer</Modal.Header>
<Modal.Content>
  <Form>
    <Form.Input fluid label="Kitty ID" readOnly value={kitty.dna} />
    <Form.Input
      fluid
      label="Receiver"
      placeholder="Receiver Address"
      onChange={formChange('target')}
    />
  </Form>
</Modal.Content>
<Modal.Actions>
  <Button basic color="grey" onClick={() => setOpen(false)}>
    Cancel
  </Button>
  <TxButton
    label="Transfer"
    type="SIGNED-TX"
    setStatus={setStatus}
    onClick={confirmAndClose}
    attrs={{
      palletRpc: 'substrateKitties',
      callable: 'transfer',
      inputParams: [formValue.target, kitty.dna],
      paramFields: [true, true],
    }}
  />
</Modal.Actions>
)
  1. KittyCard 작성하기
    Kitty.js로부터 받은 props로 컴포넌트 렌더링

Semantic UI Card컴포넌트 사용할 것임

// Use props
const KittyCard = props => {
const { kitty, setStatus } = props
const { dna = null, owner = null, gender = null, price = null } = kitty
const displayDna = dna && dna.toJSON()
const { currentAccount } = useSubstrateState()
const isSelf = currentAccount.address === kitty.owner

return (

{isSelf && (

Mine

)}
{/ Render the Kitty Avatar /}

<Card.Content>
{/ Display the Kitty DNA /}
<Card.Meta style={{ fontSize: '.9em', overflowWrap: 'break-word' }}>
DNA: {displayDna}
</Card.Meta>
{/ Display the Kitty Gender, Owner, and Price /}
<Card.Description>

    <p style={{ overflowWrap: 'break-word' }}>Gender: {gender}</p>
    <p style={{ overflowWrap: 'break-word' }}>Owner: {owner}</p>
    <p style={{ overflowWrap: 'break-word' }}>
      Price: {price || 'Not For Sale'}
    </p>
  </Card.Description>
</Card.Content>
<Card.Content extra style={{ textAlign: 'center' }}>

{owner === currentAccount.address ? (
<>


</>
) : (
<>

</>
)}
</Card.Content>

}
주인이 옮기는 경우에만 Transfer Modal과 SetPRice 컴포넌트가 뜨도록 해줌

  1. KittyCards 쓰기
  • 하나도 없으면 하나도 없으니까 하나 만들라는 메시지 보여주기
  • 있으면 3열로 보여주기

const KittyCards = props => {
const { kitties, setStatus } = props

if (kitties.length === 0) {
return (

<Message.Header>
No Kitty found here... Create one now! 

👇

</Message.Header>

)
}

return (

{kitties.map((kitty, i) => (
<Grid.Column key={kitty-${i}}>

</Grid.Column>
))}

)
}
export default KittyCards

SetPrice와 BuyKitty는 안 다룰것임 솔루션 봐라

  1. Kitties.js로 돌아와서 완성하기

return <Grid.Column width={16}>

Kitties

이거 복붙하고 : Grid안에 KittyCard 컴포넌트 넣음

{status}
; }

이거도 복붇하고 : TxButton 컴포넌트 렌더링하기 위해 form 사용

  1. App.js 업데이트

임포트문 추가
import Kitties from './Kitties'

Container에 다음 줄 추가
<Grid.Row>

</Grid.Row>

전체 정답 코드
https://github.com/substrate-developer-hub/substrate-front-end-template/blob/tutorials%2Fsolutions%2Fkitties/src/KittyCards.js

profile
풀스택 웹개발자👩‍💻✨️

0개의 댓글