Ubuntu를 사용하는 NGINX 웹 서버에 HTTP/3 설정하기
React의 라이프 사이클과 고차 컴포넌트를 이용해서 이미지가 모두 로드된 시점 확인하기
2019-11-04
Explanation
오랜만에 포스팅이네요, 요즘은 OOP나 클린 아키텍처 관련 공부에 정신없는 하루 하루를 보내고 있답니다.
오늘까지는 이전에 작성해야지 생각했던 주제의 글이고 다음 글은 아마도 OOP나 클린 아키텍처에 관한 글을 써볼까 하고 있어요.
이 글의 예제 코드는 깃허브 저장소에 올려놓았습니다~
https://github.com/falsy/blog-post-example/tree/master/react-images-loaded
이전에 웹 페이지에 이미지가 다 로드된 시점? 을 확인하기 위해서 imagesLoaded(https://imagesloaded.desandro.com/) 라는 라이브러리를 자주 사용했었는데요.
리엑트의 라이프 사이클과 고차 컴포넌트를 사용하게 간단하게 구현해 볼 수 있지 않을까 생각해봤던 내용을 적어보려합니다.
물론 범용적으로 사용하기에는 부족한 부분이 많아요. 만약 컴포넌트가 로드되는 시점이 복잡한 실제 서비스에 사용하신다면 여러가지로 많이 생각해서 구현하셔야 한답니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// src/components/Img.js import React, { PureComponent } from 'react'; class Img extends PureComponent { constructor(props) { super(props); this.imgPromise = new Promise(resolve => { this.onImageLoaded = () => { resolve(); }; }); this.onImageLoaded = this.onImageLoaded.bind(this); } componentDidMount() { this.props.imgLoaded(this.imgPromise); } componentWillUnmount() { this.onImageLoaded(); } render() { const { src, width, height, alt } = this.props; return ( <img onLoad={this.onImageLoaded} onError={this.onImageLoaded} {...{ src, width, height, alt }} /> ); } } export default Img; |
간단한 이미지 태그를 대신하여 사용할 수 있는 이미지 컴포넌트를 만들어 줍니다.
‘constructor()’에서 ‘imgPromise’라는 프로퍼티를 만들어주고 이 프로퍼티는 ‘onImageLoaded’ 라는 프로퍼티가 실행됬을때 완료되는 프로미스 객체 입니다.
그리고 ‘componentDidMount’ 이미지 엘리먼트가 DOM에 그려졌을때 아직은 알 수 없지만 부모 컴포넌트에게 받은 imgLoaded라는 함수를 호출하며 위에서 만든 프로미스 객체(imgPromise)를 넘겨줍니다.
그리고 이미지가 로드되거나 로드에 실패했을때의 시점을 알기위해 이미지 엘리먼트에 ‘onLoad’, ‘onError’ 시점에 위 프로미스를 완료하는 ‘onImageLoaded’ 라는 함수를 호출하도록 합니다.
추가로 이미지가 로드되기 전에 상위 컴포넌트의 조건문에 의해 컴포넌트가 취소될 수 도 있기 때문에 라이프사이클 ‘componentWillUnmount’ 시점에서도 ‘onImageLoaded’ 라는 호출을 호출해주도록 합니다.
여기까지 요약해서 시나리오를 한번 그려보면요,
위 컴포넌트의 이미지들이 실행되면 우선 부모 컴포넌트에게 받은 imgLoaded 함수에 현재 이미지의 로드를 기다리는 프로미스 객체를 전달하고, 이미지가 로드되거나 오류가나서 로드해오지 못하거나
또는 조건문에 의해 이미지 컴포넌트가 사라졌을때 그전에 전송했던 프로미스의 객체를 완료시키도록 해주는 역할을 합니다.
앞서 만든 이미지 컴포넌트를 사용하는 부모? 의 컴포넌트를 만들어야하는데요. 여러곳에서 사용할 수 있도록 고차컴포넌트로 만들거에요.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
// src/components/Section.js import React, { PureComponent } from 'react'; export default (Comp) => { return class extends PureComponent { constructor(props) { super(props); this.imgPromiseList = []; this.imgLoaded = this.imgLoaded.bind(this); } componentDidMount() { Promise.all(this.imgPromiseList).then(() => { console.log('이미지가 모두 로드 되었습니다.'); }); } imgLoaded(imgPromise) { this.imgPromiseList.push(imgPromise); } render() { return ( <section> <Comp {...{ ...this.props, imgLoaded: this.imgLoaded }} /> </section> ); } }; }; |
위의 ‘Comp’ 인자값으로 컴포넌트를 받아와서 아래 랜더에서 그릴거에요. 그리고 그 컴포넌트에는 공통적으로 ‘imgLoaded’ 라는 함수를 전달하고요.
그리고 전달받은 컴포넌트(인자 Comp에 해당) 안에는 앞서 2번에 만든 ‘Img’ 컴포넌트를 사용한 컴포넌트 일꺼에요.
순서상 자식의 Img 컴포넌트가 먼저 랜더되기 때문에 자식의 Img 컴포넌트들이 imgLoaded를 호출해서 프로미스 객체를 줄거에요.
그러면 그걸 ‘imgPromiseList’ 라는 배열에 담고 ‘Promise.all’을 사용해서 자식의 모든 이미지 엘리먼트들이 로드되거나 실패한 마지막 시점을 알 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// src/components/Example.js import React, { PureComponent } from 'react'; import Img from './Img'; class Example extends PureComponent { render() { return ( <div> <Img imgLoaded={this.props.imgLoaded} src="https://images.unsplash.com/photo-1572613000712-eadc57acbecd?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3170&q=80" alt="falsy logo" /> <Img imgLoaded={this.props.imgLoaded} src="https://images.unsplash.com/photo-1496886274628-64f00820aa76?ixlib=rb-1.2.1&auto=format&fit=crop&w=3152&q=80" alt="falsy logo" /> <Img imgLoaded={this.props.imgLoaded} src="https://images.unsplash.com/photo-1518979353812-b492f6d6fedd?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjIzNTA3fQ&auto=format&fit=crop&w=1268&q=80" alt="falsy logo" /> </div> ) } } export default Example; |
위와 같이 Img 컴포넌트를 가져와서 img 태그를 사용하듯이 사용하고 imgLoaded라는 속성을 부모에게서 전달해주도록 합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// src/components/Example.js import Example from './components/Example'; import Section from './components/Section'; const ExampleComp = Section(Example); ... render() { return ( <ExampleComp /> ) } ... |
위와 같이 실제로 컴포넌트가 사용되는 곳에 고차 컴포넌트를 만들어서 렌더에 넣어 사용합니다.