개발일지

1회차 발표 useState, useReducer 본문

리액트 공식문서 스터디

1회차 발표 useState, useReducer

박수미/ 2024. 5. 24. 21:15

• useState

컴포넌트에서 state변수를 추가할 수 있게 해주는 React 훅입니다.

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(28);
  const [name, setName] = useState('Taylor');
  const [something, setSomething] = useState(initialState)
  // ...

배열 구조 분해를 사용하여 [something, setSomething]과 같은 state 변수의 이름을 지정해주어야 합니다!

 

 

매개변수

• initialState: 초기에 state를 설정할 값입니다.

 

반환값

useState는 두개의 값을 반환합니다.

1. 현재 state입니다. 첫 번째 렌더링 중에는 전달한 initialState와 같습니다

2. state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 set 함수

 

사용시 주의사항

useState는 훅이므로 컴포넌트의 최상위 레벨이나 직접 만든 훅(Custom Hook)에서만 호출할 수 있습니다. 반복문이나 조건문 안에서는 호출할 수 없습니다. 

 

set 함수

useState가 반환하는 set 함수를 사용하면 state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있습니다.

const [name, setName] = useState('Edward');

function handleClick() {
  setName('Taylor');
  // ...

• nextState: state가 될 값입니다.

 

사용시 주의사항

set 함수는 다음 렌더링에 대한 state 변수만 업데이트합니다. set 함수를 호출한 후에도 state 변수에는 여전히 호출 전 화면에 있던 이전 값이 담겨 있습니다. 

 

사용법

1. 컴포넌트에 state 추가하기

import { useState } from 'react';

function MyComponent() {
  const [age, setAge] = useState(42);
  const [name, setName] = useState('Taylor');
  // ...

이 state 변수의 현재 state로, 처음에 제공한 초기 state로 설정됩니다.

 

상호작용에 반응하여 다른 값으로 변경할 수 있는 set 함수입니다.

function handleClick() {
  setName('Robin');
}

 

2. 이전 state를 기반으로 state 업데이트하기

age42라고 가정합니다. 이 핸들러는 setAge(age + 1)를 세 번 호출합니다:

function handleClick() {
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
  setAge(age + 1); // setAge(42 + 1)
}

그러나 클릭해보면 age45가 아니라 43이 됩니다! 이는 set 함수를 호출해도 이미 실행 중인 코드에서 age state 변수가 업데이트되지 않기 때문입니다. 따라서 각 setAge(age + 1) 호출은 setAge(43)이 됩니다.

 

이 문제를 해결하려면 다음 state 대신 setAge업데이터 함수를 전달할 수 있습니다:

function handleClick() {
  setAge(a => a + 1); // setAge(42 => 43)
  setAge(a => a + 1); // setAge(43 => 44)
  setAge(a => a + 1); // setAge(44 => 45)
}

여기서 a => a + 1은 업데이터 함수입니다. 이 함수는 대기 중인 state를 가져와서 다음 state를 계산합니다.

마지막 함수가 실행이 끝나면 대기 중인 다른 업데이트가 없으므로, React는 결국 45를 현재 state로 저장합니다.

 

3. 객체 및 배열 state 업데이트 

state에는 객체와 배열도 넣을 수 있습니다. React에서 state는 읽기 전용으로 간주되므로 기존 객체를 변이하지 않고, 교체를 해야 합니다. 예를 들어, state에 form 객체가 있는 경우 변이하지 마세요:

form.firstName = 'Taylor';

 

새로운 객체를 생성하여 전체 객체를 교체하세요:

setForm({
  ...form,
  firstName: 'Taylor'
});

 

4. 초기 state 다시 생성하지 않기 

React는 초기 state를 한 번 저장하고 다음 렌더링부터는 이를 무시합니다.

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos());
  // ...

createInitialTodos()의 결과는 초기 렌더링에만 사용되지만, 여전히 모든 렌더링에서 이 함수를 호출하게 됩니다.

 

이 문제를 해결하려면, 대신 이를 useState초기화 함수로 전달하세요:

function TodoList() {
  const [todos, setTodos] = useState(createInitialTodos);
  // ...

함수를 호출한 결과인 createInitialTodos()가 아니라 함수 자체인 createInitialTodos를 전달하고 있다는 것에 주목하세요. 함수를 useState에 전달하면 React는 초기화 중에만 함수를 호출합니다.

*컴포넌트가 리렌더링될 때(input에 타이핑할 때 등)에는 실행되지 않습니다.

 

5. key로 state 재설정하기 

이해가 안됩니다.. ....🥹

 

• useReducer

컴포넌트의 최상위 레벨에서 useReducer를 호출하여 reducer를 통해 state를 관리하세요.

import { useReducer } from 'react';

function reducer(state, action) {
  // ...
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });
  // ...


매개변수

const [state, dispatch] = useReducer(reducer, initialArg, init);

 

reducer: state가 업데이트되는 방식을 지정하는 reducer 함수입니다. state와 액션을 인자로 받아야 하고, 다음 state를 반환해야 합니다. state와 액션은 어떤 유형이든 가능합니다.

 

 initialArg: 초기 state가 계산되는 값입니다. 모든 유형의 값일 수 있습니다. 이 값에서 초기 state를 계산하는 방법은 다음 init 인자에 따라 달라집니다.

 

 init: 초기 state 계산 방법을 지정하는 초기화 함수입니다. 이것을 지정하지 않으면 초기 state는 initialArg로 설정됩니다.

*여기서 initialArg는 초기 값을 계산하기 위한 입력값이고, init은 그 초기 값을 기반으로 초기 상태를 계산하는 함수입니다.

 

반환값

1. 현재 state. 첫 번째 렌더링 중에는 init(initialArg) 또는 (init이 없는 경우) initialArg로 설정됩니다.

2. state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 dispatch function


dispatch function?

React는 상태가 변경될 때 컴포넌트를 다시 렌더링합니다. 그러나, 만약 새로운 상태 값이 이전 상태 값과 동일하다면, 다시 렌더링할 필요가 없습니다. 이로 인해 불필요한 렌더링을 방지하고 성능을 최적화할 수 있습니다.

const [state, dispatch] = useReducer(reducer, { age: 42 });

function handleClick() {
  dispatch({ type: 'incremented_age' });
  // ...

 

매개변수

 action: 사용자가 수행한 작업입니다. 어떤 데이터 유형이든 올 수 있습니다. 관용적으로 액션은 보통 이를 식별하는 type 속성이 있는 객체이며, 선택적으로 추가 정보가 있는 다른 속성을 포함할 수 있습니다.

 

반환값

dispatch 함수에는 반환값이 없습니다.

 

주의사항

dispatch함수는 다음 렌더링에 대한 state 변수만 업데이트합니다. 만약 dispatch함수를 호출한 후 state 변수를 읽으면, 호출 전 화면에 있던 이전 값이 계속 표시됩니다. 

React는 상태 비교에 JavaScript의 Object.is 메서드를 사용합니다. 이 메서드는 두 값이 동일한지 비교하는데, 이는 다음과 같은 경우에 두 값이 동일하다고 판단됩니다. 

* Object.is

두 값이 모두 undefined, null, true, false, +0, -0, NaN, 문자열 또는 같은 객체를 참조하는 경우.


 

사용시 주의사항

useReducer 는 훅이므로 컴포넌트의 최상위 레벨 또는 자체 훅에서만 호출할 수 있습니다. 반복문이나 조건문 내부에서는 호출할 수 없습니다.

 

사용법

1. 컴포넌트에 reducer 추가하기

import { useReducer } from 'react';

function reducer(state, action) {
  // ...
}

function MyComponent() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });
  // ...

이 state 변수의 현재 state(state). 처음에 제공한 초기 state({ age: 42 })로 설정됨.

상호작용에 반응하여 이를 변경할 수 있는 dispatch(dispatch) 함수.

 

화면에 표시되는 내용을 업데이트하려면 사용자가 수행한 작업을 나타내는 객체, 즉,액션을 사용하여 dispatch를 호출합니다:

function handleClick() {
  dispatch({ type: 'incremented_age' });
}

React는 현재 state와 액션을 reducer 함수(reducer)에 전달합니다. Reducer는 다음 state를 계산하고 반환합니다. React는 다음 state를 저장하고, 컴포넌트를 렌더링하고, UI를 업데이트합니다.

 

2. reducer 함수 작성하기

Reducer 함수는 다음과 같이 선언됩니다:

function reducer(state, action) {
  // ...
}

 

그런 다음 다음 state를 계산하고 반환할 코드를 입력해야 합니다. 관례상 switch 문으로 작성하는 것이 일반적입니다. switch의 각 case 에 대해 다음 state를 계산하고 반환해야 합니다.

function reducer(state, action) {
  switch (action.type) {
    case 'incremented_age': {
      return {
        name: state.name,
        age: state.age + 1
      };
    }
    case 'changed_name': {
      return {
        name: action.nextName,
        age: state.age
      };
    }
  }
  throw Error('Unknown action: ' + action.type);
}

 

액션은 어떤 형태든 가질 수 있습니다. 관례상 액션을 식별하는 type 프로퍼티가 있는 객체를 전달하는 것이 일반적입니다. 여기에는 reducer가 다음 state를 계산하는 데 필요한 최소한의 필수 정보가 포함되어야 합니다.

function Form() {
  const [state, dispatch] = useReducer(reducer, { name: 'Taylor', age: 42 });
  
  function handleButtonClick() {
    dispatch({ type: 'incremented_age' });
  }

  function handleInputChange(e) {
    dispatch({
      type: 'changed_name',
      nextName: e.target.value
    });
  }
  // ...

액션 유형 이름은 컴포넌트에 로컬로 지정됩니다.  각 액션은 아무리 많은 데이터를 변경하게 되더라도 오직 하나의 상호작용만을 기술합니다. state의 모양은 임의적이지만 일반적으로 객체나 배열이 될 것입니다.

 

2. 초기 state 재생성 방지하기

React는 초기 state를 한 번 저장하고 다음 렌더링에서 이를 무시합니다.

function createInitialState(username) {
  // ...
}

function TodoList({ username }) {
  const [state, dispatch] = useReducer(reducer, createInitialState(username));
  // ...

createInitialState(username)의 결과는 초기 렌더링에만 사용되지만, 이후의 모든 렌더링에서도 여전히 이 함수를 호출하게 됩니다. 

 

이 문제를 해결하려면 useReducer 세번 째 인수에 초기화 함수를 전달할 수 있습니다.

function createInitialState(username) {
  // ...
}

function TodoList({ username }) {
  const [state, dispatch] = useReducer(reducer, username, createInitialState);
  // ...

함수를 호출한 결과인 createInitialState()가 아니라 함수 자체인 createInitialState를 전달하고 있다는 점에 유의하세요. 이렇게 하면 초기화 후에는 초기 state가 다시 생성되지 않습니다.

'리액트 공식문서 스터디' 카테고리의 다른 글

useRef  (0) 2024.05.30
useContext  (0) 2024.05.28
useReducer  (0) 2024.05.22
useState  (1) 2024.05.17
props  (0) 2024.05.13