๋ณธ๋ฌธ์œผ๋กœ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๋ถ„๋ช… ๋‹ค ๋งŒ๋“ค์—ˆ์ง€๋งŒ, ๋ณด๊ธฐ์—” ํ‰ํ•œ ๊นœ๋นก์ž„ ํ˜„์ƒ!!

์ด ๊ธ€์—์„  ๋‘ ๊ฐ€์ง€ ๊นœ๋ฐ•ํž˜ ํ˜„์ƒ์— ๋Œ€ํ•ด ๋‹ค๋ฃฐ ๊ฒƒ์ด๋‹ค.

 

1. useEffect ํ˜ธ์ถœ์— ์˜ํ•œ ๊นœ๋นก์ž„

2. API ํ˜ธ์ถœ์— ์˜ํ•œ ๊นœ๋นก์ž„

 

 

1. UseEffect ํ˜ธ์ถœ์— ์˜ํ•œ ๊นœ๋นก์ž„

const [duckUrl, setDuckUrl] = useState('');

useEffect(() => {
    setDuckUrl('/duck.png');
  }, []);
  
<img alt='duck' src={duckUrl} />

Hook Flow์— ์˜ํ•˜๋ฉด DOM ๋ฐฐ์น˜ → ํŽ˜์ธํŠธ ์ž‘์—… useEffect ํ˜ธ์ถœ ์ˆœ์„œ๋ฅผ ๋”ฐ๋ฅธ๋‹ค.

๋”ฐ๋ผ์„œ useEffect๋กœ ์ดํ›„ ํŽ˜์ธํŠธ ์ž‘์—…์ด ํ•œ ๋ฒˆ ๋” ์ด๋ฃจ์–ด์ง์— ๋”ฐ๋ผ(๋ฆฌํŽ˜์ธํŠธ) ๊นœ๋นก์ด๋Š” ํ˜„์ƒ์ด ๋‚˜ํƒ€๋‚œ๋‹ค.

 

 

 

2. UseLayoutEffect ์„ ํ†ตํ•œ ํ•ด๊ฒฐ

const [duckUrl, setDuckUrl] = useState('');

useLayoutEffect(() => {
    setDuckUrl('/duck.png');
  }, []);
  
<img alt='duck' src={duckUrl} />

DOM ๋ฐฐ์น˜ → useLayoutEffect ํŽ˜์ธํŠธ ์ž‘์—… 

์ฒซ ํŽ˜์ธํŠธ ์ž‘์—…์ด ๋ฐœ์ƒํ•˜๊ธฐ ์ „์— useLayoutEffect ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ธฐ ๋•Œ๋ฌธ์—, ๊นœ๋ฐ•์ž„ ์—†์ด DOM์„ ์—…๋ฐ์ดํŠธ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 

3. API ํ˜ธ์ถœ์— ์˜ํ•œ ๊นœ๋นก์ž„

 

์ด์   API ํ˜ธ์ถœ์— ์˜ํ•œ ๊นœ๋นก์ธ ํ˜„์ƒ์„ ์•Œ์•„๋ณด์ž.

API ํ˜ธ์ถœ์„ ํ†ตํ•ด ์ด๋ฏธ์ง€ URL์„ ๋ฐ›์•„์˜ค๊ณ , ๊ทธ URL์„ ์ด๋ฏธ์ง€ ์†Œ์Šค์— ๋„ฃ์œผ๋ฉด ๋ณดํ†ต ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚˜ํƒ€๋‚œ๋‹ค.

๋ณด๊ธฐ๋งŒ ํ•ด๋„ ์•„์ฐ”ํ•ด์ง€๋Š” ๋ฒ„๋ฒ…๊ฑฐ๋ฆผ์ด๋‹ค.

 

์˜ค๋ฆฌ๊ฐ€ ๋‚˜์˜ค๊ธฐ๊นŒ์ง€ imgํƒœ๊ทธ๊ฐ€ ๋ณด์—ฌ์ง€๋Š” ๊ณผ์ •์€ ๋Œ€๋žต ์ด๋ ‡๋‹ค.

 

1. alt๊ฐ’๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ ์•„์ด์ฝ˜์ด ๋œฌ๋‹ค. (API ํ˜ธ์ถœ์„ ํ†ตํ•ด url์„ ๋ฐ›์•„์˜ค๊ธฐ ์ „)

2. alt๊ฐ’๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ ์•„์ด์ฝ˜์ด ์—†์ด ๋นˆ ํ™”๋ฉด์ด ๋œฌ๋‹ค. (์ด๋ฏธ์ง€ ์†Œ์Šค์— url์ด ์ƒ๊ธฐ๊ธฐ ๋•Œ๋ฌธ)

3. ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋ฉด์„œ ๋ถˆ๋Ÿฌ์™€์ง„๋‹ค. (์™ธ๋ถ€ url๋กœ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ)

const [duckUrl, setDuckUrl] = useState('');

useEffect(() => {
    const getDuck = async () => {
      const {
        data: { url },
      } = await axios.get('https://random-d.uk/api/random');
      setDuckUrl(url);
    };
    getDuck();
}, []);
  
<img alt="duck" src={duckUrl} />

 

 

4. ํ•ด๊ฒฐ๊ณผ์ •(1) url์ด ์ค€๋น„๋˜๋ฉด imgํƒœ๊ทธ ๋žœ๋”๋ง

1๋ฒˆ ๊ณผ์ • " alt๊ฐ’๊ณผ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ ์•„์ด์ฝ˜์ด ๋œฌ๋‹ค." ์„ ์ˆ˜์ •ํ–ˆ๋‹ค.

API ํ˜ธ์ถœ์ด ๋๋‚œ ์ง€์ ์„ ๊ธฐ์ ์œผ๋กœ ์กฐ๊ฑด๋ถ€ ๋žœ๋”๋ง์„ ์‹œ์ผœ์คฌ๋‹ค.

ํ•˜์ง€๋งŒ ์—ญ์‹œ ๋งŒ์กฑ์Šค๋Ÿฝ์ง€ ์•Š๋‹ค.

์ปคํŠผ์ฒ˜๋Ÿผ ๋ถ€์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ๋‚ด๋ ค์˜ค๋Š” ์‚ฌ์ง„์„ ๋ณด๊ณ ์žˆ์ž๋‹ˆ ๋งˆ์Œ์ด ์•„๋ ค์˜จ๋‹ค...

const [duckUrl, setDuckUrl] = useState('');
const [loaded, setLoaded] = useState(false);

useEffect(() => {
    const getDuck = async () => {
      const {
        data: { url },
      } = await axios.get('https://random-d.uk/api/random');
      setDuckUrl(url);
      setLoaded(true);
    };
    getDuck();
}, []);

{ loaded &&
      <img alt='duck' src={duckUrl} />
}

 

 

5. ํ•ด๊ฒฐ๊ณผ์ •(2) ์‚ฌ์ง„์ด ๋‹ค ๋กœ๋“œ๋˜๋ฉด, ๊ทธ๋•Œ ๋žœ๋”๋ง ํ•˜๊ธฐ

img ํƒœ๊ทธ์˜ onLoad ์ด๋ฒคํŠธ๋ฅผ ํ†ตํ•ด, ์ด๋ฏธ์ง€ ๋กœ๋“œ๊ฐ€ ๋  ๋•Œ ์กฐ๊ฑด๋ถ€ ๋žœ๋”๋ง์„ ์‹œ์ผœ์คฌ๋‹ค.

์ถ”๊ฐ€๋กœ loaded ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด display:none์„ ํ†ตํ•ด ๋ฒ„๋ฒ…๊ฑฐ๋ฆฌ๋Š” ๊ณผ์ •์„ ๋ณด์—ฌ์ง€์ง€ ์•Š๊ฒŒ ์„ค์ •ํ–ˆ๋‹ค.

์–ด๋А์ •๋„ ํ•ด๊ฒฐ์ด ๋‹ค ๋์ง€๋งŒ, ์š•์‹ฌ์ด ์ƒ๊ธด๋‹ค.

์ด๋ฏธ์ง€๊ฐ€ ๋กœ๋”ฉ์ค‘์ด๋ผ๋Š” ๊ฒƒ์„ ํ‘œํ˜„ํ•˜๊ณ  ์‹ถ๋‹ค.

const [duckUrl, setDuckUrl] = useState('');
const [loaded, setLoaded] = useState(false);

useEffect(() => {
    const getDuck = async () => {
      const {
        data: { url },
      } = await axios.get('https://random-d.uk/api/random');
      setDuckUrl(url);
    };
    getDuck();
}, []);

<img alt='duck' 
	src={duckUrl} 
    onLoad={() => setLoaded(true)}
    style={loaded ? { display: "block" } : { display: "none" }}
/>

 

 

6. ํ•ด๊ฒฐ : skeleton image loading ํšจ๊ณผ ์ฃผ๊ธฐ

์„ฑ๊ณต! ๊ธฐ๋Šฅ๋งŒ ์ถ”๊ฐ€ํ–ˆ์„ ๋•Œ ๋ณด๋‹ค ํ›จ์”ฌ UX๊ฐ€ ์ข‹์•„์กŒ๋‹ค.

์ด๋ฏธ์ง€ ํƒœ๊ทธ์˜ ๋ถ€๋ชจ ํƒœ๊ทธ์— Background-color์— ์Šค์ผˆ๋ ˆํ†ค css ํšจ๊ณผ๋ฅผ ์ฃผ์–ด ๊ตฌํ˜„ํ–ˆ๋‹ค.

์ถ”ํ›„์— ๊ด€๋ จ ๋กœ์ง์„ ๋”ฐ๋กœ ์ถ”์ถœํ•˜๊ณ , ์Šค์ผˆ๋ ˆํ†ค ๋กœ๋”ฉ์„ ์‚ฌ์šฉํ•˜๋Š” ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์— ์ด์‹ํ•˜๊ธฐ ์ˆ˜์›”ํ•˜๊ฒŒ ๋ฆฌํŒฉํ† ๋ง์„ ํ•ด์ค˜์•ผ๊ฒ ๋‹ค. 

import axios from 'axios';
import { useLayoutEffect, useEffect, useState } from 'react';
import './skeleton.css';
function App() {
  const [loaded, setLoaded] = useState(false);
  const [duckUrl, setDuckUrl] = useState('');

  useLayoutEffect(() => {
    const getDuck = async () => {
      const {
        data: { url },
      } = await axios.get('https://random-d.uk/api/random');
      setDuckUrl(url);
    };
    getDuck();
  }, []);

  return (
    <div style={{ backgroundColor: 'black' }}>
      <div style={{ width: 500, height: 500 }} className='skeleton'>
        <img
          alt='duck'
          src={duckUrl}
          style={loaded ? { display: 'block' } : { display: 'none' }}
          onLoad={() => setLoaded(true)}
        />
      </div>
    </div>
  );
}

export default App;
:root {
  --loading-grey: #ededed;
}

img {
  display: block;
  width: 500px;
  height: 500px;
}

.skeleton {
    background-color: #ededed;
    background: linear-gradient(
        100deg,
        rgba(255, 255, 255, 0) 40%,
        rgba(255, 255, 255, 0.5) 50%,
        rgba(255, 255, 255, 0) 60%
    );
    background-size: 200% 100%;
    background-position-x: 180%;
    animation: 1s loading ease-in-out infinite;
}

@keyframes loading {
    to {
        background-position-x: -20%;
    }
}

 

๋ฐ˜์‘ํ˜•