REACT/[inflearn] 처음만난 리액트

7강. Hooks

web_seul 2022. 11. 8. 13:37
반응형

Hooks의 개념과 useState, useEffect

Hooks : React v16.8에서 등장, 기존에 존재하는 기능에 추가되는 기능

 

useState : state를 사용하기 위한 Hook

//useState() 사용법
const [변수명, set함수명] = useState(초기값);
function Counter(props){
  var count = 0;
  return(
    <div>
      <p>총 {count}번 클릭했습니다.</p>
      <button onClick={() => count ++}>
        클릭
      </button>
    </div>
  );
}

//useState 사용
function Counter(props){
  count[count, setCount] = useState(0);
  return(
    <div>
      <p>총 {count}번 클릭했습니다.</p>
      <button onClick={() => setCount(count + 1)}>
        클릭
      </button>
    </div>
  );
}

 

useEffect : side effect(다른 컴포넌트에 영향을 미칠수 있으며 렌더링 중에는 작업이 완료될 수 없으므로 렌더링 후 실행되는 작업)를 수행하기 위한 Hook, componentDidMount, componentDidUpdate, componentDidUnmount 와 동일한 기능을 하나로 제공, 여러개 사용가능

//useEffect()사용법
useEffect(이펙트 함수, 의존성 배열);

//mount, unmout시 한번만 실행됨
useEffect(이펙트 함수, []);

//의존성 배열을 생략하면 컴포넌트 업데이트 시마다 호출됨
useEffect(이펙트 함수);


useEffect(() => {
  //컴포넌트가 마운트된 이후
  //의존성 배열에 있는 변수들 중 하나라도 값이 변경되었을 때 실행됨
  //의존성 배열에 빈 배열([])을 넣으면 마운트와 언마운트시에 단 한번씩만 실행됨
  //의존성 배열 생략시 컴포넌트 업데이트 시마다 실행됨
  
  return () => {
    //컴포넌트가 마운트 해제되기 전에 실행됨
  }
}, [의존성 변수1, 의존성 변수2, ..]);
//useState 사용
function Counter(props){
  count[count, setCount] = useState(0);
  
  //componentDidMount, componentDidUpdate와 비슷하게 작동
  useEffect(() => {
    //브라우저 API를 사용해서 document의 title 업데이트
    document.title = `You clicked ${count} times`;
  });
  
  return(
    <div>
      <p>총 {count}번 클릭했습니다.</p>
      <button onClick={() => setCount(count + 1)}>
        클릭
      </button>
    </div>
  );
}
function UserStatus(props) {
  const [isOnline, setIsOnline] = useState(null);
  
  function handleStatusChange(status){
    setIsOnline(status.isOnline);
  }
  
  useEffect(() =>{
    ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
    return() => {
      //컴포넌트가 unmount될 때 호출됨
      ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
    };
  });
  
  if(isOnline === null) {
    return '대기중..';
  }
  return isOnline ? '온라인' : '오프라인';
}

 

useMemo, useCallback, useRef

useMemo : Memoized value를 리턴하는 Hook, 렌더링될때마다 연산량이 높은 작업의 반복 작업 방지 가능(속도향상)

Memoization : 최적화

const memoizedValue = useMemo(
  () => {
    //연산량이 높은 작업을 수행하여 결과 반환
    return computeExpensiveValue(의존성 변수1, 의존성 변수2)
  },
  [의존성 변수1, 의존성 변수2]
);
//의존성 배열을 넣지 않을 경우 매 렌더링마다 함수가 실행됨
const memoizedValue = useMemo(
  () => computeExpensiveValue(a,b)
);

//의존성 배열이 빈 배열일 경우 컴포넌트 마운트시에만 호출됨
const memoizedValue = useMemo(
  () => {
    return comoputeExpensiveValue(a,b);
  },
  []
);

 

useCallback : useMemo() Hook과 유사하지만 값이 아닌 함수 반환, 의존성 배열이 바뀐경우에만

const memoizedCallback = useCallback(
  () => {
    doSomething(의존성 변수1, 의존성 변수2);
  },
  [의존성 변수1, 의존성 변수2]
);
//동일한 역할의 코드
useCallback(함수, 의존성 배열);

useMemo(() => 함수, 의존성 배열);
function ParentComponent(props){
  const [count, setCount] = useState(0);
  
  //재렌더링 될 때마다 매번 함수가 새로 정의됨
  const handleClick = (event) => {
    //클릭이벤트 처리
  };
  
  //컴포넌트가 마운트 될 때만 함수가 정의됨
  const handleClick = useCallback((event) => {
    //클릭이벤트 처리
  }, []);
  
  return (
    <div>
      <button
        onClick = {() => {
          setCount(count + 1);
        }}
      >
        {count}      
      </button>
      <ChildComponent handleClick = {handleClick}/>
    </div>
  );
}

 

useRef : reference를 사용하기 위한 Hook, 컴포넌트의 마운트해제 전까지 유지, 변경가능한 current 속성을 가진 상자, 일반적인 JS객체 반환, 렌더링 될때마다 항상 같은 reference객체 반환, useRef() Hook은 내부의 데이터가 변경되었을 때 별도로 알리지 않음

reference : 특정 컴포넌트에 접근할 수 있는 객체, refObject.current : 현재 참조하고 있는 element

const refContainer = useRef(초깃값);
functin TextInputWithFocusButton(props){
  const inputElem = useRef(null);
  const onButtonClick = () => {
    //'current'는 마운트된 input element를 가리킴
    inputElem.current.focus();
  };
  
  return (
    <>
      <input ref = {inputElem} type="text">
      <button onClick = {onButtonClick}>
        Focus the input
      </button>
    </>
  );
}

 

Callback ref : ref의 DOM노드가 연결, 분리되었을 때 실행

ref의 DOM노드가 연결, 분리되었을 때 실행

 

function MeasureExample(props){
  const [height, setHeight] = useState(0);
  
  //useCallback : 자식컴포넌트 변경시 알림o
  const measureRef = useCallback(node => {
    if(node !== null){
      setHeight(node.getBoundingClientRect().height);
    }
  }, []); //h1태그가 마운트, 언마운트시에만 callback 함수 호출, 재렌더링x
  
  return (
    <>
      <h1 ref={measureRef}>안녕, 리액트</h1>
      <h2>위 헤더의 높이는 {Math.round(height)}px입니다.</h2>
    </>
  );
}

 

Hook의 규칙과 Custom Hook 만들기

Hook의 규칙

1. Hook은 무조건 최상위 레벨에서만 호출해야 함, Hook은 컴포넌트가 렌더링될 때마다 매번 같은 순서로 호출되어야 함

//잘못된 Hook사용법, 조건문의 결과에 따라 Hook이 달라짐
function MyComponent(props){
  const [name, setName] = useState('Inje');
  if(name !== ''){
    useEffect(() => {
      ...
    });
  }
}

2. 리액트 함수 컴포넌트에서만 Hook을 호출해야함

 

eslint-plusin-react-hooks : Hook의 규칙을 따르도록 하는 플러그인

 

Custom Hook만들기 : 여러컴포넌트에서 반복적으로 사용되는 로직 재사용

1) 이름이 반드시 use로 시작하고 내부에서 다른 Hook을 호출하는 하나의 JS함수

2) 여러개의 컴포넌트에서 하나의 Custom Hook을 사용할 때 컴포넌트 내부에 있는 모든 state와 effects는 전부 분리되어 있음(재사용을 위한 기능)

3) 각 Custom Hook 호출에 대해서 분리된 state를 얻게됨, 각 Custom Hook의 호출은 완전히 독립적임

 

//Custom Hook을 만들어야 하는 상황
function UserStatus(props){
  const [isOnline, setIsOnline] = useState(null);
  
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
    return() =>{
      ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
    };
  });
  
  if(isOnline === null){
    return '대기중..';
  }
  return isOnline ? '온라인':'오프라인';
}
import React, {useState, useEffect} from "react";

function UserListItem(props){
  const [isOnline, setIsOnline] = useState(null);
  
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ServerAPI.subscribeUserStatus(props.user.id, handleStatusChange);
    return() =>{
      ServerAPI.unsubscribeUserStatus(props.user.id, handleStatusChange);
    };
  });
  
  return(
    <li style={{color:isOnline ? 'green' : 'black'}}>
      {props.user.name}
    </li>
  );
}
//Custom Hook 추출하기
import React, {useState, useEffect} from "react";

function useUserStatus(userId){
  const [isOnline, setIsOnline] = useState(null);
  
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ServerAPI.subscribeUserStatus(userId, handleStatusChange);
    return() => {
      ServerAPI.unsubscribeUserStatus(userId, handleStatusChange);
    };
  });
  
  return isOnline;
}

//Custom Hook 사용하기
function UserStatus(props){
  const isOnline = useUserStatus(props.user.id);
  if(isOnline === null){
    return '대기중..';
  }
  return isOnline ? '온라인' : '오프라인';
}
function UserListItem(props){
  const isOnline = useUserStatus(props.user.id);
  return (
    <li style={{color:isOnline ? 'green':'black'}}>
      {props.user.name}
    </li>
  )
}

 

Hook들 사이에서 데이터를 공유하는 방법

const userList = [
  {id:1, name: 'Inje'},
  {id:2, name: 'Mike'},
  {id:3, name: 'Steve'}  
];
function ChatuserSelector(props){
  const [userId, setUserId] = useState(1);	//현재상태(data) 저장
  const isUserOnline = useUserStatus(userId);	//현재상태를 파라미터로 저장
  
  return(
    <>
        <Circle color={isUserOnline ? 'green':'red'} />
        <select
          value={userId}
          onChange={event => setUserId(Number(event.target.value))}
        >
          {userList.map(user => (
            <option key={user.id} value={user.id}>
              {user.name}
            </option>
          ))}
        </select>
   </> 
  )
}

 

(실습) Hooks 사용해보기

//useCounter.jsx
import React, {useState} from "react";

function useCounter(initialValue){
    const [count, setCount] = useState(initialValue);
    const increaseCount = () => setCount((count) => count + 1);
    const decreaseCount = () => setCount((count) => Math.max(count -1, 0));

    return [count, increaseCount, decreaseCount];
}
export default useCounter;
//Accommodate.jsx
import React, {useState, useEffect} from "react";
import useCounter from "./useCounter";

const MAX_CAPACITY = 10;

function Accommodate(props){
    const [isFull, setIsFull] = useState(false);
    const [count, increaseCount, decreaseCount] = useCounter(0);

    useEffect(() => {
        console.log("==============");
        console.log("useEffect() is called.");
        console.log(`isFull: ${isFull}`);
    });

    useEffect(() => {
        setIsFull(count >= MAX_CAPACITY);
        console.log(`Current count value: ${count}`);
    }, [count]);

    return(
        <div style={{padding: 16}}>
            <p>{`총 ${count}명 수용했습니다.`}</p>
            <button onClick={increaseCount} disabled={isFull}>입장</button>
            <button onClick={decreaseCount}>퇴장</button>
            {isFull && <p style={{color:"red"}}> 정원이 가득찼습니다.</p>}
        </div>
    )
}
export default Accommodate;
//index.js
root.render(
  <React.StrictMode>
    <Accommodate />
  </React.StrictMode>
)

 

반응형

'REACT > [inflearn] 처음만난 리액트' 카테고리의 다른 글

9강. Conditional Rendering  (0) 2022.11.10
8강. Handling Events  (0) 2022.11.09
6강. State와 Lifecycle  (0) 2022.11.03
5강. Components and Props  (0) 2022.11.03
4강. Rendering Elements  (0) 2022.11.02