Giới thiệu về Immer - Phần 1
Tags: react, immer, immutable, beginners
Published: 2022-05-09 10:08:28

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

Immer là package cho phép bạn làm việc với state bất biến (immutable state) thuận tiện hơn.

### Immer đơn giản hóa việc xử lý các cấu trúc dữ liệu bất biến

Immer có thể được dùng trong bất kì context nào mà cần sử dụng cấu trúc dữ liệu bất biến. Ví dụ kết hợp với state của React, React hay reducer của Redux, hoặc quản lý cấu hình.

Cấu trúc dữ liệu bất biến cho phép phát hiện thay đổi: 

*Nếu tham chiếu tới 1 object không thay đổi, thì bản thân đối tượng đó không thay đổi. Ngoài ra, Immer làm cho việc sao chép tương đối rẻ (không mất nhiều công sức): Các phần không thay đổi của cây dữ liệu không cần phải sao chép và chia sẻ trong bộ nhớ với các phiên bản cũ hơn có cùng state.*

Nói chung, những lợi ích này có thể đạt được bằng cách đảm bảo rằng bạn không bao giờ thay đổi bất kỳ thuộc tính nào của một đối tượng, mảng (array) hoặc Map, bằng cách luôn tạo một bản sao đã thay đổi. ( ý nói là sử dụng các hàm kiểu như map(), filter() ... để tạo bản sao không sửa trực tiếp data gốc). Trong thực tế, điều này có thể dẫn đến việc code khá cồng kềnh và rất dễ vô tình vi phạm các ràng buộc đó. Immer sẽ giúp bạn tuân theo mô hình dữ liệu bất biến bằng cách giải quyết những điểm khó khăn sau:

1. Immer sẽ phát hiện ra các mutations và ném ra lỗi.

*Tham khảo định nghĩa Mutation: [Mutation] (https://viblo.asia/p/immutability-in-reactjs-WAyK8R49lxX)*

2. Immer sẽ loại bỏ nhu cầu về các đoạn code soạn sẵn cần thiết khi tạo các bản cập nhật sâu cho các đối tượng bất biến: Không có Immer, các bản sao đối tượng cần được thực hiện bằng tay ở mọi cấp độ. Điển hình bằng cách sử dụng rất nhiều spread operator '...'. Khi sử dụng Immer, các thay đổi được thực hiện đối với đối tượng nháp, đối tượng này sẽ ghi lại các thay đổi và xử lý việc tạo các bản sao cần thiết mà không bao giờ ảnh hưởng đến đối tượng gốc.

*Đoạn này nói là không có Immer thì ta phải tạo ra sao chép của data gốc. Sử dụng các kiểu sau để tạo ra sao chép của data gốc:*

- filter, map 
- Object.assign({}, ...)
- user: {
            ...prevState.user,
            age: prevState.user.age + 1
        }

*Tham khảo [Link](https://viblo.asia/p/dam-bao-tinh-bat-bien-cua-du-lieu-voi-thu-vien-immerjs-4dbZN83g5YM)*

3. Khi sử dụng Immer, bạn không cần phải học các API hoặc cấu trúc dữ liệu chuyên dụng để có thể tạo ra các immutable state. Với Immer, bạn sẽ sử dụng các cấu trúc dữ liệu JavaScript và sử dụng các mutable API JavaScript.

#### A quick example for comparison

```
const baseState = [
    {
        title: "Learn TypeScript",
        done: true
    },
    {
        title: "Try Immer",
        done: false
    }
]
```

Hãy tưởng tượng chúng ta có baseState ở trên và chúng ta sẽ cần cập nhật phần tử thứ 2 và thêm phần tử thứ 3.  Tuy nhiên, chúng ta không muốn thay đổi baseState gốc và chúng ta cũng muốn tránh sao chép sâu (để bảo toàn phần tử thứ 1).

##### Không dùng Immer

Nếu không có Immer, chúng ta sẽ phải sao chép cẩn thận từng cấp độ của cấu trúc state do ảnh hưởng bởi sự thay đổi của chúng ta (là cập nhật và thêm mới) :

```
const nextState = baseState.slice() // sao chép mảng
nextState[1] = {
    // thay đổi phần tử thứ 1 ...
    ...nextState[1], // sao chép phần tử thứ 1
    done: true // ...kết hợp với việc cập nhật
}
// ngay khi nextState được sao chép, có thể sử dụng push ở đây,
// nhưng làm điều tương tự vào bất kỳ thời điểm tùy ý nào trong 
// tương lai sẽ vi phạm các nguyên tắc bất biến và tạo ra lỗi!
nextState.push({title: "Tweet about it"})
```
##### Dùng Immer

Với Immer, quá trình này đơn giản hơn. Chúng ta có thể tận dụng sức mạnh của hàm produce, lấy đối số đầu tiên là *state* mà chúng ta muốn bắt đầu, và đối số thứ hai, chúng ta truyền *một hàm*, được gọi là *công thức* (recipe), được chuyển qua một bản nháp mà chúng ta có thể áp dụng các mutation. Những Mutation được ghi lại và sử dụng để tạo ra state tiếp theo sau khi *công thức* (recipe) được hoàn thành. hàm Produce sẽ xử lý tất cả các sao chép cần thiết và bảo vệ khỏi các sửa đổi ngẫu nhiên trong tương lai cũng như bằng cách đóng băng dữ liệu.

```
import produce from "immer"

const nextState = produce(baseState, draft => {
    draft[1].done = true
    draft.push({title: "Tweet about it"})
})
```

##### Cách hoạt động của Immer

Ý tưởng cơ bản là với Immer, bạn sẽ áp dụng tất cả các thay đổi của mình cho một bản nháp tạm thời, là một proxy của currentState. Khi tất cả các Mutation ( sự thay đổi ) của bạn được hoàn thành, Immer sẽ tạo ra state tiếp theo dựa trên các Mutation đối với state của bản nháp. Điều này có nghĩa là bạn có thể tương tác với dữ liệu của mình bằng cách chỉ cần sửa đổi nó trong khi vẫn giữ được tất cả các lợi ích của dữ liệu bất biến.

![Immer flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/doxje8twoppmh7jjwmvt.png)