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 |