⛏️
공부방
  • README.md
  • 프로젝트
    • ft_transcendence
      • 설계
        • 0. 프론트 디자인
        • 1. BackboneJS 뷰 객체
        • 2. API 설계
        • 3. 레일즈 라우팅 구현
        • 4. DB 설계
        • 5. 채널 설계
    • slab-saver
    • react-payment
  • 공부
    • HTML, CSS
      • GRID
      • emmet
      • position
      • CSS Unit
        • 단위 정리
        • 기준을 정해보자
        • em의 정확한 기준은 뭐야?
      • flex
      • NAVBAR 실습
      • 유튜브 화면 만들어보기
    • SQL
      • 이론
        • 1강 데이터베이스
        • 2강 다양한 데이터 베이스
        • 3강 데이터베이스 서버
      • 명령어
        • DB 관리
        • TABLE 관리
        • Constraints
        • SQL 명령어 - 1
        • SQL 명령어 - 2
        • SQL 명령어 - 3
        • SQL 명령어 - 4
        • SQL 명령어 - 5
    • Ruby
      • 루비 객체와 클래스
      • 곡괭이
        • Chapter2. Ruby.new
        • Chapter3. 클래스, 객체, 변수
        • Chapter4. 컨테이너, 블록, 반복자
        • Chapter5. 기능 공유하기
        • Chapter6. 표준 타입
        • Chapter8. 메서드 파헤치기
    • Python
      • 유용한 링크
    • RubyOnRails
      • 아직 정리하지 못한 것들
        • RSPEC 을 이용한 테스트 완전 자동화
        • 레일즈 이니셜라이징 과정
        • 액션케이블 구체적으로 정리하기
        • 웹팩으로 자바스크립트 모듈 관리하기
      • ACTIVE JOB
        • 액티브잡의 기본
        • 실전! 액티브 잡을 이용한 스케쥴링
        • 서버를 껏다 키면 스케쥴링 된 이벤트가 사라진다!
      • ACTION CABLE
        • 액션케이블 Consumer를 이용해서 문제 해결
        • 액션케이블 연결 순서
      • ACTIVE STORAGE
      • 모델
        • validation
          • seeds 데이터 validation 스킵
          • validation 검사가 save, update, create 모든 경우에 일어난다
          • validator 클래스
          • 커스텀Validation
          • validates format(정규표현식)
        • 액티브레코드 find의 다양한 활용
        • 한 레코드에 동시 접속 막자!! with_lock
        • 레일즈 where 사용법
        • 레일즈에서 모델 관련 이슈
        • 모델이름바꿀때명심할것
        • 모델의 includes 메서드
        • 연관 모델을 다른 이름으로 설정하고 가져오기
      • 기본 상식
        • form으로 전달되는 params를 분석해보자
        • StrongParameter 쿼리 배열 받기
        • view helper로 디버깅 하는 방법
        • css 파일을 수정했는데 적용이 안된다?
        • StrongParameter 일반데이터와 객체 데이터 한번에 받기
        • wrap-parameter body가 두 번씩 날라오는 이유
        • 컬렉션 map에서 요소 스킵하는법
        • CASE를 이용해서 정렬(일반적인 정렬 X)
        • 문자열(정규표현식)
        • TIME ZONE 설정하기
        • 커스텀exception
      • RSPEC으로 모델 테스트하기
      • 한 눈에 읽는 루비 온 레일즈
      • Perfect RubyOnRails
        • Chapter1. 소개
        • Chapter2. RubyOnRails 기본
        • Chapter3. 스캐폴딩
        • Chapter7. 라우팅
        • Chapter8. 테스트
    • Javascript
      • var, let, const 차이
      • 브라우저 동작 원리
      • 디바운싱과 쓰로틀링
      • Tagged Template Literal(styled-components)
      • IntersectionObserver 를 사용해서 스크롤 이벤트의 부하 줄여주기
      • EVENT LOOP
        • 자바스크립트에서 어떻게 비동기적인 실행이 가능한걸까?
        • 이벤트 루프의 동작
        • setTimeout이 실행되면 어떤 동작이 일어날까?
        • 블록은 실행이 보장된다
        • 콜스택에 있는 블록이 보장된다는 점을 이용해서 브라우저 죽이기
        • setTimeout 무한반복으로 브라우저는 죽을까?
        • Promise 무한반복으로는 브라우저를 죽일 수 있을까?
        • RAF는 그럼 뭐야?
      • forEach는 반복도중 멈출 방법이 throw 밖에 없다!
      • 임시
        • 정리할 것 목록
          • 자바스크립트 기본 문법
        • 이벤트 임시 정리
      • 유용한 링크
      • arrow function 을 이용한 bind 이슈 해결
      • preventDefault - passive
      • CRITICAL-RENDERING-PATH
      • setInterval에 클로져 개념 사용하기
      • 오디오 문제 이슈
      • 자바스크립트의 식과 문
        • 식과 문이란 무엇인가...
        • 식
          • 1. 기본값과 래퍼객체
          • 2. 참조값과 가비지컬렉팅
        • 식을 조금 더 자세히 알아보자
      • prototype, [[Prototype]] 차이
      • export, import 학습
      • ESlint
      • 아주아주기본
        • Chatper1. 기본
        • Chapter2. 타입
        • Chapter3. 연산자
        • Chapter4. 제어문
        • Chapter5. 배열
        • Chapter6. 함수
        • Chapter7-1. 객체
        • Chapter7-2. 객체
        • Chapter8. 표준객체
        • Chapter9. DOM
      • 이벤트 위임
      • 이벤트가 버블링 되서 root 까지 가다보면... 부모의 부모의 ... 모든 click 이벤트를 발동시키는거 아니야?
      • classList
    • BackboneJS
      • Backbone Model 프로토타입에 메서드 구현하기
      • BackboneJS의 각 요소의 역할과 책임을 확실히 이해하자
      • Window 이벤트를 listenTo로 감시하기
      • 뷰 자신이 자신을 지워야 할 때를 감지하려면 어떻게 해야하는가?
      • 백본 VIEW의 remove와 jquery의 remove 는 다르다!
      • 백본 컬렉션 URL에 쿼리 붙이기
      • index.html.erb와 BackboneJS의 결합
      • 백본 모델과 컬렉션에서 fetch 를 통해 JSON 가져오기!
      • 모델은 urlRoot, 컬렉션은 url
      • ISSUE
      • Absolute Beginner
        • Part1
        • Part2
        • Part3
        • Part4
    • 문제풀이
      • 01. 유효한 팰린드롬(leetcode: 125)
      • 02. 문자열 뒤집기(leetcode: 344)
      • 03. 로그파일 재정렬(leetcode 937)
      • 04. 가장 흔한 단어(leetcode: 819)
      • 05. 그룹 애너그램(leetcode: 49)
      • 06. 가장 긴 팰린드롬 문자열(leetcode: 5)
      • 07. 두 수의 합(leetcode: 1)
      • 08. 빗물 트래핑
      • 09. 세 수의 합(leetcode: 15)
    • BlackCoffeeStudy
      • level1
        • 1주차
    • express
      • Untitled
      • 구글 애널리틱스 연결하기
      • passport를 활용한 로그인
      • express-init 명령어 사용
      • ec2와 DBeaver
      • mariadb 설치
      • sequelize 설치 및 사용법
        • sequelize 설치
        • sequelize-cli 사용법
        • 모델 간 연관관계 맺기
        • Hook 사용하기
      • express-ejs-layout 활용하기
      • Bootstrap
      • npm install로 설치한 모듈 ejs에서 사용하기
      • 미들웨어
    • cypress
      • window.alert 테스트는 어떻게 하지?
      • 상수를 어디에 저장할건가?
      • before()와 beforeEach()
    • aws
      • aws로 프로젝트를 배포해보자!
      • nginx로 리버스프록시 서버를 만들자
      • github actions 로 푸쉬되면 자동으로 업데이트 하는 기능 만들어보기
    • react
      • Drag & Drop 를 이용해서 리스트 요소 순서 바꾸기
      • CRA에서 CRACO 사용하지 않고 절대경로 import(NODE_PATH)
      • useEffect내에서 state의 dependency 문제
      • IntersectionObserverAPI로 무한스크롤 구현
      • react-testing-library
        • 기본
        • react-router-dom 에서의 에러
        • event 발생시키기
        • Integration testing하기
        • async하게 렌더링 되는 요소 잡기
        • Mocking 하기
      • CRA로 만든 앱에서 절대경로로 import 해오기(alias하기)
      • 커스텀 훅 만들기
    • 타입스크립트
      • 조건부타입 (Conditional types)
      • Generics
      • Keyof 타입 오퍼레이터
      • Indexed Access Types
      • 타입 챌린지
        • easy
          • 00. Awaited
          • 01. Concat
          • 02. Exclude
          • 03. First Of Array
          • 04. If
          • 05. Includes
          • 06. Pick
          • 07. Readonly
          • 08. Length
          • 09. Tuple to Object
        • mediun
          • 01. Absolute
    • Firebase
      • 파이어스토어 규칙
    • 기타
      • 협업 프로세스
      • UUID
      • 구글애널리틱스 설치하기
      • 드림코딩 강의
        • 포트폴리오
          • CSS
            • nth-child
            • CSS 팁
          • 자바스크립트
            • 1. 스크롤에 따른 navbar 의 색 변경하기
            • 2. navbar 버튼을 누르면 해당 페이지로 스크롤링 되게 만들자
            • 3. 스크롤 다운 하면 arrow-up 버튼 나오게 하기
            • 4. project 필터링 구현
            • 5. project 필터링에 transition 효과 넣기
      • GIT
        • 기본 사용법 정리
        • git remote update - remote 브랜치 가져오기
  • 기타
    • 이것저것
      • 독서
        • 클린코드
          • Chapter0. 나는 왜 클린코드 책을 읽는가?
          • Chapter1. 클린코드
          • Chapter2. 의미있는 이름
          • Chapter3. 함수
          • Chapter4. 주석
          • Chapter5. 형식 맞추기
      • 용어
      • IDE
        • RubyMine
          • 실전이 중요!
          • 1. Editor Basic
          • 2. Navigation
          • 3. Completion
          • 4. Refactoring
          • 5. Code Assistance
      • MAC에서 살아남기
        • Alfred - Spotlight 업그레이드
        • Vimium
        • BetterTouchTool - 트랙패드
        • 구름 입력기 - ESC, `
        • Spectacle - 화면 분할
    • 원티드 프리온보딩
      • 1주차
        • 월요일
        • 목요일
      • 2주차
      • 3주차
      • 4주차
      • 5주차
      • 6주차
    • 일기장
      • 2020
        • December
          • 20201208(화)
          • 20201209(수)
          • 20201210(목)
          • 20201211(금)
          • 20201214(월)
          • 20201215(화)
          • 20201216(수)
          • 20201217(목)
          • 20201218(금)
          • 20201219(토)
          • 20201221(월)
          • 20201222(화)
          • 20201223(수)
          • 20201224(목)
          • 20201226(토)
          • 20201228(월)
          • 20201229(화)
          • 20201230(수)
          • 20201231(목)
      • 2021
        • January
          • 20210101(금)
          • 20210102(토)
          • 20210105(화)
          • 20210106(수)
          • 20210107(목)
          • 20210108(금)
          • 20210109(토)
          • 20210112(화)
          • 20210113(수)
          • 20210114(목)
          • 20210115(금)
          • 20210117(일)
          • 20210118(월)
          • 20210119(화)
          • 20210120(수)
          • 20210121(목)
          • 20210125(월)
          • 20210126(화)
          • 20210127(수)
          • 20210128(목)
          • 20210129(금)
        • February
          • 20210201(월)
          • 20210202(화)
          • 20210203(수)
          • 20210204(목)
          • 20210205(금)
          • 20210207(일)
          • 20210208(월)
          • 20210209(화)
          • 20210217(수)
          • 20210218(목)
          • 20210219(금)
          • 20210220(토)
          • 20210222(월)
          • 20210223(화)
          • 20210224(수)
          • 20210226(금)
          • 20210228(일)
        • March
          • 20210302(화)
          • 20210303(수)
          • 20210304(목)
          • 20210305(금)
          • 20210306(토)
          • 20210308(월)
          • 20210309(화)
          • 20210310(수)
          • 20210311(목)
          • 20210312(금)
          • 20210313(토)
          • 20210315(월)
          • 20210316(화)
          • 20210317(수)
          • 20210318(목)
          • 20210319(금)
          • 20210322(월)
          • 20210323(화)
          • 20210324(수)
          • 20210325(목)
          • 20210327(토)
          • 20210329(월)
          • 20210330(화)
          • 20210331(수)
        • April
          • 20210406(화)
          • 20210407(수)
          • 20210408(목)
          • 20210409(금)
          • 20210410(토)
          • 20210412(월)
          • 20210413(화)
          • 20210414(수)
          • 20210415(목)
          • 20210416(금)
          • 20210417(토)
          • 20210419(월)
          • 20210420(화)
          • 20210421(수)
          • 20210422(목)
        • July
          • 20210728(수)
Powered by GitBook
On this page
  • 커스텀 훅 만들기
  • 만들기에 앞서... 훅의 원리를 생각해본다
  • 컴포넌트가 그때그때 마다 함수로써 실행된다고?
  • 그러면 어떻게 훅으로 상태를 관리할 수 있게 된거지?
  • 이제 커스텀훅을 만들어봅니다.
  • useIntersectionObserver
  • useDebounce
  • useDetectOutsideClick

Was this helpful?

  1. 공부
  2. react

커스텀 훅 만들기

PreviousCRA로 만든 앱에서 절대경로로 import 해오기(alias하기)Next타입스크립트

Last updated 3 years ago

Was this helpful?

커스텀 훅 만들기

먼저 함수형 컴포넌트에 대해 다시 생각해보자. 함수형 컴포넌트는 클래스 컴포넌트와 다르게 인스턴스로써 그 객체가 살아있는 개념이 아니라, 그때그때 마다 함수가 호출되는 방식이다.

만들기에 앞서... 훅의 원리를 생각해본다

컴포넌트가 그때그때 마다 함수로써 실행된다고?

그러면 함수형 컴포넌트는 도대체 어떻게 자신의 상태(state) 를 관리하는 거야? 지금 관리되고 있는 상태도.. 어차피 다음번에 함수가 실행되면 초기화 되는건 당연한거잖아..? 이런 이슈로 그동안 함수형 컴포넌트는 상태를 관리하지 않을 때만 성능을 위해 사용되었고, 상태가 있는 컴포넌트는 반드시 Class형으로 만들었다.

하지만 클래스형 컴포넌트는 여간 불편한게 아니다. this 의 개념이 복잡하게 얽혀있고 또한 생명주기에 관한 이슈로 많은 코드의 중복이 있다.

이런 클래스형 컴포넌트들의 문제를 인식한 리액트 개발자들은 함수형 컴포넌트에서 상태를 관리할 수 있고 생명주기을 이용할 수 있는 기능을 추가한다. 이게 바로 Hook 이다.

그러면 어떻게 훅으로 상태를 관리할 수 있게 된거지?

상태를 관리할 때는 useState API 를 사용합니다. 원리는 간단합니다. 함수형 컴포넌트가 실행될 때, 즉 함수가 실행될 때 useState 를 이용해서 우리가 관리할 값을 어딘가에 저장하고 가져오게 됩니다.

  • 만약 처음 함수가 실행된다면 어딘가에 저장을 할 것이고

  • 두 번 이상 실행된다면 어딘가에 저장된 값을 찾아옵니다.

복수의 useState 를 사용해도 각 상태는 어딘가에 저장된다. 아래와 같은 코드가 있다고 해보자.

function Form () {
  const [name, setName] = useState('sanam');
  const [age, setAge] = useState(20);
  ...
}
  1. useState 의 인자값은 초깃값 이다.

  2. 처음 실행될 때 name 은 sanam 으로 age 는 20 으로 세팅된다는 건 눈감고도 알 수 있다.

  3. 그런데 두 번 이상 실행될 때는? 첫 번째 실행되었을 때 name 이 namSSang 으로 바뀌고 age 가 30이 되었다면? 초기값을 세팅해 두지 않았는데 어떻게 값이 유지가 되는거지?

  4. 간단하다. 값을 React 의 API 가 저장해두기 때문에 유지해준다.

  5. 두 번째 의문. 이건 매우매우 중요하다.

    • useState의 인자값으로 들어가는건 초기값이다. 즉 현재 state 가 무엇인지는 알려주지 않는다. 그런데 어떻게, 도대체 어떻게 useState을 복수개로 사용하는데 각 값이 섞이지 않고 name은 name으로만 set되고 age는 age로서 존재할 수 있게 되는걸까?

  6. 해당 의문의 정답은 매우 심플하고도 강력한 규칙에 의해 유지된다.

    • 컴포넌트가 실행되는 순서에 의존한다.

    • 이 말은 즉 name 은 처음으로 실행되는 state 고 age 는 두 번째로 실행되는 state 라는것이다. 리액트는 이를 기억하고 있다가 다음 실행 때 순서대로 name 과 age 를 세팅해준다.

  7. 이런 규칙 때문에 조건에 의한 state의 실행은 리액트훅에서 절대로 용납되지 않는다. 주의하자!

이제 커스텀훅을 만들어봅니다.

커스텀 훅은 단 하나의 심플하고도 강력한 규칙인 조건에 의한 state, effect 의 실행을 금지한다. 만 지키면 아주 구현하기 쉽습니다. 그저 반복되는 로직만을 빼서 하나의 함수로 만들면 되는겁니다.

3가지 훅을 만들어 보겠습니다.

  1. useIntersectionObserver

    • 무한스크롤링에 도움을 주는 hook 입니다.

  2. useDebounce

    • mousemove, scroll 등 무수히 많은 이벤트 콜백을 처리할 때 안정을 줄 수 있는 hook 입니다.

  3. useDetectOutsideClick

    • dropdown 의 서랍이 열렸을 때, Modal 이 open 되었을 때 외부를 누르면 종료시키는 등의 기능을 부여해주는 hook 입니다.

useIntersectionObserver

기능

  1. 감시자(observer) 와 감시대상(observation target) 을 만듭니다.

  2. 감시자는 감시대상을 감시하고, 지정한 특이점(threshold) 을 넘어서게 되면 등록한 callback 을 실행시킵니다.

  3. 감시대상의 변경되면, 이전 감시대상에 대한 감시 상태를 취소하고 새로운 대상을 감지합니다.

설계

  1. 인자

    • 특이점이 오면 실행할 callback을 받습니다. 지금은 무한스크롤을 구현하고 있으므로 데이터를 새롭게 가져오는 API call 을 넣어주면 됩니다.

  2. 리턴값

    • 감시 대상 setter 를 리턴합니다. 특이점이 오면 API 을 호출하고, 새로운 데이터가 세팅됩니다. 그러면 감시대상은 바뀌게 됩니다. 따라서 훅 외부에서 감시대상을 새롭게 세팅할 수 있게 setter를 리턴합니다.

  3. 상태관리

    • 감시자

      • 감시자는 state로서 관리될 필요가 없습니다. 왜냐하면 감시자는 누군가의 변화를 감지하면 될 뿐, 자신의 변화는 중요하지 않기 때문입니다.

    • 감시대상

      • 감시대상은 state로 관리되어야 합니다. 왜냐하면 감시대상에 변화가 생기면 감시자는 이전 감시대상에 대한 감시를 취소하고 새로운 대상을 감지하는 로직을 구현해야하기 때문입니다.

  4. effect관리

    • 감시대상 의 변화를 감지하고 실행할 로직이 들어갑니다.

구현

import { useEffect, useRef, useState } from 'react';
import 'intersection-observer';

export const useIntersectionObserver = (callback) => {
  const [observationTarget, setObservationTarget] = useState(null);
  const observer = useRef(
    new IntersectionObserver(
      ([entry]) => {
        if (!entry.isIntersecting) return;
        callback();
      },
      { threshold: 1 }
    )
  );

  useEffect(() => {
    const currentTarget = observationTarget;
    const currentObserver = observer.current;
    if (currentTarget) {
      currentObserver.observe(currentTarget);
    }
    return () => {
      if (currentTarget) {
        currentObserver.unobserve(currentTarget);
      }
    };
  }, [observationTarget]);

  return setObservationTarget;
};

사용방법

무한스크롤을 예로 듭니다.

  1. 새로운 데이터를 가져올 위치를 지정합니다.

    • 현재데이터의 끝으로 지정하겠습니다.

  2. 현재 데이터의 끝에 보이지 않는 박스를 만들어 둡니다.

    <StyledCardListContainer>
      {comments.map((comment) => (
        <Card key={comment.id} comment={comment} />
      ))}
      {lastPage && <div>더이상 불러올 데이터가 없습니다...</div>}
        // NOTE: 아래으 라인이 보이지 않는 박스가 됩니다.   
      {!lastPage && !isLoading && <div></div>}
    </StyledCardListContainer>
  3. 보이지 않는 박스를 observationTarget으로 만듭니다.

    <StyledCardListContainer>
      {comments.map((comment) => (
        <Card key={comment.id} comment={comment} />
      ))}
      {lastPage && <div>더이상 불러올 데이터가 없습니다...</div>}
        // NOTE: 아래으 라인이 보이지 않는 박스가 됩니다.   
      {!lastPage && !isLoading && <div ref={setObservationTarget}></div>}
    </StyledCardListContainer>
  4. useIntersectionObserver 를 넣어줍니다.

    function CardList() {
      const [isLoading, setIsLoading] = useState(false);
      const [comments, setComments] = useState([]);
      const [commentWorker] = useState(new CommentWorker());
      const [lastPage, setLastPage] = useState(false);
    
      const setObservationTarget = useIntersectionObserver(async () => {
        setIsLoading(true);
        const newComments = await commentWorker.getMoreComments();
        if (newComments.length < LIMIT) {
          setLastPage(true);
        }
        setComments((comments) => [...comments, ...newComments]);
        setIsLoading(false);
      });
      return (
        <StyledCardListContainer>
          {comments.map((comment) => (
            <Card key={comment.id} comment={comment} />
          ))}
          {lastPage && <div>더이상 불러올 데이터가 없습니다...</div>}
          {!lastPage && isLoading && <div>Loading...</div>}
          {!lastPage && !isLoading && <div ref={setObservationTarget}></div>}
        </StyledCardListContainer>
      );
    }
  5. 이제 보이지 않는 observationTarget 이 화면에 들어오면 새로운 데이터를 불러옵니다!

useDebounce

기능

검색창을 예로 설명하겠습니다.

  1. 검색창에 입력을 넣을 때 마다 검색을 하지 않는다.

  2. 입력간의 시간차가 특이점 을 넘어서면 등록한 콜백함수 - 검색 을 실행한다.

설계

  1. 인자

    1. debouncing 될 변수를 받습니다.

    2. debouncing 할 시간을 받습니다.

  2. 리턴값

    • 디바운스 된 값을 리턴합니다. 해당 custom hook 을 사용한 코드엥서는 이 값에 effect 를 걸어서 로직을 실행하면 됩니다.

  3. 상태관리

    • debouncedValue 를 관리합니다. 해당 값은 정해진 시간 이후에 값이 변하게 됩니다.

  4. effect

    • 인자로 들어온 value 의 값에 delay 만큼 변화가 없다면 debouncedVaalue 을 set 해줍니다.

    • 해당 기능을 위해 일정시간 뒤에 함수를 실행하게 끔 setTimeout을 넣어야합니다. 만약 일정시간 이전에 이벤트가 들어오면 해당 timeout을 끝내 줄 필요 또한 있습니다.

구현

구현은 너무나 쉽습니다.

import { useState, useEffect } from 'react';

function useDebounce({ value, delay }) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  return debouncedValue;
}

export default useDebounce;

함수가 일정 시간 뒤에 실행될 수 있게 setTimeout을 했습니다. 만약 일정시간이 지나기 전에 새로운 이벤트가 들어온다면 그 이벤트를 기준으로 다시 setTimeout을 실행해야합니다.

즉, 이전의 setTimeout은 없어져야만 합니다. 이 때 effect의 clean-up을 활용합니다.

사용방법

  1. debounced 할 값과 시간을 정합니다.

    • 입력창에 들어오는 값을 debouncedValue 로 합니다.

    • const debouncedValue = useDebounce({ value, delay: 500 });
  2. debouncedValue 가 변하면 실행할 함수를 만듭니다.

      const debouncedSearch = useCallback(() => {
        handleOnSearch(searchUsersByOption(debouncedValue, selectedOption));
      }, [debouncedValue]);
  3. 끝입니다...ㅎㅎ

useDetectOutsideClick

기능

  1. 타켓을 지정한다.

  2. 타켓의 외부가 클릭 된다면, 타켓의 active 상태를 변화시킨다.

설계

  1. 인자

    1. targetDom 을 인자로 받는다. 즉 리액트에서는 ref 가 된다.

    2. 초기값을 받는다.

  2. 리턴값

    • isActive

  3. 상태관리

    • isActive

      • 해당 상태는 현재 target이 active인지 아닌지를 판단하게 해준다.

  4. effect

    • 없다.

구현

import { useState, useEffect } from 'react';

export const useDetectOutsideClick = (targetRef, initialState) => {
  const [isActive, setIsActive] = useState(initialState);

  useEffect(() => {
    const onClick = ({ target }) => {
      if (targetRef.current !== null && !targetRef.current.contains(target)) {
        setIsActive(!isActive);
      }
    };

    if (isActive) {
      window.addEventListener('click', onClick);
    }

    return () => {
      window.removeEventListener('click', onClick);
    };
  }, [isActive, targetRef]);

  return [isActive, setIsActive];
};

사용방법

  1. target 을 한다.

    • Dropdown 을 지정한다.

    const ref = useRef(null);
  2. isActive 값을 어떻게 사용할지 결정한다.

    • 우리는 외부가 눌려지면 드롭다운이 닫히면 되므로 isActive === isOpen 으로 사용하면 된다.

    const [isOpened, setIsOpened] = useDetectOutsideClick(ref, false);
  3. 끝....

리액트 공식문서에서 친절하게 이야기해줍니다.
리액트는 훅이 호출되는 순서에 의존합니다.
리액트 공식문서를 읽어봅시다.