React Hook迷思

96
光哥很霸气
1.0 2018.12.11 18:57 字数 1884

那啥,新年快乐

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的博文上都有写到,建议大家有情况都去看看,以上只是我通过自己的实践总结出来的一些想法,希望大家一起讨论。

web前端从入门到精通