一、React 为什么要搞 Hooks,React Hooks 帮我们解决了哪些问题 ?
1. 在组件之间复用状态逻辑很难
- 比如 render props 和 高阶组件。但是这类方案需要重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解。这说明了一个更深层次的问题:React 需要为共享状态逻辑提供更好的原生途径。
- 你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑。
- 使用 hook 其中一个目的就是要解决 class 组件中生命周期函数经常包含不相关的逻辑,但又把相关联的逻辑分离到了几个不同的方法中的问题
2. 复杂组件变的难以理解
- 每个生命周期常常包含一些不相干的逻辑
- 为了解决这个问题,Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测
3. 使用多个 Effect 实现关注点分离
- 使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题
- Hook 允许我们按照代码的用途分离他们, 而不是像生命周期函数那样。React 将按照 effect 声明的顺序依次调用组件中的每一个 effect
二、什么是 Hook
- Hook 是一些可以让你在函数组件里 勾入 React state 及生命周期等特性的函数
- Hook 不能在 class 组件中使用,只能在 React 的函数组件中调用 Hook
- 只能在函数最外层调用 Hook,不要再循环、条件判断、子函数中调用
- Hook 是一种复用状态逻辑的方式,它不复用 state 本身,事实上 Hook 的每次调用都有一个完全独立的 state —— 因此你可以在单个组件中多次调用同一个自定义 Hook
三、常用 hook 介绍
1、useState
- 在这里 useState 就是一个 Hook
- 通过在函数组件里调用它,来给组件里添加内部 state
- React 会在重复渲染时,保留这个 state
- useState 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其它地方调用这个函数
- 它类似 class 组件的 setState 但是它不会将新的 state 和旧的 state 进行合并
- useState 唯一的参数就是初始的 state 不同于 this.state 这里的 state 不一定是一个对象
- 这个初始 state 只有在第一次渲染的时候被用到
import React, { useState } from 'react'
const Hooks01 = () => {
const [count, setCount] = useState(0)
return (
<div>
<p>{count} times</p>
<button onClick={() => setCount(count + 1)}> click me </button>
</div>
)
}
export default Hooks01
2、useEffect
- 它跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API。
- React 会在每次渲染的时候调用 useEffect 函数,包括第一次渲染的时候
- 在组件卸载的时候调用 useEffect 的 return 函数
import React, { useState, useEffect } from 'react'
const Hooks02 = () => {
const [count, setCount] = useState(0)
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
document.title = `you click the btn ${count} times`
})
return (
<div>
<p>click me 02 { count } times</p>
<button onClick={() => setCount(count + 2)}>click me 02</button>
</div>
)
}
export default Hooks02
- 使用多个 Effect 实现关注点分离:通过使用 Hook,你可以把组件内相关的副作用组织在一起(例如创建订阅及取消订阅),而不要把它们拆分到不同的生命周期函数里
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
- 每次更新的时候都要运行 Effect:此默认行为保证了一致性,避免了在 class 组件中因为没有处理更新逻辑而导致常见的 bug
// Mount with { friend: { id: 100 } } props
ChatAPI.subscribeToFriendStatus(100, handleStatusChange); // 运行第一个 effect
// Update with { friend: { id: 200 } } props
ChatAPI.unsubscribeFromFriendStatus(100, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(200, handleStatusChange); // 运行下一个 effect
// Update with { friend: { id: 300 } } props
ChatAPI.unsubscribeFromFriendStatus(200, handleStatusChange); // 清除上一个 effect
ChatAPI.subscribeToFriendStatus(300, handleStatusChange); // 运行下一个 effect
// Unmount
ChatAPI.unsubscribeFromFriendStatus(300, handleStatusChange); // 清除最后一个 effect
- 通过跳过 Effect 进行性能优化:
- 在某些情况下,每次渲染后都执行清理或者执行 effect 可能会导致性能问题。在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决
- 如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可
- 如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
四、自定义 hook
- 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook
- 自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性
- 自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的
- 自定义 Hook 解决了以前在 React 组件中无法灵活共享逻辑的问题
网友评论