为什么会存在React Hooks
本篇汇总React在16.8版本新增的Hooks相关内容,认识什么是Hooks、有哪些API、自定义Hook以及使用规范和注意事项。帮助大家提高自身对React的新功能了解。
一、认识Hooks
1、回顾函数组件
函数组件没有实例,也不能监听各个生命周期,也无法扩展属性和方法。只是输入props,输出jsx的纯函数。也没有state。
// class 组件
class List extends React.Component {
constructor(props) {
super(props)
}
render() {
const { list } = this.props
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
}
// 函数组件
function List(props) {
const { list } = this.props
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
2、class组件 vs 函数组件
class组件
- 大型组件很难拆分和重构,也很难测试。
- 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 组件类引入了复杂的编程模式,比如render props 和高阶组件。
函数组件
如果按照React函数式编程的思维来说,一个组件更像一个函数,而不像一个class,所以函数组件会看起来更简洁、符合逻辑。 但函数组件没有state,没有生命周期,还无法取代class组件。所以才有了Hooks。
3、state hook
函数组件必须要求是纯函数,不能有副作用。因此,要在函数组件使用state,不能直接使用,而是要“钩”进来,这就是Hooks,即useState。
使用useState的语法如下:
const [state, setState] = useState(initialState);
其中,state是我们定义的状态变量,initialState是状态的初始化值。setState是一个函数,用于更新状态。
举个例子,假设我们要在一个组件中使用计数器,可以这样写:
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default Counter;
在上面的例子中,我们使用useState定义了一个名为count的状态变量,并将其初始值设为0。在组件中,我们可以通过setCount函数来更新count的值。
当我们点击“Increment”按钮时,setCount函数会被调用,并将count的值加1。由于React会自动重新渲染组件,所以我们可以看到页面上的计数器会随着点击次数的增加而更新。
需要注意的是,useState返回的是一个数组,包含当前状态和更新状态的函数。在上面的例子中,我们使用了ES6的解构语法来将数组中的值分别赋给count和setCount。
4、effect hook
如何让函数组件有生命周期,例如组件经常会在didMount里发请求。
在React中,useEffect
可以用来模拟组件的生命周期函数。useEffect
接收两个参数,第一个参数是一个函数,称为Effect函数,它会在组件渲染后执行,第二个参数是一个数组,用于指定Effect函数依赖的变量,只有依赖的变量发生变化时,Effect函数才会重新执行。
下面是useEffect
模拟生命周期函数的示例:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 相当于 componentDidMount 和 componentDidUpdate
useEffect(() => {
console.log('Effect function is called');
// 相当于 componentWillUnmount
return () => {
console.log('Effect cleanup function is called');
};
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
在这个示例中,每次点击按钮时,count
的值会发生变化,由于我们在useEffect
的第二个参数中指定了count
,因此useEffect
会在每次count
变化时执行。同时,由于我们在Effect函数中返回了一个函数,所以在组件卸载时,该函数会被调用,相当于componentWillUnmount
生命周期函数。
二、其他Hooks API
-
useContext: 用于在函数组件中使用 context。它接收一个 context 对象作为参数,并返回该 context 的当前值。
-
useReducer: 用于在函数组件中使用 reducer。它接收一个 reducer 函数和一个初始状态作为参数,并返回一个数组,数组第一个元素是当前 state 的值,第二个元素是 dispatch 函数,用于触发 state 的更新。
-
useMemo: 用于在函数组件中进行性能优化。它接收一个回调函数和一个依赖项数组作为参数,并返回回调函数的计算结果。当依赖项数组中的任何一个值发生变化时,useMemo 会重新计算回调函数的结果。
-
useCallback: 用于在函数组件中进行性能优化。它接收一个回调函数和一个依赖项数组作为参数,并返回一个 memoized 的回调函数。当依赖项数组中的任何一个值发生变化时,useCallback 会返回一个新的 memoized 回调函数。
-
useRef: 用于在函数组件中创建一个 ref。它返回一个可变的 ref 对象,该对象的 current 属性指向传递给 useRef 的初始值。
三、自定义Hook
自定义hook是一个函数,其名称以“use”开头,它可以调用其他的hook。
自定义hook可以在不增加组件层级的情况下提供复用逻辑的能力。例如,如果你有两个组件需要获取相同的数据,你可以将获取数据的逻辑封装在一个自定义hook中,然后在这两个组件中使用这个自定义hook,而不是在每个组件中都写一遍相同的逻辑。
下面是一个自定义hook的示例,用于处理窗口大小的变化:
import { useState, useEffect } from 'react';
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
}
在这个示例中,我们使用useState来存储窗口的宽度和高度,然后使用useEffect来订阅窗口的resize事件,并在事件处理程序中更新窗口大小。最后,我们返回窗口大小的状态对象。
使用这个自定义hook非常简单,只需要在组件中调用它即可:
function MyComponent() {
const windowSize = useWindowSize();
return (
<div>
Window size: {windowSize.width} x {windowSize.height}
</div>
);
}
这个自定义hook可以在多个组件中复用,而不需要在每个组件中都写一遍相同的逻辑。
四、Hooks 使用规范
1、规则
- 只在代码最顶层使用Hook,不要在循环、判断和嵌套函数中使用。(以及不要在Hook之前return掉)
- 只在React函数组件,或自定义Hook中使用
- eslint 插件 eslint-plugin-react-hooks 可以解决以上问题
安装 npm install eslint-plugin-react-hooks --save-dev
,然后修改 eslint 配置文件
// ESLint 配置文件
{
"plugins": [
// ...此处省略 N 行...
"react-hooks"
],
"rules": {
// ...此处省略 N 行...
"react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
"react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
}
}
2、关于hook的调用顺序
在 React 中,Hook 的调用顺序非常重要,因为它会影响到组件的渲染结果。下面是 Hook 的调用顺序:
-
在函数组件的顶层,按照顺序调用所有的 Hook。
-
在每次组件更新时,也按照顺序调用所有的 Hook。
如果 Hook 的调用顺序不正确,可能会导致组件的状态出现错误,或者出现其他不可预料的问题。因此,我们需要严格按照调用顺序来使用 Hook。同时,也需要注意,每个 Hook 只能在函数组件的顶层或其他 Hook 中调用,不能在普通的 JavaScript 函数中调用。
五、Hooks组件逻辑复用
1、回顾在class组件使用的逻辑复用方法
所存在的问题如下:
- mixin:相互依赖和相互耦合
- 高阶组件HOC:使用过多,导致组件层级嵌套过多,不易渲染,更不易调试。并且可以劫持props,需要严格遵守约定。
- render-props:有一定学习成本和理解成本,并且只能传函数组件。
2、使用Hooks做组件逻辑复用
在React中,常用的Hooks包括useState、useEffect、useContext、useReducer、useMemo、useCallback和useRef。这些Hooks可以用于管理组件的状态、处理副作用、共享数据和优化性能等方面。
例如,如果我们需要在多个组件中处理窗口大小变化的逻辑,可以使用自定义hook来实现复用。下面是一个处理窗口大小变化的自定义hook的示例代码:
import { useState, useEffect } from 'react';
function useWindowSize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return windowSize;
}
export default useWindowSize;
在上面的示例中,我们使用useState来管理窗口大小的状态,使用useEffect来处理副作用。我们还使用useEffect的返回函数来清除监听器,以避免内存泄漏。
使用这个自定义hook,我们可以在多个组件中复用窗口大小变化的逻辑,而无需重复编写相同的代码。例如,我们可以在两个不同的组件中使用这个自定义hook:
import useWindowSize from './useWindowSize';
function Component1() {
const windowSize = useWindowSize();
return (
<div>
<p>Window width: {windowSize.width}</p>
<p>Window height: {windowSize.height}</p>
</div>
);
}
function Component2() {
const windowSize = useWindowSize();
return (
<div>
<p>Window width: {windowSize.width}</p>
<p>Window height: {windowSize.height}</p>
</div>
);
}
在上面的示例中,我们在两个不同的组件中使用了useWindowSize自定义hook,以获取窗口大小的状态。这样,我们就可以在多个组件中复用处理窗口大小变化的逻辑。
六、React Hooks 注意事项
-
Hook只能在函数组件和自定义Hook中使用,不能在类组件中使用。
-
在函数组件中使用Hook时,需要严格按照Hook的调用顺序来使用,不能在条件语句或循环语句中使用。
-
useState、useReducer、useContext等Hook返回的state变量和dispatch函数都是稳定的,不会随着组件的重新渲染而改变。
-
useEffect Hook用于处理副作用,需要注意副作用的依赖项,避免出现无限循环的情况。
-
useMemo和useCallback可以用来优化组件的性能,避免重复计算和重复创建函数。
-
useRef可以用来保存组件的状态,但是需要注意它与useState的区别,以及在函数组件中如何获取DOM元素。
-
自定义Hook可以用来复用组件逻辑,但是需要注意自定义Hook的命名规范和使用方式。
转载自:https://juejin.cn/post/7239206188164366397