重构利器:如何用 Immer 高雅地办理运用状况

1. immer

immer 是一个 JavaScript 库,用于处理不行变数据的状况更新。不行变数据意味着一旦创立,数据结构就不能被修正。在编写杂乱的运用程序时,不行变功能够带来一系列好处,比方更简略追踪数据的改动、更简略完成吊销/重做功用以及更简略的状况办理。

可是,处理不行变数据结构一般需求编写很多的样板代码,尤其是在更新嵌套结构时。immer 库经过供给一个简略的可变(mutable)API 来更新不行变(immutable)状况,然后简化了这一过程。

immer 的核心概念是“草稿状况”(draft state)。这个库答应你编写看似可变的代码来更新一个不变的状况,但实际上,你是在修正的一个暂时的草稿状况。一旦修正完成,immer 会依据这个草稿状况产生一个新的不行变状况。

2. 诞生背景

immer 的诞生背景主要与在 JavaScript 运用中办理杂乱的不行变状况有关。在 React 和 Redux 等现代前端结构和库中,不行变数据的概念非常重要,因为它协助开发者防止直接修正状况,然后防止潜在的副作用和功能问题。不行变数据还使得状况的变化更加可猜测,便于跟踪和调试,一起也简化了杂乱的功用如时间旅行(time travel debugging)和状况快照的完成。

React 的 setState 办法和 Redux 的 reducer 函数都要求你回来一个新的状况方针,而不是修正原有的状况。当状况结构简略时,这一般不是问题,但当运用逐渐变得杂乱,状况结构也变得更加嵌套时,更新状况会变得越来越繁琐。例如,假如你需求更新一个嵌套的数组或方针,你需求逐层克隆一切父级方针,直到到达你想要修正的特点。这不只编写起来杂乱,并且简略犯错。

为了处理这个问题,Michel Weststrate(也是 MobX 库的作者)创立了 immer。他的方针是创立一个库,它能够让你以一种更自然和声明式的方法更新不行变状况,一起防止手动处理杂乱的方针和数组克隆操作。经过 immer,你能够继续编写简略直观的可变代码,可是产生的结果是依照不变性准则处理的,这样就既坚持了代码的简练,也保证了状况的不行变性。

简而言之,immer 的诞生背景是为了处理在处理杂乱不行变状况时的编码杂乱性问题,一起协助开发者防止常见的不行变数据操作过错,进步代码的可保护性和可读性。

3. immer 方针克隆算法的简略完成

重构利器:如何用 Immer 高雅地办理运用状况

immer 库运用了一种称为结构同享的技术来完成方针克隆,这种技术能够有效地创立新的不行变状况,一起尽或许地重用旧状况中未修正的部分。下面是一个简化版的 immer 克隆算法的完成,用于展现其基本原理:

function createDraft(initialState) {
  // 遍历方针,递归地将其特点转化为可写。
  function createProxy(state) {
    if (typeof state === "object" && state !== null) {
      return new Proxy(state, {
        get(target, prop) {
          if (prop === "isDraft") return true;
          return createProxy(target[prop]);
        },
        set(target, prop, value) {
          target[prop] = value;
          return true;
        },
      });
    }
    return state;
  }
  return createProxy(initialState);
}
function finishDraft(draft) {
  // 遍历方针的草稿版别,并递归地生成终究的不行变状况。
  function finalize(state) {
    if (typeof state === "object" && state !== null && state.isDraft) {
      const finalState = Array.isArray(state) ? [] : {};
      for (const key in state) {
        finalState[key] = finalize(state[key]);
      }
      return finalState;
    }
    return state;
  }
  return finalize(draft);
}
function produce(baseState, producer) {
  // 创立草稿状况
  const draft = createDraft(baseState);
  // 履行修正器函数
  producer(draft);
  // 回来终究的不行变状况
  return finishDraft(draft);
}
// 运用示例
const baseState = {
  a: {
    b: 1,
    c: [2, 3],
  },
};
const nextState = produce(baseState, (draftState) => {
  draftState.a.b = 99;
  draftState.a.c.push(4);
});
console.log(baseState); // 输出原始状况,未被修正
console.log(nextState); // 输出已修正的新状况

上面的代码是一个简化版别的 immer 完成,它运用 Proxy 来阻拦对草稿状况的一切操作。createDraft 函数创立一个署理,该署理使得对原始状况的任何修正都会被捕获。finishDraft 函数遍历草稿状况,并为任何修正过的部分创立新的不行变方针。

请留意,这个简略完成并不包括 immer 自身的完好特性集合。例如,它不会处理方针引证的问题,也不会优化未修正特点的结构同享。此外,本完成中,一旦您对草稿方针进行操作,一切的嵌套方针都会变成一个新方针,而 immer 会尽或许地重用旧状况中未修正的部分。

immer 库自身的完成要杂乱得多,并且有很多优化,以保证功能并处理更多边际情况。假如你打算在出产环境中运用不行变数据更新,主张直接运用 immer 库,而不是自己完成。

4. 装置运用

要运用 immer 库,你首要需求将其装置到你的项目中。你能够运用 npm 或 yarn 来装置 immer

假如你运用 npm,请在你的项目根目录的命令行中运转以下命令:

npm install immer

假如你运用 yarn,请运转:

yarn add immer

一旦 immer 装置完成,你就能够在你的项目中导入并开始运用它了。下面是一个基本的运用示例:

// 引进 immer 库
import produce from "immer";
// 假设你有一个初始状况方针
const initialState = {
  user: {
    name: "Alice",
    age: 25,
    address: {
      city: "Wonderland",
    },
  },
  tasks: [],
};
// 你想要依据这个初始状况进行一些修正
const nextState = produce(initialState, (draftState) => {
  // 直接对草稿状况进行修正
  draftState.user.age = 26;
  draftState.tasks.push("Learn immer");
  // No need to return anything, immer will take care of the rest
});
// 现在,initialState 坚持不变,nextState 是更新后的状况
console.log(initialState); // 未修正的初始状况
console.log(nextState); // 更新后的新状况

在上面的代码中,咱们运用 produce 函数来获得状况的草稿版别并进行修正。在 produce 函数的第一个参数中,咱们传入了咱们想要修正的初始状况。第二个参数是一个函数,它接收一个参数 draftState,这是初始状况的草稿版别。在这个函数体内,咱们能够对 draftState 进行修正,好像它是可变的一样。一旦函数履行完成,produce 会依据咱们对草稿所做的修正生成一个新的不行变状况,并将其作为回来值。

值得留意的是,在运用 immer 时,你不需求回来新的状况 —— immer 会主动处理这一切。这便是为什么在上面的示例中,对 draftState 的修正没有明确的 return 语句。

4.1 React 结合

结合 React 运用 immer 能够极大地简化状况更新逻辑,尤其是当你的组件状况比较杂乱、层次比较深时。以下是一个简略的例子,展现了如安在一个 React 函数组件中运用 immer 进行状况办理。

然后在你的 React 组件中导入 useReducer 钩子和 immerproduce 函数:

import React, { useReducer } from "react";
import produce from "immer";
// 假设咱们的组件有一个初始状况
const initialState = {
  count: 0,
  user: {
    name: "Alice",
    favorites: ["coding", "reading"],
  },
};
// 运用 immer 创立 reducer 函数
const reducer = produce((draft, action) => {
  switch (action.type) {
    case "increment":
      draft.count += 1;
      break;
    case "addFavorite":
      draft.user.favorites.push(action.payload);
      break;
    // 更多的 action 处理...
    default:
    // 留意:在这里不需求 break,因为咱们没有修正草稿状况
  }
});
// React 函数组件
function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>Increment</button>
      <p>Favorites: {state.user.favorites.join(", ")}</p>
      <button
        onClick={() => dispatch({ type: "addFavorite", payload: "skating" })}
      >
        Add Favorite
      </button>
    </div>
  );
}
export default Counter;

在上面的示例中,咱们创立了一个运用 immerreducer 函数,它处理两种动作:增加计数器和添加一个新的喜好项。在 reducer 函数中,咱们直接对 draft 状况进行修正,而不需求担心会改动原始状况或许进行杂乱的深复制操作。

useReducer 钩子运用咱们的 reducer 函数和初始状况,并回来当前的状况和一个 dispatch 函数,咱们能够用它来触发状况更新。

在组件中,咱们运用按钮点击事情来触发不同的动作,这些动作将被 reducer 处理,然后更新状况。因为 immer 保证了状况的不行变性,这使得在 React 中运用 immer 非常合适,因为不行变状况是 React 优化烘托功能的关键

结合 React 运用 immer 能够让你专注于状况更新的逻辑,而不是状况更新的机制,简化了代码并削减了犯错的或许性。

5. 不行变库对比

不行变数据库是在 JavaScript 开发中办理状况的流行挑选之一。这些库经过供给不行变数据结构来协助开发者编写更可猜测、更简略保护的代码。以下是一些较为知名的不行变数据库,依照它们的知名度和运用情况排序:

  1. Immutable.js

    • 合适的场景:大规划的运用程序,或许对功能有较高要求的项目,特别是当涉及到杂乱的状况办理和频频的状况更新时。
    • 长处:供给丰厚的不行变数据结构(如 List、Map、Set 等);高效的数据更新和读取操作;良好的功能,尤其是在大型数据集上。
    • 下风:有较大的学习曲线,因为它有自己的 API 风格;与普通的 JavaScript 方针不直接兼容,有时需求转换;或许会导致较大的打包体积。
  2. Immer

    • 合适的场景:需求易用性和较少的学习曲线的项目,尤其是在 React 运用程序中办理状况时。
    • 长处:简化不行变数据的操作;直观的 API 规划;与普通的 JavaScript 方针操作相似;能够与现有的库和结构无缝集成;针对功能做了优化。
    • 下风:运用 Proxy,因此在旧版浏览器中不受支撑,且在某些情况下或许会有功能开支。
  3. Mori

    • 合适的场景:函数式编程风格的项目,或许期望运用 ClojureScript 风格数据结构的 JavaScript 开发者。
    • 长处:供给丰厚的不行变数据结构,类似于 Clojure 的持久化数据结构;支撑函数式编程范式;功能优秀。
    • 下风:与 Immutable.js 类似,有自己的 API,需求学习;与 JavaScript 原生方针不直接兼容。
  4. seamless-immutable

    • 合适的场景:适用于需求轻量级不行变数据处理方案的运用,不愿意引进 Immutable.js 或其他重型库的项目。
    • 长处:简略易用;API 规划类似于原生 JavaScript;较小的打包体积。
    • 下风:不供给 Immutable.js 那样丰厚的数据结构和办法;关于大型数据集或杂乱操作,功能或许不如专门的不行变数据库。

在挑选合适的不行变数据库时,你需求考虑几个关键因素,比方运用程序的规划、状况办理的杂乱性、开发团队的偏好、以及与其他库或结构的集成需求。关于功能的考量,一般主张在实际的运用场景中进行基准测验,以确认哪个库最合适你的需求。一起,也要衡量库的巨细和学习曲线对项目的整体影响。

6. 功能对比

重构利器:如何用 Immer 高雅地办理运用状况

7.你需求么

Immer 的核心价值在于简化不行变状况的更新,这个特点并不只限于 React 运用。在其他前端结构中,如 Vue.js、Angular 或甚至原生 JavaScript 项目,也能够从 Immer 供给的简练的状况办理方法中受益。以下是一些合适引进 Immer 的场景,这些场景适用于多种前端结构和上下文:

  1. 杂乱的状况结构:关于那些具有杂乱或深层嵌套的状况方针的运用程序,Immer 能够简化修正这些方针的逻辑,然后防止手动处理深复制和不行变性。

  2. 全局状况办理:在运用 Vuex(Vue.js)、NgRx(Angular)或其他全局状况办理库时,Immer 能够简化状况更新的 reducer 逻辑,使其更加可读和易于保护。

  3. 大型或中型运用:在较大的运用中,状况办理往往变得杂乱且简略犯错。Immer 经过主动处理状况不行变性,有助于下降犯错率和进步代码的清晰度。

  4. 团队项目:在多人协作的项目中,保证每个开发者都正确地处理状况不行变性或许是一个挑战。运用 Immer 能够削减这种心智担负,并协助团队成员遵从最佳实践。

  5. 学习曲线和开发速度:假如项目团队更习惯于可变的编程方法,引进 Immer 能够削减学习新的不行变数据操作办法的时间,然后加速开发速度。

  6. 功能考虑:虽然 Immer 运用 Proxy 或许有功能开支,但关于大多数运用来说,这种开支是能够接受的。在需求功能优化的当地,Immer 的结构同享能够削减不必要的数据复制和烘托。

  7. 现有代码库的重构:假如你正在重构或增强现有项目,而该项目中已经有很多的直接状况修正代码,引进 Immer 能够协助你逐渐迁移到不行变模式,而无需大幅度重写现有逻辑。

  8. 测验和可猜测性:运用 Immer 能够进步运用程序状况的可猜测性,这在编写测验和调试时特别有用,因为你能够确认状况只会以你经过 Immer 指定的方法改动。

总而言之,任何涉及杂乱状况逻辑、需求增强代码可读性和可保护性、或许期望简化状况更新逻辑的 JavaScript 运用程序,都能够考虑引进 Immer。无论你是运用 React、Vue.js、Angular 或其他结构,Immer 的规划理念和优势都是普遍适用的。如何决议是否引进 Immer,取决于项目的详细需求、团队偏好以及功能要求。


微信搜索“好朋友乐平”重视大众号。

github原文地址