React Hook迷思

作者: 光哥很霸气 | 来源:发表于2018-12-11 18:57 被阅读1次

    那啥,新年快乐

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

    相关文章

      网友评论

        本文标题:React Hook迷思

        本文链接:https://www.haomeiwen.com/subject/rcwehqtx.html