TECH

React Hooks

ssund 2023. 3. 8. 10:54

useState

컴포넌트에서 동적인 값을 상태(state)라고 합니다. useState를 사용해서 컴포넌트 내부에서 상태관리를 할 수 있습니다.

배열 비구조화 할당문법을 사용한 형태입니다.
첫번째 원소는 현재상태, 두번째 원소는 setter 입니다.

setter를 사용하면 데이터도 변경할 수있고, 컴포넌트도 리렌더링 할 수있습니다.

const [number, setNumber] = useState(0)


setState()에 callback 함수

setState에 콜백함수를 넣으면

콜백함수의 인자값으로 이전state값을 가져올 수 있다.

return을 하면 새로운 state를 지정할 수 있다. 

setState((prev) => {
	return !prev
})

 

useState 초기값으로 callback 함수

useState 초기값으로 callback함수를 선언하면
첫 렌더링시 한번만 실행해서 초기값을 만들고 그 이후에는 실행하지 않는다.

const [count, setCount] = useState(() => { return 10; })


불변성

불변성을 지켜주어야만 리액트에서 컴포넌트 상태가 업데이트 됐음을 감지하고 리렌더링을 진행합니다.


객체

객체 업데이트 시 spread연산자을 사용하여 새로운 객체를 return 해서 사용합니다.

const [inputs, setInputs] = useState({
  name: '',
  nickname: ''
})

// X
inputs[name] = '이름'

// O
// spread 연산자을 사용해 새로운 객체 return
setInputs({
  ...inputs,
  [name]: '이름'
})


배열

배열 항목 추가 시 spread 연산자와 concat을 이용해 새로운 배열을 만들어 사용합니다.

// spread 연산자
const [users, setUsers] = useState([
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com'
    },
]);

setUsers([...users, {
      id: nextId.current,
      username,
      email
}]);

// concat
const [users, setUsers] = useState([
    {
      id: 1,
      username: 'velopert',
      email: 'public.velopert@gmail.com'
    },
]);

setUsers(users.concat({
      id: nextId.current,
      username,
      email
}));


배열 항목 삭제 시 filter 함수를 사용합니다.

filter 함수는 특정 조건이 맞는 원소들만 추출해서 새로운배열을 만들어 줍니다.

const onRemove() {
  // user.id가 id인 것만 제외하고 새로운 배열 return
    setUser(users.filter(user => user.id !== id));
}

 

배열 항목 수정 시 map을 이용합니다.

const onToggle() {
    setUser(users.map(user => 
    user.id === id ? {...user, active: !user.active} : user)
  );
}

 

useRef

특정 DOM을 선택해야하는 상황에서 사용한다.

  • 특정 element의 크기를 가져오기
  • 스크롤바 위치를 가져와서 설정
  • 포커스 설정
  • 외부라이브러리 사용시 특정 DOM에 적용할 때
const nameInput = useRef();

const onReset = () => {
  nameInput.current.focus();
}

return (
    <div>
      <input ref={nameInput} />
  </div>
)

Ref.current 값은 우리가 select 하고싶은 DOM을 가르킨다.

 

배열 렌더링

배열 렌더링에서 key가 중요한 이유

const array = ['a', 'b', 'c', 'd'];

array.map(item => <div>{item}</div>);

b와 c사이에 z를 삽입하게 된다면
기존의 c 가 z 로바뀌고, d 는 c 로 바뀌고, 맨 마지막에 d 가 새로 삽입됩니다.

const array = [
  {
    id: 0,
    text: 'a'
  },
  {
    id: 1,
    text: 'b'
  },
  {
    id: 2,
    text: 'c'
  },
  {
    id: 3,
    text: 'd'
  }
];

array.map(item => <div key={item.id}>{item}</div>);

배열이 업데이트 될 때 key 가 없을 때처럼 비효율적으로 업데이트 하는것이 아니라

기존의 값은 그대로 두고 원하는 곳에 삽입하거나, 삭제할 수있습니다.

 

useEffect

마운트(처음 나타날 때), 언마운트(사라질 때), 업데이트 되는 시점에 특정작업을 처리할 수있게 해줍니다.

첫번째 파라미터는 함수, 두번째 파라미터는 의존값이 들어있는 배열(deps)를 넣어줍니다.

useEffect(실행함수, [deps])

마운트 될 때 한번만 실행 (초기 한번만 실행)

두번째 파라미터에 빈배열을 넣어주면 됩니다.

useEffect(실행함수, [])

주로 사용하는 경우는

  • Props로 받은 값을 컴포넌트의 로컬 상태로 설정
  • 외부 api 호출
  • 라이브러리 사용
  • setInterval 을 통한 반복작업 혹은 setTimeout 을 통한 작업 예약

언마운트 될 때 (사라질 때 실행)

useEffect(return () => console.log('destoryed'), [])

주로 사용하는 경우는

  • setInterval, setTimeout clear
  • 라이브러리 인스턴스 제거

값이 업데이트 될 때

Dep 에 특정값을 넣으면 해당값이 업데이트 되는 경우 실행을 시켜줍니다.

useEffect(
    실행함수
    return () => {
        사라지는때 실행시킬 실행함수
    }
, [값])

여러개의 값을 추적할 수도 있습니다.

useEffect(
    실행함수
    return () => {
        사라지는때 실행시킬 실행함수
    }
, [값1, 값2, 값3])

deps 파라미터 생략

deps 파라미터를 생략하면 컴포넌트가 리렌더링 될 때 마다 호출이 됩니다.

useEffect(실행함수)

 

useMemo

성능최적화를 위해 사용하는 hook

초기 렌더링시 계산된 값을 메모리에 저장해서 컴포넌트가 리렌더링 되더라도 메모리에서 값을 가져와 사용합니다.

useMemo(() => {
  return value;
}, [deps])
function countActiveUsers(users) {
    return users.filter(user => user.active).length;
}

const count = countActiveUsers(users);

return (
  <input type="text"/>
  <div>활성사용자 수 : {count}</div>
)

위에 코드의 문제점은
사용자 상태가 변경되었을때만 countActiveUsers가 호출되는 것이 아니라
input이 변경될 때 컴포넌트가 리렌더링되면서 countActiveUsers을 호출하고 있다.

이런경우 useMemo를 사용해서 성능 최적화를 할 수있습니다.

useMemo 두번째 파라미터에 넣어준 배열값이 변경되면 첫번째 함수를 실행해 값을 연산하고
만약 값이 변경되지 않으면 이전에 연산한 값을 재사용 합니다.

function countActiveUsers(users) {
    return users.filter(user => user.active).length;
}

const count = useMemo(() => countActiveUsers(users), [users]);

return (
  <input type="text"/>
  <div>활성사용자 수 : {count}</div>
)

 

useCallback

useMemo와 비슷한 hook입니다.

useMemo는 특정 결과값을 재사용 할 때 사용,
useCallback은 특정함수를 새로만들지 않고 재사용 할 때 사용

함수 안에 상태나 props를 바라보고 있다면, deps 배열에 추가해줘야합니다.

useCallback(기존 함수, [deps])
 const onToggle = useCallback(
    id => {
      setUsers(
        users.map(user =>
          user.id === id ? { ...user, active: !user.active } : user
        )
      );
    },
    [users] //users가 변경될 때 마다 함수를 새로 만들어준다.
);

 

React.memo

컴포넌트의 props가 바뀌지 않는다면 리렌더링을 방지해서 성능최적화를 해줄수있습니다.

이전에 렌더링을 해놓은 컴포넌트를 메모리 어딘가에 저장을 해두고 새로렌더링하는 대신 꺼내서 사용합니다.

리렌더링 방지하고 싶은 컴포넌스 함수를 memo함수로 감싸주면 됩니다.

function Hello({name}) {
  return <div>안녕하세요 {name}</div>
}

export default memo(Hello);

자주 사용되는 경우

  • 컴포넌트가 같은 props로 자주 렌더링될 때
  • 컴포넌트가 렌더링이 될 때마다 복잡한 로직을 처리해야하는 경우

memo를 사용한 컴포넌트에서 props로 object, 함수를 받는경우

//App.js
function App() {
  const name = {
    lastname: '홍',
    firstname: '길동'
  }

  return (
      <Hello name={name} />
  )
}
//Hello.js
function Hello({name}) {
  return <div>안녕하세요 {name.lastname} {name.firstname}</div>
}

prop로 내리는 값이 object인 경우 props의 값이 변경되지 않았는데도 리렌더링이 된다.

이유는? object인 경우 원시타입과 같이 변수안에 그대로 저장되는게 아니라
object가 저장되어있는 메모리 주소가 저장이 되기때문에

App.js 가 리렌더링 될 때 마다 새로운 name object를 만들어주게 되고 변수에 새로운 메모리 주소가 저장이 된다.

함수도 Object이므로 동일하게 작동한다.

useMemo를 사용하면 해결이 됩니다.

//App.js
function App() {
  const name = useMemo(() => {
    return {
      lastname: '홍',
      firstname: '길동'
      }
  }, []);

  return (
      <Hello name={name} />
  )
}

 

useReducer

상태 업데이트 로직은 컴포넌트 내부에서 useState를 사용하여 설정했습니다.

useReducer를 이용하면 컴포넌트 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있습니다. 다른파일에 작성 후 불러와서 사용할 수도 있습니다.

 

action을 dispatch -> reducer -> state 변경

reducer - 우리의 state를 업데이트 해주는 역할

action - dispatch에 들어갈 내용

Dispatch - state를 변경하고 싶을 때 reducer에게 하는 요구

const reducer = (state, action) => {
  switch(action.type) {
    case 'deposit': 
      return state + action.payload;
    default: 
      return state;
  }
};

function App() {
  const [number, setNumber] = useState(0);
  const [money, dispatch] = useReducer(reducer, 초기값);

  return (
    <>
        <input 
        type="number" 
        value={number} 
        onChange={(e) => setNumber(e.target.value)}>
        </input>

      <button type="button" onClick={() => {
          dispatch({ type: 'deposit', payload: number})
      }}>예금</button>
    </>
  ) 
}

코드를 더 깔끔하게 사용하고 싶다면 action타입을 상수로 사용해줍니다.

const ACTION_TYPES = {
  deposit: 'deposit',
  withdraw: 'withdraw'
}

const reducer = (state, action) => {
  switch(action.type) {
    case ACTION_TYPES.deposit: 
      return state + action.payload;
    default: 
      return state;
  }
};

function App() {
  const [number, setNumber] = useState(0);
  const [money, dispatch] = useReducer(reducer, 초기값);

  return (
    <>
        <input 
        type="number" 
        value={number} 
        onChange={(e) => setNumber(e.target.value)}>
        </input>

      <button type="button" onClick={() => {
          dispatch({ type: ACTION_TYPES.deposit, payload: number})
      }}>예금</button>
    </>
  ) 
}

 

useContext

useContext를 사용하면 props로 상위컴포넌트부터 하위로 일일이 전달해주지 않아도 됩니다.

하위컴포넌트에서 사용하고 싶은 값을 사용할 수있다.

useContext는 context공유한 값을 쉽게 가져올수 있도록 하는 hook

초기값은 상위컴포넌트에서 provider로 value를 넘겨주지 않을때 받을 수있는 값

//context/ThemeContext.js
import { createContext } from 'react';

export const ThemeContext = createContext(null); //초기값
//App.js
import { ThemeContext } from './context/ThemeContext'; 

function App() {
  const [isDark, setIsDark] = useState(false);

  return (
    // value에는 전달하고 싶은 값 넣어주면 된다.
    <ThemeContext.provider value={{isDark, setIsDark}}>  
        <Page />
    </ThemeContext.provider>
  );
}
//Page.js
function Page() {
  return (
    // 기존에 하위컴포넌트에 내려준 props는 없어도 된다.
    <>
        <Header />
        <Content />
        <Footer />
    </>
  )
}
//Header.js
function Header() {
  const {isDark} = useContext(ThemeContext);

  return (
      <header style={{
        backgroundColor: isDark ? 'black' : 'white',
      color: isDark ? 'white' : 'black'
    }}>
        헤더 입니다.
    </header>
  )
}


출처

https://react.vlpt.us/basic/01-concept.html

https://velopert.com/3236

https://ko.reactjs.org/docs/introducing-jsx.html