본문 바로가기
Frontend/React

[React] 포켓몬도감 만들기6

by 디스코비스킷 2023. 12. 8.
반응형

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은 아무런 영향을 받지 않게된다.

  1. Container 생성
    리액트 템플릿 하나를 이용(#root)해서 js로 접근한 다음에 엘레멘트생성하는데
    #root와 나란히 #portal을 생성해준다.
     <div id="root"></div>
     <div id="portal"></div>
  1. 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 트리가 상위가 아니더라도 리액트 트리에서 상위이면 이벤트 버블링 가능)
![](https://velog.velcdn.com/images/jngmnj/post/f843bb1c-1bb5-491f-b7db-da6640d2ae95/image.png)
DOM트리가 portal에 있더라도
![](https://velog.velcdn.com/images/jngmnj/post/69924e7b-36e0-4228-91a6-a9223b5c969b/image.png)
![](https://velog.velcdn.com/images/jngmnj/post/dcf6ae87-018f-436d-912c-ea5861cc324f/image.png)
React트리에 존재해서 그 상위인 div로 이벤트 버블링이 가능(모달을 클릭했는데 그 상위 div이벤트 버블링 확인)



## 17. Sprites 가져오기
![](https://velog.velcdn.com/images/jngmnj/post/be97cded-589b-4ba6-8618-d55284a6c401/image.png)

![](https://velog.velcdn.com/images/jngmnj/post/f639c2a2-5aff-4aed-97a2-7cdfda18342c/image.png)
1. fetchPokemonData안에 pokemonData에서 sprites도 함께 받아오기
2. formattedPokemonData에서 `sprites: formatPokemonSprites(sprites)`추가
3. formatPokemonSprites 작성

### formatPokemonSprites 함수


`console.log(Object.keys(newSprites))`
![](https://velog.velcdn.com/images/jngmnj/post/7a8686ad-15e7-42bb-b1a8-e237e1babd64/image.png)
`Object.keys(Array)`는 key만 모아둔거 확인할 수 있음

sprites의 값이 string이 아닌것 지우기
![](https://velog.velcdn.com/images/jngmnj/post/f9b3e162-3042-4f9a-84a1-6e97dc7d66b8/image.png)
map으로 돌려서 string이 아니라면, 
배열에서 해당 키 삭제 `delete newSprites[key]`

<결과>
![](https://velog.velcdn.com/images/jngmnj/post/21b5c106-db9c-4600-b8e3-6c1dc635c153/image.png)
#### 정의
```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]);
반응형

최근댓글

최근글

© Copyright 2023 jngmnj