Nói đến thư viện dùng để đảm bảo tính bất biến của dữ liệu thì hẳn các bạn nghĩ ngay đến ImmuatableJS của Facebook, tuy nhiên thì bản thân mình thấy thư viện này sử dụng khá lằng nhằng và phức tạp nên không sử dụng nhiều. Trong lúc lượn lờ trên Twitter thì mình có thấy dòng tweet của anh bạn Dan Abramov (nằm trong team phát triển ReactJS và là đồng tác giả của Redux) giới thiệu về thư viện ImmerJS nên mình đã tìm hiểu qua về thư viện này.
Immer (Tiếng Đức nghĩa là: Luôn luôn) là 1 thư viện nhỏ cho phép bạn làm việc với immutable state 1 cách thuận tiện hơn. Cách hoạt động của thư viện này dựa trên cơ chế copy-on-write.
Ý tưởng ban đầu là bạn sẽ áp dụng mọi thứ bạn muốn thay đổi trong 1 trạng thái tạm thời gọi là draftState và đại diện cho currentState. Một khi tất cả các thay đổi đã hoàn thành, Immer sẽ tạo ra nextState dựa trên sự thay đổi trong draftState. Điều này có nghĩa là bạn hoàn toàn có thể tương tác với dữ liệu bằng cách thay đổi nó trực tiếp trong khi vẫn có thể giữ lại tính bất biến của dữ liệu.
API:
Immer cung cấp 1 API duy nhất nhưng đảm nhiệm mọi công việc.
Mình sẽ đưa ra các ví dụ về việc sử dụng, gồm cả việc sử dụng trong React.setState và trong Reducer của Redux.
Ví dụ 1
Ví dụ đã hoàn thành :)
C:\Users\Administrator\Desktop\immer\src\App.js
import logo from './logo.svg';
import './App.css';
import produce from "immer";
const baseState = [{
todo: "Học TypeScript",
done: true
},
{
todo: "Dùng thử immer",
done: false
}
];
const nextState = produce(baseState, draftState => {
// draftState chính là 1 bản sao của baseState,
// ta có thể thay đổi draftState tùy ý mà không sợ ảnh hưởng đến baseState
draftState.push({ todo: "Post facebook khoe thành quả" })
draftState[1].done = true
// draftState[1] == baseState[1] => { todo: "Dùng thử immer", done: true }
});
console.log(nextState);
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
export default App;
import produce from "immer"
// baseState là 1 Array Object
const baseState = [
{
todo: "Học TypeScript",
done: true
},
{
todo: "Dùng thử immer",
done: false
}
]
// Giờ ta muốn thêm 1 phần tử vào Array này
const nextState = produce(baseState, draftState => { // draftState chính là 1 bản sao của baseState,
// ta có thể thay đổi draftState tùy ý mà không sợ ảnh hưởng đến baseState
draftState.push({todo: "Post facebook khoe thành quả"})
draftState[1].done = true // draftState[1] == baseState[1] => { todo: "Dùng thử immer", done: true }
})
import produce from "immer"
const byId = (state, action) =>
produce(state, draft => {
switch (action.type) {
case RECEIVE_PRODUCTS:
action.products.forEach(product => {
draft[product.id] = product
})
}
})
Ngay khi đọc qua ta có thể thấy, thay vì phải dùng spread operator của ES6 trong mỗi action, ta chỉ cần bọc toàn bộ action trong hàm produce của Immer và thay đổi trực tiếp giá trị trong draftState giúp cho reducer của ta vừa ngắn gọn, vừa dễ chỉnh sửa và vẫn đảm bảo tính bất biến
Lợi ích
Cá nhân mình thấy lợi ích đầu tiên của nó là API đơn giản, dễ đọc dễ hiểu, cú pháp ngắn gọn hơn các thư viện cùng mục đích (cụ thể là ImmutableJS).
Update dữ liệu nhanh hơn
Nhỏ gọn: Đóng gói và nén lại còn 2KB.
Vân vân và mây mây Các bạn muốn tìm hiểu nhiều hơn về thư viện này thì có thể vào đây để đọc.