immer를 사용한 불변성 유지

Posted by yunki kim on July 8, 2021

  상태가 복잡할때 불변성을 유지하면서 업데이를 하게 되면 코드가 복잡해 지고 가독성이 저하 된다. 이때 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
//Example code
import produce form 'immer';
const originalState = [
    {
        id: 1,
        todo: '1',
        checked: true,
    },
    {
        id: 2,
        todo: '2',
        checked: false,
    },
];
 
//immer는 produce라는 함수를 사용한다
//첫번째 파라미터는 수정하고 싶은 상태
//두번째 파라미터는 상태를 어떻게 업데트 할지 정의하는 함수
const nextState = produce(originalState, (draft) => {
    const odo = draft.find((t) => t.id === 2);
    todo.checked = true;
    draft.push({
        id: 3,
        todo: '3',
        checked: false,
    });
    draft.splice(draft.findIndex((t) => t.id === 1), 1);
});
cs

다음과 같은 코드가 있다고 해보자

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 logo from './logo.svg';
import './App.css';
import React, {useRef, useCallback, useState} from 'react';
 
const 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,
        };
 
        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={"ID"}
               value={form.username}
               onChange={onChange}
        />
        <input name={"name"}
               plcaeholder={"name"}
               value={form.name}
               onChange={onChange}
        />
        <button type={"submit"}>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를 적용하면 다음과 같이 된다.

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
import logo from './logo.svg';
import './App.css';
import React, {useRef, useCallback, useState} from 'react';
import produce from 'immer';
 
const 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,
        };
 
        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));
            })
        );
      }, [data]
  );
  return (
    <div>
      //...
    </div>
  );
}
 
export default App;
 
cs

useState의 함수형 업데이트와 immer함께 쓰기

  produce함수를 호출할 때 첫번째 파라미터가 함수 형태이면 업데이트 함수를 반환한다. 

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
import logo from './logo.svg';
import './App.css';
import React, {useRef, useCallback, useState} from 'react';
import produce from 'immer';
 
const App = () => {
  //...
  //input 수정을 위한 함수
  const onChange = useCallback(
      (e) => {
        const {name, value} = e.target;
        setForm(
            produce(draft => {
                draft[name= value;
            })
        );
      },
      [form]
  );
  //form등록을 위한 함수
  const onSubmit = useCallback(
      (e) => {
       //...
 
        setData(
            produce(draft => {
                draft.array.push(info);
            })
        );
 
        //form 초기화
        setForm({
          name'',
          username: '',
        });
        nextId.current += 1;
      },
      [data, form.name, form.username]
  );
  //항목을 삭제하는 함수
  const onRemove = useCallback(
      (id) => {
        setData(
            produce(draft => {
                draft.array.splice(draft.array.findIndex(info => info.id === id));
            })
        );
      }, [data]
  );
  return (
    <div>
      //...
    </div>
  );
}
 
export default App;
 
cs