[리액트] 기초 개념 / 내장hook

AnSuebin·2022년 8월 18일
0

[리액트] 개념 정리

목록 보기
10/13

13. 리엑트 내장 훅

  1. useState - 상태값 관리 설명
  2. useEffect - 부수효과 처리 설명
  3. useContext - Context API Consumer 기능 사용 설명
  4. useRef - 자식 요소에 직접 접근 등 설명1 설명2
  5. useMemo - 메모이제이션 기능 설명
  6. useCallback - 함수 메모이제이션 기능에 특화됨 설명
  7. useReducer - 여러 개의 상태값 관리 설명
  8. useImperativeHandle
  9. useLayoutEffect
  10. useDebugValue

1) useRef
렌더링과 상관 없는 데이터를 저장할 때 useRef가 유용하게 사용될 수 있다.

  • 타이머
    • 만약 useState를 사용한다면 불필요한 렌더링이 일어날 수 있다.
export default function App() {
  const timerIdRef = useRef(-1);
  useEffect(() => {
    timerIdRef.current = setTimeout(() => {}, 1000);
  });
  useEffect(() => {
    if (timerIdRef.current >= 0) {
      clearTimeout(timerIdRef.current);
    } 
  });
}
  • 이전 상태값을 기억
    • 이전 age 값을 기억하기 위해서 prevAgeRef를 사용한다.
    • useEffect훅은 렌더링된 후에 호출이 된다. 따라서 prevAge.currentage가 변경되기 이전의 값을 기억하고 있게 되는 것이다.
    • setAge 호출 -> age가 초기값 20에서 28로 변경됨 -> 렌더링 -> 이 때 prevAgeRef.current는 20임 -> 화면에는 age로 28이 출력되고 prevAge로 20이 출력됨 -> prevAgeRef.current에 28을 저장 -> setAge호출 -> age가 19로 변경됨 -> 렌더링 -> 이 때 prevAgeRef.current는 28임 -> 화면에는 age로 19가 출력되고 prevAge로 28이 출력됨 -> … 반복
export default function App() {
  const [age, setAge] = useState(20);
  const prevAgeRef = useRef(20);
  
  useEffect(() => {
    prevAgeRef.current = age;
  }, [age]);
  
  const prevAge = prevAgeRef.current;
  const text = (age === prevAge) ? ('same') : (age > prevAge ? 'older' : 'younger');

  return (
    // ...
    // 버튼을 누르면 setAge 함수를 호출하여 랜덤값으로 age를 변경한다.
  );
}

2) useMemo
메모이제이션 기능이 있어, 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용된다.

  • useMemo 훅의 첫 번째 매개변수로 함수를 입력하면 이 함수가 실행된 결과값을 리액트가 기억한다.
  • useMemo 훅의 두 번째 매개변수인 배열에 있는 값이 하나라도 변경되면, 첫 번째 매개변수로 입력된 함수가 실행된다.
  • 만약 두 번째 매개변수인 배열에 있는 값이 변경되지 않으면, 이전에 실행해서 기억하고 있던 결과값을 재활용한다.
  • useEffect 훅과 마찬가지로 이 두 번째 매개변수인 배열은 의존성 배열이다.
  • 위의 코드에서는 v1이나 v2가 변경되면 runExpensiveJob함수가 실행되지만 v3가 변경되면 runExpensiveJob함수가 실행되지 않는다.
export default function App() {
  const [v1, setV1] = useState(0);
  const [v2, setV2] = useState(0);
  const [v3, setV3] = useState(0);

  const value = useMemo(() => {
    runExpensiveJob(v1, v2); // 계산량이 많은 함수 호출
  }, [v1, v2]);

  return (
    //...
  );
}

3) useCallback
useMemo와 마찬가지로 메모이제이션 기능이 있으며, 함수 메모이제이션에 특화된 훅이다.

  • onSave={() => saveToServer(name)} : 자식 컴포넌트에 함수를 입력해서 속성값으로 전달할 때는 App 컴포넌트가 렌더링될 때마다 새로운 함수가 생성되어 입력이 된다. 이러면 App 컴포넌트가 렌더링될 때마다 매번 onSave 속성값이 변경되기 때문에, 자식 컴포넌트인 UserEdit 컴포넌트 입장에서는 불필요하게 새로운 속성값으로 입력받고 불필요하게 다시 렌더링된다.
  • React.memo를 사용하더라도 이러한 문제가 발생하는데 useCallback 훅을 사용하면 이 문제를 해결할 수 있다.
export default function App() {
  const [name, setName] = useState('');
  
  return (
    <div>
      <p>{`name is ${name}`}</p>
      <UserEdit
        onSave={() => saveToServer(name)}
        setName={setName}
      />
    </div>
  );
}

const UserEdit = React.memo(function ({ onSave, setName }) {
  //...
});
export default function App() {
  const [name, setName] = useState('');
  const onSave = useCallback(() => {
    saveToServer(name);
  }, [name]);
  
  return (
    <div>
      <p>{`name is ${name}`}</p>
      <UserEdit
        onSave={onSave}
        setName={setName}
      />
    </div>
  );
}

const UserEdit = React.memo(function ({ onSave, setName }) {
  //...
});
  • 위의 코드에서는 name이 변경될 때만 새로운 함수가 생성된다.

4) useReducer
useState와 비슷하지만 여러 개의 상태값 관리할 때 사용하면 좋은 훅

  • useReducer는 useState와 마찬가지로 배열로 반환이 된다.
  • 배열의 첫 번째 원소 : 상태값
  • 배열의 두 번째 원소 : 상태값을 변경할 수 있는 dispatch 함수
  • 첫 번째 매개변수 : reducer 함수
  • 두 번째 매개변수 : 초기 상태값
export default function App() {
  const [state, dispatch] = useReducer(reducer, INITIAL_STATE);

  return (
    <div>
      <p>{`name is ${state.name}`}</p>
      <p>{`age is ${state.age}`}</p>
      <input
        type='text'
        value={state.name}
        onChange={e => dispatch({ type: 'setName', name: e.currentTarget.value})}
      />
      <input
        type='number'
        value={state.age}
        onChange={e => dispatch({ type: 'setAge', age: e.currentTarget.value})}
      />
    </div>
  );
}
function reducer(state, action) {
  switch(action.type) {
    case 'setName':
      return { ...state, name: action.name };
    case 'setAge':
      return { ...state, age: action.age };
    default:
      return state;
  }
}
  • action을 보고 상태값을 어떻게 변경할지 판단한다.

5) useReducer와 Context ApI
보통 상위 컴포넌트에서 다수의 상태값을 관리한다. 이 때 자식 컴포넌트에서 발생한 이벤트에서 상위 컴포넌트의 상태값을 변경해야 하는 경우가 많다. 이를 위해서 상위 컴포넌트에서 트리의 깊은 곳까지 이벤트 처리 함수를 전달하기도 하는데 이는 비효율적이다.
useReducer 훅과 Context API를 같이 이용하면 쉽게 전달할 수 있다.

  • createContext를 이용해서 ProfileDispatch라는 Context를 만들었다.
  • App 컴포넌트에서 Provider를 이용해서 value로 useReducer의 dispatch 함수를 내려준다.
  • 그러면 필요한 컴포넌트에서 useContext를 이용해서 dispatch 함수를 사용할 수 있다.
export const ProfileDispatch = React.createContext(null);

export default function App() {
  const [state, dispatch] = useReducer(reducer, INITIAL_STATE);

  return (
    <div>
      <p>{`name is ${state.name}`}</p>
      <p>{`age is ${state.age}`}</p>
      
      <ProfileDispatch.Provider value={dispatch}>
        <SomeComponent />
      </ProfileDispatch.Provider>
    </div>
  );
}

참고
https://jess2.xyz/react/react-tip-0/#2-create-react-app-cra-%EC%9C%BC%EB%A1%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0

profile
고객에게 명료한 의미를 전달하고, 명료한 코드를 통해 생산성 향상에 기여하고자 노력합니다.

0개의 댓글