乐趣区

React-HooksUmiTypeScriptDva开发体验

前端时间,接触了 hooks,研究了一段时间后感觉使用起来十分方便,正好公司开了一个新的小项目,正好使用 hooks 来实践一下。

技术选型

1. 为什么选择 umi

在之前的文章中我也介绍过 umi 的优点,在使用过 umi 后,感觉自己的开发效率有很大的提升。umi 的路由使用起来实在是让我爱不释手,详细的我就不过多介绍了,有兴趣的可以去看我之前的文章。

2. 为什么选择 TypeScript

TypeScript 是 Microsoft 开发和维护的一种面向对象的编程语言。它是 JavaScript 的超集,包含了 JavaScript 的所有元素,可以载入 JavaScript 代码运行,并扩展了 JavaScript 的语法。在使用 TypeScript 编程时,它能帮助你在写代码的过程中考虑到各种类型上的问题,避免代码运行时出现的意想不到的错误。使用了 TypeScript 可以增强你代码的健壮性,特别是大型项目的开发中,某些小小的改动都有可能对你的项目造成严重的后果。

3. 为什么选择 Dva

其实使用 React Hooks 的目的就是为了去 redux,那我为什么还会使用基于 redux-soga 封装的 dva 呢?原因就在于 hooks 虽然很方便,但如果是一些很复杂的状态需要去管理,这时候使用 hooks 就会有点儿费劲了。所以这时候结合 dva 来解决这种特别复杂的状态管理是很方便的,原生的 redux 使用起来稍微有点儿麻烦,dva 用起来很简单,很爽,这就是我选择 dva 的原因。

4. 为什么选择 React Hooks

这个是这篇文章的重点了,你在 react 开发过程中有没有遇见这三个问题。

(一)在组件组件复用状态逻辑很难

React 没有提供将可复用性行为“附加”到组件的途径(例如,把组件连接到 store)。如果你使用过 React 一段时间,你也许会熟悉一些解决此类问题的方案,比如 render props 和 高阶组件。但是这类方案需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。如果你在 React DevTools 中观察过 React 应用,你会发现由 providers,consumers,高阶组件,render props 等其他抽象层组成的组件会形成“嵌套地狱”。尽管我们可以在 DevTools 过滤掉它们,但这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。

(二)复杂的组件变得难以理解

我们经常维护一些组件,组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致。在多数情况下,不可能将组件拆分为更小的粒度,因为状态逻辑无处不在。这也给测试带来了一定挑战。同时,这也是很多人将 React 与状态管理库结合使用的原因之一。但是,这往往会引入了很多抽象概念,需要你在不同的文件之间来回切换,使得复用变得更加困难。

(三)难以理解的 class

除了代码复用和代码管理会遇到困难外,class 是学习 React 的一大屏障。你必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。没有稳定的语法提案,这些代码非常冗余。大家可以很好地理解 props,state 和自顶向下的数据流,但对 class 却一筹莫展。即便在有经验的 React 开发者之间,对于函数组件与 class 组件的差异也存在分歧,甚至还要区分两种组件的使用场景。

如果你也被这三种问题所困扰,这时候接触 hooks,你会发现打开了新世界的大门。从面向对象编程转为函数式编程,我感觉释放了自己,写代码变得又爽又飞快。

项目搭建

因为使用了 umi,所以该项目也是用 umi 来搭建的,具体方法可查看之前文章。选择 ts 版本,然后根据自己编程习惯,配置一下 tslint 规则就 ok 了。
这里要注意一件重要的事情,升级 react 和 react-dom 的版本为 16.8.0 以上,因为 umi 脚手架搭建的项目 react 版本为 16.7.0,而 16.8.0 是 react 正式支持 hook 的版本。

Hooks 使用

1.useState

首先我们来看一下官方的代码。

import React, {useState} from 'react';

function Example() {
  // 声明一个新的叫做“count”的 state 变量
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

使用起来十分简单,函数式编程让你不用再继承 Component,直接定义一个变量就行,可以给这个变量看做你之前写 react 中 state 的一个值。使用 Hooks 之后你不用再调用 setState 来更改 state 中的值,可以使用你自己定义的更改方法,上面代码中就是使用 setCount 来更改 count 的值,是不是很方便?

怎么在 hooks 中使用 TypeScript?

如果你使用 Component 来编写你的组件,你需要通过 interface 来定义你 state 中值的类型,使用 Hooks,你只需要这样:

const [count, setCount] = useState<number>(0)

2.useEffect

Effect Hook 可以让你在函数组件中执行副作用操作,下面是官方代码。

import React, {useState, useEffect} from 'react';

function Example() {const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

这段代码中使用了 useEffect,可以让你在屏幕中显示你点击的次数。useEffect 最大的好处我认为就是去生命周期钩子函数了,使用之后你不用再使用任何生命周期钩子函数,这一点来看是不是就特别爽?useEffect 为什么会实现去生命周期钩子的作用呢?你可以在函数中写一个 console.log,查看控制台后你便会发现控制台会一直打印你的 console.log, 所以 useEffect 会在组件的生命周期中一直被调用,我们在使用的时候可以告诉 useEffect 什么时候才会被调用来进行性能优化。比如上面代码我们可以这样修改:

useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
},[count])

这样修改后 useEffect 会在 count 的值发生改变后才被调用。

怎么清除在 useEffect 中调用的函数?

在开发过程中我们可能会使用到定时器,而异步队列中的定时器在组件被销毁后也会继续执行,这样便会造成内存泄漏,在 Component 中我们会调用 componentWillUnmount 函数来清除定时器,在 useEffect 中我们该怎么办呢?

import React, {useState, useEffect}  from 'react'
import moment from 'moment'
export default function () {const [nowTime, setNowTime] = useState(moment().format('YYYY 年 MM 月 DD 日 ddd HH:mm'))

  useEffect(() => {const timer = setInterval(() => {setNowTime(moment().format('YYYY 年 MM 月 DD 日 ddd HH:mm:ss'))
    }, 1000);
    return () => {clearInterval(timer);
    }
  },[nowTime])

  return (<>{nowTime}</>
  )
}

上面代码就是一段很简单的显示当前时间的代码,我们可以通过 return 函数在组件销毁的时候清除 useEffect 中的定时器

使用 Hooks 来编写一个表格组件

从后端获取数据,然后在表格中渲染应该是很常见的一个功能了,下面我们来看一下使用 Hooks 之后怎么写这种组件

import React, {useState, useEffect}  from 'react'
import {Table} from 'antd'
export default function () {const [tableData, setTableData] = useState<any[]>([])
  const [page, setPage] = useState<number>(1)
  const [pageSize, setPageSize] = useState<number>(15)
  const [count, setCount] = useState<number>(0)
  const [loading,setLoading] = useState<boolean>(true)

  useEffect(() => {fetch('www.baidu.com').then(function(data) {if(data) {setTableData(data.result)
          setCount(data.count)
          setLoading(false)
        }
    })
  },[page,pageSize])

  const onChangePage = (pageNumber: any) => setPage(pageNumber)

  const onChangePageSize = (value: number) => setPageSize(value)

  const columns = [
    {
      title: 'ID',
      dataIndex: 'id'
    },
    {
      title: '姓名',
      dataIndex: 'name'
    },
    {
      title: '电话',
      dataIndex: 'tel',
    },
    {
      title: '性别',
      dataIndex: 'gender',
    },
    {
      title: '年龄',
      dataIndex: 'age',
    },
  ]

  return (
    <>
      <Table
        bordered={false}
        rowKey={(r, i) => (i + '')}
        columns={columns}
        loading={loading}
        dataSource={tableData.length ? tableData : []}
        pagination={false}
        style={{marginTop: 10}}
        size="small"
      />
      <Row>
        <Col span={8}>
          <span> 搜索到 {props.count} 条数据 </span>
          <span style={{margin: '0 20px'}}> 共 {Math.ceil(count / pageSize)} 页 </span>
          <Select defaultValue={15} onChange={onChangePageSize}>
            <Option value={10}>10 条 / 页 </Option>
            <Option value={15}>15 条 / 页 </Option>
            <Option value={20}>20 条 / 页 </Option>
            <Option value={30}>30 条 / 页 </Option>
          </Select>
        </Col>
        <Col span={14} push={2} style={{display:'flex', justifyContent:'flex-end', marginRight: 10}}>
           <Pagination 
             showQuickJumper={true} 
             current={page} 
             pageSize={pageSize} 
             total={count} 
             onChange={onChangePage}
           />
        </Col>
      </Row>
    </>
  )
}

这样就写完一个功能完善的表格组件了,回想一下你之前是用 Component 面向对象编程时怎么写的,再看一下用 Hooks 函数式编程的代码是不是简单很多?赶紧来试试 hooks 吧,会让你有写代码停不下来的感觉。

退出移动版