class组件和函数组件
我们希望编写代码的时候,尽可能将整块可复用的部分封装起来。这样可以一定程度提高代码的内聚性,将其耦合性,使得程序开发变得更加可维护。通常情况下,我们将代码块抽离成组件来实现封装。在React中,实现组件封装有两种方式,分别是通过class方式以及函数组件方式。
class方式:
class List extends React.Component {
constructor(props) {
super(props)
}
render() {
const { item } = this.props
return <h1>{item}</h1>
}
}
函数方式:
function List(props) {
const { item } = props
return <h1>{item}</h1>
}
从上面代码中可以看出,函数组件相比于class组件要简单很多。但是函数组件1. 没有生命周期。 2. 没有state和setState,只能接收props这就导致函数组件只能实现十分基础和简单的功能,稍微复杂一点的功能只能使用class组件来实现。
使用class组件有一个不太好的地方,因为生命周期的原因,导致状态逻辑常常发生割裂。例如,组件常常在 componentDidMount
和 componentDidUpdate
中获取数据。但是,同一个 componentDidMount
中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount
中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug,并且导致逻辑不一致[1]。
由于React工具的编程理念就是在倡导函数式编程[2],但目前的函数组件功能太简单,没有办法满足更复杂的开发需求,通过引入Hook可以增强函数组件的能力,并且规避class组件不好的地方。
State Hook
默认函数组件是没有state的,函数组件是一个纯函数,执行完即销毁,无法存储state。我们如果想要扩展函数组件的功能,可以使用"钩"的办法,把state功能"钩"到纯函数中[3]。因此称之为State Hook。
关于State Hook最基本使用方式,可以看这个参考文档:使用 State Hook
Effect Hook
函数组件第二个问题是没有生命周期,和上面介绍的通过使用State Hook来向函数组件引入state一样,我们可以通过Effect Hook向函数组件引入生命周期功能。
关于Effect Hook最基本使用方式,可以看这个参考文档:使用 Effect Hook
在阅读文档的时候,你可能会像我开始的时候一样,对副作用这个词一头雾水,这里,我再对副作用这个概念做一点个人理解的简要说明。首先要明确纯函数这个概念,满足以下几点要求的函数,我们就称之为纯函数[5]:
- 它应始终返回相同的值。不管调用该函数多少次,无论今天、明天还是将来某个时候调用它。
- 自包含(不使用全局变量)。
- 它不应修改程序的状态或引起副作用(修改全局变量)。
函数组件原本是纯函数,但使用Effect Hook之后,它可能会引起程序状态发生改变,破坏了函数组件纯函数的特点。因此我们会说,使用Effect Hook有的时候会带来副作用。比如你在函数组件生命周期里面定义了一个全局定时任务,函数组件销毁的时候却不销毁这个全局定时任务,这样就破坏了纯函数的特点,改变了整体程序状态。
最后我再说一点参考文档使用 Effect Hook中没有强调显著说明的地方。useEffect默认是模拟的DidMount
和DidUpdate
,比如说你写成这样的形式:
useEffect(() => {
console.log('我被执行啦!')
})
第一次是组件渲染的时候执行DidMount
会打印该条语句,之后组件更新是执行DidUpdate
组件更新又会打印该条语句。如果你只想要DidMount
而不想要DidUpdate
要写成这样的形式:
useEffect(() => {
console.log('我被执行啦!')
}, [])
第二点是如果写成下面这种形式,每一次组件在执行更新时,都会执行前一步的卸载。useEffect会在调用一个新的 effect 之前对前一个 effect 进行清理。
useEffect(() => {
console.log('我被执行啦!')
return () => {
console.log('我被卸载啦!')
}
})
useContext
通过上述两个Hook的使用,解决了函数组件最大的通点,大大增强了函数组件的能力。在官方文档的基础Hook中还有一个Hook是useContext。
它的作用就是获得父组件传递的数据。详情可以参考useContext
useReducer
useReducer是useState的替代方案,而不是替代Redux的。想了解关于Redux的更多内容可以参考: Redux 入门
关于useReducer,官方文档是这么说的:
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。
自定义Hook
当我们想在两个函数之间共享逻辑时,我们会把它提取到第三个函数中。而组件和 Hook 都是函数,所以也同样适用这种方式。自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。其内部可以使用useState useEffect获取其他Hooks,并且可以自定义返回结果,格式不限。 关于自定义Hook具体使用可以参考:自定义 Hook
Hook使用规范
关于Hook使用规范,可以参考:Hook 规则。简单来说,一共两条:
- 不要在循环,条件或嵌套函数中调用 Hook
- 只在 React 函数中调用 Hook
只看文档可能不太清楚,我这里再强调一下为什么一定不要在嵌套里使用Hook。因为函数组件是一个函数,有用完即销毁的特点。那些保存的变量会被清空,是没有记忆性的。所以,如果你用了判断语句,是不能保证每次useState
赋值是一致的。
参考资料
[1] Hook 简介
[2] 为什么 React 现在要推行函数式组件,用 class 不好吗?
[3] 使用 State Hook
[4] 使用 Effect Hook
[5] 什么是纯函数?它函数式编程的基础
[6] useContext
[7] Redux 入门
[8] useReducer
[9] 自定义 Hook
[10] Hook 规则