그럼에도 불구하고

👨‍💻

[React] immer란? 본문

React/React basics

[React] immer란?

zenghyun 2023. 5. 5. 01:11

 

오늘은 immer에 대해 알아보겠습니다. :)

 

 

[ immer ]

immer는 React에서 구조가 복잡한 객체도 매우 쉽고 짧은 코드를 사용하여 불변성을 유지하면서 업데이트하기 위해 사용하는 라이브러리입니다. 

 

📌 불변성이란?

쉽게 말해 상태를 변경하지 않는 것입니다. 

 

 

[ install ]

yarn add immer

 

 

 

[ immer를 사용하지 않고 불변성 유지 ]

아래 예시는 immer를 사용했을 때와 비교하기 위해 작성된 코드입니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import React, { useRef, useCallback, useState } from 'react';
 
function App() {
  const nextId = useRef(1);
  const [form, setForm] = useState({name:'', username:''});
  const [data, setData] = useState({
    array: [],
    uselessValue: null
  });
 
  // input 수정을 위한 함수 
  const onChange = useCallback( e => {
    const { name, value } = e.target;
    setForm({
      ...form,
      [name] : [value]
    });
  },[form]);
 
  // form 등록을 위한 함수 
  const onSubmit = useCallback(
    e => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username
      };
 
      // array에 새 항목 등록 
      setData({
        ...data,
        array: data.array.concat(info)
      });
 
      // form 초기화
      setForm({
        name'',
        username: ''
      });
      nextId.current += 1;
    }
    ,[data, form.name, form.username])
 
    // 항목을 삭제하는 함수
    const onRemove = useCallback(
      id => {
        setData({
          ...data,
          array: data.array.filter(info => info.id !== id)
        });
      }
      ,[data]);
 
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input 
        name="username"
        placeholder='아이디'
        value={form.username}
        onChange={onChange}
        />
        <input
        name='name'
        placeholder='이름'
        value={form.name}
        onChange={onChange}
        />
        <button type='submit'>등록</button>
      </form>
      <div>
        <ul>
          {data.array.map(info => ( 
            <li key={info.id} onClick={() => onRemove(info.id)}>
              {info.username} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}
 
export default App;
cs

 

폼에서 아이디와 이름을 입력하면 하단 리스트에 추가되고, 리스트 항목을 클릭하면 삭제되는 컴포넌트입니다. 

이렇게 전개 연산자와 배열 내장 함수를 사용하여 불변성을 유지하는 것은 어렵지 않지만, 상태가 복잡해진다면 조금 귀찮은 작업이 될 수 있습니다. 

 

 

 

[ immer 사용법 ]

produce(수정하고 싶은 상태, 상태를 업데이트하는 함수)

 

produce라는 함수는 두 가지 파라미터를 받습니다. 첫 번째 파라미터는 수정하고 싶은 상태이고, 두 번째 파라미터는 상태를 어떻게 업데이트할지 정의하는 함수입니다. 

 

두 번째 파라미터로 전달되는 함수 내부에서 원하는 값을 변경하면, produce 함수가 불변성 유지를 대신해 주면서 새로운 상태를 생성해 줍니다. 

 

이 라이브러리의 핵심은 '불변성에 신경 쓰지 않는 것처럼 코드를 작성하되 불변성 관리는 제대로 해 주는 것'입니다. 단순히 깊은 곳에 위치하는 값을 바꾸는 것 외에 배열을 처리할 때도 매우 쉽고 편합니다. 

 

 

 

[ App 컴포넌트에 immer 적용하기 ]

immer 라이브러리를 적용하지 않은 App 컴포넌트에 immer를 적용시켜보겠습니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import React, { useRef, useCallback, useState } from 'react';
import {produce} from 'immer';
 
function App() {
  const nextId = useRef(1);
  const [form, setForm] = useState({name:'', username:''});
  const [data, setData] = useState({
    array: [],
    uselessValue: null
  });
 
  // input 수정을 위한 함수 
  const onChange = useCallback( e => {
    const { name, value } = e.target;
    setForm(
      produce(form, draft => {
        draft[name= value;
      })
    );
  },[form]);
 
  // form 등록을 위한 함수 
  const onSubmit = useCallback(
    e => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username
      };
 
      // array에 새 항목 등록 
      setData(
        produce(data, draft => {
          draft.array.push(info)
        })
      );
 
      // form 초기화
      setForm({
        name'',
        username: ''
      });
      nextId.current += 1;
    }
    ,[data, form.name, form.username])
 
    // 항목을 삭제하는 함수
    const onRemove = useCallback(
      id => {
        setData(
          produce(data, draft => {
            draft.array.splice(draft.array.findIndex(info => info.id === id), 1)
          })
        );
      }
      ,[data]);
 
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input 
        name="username"
        placeholder='아이디'
        value={form.username}
        onChange={onChange}
        />
        <input
        name='name'
        placeholder='이름'
        value={form.name}
        onChange={onChange}
        />
        <button type='submit'>등록</button>
      </form>
      <div>
        <ul>
          {data.array.map(info => ( 
            <li key={info.id} onClick={() => onRemove(info.id)}>
              {info.username} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}
 
export default App;
 
cs

 

만약, immer에서 제공하는 produce 함수를 호출할 때, 첫 번째 파라미터가 함수 형태라면 업데이트 함수를 반환합니다. 

 

📌 예시 

 

1
2
3
4
5
6
7
8
9
10
11
const update = produce(draft => {
draft.value = 2;
});
 
const originalState = {
value: 1,
foo: 'bar',
};
 
const nextState = update(originalState);
console.log(nextState); // { value:2, foo: 'bar' }
cs

 

이 방법을 토대로 App 컴포넌트를 수정해보겠습니다. 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import React, { useRef, useCallback, useState } from 'react';
import {produce} from 'immer';
 
function App() {
  const nextId = useRef(1);
  const [form, setForm] = useState({name:'', username:''});
  const [data, setData] = useState({
    array: [],
    uselessValue: null
  });
 
  // input 수정을 위한 함수 
  const onChange = useCallback( e => {
    const { name, value } = e.target;
    setForm(
      produce(draft => {
        draft[name= value;
      })
    );
  },[]);
 
  // form 등록을 위한 함수 
  const onSubmit = useCallback(
    e => {
      e.preventDefault();
      const info = {
        id: nextId.current,
        name: form.name,
        username: form.username
      };
 
      // array에 새 항목 등록 
      setData(
        produce(draft => {
          draft.array.push(info)
        })
      );
 
      // form 초기화
      setForm({
        name'',
        username: ''
      });
      nextId.current += 1;
    }
    ,[form.name, form.username])
 
    // 항목을 삭제하는 함수
    const onRemove = useCallback(
      id => {
        setData(
          produce(draft => {
            draft.array.splice(draft.array.findIndex(info => info.id === id), 1)
          })
        );
      }
      ,[]);
 
  return (
    <div>
      <form onSubmit={onSubmit}>
        <input 
        name="username"
        placeholder='아이디'
        value={form.username}
        onChange={onChange}
        />
        <input
        name='name'
        placeholder='이름'
        value={form.name}
        onChange={onChange}
        />
        <button type='submit'>등록</button>
      </form>
      <div>
        <ul>
          {data.array.map(info => ( 
            <li key={info.id} onClick={() => onRemove(info.id)}>
              {info.username} ({info.name})
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}
 
export default App;
 
cs

 

[ 정리 ]

오늘은 immer 라이브러리에 대해 알아보았습니다. 이 라이브러리는 컴포넌트의 상태 업데이트가 조금 까다로울 때 사용하면 매우 좋습니다. 이 라이브러리는 편의를 위한 것이므로 꼭 필요하지는 않지만, 사용한다면 생산성을 크게 높일 수 있습니다. 만약 immer를 사용하는 것이 오히려 불편하게 느껴진다면 사용하지 않아도 좋다고 생각합니다.

 

 

Comments