React_memo_useReducer, Immer, useContext

Lina Hongbi Ko·2023년 11월 6일
0

React_memo

목록 보기
2/5

리액트 강의를 듣다 사용한 훅에 대한 정보를 정리하는 글

useReducer

useState처럼 state를 생성 & 관리할 수 있도록 한다. state가 많고 복잡할때 사용하면 도움이 된다.

  • 기본 형태
const [state, dispatch] = useReducer(reducer, initialArg, init?)
  • 시작하기
import React, { useReducer } from 'react';
  • 기본적인 사용 형태
<script>

import React, { useReducer } from "react";

const reducer = (state, action) => {};

const initialState = {};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h2>useReducer 사용해보기</h2>
    </div>
  );
}

export default App;

</script>

useReducer을 사용해 state를 업데이트하려면 dispatch함수를 사용해 업데이트 할 수 있다. dispatch는 어떤 state를 어떻게 바꿀지 지시하고, 바꾸는 것을 실행해주는 것은 reducer이다.

reducer 함수 : state와 action을 인자로 받아와 dispatch가 보내온 내용에 따라 state를 변경시켜주는 함수

initialState : useState보다 많은 것들을 담을 수 있는 state 보관함

  • state에 접근하기
<script>

import React, { useReducer } from "react";

const reducer = (state, action) => {};

const initialState = {
	name: '철수',
    age: 30
};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h2>useReducer 사용해보기</h2>
      <p>{state.name}</p>
      <button onClick={onClick}></button>
    </div>
  );
}

export default App;

</script>

initialState에 위의 코드처럼 state을 저장하면 useReducer()을 사용했을때 선언한 배열 안의 변수를 이용해 state.name, state.age 등과 같은 형태로 state에 접근할 수 있다.

  • reducer 함수 만들기
<script>

import React, { useReducer } from "react";

const reducer = (state, action) => {
	switch(action.type) {
    	case "CAHNGE_NAME" :
        	return {
            	...state,
                name: action.name
            };
        default:
        	return state;
    }
};

const initialState = {
	name: '철수',
    age: 30
};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <h2>useReducer 사용해보기</h2>
      <p>{state.name}</p>
      <button onClick={onClick}></button>
    </div>
  );
}

export default App;

</script>

reducer 함수를 switch문을 이용해 분기되어 일을 처리할 수 있도록 만들어준다.
그리고 이 reducer 함수는 직접 호출되는 일은 없고, dispatch에서 조건을 전달해주면 처리하는 역할을 담당한다. 즉, dispatch를 사용해 reducer 함수가 일을 처리하도록 한다.

  • dispatch 함수를 만들고, reducer에게 요청하기
<script>

import React, { useReducer } from "react";

const reducer = (state, action) => {
	switch(action.type) {
    	case "CAHNGE_NAME" :
        	return {
            	...state,
                name: action.name
            };
        default:
        	return state;
    }
};

const initialState = {
	name: '철수',
    age: 30
};

function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const onClick = () => {
  	dispatch({
    	type: "CHANGE_NAME",
        name: "지민"
    });
  }
  return (
    <div>
      <h2>useReducer 사용해보기</h2>
      <p>{state.name}</p>
      <button onClick={onClick}></button>
    </div>
  );
}

export default App;

</script>

onClick 함수가 실행될떄, dispatch 함수에 적은 내용이 reducer에게 전달된다. type값에 따라 처리되며 불변성을 지켜야 한다. return문의 srpead 연산자를 확인할 수 있다.

  • 다른예제 - 카운터
<script>
import React, { useReducer } from 'react';

const initialState = {count: 0};

function reducer(state, action) {
	switch (action.type) {
    	case 'increment' :
        	return {count: state.count + 1};
        case 'decrement' :
        	return {count: state.count - 1};
        default:
        	throw new Error();
    }
}

function Counter() {
	const [state, dispatch] = useReducer(reducer, initialState);
    return(
    	<>
        	Count: {state.count}
            <button onClick={() => dispatch({type: 'decrement'})}>-</button>
            <button onClick={()=> dispatch({type: 'increment'})}>+</button>
        </>
    );
}
</script>

Immer

상태를 관리하는 또 하나의 방법이다. useState를 쓰든 useReducer를 쓰든 여전히 객체의 불변성을 유지해야하는 법칙은 꼭 지켜야 한다. spread 연산자를 써야하는 것이 귀찮기 때문에 이런 것들을 쉽게 사용하고 관리하고자 만든 것이 'Immer'라이브러리이다.

Create the next immutable state tree by simply modifying the current tree

이 말은 다음 불변하는 상태트리를 현재 트리를 간단히 수정하면서 만든다는 말인데, 설명하자면 Immer 라이브러리를 사용하면, 상태를 바로 반환하는게 아니라 useState()가 내부적으로 사용되어서 자동으로 새로운 객체를 만들어 상태를 업데이트 한다. 따라서 우리는 변경하고 싶은 내용만 적으면 된다.

사용방법은,

  • Immer 설치
yarn add immer use-immer
  • Immer 사용

설치했으면, useImmer API를 불러와서 사용해준다. 사용방법은 useState()과 비슷하다. 하지만 불변성을 유지해주는 규칙을 지키지 않고 변경하고 싶은 내용만 적으면 된다.

// useImmer 사용틀
const [variable, updateVariable] = useImmer(initialState)

<scirpt>
// AppMentorsImmer.jsx

import React from 'react';
import { useImmer } from 'use-immer';

export default function AppMentorsImmer() {
	const [person, updatePerson] = useImmer(initialPerson);
	const handleUpdate = () => {
		const prev = prompt(`누구의 이름을 바꾸고 싶은가요?`);
		const current = prompt(`이름을 무엇으로 바꾸고 싶은가요?`);
		updatePerson((person) => {
			const mentor = person.mentors.find((mentor) => mentor.name === prev);
			mentor.name = current;
		});
	};
	const handleAdd = () => {
		const name = prompt(`누구의 이름을 추가하고 싶은가요?`);
		const title = prompt(`직함은 무엇인가요?`);
		updatePerson((person) => (person.mentors.push({name, title})));
	};
	const handleDelete = () => {
		const name = prompt(`누구의 이름을 삭제하고 싶은가요?`);
		updatePerson((person) => {
			const index = person.mentors.findIndex((mentor) => mentor.name === name);
			person.mentors.splice(index, 1);
		});
	}
	return(
		<div>
			<h1>{person.name}는 {person.title}</h1>
      <p>{person.name}의 멘토는:</p>
      <ul>
        {
          person.mentors.map((mentor, index) => (
            <li key={index}>
              {mentor.name} ({mentor.title})
            </li>
          ))
        }
      </ul>
      <button onClick={handleUpdate}>멘토의 이름을 바꾸기</button>
      <button onClick={handleAdd}>멘토 추가</button>
      <button onClick={handleDelete}>멘토 삭제</button>
		</div>
	);
}

const initialPerson = {
  name: '엘리',
  title: '개발자',
  mentors: [
    {
      name: '밥',
      title: '시니어개발자'
    },
    {
      name: '제임스',
      title: '시니어개발자'
    }
  ],
}
</script>

useContext

  • Context란?
    Context는 컴포넌트 사이에서 어떤값을 공유할수 있게 해 준다. props가 아닌 또 다른 방식으로 컴포넌트간에 값을 전달하는 방법
function App() {
  return <GrandParent value="Hello World!" />;
}

function GrandParent({ value }) {
  return <Parent value={value} />;
}

function Parent({ value }) {
  return <Child value={value} />;
}

function Child({ value }) {
  return <GrandChild value={value} />;
}

function GrandChild({ value }) {
  return <Message value={value} />;
}

function Message({ value }) {
  return <div>Received: {value}</div>;
}

props로 데이터를 전달하면, 위의 코드처럼 props drilling 문제가 발생한다.

또 다른 예시로, 여러 종류의 자식 컴포넌트가 특정한 값에 의존한다고 했을때 하나의 props만 받아오는 것이 아니라 더 많이 있다면 가독성이 떨어질 수 있다.

function App() {
  return (
    <AwesomeComponent value="Hello World" />
  )
}

function AwesomeComponent({ value }) {
  return (
    <div>
      <FirstComponent value={value} />
      <SecondComponent value={value} />
      <ThirdComponent value={value} />
    </div>
  )
}

function FirstComponent({ value }) {
   return (
     <div>First Component says: "{value}"</div>
   )
}

function SecondComponent({ value }) {
   return (
     <div>Second Component says: "{value}"</div>
   )
}

function ThirdComponent({ value }) {
   return (
     <div>Third Component says: "{value}"</div>
   )
}

이럴때 사용하는 것이 Context.

  • Context 사용
import { createContext } from 'react';
const MyContext = createContext();

createContext API를 이용해 Context 객체를 만들었으면, 그 안에는 Provider이라는 컴포넌트가 들어있다. 그리고 Provider에 공유하고 싶은 값을 value라는 props로 설정하고, 자식 컴포넌트는 이 값을 바로 접근해 사용할 수 있다.

function App() {
	return(
    	<MyContext.Provider value="Hello World">
        	<GrandParent />
        </MyContext.Provider>
    );
}

이렇게 사용하면, 원하는 컴포넌트에서 useContext 라는 훅을 사용해 Context에 넣은 값에 바로 접근 가능 하다. useContext 훅의 인자 안에는 createContext로 만든 MyContext를 넣는다.

import { createContetxt, useContext } from 'react';

function Message() {
	const value = useContext(MyContext);
    return (
    	<div>
        	Received: {value}
        </div>
    );
}

요로코롬 사용하면, 중간에 여러 컴포넌트를 거쳐 전달하는 props를 쓰지 않아도 된다.

import { createContext, useContext } from 'react';
const MyContext = createContext();

function App() {
	return(
    	<MyContext.Provider value="Hello World">
        	<GrandParent />
        </MyContext.Provider>
    );
}

function GrandParent() {
	return <Parent />
}

function Parent() {
	return <Child />
}

function Child() {
	return <GrandChild />
}

function GrandChild() {
	const value = useContext(MyContext);
    return <div>Received: {value}</div>
}

export default App;
import { createContext, useContext } from 'react';
const MyContext = crateContext();

function App() {
	return (
    	<MyContext.Provider value="Hello World">
        	<AwesomeComponent />
        </MyContext.Provider>
    );
}

function AwesomeComponent () {
	return(
    	<div>
        	<FirstComponent />
            <SecondComponent />
            <ThirdComponent />
        </div>
    );
}

function FirstComponent() {
	const value = useContext(MyContext);
    return <div>First Component says: "{value}"</div>
}

function SecondComponent() {
	const value = useContext(MyContext);
    return <div>Second Component says: "{value}"</div>
}

function ThirdComponent() {
	const value = useContext(MyContext);
    return <div>Third Component says: "{value}"</div>
}

export default App;

출처

https://react.dev/reference/react/useReducer

https://velog.io/@reasonz/%EB%A6%AC%EC%95%A1%ED%8A%B8-useReducer%EB%A1%9C-state-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0

https://delivan.dev/react/stop-asking-if-react-hooks-replace-redux-kr/

https://velog.io/@velopert/react-context-tutorial

엘리의 드림코딩 아카데미 리액트편

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글