6. 리액트 기본훅
훅(HOOK) 이란?
- 리액트 컴포넌트는 클래스형 컴포넌트(Class component)와 함수형 컴포넌트(Functional component)로 나뉩니다.
- 리액트 훅은 새로운 기능으로 React 16.8버전에 새로 추가된 기능
- 함수형태의 컴포넌트에서 사용되는 몇가지 기술을 Hook이라고 부른다. (useState, userEffect 등)
- 리액트 훅은 함수형 컴포넌트가 클래스형 컴포넌트의 기능을 사용할 수 있도록 해주는 기능이다.
훅의 규칙
- 최상위 에서만 Hook을 호출해야 한다 -> (함수형 컴포넌트의 중괄호 바로 아래에서만 호출해야 된다는 뜻.)
- 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하면 안된다.
- 이 규칙을 따르면 컴포넌트가 렌더링될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장된다.
- 리액트 함수 컴포넌트에서만 Hook을 호출해야 한다.
반드시 알아야 할 기본 훅
useState(초기값)
useState() : 배열반환
첫번째 배열의 요소에는 현재값을, 두번째 요소는 상태를 변경하는 (setter) 를 반환합니다.
const [data, setData] = useState('초기값')
useEffect(실행시킬 콜백함수, 값에 따른 렌더링 지정 )
userEffect의 첫번째 매개변수는 실행시킬 콜백함수
userEffect의 두번째 매개변수는 배열[]을 사용하여 특정값이 update 될 때만 실행시켜 줄 수 있습니다.
useEffect() 는 컴포넌트의 라이프 사이클을 다룹니다.
리액트 컴포넌트가 mount, mount이후, unmount때 마다 특정작업을 수행합니다.
(mount는 화면이 렌더링 된 후 화면이 브라우저에 올라가는것(생성)을 말합니다.)
라이프 사이클
#mount 이후 - 컴포넌트가 마운트 됨, 즉 컴포넌트의 첫번째 렌더링이 마치면 호출되는 메서드 입니다.
클래스형 componentDidMount() 대체
-> 렌더링 이후 데이터를 가져와야하는 일이 제일 많기 때문에 이걸 제일 많이 사용함.
함수형 훅
//useEffect(함수) - 화면이 mount된 이후에 동작됩니다
useEffect(() => {
console.log(`렌더링완료. state값${name}`);
});
# mount 이후 업데이트 될 때는 실행되지 않으려면, 두번째 매개변수 배열를 줍니다.
//useEffect(함수, []) - 화면이 첫번째 mount에서만 실행됩니다.
useEffect(()=>{
console.log('처음만 실행됩니다')
},[]);
#update 이후 - 특정값에 의해 컴포넌트가 업데이트 되고 난 후 발생합니다.
클래스형 componentDidUpdate() 대체
함수형 훅
import { useEffect, useState } from "react";
const HookEffect = () => {
//useState 훅
const [name, setName] = useState('');
const [age, setAge] = useState('');
const handleName = (e) => {
setName(e.target.value);
}
const handleAge = (e) => {
setAge(e.target.value);
}
//useEffect(함수, [state]) - 특정값이 렌더링 될때만 실행됩니다.
useEffect(() => {
console.log('age가 변경될 때 실행됩니다')
},[age, name])
return (
<>
이름:<input type="text" onChange={handleName}/><br/>
나이:<input type="number" onChange={handleAge}/><br/>
이름: {name}, 나이 {age}
</>
)
}
export default HookEffect;
#unmount직전 - 컴포넌트가 화면에서 사라지기 직전에 호출됩니다.
-화면에 렌더링 되기 직전에 기존화면이 지워집니다. 이 순간을 unmount라고 합니다.
componentWillUnMount() 대체하는 useEffect는 unmount 직전에 실행되는 것입니다.
-클래스형 componentWillUnMount() 대체
//componentWillUnMount() 대체하는 useEffect
useEffect( () => {
console.log("name이 변경될 때 return 됩니다");
//컴포넌트가 unmount될 때 실행됩니다.
return () => {
console.log('unmount됩니다'); //렌더링이 그려지면, 기존화면은 지워집니다.
console.log(`update전 값:${name}`); //state는 직전값이 나옵니다.
}
},[name]);
-위에 코드를 보면 return 문 안에 함수에서 내용은
화면이 렌더링 되기 직전에 실행됩니다. 그래서 현재 state값이 변경되었다면
변경된 state값이 화면에 다시 그려지기전에 return문 안에 내용이 실행되기 때문에.
state값은 변경되었지만 그 이전에 값이 출력됩니다.
-return문 밖에 출력문은 화면이 렌더링 되고나서 나타납니다.
위에 두 차이점을 잘 구분해 놓읍시다.
import { useState } from "react";
import HookEffect from "./hook/HookEffect";
import HookQ from "./hook/HookQ";
import HookQ2 from "./hook/HookQ2";
import HookRef from "./hook/HookRef";
const App = () => {
/*
1.필수훅
useState()
컴포넌트에서 상태값을 제어하는 가장기본이 되는 hook
useEffect()
컴포넌트의 라이프사이클(생명주기)를 다룹니다.
mount, mount이후, state변경될 때, unmount이전에 특정작업을 수행할 수 있습니다.
*/
const[visible, setvisible] = useState(true);
const handleClick = () => {
// console.log(visible);
setvisible(!visible); //visible이 가진 값의 반대
}
return(
<>
{/* effect훅 */}
<button onClick={handleClick}>{visible ? "숨기기" : "보이기"}</button>
<br/>
{visible ? <HookEffect/> : null}
<hr/>
{/* ref훅 */}
<HookRef/>
<hr/>
{/* 실습 */}
<HookQ/>
<hr/>
<HookQ2/>
</>
)
}
export default App;
*HookEffect.js
import { useEffect, useState } from "react";
const HookEffect = () => {
//useState 훅
const [name, setName] = useState('');
const [age, setAge] = useState('');
const handleName = (e) => {
setName(e.target.value);
}
const handleAge = (e) => {
setAge(e.target.value);
}
//useEffect(함수) - 화면이 mount된 이후에 동작됩니다
// useEffect(() => {
// console.log(`렌더링완료. state값${name}`);
// });
//useEffect(함수, []) - 화면이 첫번째 mount에서만 실행됩니다.
useEffect(()=>{
console.log('처음만 실행됩니다')
},[]);
//useEffect(함수, [state]) - 특정값이 렌더링 될때만 실행됩니다.
// useEffect(() => {
// console.log('age가 변경될 때 실행됩니다')
// },[age, name])
//componentWillUnMount() 대체하는 useEffect
useEffect( () => {
console.log("name이 변경될 때 return 됩니다");
//컴포넌트가 unmount될 때 실행됩니다.
return () => {
console.log('unmount됩니다'); //렌더링이 그려지면, 기존화면은 지워집니다.
console.log(`update전 값:${name}`); //state는 직전값이 나옵니다.
}
},[name]);
//★★useEffect는 여러개여도 됩니다.★★
//하지만 너무 많이 사용하는 것은 권장하지 않습니다.
return (
<>
이름:<input type="text" onChange={handleName}/><br/>
나이:<input type="number" onChange={handleAge}/><br/>
이름: {name}, 나이 {age}
</>
)
}
export default HookEffect;
#특정 태그에 이름달기 useRef()
* useRef() 라는 훅을 무분별하게 막 사용하는 것은 권장하지 않습니다.
기본적으로 데이터는 state로 관리를 하되 직접적으로 태그를 핸들링 해야하는 경우에만 useRef()를 사용하도록 합시다.
useRef(초기값)
const 사용할이름 = useRef(null);
이벤트를 사용하다 보면 특정 태그에 접근해서 핸들링 하는 경우가 생깁니다.
arrow function에 event 매개변수를 이용해서, 자신 태그에는 접근할 수 있지만, 다른태그는 핸들링 하기가 어렵습니다.
이런경우 useRef() 훅을 이용해서 특정태그에 이름을 지정하고 핸들링 할 수 있습니다.
import { useRef, useState } from "react";
const HookRef = () => {
//사용자 입력값 data, 화면에 출력값 result
const[form, setForm] = useState({data: '', result:''})
const handleChange = (e) => {
setForm({...form,["data"]:e.target.value}) //state값을 객체로 관리할 때 나오는 문장
}
//등록
const handleClick = () => {
setForm({data:'', result:form.data})
//Ref의 사용
// console.log(inputTag);
// console.log(inputTag.current);
console.log(inputTag.current.value);
inputTag.current.focus();
}
//특정 태그에 이름달기 useRef()
//반환된 이름을 사용할 태그에 ref속성에 넣습니다.
const inputTag = useRef(null);
// console.log(inputTag); //특정 객체를 주고 안에 current는 속성이 있음을 확인
return (
<>
내용: <input type="text" onChange={handleChange} value={form.data} ref={inputTag}/>
<button onClick={handleClick}>등록하기</button>
<br/>
결과: {form.result}
</>
)
}
export default HookRef;
#useEffect(), useRef() 사용한 실습
*HookQ.js
import { useEffect, useRef, useState } from "react";
const HookQ = () => {
const idTag = useRef(null)
const pwTag = useRef(null)
// 1.컴포넌트가 마운트된 이후 한번만 id태그에 포커스를 줍니다.
useEffect(()=>{
idTag.current.focus(); //처음 마운트 이후 id태그에 포커싱
},[])
// 2.id, pw는 state로 관리합니다
// 로그인버튼 클릭시 공백이라면 공백인 태그에 포커스를 주세요.
// 로그인버튼 클릭시 공백이 아니라면 경고창으로 id와 pw를 출력해주세요.
const[login, setLogin] = useState({id:'',pw:''});
const handleChange = (e) => {
// console.log(e.target);
setLogin({...login, [e.target.name]:e.target.value}) //tag의 name 속성 이용.
// if(e.target.type === 'text'){
// setLogin({...login, ["id"] : e.target.value})
// }
// if(e.target.type === 'password'){
// setLogin({...login, ["pw"]: e.target.value})
// }
}
const handleClick = () => {
if(idTag.current.value === ''){
idTag.current.focus(); //id태그
} else if(pwTag.current.value === ''){
pwTag.current.focus(); //pw태그
} else {
alert(idTag.current.value + ',' + pwTag.current.value);
}
//state로 처리한것.
// if(login.id === ''){
// idTag.current.focus();
// } else if(login.pw === ''){
// pwTag.current.focus();
// } else {
// // console.log(login.pw);
// alert(login.id + ',' + login.pw);
// // alert(login.id , login.pw);
// //alert는 매개변수가 하나여서 ,로 여러개 표현하는게 안됨. console.log는 매개변수가 가변매개변수여서 ,로 여러개 표현 가능한것.
// }
}
return (
<div>
<input type="text" name="id" placeholder="아이디" ref={idTag} onChange={handleChange}/>
<input type="password" name="pw" placeholder="비밀번호" ref={pwTag} onChange={handleChange}/>
<button onClick={handleClick}>로그인</button>
</div>
)
}
export default HookQ;
*HookQ2.js
import { useRef, useState } from "react";
const HookQ2 = () => {
/*
실습(할일목록 만들기)
1.state는 {todo:'', list:[]}로 관리하세요
2.할일 목록을 작성하고 클릭시, list에 인풋에 입력값을 추가하고 map을 통해서 화면을 그립니다.
3.등록버튼 클릭 이후에는 ref를 활용해서 input태그에 포커싱을 줍니다.
*/
const[todoList,setTodoList] = useState({todo:'', list:[]});
const inputTag = useRef(null); //input태그
//인풋데이터 핸들링
const handleChange = (e) => {
setTodoList({...todoList, [e.target.name]:e.target.value})
}
//추가하기
const handleClick = () => {
//여기서 concat대신에 push를 쓰면 안되는 이유는 push는 원본 값에 새로운 데이터를 넣는 것.
//그래서 push를 쓰면 원본 값이 변경이 되는 것이기 때문에 사용하면 안됨.
//concat은 기존 list는 유지, 새롭게 합쳐진 리스트를 반환.
setTodoList({...todoList,['todo']:'',['list']:todoList.list.concat(todoList.todo)})
//inputTag에 포커싱
inputTag.current.focus();
}
//ul태그에 할일 목록 그리기.
const newList = todoList.list.map((item, index)=>
<li style={{listStyle:"none", fontWeight:"600", fontSize:"20"}}key={index}>{index+1}. {item}</li>
)
return(
<div>
<h3>ref로 할일목록 만들기</h3>
<input type="text" name="todo" placeholder="할일목록" onChange={handleChange} ref={inputTag} value={todoList.todo}/>
<button type="button" onClick={handleClick}>등록하기</button>
<ul>
{newList}
</ul>
</div>
)
}
export default HookQ2;
훅의 종류는 이뿐이 아니고 굉장히 많습니다.
-부가적인 훅-
# useReducer()
* useReducer() 란?
useState의 사용을 외부에서 사용할 수 있게 해주는 훅 (state의 변경을 외부에서 제어할 수 있습니다.)
외부에서 제어할 수 있게됨으로서 state를 다루는 부분을 컴포넌트화 해서 재활용 할 수 있습니다.
-> 컴포넌트화 해놓은 리듀서를 원하는 컴포넌트에 가져와서 state를 다루는 기능을 그대로 사용하면 됩니다.
*useReducer()의 생김새
const [현재값,리듀서를 업데이트할수있는 함수] = useReducer(외부에서 사용할 리듀서함수, 리듀서의 초기값)
*state의 값이 이동하는 순서
*HookReducer.js
//리듀서를 사용하면 state값을 외부에서 사용할 수 있다.
//state 값을 외부에서 사용할 수 있다는 것의 장점은 재활용이 가능하다는 것.
//리듀서를 원하는 컴포넌트에 가져와서 기능을 그대로 사용할 수 있음.
import { useEffect, useReducer } from "react";
//이렇게 리듀서를 사용하면 재활용이 가능함.
import {myReducer} from './HookReducerComponent';
//리듀서 선언 (현재의 state, 업데이트에 필요한정보-객체임)
//action을 판단해서 state를 체인지
/*
const myReducer = (state, action) => {
//action은 객체 ★
console.log(state);
console.log(action);
if(action.type === 'increase'){
state = {value : state.value + 1};
} else if(action.type === 'decrease'){
state = {value : state.value - 1};
} else if(action.type === 'reset'){
state = {value : 0}
}
return state; //return {value: state.value + 1}
}
*/
const HookReducer = () => {
//const[현재값, 리듀서를 업데이트할수 있는 함수] = useReducer(외부에서 사용할 리듀서함수, 리듀서의 초기값)
const [state, func] = useReducer(myReducer, {value: 0})
/*
useEffect(()=>{
func({type: 'reset'}); //리듀서를 싱행시키고, MyReducer의 action으로 전달됩니다.
},[])
console.log(state)
*/
const up = () => {
func({type: "increase"});
}
return (
<>
<button onClick={up}>증가</button>
<button onClick={()=> func({type:"decrease"})}>감소</button>
<button onClick={()=> func({type: "reset"})}>초기화</button>
결과{state.value}
</>
)
}
export default HookReducer;
*HookReducerComponent.js
HookReducer.js (위) 와 HookReducer2.js (아래) 의 리듀서를 컴포넌트화 해놓은 파일입니다.
const myReducer = (state, action) => {
//action은 객체
// console.log(state);
// console.log(action);
if(action.type === 'increase'){
state = {value : state.value + 1};
} else if(action.type === 'decrease'){
state = {value : state.value - 1};
} else if(action.type === 'reset'){
state = {value : 0}
}
return state; //return {value: state.value + 1}
}
const nameReducer = (state, action) => {
// console.log(action); //e.target
/*
if(action.name == 'name'){
state = {...state, ['name']: action.value};
} else if(action.name == 'age'){
state = {...state, ['age']: action.value};
}
*/
state = {...state, [action.name]: action.value};
return state;
}
//기본디폴트 모형
export {myReducer, nameReducer};
*HookReducer2.js
import { useReducer } from "react";
import { nameReducer } from "./HookReducerComponent";
//리듀서
/*
const nameReducer = (state, action) => {
// console.log(action); //e.target
// if(action.name == 'name'){
// state = {...state, ['name']: action.value};
// } else if(action.name == 'age'){
// state = {...state, ['age']: action.value};
// }
state = {...state, [action.name]: action.value};
return state;
}
*/
const HookReducer2 = () => {
//[스테이트, 리듀서제어함수] = useReducer(리듀서, 초기값)
const[state, func] = useReducer(nameReducer, {name: '', age: ''});
const {name, age} = state; //스테이트 값 구조분해할당
return (
<>
이름:<input type="text" name="name" onChange={(e) => func(e.target)}/>
나이:<input type="text" name="age" onChange={(e) => func(e.target)}/>
<br/>
결과값:{name}<br/>
결과값:{age}<br/>
</>
)
}
export default HookReducer2;