内容简介:目标是把Redux中selector:转换为:
目标是把Redux中selector:
export const getUserName = state => state.user.name export const isLoggedIn = state => state.user.id != null export const getTotalItemCount = state => Object.values(state.items.byId) .reduce((total, item) => total + item.count, 0) 复制代码
转换为:
import R from 'ramda' // Helper functions const 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 functions export 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.name export const isLoggedIn = state => state.user.id != null export 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) // true R.isNil(undefined) // true R.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) // true isNotNil(null) // false isNotNil(undefined) // false 复制代码
进一步重构的版本:
const pathIsNotNil = (path, state) => isNotNil(R.path(path, state)) //:no_entry:️柯理化的版本 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) //:point_down::point_down:柯理化 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.count const 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 functions const 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 functions export 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的组件也可以看成一个函数, 接收应用的数据, 对数据进行处理,之后进行渲染.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 基于 Laravel、Lumen 框架集成百度翻译、有道翻译、Google 翻译扩展包
- 腾讯发布人工智能辅助翻译 致敬人工翻译
- golang调用baidu翻译api实现自动翻译
- 监管机器翻译质量?且看阿里如何搭建翻译质量评估模型
- 机器翻译新突破:谷歌实现完全基于attention的翻译架构
- PendingIntent 是个啥?官方文档描述的很到位。我给翻译翻译
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。