혼자 적어보는 노트

프로그래머스 데브코스 TIL - Day 50 본문

스터디

프로그래머스 데브코스 TIL - Day 50

jinist 2022. 5. 27. 23:11

 

✅ 오늘의 학습

📌 React (3)

 

- 컴포넌트 연습하기

 


 

Image 컴포넌트

 

intersection observer를 사용하여 viewport에 이미지가 나타날 때

네트워크에 이미지 요청을 하는 방식을 강의에서 다루었다.

 

image 컴포넌트의 기본 형태

export const Image = ({ lazy, threshold = 0.5, placeholder, src, block, width, height, alt, mode, ...props }) => {
  const [loaded, setLoaded] = useState(false);
  const imgRef = useRef(null);

  const imageStyle = {
    display: block ? "block" : "undefined",
    width,
    height,
    objectFit: mode, // cover, fill, contain
  };

  return <img ref={imgRef} src={loaded ? src : placeholder} style={{ ...props.style, ...imageStyle }} alt={alt} />;
};

Image에서 설정할 모든 값들을 props로 받고

Lazy의 여부와 옵션 값들 또한 props를 통해 전달함으로써 재사용성을 높였다.

 

Lazy Loading 적용하기

  useEffect(() => {
    if (!lazy) {
      setLoaded(true);
      return;
    }
    const handleLoadImage = () => setLoaded(true);

    const imgElement = imgRef.current;
    imgElement && imgElement.addEventListener(LOAD_IMG_EVENT_TYPE, handleLoadImage);

    return () => {
      imgElement && imgElement.removeEventListener(LOAD_IMG_EVENT_TYPE, handleLoadImage);
    };
  }, [lazy]);

  useEffect(() => {
    if (!lazy) return;
    if (!observer) { // observer가 등록되어 있지 않다면 새로 생성
      observer = new IntersectionObserver(onIntersection, { threshold });
    }
	
    // observer에 해당 컴포넌트의 image 등록
    imgRef.current && observer.observe(imgRef.current);
  }, [lazy, threshold]);

useEffect부분에서 이미지 엘리먼트에 커스텀 이벤트를 붙이는 부분과

observer를 통해 감시하는 부분으로 나누었다.

 

// 모듈내에서 전역적으로 사용되도록 바깥에 작성
let observer = null;

const LOAD_IMG_EVENT_TYPE = "loadImage";

const onIntersection = (entries, io) => {
  console.log(entries);
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      // 이 조건이 있어야 threshold 값대로 처리할 수 있음.
      io.unobserve(entry.target);
      entry.target.dispatchEvent(new CustomEvent(LOAD_IMG_EVENT_TYPE));
    }
  });
};

 observer에 연결시킨 함수는 모듈 내에서 전역적으로 사용할 수 있도록

컴포넌트 바깥에서 작성했고, 뷰포트에 해당 이미지가 들어오면

해당 observer의 감시를 끄고 커스텀 이벤트를 실행시켰다.

 

intersection Observer는 무한스크롤을 구현할 때 몇번 사용해보기는 했지만

동작 원리에 대해서 아직 좀 미흡했었는데 이번 예제를 통해 조금은 더  이해가 되었다.

 

 

 

Spacer 컴포넌트

내부 요소들의 간격을 지정할 수 있는 컴포넌트.

<Spacer type="vertical">
    <img src="https://picsum.photos/200" style={{ display: "block" }} />
    <img src="https://picsum.photos/200" style={{ display: "block " }} />
</Spacer>

컴포넌트를 만들어서 type을 prop으로 전달하여 해당 type에 맞게 지정할 수 있다.

 

 

하위 엘리먼트 변환하기

React 공식문서 : 엘리먼트 변환하기

 

  const nodes = React.Children.toArray(children)
    .filter((element) => React.isValidElement(element))
    .map((element, index, elements) => {
      return React.cloneElement(element, {
        ...element.props,
        style: {
          ...element.props.style,
          marginRight: type === "horizontal" && index !== elements.length - 1 ? size : undefined,
          marginBottom: type === "vertical" && index !== elements.length - 1 ? size : undefined,
        },
      });
    });
  
  return (
    <div {...props} style={spacerStyle}>
      {nodes} // 새로 만든 자식들을 보여준다.
    </div>
  );

React.Children.toArray(children) 를 통해 하위 엘리먼트를 배열 형태로 받을 수 있으며

받은 요소들을 가공하여 새로운 엘리먼트를 반환하여 처리할 수 있다.

 

* 하위 엘리먼트가 아닌 "하위 컴포넌트"에도 적용하려면

아래와 같이 하위 컴포넌트에 props로 받은 style을 지정하도록 작성 해두어야 한다.

const Box = ({ width = 100, height = 100, ...props }) => {
  const style = {
    width,
    height,
  };

  return <div {...props} style={{ ...props.style, ...style }} />;
};
export default Box;

 

 

Spinner 컴포넌트

svg파일과 props로 size를 전달해주어 간단하게 로딩 spinner를 구현할 수 있었다.

import styled from "@emotion/styled";

const Icon = styled.i`
  display: inline-block,
  vertical-align: middle
`;

const Spinner = ({ size = 24, color = "#919eab", loading = true, ...props }) => {
  const sizeStyle = {
    width: size,
    height: size,
  };
  
  return loading ? (
    <Icon>
      <svg
        version="1.1"
        xmlns="http://www.w3.org/2000/svg"
        viewBox="0 0 100 100"
        style={sizeStyle}
      >
      <!----- svg 코드 작성 ------>
      </svg>
    </Icon>
  ) : null;
};

export default Spinner;

내가 실습 했을 때는 애니메이션이 들어가 있는 svg파일이었기 때문에

css 애니메이션 처리 없이 적용했는데, 이미지로 진행하려면 내부에서 css 애니메이션을 사용하면 될 듯 하다.

 

Toggle 컴포넌트

디자인이 들어간 toggle버튼의 경우 checked와 disabled값을 props로 받아서

input에 전달하고 해당 input을 숨기는 형태로 구현할 수 있다.

 

// ToggleContainer, ToggleInput, ToggleSwitch css 작성하기

const Toggle = ({ name, on = false, disabled = true, onChange, ...props }) => {
  const [checked, toggle] = useToggle(on);

  const handleChange = () => {
    toggle();
    onChange && onChange();
  };

  return (
    <ToggleContainer {...props}>
      <ToggleInput type="checkbox" name={name} checked={checked} disabled={disabled} onChange={handleChange} />
      <ToggleSwitch />
    </ToggleContainer>
  );
};

원래의 나였다면 input이 아닌 state들을 추가해서 구현했었을텐데

이러한 트릭을 사용하여 구현하는 방식이 굉장히 흥미로웠다.

이전에 파일 불러오기 버튼을 input을 숨기고 따로 버튼을 추가해서 처리를 했던 사례를 본 적 있었는데

다양하게 응용할 수 있다는 것을 알게 되었다.

 

 


느낀 점

 

리액트를 하면서 이전에 다루었던 것들이 나오기는 했지만 새롭게 익히는 느낌이 들었다.

예를들어 이전에는 그냥 "이렇게 사용하는건가 보다~" 라고 생각했다면

요즘은 "이런 이점이 있어서 이렇게 사용하는것이구나~" 하는 깨달음과 함께 머릿속에 들어오는 기분이다.

그리고 응용하고 활용하는데에 있어서 나는 아직 많이 부족하다는 것을 느꼈다.

그런 면에 있어서 이번 강의들은 너무 너무 유익했다!

Comments