React hooks已经出来有一段时间了,但是许多React开发人员对它们的态度并不是很积极。我发现这背后主要有两个原因。第一个原因是许多React开发人员已经参与了一个大型项目,需要付出巨大的努力才能迁移整个代码库。另一个原因是大家对React类已经很熟悉了。有足够经验的话,继续使用类会感到更自在。
在本文中,我们将探讨考虑使用React Hooks的六个原因。
经常会有这种情况,那就是一个React组件从一个函数式组件开始开发,一开始这个函数式组件只依赖props,后来演变为具有状态的类组件。从函数式组件更改为类组件需要一些重构工作,具体取决于组件的复杂程度。
使用React Hooks时,由于函数式组件具有进入状态的能力,因此重构工作会非常少。来看以下示例,这是一个哑组件,它会显示一个带有计数的标签。
export function ShowCount(props) {
return (
<div>
<h1> Count : {props.count} </h1>
</div>
);
}
ShowCount函数式组件
假设我们需要通过点击鼠标来增加计数,并假设这只会影响这一个组件。第一步,我们需要将状态引入组件。我们看一下如何使用基于类的方法。
export class ShowCount extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
componentDidMount() {
this.setState({
count: this.props.count
})
}
render() {
return (
<div>
<h1> Count : {this.state.count} </h1>
</div>
);
}
}
引入状态后的ShowCount组件
如果使用hooks,则相同的组件会是下面这样。
export function ShowCount(props) {
const [count, setCount] = useState();
useEffect(() => {
setCount(props.count);
}, [props.count]);
return (
<div>
<h1> Count : {count} </h1>
</div>
);
}
带hooks的ShowCount组件
人类和机器都会因为类而困惑
上面这句话来自React文档。造成这种混乱的原因之一是this关键字。如果你熟悉JavaScript,就会知道JavaScript中的this与其他语言并不完全一样。但在React Hooks这边,你完全不必操心this了。这对初学者和经验丰富的开发人员来说都是有益的。
分别使用hooks与类访问状态的对比
根据上面的示例,你可以看到访问状态时我们不再需要使用“this”。这样大家就都不会感到困惑了。
现在,对于前面提到的那个ShowCount组件,我们将引入一种方法,当用户单击标签时,该方法可以更新状态计数。
export class ShowCount extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.handleClickEvent = this.handleClickEvent.bind(this);
}
componentDidMount() {
this.setState({
count: this.props.count
});
}
handleClickEvent() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div>
<h1 onClick={this.handleClickEvent} > Count : {this.state.count} </h1>
</div>
);
}
}
具有Click事件处理程序的ShowCount组件
我们引入了handleClickEvent方法。要使用它,首先我们必须将其绑定到该组件的this上。
this.handleClickEvent = this.handleClickEvent.bind(this);
我们必须这样做,因为执行方法时的执行上下文是不一样的。对于初学者来说,这可能有点难以理解。
除了绑定所有方法之外,还有一些语法提案可以解决这个问题。例如,我们可以将这个函数重写为一个箭头函数。
handleClickEvent = () => {
this.setState({count: this.state.count + 1});
}
让我们看看如何使用hooks实现相同的功能。
export function ShowCount(props) {
const [count, setCount] = useState();
useEffect(() => {
setCount(props.count);
}, [props.count]);
function handleClickEvent() {
setCount(count + 1);
}
return (
<div>
<h1 onClick={handleClickEvent}> Count : {count} </h1>
</div>
);
}
hooks中带有事件处理程序的ShowCount组件
如你所见,我们只添加了这个函数。另外你可能会注意到,当我们使用事件处理程序时,已经在模板中删掉了this。
onClick={ handleClickEvent }
使用hooks时,逻辑和UI更容易分离。无需HOC或渲染props。hooks用更少的样板和更直观的UI和逻辑组合来优雅地做到这一点。
当使用Bit之类的工具和平台共享组件时,这种“优雅的分离”尤其重要,因为每个(独立共享的)组件在不同应用程序之间都会更容易理解、维护和重用。
Bit.dev上的共享React组件
复杂的组件会变得难以理解
使用基于类的方法,我们会有不同的生命周期方法,例如componentDidMount和componentDidUpdate等。让我们考虑一种情况,就是在componentDidMount中订阅服务A和B,然后在componentWillUnmount中取消订阅它们。随着时间的流逝,两种生命周期方法中都会包含许多逻辑,并且很难跟踪挂载与卸载过程中哪些部分是相关联的。
为了演示这一点,我们来创建一个基于RxJs的服务来获取计数。我们将使用这个服务来更新ShowCount示例中的计数。请注意,由于不再需要在click事件中更新组件,我们将删除handleClickEvent。
import { Subject } from "rxjs";
export function getCounts() {
const subject = new Subject();
let count = 0;
const interval = setInterval(() => {
if (count > 10 || subject.isStopped) {
clearInterval(interval);
subject.complete();
}
subject.next(count++);
}, 1000);
return subject;
}
getCounts函数
import { getCounts } from "./reactive-service";
export function ShowCount(props) {
const [count, setCount] = useState();
useEffect(() => {
const countServiceSubject = getCounts();
countServiceSubject.subscribe((count) => {
setCount(count);
});
return () => {
countServiceSubject.unsubscribe();
};
}, []);
return (
<div>
<h1> Count : {count} </h1>
</div>
);
}
带有Effect hook中getCounts方法的ShowCount组件
你可以看到在useEffect内部,我们包括了订阅以及相应的取消订阅逻辑。同样,如果我们需要引入更多的服务订阅或不相关的逻辑,则可以添加多个useEffect块,在逻辑上分离不同的关注点。
import { getCounts } from "./reactive-service";
export function ShowCount(props) {
const [count, setCount] = useState();
const [secondCount, setSecondCount] = useState(0);
useEffect(() => {
const countServiceSubject = getCounts();
countServiceSubject.subscribe((count) => {
setCount(count);
});
return () => {
countServiceSubject.unsubscribe();
};
}, []);
useEffect(() => {
setSecondCount(secondCount + 1);
}, []);
return (
<div>
<h1> Count : {count} </h1>
<h1> Second Count: {secondCount} </h1>
</div>
);
}
多个useEffect块可分离不相关的逻辑
使用基于类的方法时,我们很难在组件之间共享状态逻辑。考虑两个组件,这两个组件都必须从两个不同的数据源获取、排序和显示数据。即使两个组件具有相同的功能,它们之间也很难共享逻辑,因为这些组件具有不同的源和状态。
虽然我们可以使用渲染props和高阶组件来解决这个问题,但由于我们必须重构组件,这会引入额外的成本,到头来会变得更麻烦。
使用自定义React Hooks,你可以提取这些可重用的状态逻辑并分别测试它们。
我们可以从ShowCount示例中提取一个自定义hook。
import { useState, useEffect } from "react";
export function useCount(serviceSubject) {
const [count, setCount] = useState();
useEffect(() => {
serviceSubject.subscribe((count) => {
setCount(count);
});
return () => {
serviceSubject.unsubscribe();
};
}, [serviceSubject]);
return [count, setCount];
}
自定义hook来共享状态逻辑
使用上面的自定义hook,我们可以像下面这样重写ShowCount组件。注意,我们必须将数据源作为参数传递给这个自定义hook。
import { useCount } from "./use-count";
export function ShowCount(props) {
const [count, setCount] = useCount(props.serviceSubject);
useEffect(() => {
setCount(-1);
}, [setCount]);
return (
<div>
<h1> Count : {count} </h1>
</div>
);
}
ShowCount组件,带有数据源参数
请注意,我们在父组件,而不是ShowCount组件中调用getCounts。否则,serviceSubject每次运行ShowCount时都将有一个新值,而我们将无法获得预期的结果。
切换到React Hooks的理由有很多,但我已经提到了其中一些最令人信服的原因,这些足够让我改用React Hooks了。如果查看官方文档,你会发现React Hooks有很多有趣的功能。请大家也谈一谈自己的React Hooks之旅吧。
你可以在此处找到完整的源代码。
原文链接:
https://blog.bitsrc.io/6-reasons-to-use-react-hooks-instead-of-classes-7e3ee745fe04
领取专属 10元无门槛券
私享最新 技术干货