lodash已死?radash库方法介绍及源码解析 —— 函数柯里化 + Number篇

写在前面

tips:点赞 + 收藏 = 学会!

  • 主页有更多其他篇章的方法,欢迎访问查看。
  • 本篇我们继续介绍radash中函数柯里化和Number 相关的方法使用和源码解析。

函数柯里化

chain:创建一个函数链并依次执行

  1. 使用说明
    1. 功能描述:用于创建一个函数链,该链依次执行一系列函数,每个函数的输出都是下一个函数的输入。这种模式常见于函数式编程,特别是在数据转换和流水线处理中。
    2. 参数:函数数组(或者说任意数量的函数)。
    3. 返回值:返回一个新的函数。
  2. 使用代码示例
    import { chain } from 'radash'
    
    const add = (y: number) => (x: number) => x + y
    const mult = (y: number) => (x: number) => x * y
    const addFive = add(5)
    const double = mult(2)
    
    const chained = chain(addFive, double)
    
    chained(0) // => 10
    chained(7) // => 24
    
  3. 源码解析
    // 定义一个名为 `chain` 的函数。
    export function chain(
      // `funcs` 是一个由函数组成的数组,这些函数将会被依次执行。
      // 每个函数都可以接受任意参数,并返回任何类型的值。
      ...funcs: ((...args: any[]) => any)[]
    ) {
      // `chain` 函数返回一个新的函数,这个新函数接受任意参数。
      return (...args: any[]) => {
        // 使用 `funcs` 数组中的第一个函数和传入的参数 `args` 来初始化累加器 `acc`。
        // 然后,使用 `reduce` 方法依次执行剩余的函数。
        return funcs.slice(1).reduce(
          // 在每个迭代中,将前一个函数的返回值作为下一个函数的输入。
          (acc, fn) => fn(acc),
          // 第一次迭代的初始值是 `funcs` 数组中第一个函数的执行结果。
          funcs[0](...args)
        )
      }
    }
    
    • 方法流程说明:
      1. chain 函数接受任意数量的函数作为参数,并返回一个新的函数。
      2. 当新函数被调用时,它首先使用传入的参数调用 funcs 数组中的第一个函数。
      3. 然后,它使用 reduce 方法依次执行 funcs 数组中剩余的函数,每个函数的返回值都被传递给下一个函数。
      4. 最终,返回最后一个函数的执行结果。

compose:依次执行传入的函数,每个函数的输出作为下一个函数的输入

  1. 使用说明
    1. 功能描述:接受一系列函数作为参数,并返回一个新的函数。这个新函数将从右到左依次执行传入的函数,每个函数的输出作为下一个函数的输入。这是函数式编程中的组合(composition)模式。
    2. 参数:一系列函数(函数数组)。
    3. 返回值:一个新的函数。
  2. 使用代码示例
    import { compose } from 'radash'
    
    const useZero = (fn: any) => () => fn(0)
    const objectize = (fn: any) => (num: any) => fn({ num })
    const increment = (fn: any) => ({ num }: any) => fn({ num: num + 1 })
    const returnArg = (arg: any) => (args: any) => args[arg]
    
    const composed = compose(
      useZero,
      objectize,
      increment,
      increment,
      returnArg('num')
    )
    
    composed() // => 2
    
  3. 源码解析
    // 定义一个名为 `compose` 的函数。
    export function compose(
      // 使用展开运算符接收一个函数数组,每个函数可以接受任意参数并返回任何类型的值。
      ...funcs: ((...args: any[]) => any)[]
    ) {
      // `compose` 函数返回一个新的函数。
      return (...args: any[]) => {
        // 首先反转 `funcs` 数组,因为我们需要从右到左执行函数。
        // 然后使用 `reduce` 方法来组合这些函数。
        return funcs.reverse().reduce((acc, fn) => {
          // 在每次迭代中,将上一个函数的返回值(acc)作为当前函数(fn)的参数。
          return fn(acc);
        }, args); // 初始值 `args` 是传递给组合函数的参数。
      };
    }
    
    • 方法流程说明:
      1. 接受任意数量的函数作为参数。
      2. 返回一个新的函数,这个新函数接受任意参数。
      3. 当新函数被调用时,它首先反转 funcs 数组,以确保函数能够从右到左执行。
      4. 使用 reduce 方法应用每个函数,从最右边的函数开始,每个函数的返回值都作为下一个函数的输入。
      5. 最终,返回最左边函数的执行结果。

debounce:函数防抖

  1. 使用说明
    1. 功能描述:创建一个防抖函数,该函数会在指定的延迟时间后执行。防抖(debouncing)是一种控制函数执行频率的技术,它确保函数只在最后一次被调用后的一段时间后执行,通常用于处理像窗口调整大小或键盘输入这样的连续事件。
    2. 参数:配置对象(包含delay——延时)、需要防抖的函数。
    3. 返回值:返回防抖后的函数。
  2. 使用代码示例
    import { debounce } from 'radash'
    
    const makeSearchRequest = (event) => {
      api.movies.search(event.target.value)
    }
    
    input.addEventListener('change', debounce({ delay: 100 }, makeSearchRequest))
    
  3. 源码解析
    // 定义一个泛型函数 `debounce`。
    export const debounce = <TArgs extends any[]>(
      // 第一个参数是一个对象,包含 `delay` 属性,它是函数延迟执行的毫秒数。
      { delay }: { delay: number },
      // 第二个参数 `func` 是需要被防抖的函数。
      func: (...args: TArgs) => any
    ) => {
      // 初始化一个变量 `timer` 用于保存 setTimeout 的返回值。
      let timer: NodeJS.Timeout | undefined = undefined
      // 初始化一个标志变量 `active` 来控制函数是否应该执行。
      let active = true
    
      // 定义一个防抖后的函数 `debounced`。
      const debounced: DebounceFunction<TArgs> = (...args: TArgs) => {
        // 如果 `active` 是 `true`,则执行防抖逻辑。
        if (active) {
          // 清除之前的定时器(如果有的话)。
          clearTimeout(timer)
          // 设置一个新的定时器,延迟 `delay` 毫秒后执行 `func`。
          timer = setTimeout(() => {
            // 检查 `active` 是否仍然是 `true`,如果是,则执行 `func`。
            active && func(...args)
            // 执行后,将 `timer` 设置为 `undefined`。
            timer = undefined
          }, delay)
        } else {
          // 如果 `active` 是 `false`,则立即执行 `func`,不使用防抖逻辑。
          func(...args)
        }
      }
      // 为 `debounced` 函数添加一个方法 `isPending`,用于检查是否有等待执行的 `func`。
      debounced.isPending = () => {
        return timer !== undefined
      }
      // 为 `debounced` 函数添加一个方法 `cancel`,用于取消执行 `func`。
      debounced.cancel = () => {
        active = false
      }
      // 为 `debounced` 函数添加一个方法 `flush`,用于立即执行 `func`。
      debounced.flush = (...args: TArgs) => func(...args)
    
      // 返回防抖后的函数 `debounced`。
      return debounced
    }
    
    • 方法流程说明:
      1. 接受一个包含延迟时间 delay 的对象和一个需要被防抖的函数 func 作为参数。
      2. 返回一个新的函数 debounced,这个函数在被连续调用时会取消之前的调用并重新计时。
      3. 如果在延迟时间 delay 内没有再次被调用,func 将被执行。
      4. debounced 函数提供了三个额外的方法:isPending 检查是否有等待执行的函数,cancel 取消等待执行的函数,flush 立即执行函数。
      5. 使用 debounce 函数可以帮助你控制函数的执行频率,尤其是在处理频繁触发的事件时。

memo:创建一个记忆化(memoized)版本的给定函数

  1. 使用说明
    0. 功能描述:记忆化是一种优化技术,它存储函数执行的结果,并在后续调用中重用这个结果,以避免重复执行相同计算。这种技术特别适用于计算成本高昂或调用频繁的函数。
    0. 参数:需要被记忆化的函数、可选配置对象。
    0. 返回值:返回一个记忆版本的函数。
  2. 使用代码示例
    import { memo } from 'radash'
    
    const timestamp = memo(() => Date.now())
    
    const now = timestamp()
    const later = timestamp()
    
    now === later // => true
    
  3. 源码解析
    // 定义一个泛型函数 `memo`。
    export const memo = <TArgs extends any[], TResult>(
      // 第一个参数 `func` 是需要被记忆化的函数。
      func: (...args: TArgs) => TResult,
      // 第二个参数 `options` 是一个可选的配置对象。
      options: {
        // `key` 是一个可选的函数,用于根据函数的参数生成一个唯一的缓存键。
        key?: (...args: TArgs) => string
        // `ttl` 是一个可选的数字,表示缓存的生存时间(以毫秒为单位)。
        ttl?: number
      } = {} // 如果没有提供 `options`,则使用一个空对象作为默认值。
    ) => {
      // `memo` 函数返回一个记忆化版本的 `func`。
      return memoize({}, func, options.key ?? null, options.ttl ?? null) as (
        // 返回的函数类型与原始 `func` 相同。
        ...args: TArgs
      ) => TResult
    }
    
    • 方法流程说明:
      1. memo 函数接受一个函数 func 和一个可选的配置对象 options
      2. options 对象包含两个可选的属性:keyttlkey 是一个函数,用于生成缓存键;ttl 是缓存的生存时间。
      3. memo 函数调用 memoize 函数(在代码片段中未定义)来创建一个记忆化版本的 funcmemoize 函数接受一个缓存对象、原始函数 func、键生成函数 options.key 和生存时间 options.ttl
      4. 如果没有提供 options.key,则使用 null 作为默认值;如果没有提供 options.ttl,也使用 null 作为默认值。
      5. memo 函数返回一个记忆化版本的 func,它的类型与原始 func 相同。

partial :创建一个偏应用的部分函数(允许你预先填充一些参数,只需传入剩余参数)

  1. 使用说明
    1. 功能描述:创建一个新函数,这个新函数是原始函数 fn 的偏应用版本。偏应用(Partial Application)是一种函数式编程技术,它允许你预先填充一些参数,并返回一个新函数,这个新函数只需要剩余的参数就可以执行。
    2. 参数:原始函数,原始参数数组。
    3. 返回值:返回一个接受剩余的参数数组 rest的新函数。
  2. 使用代码示例
    import { partial } from 'radash'
    
    const add = (a: number, b: number) => a + b
    
    const addFive = partial(add, 5)
    
    addFive(2) // => 7
    
  3. 源码解析
    // 定义一个泛型函数 `partial`。
    export const partial = <T extends any[], TA extends Partial<T>, R>(
      // `fn` 是原始函数,它接受一个参数数组 `T` 并返回一个结果 `R`。
      fn: (...args: T) => R,
      // 使用展开运算符接收一个或多个预先填充的参数数组 `args`,其类型为 `TA`。
      // `TA` 是原始参数数组 `T` 的部分类型。
      ...args: TA
    ) => {
      // `partial` 函数返回一个新函数,这个新函数接受剩余的参数数组 `rest`。
      return (...rest: RemoveItemsInFront<T, TA>) =>
        // 新函数调用原始函数 `fn`,首先展开预先填充的参数 `args`,然后展开剩余的参数 `rest`。
        // 使用类型断言 `as T` 确保参数数组的类型正确。
        fn(...([...args, ...rest] as T))
    }
    
    • 这段代码中使用了几个未定义的函数和类型,如 tryitlistforksort,以及类型 WorkItemResult<K>。我们可以假设这些函数和类型具有以下功能:
      1. partial 函数接受一个原始函数 fn 和一系列预先填充的参数 args
      2. 返回一个新函数,这个新函数接受剩余的参数 rest
      3. 当新函数被调用时,它将预先填充的参数 args 和剩余的参数 rest 合并成一个完整的参数数组,并调用原始函数 fn
      4. 原始函数 fn 被执行,并返回结果。
      5. 类型 RemoveItemsInFront<T, TA> 是一个类型操作,它从类型 T 中移除与 TA 对应的项。它在这段代码中没有定义,我们可以假设它的作用是确保 rest 参数只包含原始函数 fn 还需要的参数。

partob:创建一个偏应用的部分函数(跟partial类似,不过接收参数不一样)

  1. 使用说明
    1. 功能描述:创建一个新的函数,该函数是原始函数 fn 的偏应用版本。这个新函数将接受一个对象参数 restobj,它包含了原始函数 fn 所需参数的剩余部分,然后将 restobj 与预先填充的参数对象 argobj 合并后调用 fn
    2. 参数:原始函数、预先填充的参数对象。
    3. 返回值:返回一个接收剩余参数对象 restobj的新函数。
  2. 使用代码示例
    import { partob } from 'radash'
    
    const add = (props: { a: number; b: number }) => props.a + props.b
    
    const addFive = partob(add, { a: 5 })
    
    addFive({ b: 2 }) // => 7
    
  3. 源码解析
    // 定义一个泛型函数 `partob`。
    export const partob = <T, K, PartialArgs extends Partial<T>>(
      // `fn` 是原始函数,它接受一个类型为 `T` 的参数对象,并返回一个类型为 `K` 的结果。
      fn: (args: T) => K,
      // `argobj` 是一个预先填充的参数对象,其类型为 `PartialArgs`,它是原始参数对象 `T` 的部分类型。
      argobj: PartialArgs
    ) => {
      // `partob` 函数返回一个新的函数,该函数接受一个类型为 `Omit<T, keyof PartialArgs>` 的参数对象 `restobj`。
      // `Omit<T, keyof PartialArgs>` 表示从 `T` 中省略掉 `PartialArgs` 中已有的键,只保留剩余的键。
      return (restobj: Omit<T, keyof PartialArgs>): K =>
        // 新函数调用原始函数 `fn`,传入合并后的参数对象。
        fn({
          // 使用展开运算符将 `argobj` 和 `restobj` 合并为一个新对象。
          // 这里的类型断言确保合并后的对象符合原始参数对象 `T` 的类型。
          ...(argobj as Partial<T>),
          ...(restobj as Partial<T>)
        } as T)
    }
    
    • 方法流程说明:
      1. partob 函数接受一个原始函数 fn 和预先填充的参数对象 argobj
      2. 返回一个新的函数,该函数接受剩余参数对象 restobj
      3. 当新函数被调用时,它将预先填充的参数对象 argobj 和剩余参数对象 restobj 合并成一个完整的参数对象,并调用原始函数 fn
      4. 原始函数 fn 被执行,并返回结果。

proxied:创建动态代理对象

  1. 使用说明
    1. 功能描述:创建的代理对象可以拦截对其属性的访问并返回由一个处理函数 handler 产生的值。
    2. 参数:处理函数(该函数接受一个属性名)。
    3. 返回值:返回一个新的 Proxy 对象。
  2. 使用代码示例
    import { proxied } from 'radash'
    
    type Property = 'name' | 'size' | 'getLocation'
    
    const person = proxied((prop: Property) => {
      switch (prop) {
        case 'name':
          return 'Joe'
        case 'size':
          return 20
        case 'getLocation'
          return () => 'here'
      }
    })
    
    person.name // => Joe
    person.size // => 20
    person.getLocation() // => here
    
  3. 源码解析
    // 定义一个泛型函数 `proxied`。
    export const proxied = <T, K>(
      // `handler` 是一个函数,接受一个属性名 `propertyName` 并返回一个类型为 `K` 的值。
      handler: (propertyName: T) => K
    ): Record<string, K> => {
      // 返回一个新的 Proxy 对象。
      return new Proxy(
        // 第一个参数是要代理的目标对象,这里是一个空对象。
        {},
        // 第二个参数是一个处理器对象,它定义了多种拦截操作的方法。
        {
          // `get` 方法用于拦截对属性的读取操作。
          get: (target, propertyName: any) =>
            // 当尝试读取属性时,调用 `handler` 函数并传入属性名。
            // 返回 `handler` 函数的结果作为属性的值。
            handler(propertyName)
        }
      )
    }
    
    • 方法流程说明:
      1. proxied 函数接受一个 handler 函数作为参数。
      2. 使用 new Proxy() 创建一个新的 Proxy 对象,它代理一个空对象。
      3. 定义 Proxy 对象的 get 方法,用于拦截对代理对象属性的读取操作。
      4. 当尝试读取任何属性时,get 方法调用 handler 函数,传入被读取的属性名称。
      5. handler 函数返回的值作为属性的值返回给调用者。
      6. 返回创建的 Proxy 对象,它的类型为 Record<string, K>,表示一个对象,其属性名为字符串,属性值的类型为 K

throttle :函数节流

  1. 使用说明
    1. 功能描述:节流(Throttling)是一种控制函数调用频率的技术,它确保函数在指定的时间间隔内最多只执行一次。这通常用于限制频繁触发的事件(如窗口调整大小、滚动等)的处理函数。
    2. 参数:对象({interval})—— 触发间隔、需要节流的函数。
    3. 返回值:返回节流后的函数。
  2. 使用代码示例
    import { throttle } from 'radash'
    
    const onMouseMove = () => {
      rerender()
    }
    
    addEventListener('mousemove', throttle({ interval: 200 }, onMouseMove))
    
  3. 源码解析
    // 定义一个泛型函数 `throttle`。
    export const throttle = <TArgs extends any[]>(
      // 第一个参数是一个对象,包含 `interval` 属性,它是函数执行之间的毫秒间隔。
      { interval }: { interval: number },
      // 第二个参数 `func` 是需要被节流的函数。
      func: (...args: TArgs) => any
    ) => {
      // 初始化一个标志变量 `ready`,表示函数是否准备好执行。
      let ready = true
      // 初始化一个变量 `timer` 用于保存 setTimeout 的返回值。
      let timer: NodeJS.Timeout | undefined = undefined
    
      // 定义一个节流后的函数 `throttled`。
      const throttled: ThrottledFunction<TArgs> = (...args: TArgs) => {
        // 如果函数尚未准备好执行,直接返回。
        if (!ready) return
        // 调用 `func` 并传入参数。
        func(...args)
        // 设置 `ready` 为 `false`,防止函数在间隔时间内再次执行。
        ready = false
        // 设置一个定时器,在 `interval` 毫秒后将 `ready` 重新设为 `true`,允许函数再次执行。
        timer = setTimeout(() => {
          ready = true
          // 定时器执行完毕后,清除 `timer`。
          timer = undefined
        }, interval)
      }
      
      // 为 `throttled` 函数添加一个方法 `isThrottled`,用于检查函数是否处于节流状态。
      throttled.isThrottled = () => {
        return timer !== undefined
      }
      
      // 返回节流后的函数 `throttled`。
      return throttled
    }
    
    • 方法流程说明:
      1. throttle 函数接受一个配置对象(包含 interval 属性)和一个需要被节流的函数 func 作为参数。
      2. 返回一个新的函数 throttled,该函数在被连续调用时会限制函数的执行频率。
      3. throttled 函数被调用时,如果 readytrue(即函数准备好执行),func 将被执行。
      4. 执行 func 后,ready 设置为 false,并通过 setTimeout 设置一个定时器,定时器在 interval 毫秒后执行,将 ready 设置回 true
      5. 如果 throttled 函数在定时器完成之前再次被调用,由于 readyfalsefunc 不会被执行。
      6. throttled 函数提供了一个额外的方法 isThrottled,用于检查函数是否正在等待下一次执行的间隔。

Number相关

inRange :检查给定数字是否在两个数字之间

  1. 使用说明
    1. 功能描述:检查给定数字是否在两个数字之间。判断包含起始值。不包含结束值。范围的开始和结束可以是升序或降序。如果未指定结束值,则将其设置为起始值。并且把起始值设置为0。
    2. 参数:需要检查的值,判断的起始值,[判断的结束值]。
    3. 返回值:在范围内返回 true,否则返回 false
  2. 使用代码示例
    import { inRange } from 'radash'
    
    inRange(10, 0, 20) // true
    inRange(9.99, 0, 10) // true
    inRange(Math.PI, 0, 3.15) // true
    inRange(10, 10, 20) // true
    inRange(10, 0, 10) // false
    
    inRange(1, 2) // true
    inRange(1, 0) // false
    
  3. 源码解析
    // 定义一个名为 `inRange` 的函数。
    function inRange(number, start, end) {
        // 首先检查传入的参数类型是否正确:`number` 和 `start` 必须是数字类型,
        // `end` 要么是未定义,要么是数字类型。
        const isTypeSafe = typeof number === "number" && typeof start === "number" && (typeof end === "undefined" || typeof end === "number");
        // 如果参数类型不正确,直接返回 `false`。
        if (!isTypeSafe) {
          return false;
        }
    
        // 如果 `end` 参数未提供(即 `undefined`),则将 `end` 设置为 `start` 的值,
        // 而将 `start` 设置为 `0`。这样就创建了一个从 `0` 到 `start` 的范围。
        if (typeof end === "undefined") {
          end = start;
          start = 0;
        }
    
        // 检查 `number` 是否在 `start` 和 `end` 指定的范围内。
        // 使用 `Math.min` 和 `Math.max` 来确保 `start` 和 `end` 的顺序正确,
        // 即使它们被反向提供(例如 `end` 小于 `start`)。
        return number >= Math.min(start, end) && number < Math.max(start, end);
    }
    
    • 方法流程说明:
      1. 首先验证所有参数的类型。如果 numberstart 不是数字,或者 end 不是数字且不是 undefined,函数返回 false
      2. 如果 end 参数未定义,函数将其解释为只提供了一个参数的情况,即一个从 0start 的范围。
      3. 函数计算 number 是否大于等于范围的最小值(Math.min(start, end))并且小于范围的最大值(Math.max(start, end))。
      4. 如果 number 在这个范围内,函数返回 true;否则返回 false

toFloat :可能的情况下,将一个值转为浮点值

  1. 使用说明
    1. 功能描述:将一个值转换为浮点数。如果转换失败或者提供的值是 nullundefined,函数将返回默认值。
    2. 参数:需要转换的值、默认值。
    3. 返回值:能转换则返回转换后的浮点数,否则返回传入的默认值。
  2. 使用代码示例
    import { toFloat } from 'radash'
    
    toFloat(0) // => 0.0
    toFloat(null) // => 0.0
    toFloat(null, 3.33) // => 3.33
    
  3. 源码解析
    // 定义一个名为 `toFloat` 的函数。
    const toFloat = (value, defaultValue) => {
      // 如果没有提供 `defaultValue`,则使用 `0` 作为默认值。
      // `void 0` 是 `undefined` 的一种安全写法。
      const def = defaultValue === void 0 ? 0 : defaultValue;
    
      // 如果 `value` 是 `null` 或 `undefined`,返回默认值 `def`。
      if (value === null || value === void 0) {
        return def;
      }
    
      // 尝试将 `value` 转换为浮点数。
      const result = parseFloat(value);
    
      // 如果转换结果是 `NaN`(Not-a-Number),返回默认值 `def`。
      // 否则,返回转换后的浮点数 `result`。
      return isNaN(result) ? def : result;
    };
    
    • 方法流程说明:
      1. 首先检查 defaultValue 是否提供,如果没有提供,则将 def 设置为 0
      2. 检查 value 是否是 nullundefined,如果是,返回 def
      3. 使用 parseFloat 函数尝试将 value 转换为浮点数。
      4. 使用 isNaN 函数检查转换结果是否为 NaN
      5. 如果结果是 NaN,返回 def;如果转换成功,返回浮点数结果 result

toInt:可能的情况下,将一个值转为整数

  1. 使用说明
    1. 功能描述:将一个值转换为整数。如果转换失败或者提供的值是 nullundefined,函数将返回默认值。
    2. 参数:需要转换的值、默认值。
    3. 返回值:能转换则返回转换后的整数,否则返回传入的默认值。
  2. 使用代码示例
    import { toInt } from 'radash'
    
    toInt(0) // => 0
    toInt(null) // => 0
    toInt(null, 3) // => 3
    
  3. 源码解析
    // 定义一个名为 `toFloat` 的函数。
    const toFloat = (value, defaultValue) => {
      // 如果没有提供 `defaultValue`,则使用 `0` 作为默认值。
      // `void 0` 是 `undefined` 的一种安全写法。
      const def = defaultValue === void 0 ? 0 : defaultValue;
    
      // 如果 `value` 是 `null` 或 `undefined`,返回默认值 `def`。
      if (value === null || value === void 0) {
        return def;
      }
    
      // 尝试将 `value` 转换为浮点数。
      const result = parseFloat(value);
    
      // 如果转换结果是 `NaN`(Not-a-Number),返回默认值 `def`。
      // 否则,返回转换后的浮点数 `result`。
      return isNaN(result) ? def : result;
    };
    
    • 方法流程说明:
      1. 首先检查 defaultValue 是否提供,如果没有提供,则将 def 设置为 0
      2. 检查 value 是否是 nullundefined,如果是,返回 def
      3. 使用 parseFloat 函数尝试将 value 转换为浮点数。
      4. 使用 isNaN 函数检查转换结果是否为 NaN
      5. 如果结果是 NaN,返回 def;如果转换成功,返回浮点数结果 result

写在后面

  • 老实说能看到这里的人呢,都是美丽帅气有眼光又多金的人才,感谢阅读到最后,Peace and Love!
  • 后续我们会继续分享 Radash 库中其他方法的使用和源码解析。
  • 大家有任何问题或见解,欢迎评论区留言交流和批评指正!!!
  • 你的每一个点赞和收藏都是作者写作的动力!!!(求个点赞《《小声逼逼》》)
  • 点击访问:radash官网

热门相关:笙秋   危险的嗜好   奇妙王国之魔法奇缘   法利赛人   单身的世界