문제
유저 정보 페이지 리팩토링 중,
UserImage
를 기존 base component
로 대체하고 싶었고, 따라서 Image
컴포넌트를 임포트하였다.이후, 이미지에 정확히
props
를 집어 넣어주었다. 그러나 제대로 동작하지 않았다. 문제가 무엇일까?

추후 비슷한 상황이 발생할 때, 이를 간단히 참고할 수 있도록 해당 코드를 남겨 놓는다.
Image.js
import React, { useState, useRef, useEffect } from 'react'; let observer = null; const LOAD_IMG_EVENT_TYPE = 'loadImage'; const onIntersection = (entries, io) => { entries.forEach((entry) => { if (entry.isIntersecting) { io.unobserve(entry.target); entry.target.dispatchEvent(new CustomEvent(LOAD_IMG_EVENT_TYPE)); } }); }; const Image = ({ src, placeholder, lazy, threshold = 0.5, block, width, height, alt, mode, isCircle, ...props }) => { const [loaded, setLoaded] = useState(false); const imgRef = useRef(null); const imageStyle = { display: block ? 'block' : undefined, width, height, objectFit: mode, // cover, fill, contain ...(isCircle ? { borderRadius: '50%' } : {}), }; useEffect(() => { if (!lazy) { setLoaded(true); return; } const handleLoadImage = () => setLoaded(true); const imgElement = imgRef.current; imgElement && imgElement.addEventListener(LOAD_IMG_EVENT_TYPE, handleLoadImage); // Arrow function expected no return value. eslint(consistent-return) // eslint-disable-next-line return () => { imgElement && imgElement.removeEventListener(LOAD_IMG_EVENT_TYPE, handleLoadImage); }; }, [lazy]); useEffect(() => { if (!lazy) return; if (!observer) { observer = new IntersectionObserver(onIntersection, { threshold }); } imgRef.current && observer.observe(imgRef.current); }, [lazy, threshold]); return ( <img ref={imgRef} src={loaded && src ? src : placeholder} style={{ ...props.style, ...imageStyle }} alt={alt} {...props} /> ); }; export default Image;
UserDetailInfo.js
<Image src={image} placeholder={userPlaceholder} block lazy width={200} height={200} alt="userProfile" isCircle style={{ border: `5px solid ${colors.default.white}` }} />
해결
핵심 원인은
props
의 순서였다.리액트의
style prop
은 CSS에서의 selector properties
를 반영하기 때문에 inline style
을 우선적으로 적용한다.따라서 다음 가설을 세웠다.
1. style도 엄연한 props이다.
2. 따라서 prop에 style을 추가했다면, style을 선언 후 또
prop
를 추가로 setting해준다면, 마지막 {...props}
에 들어 있는 prop
이 결과적으로 다시 또 update될 것이다.
3. 그렇다면, 결과적으로 마지막 style
만 적용되고, imageStyle
이 적용되지 않을 것이다.이러한 생각을 하게 된 이유는, 추가로 지정했던
style
만이 반영되었기 때문이다.따라서 이를 제대로 적용하기 위해서는, 다음과 같이
props
배치를 앞으로 해주었어야 했다.Image.js
return ( <img {...props} ref={imgRef} src={loaded && src ? src : placeholder} style={{ ...props.style, ...imageStyle }} alt={alt} /> );
결과
잘 적용된다!

배운 점
props
는 왼쪽에서 오른쪽, 윗쪽에서 아랫쪽 순서대로 읽는다.
이건 설마설마 했는데, 정말로 그렇더라. 앞으로 단순히 배치할 게 아니말아야겠다.
혹여나 발생할 수 있는 사이드 이펙트에 따른 우선순위에 따라서 배치해야겠다고 생각했다!
- 리액트도 CSS 기반 선택자 우선순위에 따른
style
규칙을 잘 반영했다.
결국 인라인 스타일의 우선 순위를 제대로 반영했다고 본다. (레온 강사님의 말씀에 따르면, 인라인 규칙의 우선 순위는 1000점)이니,
CSS in JS에서 나중에 !important로도 우선순위를 바꿀 수 있는지 테스트 해봐야겠다.