以前我没得选,现在只想用 Array.reduce(下)

简介: 第一眼看 Array.reduce 这个函数总感觉怪怪的,用法也得花几分种才弄懂,懂了之后也不知道应用场景是啥。最近写项目的时候才慢慢对这个函数有更多的理解,可以算是 Array 类型下最强大的函数之一了。

normalize


在写 redux 的时候,我们有时可能会需要将数组进行 Normalization,比如

[{id: 1, value:1}, {id: 2, value: 2}]
=>
{
  1: {id: 1, value: 1}
  2: {id: 2, value: 2}
}
复制代码


使用 reduce 可以先给个初始值 {} 来存放,然后每次只需要将 id => object 就可以了:

export type TUser = {
  id: number;
  name: string;
  age: number;
}
type TUserEntities = {[key: string]: TUser}
function normalize(array: TUser[]) {
  return array.reduce((prev: TUserEntities, curt) => {
    prev[curt.id] = curt
    return prev
  }, {})
}
export default normalize
复制代码


用例

describe('normalize', () => {
  it('可以 normalize user list', () => {
    const users: TUser[] = [
      {id: 1, name: 'Jack', age: 11},
      {id: 2, name: 'Mary', age: 12},
      {id: 3, name: 'Nancy', age: 13}
    ]
    const result = normalize(users)
    expect(result).toEqual({
      1: users[0],
      2: users[1],
      3: users[2]
    })
  })
})
复制代码


assign


上面例子也只是对 number 做相加,那对象“相加”呢?那就是 Object.assign 嘛,所以用 reduce 去做对象相加也很容易:


function assign<T>(origin: T, ...partial: Partial<T>[]): T {
  const combinedPartial = partial.reduce((prev, curt) => {
    return { ...prev, ...curt }
  })
  return { ...origin, ...combinedPartial }
}
export default assign
复制代码


用例

describe('assign', () => {
  it('可以合并多个对象', () => {
    const origin: TItem = {
      id: 1,
      name: 'Jack',
      age: 12
    }
    const changeId = { id: 2 }
    const changeName = { name: 'Nancy' }
    const changeAge = { age: 13 }
    const result = assign<TItem>(origin, changeId, changeName, changeAge)
    expect(result).toEqual({
      id: 2,
      name: 'Nancy',
      age: 13
    })
  })
})
复制代码


虽然这有点脱裤子放屁,但是如果将数组的里的每个对象都看成 [middleState, middleState, middleState, ...],初始值看成 prevState,最终生成结果看成成 nextState,是不是很眼熟?那不就是 redux 里的 reducer 嘛,其实也是 reducer 名字的由来。


concat


说了对象“相加”,数组“相加”也简单:

function concat<T> (arrayList: T[][]): T[] {
  return arrayList.reduce((prev, curt) => {
    return prev.concat(curt)
  }, [])
}
export default concat
复制代码


用例

describe('concat', () => {
  it('可以连接多个数组', () => {
    const arrayList = [
      [1, 2],
      [3, 4],
      [5, 6]
    ]
    const result = concat<number>(arrayList)
    expect(result).toEqual([1, 2, 3, 4, 5, 6])
  })
})
复制代码


functionChain


函数的相加不知道大家会想到什么,我是会想到链式操作,如 a().b().c()....,使用 reduce 实现,就需要传入这样的数组:[a, b, c]

function functionChain (fnList: Function[]) {
  return fnList.reduce((prev, curt) => {
    return curt(prev)
  }, null)
}
export default functionChain
复制代码


用例

describe('functionChain', () => {
  it('可以链式调用数组里的函数', () => {
    const fnList = [
      () => 1,
      (prevResult) => prevResult + 2,
      (prevResult) => prevResult + 3
    ]
    const result = functionChain(fnList)
    expect(result).toEqual(6)
  })
})
复制代码


promiseChain


既然说到了链式调用,就不得不说 Promise 了,数组元素都是 promise 也是可以进行链式操作的:

function promiseChain (asyncFnArray) {
  return asyncFnArray.reduce((prev, curt) => {
    return prev.then((result) => curt(result))
  }, Promise.resolve())
}
export default promiseChain
复制代码


这里要注意初始值应该为一个 resolved 的 Promise 对象。


用例

describe('functionChain', () => {
  it('可以链式调用数组里的函数', () => {
    const fnList = [
      () => 1,
      (prevResult) => prevResult + 2,
      (prevResult) => prevResult + 3
    ]
    const result = functionChain(fnList)
    expect(result).toEqual(6)
  })
})
复制代码


compose


最后再来说说 compose 函数,这是实现 middeware /洋葱圈模型最重要的组成部分.



为了造这种模型,我们不得不让函数疯狂套娃:

mid1(mid2(mid3()))
复制代码


要构造上面的套娃,这样的函数一般叫做 compose,使用 reduce 实现如下:

function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((aFunc, bFunc) => (...args) => aFunc(bFunc(...args)))
}
export default compose
复制代码


用例

describe('compose', () => {
  beforeEach(() => {
    jest.spyOn(console, 'log')
  })
  afterEach(() => {
    clearAllMocks()
  })
  it('可以 compose 多个函数', () => {
    const aFunc = () => console.log('aFunc')
    const bFunc = () => console.log('bFunc')
    const cFunc = () => console.log('cFunc')
    compose(aFunc, bFunc, cFunc)()
    expect(console.log).toHaveBeenNthCalledWith(1, 'cFunc')
    expect(console.log).toHaveBeenNthCalledWith(2, 'bFunc')
    expect(console.log).toHaveBeenNthCalledWith(3, 'aFunc')
  })
  it('可以使用 next', () => {
    const aFunc = (next) => () => {
      console.log('before aFunc')
      next()
      console.log('after aFunc')
      return next
    }
    const bFunc = (next) => () => {
      console.log('before bFunc')
      next()
      console.log('after bFunc')
      return next
    }
    const cFunc = (next) => () => {
      console.log('before cFunc')
      next()
      console.log('after cFunc')
      return next
    }
    const next = () => console.log('next')
    const composedFunc = compose(aFunc, bFunc, cFunc)(next)
    composedFunc()
    expect(console.log).toHaveBeenNthCalledWith(1, 'before aFunc')
    expect(console.log).toHaveBeenNthCalledWith(2, 'before bFunc')
    expect(console.log).toHaveBeenNthCalledWith(3, 'before cFunc')
    expect(console.log).toHaveBeenNthCalledWith(4, 'next')
    expect(console.log).toHaveBeenNthCalledWith(5, 'after cFunc')
    expect(console.log).toHaveBeenNthCalledWith(6, 'after bFunc')
    expect(console.log).toHaveBeenNthCalledWith(7, 'after aFunc')
  })
})
复制代码

image.png

总结


上面的例子仅仅给出了 reduce 实现的最常见的一些工具函数。

就像第2点说的不同类型和不同操作有非常多的组合方式,因此使用 reduce 可以玩出很多种玩法。希望上面给出的这些可以给读者带来一些思考和灵感,在自己项目里多使用 reduce 来减轻对数组的复杂操作。

相关文章
|
数据可视化 Linux API
使用Docker安装部署Swagger Editor并远程访问编辑API文档
使用Docker安装部署Swagger Editor并远程访问编辑API文档
307 0
|
开发工具 git
Git操作远程仓库及解决合并冲突
Git操作远程仓库及解决合并冲突
431 0
|
数据可视化 应用服务中间件 数据安全/隐私保护
轻量应用服务器部署k3s,并搭建可视化高性能网关 apisix
k3s低资源占用集群,apisix 可视化高性能网关。小白教程
2032 0
|
机器学习/深度学习 传感器 算法
BES-LSSVM分类预测 | 秃鹰搜索优化最小二乘支持向量机分类预测
BES-LSSVM分类预测 | 秃鹰搜索优化最小二乘支持向量机分类预测
|
设计模式 Java 关系型数据库
【设计模式——学习笔记】23种设计模式——适配器模式Adapter(原理讲解+应用场景介绍+案例介绍+Java代码实现)
【设计模式——学习笔记】23种设计模式——适配器模式Adapter(原理讲解+应用场景介绍+案例介绍+Java代码实现)
383 0
|
弹性计算 人工智能 PyTorch
GPU实验室-在云上部署ChatGLM2-6B大模型
ChatGLB2-6B大模型:由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。截至2023年7月,在C-Eval中,排名靠前。Pytorch:一个开源的Python机器学习库,基于Torch,底层由C++实现,应用于人工智能领域,如计算机视觉和自然语言处理。它主要由Meta Platforms的人工智能研究团队开发。著名的用途有:特斯拉自动驾驶,Uber最初发起而现属Linux基金会项目的概率编程软件Pyro,Lightning。
|
机器学习/深度学习 算法 PyTorch
PyTorch 模型性能分析和优化 - 第 6 部分
PyTorch 模型性能分析和优化 - 第 6 部分
|
消息中间件 Kafka Java
Spring 框架与 Kafka 联姻,竟引发软件世界的革命风暴!事件驱动架构震撼登场!
【8月更文挑战第31天】《Spring 框架与 Kafka 集成:实现事件驱动架构》介绍如何利用 Spring 框架的强大功能与 Kafka 分布式流平台结合,构建灵活且可扩展的事件驱动系统。通过添加 Spring Kafka 依赖并配置 Kafka 连接信息,可以轻松实现消息的生产和消费。文中详细展示了如何设置 `KafkaTemplate`、`ProducerFactory` 和 `ConsumerFactory`,并通过示例代码说明了生产者发送消息及消费者接收消息的具体实现。这一组合为构建高效可靠的分布式应用程序提供了有力支持。
233 0
|
存储 JSON 测试技术
Python中最值得学习的第三方JSON库
Python中最值得学习的第三方JSON库
329 0
|
传感器 数据采集 算法
振南技术干货集:FFT 你知道?那数字相敏检波 DPSD 呢?(2)
振南技术干货集:FFT 你知道?那数字相敏检波 DPSD 呢?(2)