React Hooks 加持下的函数组件设计

时间: 2020-05-26阅读: 66标签: Hooks

有了 react Hooks 的加持,妈妈再也不用担心函数组件记不住状态

过去,react 中的函数组件都被称为无状态函数式组件(stateless functional component),这是因为函数组件没有办法拥有自己的状态,只能根据 Props 来渲染 UI ,其性质就相当于是类组件中的 render 函数,虽然结构简单明了,但是作用有限。

但自从 react Hooks 横空出世,函数组件也拥有了保存状态的能力,而且也逐渐能够覆盖到类组件的应用场景,因此可以说 react Hooks 就是未来 react 发展的方向。


react Hooks 解决了什么问题

复杂的组件难以分拆

我们知道组件化的思想就是将一个复杂的页面/大组件,按照不同层次,逐渐抽象并拆分成功能更纯粹的小组件,这样一方面可以减少代码耦合,另外一方面也可以更好地复用代码;但实际上,在使用 react 的类组件时,往往难以进一步分拆复杂的组件,这是因为逻辑是有状态的,如果强行分拆,会令代码复杂性急剧上升;如使用 HOC 和 Render Props 等设计模式,这会形成“嵌套地狱”,使我们的代码变得晦涩难懂。

状态逻辑复杂,给单元测试造成障碍

这其实也是上一点的延续:要给一个拥有众多状态逻辑的组件写单元测试,无疑是一件令人崩溃的事情,因为需要编写大量的测试用例来覆盖代码执行路径。

组件生命周期繁复

对于类组件,我们需要在组件提供的生命周期钩子中处理状态的初始化、数据获取、数据更新等操作,处理起来本身逻辑就比较复杂,而且各种“副作用”混在一起也使人头晕目眩,另外还很可能忘记在组件状态变更/组件销毁时消除副作用。

react Hooks 就是来解决以上这些问题的

  • 针对状态逻辑分拆复用难的问题:其实并不是 react Hooks 解决的,函数这一形式本身就具有逻辑简单、易复用等特性。
  • 针对组件生命周期繁复的问题:react Hooks 屏蔽了生命周期这一概念,一切的逻辑都是由状态驱动,或者说由数据驱动的,那么理解、处理起来就简单多了。

利用自定义 Hooks 捆绑封装逻辑与相关 state

我认为 React Hooks 的亮点不在于 React 官方提供的那些 API ,那些 API 只是一些基础的能力;其亮点还是在于自定义 Hooks —— 一种封装复用的设计模式。

例如,一个页面上往往有很多状态,这些状态分别有各自的处理逻辑,如果用类组件的话,这些状态和逻辑都会混在一起,不够直观:

class Com extends React.Component {
    state = {
        a: 1,
        b: 2,
        c: 3,
    }
    
    componentDidMount() {
        handleA()
        handleB()
        handleC()
    }
}

而使用 React Hooks 后,我们可以把状态和逻辑关联起来,分拆成多个自定义 Hooks ,代码结构就会更清晰:

function useA() {
    const [a, setA] = useState(1)
    useEffect(() => {
        handleA()
    }, [])
    
    return a
}

function useB() {
    const [b, setB] = useState(2)
    useEffect(() => {
        handleB()
    }, [])
    
    return b
}

function useC() {
    const [c, setC] = useState(3)
    useEffect(() => {
        handleC()
    }, [])
    
    return c
}

function Com() {
    const a = useA()
    const b = useB()
    const c = useC()
}

我们除了可以利用自定义 Hooks 来拆分业务逻辑外,还可以拆分成复用价值更高的通用逻辑,比如说目前比较流行的 Hooks 库:react-use;另外,React 生态中原来的很多库,也开始提供 Hooks API ,如 react-router 。


忘记组件生命周期吧

React 提供了大量的组件生命周期钩子,虽然在日常业务开发中,用到的不多,但光是 componentDidUpdate 和 componentWillUnmount 就让人很头痛了,一不留神就忘记处理 props 更新组件销毁需要处理副作用的场景,这不仅会留下肉眼可见的 bug ,还会留下一些内存泄露的隐患。

类 MVVM 框架讲究的是数据驱动,而生命周期这种设计模式,就明显更偏向于传统的事件驱动模型;当我们引入 React Hooks 后,数据驱动的特性能够变得更纯粹。

处理 props 更新

下面我们以一个非常典型的列表页面来举个例子:

class List extends Component {
  state = {
    data: []
  }
  fetchData = (id, authorId) => {
    // 请求接口
  }
  componentDidMount() {
    this.fetchData(this.props.id, this.props.authorId)
    // ...其它不相关的初始化逻辑
  }
  componentDidUpdate(prevProps) {
    if (
      this.props.id !== prevProps.id ||
      this.props.authorId !== prevProps.authorId // 别漏了!
    ) {
      this.fetchData(this.props.id, this.props.authorId)
    }
    
    // ...其它不相关的更新逻辑
  }
  render() {
    // ...
  }
}

上面这段代码有3个问题:

  • 需要同时在两个生命周期里执行几乎相同的逻辑。
  • 在判断是否需要更新数据的时候,容易漏掉依赖的条件。
  • 每个生命周期钩子里,会散落大量不相关的逻辑代码,违反了高内聚的原则,影响阅读代码的连贯性。

如果改成用 React Hooks 来实现,问题就能得到很大程度上的解决了:

function List({ id, authorId }) {
    const [data, SetData] = useState([])
    const fetchData = (id, authorId) => {}
    useEffect(() => {
        fetchData(id, authorId)
    }, [id, authorId])
}

改用 React Hooks 后:

  • 我们不需要考虑生命周期,我们只需要把逻辑依赖的状态都丢进依赖列表里, React 会帮我们判断什么时候该执行的。
  • React 官方提供了 eslint 的插件来检查依赖项列表是否完整。
  • 我们可以使用多个 useEffect ,或者多个自定义 Hooks 来区分开多个无关联的逻辑代码段,保障高内聚特性。

处理副作用

最常见的副作用莫过于绑定 DOM 事件:

class List extends React.Component {
    handleFunc = () => {}
    componentDidMount() {
        window.addEventListener('scroll', this.handleFunc)
    }
    componentWillUnmount() {
        window.removeEventListener('scroll', this.handleFunc)
    }
}

这块也还是会有上述说的,影响高内聚的问题,改成 React Hooks :

function List() {
    useEffect(() => {
        window.addEventListener('scroll', this.handleFunc)
    }, () => {
        window.removeEventListener('scroll', this.handleFunc)
    })
}

而且比较绝的是,除了在组件销毁的时候会触发外,在依赖项变化的时候,也会执行清除上一轮的副作用。


利用 useMemo 做局部性能优化

在使用类组件的时候,我们需要利用 componentShouldUpdate 这个生命周期钩子来判断当前是否需要重新渲染,而改用 React Hooks 后,我们可以利用 useMemo 来判断是否需要重新渲染,达到局部性能优化的效果:

function List(props) => {
  useEffect(() => {
    fetchData(props.id)
  }, [props.id])

  return useMemo(() => (
    // ...
  ), [props.id])
}

在上面这段代码中,我们看到最终渲染的内容是依赖于props.id,那么只要props.id不变,即便其它 props 再怎么办,该组件也不会重新渲染。


依靠 useRef 摆脱闭包

在我们刚开始使用 React Hooks 的时候,经常会遇到这样的场景:在某个事件回调中,需要根据当前状态值来决定下一步执行什么操作;但我们发现事件回调中拿到的总是旧的状态值,而不是最新状态值,这是怎么回事呢?

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

  const log = () => {
    setCount(count + 1);
    setTimeout(() => {
      console.log(count);
    }, 3000);
  };

  return (
    <button onClick={log}>报数</button>
  );
}

/*
    如果我们在三秒内连续点击三次,那么count的值最终会变成 3,而随之而来的输出结果是?
    0
    1
    2
 */

“这是 feature 不是 bug ”,哈哈哈,说是 feature 可能也不太准确,因为这不正是 JavaScript 闭包的特性吗?当我们每次往setTimeout里传入回调函数时,这个回调函数都会引用下当前函数作用域(此时 count 的值还未被更新),所以在执行的时候打印出来的就会是旧的状态值。

类组件是怎么实现的?

那为啥类组件中,每次都能取到最新的状态值呢?这是因为我们在类组件中取状态值都是从this.state里取的,这相当于是类组件的一个执行上下文,永远都是保持最新的。

借助 useRef 共享修改

通过useRef创建的对象,其值只有一份,而且在所有 Rerender 之间共享

听上去,这 useRef 其实跟 this.state 很相似嘛,都是一个可以一直维持的值,那我们就可以用它来维护我们的状态了:

function Counter() {
  const count = useRef(0);

  const log = () => {
    count.current++;
    setTimeout(() => {
      console.log(count.current);
    }, 3000);
  };

  return (
    <button onClick={log}>报数</button>
  );
}

/*
    3
    3
    3
 */

站长推荐

1.云服务推荐: 国内主流云服务商,各类云产品的最新活动,优惠券领取。地址:阿里云腾讯云华为云

2.广告联盟: 整理了目前主流的广告联盟平台,如果你有流量,可以作为参考选择适合你的平台点击进入

链接: http://www.fly63.com/article/detial/9342

React-Hooks

以下是上一代标准写法类组件的缺点,也正是hook要解决的问题,型组件很难拆分和重构,也很难测试。业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。

React将引入Hooks,你怎么看?

近日,据 MIT Technology Review 报道,一位名为“Repairnator”的机器人在 GitHub 上“卧底”数月,查找错误并编写和提交修复补丁,结果有多个补丁成功通过并被采纳,这位 Repairnator 到底是如何拯救程序员于水火的呢?

在hooks中使用Mobx

创建store;注入store,这样既可以在class中使用,也可以在hooks中使用了;在class中使用;在hooks中使用

React Hooks实践

9月份开始,使用了React16.8的新特性React Hooks对项目进行了重构,果然,感觉没有被辜负,就像阮一峰老师所说的一样,这个 API 是 React 的未来。

如何用 Hooks 来实现 React Class Component 写法?

Hooks 的 API 可以参照 React 官网。本文主要是结合 Demo 详细讲解如何用 Hooks 来实现 React Class Component 写法,让大家更深的理解 Hooks 的机制并且更快的入门。 注意:Rax 的写法和 React 是一致的

用React hooks实现TDD

由于篇幅所限文章中并没有给出demo的所有代码,大家如果有兴趣可以将代码clone到本地从commit来看整个demo的TDD过程,配合文章来看会比较清晰,从进公司前认识了TDD,到实践TDD

React Hooks 你真的用对了吗?

从 React Hooks 正式发布到现在,我一直在项目使用它。但是,在使用 Hooks 的过程中,我也进入了一些误区,导致写出来的代码隐藏 bug 并且难以维护。这篇文章中,我会具体分析这些问题,并总结一些好的实践,以供大家参考

精通React今年最劲爆的新特性——React Hooks

你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗?你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗?你在还在为组件中的this指向而晕头转向吗?这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张。

react hooks系列之useRef

react hooks是 react 16.8 引入的特性,这里我们通过对react-hook-form进行分析来了解成熟的库是如何使用hook的。这将是一个系列,首先推荐 useRef

useEffect Hook 是如何工作的?

使用useEffect 就像瑞士军刀。它可以用于很多事情,从设置订阅到创建和清理计时器,再到更改ref的值。与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!

文章投稿关于web前端网站点搜索站长推荐网站地图站长QQ:522607023

小程序专栏: 土味情话心理测试脑筋急转弯幽默笑话段子句子语录成语大全运营推广