Dữ liệu phản hồi có thể không bị thay đổi khi sử dụng truy cập mảng

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
9 và
// ❌ Illegal - by default, this will mutate the state!
state.value = 123
0 của Bộ công cụ Redux tự động sử dụng Immer trong nội bộ để cho phép bạn viết logic cập nhật bất biến đơn giản hơn bằng cách sử dụng cú pháp "mutating". Điều này giúp đơn giản hóa hầu hết các triển khai bộ giảm tốc

Bởi vì bản thân Immer là một lớp trừu tượng, điều quan trọng là phải hiểu tại sao Redux Toolkit sử dụng Immer và cách sử dụng nó đúng cách

Tính bất biến và Redux

Khái niệm cơ bản về tính bất biến

"Mutable" có nghĩa là "có thể thay đổi". Nếu một cái gì đó là "bất biến", nó không bao giờ có thể thay đổi

Các đối tượng và mảng JavaScript đều có thể thay đổi theo mặc định. Nếu tôi tạo một đối tượng, tôi có thể thay đổi nội dung của các trường của nó. Nếu tôi tạo một mảng, tôi cũng có thể thay đổi nội dung

const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3

const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'

Điều này được gọi là biến đổi đối tượng hoặc mảng. Đó là cùng một đối tượng hoặc tham chiếu mảng trong bộ nhớ, nhưng bây giờ nội dung bên trong đối tượng đã thay đổi

Để cập nhật các giá trị một cách bất biến, mã của bạn phải tạo các bản sao của các đối tượng/mảng hiện có, sau đó sửa đổi các bản sao đó

Chúng ta có thể thực hiện việc này bằng tay bằng cách sử dụng các toán tử trải rộng mảng/đối tượng của JavaScript, cũng như các phương thức mảng trả về các bản sao mới của mảng thay vì thay đổi mảng ban đầu

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')

Bạn muốn biết thêm?

Để biết thêm thông tin về cách hoạt động của tính bất biến trong JavaScript, hãy xem

  • Hướng dẫn trực quan về tham chiếu trong JavaScript
  • Tính bất biến trong React và Redux. Hướng dẫn hoàn chỉnh

Bộ giảm tốc và cập nhật bất biến

Một trong những quy tắc chính của Redux là bộ giảm tốc của chúng tôi không bao giờ được phép thay đổi giá trị trạng thái ban đầu/hiện tại

sự nguy hiểm

// ❌ Illegal - by default, this will mutate the state!
state.value = 123

Có một số lý do tại sao bạn không được thay đổi trạng thái trong Redux

  • Nó gây ra lỗi, chẳng hạn như giao diện người dùng không cập nhật đúng cách để hiển thị các giá trị mới nhất
  • Nó làm cho nó khó hiểu hơn tại sao và làm thế nào trạng thái đã được cập nhật
  • Nó làm cho việc viết bài kiểm tra trở nên khó khăn hơn
  • Nó phá vỡ khả năng sử dụng "gỡ lỗi du hành thời gian" một cách chính xác
  • Nó đi ngược lại tinh thần và mô hình sử dụng dự định cho Redux

Vì vậy, nếu chúng tôi không thể thay đổi bản gốc, làm cách nào để trả về trạng thái đã cập nhật?

tiền boa

Các bộ giảm chỉ có thể tạo các bản sao của các giá trị ban đầu và sau đó chúng có thể thay đổi các bản sao đó

// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}

Chúng ta đã thấy rằng chúng ta có thể viết các bản cập nhật bất biến bằng tay, bằng cách sử dụng các toán tử trải rộng mảng/đối tượng của JavaScript và các hàm khác trả về các bản sao của các giá trị ban đầu

Điều này trở nên khó khăn hơn khi dữ liệu được lồng vào nhau. Một quy tắc quan trọng của các bản cập nhật không thay đổi là bạn phải tạo một bản sao của mọi cấp độ lồng nhau cần được cập nhật

Một ví dụ điển hình về điều này có thể trông giống như

function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}

Tuy nhiên, nếu bạn đang nghĩ rằng "viết các bản cập nhật bất biến bằng tay theo cách này có vẻ khó nhớ và khó thực hiện chính xác". Uh, đúng vậy. . )

Viết logic cập nhật bất biến bằng tay rất khó và vô tình thay đổi trạng thái trong bộ giảm tốc là lỗi phổ biến nhất mà người dùng Redux mắc phải

Cập nhật bất biến với Immer

Immer là một thư viện đơn giản hóa quá trình viết logic cập nhật bất biến

Immer cung cấp một hàm có tên là

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
1, chấp nhận hai đối số.
// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2 ban đầu của bạn và chức năng gọi lại. Hàm gọi lại được cung cấp phiên bản "bản nháp" của trạng thái đó và bên trong hàm gọi lại, việc viết mã làm thay đổi giá trị bản nháp là an toàn. Immer theo dõi tất cả các nỗ lực thay đổi giá trị dự thảo và sau đó phát lại các thay đổi đó bằng cách sử dụng các giá trị tương đương không thay đổi của chúng để tạo kết quả an toàn, được cập nhật bất biến

import produce from 'immer'

const baseState = [
{
todo: 'Learn typescript',
done: true,
},
{
todo: 'Try immer',
done: false,
},
]

const nextState = produce(baseState, (draftState) => {
// "mutate" the draft array
draftState.push({ todo: 'Tweet about it' })
// "mutate" the nested state
draftState[1].done = true
})

console.log(baseState === nextState)
// false - the array was copied
console.log(baseState[0] === nextState[0])
// true - the first item was unchanged, so same reference
console.log(baseState[1] === nextState[1])
// false - the second item was copied and updated

Bộ công cụ Redux và Immer

Redux Toolkit's

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
9 API sử dụng Immer tự động trong nội bộ. Vì vậy, nó đã an toàn để "biến đổi" trạng thái bên trong bất kỳ hàm giảm chữ hoa chữ thường nào được chuyển đến
const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
9

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2

Đổi lại,

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
0 sử dụng
const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
9 bên trong, do đó, việc "biến đổi" trạng thái ở đó cũng an toàn

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
5

Điều này thậm chí còn áp dụng nếu các chức năng rút gọn trường hợp được xác định bên ngoài cuộc gọi

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
7. Ví dụ: bạn có thể có một chức năng rút gọn trường hợp có thể tái sử dụng, dự kiến ​​sẽ "biến đổi" trạng thái của nó và bao gồm nó khi cần

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
7

Điều này hoạt động vì logic "đột biến" được bao bọc trong phương thức

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
1 của Immer bên trong khi nó thực thi

thận trọng

Hãy nhớ rằng, logic "đột biến" chỉ hoạt động chính xác khi được bao bọc bên trong Immer. Nếu không, mã đó sẽ thực sự thay đổi dữ liệu

Mô hình sử dụng ngâm

Có một số mẫu hữu ích cần biết và các vấn đề cần chú ý khi sử dụng Immer in Redux Toolkit

Trạng thái đột biến và quay trở lại

Immer hoạt động bằng cách theo dõi các nỗ lực thay đổi giá trị trạng thái được soạn thảo hiện có, bằng cách gán cho các trường lồng nhau hoặc bằng cách gọi các hàm thay đổi giá trị. Điều đó có nghĩa là

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2 phải là một đối tượng hoặc mảng JS để Immer có thể thấy các thay đổi đã cố gắng. (Bạn vẫn có thể có trạng thái của một lát cắt là nguyên thủy như chuỗi hoặc boolean, nhưng vì dù sao thì nguyên thủy cũng không bao giờ có thể bị thay đổi, tất cả những gì bạn có thể làm chỉ là trả về một giá trị mới. )

Trong bất kỳ bộ giảm trường hợp cụ thể nào, Immer hy vọng rằng bạn sẽ thay đổi trạng thái hiện tại hoặc tự xây dựng một giá trị trạng thái mới và trả về nó, chứ không phải cả hai trong cùng một chức năng. Ví dụ: cả hai đều là bộ giảm tốc hợp lệ với Immer

const obj = { a: 1, b: 2 }
// still the same object outside, but the contents have changed
obj.b = 3

const arr = ['a', 'b']
// In the same way, we can change the contents of this array
arr.push('c')
arr[1] = 'd'
0

Tuy nhiên, có thể sử dụng các bản cập nhật không thay đổi để thực hiện một phần công việc và sau đó lưu kết quả thông qua một "đột biến". Một ví dụ về điều này có thể lọc một mảng lồng nhau

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
0

Lưu ý rằng việc thay đổi trạng thái trong hàm mũi tên có trả về ẩn sẽ phá vỡ quy tắc này và gây ra lỗi. Điều này là do các câu lệnh và lệnh gọi hàm có thể trả về một giá trị và Immer nhìn thấy cả biến đổi đã thử và giá trị mới được trả về và không biết nên sử dụng giá trị nào làm kết quả. Một số giải pháp tiềm năng đang sử dụng từ khóa

// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}
0 để bỏ qua việc có giá trị trả về hoặc sử dụng dấu ngoặc nhọn để tạo phần thân cho hàm mũi tên và không có giá trị trả về

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
1

Mặc dù viết logic cập nhật bất biến lồng nhau là khó, nhưng đôi khi việc thực hiện thao tác trải rộng đối tượng để cập nhật nhiều trường cùng một lúc sẽ đơn giản hơn so với việc chỉ định các trường riêng lẻ

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
2

Thay vào đó, bạn có thể sử dụng

// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}
1 để thay đổi nhiều trường cùng một lúc, vì
// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}
1 luôn thay đổi đối tượng đầu tiên được cung cấp

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
3

Đặt lại và thay thế trạng thái

Đôi khi, bạn có thể muốn thay thế toàn bộ

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2 hiện có, vì bạn đã tải một số dữ liệu mới hoặc bạn muốn đặt lại trạng thái về giá trị ban đầu

sự nguy hiểm

Một sai lầm phổ biến là thử chỉ định trực tiếp

// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}
4. Điều này sẽ không làm việc. Điều này chỉ trỏ biến
// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2 cục bộ đến một tham chiếu khác. Điều đó không làm biến đổi đối tượng/mảng
// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2 hiện có trong bộ nhớ, cũng không trả về một giá trị hoàn toàn mới, vì vậy Immer không thực hiện bất kỳ thay đổi thực tế nào

Thay vào đó, để thay thế trạng thái hiện có, bạn nên trả về giá trị mới trực tiếp

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
4

Gỡ lỗi và kiểm tra trạng thái soạn thảo

Thông thường, bạn muốn đăng nhập trạng thái đang tiến hành từ bộ giảm tốc để xem nó trông như thế nào khi nó đang được cập nhật, chẳng hạn như

// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}
7. Thật không may, các trình duyệt hiển thị các phiên bản Proxy đã ghi ở định dạng khó đọc hoặc khó hiểu

Logged proxy draft

Để giải quyết vấn đề này, Immer bao gồm một hàm

// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}
8 trích xuất một bản sao của dữ liệu được bao bọc và RTK tái xuất
// ✅ This is safe, because we made a copy
return {
...state,
value: 123,
}
8. Bạn có thể sử dụng điều này trong bộ giảm tốc của mình nếu bạn cần đăng nhập hoặc kiểm tra trạng thái công việc đang tiến hành

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
5

Thay vào đó, đầu ra chính xác sẽ trông như thế này

Logged current value

Immer cũng cung cấp các hàm

function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}
0 và
function handwrittenReducer(state, action) {
return {
...state,
first: {
...state.first,
second: {
...state.first.second,
[action.someId]: {
...state.first.second[action.someId],
fourth: action.someValue,
},
},
},
}
}
1, giúp truy xuất dữ liệu gốc mà không áp dụng bất kỳ bản cập nhật nào và kiểm tra xem liệu một giá trị đã cho có phải là bản nháp được bao bọc bởi Proxy hay không. Kể từ RTK 1. 5. 1, cả hai đều được tái xuất từ ​​​​RTK

Cập nhật dữ liệu lồng nhau

Immer đơn giản hóa rất nhiều việc cập nhật dữ liệu lồng nhau. Các đối tượng và mảng lồng nhau cũng được bao bọc trong Proxy và được soạn thảo, đồng thời có thể an toàn khi lấy một giá trị lồng nhau vào biến riêng của nó rồi thay đổi nó

Tuy nhiên, điều này vẫn chỉ áp dụng cho các đối tượng và mảng. Nếu chúng tôi kéo một giá trị nguyên thủy vào biến riêng của nó và cố gắng cập nhật nó, Immer sẽ không có gì để bọc và không thể theo dõi bất kỳ cập nhật nào

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
6

Có một gotcha ở đây. . Hầu hết thời gian điều này không thành vấn đề, nhưng có thể có những lúc bạn muốn chèn một giá trị và sau đó cập nhật thêm cho nó

Liên quan đến vấn đề này, RTK có thể được sử dụng làm bộ giảm tốc độc lập hoặc chức năng cập nhật "đột biến". Các hàm này xác định liệu có "biến đổi" hay trả về một giá trị mới hay không bằng cách kiểm tra xem liệu trạng thái mà chúng đưa ra có được bao bọc trong bản nháp hay không. Nếu bạn đang tự gọi các hàm này bên trong bộ giảm chữ hoa chữ thường, hãy chắc chắn rằng bạn biết liệu bạn đang chuyển cho chúng giá trị nháp hay giá trị đơn giản

Cuối cùng, cần lưu ý rằng Immer không tự động tạo các đối tượng hoặc mảng lồng nhau cho bạn - bạn phải tự tạo chúng. Ví dụ: giả sử chúng ta có một bảng tra cứu chứa các mảng lồng nhau và chúng ta muốn chèn một mục vào một trong các mảng đó. Nếu chúng ta cố gắng chèn vô điều kiện mà không kiểm tra sự tồn tại của mảng đó, logic sẽ bị lỗi khi mảng không tồn tại. Thay vào đó, bạn cần đảm bảo mảng tồn tại trước

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
7

Đột biến trạng thái Linting

Nhiều cấu hình ESLint bao gồm https. // eslint. quy tắc org/docs/rules/no-param-resign, cũng có thể cảnh báo về các đột biến đối với các trường lồng nhau. Điều đó có thể khiến quy tắc cảnh báo về các đột biến thành

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2 trong bộ giảm tốc do Immer cung cấp, điều này không hữu ích

Để giải quyết vấn đề này, bạn có thể yêu cầu quy tắc ESLint bỏ qua các thay đổi đối với tham số có tên

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
2

const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
8

Tại sao Immer được tích hợp sẵn

Theo thời gian, chúng tôi đã nhận được một số yêu cầu biến Immer thành một phần tùy chọn trong API

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
0 và
const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
9 của RTK, thay vì yêu cầu nghiêm ngặt

Câu trả lời của chúng tôi luôn giống nhau. Bắt buộc phải nhập vai trong RTK và điều đó sẽ không thay đổi

Thật đáng để xem xét lý do tại sao chúng tôi coi Immer là một phần quan trọng của RTK và tại sao chúng tôi sẽ không biến nó thành tùy chọn

Lợi ích của Immer

Immer có hai lợi ích chính. Đầu tiên, Immer đơn giản hóa đáng kể logic cập nhật bất biến. . Nhìn chung, các hoạt động dài dòng đó rất khó đọc và cũng làm xáo trộn mục đích thực sự của câu lệnh cập nhật là gì. Immer loại bỏ tất cả các trải rộng lồng nhau và các lát mảng. Mã không chỉ ngắn hơn và dễ đọc hơn mà còn rõ ràng hơn nhiều về bản cập nhật thực tế sẽ xảy ra

Thứ hai, viết các cập nhật bất biến một cách chính xác rất khó và rất dễ mắc lỗi (chẳng hạn như quên sao chép một cấp độ lồng trong một tập hợp các trải rộng đối tượng, sao chép một mảng cấp cao nhất chứ không phải mục cần cập nhật bên trong mảng, . Đây là một phần lý do tại sao. Immer loại bỏ hiệu quả các đột biến ngẫu nhiên. Không chỉ không có thêm các thao tác trải rộng có thể bị viết sai mà Immer còn tự động đóng băng trạng thái. Điều này gây ra lỗi nếu bạn vô tình thay đổi, ngay cả bên ngoài bộ giảm tốc. Loại bỏ nguyên nhân số 1 gây ra lỗi Redux là một cải tiến lớn

Ngoài ra, Truy vấn RTK sử dụng các khả năng vá lỗi của Immer để kích hoạt các bản cập nhật tối ưu và cập nhật bộ đệm thủ công.

Đánh đổi và mối quan tâm

Giống như bất kỳ công cụ nào, việc sử dụng Immer cũng có sự đánh đổi và người dùng đã bày tỏ một số lo ngại về việc sử dụng nó

Immer không thêm vào kích thước gói ứng dụng tổng thể. Đó là khoảng 8K phút, 3. 3K phút+gz (tham khảo. ngâm tài liệu. Cài đặt, gói. js. phân tích tổ chức). Tuy nhiên, kích thước gói thư viện đó bắt đầu tự trả tiền bằng cách thu hẹp lượng logic bộ giảm tốc trong ứng dụng của bạn. Ngoài ra, lợi ích của mã dễ đọc hơn và loại bỏ các lỗi đột biến có giá trị lớn

Immer cũng thêm một chút chi phí hoạt động trong thời gian chạy. Tuy nhiên, theo trang tài liệu "Hiệu suất" của Immer, chi phí hoạt động không có ý nghĩa trong thực tế. Ngoài ra,. Thay vào đó, chi phí cập nhật giao diện người dùng quan trọng hơn nhiều

Vì vậy, mặc dù việc sử dụng Immer không phải là "miễn phí", nhưng chi phí gói và chi phí hoàn hảo đủ nhỏ để xứng đáng

Điểm khó khăn thực tế nhất khi sử dụng Immer là trình gỡ lỗi trình duyệt hiển thị Proxies theo cách khó hiểu, điều này khiến khó kiểm tra các biến trạng thái trong khi gỡ lỗi. Đây chắc chắn là một sự khó chịu. Tuy nhiên, điều này không thực sự ảnh hưởng đến hành vi thời gian chạy và chúng tôi đã ở trên trang này. (Do việc sử dụng Proxy ngày càng rộng rãi như một phần của các thư viện như Mobx và Vue 3, điều này cũng không phải là duy nhất đối với Immer. )

Một vấn đề khác là giáo dục và hiểu biết. Redux luôn yêu cầu tính không thay đổi trong bộ giảm tốc và do đó, việc nhìn thấy mã "đột biến" có thể gây nhầm lẫn. Chắc chắn những người dùng Redux mới có thể thấy những "đột biến" đó trong mã ví dụ, cho rằng việc sử dụng Redux là bình thường và sau đó thử làm điều tương tự bên ngoài

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
0. Điều này thực sự sẽ gây ra các đột biến và lỗi thực sự, bởi vì nó nằm ngoài khả năng của Immer trong việc hoàn thành các bản cập nhật

Chúng tôi đã giải quyết vấn đề này bằng cách bao gồm nhiều phần được đánh dấu để nhấn mạnh điều đó và thêm trang tài liệu cụ thể mà bạn hiện đang đọc này

Kiến trúc và ý định

Có thêm hai lý do tại sao Immer không phải là tùy chọn

Một là kiến ​​trúc của RTK.

// ❌ Illegal - by default, this will mutate the state!
state.value = 123
0 và
const obj = {
a: {
// To safely update obj.a.c, we have to copy each piece
c: 3,
},
b: 2,
}

const obj2 = {
// copy obj
...obj,
// overwrite a
a: {
// copy obj.a
...obj.a,
// overwrite c
c: 42,
},
}

const arr = ['a', 'b']
// Create a new copy of arr, with "c" appended to the end
const arr2 = arr.concat('c')

// or, we can make a copy of the original array:
const arr3 = arr.slice()
// and mutate the copy:
arr3.push('c')
9 được triển khai bằng cách nhập trực tiếp Immer. Không có cách nào dễ dàng để tạo một phiên bản của một trong số chúng có tùy chọn
import produce from 'immer'

const baseState = [
{
todo: 'Learn typescript',
done: true,
},
{
todo: 'Try immer',
done: false,
},
]

const nextState = produce(baseState, (draftState) => {
// "mutate" the draft array
draftState.push({ todo: 'Tweet about it' })
// "mutate" the nested state
draftState[1].done = true
})

console.log(baseState === nextState)
// false - the array was copied
console.log(baseState[0] === nextState[0])
// true - the first item was unchanged, so same reference
console.log(baseState[1] === nextState[1])
// false - the second item was copied and updated
2 giả định. Bạn không thể thực hiện nhập tùy chọn và chúng tôi cần có sẵn Immer ngay lập tức và đồng bộ trong lần tải đầu tiên của ứng dụng

Ngoài ra, RTK hiện gọi ngay khi nhập, để đảm bảo rằng Immer hoạt động chính xác trong môi trường không hỗ trợ Proxy ES6 (chẳng hạn như IE11 và các phiên bản React Native cũ hơn). Điều này là cần thiết vì Immer đã tách hành vi ES5 thành một plugin trong khoảng phiên bản 6. 0, nhưng việc bỏ hỗ trợ ES5 sẽ là một thay đổi đột phá lớn đối với RTK và phá vỡ người dùng của chúng tôi. Bởi vì RTK tự gọi

import produce from 'immer'

const baseState = [
{
todo: 'Learn typescript',
done: true,
},
{
todo: 'Try immer',
done: false,
},
]

const nextState = produce(baseState, (draftState) => {
// "mutate" the draft array
draftState.push({ todo: 'Tweet about it' })
// "mutate" the nested state
draftState[1].done = true
})

console.log(baseState === nextState)
// false - the array was copied
console.log(baseState[0] === nextState[0])
// true - the first item was unchanged, so same reference
console.log(baseState[1] === nextState[1])
// false - the second item was copied and updated
3 từ điểm vào, nên Immer luôn được kéo vào

Và cuối cùng. Theo mặc định, Immer được tích hợp vào RTK vì chúng tôi tin rằng đó là lựa chọn tốt nhất cho người dùng của chúng tôi. Chúng tôi muốn người dùng của mình sử dụng Immer và coi đó là một thành phần quan trọng không thể thương lượng của RTK. Những lợi ích tuyệt vời như mã giảm tốc đơn giản hơn và ngăn chặn các đột biến ngẫu nhiên vượt xa những mối quan tâm tương đối nhỏ