before we know :
- 함수형 컴포넌트는 그냥 함수다. 함수형 컴포넌트는 단지 jsx를 반환하는 함수이다.
- 컴포넌트가 렌더링 된다는 것은 누군가가 그 함수(컴포넌트)를 호출하여서 실행되는 것을 말한다. 함수가 실행 될 때마다, 내부에 선언되어 있던 표현식(변수,또다른 함수 등)도 매번 다시 선언되어 사용된다.
- 컴포넌트는 자신의 state가 변경되거나, 부모에게서 받는 props가 변경 되었을 때마다, 리렌더링 된다. (하위 컴포넌트에 최적화 설정을 해주지 않으면, 부모에게서 받는 props가 변경되지 않았덛라도, 리렌더링 되는게 기본이다.)
useMemo
메모이제이션된 값을 반환한다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useReducer
useState보다 더 다양한 컴포넌트 상황에 따라 다양한 상태를 다른 값으로 업데이트 해 주고 싶을 때, 사용하는 Hook
리듀서는 현재 상태 그리고 업데이트를 위해 필요한 정보를 담은 액션(action) 값을 전달받아, 새로운 상태를 반환하는 함수이다.
리듀서함수에서 새로운 상태를 만들 때 반드시 불변성을 지켜 주어야 한다.
Redux의 reducer와 React의 useReducer의 차이
리덕스에서 사용하는 액션 객체에는 어떤 액션인지 알려 주는 type필드가 꼭 있어야 하지만,
useReducer에서 사용하는 액션 객체는 반드시 type을 가질 필요는 없다. 심지어 객체가 아니라 문자열이나 숫자여도 상관없다.
import React, { useReducer } from "react"; function reducer(state, action) { // action.type 에 따라 다른 작업 수행 switch (action.type) { case "INCREMENT": return { value: state.value + 1 }; case "DECREMENT": return { value: state.value - 1 }; default: // 아무것도 해당되지 않을 때 기존 상태 반환 return state; } } const Counter = () => { const [state, dispatch] = useReducer(reducer, { value: 0 }); // 첫번째 파라미터 = 리듀서함수 , 두번째 파라미터 = 해당 리듀서의 기본값 return ( <div> <p> 현재 카운터 값은 <b>{state.value}</b> 입니다. </p> <button onClick={() => dispatch({ type: "INCREMENT" })}>+1</button> <button onClick={() => dispatch({ type: "DECREMENT" })}>-1</button> </div> ); }; export default Counter;
인풋 상태 관리하기
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); const onChange = e => { dispatch(e.target); }; return [state, onChange]; } //--------------------------------------------------------------------- import React from 'react'; import useInputs from './useInputs'; const Info = () => { 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;
useMemo, useRef, useCallback
- useMemo를 사용하면 함수형 컴포넌트 내부에서 발생하는 연산을 최적화 할 수 있다.
- useCallback은 useMemo와 상당히 비슷한 함수이다. 주로 렌더링 성능을 최적화 해야 하는 상황에서 사용한다. 이 Hook을 사용하면 만들어 놨던 함수를 재사용 할 수 있다. 컴포넌트가 리렌더링될 때 마다, 새로 만들어진 함수를 사용하게 된다. 컴포넌트의 렌더링이 자주 발생하거나, 렌더링해야 할 컴포넌트의 개수가 많아지면, 이 부분을 최적화 시켜주는게 좋다. useCallback의 첫번째 파라미터에는 생성하고 싶은 함수를 넣고, 두번째 파라미터에는 배열을 넣는다. 배열에는 어떤 값이 바뀌었을 때, 함수를 새로 생성해야 하는지 명시해야 한다. 함수 내부에서 상태 값에 의존해야 할 때, 그 값을 반드시 두 번째 파라미터 안에 포함 시켜 주어야 한다. onChange의 경우 기존 값을 조회하지 않고 바로 이벤트를 불러오기 때문에 매번 불러도 상관이 없지만, onInsert는 기존의 number와 list를 조회해서 nextList를 생성하기 때문에, 배열안에 값을 꼭 넣어 줘야한다.
- useRef 함수형 컴포넌트에서 ref를 쉽게 사용할 수 있도록 해준다. ----> DOM에 접근 해야 할 때 사용.
import React, { useState, useMemo, useRef, useCallback } 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(""); const inputEl = useRef(null); const onChange = useCallback((e) => { setNumber(e.target.value); }, []); // 컴포넌트가 처음 렌더링 될 때만 함수 생성 (두번째 인자에 []) const onInsert = useCallback(() => { const nextList = list.concat(parseInt(number)); setList(nextList); setNumber(""); inputEl.current.focus(); }, [number, list]); // number 혹은 list 가 바뀌었을 때만 함수 생성 (두번째 인자에 [number, list]) const avg = useMemo(() => getAverage(list), [list]); // useMemo를 사용하지 않으면, 렌더링 될 때마다 필요없는 함수를 계속 실행하기 때문에 낭비가 심하다. 그래서, useMemo를 이용하여 원하는 값이 바뀔 때만 렌더링 하게 기능을 부여 할 수 있다. return ( <div> <input value={number} onChange={onChange} ref={inputEl} /> <button onClick={onInsert}>등록</button> <ul> {list.map((value, index) => ( <li key={index}>{value}</li> ))} </ul> <div> <b>평균값:</b> {avg} </div> </div> ); }; export default Average;
useCallback
props로 전달해야 할 함수를 만들 때는, useCallback을 사용하여 함수를 감싸는 것을 습관화 하자.
onClick과 onSubmit
onClick 이벤트는 클릭만 구현 가능해서, onKeyPress나 onKeyUp과 같은 이벤트로 Enter키 사용로직을 따로 짜줘야한다.
하지만,
과 onSubmit과 e.preventDefault() 메소드로 새로고침을 초기화를 방지해주고 사용하면, 클릭과 엔터기능을 모두 구현할 수 있다.