lodash 学习笔记/使用心得

lodash 学习笔记/使用心得

简单记一下 lodash 的一点学习笔记+使用心得,最近也是打算清理一下所有的 dead code,然后发现我们用了好多的 lodash 方法。对比了之前的写法,重新看了一下官方文档,再自己重新动手写了点 util 之后发现......

lodash 确实是怪好用的 😯 之前用法其实没能够使用到 lodash 最方便的地方,而且 lodash 对于 null/undefined 这种 falsy value 的支持很好,至少是一致的,这在大多数情况下可以很好的减少报错的问题。当然,用不好的话也有可能会造成 debug 的困难就是了......

我个人在复习这个代码之前其实不太喜欢用 lodash:

  1. 我写前端已经是 ES6 之后的事情了,很多方法 JS 本身就进行了实现

  2. 之前项目结构设计有问题,lodash 被滥用的太厉害了,可读性很差

    主要原因还是因为页面渲染所需要的数据,是通过 aggregate 几个 model 而获得的。

    之前的设计基于 array 进行实现,在进行 CRUD 操作的时候,需要对数据进行大量的循环+拆分以获取正确的 model 及 id

    之后的重构考虑了这个问题,所以才去了 redux+基于 object 的实现,这样事情变得简单很多了------只需要找到正确的 id,直接从 redux state 中获取源数据进行 CRUD 操作即可。这样就排除了大量的循环和拆分问题。CRUD 的副作用则是通过 useMemo 进行管理,也不需要考虑原生数据的 index 问题

基本上来说,如果是使用比较新的技术栈------即有 webpack/vite 的 tree-shaking,那么直接使用 lodash 就好了,就是导入的方法需要稍微有些不同

比如说官网的代码是这样使用的:

javascript 复制代码
_.funcName();

然后就想当然的这么导入 lodash,或者是用代码的自动提示导入 lodash:

javascript 复制代码
import _ from "lodash";
import { funcName } from "lodash";

这样就会导入整个 lodash 的包,包括没有用到的方法,正确的方法应该是这么导入:

javascript 复制代码
import { funcName } from "lodash-es"; // 推荐 ✅,只有对应的 func 会被打包
import funcName from "lodash/funcName"; // tress-shaking 的支持不如第一种好

这样就可以导入合适的 es 模块,而不是导入整个包------原生代码使用 commonjs 实现,导出也是整个包

lodash 的一些常识

开始使用 lodash 前需要了解的一些基础知识,如果想要更好的理解文档,还是最好了解一下

iteratee

即循环迭代的 callback,lodash 原生提供好几种不同的 iteratee,如 forEach/map 常用的 iteratee 的 signature 为:iteratee(value, index|key, collection),这代表对于当前的 collection,lodash 所接受的参数,即:

  • array

    第一个参数为 value,第二个参数为 index,第三个参数为原本的 collection

  • object

    第一个参数为 value,第二个参数为 key,第三个参数为原本的 collection

但是 lodash 还支持一些 iteratee 缩写,如 _.match, _.property 这种,有些困惑的话看看下面的案例说不定会好一些......

另外就是,lodash 和 lodash/fp 对参数的处理是不太一样,所以支持的 iteratee 也会有些许的不同。具体还是要看文档,二者不是一对一转换的关系

identity

看到文档会经常看到 _.identity 这个用法,其实就是 _identity(value) => value 这个意思,也就是返还传禁区的参数

chain

chain 的一个前提就是需要了解,lodash 调用函数后,一定会有返回值。如过函数本身会对参数进行修改------如 _.forEach,那么返回的就是原来的参数;而如果函数本身会返回一个新的值------如 _.filter,那么返回的就是新的值

这也是我前面提到的 一致 的地方

chain 用的比较多的还是在使用 _ 的情况下,直接使用模块,搭配上 lodash 所有的函数都有返回值的特点,就可以减少创立新的变量,让代码看起来简洁一些。

如:

javascript 复制代码
const arr = [
  { name: "a", isActive: true, value: 2 },
  { name: "b", isActive: true, value: 3 },
  { name: "c", isActive: false, value: 10 },
  { name: "d", isActive: false, value: 6 },
];

arr.forEach((obj) => (obj.value = obj.value ** 2));
const filteredValue = arr.filter((obj) => obj.isActive);

javascript 复制代码
const arr2 = [
  { name: "a", isActive: true, value: 2 },
  { name: "b", isActive: true, value: 3 },
  { name: "c", isActive: false, value: 10 },
  { name: "d", isActive: false, value: 6 },
];

const filteredValue2 = _.chain(arr2)
  .forEach((obj) => (obj.value = obj.value ** 2))
  .filter((obj) => obj.isActive)
  .value();

从结果上是一样的,代码实现上来看,2 的写法比 1 要干净一点。chain 的方法越多,这个方法看起来就越明显一些

以下几个时关于 chain 的一些 💡:

  • chain 可以在 runtime 时提升性能------这个主要针对导入所有 lodash 代码的前提
  • 使用 lodash/es 的情况下,调用 chain 还是可能会导致引入没有使用过的包,所以性能不一定会有很好的提升
  • 想要性能的同时也写出类似的干净代码,可以考虑 lodash/fp 里的功能,不过那里的 api 调用会不太一样

空值的安全处理

这也是 lodash 另一个保持高度一致的地方,日常情况下,当在对空值进行方法调用的时候,会直接报错,如:

bash 复制代码
null.forEach()
     ^

TypeError: Cannot read properties of null (reading 'forEach')
    at Object.<anonymous> (/Users/GA/study/lodash/array_obj_.js:48:6)
    at Module._compile (node:internal/modules/cjs/loader:1554:14)
    at Object..js (node:internal/modules/cjs/loader:1706:10)
    at Module.load (node:internal/modules/cjs/loader:1289:32)
    at Function._load (node:internal/modules/cjs/loader:1108:12)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:170:5)
    at node:internal/main/run_main_module:36:49

所以很多时候在调用的时候都会加上 optional chaining,即 arr?.forEach(() => {})。在有些值嵌套比较深的情况下,就会比较的难受:data?.someVal?.someOtherVal?.forEach(() => {})

对比起来,lodash 处理的方法要稍微方便一些:

javascript 复制代码
_.forEach(undefined, () => {
  console.log("undefined");
});

这样的代码在 lodash 中不会抛出任何的异常

对 collection 的支持

当文档中出现 Collection,这就代表当前的函数可以同时用在 array 和 object 上,如:

javascript 复制代码
const obj = {
  1: { name: "array" },
  2: { name: "object" },
  3: { name: "undefined" },
  4: { name: "null" },
};

_.forEach(obj, (value, idx) => {
  console.log(value, idx);
});

运行结果如下:

bash 复制代码
❯ node array_obj_.js
{ name: 'array' } 1
{ name: 'object' } 2
{ name: 'undefined' } 3
{ name: 'null' } 4

具体的参数还是需要查看文档的,不过一般情况下,lodash 的参数与 iteratee 一致

array & objects

这是 lodash 比较好用的另一个地方,它提供了对 collection 的支持

需要注意的是,object 的迭代顺序是无法保证的

没有强调的情况下,lodash 中的 callback signature 为 iteratee

forEach 🌟🌟🌟🌟

大体用法如下:

javascript 复制代码
const _ = require("./lodash.min.js");

const arr = [1, 2, 3, 4, 5];
const obj = {
  1: { name: "array" },
  2: { name: "object" },
  3: { name: "undefined" },
  4: { name: "null" },
};

_.forEach(arr, (value, idx) => {
  console.log(value, idx);
});

_.forEach(obj, (value, key) => {
  console.log(value, key);
});

效果:

bash 复制代码
1 0
2 1
3 2
4 3
5 4
{ name: 'array' } 1
{ name: 'object' } 2
{ name: 'undefined' } 3
{ name: 'null' } 4

四颗星的原因是因为,针对我们这种项目实现,_.forEach 可以提供一个很好的 wrapper function。我们有一些与 structure/redux 相关的功能还没有完整地被重构,所以还是需要同时支持 array 和 object 的情况。但是用的不如 map 多,与 map 相比就只能是四星了

具体对比为:

javascript 复制代码
const iterateUtil = (collection) => {
  return _.forEach(collection, (_, identifier, original) => {
    console.log(original[identifier]);
  });
};

console.log(iterateUtil(arr));
console.log(iterateUtil(obj));

运行结果如下:

bash 复制代码
# array 相关的数据
❯ node array_obj_.js
{ name: 'array', id: 1 }
{ name: 'object', id: 2 }
{ name: 'undefined', id: 3 }
{ name: 'null', id: 4 }
# 这是 original value
[
  { name: 'array', id: 1 },
  { name: 'object', id: 2 },
  { name: 'undefined', id: 3 },
  { name: 'null', id: 4 }
]
# object 相关的数据
{ name: 'array', id: 1 }
{ name: 'object', id: 2 }
{ name: 'undefined', id: 3 }
{ name: 'null', id: 4 }
# 这是 original value
{
  '1': { name: 'array', id: 1 },
  '2': { name: 'object', id: 2 },
  '3': { name: 'undefined', id: 3 },
  '4': { name: 'null', id: 4 }
}

与另外单独分开,分别使用 forEachObject.entries 进行循环迭代:

javascript 复制代码
const iterateUtilNative = (collection) => {
  if (!collection) return;

  if (collection instanceof Array) {
    collection.forEach((val, i, original) => {
      console.log(original[i]);
    });
  } else if (typeof collection === "object") {
    // 这里的 original 其实是 object 转换成 array 之后的情况
    // 并不等同 collection
    Object.entries(collection).forEach(([key, val], index, original) => {
      console.log(collection[key]);
    });
  }

  return collection;
};

可以看到,逻辑处理起来还是比较复杂的,尤其是对 collection 的处理逻辑比较相似的情况下,代码重复量就会比较大

map 🌟🌟🌟🌟🌟

我们项目因为总是需要返回被处理过的值,所以 map 的使用率就特别高,这也是我在看了文档后发现,在我们项目中最被低估的函数之一

其中最基础的写法为:

javascript 复制代码
const square = (n) => n * n;

_.map([4, 8], square);
// => [16, 64]

_.map({ a: 4, b: 8 }, square);
// => [16, 64] (iteration order is not guaranteed)

// equivalent to
_.map({ a: 4, b: 8 }, (value, key, original) => value * value);

另一个之前漏掉的方法则是默认使用的 _.property 的省略用法:

javascript 复制代码
const users = [{ user: "barney" }, { user: "fred" }];

// The `_.property` iteratee shorthand.
_.map(users, "user");
// => ['barney', 'fred']

// equivalent to
_.map(users, (value, key, original) => _.get(value, "user"));

我们这个用的可太多了,原因之一就是我们的 api 用的是 elide json 的格式,也就是下面这样的格式:

json 复制代码
{
  "data": [
    {
      "type": "book",
      "id": "1",
      "attributes": {
        "title": "The Hobbit",
        "genre": "Fantasy"
      },
      "relationships": {
        "author": {
          "data": { "type": "author", "id": "42" }
        }
      }
    }
    // ...
  ],
  "included": [
    {
      "type": "author",
      "id": "42",
      "attributes": {
        "name": "J.R.R. Tolkien"
      }
    }
  ],
  "meta": {
    "page": {
      "totalRecords": 100,
      "limit": 25,
      "offset": 0
    }
  }
}

每一个真正的 data 下一定会包含这样几个属性:

  • attributes

    真正的 data property

  • type

    在我们这个使用范围里就是 api endpoint

  • id

  • relationships

    数据库定义的外链关系

我们把数据存在 redux 里的时候是要包含 attributes 和 relationships 的,但是将数据丢到 table 里,则只需要 attributes。二者对比起来:

javascript 复制代码
const memoData = _.map(data, "attributes");
// redux 里的数据是基于 id 进行 object-based 存储的
const memoData2 = Object.entries(data).map(([_, val]) => val.attributes);

从可读性来说,也是使用 lodash 的更高一些

filter 🌟🌟🌟

这个我用的比较少,因为直接有了 id,所以可以直接从 redux state 里面通过 O(1)的时间直接捞数据,再通过 relationship 找到对应数据的 id,所以现阶段我写的部分没什么 filter 的需求

这里就用 lodash 的官方案例了:

javascript 复制代码
var users = [
  { user: "barney", age: 36, active: true },
  { user: "fred", age: 40, active: false },
];

_.filter(users, function (o) {
  return !o.active;
});
// => objects for ['fred']

// The `_.matches` iteratee shorthand.
_.filter(users, { age: 36, active: true });
// => objects for ['barney']

// The `_.matchesProperty` iteratee shorthand.
_.filter(users, ["active", false]);
// => objects for ['fred']

// The `_.property` iteratee shorthand.
_.filter(users, "active");
// => objects for ['barney']

这里主要是一些 iteratee 的用法比较有意思,这里前两者都是找完全相等,需要注意的是第三个,也就是 _.property 的用法

这个的写法最终会返回一个 truthy/falsy value,在有些情况下会产生一些 false positive 的情况,如:

javascript 复制代码
var users = [
  { user: "barney", age: 36, active: true },
  { user: "fred", age: 40, active: false },
  { user: "jack", age: 40, active: "false" },
];

console.log(_.filter(users, "active"));

就会返回 barney 和 jack,因为 "false" 是一个 truthy value。在这种情况下,可以理解成 Boolean(_.get(object, key))。这个在使用这种 iteratee 缩写时需要注意的一些问题

一些相关联的方法还有:

  • _.reject

    这个就是和 filter 完全相反的操作

    如:

    javascript 复制代码
    console.log(
      _.filter(users, function (o) {
        return !o.active;
      })
    );
    
    console.log(
      _.reject(users, function (o) {
        return o.active;
      })
    );

    这两个结果时完全一致的,在有的时候求 否 的情况下,用 reject 更容易理解

  • without

    filter 和 reject 接受的是 predicate,without 直接接受值,这就是最大的区别

    javascript 复制代码
    _.without([1, 2, 1, 3], 1);
    // => [2, 3]
    _.without([1, 2, 1, 3], 1, 2);
    // => [3]

    ⚠️:这个操作对由复杂结构(object, map, etc)组成的数组不管用,它底层还是执行 !== 这个比较,所以智能排除引用相同的值

  • removes

    返回的结果和 filter 一样,但是区别在于,remove 修改的是原数组,而 filter 返回一个新的数组------mutation 的差别

find 🌟🌟

用法与 filter 相似,同样是可以接受好几种 iteratee

这个我们曾经相对用的多一些,主要之前数据是基于 array 实现的,又有 data mapping------map by relationships 的需求。但是在换成 TS+redux 之后用的就比较少了,毕竟 TS 的 intellisense 的提示其实好多了,而且也能够有效避免 typo

需要注意的是,_.find 只会返回第一个找到的数据,同时其他类似的函数还有 _.findIndex, _.findLastIndex_.findLast

使用 find 的对比大体如下:

javascript 复制代码
_.find(data, ["relationships.author.data.id", "43"]);

data.find((obj) => obj.relationships?.author?.data?.id === "43");

可以看到,其实并没有优化很多

如果需要同时对 object 和 array 支持的话,find 会有用些。不过之前已经提到过了,在我们的实现中,只有在 useMemo 中才需要 map relationship 去集成一个新的 model。这个时候我们会专门新加一个 key 作为 refRelationship 的 key,这样就可以在进行 crud 的操作时,直接获得这个 foreign key,从而避免使用 find 造成的 O(n) 的时间损耗

而如果是其他的 toC 端项目,用 TS 的提示更好,不用 TS 其实也没有差很多......毕竟 lodash 中也是需要提供完整的路径,没有办法做部分匹配

every & some 🌟

用法与之前提到的 collection 差不多,在我的经验中其实相对而言用的还是比较少的,毕竟一般情况下还是会使用一个 forEach 或者 map 去对数据进行操作的同时,再检查某些条件

sortBy 🌟🌟🌟

这个用法比起原生实现的 sort 要简单很多,参考下面的数据:

javascript 复制代码
const users = [
  { user: "alice", age: 25 },
  { user: "bob", age: 32 },
  { user: "claire", age: 41 },
  { user: "david", age: 28 },
  { user: "elena", age: 37 },
  { user: "frank", age: 46 },
  { user: "grace", age: 30 },
  { user: "henry", age: 52 },
  { user: "isla", age: 22 },
  { user: "jack", age: 39 },
];

原生实现如下:

javascript 复制代码
users.sort((a, b) => {
  if (a.user < b.user) return -1;
  if (a.user > b.user) return 1;
  return a.age - b.age;
});

可以看到逻辑不是绝对意义上的清晰,而且随着循环的条件变多,if/else 也是会顺势增加。对比使用 lodash:

javascript 复制代码
// 第二个参数可省略,如果默认 asc 排序的话
_.sortBy(users, ["user", "age"], ["asc", "desc"]);

// 或者传一个 cb 进去排序
_.sortBy(users, [
  function (o) {
    return o.user;
  },
]);

这个评价不高主要还是因为我们 customized sorting 的功能做的不多,主要是依靠 react-table 对数据表单进行排序

这个函数也是 collection 可用,返回的是一个数组而已

groupBy 🌟🌟🌟

这个是我们用的比较多的方法,还是因为 react-teble 可以接受 subRows 这个参数,而我们在不少的数据当中是需要用 subRows 去展示数据的

另外一方面就是在做 data form 的时候,有的时候需要按照 section 进行排列。当表单比较小的时候,直接在不同的页面中写死也不是不行,不过我们表单确实挺多的,而且具体的 fields 也多,所以最终还是用 map + groupBy 去实现

以下面的数据为例------这个需求是满足了我们数据的需求------基于 A 进行 groupBy,同时基于 B 进行排序:

javascript 复制代码
const users = [
  { firstName: "alice", lastName: "smith", age: 25 },
  { firstName: "bob", lastName: "johnson", age: 32 },
  { firstName: "claire", lastName: "smith", age: 41 },
  { firstName: "david", lastName: "johnson", age: 28 },
  { firstName: "elena", lastName: "smith", age: 37 },
  { firstName: "frank", lastName: "brown", age: 46 },
  { firstName: "grace", lastName: "brown", age: 30 },
];

这里的条件以 lastName 进行 group,同时以年龄进行排序,我们现在实现的方法大体是这样的:

javascript 复制代码
const familyMap = new Map();

data.forEach((person) => {
  const key = person.lastName;

  if (!familyMap.has(key)) {
    familyMap.set(key, {
      parent: person,
      children: [],
    });
  } else {
    const group = familyMap.get(key);

    if (person.age > group.parent.age) {
      group.children.push(group.parent);
      group.parent = person;
    } else {
      group.children.push(person);
    }
  }
});

return Array.from(familyMap.values()).map((group) => ({
  ...group.parent,
  subRows: group.children.sort((a, b) => b.age - a.age),
}));

但是使用 lodash,代码不仅简单多了,并且可读性也高了很多:

javascript 复制代码
_(users)
  .groupBy("lastName")
  .map((group) => {
    const sorted = _.orderBy(group, "age", "desc");
    const [parent, ...children] = sorted;
    return { ...parent, subRows: children };
  })
  .value();

认真来说,groupBy + map 这个搭配绝对是 4🌟,甚至是 5🌟 的

获取部分 array 🌟🌟

这里主要就是介绍一下常使用的几个,下面的函数只能在 array 里用:

  • slice

    和正常的 Array.prototype.slice 用法没什么差别

  • head

    只取第一个元素

  • tail

    取除了第一个以外的其他元素

  • last

    只取最后一个元素

  • initial

    取除了最后一个以外的其他元素

  • take

    取前 n 个元素

  • drop

    丢下前 n 个元素

  • chunk

    可以把数组分割成每个数组包含 n 个元素的 2 维数组

大体可以理解成 slice 的变形,不过可以少传点参数

我记得我们项目里只用到了 head,这也是之前搭配 lodash 的 groupBy 实现的,不过后面用 spread operator 取代了,就只有一些还没有完全重构的代码在使用这个了

string

接下来就是一些字符串相关的处理了

这部分整体其实不算多,因为原生 JS 的实现是够用了,所以一般就用原生的函数,提示也更好一些

不同的格式 🌟🌟🌟

包含:

  • toLower,小写
  • toUpper,大写
  • camelCase,驼峰
  • kebabCase,a-b 这种
  • snakeCase,常见的 python 格式
  • startCase,有点像 title case,就是常见的标题类
  • capitalize,大写第一个字母

我们项目里用的其实不能算少,主要是因为内部使用的 UI 库的命名规范为 kebabCase,所以为了测试队伍 debug 起来更方便一些,我们内部添加 id 的时候也会转成 kebabCase

还有就是用 humanize(人名),之前用的是另一个库,不过那个 outdated 了,就直接简单的实现了一下: startCase(toLower(str)),这个不是严格正确的,因为 startCase 只负责将每个单词前的首字母转成大写字母......不过具体实现就看具体需求了,这种情况下我还是觉得用 regex 会稍微简单些 除了写 regex 很难之外

truncate 🌟🌟

有业务场景但是不多,主要用在 notifications system,把提示信息控制在 50 个字符上,多余的就用 ... 代替

pad & trim 🌟🌟

用的不算多,毕竟主要是增删空格让排序看的好看些,所以原生 JS 就够用了

但是话又说回来,如果需要删除首位特定字符的话,lodash 的 trim 就很好用了

join & split 🌟

已经用原生的 JS 实现了,这个可能最好用的还是在直接接触 DOM,需要对 array-like 的数据进行操作的情况下比较有用

我们的项目不需要操作这种数据格式就是了

其他

一些不太常见但是还是挺好用的方法

clone & cloneDeep 🌟🌟🌟🌟🌟

clone 用的不多,已经被 spread operator 取代了,但是 cloneDeep 的业务场景太广泛了......

基本上所有的 setState 的 setter 都会调用一次 cloneDeep 取进行深拷贝,之前是刚刚遇到了一个因为直接从当前的 state 中拿数据,而不是用 setter 中的 prev state 进行更新的 bug......这破玩意儿还是同事写的,查了很久的 bug 才发现才是这个破问题......

为啥都不喜欢从 setter 中拿 prevState 进行更新啊......这还真是令人困惑,每次在 code review 中都提,但是每次都不改......拿 code review 的意义是什么啊......真不明白......

pick 🌟🌟🌟🌟

这个用的比较多,主要是过滤对象里面多余的值,比如说后端数据库会自动生成/更新 createdAt, createdBy, updatedBy, updatedAt 这种 timestamp,或者是 version 等不是不重要,但是于 UI 来说关联不大的数据

需要注意,参数是 [paths],也就是 array 的数据格式,我们现在有设置一些 default values,然后用 Object.keys() 作为 pick 的参数

type check 🌟🌟🌟

这种类型检查其使用的还是挺多的,主要还是因为 typeof null 会出现 object 这种奇怪的问题,不然的话使用频率也不会特别高

比较常用的是:

  • isNil

    这个应该是用的最多的,因为它的判断是 value !== undefined && value !== null,是一个比较好用且完整的空值判断

  • isString

    这个其实出乎意料的用的还蛮多的,因为我同事转换数字和字符串的时候不太喜欢用 toString,觉得这样会有空值报错的风险。所以会混用 new String()String(),后者的问题其实不大,但是前者返回值就成了 wrapper class,也就变成了 object......

  • isArray

    无他,稍微短一些

    Array.isArray() 也可以

  • isPlainObject

    等同于 typeof value === 'object' && value !== null

  • isEmpty

    这个主要是用来判断 collection 的,也就是 array、object,如果长度为 0 的话就会返回 false

    除此之外,primitive 也会返回 false

  • isEqual

    主要用来判断修改前和修改后的数据,因为其中一个需求是展示所有不同的数据,所以这个用法是需要搭配 map & filter 去实现的

  • isMatch

    很少用,业务需求主要还是在找不同上,在 UI 给用户一个比较直观的结果,所以用 isEqual 搭配其他的方法更多

  • isFunction

    很少用,只有在一些第三方的库中取值时需要做判断,一般放在 util 里的

    这种情况就是判断返回的是 value,还是 function,后者是内部库进行的封装,我们这里从对方提供的回调函数中拿值

    稍微短一些,用 typeof 也可以

  • isNull

    很少用,一般直接 !== null 判断了

  • isUndefined

    很少用,一般直接 !== undefined 判断了

补充一下,如果想要判断是不是 falsy value,即空字符串、undefined、null、NaN、0 这种情况,可以用 double negate 去判断,即 !!value

debounce & throttle 🌟🌟🌟

这是两个用得到就很重要,用不到就在意不到的方法,用法大体如下:

javascript 复制代码
const someFn = () => {};

const debouncedFn = _.debounce(fn, time);
const throttledFn = _.throttle(fn, time);

// 取消所有未执行的操作,React里面可以放在 useEffect 中的 return
debouncedFn.cancel();
throttledFn.cancel();

// 如果想要立即执行未执行的操作
debouncedFn.flush();
throttledFn.flush();

random 🌟

这个看用的需求是什么了,lodash 提供的比较简单,参数是 _.random([lower=0], [upper=1], [floating]),即下限、上限和是否准许浮点数,它的使用会比用原生 js 的 random 要简单些

我们之前也考虑过用 random+timestamp 去做随机的 id,但是这个伪随机生成的还是会出现重复。后面还是用反转的 index 了,这样至少能够保证 id 唯一

uniqueId 🌟

有优点也有缺点,有点在于全局导入 lodash 的时候,它的 randomId 是存储在根文件下的,确实可以保证这个 id 是唯一的。缺点就在于全局导入 lodash 会打包很多不需要的函数,增加 bundle size

如果想要一个基本可以保证独特的 id,可以考虑用 cryto 里面的 randomBytes() 或者 randomUUID()。这两个方法在生成小规模的随机 id,一般不太会出现数据冲突的情况,而且 randomUUID 也是后端可以直接用的值

flatten 🌟

在重构之前用过,重构之后数据结构相对扁平化了就没需求了,这个主要可以把嵌套的 array 扁平化

比较新的版本------ES2019 之后 Array 也有内置的 flat 实现,所以确实用的不多

compact 🌟

去除数组中所有 falsy 的值,大概用过一次......

assign 🌟

用的比较少,我们基本上都用 spread operator 代替了

mixin 🌟

提一下,用 mixin 可以绑定 custom function,有需求可以用,但是我觉得大多数情况下是用不到的......

我们会更多的写一些不同的 util function,而不是把所有东西丢到 lodash 里

相关推荐
小满Autumn2 小时前
log4net 日志框架 — 从配置到实战速查手册
笔记·c#·.net·wpf·上位机·log4net
老毛肚5 小时前
jeecg-boot-base-core 02 day
javascript·python
袁小皮皮不皮9 小时前
1.HCIP BFD 学习笔记(优化版)
服务器·网络·笔记·网络协议·学习·智能路由器·ip
装不满的克莱因瓶9 小时前
【自动驾驶领域】学习 Cityscapes 数据集——城市街景语义理解的标准基准
人工智能·pytorch·python·深度学习·学习·机器学习·自动驾驶
清辞85310 小时前
产品经理需求推进流程
大数据·深度学习·学习·产品经理
烬羽10 小时前
后端返回的 JSON 字符串,浏览器怎么"看懂"的?——Ajax 全链路拆解
javascript
YM52e10 小时前
鸿蒙PC ArkTS 声明合并问题深度解析与最佳实践
学习·华为·harmonyos·鸿蒙·鸿蒙系统
半个落月11 小时前
一个新手用 Bun + Axios 调通 DeepSeek API 的实践记录
javascript
不好听61311 小时前
深入理解链表:线性数据结构的另一面
javascript·数据结构
林希_Rachel_傻希希11 小时前
学React治好了我的焦虑症,1小时速通React 前20分钟。
前端·javascript·面试