일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- react
- list
- 리덕스
- 리사이클러뷰
- CSS
- hot Reloading
- php문법
- rebase
- 가상회선교환
- 공부
- Git
- typescript
- 자동반영
- merge
- list.map
- createPortal
- Kotlin
- 컬러구성
- redux
- useState
- 리액트
- 비동기처리
- useCallback
- pull
- async
- await
- 웹
- 리액트를 다루는 기술
- equalityFn
- javascript
- Today
- Total
공부블로그
리액트 8강. Hooks 본문
Hooks는 함수형 컴포넌트에서도 상태 관리를 할 수 있는 useState, 렌더링 직후 작업을 설정하는 useEffect 등의 기능을 제공한다.
리액트 내장 Hooks를 사용하는 방법을 배우고 커스텀 Hooks를 만들어보자.
8. 1 ) useState
useState는 함수형 컴포넌트에서도 가변적인 상태를 지닐 수 있도록 해준다. ( 3장에서 배움 )
Counter. js
import React, { useState } from "react";
const Counter = () => {
const [value, setValue] = useState(0);
return (
<div>
<p>
현재 카운터 값은 <b>{value}</b>입니다.
</p>
<button onClick={() => setValue(value + 1)}>+1</button> // value값 업데이트
<button onClick={() => setValue(value - 1)}>-1</button>
</div>
);
};
export default Counter;
App. js
import React from "react";
import Counter from "./Counter";
const App = () => {
return <Counter />;
};
export default App;
8. 1. 1 ) useState 여러번 사용하기
Info. js
import React, { useState } from "react";
const Info = () => {
const [name, setName] = useState("");
const [nickname, setNickname] = useState("");
const onChangeName = (e) => {
setName(e.target.value);
};
const onChangeNickname = (e) => {
setNickname(e.target.value);
};
return (
<div>
<div>
<input value={name} onChange={onChangeName} />
<input value={nickname} onChange={onChangeNickname} />
</div>
<div>
<div>
<b>이름:</b> {name}
</div>
<div>
<b>닉네임:</b> {nickname}
</div>
</div>
</div>
);
};
export default Info;
8. 2 ) useEffect
useEffect는 모든 렌더링이 완료된 후에 수행됩니다만, 어떤 값이 변경되었을 때만 실행되게 할 수도 있습니다.
8. 2. 1 ) 모든 렌더링이 완료된 후에 실행하고 싶을 때
useEffect에서 설정한 함수를 컴포넌트가 맨 처음 렌더링될 때만 실행되고,
업데이트될 때는 실행하지 않으려면 함수의 두번째 파라미터로 비어있는 배열을 넣어준다.
Info. js - useEffect
useEffect(() => {
console.log("마운트될 때만 실행됩니다.");
}, []);
컴포넌트가 처음 나타날 때만 콘솔에 문구가 나타나고, 그 이후에는 나타나지 않는다.
8. 2. 2 ) 특정값이 업데이트될 때만 실행하고 싶을 때
- 클래스형 컴포넌트
componentDidUpdate(prevProps, prevState) {
if (prevProps.value !== this.props.value) { //props의 value 값이 바뀔 때만 작업 수행
doSomething( );
}
}
- 함수형 컴포넌트 : useEffect 의 두번째 파라미터로 전달되는 배열 안에 검사하고 싶은 값을 넣어줌
useEffect(()=> {
console.log(name);
}, [name] );
8. 2. 3 ) 뒷정리 함수
useEffect는 기본적으로 렌더링되고 난 직후마다 실행되며, 두 번째 파라미터 배열에 무엇을 넣는지에 따라 실행되는 조건이 다르다.
컴포넌트가 언마운트되기 전이나 업데이트되기 직전에 어떤 작업을 수행하고 싶으면 뒷정리 함수(return)를 반환해야 한다.
App. js
import React, { useState } from "react";
import Info from "./Info";
const App = () => {
const [visible, setVisible] = useState(false);
return (
<div>
<button
onClick={() => {
setVisible(!visible); //visible 값을 toogle
}}
>
{visible ? "숨기기" : "보이기"}
</button>
<hr />
{visible && <Info />}
</div>
);
};
export default App;
Info. js - useEffect
useEffect(()=>{
console.log('effect');
console.log(name);
return()=>{
console.log('cleanup');
console.log(name);
};
} /* , [] */ );
렌더링될 때마다 뒷정리 함수가 나타나고 업데이트되기 직전의 값을 보여준다.
( 아래 상태에서 숨기기 버튼을 누르면 'cleanup 정서영' 출력 )
언마운트될 때만 뒷정리 함수를 호출하고 싶으면 useEffect의 두번째 파라미터에 비어있는 배열을 넣어준다.
( 보이기 누르면 effect, 숨기기 누르면 cleanup 출력 )
8. 3 ) useReducer
useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트하고 싶을 때 시용
현재상태, 업데이트를 위해 필요한 정보를 담은 액션 값을 전달받아 새로운 상태를 반환하는 함수. 불변성을 지켜야함
1. 리듀서 함수 만들기 : function reducer( state, action ) { // action. type에 따라 다른 작업 수행 }
2. 메인 함수에서 useReducer사용 : const [ state, dispatch ] = useReducer( reducer, { value: 0 //해당 리듀서의 기본값})
- dispatch: 액션을 발생시키는 함수, 함수안에 파라미터로 액션을 넣어주면 리듀서 함수가 호출됨.
useReducer의 장점: 컴포넌트 업데이트 로직을 컴포넌트 바깥으로 빼낼 수 있다!
8. 3. 1 ) 카운터 구현하기
Counter. js
import React, { useReducer } from "react";
function reducer(state, action) { //1. 리듀서 함수 만들기
switch (action.type) {
case "INCREMENT": //action.type == 'INCREMENT'일때
return { value: state.value + 1 }; //state의 value에 +1
case "DECREMENT":
return { value: state.value - 1 };
default:
return state;
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { value: 0 });
// 현재상태 기본값
// dispatch: 함수안에 액션값을 파라미터로 넣어주면 리듀서 함수 호출
return (
<div>
<p>
현재 카운터 값은 <b>{state.value}</b>입니다.
</p>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button>
{/* dispatch에 파라미터로 액션값을 넣어줌 -> reducer함수에서 action type에 맞게 실행 */}
<button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button>
</div>
);
};
export default Counter;
8. 3. 2 ) 인풋 상태 관리하기
4장에서 여러개의 input을 받아올 때, e. target. name( input태그의 name 속성 )을 이용한 것과 비슷하게 처리할 수 있다.
Info. js
import React, { useReducer } from "react";
function reducer(state, action) {
// 리듀서 함수
return {
...state, // 기존값 복사하고
[action.name]: action.value, // [e.target.name]: e.target.value 와 유사
// input중 name을 key값으로 써서 입력된 값을 state의 해당 key에 해당하는 곳에 업데이트
};
}
const Info = () => {
const [state, dispatch] = useReducer(reducer, {
name: "",
nickname: "", //기본값
});
const { name, nickname } = state; //객체
const onChange = (e) => {
dispatch(e.target); //이벤트 객체가 지니고 있는 e.target값 자체를 액션값으로 사용
//액션값을 받으면 reducer함수 호출
};
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;
8. 4 ) useMemo
함수형 컴포넌트 내부에서 발생하는 연산을 최적화( 특정값이 바뀌었을 때만 연산 )할 수 있다.
const avg = useMemo( ( )=> getAverage( list ), [ list ] );
// list 값이 바뀌었을 때만 getAverage함수를 호출, 바뀌지 않으면 이전에 연산했던 결과( [list] )를 다시 사용
Average. js ( 리스트에 숫자를 추가하면 추가된 숫자들의 평균을 보여주는 컴포넌트 )
import React, { useState, useMemo } from "react";
const getAverage = (numbers) => { //평균을 계산하는 함수
console.log("평균값 계산 중...");
if (numbers.length === 0) return 0;
const sum = numbers.reduce((a, b) => a + b);
return sum / numbers.length;
};
const Average = () => {
const [list, setList] = useState([]);
const [number, setNumber] = useState("");
//list, number (state 2개)
const onChange = (e) => {
setNumber(e.target.value); //input들어오면 입력값을 number에 업데이트
};
const onInsert = (e) => {
//버튼 핸들링 메서드
const nextList = list.concat(parseInt(number)); //number를 추가한 새 배열 만들어서 list 업데이트
setList(nextList);
setNumber(""); //number는 초기화
};
const avg = useMemo(() => getAverage(list), [list]);
// list 값이 바뀌었을 때만 getAverage함수를 호출, 아니면 이전에 연산했던 결과( [list] )를 다시 사용
return (
<div>
<input value={number} onChange={onChange} />
<button onClick={onInsert}>등록</button>
<u1>
{list.map((val, index) => (
<li key={index}>{val}</li> //list의 원소들을 <li>{원소}<li> 형식으로 만들어줌
))}
</u1>
<div>
<b>평균값: </b>
{avg}
</div>
</div>
);
};
export default Average;
8. 5 ) useCallback
렌더링 성능을 최적화해야 하는 상황에서 사용, 만들어놨던 함수를 재사용할 수 있다.
위의 Average. js 에서 작성한 코드는 리렌더링 될때마다 새로 만들어진 onChange, onInsert함수를 사용 => 비효율적이므로 useCallback을 사용해서 최적화한다.
const onChange = useCallback(( ) => { 생성하고 싶은 함수 } , [ 이 값이 바뀌었을 때 함수 생성 ] );
// 빈 배열을 넣으면 컴포넌트가 처음 렌더링될 때만 함수 생성
Average. js - onChange, onInsert
const onChange = useCallback((e) => {
setNumber(e.target.value);
}, []); //컴포넌트가 처음 렌더링될 때만 함수 생성
const onInsert = useCallback(() => {
//버튼 핸들링 메서드
const nextList = list.concat(parseInt(number)); //number를 추가한 새 배열 만들어서 list 업데이트
setList(nextList);
setNumber(""); //number는 초기화
}, [number, list]); //number 또는 list가 바뀌었을 때만 함수생성 (함수 내부에서 상태값에 의존하므로 필수)
8. 6 ) useRef
함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해준다.
useRef를 사용하여 ref를 설정하면 useRef를 통해 만든 객체 안의 current값이 실제 엘리먼트를 가리킨다.
const Average -> const inputEl = useRef( null ) ; //useRef를 사용해서 ref객체인 inputEl 설정
const Average -> const onInsert = useCallback(( ) =>{ ... inputEl. current. focus( ); //포커스 넘겨주기 }, [ number, list ] );
return -> <input value={number} onChange={onChange} ref={ inputEl } /> // inputEl는 input요소를 카리킨다
8. 6. 1 ) 로컬변수 사용하기
로컬변수: 렌더링과 상관없이 바뀔 수 있는 값
클래스형 컴포넌트
import React, { Component } from "react";
// 렌더링과 상관없이 이 컴포넌트만의 로컬변수 사용 가능
class MyComponent extends Component {
id = 1; //로컬변수
setId = (n) => {
this.id = n;//로컬변수에 접근하는 함수
};
printId = () => {
console.log(this.id);
};
render() {
return <div>MyComponent</div>; //자기자신을 리턴
}
}
export default MyComponent;
함수형 컴포넌트 - useRef 사용
import React, { useRef } from "react";
const RefSample = () => {
const id = useRef(1);
//로컬변수의 초기값 설정
const setId = (n) => {
id.current = n; //로컬변수를 수정하는 함수
};
const printId = () => {
console.log(id.current); //로컬변수 조회
};
return <div>refsample</div>;
};
export default RefSample;
useRef로 감싸진 current가 가리키는 값은 React에 의해 기억되기 때문에 직접 변경하기 전까지 해당 컴포넌트가 호출될 때마다 동일하다.
8. 7 ) 커스텀 Hooks 만들기
여러 컴포넌트에서 비슷한 기능을 공유할 경우, 이를 나만의 Hook으로 작성하여 로직을 재사용할 수 있다.
기존에 Info 컴포넌트에서 여러개의 인풋을 관리하기 위해 useReducer로 작성했던 로직을 useInputs라는 Hook으로 따로 분리해 보자.
// 보통 커스텀 Hook은 use라는 키워드로 시작하는 파일을 많이 쓴다.
useInputs. js
import { useReducer } from "react";
function reducer(state, action) {
return {
...state,
[action.name]: action.value,
};
}
export default function useInputs(initialForm) {
//초기값을 파라미터로 받는다.
const [state, dispatch] = useReducer(reducer, initialForm);
//dispatch -> reducer함수 호출
const onChange = (e) => {
dispatch(e.target); //이벤트 객체가 지니고있는 e.target 값 자체를 액션 값으로 사용
};
return [state, onChange];
}
Info. js
import React from "react";
import useInputs from "./useInputs";
/*
function reducer(state, action) {
// 리듀서 함수
return {
...state, // 기존값 복사하고
[action.name]: action.value, // [e.target.name]: e.target.value 와 유사
// input중 name을 key값으로 써서 입력된 값을 state의 해당 key에 해당하는 곳에 업데이트
};
}*/
const Info = () => {
const [state, onChange] = useInputs({
name: "",
nickname: "", //기본값
});
const { name, nickname } = state; //객체
/*
const onChange = (e) => {
dispatch(e.target); //이벤트 객체가 지니고 있는 e.target값 자체를 액션값으로 사용
//액션값을 받으면 reducer함수 호출
}; */
return (
<div>
<div>
<input name="name" value={name} onChange={onChange} />
<input name="nickname" value={nickname} onChange={onChange} />
</div>
...
</div>
);
};
export default Info;
이번 장에서는 다양한 Hooks에 대해 알아보았다.
- 가변적인 상태를 가질 수 있게 해주는 useState
- 리액트가 렌더링될 때마다 특정 작업을 수행하는 useEffect
- useState와 비슷한 기능을 하지만 업데이트를 위한 액션값을 전달받아 새로운 상태를 반환하는 useReducer
- 특정값이 바뀌었을 때만 연산하는 useMemo
- 만들어놨던 함수를 재사용하는 useCallback
- ref를 설정하거나 로컬변수를 만들어주는 useRef
- 커스텀 Hook만들기
리액트에서는 함수형 컴포넌트를 쓰는 것을 권장하지만 기존에 클래스형 컴포넌트로 작성된 파일을 유지, 보수할 때 굳이 함수형 컴포넌트와 Hooks를 사용하는 형태로 전환할 필요는 없다.
▶ useReducer vs useState - 뭐 쓸까? 어떨 때 useReducer 를 쓰고 어떨 때 useState 를 써야 할까요? 일단, 여기에 있어서는 정해진 답은 없습니다. 상황에 따라 불편할때도 있고 편할 때도 있습니다. 예를 들어서 컴포넌트에서 관리하는 값이 딱 하나고, 그 값이 단순한 숫자, 문자열 또는 boolean 값이라면 확실히 useState 로 관리하는게 편할 것입니다. const [value, setValue] = useState(true); 하지만, 만약에 컴포넌트에서 관리하는 값이 여러개가 되어서 상태의 구조가 복잡해진다면 useReducer로 관리하는 것이 편해질 수도 있습니다. 이에 대한 결정은, 앞으로 여러분들이 useState, useReducer 를 자주 사용해보시고 맘에드는 방식을 선택하세요. 저의 경우에는 setter 를 한 함수에서 여러번 사용해야 하는 일이 발생한다면 setUsers(users => users.concat(user)); setInputs({ username: '', email: '' }); 그 때부터 useReducer 를 쓸까? 에 대한 고민을 시작합니다. useReducer 를 썼을때 편해질 것 같으면 useReducer 를 쓰고, 딱히 그럴것같지 않으면 useState 를 유지하면 되지요. |
'리액트 > 리액트를 다루는 기술' 카테고리의 다른 글
10장. 일정관리 웹 애플리케이션 만들기 (0) | 2021.01.21 |
---|---|
9강 컴포넌트 스타일링 (0) | 2021.01.20 |
리액트 7장. 컴포넌트의 라이프사이클 메서드 (0) | 2021.01.07 |
리액트 6장. 컴포넌트 반복 (0) | 2021.01.06 |
리액트 5장. ref: DOM에 이름 달기 (0) | 2021.01.05 |