DEV Community

mikebui
mikebui

Posted on

Giới thiệu về Immer - Phần 4

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>)
}
Enter fullscreen mode Exit fullscreen mode

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 produceproduce 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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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
    }
})
Enter fullscreen mode Exit fullscreen mode

Top comments (0)