React函数式进阶

React让很多人让追捧的一个特性是它的所有的组件都是完全由JavaScript组成的。组件的定义是JavaScript,组件的模板也可以是JavaScript,组件的样式也可以是JavaScript(参考styled-component)。React并没有创造太多概念,唯一的创造品——JSX,其内部的statement也是一段段纯JavaScript代码,并且在Babel编译后依然转变成了JavaScript。

而JavaScript又是一个把函数当作一等公民的语言。函数不仅可以被声明和调用,也可以像值一样做赋值、传参、返回的操作。

这样运行在一个有着first-class functions特性的语言之上的纯JavaScript组件库,自然可以脑洞大开的有很多玩法。

Stateless Component

使用React的同学自然对这个概念一点都陌生。虽然大多数情况下我们都会使用 class extends React.Component 来声明一个Stateful Component,虽然Stateless Component没有完整的生命周期,虽然Stateless Component的性能相比Stateful Component并没有提升,但是它在很多场合下仍然是有意义的。下面是一个最简单的Stateless Component的声明:

function Welcome({name}) {
  return <div>{name}</div>
}

由于这是一个纯函数,我们可以基于它创建一个简单的High Order Component。

const withNameX = WrappedComponent => props => WrappedComponent(Object.assign({}, props, {name: ‘x’}))
const WelcomeX = withNameX(Welcome)

我们也可以对 Welcome 做简单的 compose。

const toUpperCase = props => Object.keys(props).reduce((target, next) => {
  target[next] = props[next].toUpperCase()
  return target
})
const UpperWelcome = compose(Welcome, toUpperCase )

使用Stateless Component好处很多,包括

  • Pure。单元测试很方便。
  • 强制你从更简单的角度思考组件的组织。单个函数的代码量更小,功能更单一。「The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that.」——《Clean Code》
  • 可以使用compose、curry等特性构件可复用组件。

Stateless Component最大的不足是它没有能力在最佳实践的前提下处理需要回传属性的事件回调,我们只能写成这样:

const Trigger = ({onClick, id}) => <Button onClick={() => onClick(id)}>test</Button>

由于每次调用都会生成一个新的匿名函数,子组件是无法利用PureComponent做优化的。这是我在实际工作场景下使用Stateless Component最大的障碍。

recompose

上面的障碍当然也是有解的,recompose是一个为Stateless React爱好者提供的一个工具库。我们可以使用它提供的 withHandlers 方法。

const Trigger = withHandlers({
  onClick: props => () => props.onClick(props.id)
})(({onClick}) => <Button onClick={onClick}>test</Button>)

是不是也很优雅。

当然为了能处理这种类型的回调,withHandlers 内部也是使用了Stateful Component的,感兴趣的同学可以看看recompose的源码。

recompose还有 withState, pure, onlyUpdateForKeys, withContext 等很多实用的工具函数,帮助我们至少从代码编写角度实现全面使用Stateless Component替代Stateful Component。

Function as child Components

这也是React社区一种常见的组件构建方式。它也能解决HOC中丢失上下文、丢失ref的问题。它也能有效的提升代码复用率,而且某些情况下比HOC要更加优雅。

一个最简单的Function as Child Component如下:

class MyComponent extends React.Component {
  render() {
    return (
      <div>
        {this.props.children('world')}
      </div>
    );
  }
}

<MyComponent>
  {(name) => (
    <div>Hello {name}!</div>
  )}
</MyComponent>

PayPal开源的downshift就是使用Function as Child Component模型来构建他们的autocomplete,dropdown, select等组件的。

一般我们写一个autocomplete组件,是基于Popover -> Menu + InputTrigger -> AutoComplete这样逐步组合、增强基础组件的方式。这么写会有几个问题:高级组件或者完全无法获取底层组件的引用,或者需要通过很奇怪的方式把引用回调一层层传下去;为了适配很多情况和需求,为了能控制各组合组件的行为,高级组件的参数会多的可怕:ant.design的AutoComplete组件有14个参数,material-ui则有27个参数。

Downshift则完全不处理组件的展示和组合,这部分逻辑交给开发者自己,通过Function as Child Components的方式自由设计他们希望的样式和行为。Downshift只处理这一类组件的交互逻辑,维护组件状态,并暴露少数几个必须设置的子组件属性的接口。这样的代码组织,输入输出都很明确,组件间的耦合也很小,不仅解决了参数爆炸的问题,也提升了可维护性。

对比High Order Component与Function as Child Components

HOC

FaCC

使用者无关,HOC帮你完成了一切组件行为

使用者完全大部分组件展示和行为,更可控

HOC在运行时无法获取组件相关的state和props

可以在运行时获取组件的 state & props

HOC可以通过shouldComponentUpdate做优化

FaCC由于每次render都会改变,无法使用shouldComponentUpdate做优化

总结

本文提到了两种组件设计的思路——利用recompose拆分组件为Stateless Component,使用Function as Child Components来剥离行为和展现——以此来提升代码的可读性和可维护性。实际项目中可以按需使用。

参考资料

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏一个番茄说

函数响应式编程框架RxSwift 学习——Observable

最近开始研究RxSwift,网上能查到的资料太有限,边学边记录,有不对的地方欢迎大家指正。

591
来自专栏Java架构沉思录

介绍常见的JSON压缩算法

作者:勇哥。已获作者授权发布。 原文地址: https://blog.csdn.net/qq646350979/article/details/79841556...

39710
来自专栏Java Edge

React.js实战之React 生命周期1 组件的生命周期

1384
来自专栏偏前端工程师的驿站

WebComponent魔法堂:深究Custom Element 之 面向痛点编程

前言  最近加入到新项目组负责前端技术预研和选型,一直偏向于以Polymer为代表的WebComponent技术线,于是查阅各类资料想说服老大向这方面靠,最后得...

1725
来自专栏猿人谷

HANDLE

    HANDLE:句柄,是Windows用来表示对象的(不是C++的对象),HWND是其中一种,HWND是HANDLE,但HANDLE不只是HWND,更具体...

1768
来自专栏前端架构

重谈react优势——react技术栈回顾

现在,react已经慢慢退火,该用用react技术栈的已经使用上,填过多少坑,加过多少班,血泪控诉也不下千文。

651
来自专栏青玉伏案

设计模式(二):自己动手使用“观察者模式”实现通知机制

在之前发布Objective-C系列博客的时候,其中提到过OC的通知机制,请参考《Objective-C中的老板是这样发通知的(Notification)》这篇...

2076
来自专栏何俊林

Android Multimedia框架总结(七)C++中MediaPlayer的C/S架构补充及MediaService介绍

前面一篇主要介绍c++中MediaPlayer的C/S架构中和Client相关部分,并中间穿插了mediaplayerservice的部分。但是对于这块C/S部...

1966
来自专栏互联网杂技

React -- 组件间通信

分为三种类型的通信关系: 1、父组件向子组件通信 2、子组件向父组件通信 3、没有嵌套关系的组件之间的通信 父组件向子组件通信 父组件通过子组件的prop...

3737
来自专栏计算机视觉与深度学习基础

Leetcode 146 LRU Cache 模拟操作系统LRU

科研学习压力比较大,最近更新的不多,有时间尽量多做一点吧!上来就是一个大模拟。 Design and implement a data structure ...

18110

扫码关注云+社区