前言
React v16.8以前:
Class类式组件实例的三个核心属性:state、props、refs (主流)
函数式组件:props (没有state状态,功能有限)
React v16.8以后:
Hooks的出现,自此,函数组件成为 React 的新宠儿。
Hooks(钩子) 是 React v16.8 中的新增功能 ,只能在函数组件中使用
作用:
为函数组件提供状态、生命周期等原本 class 组件中提供的 React 功能
可以理解为通过 Hooks 为函数组件钩入 class 组件的特性
React v16.8 版本前后,组件开发模式的对比:
- React v16.8 以前: class 组件(提供状态) + 函数组件(展示内容)
- React v16.8 及其以后:
- class 组件(提供状态) + 函数组件(展示内容)
- Hooks(提供状态) + 函数组件(展示内容)
- 混用以上两种方式:部分功能用 class 组件,部分功能用 Hooks+函数组件
注意1:虽然有了 Hooks,但 React 官方并没有计划从 React 库中移除 class。
注意2:有了 Hooks 以后,不能再把函数组件称为无状态组件了,因为 Hooks 为函数组件提供了状态。
两个角度:组件的状态逻辑复用、class 组件自身的问题
componentDidMount -> window.addEventListener('resize', this.fn)componentWillUnmount -> window.addEventListener('resize', this.fn)
由于原来 React 中存在的问题,促使 React 需要一个更好的自带机制来实现组件状态逻辑复用。
从开发者角度:
- 语法更简单。
- 组件的解构更加清晰明了,按功能进行代码分区。
- 没有class类式组件中的生命周期。
- 避免了class类式组件的class语法和相关API,纯函数,要求更低。
- 复用组件状态逻辑,而无需更改组件层次结构。
从项目角度:
- 具有更好的 TS 类型推导。
- tree-shaking 友好,打包时去掉未引用的代码。
- 更好的压缩。
项目开发中,Hooks 的采用策略:
前言
问题:Hook 是什么? 一个 Hook 就是一个特殊的函数,让你在函数组件中获取状态等 React 特性
使用模式:函数组件 + Hooks
特点:从名称上看,Hook 都以 use 开头,useState Hook
useState 函数useState 函数,并传入状态的初始值(注意:useState 的初始值(参数)只会在组件第一次渲染时生效。)useState 函数的返回值中,拿到状态和修改状态的函数
class类式组件:

函数式组件useState Hook:

3.1、 使用数组解构简化
比如,要获取数组中的元素:
const arr = ['aaa', 'bbb']
const a = arr[0] // 获取索引为 0 的元素
const b = arr[1] // 获取索引为 1 的元素
const arr = ['aaa', 'bbb']
const [a, b] = arr
// a => arr[0]
// b => arr[1]
const [state, setState] = arr
useState 的使用
// 解构出来的名称可以是任意名称
const [state, setState] = useState(0)
const [age, setAge] = useState(0)
const [count, setCount] = useState(0)
3.2、 对象解构
// 1.
const {foo,bar}={foo:'hello',bar:"world"};
console.log(foo,bar);
// 2.如果变量名和属性名不一致,需要重命名
const {foo:baz}={foo:"hello",bar:"world"}; baz:hello
3.3、 字符串解构
// 1.可以使用对象解构或者是数组解构,使用数组结构可以获取指定字符;使用对象结构可以获取实例属性方法;
const [a,b,c]='hello';
console.log(a,b,c);h e l
useState的初值可以是任意数据类型。比如,数值、字符串、对象等。
const [user, setUser] = useState({
name: 'zs',
age: 18,
})
规则一:

规则二:
React Hooks 必须要每次组件渲染时,按照相同的顺序来调用所有的 Hooks。
为什么会有这样的规则? 因为 React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook
通过开发者工具可以查看到。


前言
副作用(side effect)是什么?
对于 React 组件来说:
主作用:就是根据数据(state/props)渲染 UI
副作用:比如,数据(Ajax)请求、手动修改 DOM、localStorage(token) 操作等等
当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了
注意:在实际开发中,副作用是不可避免的。因此,react 专门提供了 useEffect Hook 来处理函数组件中的副作用
useEffect 函数useEffect 函数,并传入参数(回调函数)
解释:
componentDidMount + componentDidUpdate
1. 不设置依赖项目:默认只要函数组件内有任意状态发生变化,就会触发useEffect

2. 依赖项为有元素的数组:可以为多个状态元素,只要其中一个状态元素发生变化,就会触发useEffect

3. 依赖项为空数组:相当于componentDidMount,只有组件加载完毕会执行一次

不要对useEffect的依赖项撒谎:
在useEffect回调函数中使用到的状态,必须在依赖项的数组中写明,useEffect才会生效(调用谁,依赖谁)
class类式组件:

函数式组件useState Hook:

有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。
内存泄漏:
内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
比如,组件开了计时器,组件已经销毁了,但是计时器没有销毁,占用内存,造成内存泄漏
在组件卸载时,要用到 effect 的返回值return了


解释:
// 1 将 resize 事件处理程序放在 effect 回调中,当前这个代码是没有问题的
useEffect(() => {
const handleResize = () => {
console.log('window 窗口大小改变了')
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
// 2 将 resize 事件处理程序拿到 useEffect 的外部,当前这个代码是没有问题的
const handleResize = () => {
console.log('window 窗口大小改变了')
}
useEffect(() => {
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
// 3 有依赖项的情况:
useEffect(() => {
// resize 事件的处理程序
const handleResize = () => {
console.log('window 窗口大小改变了', count)
}
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [count])
// 注意:此处的代码,会给一些警告!!! 不要按照这种方式写代码!!!
// 4 如果将 handleResize 放到了 useEffect 外部,React 会给以警告:
// 要么将 handleResize 放到 useEffect 中
// 要么使用 useCallback 这个 hook 来包裹 handleResize
// resize 事件的处理程序
const handleResize = () => {
console.log('window 窗口大小改变了', count)
}
useEffect(() => {
console.log('useeffect 执行了')
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [handleResize])
// 总结以上几种情况,推荐:在给 window 绑定事件时,将 事件处理程序放在 useEffect 内部。
在组件中,使用 useEffect Hook 发送请求获取数据(side effect):
useEffect(() => {
//在内部定义一个发请求的方法
const loadData = async () => {}
loadData()
}, [])
解释:
// 错误演示:
// 不要给 effect 添加 async
useEffect(async () => {}, [])
// https://github.com/facebook/react/issues/14326#issuecomment-441680293
useEffect(() => {
// 是否取消本次请求
let didCancel = false
//在内部定义一个发请求的方法
async function fetchMyAPI() {
let url = 'http://something/' + productId
let config = {}
const response = await myFetch(url)
// 如果开启其他请求,就忽略本次(过时)的请求结果
if (!didCancel) {
console.log(response)
}
}
fetchMyAPI()
return () => { didCancel = true } // 取消本次请求
}, [productId])
useEffect(() => {
const getList = async () => {
const res = await axios.get('http://geek.itheima.net/v1_0/channels')
setList(res.data.data.channels)
}
console.log(list)
getList()
}, [list])