RamdaJs的使用

Ramda是一款强调函数式编程风格的JavaScript库,支持数据不可变性和函数无副作用,适合构建简洁优雅的应用。本文介绍Ramda的基本概念、与lodash的区别及在React中的应用。

Ramda工具的使用

Ramda是什么?

类似于lodash的JavaScript函数式编程库,如果你习惯于函数式编程pipeline,那么Ramda会非常适合你。

Ramda和lodash的区别

在目前的情况下,lodash的使用率更高,Ramda和lodash相比又有什么优势呢?

  • Ramda 强调更加纯粹的函数式风格。数据不变性和函数无副作用是其核心设计理念。这可以帮助你使用简洁、优雅的代码来完成工作。
  • Ramda 函数本身都是自动柯里化的。这可以让你在只提供部分参数的情况下,轻松地在已有函数的基础上创建新函数。
  • Ramda 函数参数的排列顺序更便于柯里化。要操作的数据通常在最后面。

最后两点一起,使得将多个函数构建为简单的函数序列变得非常容易,每个函数对数据进行变换并将结果传递给下一个函数。Ramda 的设计能很好地支持这种风格的编程。

Ramda 还有一个特点:所有方法都支持柯里化。也就是说,所有多参数的函数,默认都可以单参数使用。

但是Ramda的性能相比较lodash来说仍然是要差一些的。

Ramda的使用方法

在react中的使用方法:

npm install ramda

使用babel-plugin-ramda减少打包体积

npm install babel-plugin-ramda --save  

// 使用时在config中添加此plugin

在需要使用的地方导入:

import * as R from ramda

Ramda的常用方法

目前使用Ramda常常结合hooks一起使用

  • R.pipe

    从左往右执行函数组合。第一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数。

    在一些库中,此函数也被称为 sequence

    注意: pipe 函数的结果不是自动柯里化的

    const f = R.pipe(Math.pow, R.negate, R.inc);
    
    f(3, 4); // -(3^4) + 1
    
  • R.compose

    从右往左执行函数组合(右侧函数的输出作为左侧函数的输入)。最后一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数。

    **注意:**compose 输出的函数不会自动进行柯里化。

    const classyGreeting = (firstName, lastName) => "The name's " + lastName + ", " + firstName + " " + lastName
    const yellGreeting = R.compose(R.toUpper, classyGreeting);
    yellGreeting('James', 'Bond'); //=> "THE NAME'S BOND, JAMES BOND"
    
    R.compose(Math.abs, R.add(1), R.multiply(2))(-4) //=> 7
    
  • R.identity

    占位符,将输入值原样返回,适用于默认或占位函数。

    结合useSelector使用:

    const { loading } = useSelector(R.identity)
    
  • R.pick

    返回对象的部分拷贝,其中仅包含指定键对应的属性。如果某个键不存在,则忽略该属性。

    const { data } = useSelector(R.pick['myStore'])
    
  • R.last

    返回列表或字符串的最后一个元素。

    R.last(['a', 'b', 'c'])
    
  • R.prop

    取出

    对象中指定属性的值。如果不存在,则返回 undefined。

    R.prop('x', {x: 100}); //=> 100
    R.prop('x', {}); //=> undefined
    R.prop(0, [100]); //=> 100
    R.compose(R.inc, R.prop('x'))({ x: 3 }) //=> 4
    
  • R.props

    返回 prop 的数组:输入为 keys 数组,输出为对应的 values 数组。values 数组的顺序与 keys 的相同。

    R.props(['x', 'y'], {x: 1, y: 2}); //=> [1, 2]
    R.props(['c', 'a', 'b'], {b: 2, a: 1}); //=> [undefined, 1, 2]
    
    const fullName = R.compose(R.join(' '), R.props(['first', 'last']));
    fullName({last: 'Bullet-Tooth', age: 33, first: 'Tony'}); //=> 'Tony Bullet-Tooth'
    
  • R.uniq

    列表去重操作。返回无重复元素的列表。通过 R.equals 函数进行相等性判断。

    R.uniq([1, 1, 2, 1]); //=> [1, 2]
    R.uniq([1, '1']);     //=> [1, '1']
    R.uniq([[42], [42]]); //=> [[42]]
    
  • R.type和R.is

    R.type用一个单词来描述输入值的(原生)类型,返回诸如 ‘Object’、‘Number’、‘Array’、‘Null’ 之类的结果。不区分用户自定义的类型,统一返回 ‘Object’。

    R.is检测一个对象(val)是否是给定构造函数的实例。该函数会依次检测其原型链,如果存在的话。

    R.type({}); //=> "Object"
    R.type(1); //=> "Number"
    R.type(false); //=> "Boolean"
    R.type('s'); //=> "String"
    R.type(null); //=> "Null"
    R.type([]); //=> "Array"
    R.type(/[A-z]/); //=> "RegExp"
    R.type(() => {}); //=> "Function"
    R.type(undefined); //=> "Undefined"
    
    R.is(Object, {}); //=> true
    R.is(Number, 1); //=> true
    R.is(Object, 1); //=> false
    R.is(String, 's'); //=> true
    R.is(String, new String('')); //=> true
    R.is(Object, new String('')); //=> true
    R.is(Object, 's'); //=> false
    R.is(Number, {}); //=> false
    
  • R.equals

    如果传入的参数相等,返回 true;否则返回 false。可以处理几乎所有 JavaScript 支持的数据结构。

    若两个参数自身存在 equals 方法,则对称地调用自身的 equals 方法。

    R.equals(1, 1); //=> true
    R.equals(1, '1'); //=> false
    R.equals([1, 2, 3], [1, 2, 3]); //=> true
    
    const a = {}; a.v = a;
    const b = {}; b.v = b;
    R.equals(a, b); //=> true
    
  • R.path

    取出给定路径上的值。

    R.path(['a', 'b'], {a: {b: 2}}); //=> 2
    R.path(['a', 'b'], {c: {b: 2}}); //=> undefined
    R.path(['a', 'b', 0], {a: {b: [1, 2, 3]}}); //=> 1
    R.path(['a', 'b', -2], {a: {b: [1, 2, 3]}}); //=> 2
    
  • R.without

    求第二个列表中,未包含在第一个列表中的任一元素的集合。通过 R.equals 函数进行相等性判断。

    若在列表位置中给出 transfomer,则用作 transducer 。

    R.without([1, 2], [1, 2, 1, 3, 4]); //=> [3, 4]
    

更多的方法可以参考remda官网进行查看,取值的方法加上Eq都可以进行判断比对

Pointfree示例

var str = 'Lorem ipsum dolor sit amet consectetur adipiscing elit';

上面是一个字符串,请问其中最长的单词有多少个字符?

我们先定义一些基本运算。

// 以空格分割单词
var splitBySpace = s => s.split(' ');

// 每个单词的长度
var getLength = w => w.length;

// 词的数组转换成长度的数组
var getLengthArr = arr => R.map(getLength, arr); 

// 返回较大的数字
var getBiggerNumber = (a, b) => a > b ? a : b;

// 返回最大的一个数字
var findBiggestNumber = 
  arr => R.reduce(getBiggerNumber, 0, arr);

然后,把基本运算合成为一个函数(查看完整代码

var getLongestWordLength = R.pipe(
  splitBySpace,
  getLengthArr,
  findBiggestNumber
);

getLongestWordLength(str) // 11

可以看到,整个运算由三个步骤构成,每个步骤都有语义化的名称,非常的清晰。这就是 Pointfree 风格的优势。

Ramda 提供了很多现成的方法,可以直接使用这些方法,省得自己定义一些常用函数(查看完整代码)。

// 上面代码的另一种写法
var getLongestWordLength = R.pipe(
  R.split(' '),
  R.map(R.length),
  R.reduce(R.max, 0)
);

Pointfree 示例二

拷贝自 Scott Sauyet 的文章《Favoring Curry》。那篇文章能帮助你深入理解柯里化,强烈推荐阅读。

下面是一段服务器返回的 JSON 数据。
请添加图片描述

现在要求是,找到用户 Scott 的所有未完成任务,并按到期日期升序排列。
请添加图片描述

过程式编程的代码如下(查看完整代码)。

请添加图片描述

上面代码不易读,出错的可能性很大。

现在使用 Pointfree 风格改写(查看完整代码)。

var getIncompleteTaskSummaries = function(membername) {
  return fetchData()
    .then(R.prop('tasks'))
    .then(R.filter(R.propEq('username', membername)))
    .then(R.reject(R.propEq('complete', true)))
    .then(R.map(R.pick(['id', 'dueDate', 'title', 'priority'])))
    .then(R.sortBy(R.prop('dueDate')));
};

上面代码已经清晰很多了。

另一种写法是,把各个then里面的函数合成起来(查看完整代码)。

// 提取 tasks 属性
var SelectTasks = R.prop('tasks');

// 过滤出指定的用户
var filterMember = member => R.filter(
  R.propEq('username', member)
);

// 排除已经完成的任务
var excludeCompletedTasks = R.reject(R.propEq('complete', true));

// 选取指定属性
var selectFields = R.map(
  R.pick(['id', 'dueDate', 'title', 'priority'])
);

// 按照到期日期排序
var sortByDueDate = R.sortBy(R.prop('dueDate'));

// 合成函数
var getIncompleteTaskSummaries = function(membername) {
  return fetchData().then(
    R.pipe(
      SelectTasks,
      filterMember(membername),
      excludeCompletedTasks,
      selectFields,
      sortByDueDate,
    )
  );
};

上面的代码跟过程式的写法一比较,孰优孰劣一目了然。

小结

ramda最大的特点就是结合函数式编程,利用pipe或者compose来组合函数,类似管道的方式进行数据流动。

参考资料

Pointfree 编程风格指南

ramda中文文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MaxLoongLvs

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值