Hooks
Hooks란?
• 리액트 버전 16.8에 새로 도입된 기능
• 함수형 컴포넌트에서 상태 관리를 할 수 있는 기능 제공
종류
• useState
• useEffect
• useReducer
• useMemo
• useCallback
• useRef
useState()
• 가장 기본적인 Hook
• 함수형 컴포넌트가 가변적인 상태를 지닐 수 있도록 해 줌
• 형태
const [state, setState] = useState(초기값); |
• 예시
const [ number, setNumber ] = useState(3); |
• 현재 number 값 = 3
• number 값 변경 = setNumber(6);
실습) AddName
AddName.js (App.js에서는 호출하는 중)
import React, { useState } from "react";
function AddName() {
const [names, setNames] = useState(["정수아", "리액트"]);
const [input, setInput] = useState("");
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames([input]);
}
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행 결과)
"리액트" 다음 줄에 "내용추가"가 올 줄 알았는데 기존 내용 없어지고 "내용추가"만 입력되는 문제 발생
문제 원인: map함수를 names라는 배열에 돌리면서 p태그를 그려라 하고 있고 names 라는 배열 안에 두개의 데이터가 있음. uploadInput 코드가 내용을 추가 중이며 기존에 있던 names를 새로운 값으로 바꿔주세요 라고 하고 있음.
→ 인풋 값으로 인풋 안에는 기존의 값 유지 안하고 내가 새롭게 입력한 한 개로 바꿔주세요(갱신해주세요)라고 한 꼴임
따라서 기존의 값 유지해달라는 코드를 추가해야함
uploadInput() 함수 수정
state의 기존 값을 유지하면서 새로운 값을 추가하려면 setState()의 콜백 함수에 prevState 값을 전달해서 유지해야 함
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
위에는 화살표 함수 아래는 그냥 함수 (둘이 같음)
function uploadInput() {
setNames(function(prevState) {
return [input, ...prevState]
});
}
위 코드 추가하여 AddName.js 수정
import React, { useState } from "react";
function AddName() {
const [names, setNames] = useState(["정수아", "리액트"]);
const [input, setInput] = useState("");
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]); //수정완료
}
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행결과)
prevsate는 매개변수일 뿐(이름바꿔도 상관 없음) 그러나 이전 값들이 들어온다
게시판 댓글기능 활용
setState() 함수 동작 과정
render() - 화면에 출력 바뀐 것만 자동으로 갱신
setState() 함수
setState() 함수의 인자로 state를 전달 시 동작 과정
• 이전 state와 새로운 state를 비교하여 바뀐 데이터만 업데이트
• 변경되지 않은 값은 그대로 유지함
useState() 성능 최적화
성능 최적화
• useState() 함수의 인자에 초기값을 지정한 경우
• state 값이 업데이트 될 때마다 초기값이 계속해서 호출됨
• 만약 초기값에 복잡한 계산식이 있다면 성능 저하 문제 발생
실습)
AddName.js 수정
import React, { useState} from "react";
function AddName() {
const [names, setNames] = useState(heavyWork());
const [input, setInput] = useState("");
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
}
return ["정수아", "리액트"];
}
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
});
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행 결과
문제점
• state가 업데이트 될 때마다 heavyWork()가 계속 호출
• 초기값은 최초 한번만 호출되도록 수정해야 함
해결 방법
• useState()함수의 인자로 콜백 함수를 넣어줌
AddName.js 수정
import React, { useState} from "react";
function AddName() {
const [names, setNames] = useState(() => heavyWork()); //콜백함수로 넣어줘서 수정완료
const [input, setInput] = useState("");
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
}
return ["정수아", "리액트"];
}
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
});
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행 결과
useEffect()
• 리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 수행하도록 설정해주는 Hook
• 최초에 한번 실행하게 하고 싶은 작업을 작성할 때 주로 사용
예) fetch()를 이용한 네트워크 통신 연결
useEffect() 구조
useEffect() 매개변수로 두개의 데이터를 받아올 수 있음. (첫번째는 필수, 두번째는 옵션)
매개변수 1 - 콜백함수
매개변수 2 - 배열 or [변수이름]
첫번째 케이스) useEffect()의 매개변수에 콜백함수만 있는 경우
• useEffect()의 매개변수에 콜백함수만 있는 경우
• 컴포넌트가 렌더링 될 때마다 실행됨
useEffect(() => {
// 작업
});
콜백함수만 있는 경우엔 사용하는 의미가 없음
MyComponent() {
function test(){
//작업
}
}
- 네트워크 통신 -> 데이터를 가져오기
- 매번 데이터를 가져온다.
두번째 케이스) useEffect()의 매개변수에 콜백함수, 배열이 있는 경우
매개변수1: 콜백함수
매개변수2: 배열 => []
• 컴포넌트가 처음 렌더링 될 때 실행
• value 값이 변경 되었을 때 실행
- 최초에 한번만 콜백함수를 실행 (배열을 비워주기)
useEffect(() => { // 작업 }, []); |
세번째 케이스) useEffect()의 매개변수인 배열에 데이터가 있는 경우
매개변수1 : 콜백함수
매개변수2 : 배열-> [변수이름]
- 변수의 값이 변경될 때마다 콜백함수 실행
useEffect(() => { // 작업 }, [value]); |
👉 의미있게 사용하려면 매개변수 2개 넣어야 함
실습) useEffect 사용하여 AddName.js 수정
import React, { useState, useEffect } from "react";
function AddName() {
const [names, setNames] = useState(() => heavyWork());
const [input, setInput] = useState("");
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
}
return ["정수아", "리액트"];
}
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
useEffect(() => {
console.log("렌더링이 완료되었습니다.");
console.log({ names });
});
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행 결과
mount될 때만 실행하고 싶을 때
함수의 두 번째 매개변수에 빈 배열을 넣음
useEffect(()=>{ console.log("렌더링이 완료되었습니다."); console.log({names}); }, []) |
useEffect 두번째 변수에 빈배열 추가하여 AddName.js 수정
import React, { useState, useEffect } from "react";
function AddName() {
const [names, setNames] = useState(() => heavyWork());
const [input, setInput] = useState("");
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
}
return ["정수아", "리액트"];
}
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
useEffect(() => {
console.log("렌더링이 완료되었습니다.");
console.log({ names });
}, []);
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행 결과)
특정 값이 update될 때만 실행하고 싶을 때
함수의 두 번째 매개변수 배열에 검사하고 싶은 값을 넣어줌
useEffect(()=>{
console.log("렌더링이 완료되었습니다.");
console.log({names});
}, [names])
import React, { useState, useEffect } from "react";
function AddName() {
const [names, setNames] = useState(() => heavyWork());
const [input, setInput] = useState("");
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
}
return ["정수아", "리액트"];
}
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
useEffect(() => {
console.log("렌더링이 완료되었습니다.");
console.log({ names });
}, [names]);
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행 결과)
※ 내꺼만 두번 렌더링 되는 사항이 발생
index.js 에서 블록 처리 -> 1번만 랜더링 됨
뒷정리하기 - cleanup
컴포넌트가 update되기 직전 또는 unmount되기 직전에 어떠한 작업을 수행하고 싶다면 뒷정리(cleanup) 작업을 해줘야 함
해결 방법
useEffect() 함수 내부에서 return 함수를 반환하면 됨
실습) cleanup
AddName.js 수정
import React, { useState, useEffect } from "react";
function AddName() {
const [names, setNames] = useState(() => heavyWork());
const [input, setInput] = useState("");
function heavyWork() {
for (let i = 0; i < 1000; i++) {
console.log("엄청 복잡한 계산 중.. 시간 오래 걸림..");
}
return ["정수아", "리액트"];
}
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
useEffect(() => {
console.log("렌더링이 완료되었습니다.");
console.log({ names });
return() => { //뒷작업 해주기
console.log("cleanup");
console.log({names});
}
}, [names]);
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<div>
{names.map((name, idx) => (
<p key={idx}>{name}</p>
))}
</div>
</div>
);
}
export default AddName;
실행 결과
useRef()
컴포넌트 내부에서 사용되는 변수를 저장하는 Hook
특징
• 컴포넌트가 재렌더링되어도 저장된 변수 값을 유지 - 실제 화면을 다시 그리진 않음
• 불필요한 렌더링을 방지할 수 있음
• 특정 DOM 요소에 접근 가능
사용 방법
const ref = useRef(value) |
• useRef() 함수는 value 값으로 초기화된 ref 객체를 반환
• ref 객체
{ current : value } |
• ref 객체 값 변경
ref.current = "hello" |
함수에 매개변수로 내가 넣어주고 싶은 값을 넣어주면 됨
실습) useRef() 사용하여 값 저장하기
UseRefComponent1.js
import React, {useRef} from 'react';
const UseRefComponent1 = () => {
const ref = useRef("안녕하세요");
console.log("변경 전 ref 값 : ", ref.current);
ref.current = "Hello";
console.log("변경 후 ref 값 : ", ref.current);
return (
<div>
</div>
);
};
export default UseRefComponent1;
실행 결과)
실습) State vs useRef()
UseRefComponent2.js
import React, { useState, useRef } from "react";
const UseRefComponent2 = () => {
const [count, setCount] = useState(0);
const countRef = useRef(0);
function addStateHandler() {
console.log("STATE 변경");
setCount(count + 1);
}
function addRefHandler() {
countRef.current = countRef.current + 1;
console.log(countRef.current);
}
return (
<div>
<h3>State 값 : {count}</h3>
<h3>Ref 값 : {countRef.current}</h3>
<button onClick={addStateHandler}>State</button>
<button onClick={addRefHandler}>Ref</button>
</div>
);
};
export default UseRefComponent2;
실행 결과
• State 버튼 5번 클릭, Ref 버튼 3번 클릭
• State 버튼 1번 클릭
UseRefComponent3.js
import React, { useState, useRef } from "react";
const UseRefComponent3 = () => {
const [refresh, setRefresh] = useState();
const countRef = useRef(0);
let currentVar = 0;
function refreshHandler() {
setRefresh(refresh + 1);
}
function addRefHandler() {
countRef.current = countRef.current + 1;
console.log("ref : ", countRef.current);
}
function addVarHandler() {
countRef.current = countRef.current + 1;
console.log("ref : ", countRef.current);
}
function addVarHandler() {
currentVar = currentVar + 1;
console.log("var : ", currentVar);
}
return (
<div>
<h3>Ref 값 : {countRef.current}</h3>
<h3>변수 값 : {currentVar}</h3>
<button onClick={addRefHandler}>Ref</button>
<button onClick={addVarHandler}>Var</button>
<button onClick={refreshHandler}>Rendering</button>
</div>
);
};
export default UseRefComponent3;
실행 결과
• Ref 버튼 3번 클릭, Var 버튼 2번 클릭, Rendering 버튼 1번 클릭
useState값이 변경되면 화면이 렌더링 됨
useRef 값은 저장, 변경되고 있지만 화면 렌더링(출력)은 안 됨
컴포넌트 내부에서 자주 바뀌는 거 (화면에 영향을 미치지는 않는) - useRef에 저장하면 됨
화면을 재렌더링 한다는 것은 컴퓨터 메모리를 많이 쓴다는 것임
일반변수
값은 바뀌지만 렌더링되는 순간에 값이 초기값으로 되돌아감 - 거의 안 씀
실습) useRef()로 DOM에 접근하기
import React, {useEffect, useRef} from 'react';
const UseRefDom = () => {
const inputRef = useRef();
useEffect(()=> {
console.log(inputRef);
//input 태그에 focus 설정
inputRef.current.focus();
}, [])
return (
<div>
ID : <input type="text" ref={inputRef} />
</div>
);
};
export default UseRefDom;
실행 결과
useRef - ref는 JS의 getElementById()처럼, component의 어떤 부분을 선택할 수 있게 해주는 함수
리액트에 있는 모든 component는 reference element를 가지고 있어서,
어떤 component에 ref={변수명} 을 넣어주면, 해당 component를 참조하게 됨
출처)
https://velog.io/@suuhyeony/React-useRef-useEffect
[리액트] useRef, useEffect
리액트에 내장된 Hook에 대해 더 알아보자.특정 DOM을 가리킬 때 사용하는 Hook 함수.Ex. 포커스 설정, 특정 엘리먼트의 크기/색상 변경 등..: ref는 JS의 getElementById()처럼, component의 어떤 부분을 선택
velog.io
[실습회고]
1. 라면
초
콜
렛
이런식으로 뜨는 문제 발견
import React, {useState} from "react";
const Practice1 = () => {
const [names, setNames] = useState(["초콜렛", "사탕"]);
const [input, setInput] = useState("");
function InputChange(e) {
setInput(e.target.value);
}
function uploadInput() {
setNames((prevState) => [input, ...prevState]);
}
return (
<div>
<input type="text" onChange={InputChange} />
<button onClick={uploadInput}>추가</button>
<ul>
{names.map((name, idx) => (
<li key={idx}>{name}</li>
))}
</ul>
</div>
);
};
export default Practice1;
setNames((prevState) => [input, ...prevState]);
}
3.
import React, { useEffect, useState } from "react";
const Practice3 = () => {
const [count, setCount] = useState(0);
const [renderCount, setRenderCount] = useState(-1);
useEffect(() => {
setRenderCount(renderCount + 1);
console.log("랜더링 완료");
},[count]);
return (
<div>
<h1>Count : {count}</h1>
<h1>랜더링 횟수 : {renderCount}</h1>
<button onClick={() => setCount(count + 1)}>클릭</button>
</div>
);
};
export default Practice3;
useRef를 써야 하는 문제인데 안 쓰고 풀어벌인 ,,,
그리고 렌더링 카운트 세는 게 0부터 시작이 안 되어서 -1로 박아놓은 상태
풀이
import React, { useEffect, useRef, useState } from "react";
const Prac03 = () => {
const [count, setCount] = useState(0);
// 값이 변해도 렌더링되지 않는 useRef()를 사용
const renderCount = useRef(0);
// 렌더링 될 때마다 실행
useEffect(() => {
renderCount.current = renderCount.current + 1;
console.log("렌더링 완료");
});
return (
<div>
<h1>Count : {count}</h1>
<h1>렌더링 횟수 : {renderCount.current}</h1>
<button onClick={() => setCount(count + 1)}>클릭</button>
</div>
);
};
// const Prac03 = () => {
// const [count, setCount] = useState(0);
// const [renderCount, setRenderCount] = useState(0);
// // 의존성 배열이 없기 때문에, 렌더링 될 때 마다 useEffect()가 실행
// // 따라서 setRenderCount()는 계속 호출 됨
// useEffect(() => {
// setRenderCount(renderCount + 1);
// console.log("렌더링 완료");
// });
// return (
// <div>
// <h1>Count : {count}</h1>
// <h1>렌더링 횟수 : {renderCount}</h1>
// <button onClick={() => setCount(count + 1)}>클릭</button>
// </div>
// );
// };
export default Prac03;
4. 풀었긴 했는데 처음엔 또 useref를 안썼었다,,,
풀이
import React, { useRef, useState } from "react";
/*
const Prac04 = () => {
const [currentText, setCurrentText] = useState("");
const inputRef = useRef(); // inputRef = { current : input }
function sendBtnHandler(e) {
setCurrentText(inputRef.current.value); // 렌더링 -> inputRef.current.value 화면 업데이트
console.log("렌더링 완료");
}
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={sendBtnHandler}>전송</button>
<h3>전송된 단어 : {currentText}</h3>
</div>
);
};
*/
const Prac04 = () => {
const [text, setText] = useState("");
const [currentText, setCurrentText] = useState("");
function changeHandler(e) {
setText(e.target.value);
console.log(text);
}
return (
<div>
<input type="text" value={text} onChange={changeHandler} />
<button onClick={() => setCurrentText(text)}>전송</button>
<h3>전송된 단어 : {currentText}</h3>
</div>
);
};
export default Prac04;
useRef 굉장히 편한 것 같은데 아직 익숙치 않아서 사용하기 쉽지 않다 ,, 그러나 익숙해지도록 노력할 것 ,,,!
새싹DT 기업연계형 프론트엔드 실무 프로젝트 과정 8주차 블로그 포스팅
'React' 카테고리의 다른 글
[새싹 프론트엔드] 8주차 - 3 Context (0) | 2022.12.07 |
---|---|
[새싹 프론트엔드] 8주차 - 2 Hooks (0) | 2022.12.06 |
[새싹 프론트엔드] 7주차 - 4 (0) | 2022.12.01 |
[새싹 프론트엔드] 7주차 - 3 (0) | 2022.11.30 |
[새싹 프론트엔드] 7주차 - 2 (0) | 2022.11.29 |