Hook 이 뭐야?
Hook이 지켜야할 규칙
클래스컴포넌트의 라이프사이클은 컴포넌트 중심으로 맞춰져 있다면, 함수컴포넌트는 데이터의 변화에 중심적으로 훅이 작동한다.
useEffect(업데이트함수, 의존성배열)
console.log('hidden changed');
console.log('hidden이 바뀔 예정입니다.');
useEffect(() => {
console.log('hidden changed');
return () => {
console.log('hidden이 바뀔 예정입니다.');
};
}, [hidden]);
import React, { useEffect, useRef } from 'react';
const Basic = () => {
// 초기값을 false로 놓기
const mountRef = useRef(false);
// mounted 되면 mountRef가 True로 바뀌고, 재랜더링이 될 때는 mountRef가 True이기 때문에 작동시킬 로직을 작동시킬 수 있음
useEffect(() => {
if (mountRef.current) {
// 작동할 로직
console.log('updated!');
} else {
mountRef.current = true;
}
});
return <div>Basic</div>;
};
export default Basic;
clean-up function 이란?
effect를 정리(clean-up)하는 시점
정리(clean-up)가 필요한 Effects
빈 배열로도 메모리 누수가 해결되지 않는 경우
useEffect(() => {
window.addEventListener("resize", checkSize);
return () => {
window.removeEventListener("resize", checkSize); //다음 useEffect 호출시 다음 useEffect 실행 직전에 실행됨
};
}, []);
변수.current
로 접근해야함 useRef
를 통해 ref 변수 선언ref변수.current
를 통하여 저장한 DOM노드를 사용하기import React, { useState, useRef } from 'react';
function InputSample() {
const [inputs, setInputs] = useState({
name: '',
nickname: ''
});
// ref 선언
const nameInput = useRef();
const { name, nickname } = inputs;
const onChange = e => {
const { value, name } = e.target;
setInputs({
...inputs,
[name]: value
});
};
const onReset = () => {
setInputs({
name: '',
nickname: ''
});
// ref 를 통해서 focus 이동시키기
nameInput.current.focus();
};
// html 태그에 ref={ref명}을 달아줌
return (
<div>
<input
name="name"
placeholder="이름"
onChange={onChange}
value={name}
ref={nameInput}
/>
<input
name="nickname"
placeholder="닉네임"
onChange={onChange}
value={nickname}
/>
<button onClick={onReset}>초기화</button>
<div>
<b>값: </b>
{name} ({nickname})
</div>
</div>
);
}
export default InputSample;
대부분 경우에 React에서 폼을 구현하는데 제어 컴포넌트를 사용하는 것이 좋습니다.
// 제어 컴포넌트 - state
const [state, setState] = useState("");
// input 값 변경에 따른 state 값 변경
const handleChange = (event) => {
setState(event.target.value);
}
// submit 버튼 클릭을 통한 state 값 console 로 출력
const handleSubmit = (event) => {
event.preventDefault();
console.log(state);
}
// 컴포넌트가 mounted가 되고 나면 target DOM에 focus 이벤트 넣기
useEffect(() => {
refContainer.current.focus();
});
return (
<form className="form" onSubmit={handleSubmit}>
<input type="text" ref={refContainer} value={state} onChange={handleChange}>
</form>
)
ref를 꼭 사용해야하는 경우는 많지않다. 될 수 있으면 props나 state를 고정시키는 것이 좋다.
하지만,
interval이나 subscription 같은 명령형 API를 다룰 때 ref는 유용하게 사용할 수 있다.
ref를 이용하여 패턴 최적화(userCallback이 자주 바뀔 때)
context 란?
작동방식
createContext 내부에 공유하길 원하는 데이터의 초깃값을 넣어두고 value 변수로 묶고, export const 로 외부에서 접근이 가능하도록 export 하기
export const 변수 = createContext({ ... })
value 객체는 객체이므로 리렌더링의 주범이 되므로 useMemo로 캐싱하기
context값을 사용할 자식컴포넌트들을 <createContext로만든변수.Provider value={useMemo로저장한value값}>
으로 감싸기
context값을 사용할 컴포넌트에서 useContext(createContext로만든변수)
로 값을 불러오고 사용하기
// context를 만드는 GrandParent
import React, { createContext, useMemo, useState } from 'react';
import Parent from './Parent';
// createContext 로 context 생성
export const UserContext = createContext({
setLoggedIn: () => {},
setLoading: () => {},
});
const GrandParent = () => {
const [loggedIn, setLoggedIn] = useState(false);
const [loading, setLoading] = useState(false);
// context 를 사용하는 컴포넌트들이 GrandParent가 리랜더링 될때마다 랜더링되지 않도록 하기 위해 useMemo 사용
const value = useMemo(() => ({ setLoggedIn, setLoading }), [setLoggedIn, setLoading]);
// UserContext.Provider 로 감싸서 내부의 컴포넌트들이 context 사용할 수 있도록 하기
return (
<UserContext.Provider value={value}>
<Parent />
</UserContext.Provider>
);
};
export default GrandParent;
// 아무것도 안하고 children 을 불러오는 Parent 요소
import React from 'react';
import Children from './Children';
const Parent = () => {
return <Children />;
};
export default Parent;
// context 값을 가져와서 쓰는 Children
import React, { useContext } from 'react';
import { UserContext } from './GrandParent';
const Children = () => {
// GrandParent의 Context 불러오기
const { setLoading, setLoggedIn } = useContext(UserContext);
// GrandParent의 Context 값 사용하기
return (
<>
<button onClick={() => setLoading((prev) => !prev)}>로딩토글</button>
<button onClick={() => setLoggedIn((prev) => !prev)}>로딩토글</button>
</>
);
};
export default Children;
function 컴포넌트는 랜더링이 일어날 떄마다 내부에 선언된 함수나 변수를 새로 생성한다.
이렇게 되면, 불필요한 메모리를 낭비하고, 최적화에도 좋지 않게되는데, useCallback이나 useMemo같은 캐싱을 해주는 hook을 통하여 성능 최적화를 하게된다.
주의점
함수형 컴포넌트 내부에서 쓰이는 함수를 메모이제이션(memoization)하기 위해 사용되는 hook
const memoizedCallback = useCallback(함수, 의존성배열);
function Component() {
const [count, setCount] = React.useState(0)
// useCallback 사용
const handleClick = React.useCallback(
() => console.log('clicked!'),
[])
return (
<>
<button onClick={() => setCount(count + 1)}>카운트 올리기</button>
<button onClick={handleClick}>클릭해보세요!</button>
</>
)
}
useCallback()
을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있다.===
)을 취급할 때 하는 행위가 완전히 동일하더라도, 함수 자체가 객체로 취급되기 때문에 메모리 주소에 의한 참조비교가 일어난다.함수형 컴포넌트 내부에서 쓰는 연산이 필요한 변수를 메모이제이션(memoization)하여 연산을 최적화한다.
const memoized = useMemo(값을반환하는함수, 의존성배열);
값을 반환하는 함수
원래 값을 계산하던 함수를 넣어준다
하지만, 매개변수를 넣어줄 수 없기떄문에 () => 값을반환하는함수(매개변수)
꼴로 넣어준다.
의존성 배열
// 계산된 값을 반환하는 함수 선언
function countHighScores(scores) {
console.log('80점 이상 점수 세기');
return users.filter(score => score>=80).length;
}
function App() {
// 사용할 매개변수
const [scores, setScores] = useState([60, 70, 90, 100, 50]);
// useMemo를 통한 결과값 캐싱
const count = useMemo(() => countHighScore(scores), [scores]);
return (
<>
<div>80 점 이상 점수 : {count}</div>
</>
)
컴포넌트를 만들다보면, 반복되는 로직이 자주 발생되는데, 커스텀 Hook을 만들어서 반복되는 로직을 쉽게 재사용할 수 있다.
Custom Hook이란?
알아야할 사항
사용해보기
// custom hook으로 만들기
import { useReducer } from 'react';
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
// custom hook만들고 export default 해주기 - 매개변수는 props
export default function useInputs(initialForm) {
const [state, dispatch] = useReducer(reducer, initialForm);
const onChange = e => {
dispatch(e.target);
};
return [state, onChange];
}
// 기존에 hook을 쓰던 컴포넌트를 custom hook으로 대체
import React from 'react';
import useInputs from './useInputs';
/* useInputs 로 이동
function reducer(state, action) {
return {
...state,
[action.name]: action.value
};
}
*/
const Info = () => {
/* useInputs로 대체됨에 따라 변경
const [state, dispatch] = useReducer(reducer, {
name: '',
nickname: ''
});
*/
const [state, onChange] = useInputs({
name: '',
nickname: ''
});
const { name, nickname } = state;
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임: </b>
{nickname}
</div>
</div>
</div>
);
};
export default Info;