React Hook迷思

那啥,新年快乐

React在年底送给了各位React开发者一个年终大礼包,那就是React Hook。如果还不了解的小伙伴,推荐大家看一下React Hook文档以及Dan的这篇Making Sense of React Hooks
这篇文章,不是教大家怎么用,因为是什么,怎么用,React文档都说的明明白白,如果你现在还没养成看文档的习惯,那么我建议你现在就去看一下。

新的思想

开头我其实是想讲讲useHook给我带来的改变。
TL;DR 以前开发,是以行为作为划分依据来划分逻辑,代码之间过分耦合;现在,是以功能为维度去编写开发,更加各司其职,更易于测试。
这边以一个简单的列表页为例,说说我以前的写法和现在的写法。
假设我们要开发一个中台系统的商品列表页,那么它涉及几个模块

  1. 搜索模块
    主要对商品的名称,金额之类的进行查询;
  2. tabs标签页,这个标签页主要是将商品从流程开始到结束划分成几种状态,例如,从商品被买家下订单,到付款,到发货,到确认收货,这直接的各个流程状态,作为一个个标签页;
  3. 第三个模块也就是这个商品列表,包含一个表格,用来展示商品,以及一个分页器,也就是表格的页码之类。
    组件就不谈了,直接说逻辑处理吧。这里总共有三个交互,搜索的时候点击查询按钮交互,切换tab按钮的交互以及表格分页器的交互。

以前的方法

以前我的开发逻辑,其实就是很单纯的,根据直觉的去开发。搜索我就包含两块,第一将搜索数据保存在state里,紧接着执行数据请求;切换tab也是第一步将tab状态保存在state里,紧接着执行数据请求;执行分页呢,就改变分页状态,紧接着执行数据请求。
伪代码如下,非常的直观:

// 查询按钮点击 
function onSearchBtnClick(values) { 
  this.setState({values},this.query); 
} 
// tab标签页切换 
function onTabChange(tabStatus){ 
  this.setState({tabStatus}, this.query); 
} 
// 表格分页切换 
function onPageChange(pageNo,pageSize){ 
  this.setState({pageNo,pageSize}, this.query); 
} 
async function query(){ 
  const payload = { 
  ...this.state.values, 
  status: this.state.tabStatus, 
  pageNo: this.state.pageNo, 
  pageSize: this.state.pageSize 
}; 
const data = await fetchList(payload); 
  this.setState({data}); 
} 
// 通常刚进页面就要初始化数据 
componentDidMount() { 
  // 其他操作... 
  this.query(); 
} 

PS:setState第二个参数是个callback,该callback会在新的state真正被赋值时执行。
ok,到这边一直是我们常规的开发思路,看上去也没有任何问题。
现在我们做一个大胆的假设,来给上面这个页面脱掉一层衣服。 现在假设我们这个页面是一个没有数据请求的页面,就是单纯的点击搜索按钮就是将搜索表单状态保存,点击分页就是保存到分页状态,只是将所有的组件作为纯展示性的受控组件,代码会是什么样的:

// tab标签页切换 
function onTabChange(tabStatus){ 
  this.setState({tabStatus}); 
} 
// 表格分页切换 
function onPageChange(pageNo,pageSize){ 
  this.setState({pageNo,pageSize}); 
} 
// 组件 
<SearchForm onClick={onSearchBtnClick} /> 
<Tab 
  status={this.statetabStatus} 
  onChange={onTabChange} 
/> 
<Pagination 
  onChange={onPageChange} 
  pageSize={this.state.pageSize} 
  pageNo={this.state.pageNo} 
/> 

PS:由于查询按钮并不包含任何交互,因此这边直接忽视
现在看一看,这是不是每个组件的交互逻辑最纯粹的样子,不包含任何的副作用。
ok,现在我提出新的需求了,要在页面刚加载的时候加载数据。

componentDidMount(){ 
  query() 
} 

代码很简单,接下来我提出了新的需求,在用户点击查询按钮,切换分页,切换tab的时候进行数据请求,你该怎么做?
如果根据我们最初的逻辑,你是不是继续吭哧吭哧的在每个handle事件下面去增加query函数?那如果我继续加需求,在切换分页的时候动态改变document.title呢?
为什么要将side effect和正常的交互逻辑去柔和在一起呢。点击页面交互组件,将操作反馈响应到页面是handle的工作,把它们硬是根据用户的行为去柔和在一起,并不符合单一职责原则,并且也很不利于测试。除非这个按钮本身没有任何功能,专门用来加载数据,那说明逻辑很简单,简单的逻辑并不需要考虑这么多问题。
在这个例子中,我们应该将数据请求这一逻辑,和页面交互逻辑单独拆分出来思考。此时我们要想的是,有哪些情况会触发数据请求。注意我用的是触发,而不是执行。
很显然,当tab状态改变,分页改变,以及点击查询按钮的时候,就是触发数据请求的时候。
我举个生活中的例子,来区分根据行为划分东西以及根据功能划分东西的区别。
我们人都要吃饭,吃饭这一行为涉及到哪些东西,烧饭,烧饭要什么,要厨具;我们的菜要装吧,所以要餐具;吃完我要洗餐具,就要洗洁精抹布这些东西;吃完饭我可能有点口渴要喝口水,那就要杯子。这里如果根据行为划分,就会将厨具餐具洗洁精抹布和杯子这些东西全都放在一起;那根据功能划分呢?烧饭就是厨具,所有厨具放在一起;碟子碗筷这些作为餐具也放在一起,等等。前者是混乱的,而后者是高内聚低耦合的。假设我今天想烧饭但是我不想洗碗,我想让我妈洗,我只要去把厨具拿出来用就好了,而不是从一堆锅碗瓢盆里去找到底哪个才是我需要的。
同样,我洗碗这个逻辑,只要有脏的碗我可以洗;并且在后期我还可以将洗碗进行更深一步的抽象,抽象成洗,我不仅可以洗碗,我还可以洗衣服。
因此如果根据触发情况来写,最后我们的数据请求代码会变成这样:

componentDidMount() { 
  query() 
} 
componentDidUpdate(_, prevState) { 
if(prevState.pageNo !== this.state.pageNo || 
   prevState.pageSize !== this.state.pageSize || 
   preState.tabStatus !== this.state.tabStatus || 
   prevState.values !== this.state.values){ 
  query() 
} 
} 

大家也看到了,一个页面不只只有一个副作用,因此并不是每次的状态改变都需要触发query,所以我们需要很啰嗦的去判断状态是否改变。并且,同样一个数据查询,我们写在了两个函数里,并且这两个函数的逻辑,随着页面越来越复杂,也会越来越臃肿,于是React Hooks应运而生

新的方法

useEffect(()=>{ 
  query() 
},[pageNo, pageSize, tabStatus, values]) 

上面就讲query这个逻辑从两个硬生生的生命周期钩子里脱离开,成为一个独立的逻辑。如果整个代码逻辑完全使用React Hooks,会是下面这样:

function List(){ 
  const [values, setValues] = useState({}); 
  const [tabStatus, setStatus] = useStatus(0); 
  const [pagination, setPagination] = useStatus({ 
    pageNo: 1, 
    pageSize: 10 
  }); 
  useEffect(()=>{ 
    query() 
  },[values, tabStatus, pagination]) 
  return ( 
    <Fragment> 
      <SearchForm onClick={setValues} /> 
      <Tab 
        status={status} 
        onChange={setStatus} 
      /> 
      <Pagination 
        onChange={setPagination} 
        pageSize={pageSize} 
        pageNo={pageNo} 
      /> 
    </Fragment> 
  ); 
} 

结语

当然,React Hooks的优点有很多,但是大部分在官方文档和Dan的博文上都有写到,建议大家有情况都去看看,以上只是我通过自己的实践总结出来的一些想法,希望大家一起讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,368评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,941评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,369评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,848评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,719评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,505评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,904评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,528评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,819评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,848评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,652评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,468评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,912评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,095评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,389评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,906评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,120评论 2 339

推荐阅读更多精彩内容