티스토리 뷰

Custom Hook 만들기 - 1

date-fns를 이용한 시간 표시 기능을 컴포넌트로 추출 후 이를 처리하는 커스텀 훅 만들기

  • Datetime Component
    • 시간표시(eg. 몇분 전) 기능을 컴포넌트로 따로 추출
import distanceInWords from 'date-fns/distance_in_words_to_now';
import useUpdate from '@/hooks/useUpdate'

// date-fns 함수 호출을 distance라는 함수 내에 넣기. 그래야 distance 함수가 불렸을 때 비로소 실행되도록 할 수 있다. 
const distance = (time) => {
    distanceInwords(time, {
        locale,
        addSuffix:true
    });
}

// createAt 변수를 time으로 받음
// 최초 상태는 distance(time) 으로 설정
const Datetime = ({time}) => {
    const [datetime, setDatetime] = useState(distance(time));
    // 커스텀 훅 useUpdate 사용
    // setDatetime함수를 콜백으로 넘기면 끝 
    useUpdate(()=>setDatetime(distance(time)));
    return <span>{datetime}</span>
}

export default Datetime;

  • useUpdate
    • Datetime이 1분마다 갱신되도록 하는 기능을 커스텀 훅으로 구현
    import {useEffect} from 'react';
    
    const listeners = [];
    const useUpdate = (callback) => {
        useEffect(()=>{
            if(!listeners.includes(callback) && typeof callback === 'function') {
                listeners.push(callback)
            }
            
            return () => {
    			if(listeners.includes(callback)) {
                    listeners.splice(listeners.indexOf(callback),1);
                }
            };
        }, [callback]);
    }
    
    // setTimeout을 사용하면 listener 중 하나가 제대로 작동하지 않아도 나머지 listener 실행을 멈추지 않는다.  
    setInterval(()=>listeners.forEach(listener => setTimeout(listener)), 60000);
    
    export default useUpdate;
    

    1. 몇 분전 시간 표시 컴포넌트(=Datetime)를 포함하는 포스트, 코멘트 컴포넌트가 마운트된다.
    2. 자식 컴포넌트인 Datetime 컴포넌트가 마운트된다. 이 때 부모인 포스트와 코멘트로부터 생성된 시간을 의미하는 createAt이라는 변수를 time이라는 이름으로 전달받는다. createAt은 LocalDatetime.now()로 생성된 것이다. (컨텐츠를 클라이언트로부터 전달받으면 백엔드 API가 포스트나 코멘트를 생성하여 응답하는 구조이다)
    3. 전달받은 time을 date-fns로부터 임포트한 distanceInWords 함수에 아규먼트로 전달하고 이를 datetime이라는 변수의 초기값으로 설정한다. distanceInWords 함수는 함수명처럼 date 객체를 인자로 받아 n분전, n일전 등의 말로 표시된 결과를 리턴한다.
    4. 그리고 커스텀 훅인 useUpdate에 ()=>setDatetime(distance(time)) 함수를 전달한다. distance함수를 따로 설정한 것은 distanceInWords 가 바로 실행되는 것을 막기 위해서다.
    5. useUpdate 훅은 useEffect를 사용하여 만들었고, 아규먼트로 전달된 콜백함수(이 경우 ()=>setDatetime(distance(time)) 함수)가 리스너 배열에 추가되도록 되어 있다. 여기서 useEffect의 주요한 일은 이 리스너 배열을 관리하는 것이다.
    6. 그리고 리스너 배열들에 존재하는 함수들은 setInterval 함수를 통해 1분마다 실행되도록 설정되어 있다.리스너 배열?
    7.  
    • 여기서 useUpdate의 리스너 배열의 존재가 중요하다. 이 리스너 배열은 Datetime컴포넌트의 setDatetime함수 즉, 1분마다 컴포넌트에 표시되는 시간을 갱신하는 것을 모두 한꺼번에 모아서 처리하기 위한 것이다.
    • setInterval은 1분마다 리스너 배열에 있는 함수들을 하나씩 꺼내서 실행하는 것을 반복한다. 이 함수들은 부모 컴포넌트로부터 온 createAt(time)을 distanceInWords에 아규먼트로 전달한 결과(n분전으로 표시되는 결과)를 업데이트 하는 setDatetime함수이다.
    • 일분마다 무조건 setInterval은 시행될 거고, 그때마다 리스너 배열에 모든 포스트 컴포넌트와 코멘트 컴포넌트의 setDatetime이 들어가있는게 중요하다. 포스트 컴포넌트가 1부터 10까지 존재하는데, 그 중 9개 포스트만 Datetime을 처리하는 함수가 리스너에 들어있다면 안되는 것이다.
    • 즉 이렇게 Datetime을 포함하고 있는 부모 컴포넌트들의 함수들을 모아놓은 것이 리스너 배열이고, 이렇게 한꺼번에 모아둠으로써 setInterval을 한 번만 돌려도 된다는 이점이 있다.
    • 더구나 setTimeout으로 실행하여 비동기처리되므로 중간에 하나가 제대로 실행되지 않아도 나머지의 실행에는 영향을 미치지 않을 것이다.그렇다면 useEffect는 리스너 배열을 관리하는 일을 잘해내고 있는가?
    •  
    • useEffect의 구조를 보면 콜백함수와 클린업함수를 갖고 있고, 아규먼트를 받아서 이를 subscribe하고 있다.
    • useEffect의 콜백함수는 컴포넌트가 처음 마운트될 때, 그리고 컴포넌트가 업데이트될 때 실행된다. (componentDidMount, componentDidUpdate)
    • useEffect의 클린업 함수(리턴 함수)는 컴포넌트가 업데이트될 때, 그리고 컴포넌트가 언마운트될 때 실행된다. (componentDidUpdate, componentWillUnmount)
    • 위에서 말했듯이 리스너 배열에서 중요한 점은 마운트되어 있는 모든 Datetime의 부모 컴포넌트들의 함수가 포함되어야 한다는 점이다. 즉 포스트가 API로부터 fetch되거나, 포스트가 새로 등록돼서 API에 갔다가 돌아왔거나 등등의 액션이 발생했을 때 리스너에 성공적으로 추가되어야 한다는 소리이다. 당연히 언마운트될 때는 리스너 배열에서 삭제돼야 하고.
    • 새로운 컴포넌트가 fetch될 때, 해당 컴포넌트의 ()=>setDatetime(distance(time)) 함수는 새로이 만들어지고 리스너 배열에 추가된다. 당연히 언마운트될 때에는 클린업함수가 실행되어 제거된다.
    • 다만 아직 궁금한 점은 comment하나가 fetch될 때 기존에 이미 업데이트가 된 포스트들과 코멘트들의 useEffect가 재실행되는데(정확히는 클린업되어 리스너에서 제거됐다가, 다시 콜백이 실행되며 리스너에 추가된다. componenetDidUpdate된 경우 클린업과 콜백 모두 실행되므로, 컴포넌트가 구독하고 있는 것이 업데이트됐다고 볼 수 있다.), 이것은 subscribe하고 있는 함수가 변경되었다는 소리이다. 홈에서 포스트와 코멘트 컴포넌트를 map으로 풀어서 리턴하고 있긴한데, 그 과정에서 그렇게 되는 것일까? 왜 나머지 함수들이 변경되는지는 좀 더 찾아봐야될 것 같다.
  • 이 Datetime 컴포넌트가 작동되는 순서는 다음과 같다
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함