摘要:右侧展现对应产品。我们使用命名为的对象表示过滤条件信息,如下此数据需要在组件中进行维护。因为组件的子组件和都将依赖这项数据状态。化应用再回到之前的场景,我们设计化函数,进一步可以简化为对于的偏应用即上面提到的相信大家已经理解了这么做的好处。
使用 React 开发应用,给予了前端工程师无限“组合拼装”快感。但在此基础上,组件如何划分,数据如何流转等应用设计都决定了代码层面的美感和强健性。
同时,在 React 世界里提到 curry 化,也许很多开发者会第一时间反应出 React-redux 库的 connect 方法。然而,如果仅仅机械化地停留于此,而没有更多灵活地应用,是非常可惜的。
这篇文章以一个真实场景为基础,从细节出发,分析 curry 化如何化简为繁,更优雅地实现需求。
场景介绍需求场景为一个卖食品的电商网站,左侧部分为商品筛选栏目,用户可以根据:价格区间、商品年限、商品品牌进行过滤。右侧展现对应产品。如下图:
作为 React 开发者,我们知道 React 是组件化的,第一步将考虑根据 UE 图,进行组件拆分。这个过程比较简单直观,我们对拆分结果用下图表示:
对应代码为:
初级实现
React 是基于数据状态的,紧接着第二步就要考虑应用状态。商品展现结果数据我们暂时不需要关心。这里主要考虑应用最重要的状态,即过滤条件信息。
我们使用命名为 filterSelections 的 JavaScript 对象表示过滤条件信息,如下:
filterSelections = { price: ..., ages: ..., brands: ..., }
此数据需要在 Products 组件中进行维护。因为 Products 组件的子组件 Filters 和 ProductResults 都将依赖这项数据状态。
Filters 组件通过 prop 接收 filterSelections 状态,并拆解传递给它的三项筛选子组件:
class Filters extends React.Component { render() { return (); }; }
同样地,ProductResults 组件也通过 prop 接收 filterSelections 状态,进行相应产品的展示。
对于 Filters 组件,它一定不仅仅是接收 filterSelections 数据而已,同样也需要对此项数据进行更新。为此,我们在 Products 组件中设计相应的 handler 函数,对过滤信息进行更新,命名为 updateFilters,并将此处理函数作为 prop 下发给 Filters 组件:
class Products extends React.Component { constructor(props) { super(props); this.state = { filterSelections: { price: someInitialValue, ages: someInitialValue, brands: someInitialValue, } } } updateFilters = (newSelections) => { this.setState({ filterSelections: newSelections }) }; render() { return(); } }
注意这里我们对 this 绑定方式。有兴趣的读者可以参考我的另一篇文章:从 React 绑定 this,看 JS 语言发展和框架设计。
作为 Filters 组件,同样也要对处理函数进行进一步拆分和分发:
class Filters extends React.Component { updatePriceFilter = (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, price: newValue }) }; updateAgeFilter = (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, ages: newValue }) }; updateBrandFilter = (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, brands: newValue }) }; render() { return (); }; }
我们根据 selectionsChanged 函数,通过传递不同类型参数,设计出 updatePriceFilter、updateAgeFilter、updateBrandFilter 三个方法,分别传递给 PriceFilter、AgeFilter、BrandFilter 三个组件。
这样的做法非常直接,然而运行良好。但是在 Filters 组件中,多了很多函数,且这些函数看上去做着相同的逻辑。如果将来又多出了一个或多个过滤条件,那么同样也要多出同等数量的“双胞胎”函数。这显然不够优雅。
currying 是什么在分析更加优雅的解决方案之前,我们先简要了解一下 curry 化是什么。curry 化事实上是一种变形,它将一个函数 f 变形为 f",f" 的参数接收原本函数 f 的参数,同时返回一个新的函数 f"",f"" 接收剩余的参数并返回函数 f 的计算结果。
这么描述无疑是抽象的,我们还是通过代码来理解。这是一个简单的求和函数:
add = (x, y) => x + y;
curried 之后:
curriedAdd = (x) => { return (y) => { return x + y; } }
所以,当执行 curriedAdd(1)(2) 之后,得到结果 3,curriedAdd(x) 函数有一个名字叫 partial application,curriedAdd 函数只需要原本 add(X, y) 函数的一部分参数。
Currying a regular function let’s us perform partial application on it.curry 化应用
再回到之前的场景,我们设计 curry 化函数:updateSelections,
updateSelections = (selectionType) => { return (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, [selectionType]: newValue, }); } };
进一步可以简化为:
updateSelections = (selectionType) => (newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, [selectionType]: newValue, }) };
对于 updateSelections 的偏应用(即上面提到的 partial application):
updateSelections("ages"); updateSelections("brands"); updateSelections("price");
相信大家已经理解了这么做的好处。这样一来,我们的 Filters 组件完整为:
class Filters extends React.Component { updateSelections = (selectionType) => { return (newValue) => { this.props.selectionsChanged({ ...this.props.selections, [selectionType]: newValue, // new ES6 Syntax!! :) }); } }; render() { return (); }; }
当然,currying 并不是解决上述问题的唯一方案。我们再来了解一种方法,进行对比消化,updateSelections 函数 uncurried 版本:
updateSelections = (selectionType, newValue) => { this.props.updateFilters({ ...this.props.filterSelections, [selectionType]: newValue, }); }
这样的设计使得每一个 Filter 组件:PriceFilter、AgeFilter、BrandFilter 都要调用 updateSelections 函数本身,并且要求组件本身感知 filterSelections 的属性名,以进行相应属性的更新。这就是一种耦合,完整实现:
class Filters extends React.Component { updateSelections = (selectionType, newValue) => { this.props.selectionsChanged({ ...this.props.filterSelections, [selectionType]: newValue, }); }; render() { return ( <>this.updateSelections("price", value)} /> this.updateSelections("ages", value)} /> this.updateSelections("brands", value)} /> > ); }; }
其实我认为,在这种场景下,关于两种方案的选择,可以根据开发者的偏好来决定。
总结这篇文章内容较为基础,但从细节入手,展现了 React 开发编写和函数式理念相结合的魅力。文章译自这里,部分内容有所改动。
广告时间:
如果你对前端发展,尤其对 React 技术栈感兴趣:我的新书中,也许有你想看到的内容。关注作者 Lucas HC,新书出版将会有送书活动。
Happy Coding!
PS: 作者 Github仓库 和 知乎问答链接 欢迎各种形式交流!
我的其他几篇关于React技术栈的文章:
从setState promise化的探讨 体会React团队设计思想
从setState promise化的探讨 体会React团队设计思想
通过实例,学习编写 React 组件的“最佳实践”
React 组件设计和分解思考
从 React 绑定 this,看 JS 语言发展和框架设计
做出Uber移动网页版还不够 极致性能打造才见真章**
React+Redux打造“NEWS EARLY”单页应用 一个项目理解最前沿技术栈真谛**
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/94377.html
摘要:右侧展现对应产品。我们使用命名为的对象表示过滤条件信息,如下此数据需要在组件中进行维护。因为组件的子组件和都将依赖这项数据状态。化应用再回到之前的场景,我们设计化函数,进一步可以简化为对于的偏应用即上面提到的相信大家已经理解了这么做的好处。 showImg(https://segmentfault.com/img/remote/1460000014458612?w=1240&h=663...
摘要:这一周连续发表了两篇关于的文章组件复用那些事儿实现按需加载轮子应用设计之道化妙用其中涉及到组件复用轮子设计相关话题,并配合相关场景实例进行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 这一周连续发表了两篇关于 React 的文章: 组件复用那些事儿 - React 实现按需加载轮子 React ...
摘要:这一周连续发表了两篇关于的文章组件复用那些事儿实现按需加载轮子应用设计之道化妙用其中涉及到组件复用轮子设计相关话题,并配合相关场景实例进行了分析。 showImg(https://segmentfault.com/img/remote/1460000014482098); 这一周连续发表了两篇关于 React 的文章: 组件复用那些事儿 - React 实现按需加载轮子 React ...
阅读 493·2021-09-03 00:22
阅读 1367·2021-08-03 14:03
阅读 2084·2021-07-25 21:37
阅读 649·2019-08-30 13:18
阅读 1877·2019-08-29 16:19
阅读 2685·2019-08-29 13:22
阅读 1296·2019-08-29 12:16
阅读 2588·2019-08-26 12:16