本文作者:布鲁托
1. 函数组件
hook是可以给函数组件中添加了state和副作用的机制,可以不编写class组件使用react提供的特性。
一个简单的计算器例子:
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在函数组件内使用了useState为函数组件引入了state,按钮点击后state更新,组件重新渲染最新的state。这个useState就是官方提供的hook,为了实现class组件中的功能,官方还提供了useEffect,useContext等。
2. 对比类组件
举一个当前很常见的组件:
-
进入当前组件(页面)开始请求api;
-
处理数据:统计、过滤或者重新聚合;
-
副作用:更新图表之类的;
-
事件监听与取消;
-
根据props更新组件。
2.1 componentDidMount
在组件挂载完成后执行一次。
类组件:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
originData = [];
}
}
componentDidMount() {
API().then(res => {
this.setState({
originData: res
});
})
}
render() {
return (
//...
)
}
}
函数组件:
function Example(props) {
const [originData, setOriginData] = useState([]);
useEffect(() => {
API().then(res => {
setOriginData(res);
});
}, []);
return (
// ...
)
}
在class组件中componentDidMount很清楚的标注了这个函数中的代码将在组件挂载完成后执行一次;函数组件中实现这样的功能是给useEffect加上第二个参数,一个空数组来实现。
2.2 对接口数据处理
类似于上面的例子,总之是要在请求成功后才可以操作。
2.3 执行副作用
类组件:
componentDidMount() {
this.chart = createChart();
this.chart.init();
}
componentDidUpdate(prevProps, prevState) {
if(prevProps.active !== this.props.active) {
this.chart.update();
}
}
函数组件:
const exampleRef = useRef(null);
useEffect(() => {
exampleRef.current = createChart();
exampleRef.current.init();
}, [])
useEffect(() => {
exampleRef.current.update();
}, [props.active]);
图表往往是依赖于一个实际的dom节点并在其上渲染的,因此初始化和更新必不可少。这个例子中,class组件实例会一直保存创建好的chart对象,可以很方便的在生命周期中使用;函数组件中如果用const变量保存,很明显每次组件更新都是一个新的chart对象,想要创建一个组件实例需要引入useRefhook,也许有更好的方式实现,但就这里来看还是支持class组件。
2.4 事件监听与取消
handleResize = () => {
// ...
}
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
函数组件:
useEffect(() => {
const handleResize = () => {
// ...
}
window.addEventListener('resize', this.handleResize);
return () => {
window.RemoveEventListener('resize', this.handleResize);
}
}, []);
为了不影响其他组件,在本组件监听全局性的事件后,在组件销毁的时候应该取消监听。这个例子中hook的优势要明显一点,同一个事件handle的监听和取消在代码组织形式上放在了一起,看起来更紧凑。
2.5 prop更新
类组件
componetDidUpdate(prevProps) {
if (prevProps.data !== this.props.data) {
// 一些相关的更新操作
}
}
函数组件:
useEffect(() => {
// 一些相关的更新操作
}, [props.data]);
如果存在多个props属性更新需要执行一些副作用时,使用hook就无需写一长串比较运算。
3. 总结
无论使用函数式或Class,选择哪种取决于个人喜好,而且官方也没有计划会取消Class组件。需要指出的一点是,在编写图表组件时,使用Class组件会更胜一筹,因为this可以随时保存图表实例或者一些计算方法;而hooks需要使用useRef保存实例,而对于依赖于props的计算方法,用useMemo或useCallback都会因props更新导致计算方法实例更新,对初始化造成影响,而且写法不够优雅。