immer: 不可变对象

一个可以提升性能的小工具
更新于: 2024-01-09 08:39:52

安装

# 原生 immer / 还有一个 hook(并不是必须的)
yarn add immer use-immer

使用 userState + immer

引用这一句: import { produce } from "immer";

import React, { 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 = (id) => {
    setTodos(
      produce((draft) => {
        const todo = draft.find((todo) => todo.id === id);
        todo.done = !todo.done;
      })
    );
  };

  const handleAdd = () => {
    setTodos(
      produce((draft) => {
        draft.push({
          id: 'todo_' + Math.random(),
          title: 'A new todo',
          done: false
        });
      })
    );
  };

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li
            key={todo.id}
            onClick={() => handleToggle(todo.id)}
            style={{
              textDecoration: todo.done ? 'line-through' : ''
            }}>
            {todo.title}
          </li>
        ))}
      </ul>
      <button onClick={handleAdd}>Add Todo</button>
    </div>
  );
};

export default TodoList;

使用 useImmer

初始化 useImmer,并传入数据

const [todos, setTodos] = useImmer([
  {
    id: "React",
    title: "Learn React",
    done: true,
  },
  {
    id: "Immer",
    title: "Try Immer",
    done: false,
  },
]);

更新 todos

const handleToggle = (id) => {
  setTodos((draft) => {
    const todo = draft.find((todo) => todo.id === id);
    todo.done = !todo.done;
  });
};

完整代码如下

import React, { useCallback } from 'react';
import { useImmer } from 'use-immer';

const TodoList = () => {
  const initialState = [
    { id: 'React', title: 'Learn React', done: true },
    { id: 'Immer', title: 'Try Immer', done: false }
  ];

  const [todos, setTodos] = useImmer(initialState);
  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: 'TodoList',
        title: 'Try TodoList',
        done: false
      });
    });
  }, []);

  return (
    <div>
      <ul>
        {todos.map((todo) => (
          <li
            key={todo.id}
            onClick={() => handleToggle(todo.id)}
            style={{
              textDecoration: todo.done ? 'line-through' : ''
            }}>
            {todo.title}
          </li>
        ))}
      </ul>
      <button onClick={handleAdd}>Add Todo</button>
    </div>
  );
};

export default TodoList;

使用 userReducer + immer

import { useReducer } from 'react';
import { produce } from 'immer';

const TodoList = () => {
  const initialState = [
    { id: 1, text: 'Learn React', completed: false },
    { id: 2, text: 'Learn Redux', completed: false }
  ];
  const reducer = (state, action) => {
    switch (action.type) {
      case 'ADD_TODO':
        return produce(state, (draftState) => {
          draftState.push(action.payload);
        });
      case 'TOGGLE_TODO':
        return produce(state, (draftState) => {
          const todo = draftState.find((todo, index) => index === action.payload);
          todo.completed = !todo.completed;
        });
      default:
        return state;
    }
  };

  const [todos, dispatch] = useReducer(reducer, initialState);

  const toggleTodo = (index) => dispatch({ type: 'TOGGLE_TODO', payload: index });
  const handleAdd = () => {
    dispatch({
      type: 'ADD_TODO',
      payload: {
        id: 3,
        text: 'Learn Immer',
        completed: false
      }
    });
  };

  return (
    <div>
      <ul>
        {todos.map((todo, index) => (
          <li
            key={index}
            style={{
              textDecoration: todo.completed ? 'line-through' : ''
            }}
            onClick={() => {
              toggleTodo(index);
            }}>
            {todo.text}
          </li>
        ))}
      </ul>
      <button onClick={handleAdd}>Add Todo</button>
    </div>
  );
};

export default TodoList;

使用 userImmerReducer

import { useImmerReducer } from 'use-immer';

const TodoList = () => {
  const initialState = [
    { text: 'Learn Hooks', completed: false },
    { text: 'Learn Redux', completed: false }
  ];

  const reducer = (state, action) => {
    switch (action.type) {
      case 'add-todo':
        state.push({ text: action.payload, completed: false });
        break;
      case 'toggle-todo':
        state[action.payload].completed = !state[action.payload].completed;
        break;
      default:
        break;
    }
  };

  // useImmmerReducer
  const [todos, dispatch] = useImmerReducer(reducer, initialState);
  const handleAdd = () => dispatch({ type: 'add-todo', payload: 'new todo' });
  const toggleTodo = (index) => dispatch({ type: 'toggle-todo', payload: index });

  return (
    <div>
      <ul>
        {todos.map((todo, index) => (
          <li
            key={index}
            style={{ textDecoration: todo.completed ? 'line-through' : '' }}
            onClick={() => toggleTodo(index)}>
            {todo.text}
          </li>
        ))}
      </ul>
      <button onClick={handleAdd}>Add Todo</button>
    </div>
  );
};

export default TodoList;

参考