16. React Portal 로 모달 생성하기
Portals란 ?
Portal은 부모 컴포넌트의 DOM 계층구조 바깥에 있는 DOM 노드로 자식을 렌더링 하는 최고의 방법을 제공한다.
ReactDOM.createPortal(child, container)
첫번째 인자 child는 엘리먼트, 문자열, 혹은 fragment와 같은 어떤 종류이든 렌더링할 수 있는 React자식이다. 두번째 인자 container는 DOM 엘리먼트이다.
Portal을 이용한 모달 생성
테스트 프로젝트 생성 npx create-react-app react-portal-modal
실행npm run start
문제점
첫번째 div와 두번째 div가 같은 z-index(1)가지고
첫번째 div안에 자식인 모달이 z-index 1000인데도
두번째 div가 모달보다 상위에 있게되는 문제
해결
모달을 포털에 넣어주어 정리하여보자.
포털을 사용하면 #root
의 자식이 아무리 z-index가 높다고 하더라도 #portal
은 아무런 영향을 받지 않게된다.
- Container 생성
리액트 템플릿 하나를 이용(#root)해서 js로 접근한 다음에 엘레멘트생성하는데#root
와 나란히#portal
을 생성해준다.<div id="root"></div> <div id="portal"></div>
- createPortal 이용
import ReactDOM from 'react-dom' ...
const Modal = ({ open, onClose, children }) => {
if(!open) return null; // false면
return ReactDOM.createPortal(
<>
{children}
</>,
document.getElementById('portal')
);
}
#### Portal 장점
1. z-index에 관계없이 잘 나온다. 또한 계층구조(hierarchy)도 내부가 아닌 밖으로 나오는것을 볼 수 있다.
2. 이벤트 버블링이 가능하다
이벤트 버블링은 중첩된 자식 요소에서 이벤트가 발생할 때 그 이벤트가 부모로 전달된다. 비록 포탈로 생성한 부분이 부모 DOM 밖에 생성되더라도(DOM 트리에서 위치에 상관없이) portal은 여전히 React 트리에 존재해서 React 트리에 포함된 상위로 이벤트버블링이 가능하다.
(요약: 비록 DOM 트리가 상위가 아니더라도 리액트 트리에서 상위이면 이벤트 버블링 가능)

DOM트리가 portal에 있더라도


React트리에 존재해서 그 상위인 div로 이벤트 버블링이 가능(모달을 클릭했는데 그 상위 div이벤트 버블링 확인)
## 17. Sprites 가져오기


1. fetchPokemonData안에 pokemonData에서 sprites도 함께 받아오기
2. formattedPokemonData에서 `sprites: formatPokemonSprites(sprites)`추가
3. formatPokemonSprites 작성
### formatPokemonSprites 함수
`console.log(Object.keys(newSprites))`

`Object.keys(Array)`는 key만 모아둔거 확인할 수 있음
sprites의 값이 string이 아닌것 지우기

map으로 돌려서 string이 아니라면,
배열에서 해당 키 삭제 `delete newSprites[key]`
<결과>

#### 정의
```js
const formatPokemonSprites = (sprites) => {
const newSprites = {...sprites};
// console.log(Object.keys(newSprites))
// string이 아닌 값을 가졌다면 삭제하기(url 있는것만 남기기위해)
(Object.keys(newSprites).forEach(key => {
if(typeof newSprites[key] !== 'string') {
delete newSprites[key]
}
}));
// console.log(newSprites);
return Object.values(newSprites); // url만 return
}
return
<div className="flex my-8 flex-wrap justify-center">
{pokemon.sprites.map((url, index) => (
<img
key={index}
src={url}
alt="sprites"
/>
))}
</div>
18. Description 가져오기
fetchPokemonData에 설명데이터 추가
const formattedPokemonData = {
id,
name,
weight: weight / 10,
height: height / 10,
previous: nextAndPreviousPokemon.previous,
next: nextAndPreviousPokemon.next,
abilities: formatAbilities(abilities),
stats: formatPokemonStats(stats),
DamageRelations,
types: types.map(type => type.type.name),
sprites: formatPokemonSprites(sprites),
description: await getPokemonDescription(id)
};
api에서 가져오기
const getPokemonDescription = async (id) => {
const url = `https://pokeapi.co/api/v2/pokemon-species/${id}/`;
const {data: pokemonSpecies} = await axios.get(url);
// return data;
}
데이터 필터링(한국어만) + 줄바꿈 띄어쓰기로 대체
const filterAndFormatDescription = (flavorText) => {
const koreanDescriptions = flavorText
?.filter((text) => text.language.name === "ko")
.map((text) => text.flavor_text.replace(/\r|\n|\f/g, ' '));
return koreanDescriptions;
}
const getPokemonDescription = async (id) => {
const url = `https://pokeapi.co/api/v2/pokemon-species/${id}/`;
const {data: pokemonSpecies} = await axios.get(url);
// 한국어 description필터링
const descriptions = filterAndFormatDescription(pokemonSpecies.flavor_text_entries);
return descriptions;
}
랜덤으로 하나 보여주기
배열 A중에 하나 랜덤으로 보여주는 함수
const A = [1,2,3,4]
A[Math.floor(Math.random() * A.length)]
getPokemonDescription
에서 랜덤부분 추가하여 리턴값 넣는다.return descriptions[Math.floor(Math.random() * descriptions.length)]
UI 생성
<h2 className={`text-base font-semibold ${text}`}>설명</h2>
<p className="text-md leading-4 font-sans text-zinc-200 max-w-[30rem] text-center">
{pokemon.description}
</p>
19. 버그 해결하기
디테일 페이지에서 앞/뒤버튼 미작동
useEffect에서 디펜던시 pokemonId
추가해준다.
로딩 state 활용
앞/뒤눌렀을때 좀 기다렸다가 데이터를 보여주는것이 아닌
데이터가 준비되기전 로딩을 true로 만들어주고
데이터 준비후 false로 만들어줄것이다.
이부분은 isLoading 초깃값을 false로 하고
useEffect에서 setIsLoading(true)후
fetchPokemonData에서 try 안에 마지막에 setIsLoading(false)로 만들어준다.
useEffect(() => {
setIsLoading(true);
fetchPokemonData();
}, [pokemonId]);
'Frontend > React' 카테고리의 다른 글
[React] 포켓몬도감 만들기8 - Typescript로 변경하기 (2) | 2023.12.08 |
---|---|
[React] 포켓몬도감 만들기7 (0) | 2023.12.08 |
[React] 포켓몬도감 만들기5 (1) | 2023.12.08 |
[React] 포켓몬도감 만들기4 (0) | 2023.12.08 |
[React] 포켓몬도감 만들기3 (1) | 2023.12.08 |