Giới thiệu về Immer - Phần 4
Tags: react, beginners, immer, immutation
Published: 2022-05-10 02:11:24

Bài dịch từ trang chủ của Immer:
https://immerjs.github.io/immer/

### React & Immer

#### useState + Immer

Hook useState giả định rằng bất kỳ state nào được lưu trữ bên trong nó đều được coi là bất biến. Các cập nhật sâu trong state của các component của React có thể được đơn giản hóa rất nhiều bằng cách sử dụng Immer. Ví dụ sau cho thấy cách sử dụng `produce` kết hợp với `useState` và có thể thử trên [CodeSandbox](https://codesandbox.io/s/immer-usestate-ujkgg?file=/src/index.js).

```
import React, { useCallback, useState } from "react";
import produce from "immer";

const TodoList = () => {
  const [todos, setTodos] = useState([
    {
      id: "React",
      title: "Learn React",
      done: true
    },
    {
      id: "Immer",
      title: "Try Immer",
      done: false
    }
  ]);

  const handleToggle = useCallback((id) => {
    setTodos(
      produce((draft) => {
        const todo = draft.find((todo) => todo.id === id);
        todo.done = !todo.done;
      })
    );
  }, []);

  const handleAdd = useCallback(() => {
    setTodos(
      produce((draft) => {
        draft.push({
          id: "todo_" + Math.random(),
          title: "A new todo",
          done: false
        });
      })
    );
  }, []);

  return (<div>{*/ See CodeSandbox */}</div>)
}
```

_Thắc mắc: ở trong setTodos hàm produce baseState ở đâu ??? 
Đây là tham số đầu vào của produce:_ 

_produce(baseState, recipe: (draftState) => void): nextState_

_Sau một hồi tìm hiểu, đọc lại doc hóa ra đang sử dụng curried producer. (Giờ mới hiểu phần curried producer :D )_

_Là đoạn này đây:_
ví dụ trên có thể được đơn giản hóa bằng cách sử dụng dạng `curried` của `produce`, trong đó bạn chỉ truyền `công thức (recipe)` cho `produce` và `produce` sẽ trả về một hàm mới , hàm mới này áp dụng công thức (recipe) tới baseState.

#### useImmer

Vì tất cả các trình cập nhật state tuân theo cùng một pattern trong đó chức năng cập nhật được bọc trong `produce`, nên cũng có thể đơn giản hóa điều trên bằng cách tận dụng thư viện `use-immer` sẽ tự động bọc các chức năng cập nhật trong `produce`:

```
import React, { useCallback } from "react";
import { useImmer } from "use-immer";

const TodoList = () => {
  const [todos, setTodos] = useImmer([
    {
      id: "React",
      title: "Learn React",
      done: true
    },
    {
      id: "Immer",
      title: "Try Immer",
      done: false
    }
  ]);

  const handleToggle = useCallback((id) => {
    setTodos((draft) => {
      const todo = draft.find((todo) => todo.id === id);
      todo.done = !todo.done;
    });
  }, []);

  const handleAdd = useCallback(() => {
    setTodos((draft) => {
      draft.push({
        id: "todo_" + Math.random(),
        title: "A new todo",
        done: false
      });
    });
  }, []);

  // etc
```
Xem demo ở đây [CodeSandbox](https://codesandbox.io/s/use-immer-bvd5v?file=/src/index.js)

#### useReducer + Immer

Tương tự như useState, useReducer cũng có thể kết hợp với Immer, như được trình bày trong [CodeSandbox](https://codesandbox.io/s/immer-usereducer-bqpzn?file=/src/index.js:0-1018) này:

```
import React, {useCallback, useReducer} from "react"
import produce from "immer"

const TodoList = () => {
    const [todos, dispatch] = useReducer(
        produce((draft, action) => {
            switch (action.type) {
                case "toggle":
                    const todo = draft.find(todo => todo.id === action.id)
                    todo.done = !todo.done
                    break
                case "add":
                    draft.push({
                        id: action.id,
                        title: "A new todo",
                        done: false
                    })
                    break
                default:
                    break
            }
        }),
        [
            /* initial todos */
        ]
    )

    const handleToggle = useCallback(id => {
        dispatch({
            type: "toggle",
            id
        })
    }, [])

    const handleAdd = useCallback(() => {
        dispatch({
            type: "add",
            id: "todo_" + Math.random()
        })
    }, [])

    // etc
}
```

#### useImmerReducer

.. một lần nữa, có thể bị rút ngắn một chút bởi `useImmerReducer` từ thư viện `use-immer` 

```
import React, { useCallback } from "react";
import { useImmerReducer } from "use-immer";

const TodoList = () => {
  const [todos, dispatch] = useImmerReducer(
    (draft, action) => {
      switch (action.type) {
        case "toggle":
          const todo = draft.find((todo) => todo.id === action.id);
          todo.done = !todo.done;
          break;
        case "add":
          draft.push({
            id: action.id,
            title: "A new todo",
            done: false
          });
          break;
        default:
          break;
      }
    },
    [ /* initial todos */ ]
  );

  //etc

```
#### Redux + Immer

Redux + Immer được đề cập rộng rãi trong tài liệu của **Redux Toolkit**. Đối với Redux không có **Redux Toolkit**, bạn có thể áp dụng thủ thuật tương tự như đã áp dụng cho useReducer ở trên: bọc hàm reducer bằng `produce` và bạn có thể thay đổi bản nháp một cách an toàn!

Ví dụ:

```
import produce from "immer"

// Reducer with initial state
const INITIAL_STATE = [
    /* bunch of todos */
]

const todosReducer = produce((draft, action) => {
    switch (action.type) {
        case "toggle":
            const todo = draft.find(todo => todo.id === action.id)
            todo.done = !todo.done
            break
        case "add":
            draft.push({
                id: action.id,
                title: "A new todo",
                done: false
            })
            break
        default:
            break
    }
})
```