Giới thiệu về Immer - Phần 4
mikebui
Posted on May 10, 2022
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.
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
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 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
}
})
Posted on May 10, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.