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 里

相关推荐
大小胖虎2 小时前
数据结构——第六章:图
数据结构·笔记··最小生成树·拓扑排序·最短路径
maybe02092 小时前
前端表格数据导出Excel文件方法,列自适应宽度、增加合计、自定义文件名称
前端·javascript·excel·js·大前端
云上艺旅4 小时前
K8S学习之基础四十七:k8s中部署fluentd
学习·云原生·容器·kubernetes
锋小张4 小时前
a-date-picker 格式化日期格式 YYYY-MM-DD HH:mm:ss
前端·javascript·vue.js
kfepiza5 小时前
netplan是如何操控systemd-networkd的? 笔记250324
linux·网络·笔记·ubuntu
王小小海5 小时前
【笔记分享】nRF54H20 SPI速率范围记录
笔记·单片机·嵌入式硬件·嵌入式
鱼樱前端5 小时前
前端模块化开发标准全面解析--ESM获得绝杀
前端·javascript
yanlele5 小时前
前端面试第 75 期 - 前端质量问题专题(11 道题)
前端·javascript·面试
就是有点傻6 小时前
C#中Interlocked.Exchange的作用
java·javascript·c#
拉不动的猪6 小时前
刷刷题44(uniapp-中级)
前端·javascript·面试