前言

在 React 的世界中,有容器组件和 UI 组件之分,在 React Hooks 出现之前,UI 组件我们可以使用函数,无状态组件来展示 UI,而对于容器组件,函数组件就显得无能为力,我们依赖于类组件来获取数据,处理数据,并向下传递参数给 UI 组件进行渲染。在我看来,使用 React Hooks 相比于从前的类组件有以下几点好处:

  1. 代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护
  2. 组件树层级变浅,在原本的代码中,我们经常使用 HOC/render props 等方式来复用组件的状态,增强功能等,无疑增加了组件树层数及渲染,而在 React Hooks 中,这些功能都可以通过强大的自定义的 Hooks 来实现

React 在 v16.8 的版本中推出了 React Hooks 新特性,虽然社区还没有最佳实践如何基于 React Hooks 来打造复杂应用(至少我还没有),凭借着阅读社区中大量的关于这方面的文章,下面我将通过十个案例来帮助你认识理解并可以熟练运用 React Hooks 大部分特性。

useState 保存组件状态

在类组件中,我们使用 this.state 来保存组件状态,并对其修改触发组件重新渲染。比如下面这个简单的计数器组件,很好诠释了类组件如何运行:

`import` `React from` `"react"``;`

`class` `App` `extends` `React.Component {`

 `constructor(props) {`

 `super``(props);`

 `this``.state = {`

 `count: 0,`

 `name:` `"alife"`

 `};`

 `}`

 `render() {`

 `const { count } =` `this``.state;`

 `return` `(`

 `<div>`

 `Count: {count}`

 `<button onClick={() =>` `this``.setState({ count: count + 1 })}>+</button>`

 `<button onClick={() =>` `this``.setState({ count: count - 1 })}>-</button>`

 `</div>`

 `);`

 `}`

`}`

一个简单的计数器组件就完成了,而在函数组件中,由于没有 this 这个黑魔法,React 通过 useState 来帮我们保存组件的状态。

`import` `React, { useState } from` `"react"``;`

`function` `App() {`

 `const [obj, setObject] = useState({`

 `count: 0,`

 `name:` `"alife"`

 `});`

 `return` `(`

 `<div className=``"App"``>`

 `Count: {obj.count}`

 `<button onClick={() => setObject({ ...obj, count: obj.count + 1 })}>+</button>`

 `<button onClick={() => setObject({ ...obj, count: obj.count - 1 })}>-</button>`

 `</div>`

 `);`

`}`

通过传入 useState 参数后返回一个带有默认状态和改变状态函数的数组。通过传入新状态给函数来改变原本的状态值。值得注意的是 useState 不帮助你处理状态,相较于 setState 非覆盖式更新状态,useState 覆盖式更新状态,需要开发者自己处理逻辑。(代码如上)

似乎有个 useState 后,函数组件也可以拥有自己的状态了,但仅仅是这样完全不够。

useEffect 处理副作用

函数组件能保存状态,但是对于异步请求,副作用的操作还是无能为力,所以 React 提供了 useEffect 来帮助开发者处理函数组件的副作用,在介绍新 API 之前,我们先来看看类组件是怎么做的:

`import` `React, { Component } from` `"react"``;`

`class` `App` `extends` `Component {`

 `state = {`

 `count: 1`

 `};`

 `componentDidMount() {`

 `const { count } =` `this``.state;`

 `document.title =` `"componentDidMount"` `+ count;`

 `this``.timer = setInterval(() => {`

 `this``.setState(({ count }) => ({`

 `count: count + 1`

 `}));`

 `}, 1000);`

 `}`

 `componentDidUpdate() {`

 `const { count } =` `this``.state;`

 `document.title =` `"componentDidMount"` `+ count;`

 `}`

 `componentWillUnmount() {`

 `document.title =` `"componentWillUnmount"``;`

 `clearInterval(``this``.timer);`

 `}`

 `render() {`

 `const { count } =` `this``.state;`

 `return` `(`

 `<div>`

 `Count:{count}`

 `<button onClick={() => clearInterval(``this``.timer)}>clear</button>`

 `</div>`

 `);`

 `}`

`}`

在例子中,组件每隔一秒更新组件状态,并且每次触发更新都会触发 document.title 的更新(副作用),而在组件卸载时修改 document.title(类似于清除)

从例子中可以看到,一些重复的功能开发者需要在 componentDidMount 和 componentDidUpdate 重复编写,而如果使用 useEffect 则完全不一样。

`import` `React, { useState, useEffect } from` `"react"``;`

`let` `timer =` `null``;`

`function` `App() {`

 `const [count, setCount] = useState(0);`

 `useEffect(() => {`

 `document.title =` `"componentDidMount"` `+ count;`

 `},[count]);`

 `useEffect(() => {`

 `timer = setInterval(() => {`

 `setCount(prevCount => prevCount + 1);`

 `}, 1000);`

 `// 一定注意下这个顺序:`

 `// 告诉react在下次重新渲染组件之后,同时是下次执行上面setInterval之前调用`

 `return` `() => {`

 `document.title =` `"componentWillUnmount"``;`

 `clearInterval(timer);`

 `};`

 `}, []);`

 `return` `(`

 `<div>`

 `Count: {count}`

 `<button onClick={() => clearInterval(timer)}>clear</button>`

 `</div>`

 `);`

`}`
我们使用 useEffect 重写了上面的例子,**useEffect 第一个参数接收一个函数,可以用来做一些副作用比如异步请求,修改外部参数等行为,而第二个参数称之为dependencies,是一个数组,如果数组中的值变化才会触发 执行useEffect 第一个参数中的函数。返回值(如果有)则在组件销毁或者调用函数前调用**。

*   1.比如第一个 useEffect 中,理解起来就是一旦 count 值发生改变,则修改 documen.title 值;
*   2.而第二个 useEffect 中传递了一个空数组[],这种情况下只有在组件初始化或销毁的时候才会触发,用来代替 componentDidMount 和 componentWillUnmount,慎用;
*   3.  还有另外一个情况,就是不传递第二个参数,也就是useEffect只接收了第一个函数参数,代表不监听任何参数变化。每次渲染DOM之后,都会执行useEffect中的函数。

基于这个强大 Hooks,我们可以模拟封装出其他生命周期函数,比如 componentDidUpdate 代码十分简单

lucky_qi
29 声望0 粉丝