React Hooks简介

Stella981
• 阅读 496

感谢支持ayqy个人订阅号,每周义务推送1篇(only unique one)原创精品博文,话题包括但不限于前端、Node、Android、数学(WebGL)、语文(课外书读后感)、英语(文档翻译)        
       如果觉得弱水三千,一瓢太少,可以去 http://blog.ayqy.net 看个痛快    

一.出发点

在 React 现有的组件模型下,存在很多难以解决的问题:

  • 难以跨组件复用状态逻辑

  • 组件复杂度高难以理解

  • Class 的诸多弊病

  • ……

而 Hooks,肩负着破局使命

组件间逻辑复用

组件间逻辑复用一直是个问题,Render Props、Higher-Order Components等常用套路模式都是为了分离横切关注点(Cross-cutting concern),复用诸如:

  • 日志

  • 缓存/同步/持久化

  • 数据校验

  • 错误捕获/异常处理

的逻辑,目的是_将横切关注点与核心业务逻辑分离开_,以便专注于业务逻辑

P.S.关于切面、关注点等 AOP 概念的更多信息,见AOP(Aspect-Oriented Programming)

然而,HOC 与 Render Props 虽然能以组件形式分离横切关注点,但也带来了一些新问题:

  • 扩展性限制

  • Ref 传递问题

  • Wrapper Hell

之所以会出现这些问题,根本原因在于:

细粒度代码复用不应该与组件复用捆绑在一起

而一直以来都缺少一种简单直接的组件行为扩展方式:

React doesn’t offer a way to “attach” reusable behavior to a component (for example, connecting it to a store).

提出 Hooks 的主要目的就是为了解决这个问题:

React needs a better primitive for sharing stateful logic.

P.S>关于组件间逻辑复用方式的更多信息,见React 组件间逻辑复用

组件复杂度问题

如你所见,React 组件正在变得越来越复杂:

Provider, Consumer, Higher-order Component, Render Props// with ReduxAction, Reducer, Container// with ReselectSelector// with xxx ...

等诸多抽象层缓解了状态逻辑的组织和复用问题,但随之而来的问题是组件复用成本更高了,不再是简单地引入组件就能获得完整的业务功能

诚然,这些细分抽象层能让代码职责变得更加清晰,但状态逻辑也被打散了,难以复用。因而组件复用程度大多停留在 View Component (View + UI State Logic)层面,而无法更进一步复用 Business Component (View + Business State Logic)。除非将这些业务状态逻辑(请求数据、订阅其它数据源、定时器等)全都收拢到单一组件里(比如改用mobxjs/mobx管理状态)。即便这样,也无法避免组件中掺杂着的副作用,以及生命周期方法中混在一起的不相干的逻辑,比如componentDidMount里含有数据请求、事件监听等……我们发现,_真正有内在关联的代码被生命周期拆开了,而完全不相干的代码最终却凑到了一个方法里_:

Mutually related code that changes together gets split apart, but completely unrelated code ends up combined in a single method.

按组件生命周期拆分逻辑让组件体积迅速膨胀,而且这种巨大组件不容易拆成一堆小组件,因为状态逻辑到处都有,还难以测试

因此,_需要一种更合理的、更容易复用的状态逻辑组织方式_,让有内在关联的代码聚在一起,而不是被生命周期方法强制拆开

P.S.关于 MobX 的更多信息,见MobX

Class 弊病

Class 作为对象模具,是 OOP 中相当重要的一部分。然而,用 Class 来定义(视图与逻辑相结合的)组件却不那么理想:

  • 让代码难以组织/复用

  • 带来更高的学习成本

  • 阻碍编译优化

代码组织/复用方面,Class 的生命周期方法是典型的例子,一个组件只能存在一个特定的生命周期方法,因而只能将一些不相干的逻辑放在一起,并且这种模板式的划分让具有内在关联的代码被拆开了,不利于代码复用

学习成本上,主要体现在两点:

  • 要理解 JavaScript 中的this(与其它语言不一样),并记着bind(this)

  • 理解函数式组件与 Class 组件的区别,及各自的应用场景

编译优化方面,Class 让一些工具优化效果大打折扣,例如:

  • 组件提前编译(ahead-of-time compilation)效果不理想(React 团队已经在这方面做了一些尝试,发现 Class 组件不利于编译优化)

  • Class 不利于代码压缩

  • 难以正确热重载(hot reloading)

P.S.组件提前编译类似于GCC 的高级模式,对defaultProps常量等进行内联优化,并去除无用代码

因此,希望提供一套编译优化友好的 API:

We want to present an API that makes it more likely for code to stay on the optimizable path.

所以抛弃 Class,拥抱函数:

Hooks let you use more of React’s features without classes.

P.S.并非转投函数式编程,全面引入 FP 概念,而是提供了通向命令式编程的“逃生舱”,而不必掌握函数式编程、响应式编程等技术:

Hooks provide access to imperative escape hatches and don’t require you to learn complex functional or reactive programming techniques.

二.目标

为了解决以上种种问题,Hooks 应运而生,目标是:

  • 提供一种简单直接的代码复用方式

  • 提供一种更合理的代码组织方式

  • 提供一种 Class 的替代方案

一方面解决代码组织、复用的问题,另一方面,新的组件定义方式也是 React 未来愿景的一部分:

Hooks represent our vision for the future of React.

那么,Hooks 到底是个什么东西?

三.定位

_Hooks 是一些能让函数式组件接入 React State 和生命周期等特性的函数_:

Hooks are functions that let you “hook into” React state and lifecycle features from function components.

一方面借助 Hooks 更合理地拆分/组织代码,解决复用问题,另一方面通过 Hooks 增强函数式组件,让其拥有与 Class 组件相同的表达力,进而成为一种替代选项,最终取而代之

四.作用

Hooks 主要解决了代码组织、逻辑复用方面的问题,例如:

  • 组织被生命周期拆开的关联逻辑,如数据源订阅/取消订阅、事件监听注册/注销等

  • 跨组件复用散落在生命周期中的重复逻辑,同时解决 HOC 和 Render Props 等基于组件组合的复用模式带来的组件嵌套问题(Wrapper Hell)

此外,对 React 自身而言,Hooks 还解决了大规模优化上的阻碍,比如内联组件的编译难题

代码组织

Hooks 方案下,最大的区别在于,_可以将组件基于代码块的内在关联拆分成一些小函数,而不是强制按照生命周期方法去拆分_:

Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.

例如:

// 自定义Hookfunction useFriendStatus(friendID) {  const [isOnline, setIsOnline] = useState(null);  // 注册/注销外部数据源  useEffect(() => {    function handleStatusChange(status) {      setIsOnline(status.isOnline);    }    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);    return () => {      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);    };  });  return isOnline;}

注册/注销外部数据源的代码紧密地联系在一起,而不用再关心调用时机(组件生命周期)的差异

逻辑复用

同时,如上面示例,这些_状态逻辑和副作用能被轻松抽离到 Hooks 中,并组合成 Custom Hook_,漂亮地解决了状态逻辑的复用问题:

With Hooks, you can extract stateful logic from a component so it can be tested independently and reused.

例如:

// View组件1function FriendStatus(props) {  // 使用自定义Hook  const isOnline = useFriendStatus(props.friend.id);  if (isOnline === null) {    return 'Loading...';  }  return isOnline ? 'Online' : 'Offline';}// View组件2function FriendListItem(props) {  // 使用自定义Hook  const isOnline = useFriendStatus(props.friend.id);  return (    <li style={{ color: isOnline ? 'green' : 'black' }}>      {props.friend.name}    </li>  );}

另一方面,这种复用方式是“无伤的”,_不必调整组件树层级结构,即引即用_:

Hooks allow you to reuse stateful logic without changing your component hierarchy. This makes it easy to share Hooks among many components or with the community.

五.总结

单从形式上看,Hooks 是对函数式组件的增强,使之能与类组件平起平坐,甚至(期望)取而代之。_实质意义在于进一步将更多的函数式思想引入到前端领域_,比如 Effect、Monad 等。算是在提出v = f(d)的 UI 层函数式思路之后,在这条路上的进一步探索

(摘自React 16 Roadmap)

从某种程度上来讲,这种_思想风暴_是比 Concurrent Mode 等核心特性更激动人心的

参考资料

  • React 16.x Roadmap

  • rfcs/text/0068-react-hooks.md

  • Introducing Hooks

  • Hooks at a Glance

  • Rules of Hooks

  • Building Your Own Hooks

联系ayqy      

如果在文章中发现了什么问题,请查看原文并留下评论,ayqy看到就会回复的(不建议直接回复公众号,看不到的啦)

特别要紧的问题,可以直接微信联系ayqywx      

本文分享自微信公众号 - 前端向后(backward-fe)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
Electron webview完全指南
感谢支持ayqy个人订阅号,每周义务推送1篇(only_unique_one)原创精品博文,话题包括但不限于前端、Node、Android、数学(WebGL)、语文(课外书读后感)、英语(文档翻译)        如果觉得弱水三千,一瓢太少,可以去http://blog.ayqy.net看个痛快  一.webview标签
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
2年前
JS内存泄漏排查方法
感谢支持ayqy个人订阅号,每周义务推送1篇(only_unique_one)原创精品博文,话题包括但不限于前端、Node、Android、数学(WebGL)、语文(课外书读后感)、英语(文档翻译)        如果觉得弱水三千,一瓢太少,可以去http://blog.ayqy.net看个痛快  写在前面J
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这