博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
翻译|Better Redux Selectors with Ramda
阅读量:6578 次
发布时间:2019-06-24

本文共 6893 字,大约阅读时间需要 22 分钟。

目标

目标是把Redux中selector:

export const getUserName = state => state.user.nameexport const isLoggedIn = state => state.user.id != nullexport const getTotalItemCount = state =>    Object.values(state.items.byId)        .reduce((total, item) => total + item.count, 0)复制代码

转换为:

import R from 'ramda'// Helper functionsconst isNotNil = R.complement(R.isNil)const pathIsNotNil = path => R.compose(isNotNil, R.path(path))const addProp = propName => R.useWith(R.add, [R.identity, R.prop(propName)])const sumProps = propName => R.reduce(addProp(propName), 0)const sumCounts = sumProps('count')// Selector functionsexport const getUserName = R.path(['user', 'name'])export const isLoggedIn = pathIsNotNil(['user', 'id'])export const getTotalItemCount =    R.compose(sumCounts, R.values, R.path(['items', 'byId']))复制代码

介绍

Selector函数回顾

selector 的概念在 Redux 的文档中出现过, 为了代替直接在 React 组件中访问 state tree,可以定义从 state 获取数据的函数. 可以认为是从 state获取数据的 API. 不是必须的, 甚至 Redux也不是一定要用.

Selector函数接收 Redux的 state 对象作为参数, 返回任何你需要的数据. 实例:

从 State tree 获取 属性

function getUserName(state) {    return state.user.name}复制代码

从属性衍生数据

function isLoggedIn(state) {    return state.user.id != null}复制代码

从一个列表数据中衍生数据

function getTotalItemCount(state) {    return Object.keys(state.items.byId)        .reduce(function(total, id) {            return total + state.items.byId[id].count        }, 0)}复制代码

假定列表的每个 item都有一个count属性, 这个 selector 使用array.reduce()函数计算count的综合.

export function getUserName(state) {    return state.user.name}export function isLoggedIn(state) {    return state.user.id != null}export function getTotalItemCount(state) {    return Object.keys(state.items.byId)        .reduce(function(total, id) {            return total + state.items.byId[id].count        }, 0)}复制代码

如果使用 ES2015语法,会更简洁

export const getUserName = state => state.user.nameexport const isLoggedIn = state => state.user.id != nullexport const getTotalItemCount = state =>    Object.values(state.items.byId)        .reduce((total, item) => total + item.count, 0)复制代码

Object.values 是 ES2017的语法

Ramda 的原则

  • 自动柯理化
  • 数据最后传入

使用 Ramda编写 Selectors

getUserName

export const getUserName = state => R.path(['user', 'name'], state)复制代码

改为柯理化的版本:

export const getUserName = state => R.path(['user', 'name'])(state)复制代码

就可以等待数据了

export const getUserName = R.path(['user', 'name'])复制代码

这里的函数就看不到数据了, 这个技术被称为 point-free style 或者tacit programming.

isLoggedIn

我们想要的版本是获取用于的 ID, 然后判断是否为 true

export const isLoggedIn = pathIsNotNullOrUndefined(['user', 'id'])复制代码

Ramda的 isNil 方法

R.isNil(null) // trueR.isNil(undefined) // trueR.isNil(false) // false复制代码

现在可以改为:

const isNotNil = val => !R.isNil(val)复制代码

在更进一步:

const isNotNil = val => R.not(R.isNil(val))复制代码

另一个方法:

const isNotNil = R.complement(R.isNil)isNotNil(true) // trueisNotNil(null) // falseisNotNil(undefined) // false复制代码

进一步重构的版本:

const pathIsNotNil = (path, state) => isNotNil(R.path(path, state))//⛔️柯理化的版本const pathIsNotNil = path => state => isNotNil(R.path(path, state))复制代码

也就是从一个 state获取嵌套属性,并且判断是否为 true, 由于属性的路径是在 default 中已经配置好的, 所以我们可以使用柯理化提前配置获取的方法, 等待变化的 state 数据. 这就配置出了一个处理 state 的工厂. 为什么柯理化在函数式编程中很重要,这就是原因. 配置出的工厂是与数据独立的, 这就是上面提到的 tacit programming 无参数编程, 函数的配置和传入的参数是无关的, 顶多是对参数的类型做出约束.

在使用 R.compose 做重构

const pathIsNotNil = path => state => R.compose(isNotNil, R.path)(path, state)复制代码

整个操作和path,state 有关, 通过 path从 state 获取属性, 然后判断是否为 true,

const pathIsNotNil = path => state => R.compose(isNotNil, R.path(path))(state)//??柯理化const pathIsNotNil = path => R.compose(isNotNil, R.path(path))复制代码

在 pathIsNotNil中实际的数据只有 state,path 是属于配置项, 也就是在程序中是不改变的.

export const isLoggedIn = pathIsNotNil(['user', 'id'])//配置了从对象的路径 user.id 获取属性值,然后判断是否为 true复制代码

getTotalItemCount

//刚开始的方法

export const getTotalItemCount = state =>    Object.values(state.items.byId)        .reduce((total, item) => total + item.count, 0)复制代码

面对复杂的问题, 使用函数式风格进行分解是比较好的选择.创建一个函数sumCounts,接收一个数组, 返回项目中count属性的总计.

const sumCounts = items =>    items.reduce((total, item) => total + item.count, 0)复制代码

使用 map

const sumCounts = R.compose(R.sum, R.map(R.prop('count')))复制代码

R.map 对数组的每一项调用 R.prop('count')函数, 获取的所有count 属性,放到一个新的数组中, 之后用 R.sum 对数组中的属性值做合计

Ramda 针对map 这个函数的使用,也有更简单的方法

const sumCounts = R.compose(R.sum, R.pluck('count'))复制代码

R.pluck 从数组中获取每一项的属性值

使用 Reduce 函数的替代方案

const sumCounts = R.reduce((total, item) => total + item.count, 0)复制代码
const addCount=(total,item)=>total+item.countconst sumCounts = R.reduce(addCount, 0)复制代码

使用 Ramda 的 R.add 方法

const addCount = (total, item) => R.add(total, item.count)复制代码

从对象中获取属性

const addCount = (total, item) => R.add(total, prop('count', item))// 柯理化形式const addCount = (total, item) => R.add(total, prop('count')(item))复制代码

上面的函数通用的模式是: 接收两个参数,传递给另一个函数, 第一个参数不懂, 对第二个参数运用一个函数进行处理,之后再执行第一个函数

Ramda 有一个函数可以帮我们完成这个任务, useWith, useWith函数接收两个参数, 第一个参数是单个的函数,和一个函数数组. 数组中的函数被称为变换函数-在对应位置的参数被第一个函数调用之前进行变换处理. 换句话说,数组的第一个函数对第一个参数进行处理, 第二个函数对第二个参数进行处理,以此类推.转换后的参数传递个第一个参数的函数.

在我们的实例中,第一个参数的函数 R.add,

const addCount = R.useWith(R.add, [/* transformers */])复制代码

需要对 R.add 的第二个参数进行处理, 从 count属性中获取值, 所以放在第二个函数的位置

const addCount = R.useWith(R.add [/* 1st */, R.prop('count')])复制代码

第一个参数怎么办? 这个参数对应的是 total 值, 不需要转换 , Ramda有一个函数可以原封不动的返回一个数值, R.identity.

const addCount = R.useWith(R.add, [R.identity, R.prop('count')])复制代码

现在的函数:

const addCount = R.useWith(R.add, [R.identity, R.prop('count')])const sumCounts = R.reduce(addCount, 0)复制代码

现在可以获得更为通用的方式,获取任意的属性,

const addProp = propName => R.useWith(R.add, [R.identity, R.prop(propName)])const sumProps = propName => R.reduce(addProp(propName), 0)const sumCounts = sumProps('count')复制代码

参数的转换方式也可以抽象出来:

const addTransformedItem = transformer =>    R.useWith(R.add, [R.identity, transformer])const sumTransformedItems = transformer =>    R.reduce(addTransformedItem(transformer), 0)const totalItemComments = R.compose(R.length, R.prop('comments'))const sumComments = sumTransformedItems(totalItemComments)复制代码

最终在 Ramda 的帮助下, 总的数据获取流是有更小的可以重用的函数组成的..

结构

如果有下面的数据

const state = {    items: {        byId: {            'item1': { id: 'item1', count: 2 },            'item2': { id: 'item2', count: 4 },            'item3': { id: 'item3', count: 7 }        }    }}复制代码
export const getTotalItemCount =    R.compose(sumCounts, R.values, R.path(['items', 'byId']))复制代码

最终得到的结果

mport R from 'ramda'// Helper functionsconst isNotNil = R.complement(R.isNil)const pathIsNotNil = path => R.compose(isNotNil, R.path(path))const addProp = propName => R.useWith(R.add, [R.identity, R.prop(propName)])const sumProps = propName => R.reduce(addProp(propName), 0)const sumCounts = sumProps('count')// Selector functionsexport const getUserName = R.path(['user', 'name'])export const isLoggedIn = pathIsNotNil(['user', 'id'])export const getTotalItemCount =    R.compose(sumCounts, R.values, R.path(['items', 'byId']))复制代码

函数式编程最好的解释应该是: 数据和要对数据进行操作的函数式分离开的. 基于此, 就可以发现, React的组件也可以看成一个函数, 接收应用的数据, 对数据进行处理,之后进行渲染.

转载于:https://juejin.im/post/5cbf36f2f265da03a85ac1ac

你可能感兴趣的文章
动态单链表的传统存储方式和10种常见操作-C语言实现
查看>>
SpringBatch的流程简介
查看>>
JS 数据类型转换
查看>>
【Android】进入Material Design时代
查看>>
Add Two Numbers
查看>>
Sublime Text 3安装与使用
查看>>
sql点滴40—mysql乱码问题总结
查看>>
leetcode:Best Time to Buy and Sell Stock II
查看>>
centos 6.5 文件目录管理
查看>>
Windows下搭建Eclipse+Android4.0开发环境
查看>>
Hadoop中客户端和服务器端的方法调用过程
查看>>
CodeForces 81D.Polycarp's Picture Gallery 乱搞
查看>>
Android开发最佳学习路线图
查看>>
ASP.NET MVC+EF框架+EasyUI实现权限管理系列(11)-验证码实现和底层修改
查看>>
thinkphp学习笔记3—项目编译和调试模式
查看>>
魅族MX3\MX2 在MTP模式下恢复手机误删数据教程
查看>>
SharePoint 2013 自定义扩展菜单(二)
查看>>
[Unity3D]再次点击以退出程序
查看>>
架构师的97种习惯
查看>>
PHP 开发 APP 接口 学习笔记与总结 - XML 方式封装通信接口
查看>>