Yiero 发表于 2024-5-4 22:24:27

[radash 源码解析#4] `radash.select` 的使用和源码实现

# `radash.select` 的使用和源码实现

## 0. 前言

内容先行预览:

- `radash.select` 函数的说明和使用方法.
- 在 `Array.prototype.reduce()` 方法上标注泛型.

## 1. 说明

### 函数说明

`radash.select()` 是将 **一个数组先过滤, 然后重新赋值** , 最后输出为 **一个新的数组** 的函数.

这个函数相当于对输入的数组 `list`, 先进行一次 `list.filter()` 操作, 然后再进行一个 `list.map()` 操作, 但是这样的操作需要遍历两次 `list`.
`radash.select()` 帮助实现了上述的操作, 但只遍历一次 `list` .



## 2. 使用

### 用法

> 引入 `radash.select()`

```ts
import { select } from 'radash';
```

> 使用 `radash.select()`

```ts
// 第一个参数输入任意数组
const list = [];
// 第二个参数输入任意 map 函数, 等同于 Array.prototype.map(list, mapper);
const mapper = (item, index) => item;
// 第三个参数输入任意 filter 函数, 等同于 Array.prototype.filter(list, filter);
const filter = (item, index) => item;
// 获取结果
const result = select( list, mapper, filter );
```

**参数**

- `list`:
一个任意类型的数组.
- `mapper`:
一个回调函数, 类型为: `(item: unknown, index: number) => unknown`.
其类型等同于 `Array.prototype.map(list, mapper)` 中的 `mapper` 参数.
- `filter`:
一个回调函数, 类型为: `(item: unknown, index: number) => boolean`.
其类型等同于 `Array.prototype.filter(list, filter)` 中的 `filter` 参数.

> - `item`:
>   遍历出 `list` 中的每一项
> - `index`:
>   遍历出 `list` 中的每一项对应的索引

**返回值**

一个新的数组.

### 示例

```ts
import { select } from 'radash';

const array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

// 筛选出 大于等于4, 小于等于8 的数字, 并筛选之后的数字乘以 2 输出为一个新数组
const result = select(
        array,
        ( item ) => item * 2, // mapper
        ( item ) => item >= 4 || item <= 8, // filter
);

console.log( result );
// -> [ 8, 10, 12, 14, 16 ]
```



### 原生实现方法

> 使用 `filter()` + `map()` .

```ts
const array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

// filter
let result = array.filter( ( item ) => item >= 4 && item <= 8 );

// map
result = array.map( ( item ) => item * 2 );

console.log( result );
// -> [ 8, 10, 12, 14, 16 ]
```

> 使用 `reduce()`

```ts
const array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

let result = array.reduce<number[]>( ( result, item ) => {
    if ( item >= 4 && item <= 8 ) {
      result.push( item * 2 );
    }

    return result;
}, [] );

console.log( result );
// -> [ 8, 10, 12, 14, 16 ]
```

> 使用 `for-of`

```ts
const array = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

let result = [];
for ( const item of array ) {
    if ( item >= 4 && item <= 8 ) {
      result.push( item * 2 );
    }
}

console.log( result );
// -> [ 8, 10, 12, 14, 16 ]
```



## 3. 源码实现 - TypeScript

### Step - 1: 定义参数类型和返回值类型

**1. 定义输入和输出的泛型**

> 首先定义两个泛型 `<T, K>` , 泛型`T` 表示任意输入的数组项的类型, 泛型 `K` 表示任意输出的数组项的类型.
> 因为 **输入** 和 **输出** 并不统一, 可以通过 `mapper` 重新定义输出的值, 所以定义两个泛型.

```ts
const select = <T, K>(
        list: T[],
    mapper: Function,
    filter: Function,
) => {}
```



---

**2. 定义 `mapper` 参数的类型**

> `mapper` 参数传入两个参数, `item` 和 `index` .
> `item` 的类型就是泛型 `T` , `index` 的类型则是 `number` 因为是 `list` 遍历出来的索引值.
> 返回值的类型是泛型 `K` , 表示未知但不同于 `T` 的类型.

```ts
const select = <T, K>(
        list: T[],
    mapper: ( item: T, index: number ) => K,
    filter: Function,
) => {}
```



---

**3. 定义 `filter` 参数的类型**

> `filter` 参数的传入和 `mapper` 一样, 返回值不同而已.
> `filter` 返回类型 `boolean` . 因为 `filter` 需要进行判断, 当前遍历到的数组项是否支持写入新数组.

```ts
const select = <T, K>(
        list: T[],
        mapper: ( item: T, index: number ) => K,
        filter: ( item: T, index: number ) => boolean,
) => {}
```



---

**4. 完整的类型定义**

> 最后加上返回值的类型 `K[]` 就可以了.

```ts
const select = <T, K>(
        // 传入一个T类型的数组list
        list: T[],
        // 传入一个函数mapper,参数为T类型的item,返回值为K类型
        mapper: ( item: T, index: number ) => K,
        // 传入一个函数filter,参数为T类型的item,返回值为boolean类型
        filter: ( item: T, index: number ) => boolean,
): K[] => {}
```



### Step - 2: 具体的遍历和判断逻辑编写

> 因为 `radash.select()` 使用了 `Array.prototype.reduce()` , 所以具体示例就不使用 `reduce()` 了. 使用 `for-i` 先编写一遍, 后面我会贴出一份用 `reduce()` 写的版本, 指出一些问题.

> 具体逻辑在上文 ** 中已经写得比较清楚了, 只是将里面的判断逻辑和重新赋值逻辑, 抽象出来而已, 就不进行详细的解释了.

```ts
// 下面只贴出函数体
{
        // 定义一个K类型的空数组resultList
        let resultList: K[] = [];
       
        // 遍历list数组
        for ( let index = 0; index < list.length; index++ ) {
                const item = list[ index ];
                // 如果filter函数返回值为false,则跳过当前循环
      const isFilter = !filter( item, index );
                if ( isFilter ) {
                        continue;
                }
               
                // 将mapper函数返回值添加到resultList数组中
      const newItem = mapper( item, index );
                resultList.push( newItem );
        }
       
        // 返回resultList数组
        return resultList;
}
```



### Step - 3: 错误处理

> 进行简单的错误处理, 如果输入的 `list` 的类型不为数组类型, 则返回一个空数组.
> 这里注释了一行, 注释的行是**抛出错误**, 两种处理逻辑都可以, `radash` 是输出空数组, 就跟着 `radash` 的实现.

```ts
{
        // 判断list是否为数组类型,如果不是抛出错误
        if ( !Array.isArray( list ) ) {
                // throw new TypeError( 'Input parma "list" must be type Array.' );
                return [];
        }
   
    /* Step-2 的代码 (下略) */
}
```

### 完整代码

```ts
const select = <T, K>(
        // 传入一个T类型的数组list
        list: T[],
        // 传入一个函数mapper,参数为T类型的item,返回值为K类型
        mapper: ( item: T, index: number ) => K,
        // 传入一个函数filter,参数为T类型的item,返回值为boolean类型
        filter: ( item: T, index: number ) => boolean,
): K[] => {
        // 判断list是否为数组类型,如果不是抛出错误
        if ( !Array.isArray( list ) ) {
                // throw new TypeError( 'Input parma "list" must be type Array.' );
                return [];
        }
       
        // 定义一个K类型的空数组resultList
        let resultList: K[] = [];
       
        // 遍历list数组
        for ( let index = 0; index < list.length; index++ ) {
                const item = list[ index ];
                // 如果filter函数返回值为false,则跳过当前循环
                if ( !filter( item, index ) ) {
                        continue;
                }
               
                // 将mapper函数返回值添加到resultList数组中
                resultList.push( mapper( item, index ) );
        }
       
        // 返回resultList数组
        return resultList;
};
```





### Step - Extra: 使用 `Array.prototype.reduce()` 实现

> `radash` 使用 `Array.prototype.reduce()` 方法喜欢在 `initValue` 处**声明类型断言**, 防止类型错误报错.
> 但其实可以直接在 `Array.prototype.reduce()` 方法上**标注泛型**, 这样就不会报错了, 我认为会比在 `initValue` 上声明类型断言更好一些.
>
> 两种方法的比较如下:
>
> ```ts
> // 在 `initValue` 上声明类型断言:
> [].reduce( ( r ) => r, [] as number[] );
> ```
>
> ```ts
> // 在 `Array.prototype.reduce()` 方法上标注泛型
> [].reduce<number[]>( ( r ) => r, [] );
> ```

```ts
const select = <T, K>(
    // 传入一个T类型的数组list
    list: T[],
    // 传入一个函数mapper,参数为T类型的item,返回值为K类型
    mapper: ( item: T ) => K,
    // 传入一个函数filter,参数为T类型的item,返回值为boolean类型
    filter: ( item: T ) => boolean,
): K[] => {
    // 判断list是否为数组类型,如果不是抛出错误
    if ( !Array.isArray( list ) ) {
      // throw new TypeError( 'Input parma "list" must be type Array.' );
      return [];
    }

    // 使用reduce函数遍历list数组,将mapper函数返回值添加到resultList数组中
    return list.reduce<K[]>( ( resultList, item ) => {
      // 如果filter函数返回值为true,则将mapper函数返回值添加到resultList数组中
      if ( filter( item ) ) {
            resultList.push( mapper( item ) );
      }

      return resultList;
    }, [] );
};
```



## 4. radash 源码

### `TypeScript`

```ts
/**
* select 方法使用 reduce 内部执行 filter 和 map,
* 只迭代数组一次.
*
* @example
* select(, x => x*x, x > 2) -->
*/
export const select = <T, K>(
        array: readonly T[],
        mapper: ( item: T, index: number ) => K,
        condition: ( item: T, index: number ) => boolean,
) => {
        if ( !array ) return [];
        return array.reduce( ( acc, item, index ) => {
                if ( !condition( item, index ) ) return acc;
                acc.push( mapper( item, index ) );
                return acc;
        }, [] as K[] );
};
```



### `JavaScript`

```ts
const select = ( array, mapper, condition ) => {
        if ( !array )
                return [];
        return array.reduce( ( acc, item, index ) => {
                if ( !condition( item, index ) )
                        return acc;
                acc.push( mapper( item, index ) );
                return acc;
        }, [] );
};
```



## 附页

> ---
>
> - (https://www.npmjs.com/package/radash)
> - (https://github.com/rayepps/radash)
>
> ---
页: [1]
查看完整版本: [radash 源码解析#4] `radash.select` 的使用和源码实现