Code repository: github.com/Haixiang612…

preface

The array. reduce function feels strange at first glance and takes a few moments to understand. Once you understand it, you don’t know what the application scenario is. This function is one of the most powerful functions of the Array type.

API usage

The API can be used with or without an initial value:

No initial value

const array = [1.2.3]

array.reduce((prev, curt) = > {
  return prev + curt
}) // 1 + 2 + 3 = 6
Copy the code

With the initial value

const array = [1.2.3]

array.reduce((prev, curt) = > {
  return prev + curt
}, 99) // 99 + 1 + 2 + 3 = 105
Copy the code

The main function of reduce is to combine multiple things into one thing. You’ve all done math olympiad in elementary school, something like this

The functionality that reduce provides is the plus sign, and it’s up to you to decide what the addition is. The process of addition can be graphically understood as a snake

🐍 (prev) + 💩 (curt) + 💩 (curt) + 💩 (curt) = 🛫 (return value)Copy the code

The existing snake body is the prev parameter, the beans to be eaten are curt, the state of the eaten beans is the return value of the callback function, and the return value of the entire reduce function is the state of the dead 🐍.

Application scenarios

The hardest thing about reduce is that you can’t think of any usage scenarios. Here’s a primer:

The above example only shows the addition of numbers. There are 7 basic data types in JS: number, String, object, symbol, NULL, Boolean, undefined. What happens if these types add up?

In addition to these basic types, object also includes Array, Function, object, and Promise. Can addition of these types also be used in many applications?

The other thing is that I can do subtraction as well as addition, and even compare asion, Max, min, etc.

Put the above three points together, and it’s not hard to see that a single Reduce can be used in dozens of ways. Here are some typical examples to give you some inspiration.

All agents (both source and test code) are here: github.com/Haixiang612…

max

Python has the syntax Max ([1, 2, 3] // => 3, JS is not available, reduce can easily implement the above functions:

type TComparator<T> = (a: T, b: T) = > boolean

function max<T> (array: T[], largerThan? : TComparator
       ) :T {
  return array.reduce((prev, curt) = > {
    return largerThan(prev, curt) ? prev : curt
  })
}

export default max
Copy the code

Use cases

describe('max'.() = > {
  it('Return simple element maximum value'.() = > {
    const array = [1.2.3.4.5]

    const result = max<number>(array, (a, b) = > a > b)

    expect(result).toEqual(5)
  })
  it('Returns the maximum value of a complex object'.() = > {
    const array: TItem[] = [
      {value: 1}, {value: 2}, {value: 3}]const result = max<TItem>(array, (a, b) = > a.value > b.value)

    expect(result).toEqual({value: 3})})})Copy the code

findIndex

JS has an array.indexof API, but not for data structures like [{id: 2}, {id: 3}], where we want to pass a callback to find the corresponding object’s index. Reduce can be written like this:

type TEqualCallback<T> = (item: T) = > boolean

function findIndex<T> (array: T[], isEqual: TEqualCallback<T>) {
  return array.reduce((prev: number, curt, index) = > {
    if (prev === -1 && isEqual(curt)) {
      return index
    } else {
      return prev
    }
  }, -1)}export default findIndex
Copy the code

Use cases

describe('findIndex'.() = > {
  it('You can find the subscript.'.() = > {
    const array: TItem[] = [
      {id: 1.value: 1},
      {id: 2.value: 2},
      {id: 3.value: 3},]const result = findIndex<TItem>(array, (item) = > item.id === 2)

    expect(result).toEqual(1)})})Copy the code

filter

Reduce can also be used to re-implement some of the APIS under Array, such as filter:

type TOkCallback<T> = (item: T) = > boolean

function filter<T> (array: T[], isOk: TOkCallback<T>) :T[] {
  return array.reduce((prev: T[], curt: T) = > {
    if (isOk(curt)) {
      prev.push(curt)
    }

    return prev
  }, [])
}

export default filter
Copy the code

Use cases

describe('filter'.() = > {
  it('It can be filtered'.() = > {
    const array: TItem[] = [{id: 1}, {id: 2}, {id: 3}]

    const result = filter<TItem>(array, (item= >item.id ! = =1))

    expect(result).toEqual([{id: 2}, {id: 3})})})Copy the code

normalize

When writing Redux, we might sometimes need to do some Normalization of arrays, for example

[{id: 1, value:1}, {id: 2, value: 2}]
=>
{
  1: {id: 1, value: 1}
  2: {id: 2, value: 2}
}
Copy the code

Reduce can be stored with an initial value {}, and then only need to set id => object each time:

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
Copy the code

Use cases

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]})})})Copy the code

assign

In the example above, we just add the number. What about the object “add”? Object. Assign, reduce, add objects easily:

function assign<T> (origin: T, ... partial: Partial
       
        []
       ) :T {
  const combinedPartial = partial.reduce((prev, curt) = > {
    return{... prev, ... curt } })return{... origin, ... combinedPartial } }export default assign
Copy the code

Use cases

describe('assign'.() = > {
  it('Can merge multiple objects'.() = > {
    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})})})Copy the code

This is a bit of farting, but if you treat each object in the array as [middleState, middleState, middleState,… The initial value is prevState, and the final result is nextState. Does that look familiar? That’s reducer in redux, and that’s where the reducer name came from.

concat

Adding objects is also easy:

function concat<T> (arrayList: T[][]) :T[] {
  return arrayList.reduce((prev, curt) = > {
    return prev.concat(curt)
  }, [])
}

export default concat
Copy the code

Use cases

describe('concat'.() = > {
  it('Can concatenate multiple arrays'.() = > {
    const arrayList = [
      [1.2],
      [3.4],
      [5.6]]const result = concat<number>(arrayList)

    expect(result).toEqual([1.2.3.4.5.6])})})Copy the code

functionChain

I don’t know what you think of when you add functions, but I think of chain operations, such as a().b().c()…. To implement reduce, you need to pass in the array [a, b, c].

function functionChain (fnList: Function[]) {
  return fnList.reduce((prev, curt) = > {
    return curt(prev)
  }, null)}export default functionChain
Copy the code

Use cases

describe('functionChain'.() = > {
  it('I can chain call a function in an array'.() = > {
    const fnList = [
      () = > 1.(prevResult) = > prevResult + 2.(prevResult) = > prevResult + 3
    ]

    const result = functionChain(fnList)

    expect(result).toEqual(6)})})Copy the code

promiseChain

Now that we’re talking about chained calls, we have to say Promise. Array elements are all promises and can be chained:

function promiseChain (asyncFnArray) {
  return asyncFnArray.reduce((prev, curt) = > {
    return prev.then((result) = > curt(result))
  }, Promise.resolve())
}

export default promiseChain
Copy the code

Note that the initial value should be an Resolved Promise object.

Use cases

describe('functionChain'.() = > {
  it('I can chain call a function in an array'.() = > {
    const fnList = [
      () = > 1.(prevResult) = > prevResult + 2.(prevResult) = > prevResult + 3
    ]

    const result = functionChain(fnList)

    expect(result).toEqual(6)})})Copy the code

compose

Finally, come to the compose function, which is the most important part of implementing the Middeware/Onion ring model.

To build this model, we have to make the function crazy doll:

mid1(mid2(mid3()))
Copy the code

To construct the nesting child above, such a function is typically called compose and is implemented using reduce as follows:

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
Copy the code

Use cases

describe('compose'.() = > {
  beforeEach(() = > {
    jest.spyOn(console.'log')
  })

  afterEach(() = > {
    clearAllMocks()
  })

  it('Can compose multiple functions'.() = > {
    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('You can use 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')})})Copy the code

conclusion

The examples above show only some of the most common utility functions for reduce implementations.

As mentioned in point 2, there are so many combinations of different types and different actions that there are so many ways to play with Reduce. Hopefully, the above will give readers some thought and inspiration to use Reduce in their own projects to reduce the complexity of array manipulation.