lodash 学习笔记/使用心得
简单记一下 lodash 的一点学习笔记+使用心得,最近也是打算清理一下所有的 dead code,然后发现我们用了好多的 lodash 方法。对比了之前的写法,重新看了一下官方文档,再自己重新动手写了点 util 之后发现......
lodash 确实是怪好用的 😯 之前用法其实没能够使用到 lodash 最方便的地方,而且 lodash 对于 null/undefined 这种 falsy value 的支持很好,至少是一致的,这在大多数情况下可以很好的减少报错的问题。当然,用不好的话也有可能会造成 debug 的困难就是了......
我个人在复习这个代码之前其实不太喜欢用 lodash:
-
我写前端已经是 ES6 之后的事情了,很多方法 JS 本身就进行了实现
-
之前项目结构设计有问题,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 }
}
与另外单独分开,分别使用 forEach
和 Object.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 完全相反的操作
如:
javascriptconsole.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 里