1.介绍
1-1 hooks:钩子-钩子函数:自动执行的函数 1-2 hoos是react v16.8中新增的功能 1-3 作用:为函数组件提供状态,生命周期等原本class组件中提供的react功能(可以理解为Hooks为函数组件购入class组件的特性) 1-4 注意:Hooks只能在函数组件中使用
2.为什么要有Hooks
1-1 组件状态逻辑的复用 1-2 解决class语法记忆成本高、this指向等问题
- 组件逻辑状态的复用:- 在 Hooks 之前,组件的状态逻辑复用经历了:mixins(混入)、HOCs(高阶组件)、render-props 等模式。- (早已废弃)mixins 的问题:1 数据来源不清晰 2 命名冲突。- HOCs、render-props 的问题:重构组件结构,导致组件形成 JSX 嵌套地狱问题。
- class自身的问题:
- 选择:函数组件和 class 组件之间的区别以及使用哪种组件更合适- 需要理解 class 中的 this 是如何工作的- 相互关联且需要对照修改的代码被拆分到不同生命周期函数中- componentDidMount -> window.addEventListener('resize', this.fn)- componentWillUnmount -> window.addEventListener('resize', this.fn)
- 相比于函数组件来说,不利于代码压缩和优化,也不利于 TS 的类型推导
正是由于 React 原来存在的这些问题,才有了 Hooks 来解决这些问题
3.优势
1-1 Hooks 只能在函数组件中使用,避免了 class 组件的问题 1-2 复用组件状态逻辑,而无需更改组件层次结构 1-3 根据功能而不是基于生命周期方法强制进行代码分割 1-4 抛开 React 赋予的概念来说,Hooks 就是一些普通的函数 1-5 具有更好的TS类型推导 1-6 tree- - shaking友好,打包时去掉未引用的代码 1-7 更好的压缩
项目开发中,Hooks 的采用策略:
- 不推荐直接使用 Hooks 大规模重构现有组件
- 推荐:新功能用 Hooks,复杂功能实现不了的,也可以继续用 class
- 找一个功能简单、非核心功能的组件开始使用 hooks
4.useState
1.概念
1-1 为函数组件提供状态(state) 1-2 使用场景:当你想要在函数组件中,使用组件状态时,就要使用 useState Hook 了
//当你想要在函数组件中,使用组件状态时,就要使用 useState Hook
//规则 if语句/for循环中/普通函数不允许调用useState
//react中非普通函数 1. 函数式组件 2.use开头的函数
import React, { useState } from 'react'
export default function App() {
//使用useState 解构获取数据
//第一个参数的数据 即是useState传的实参
//setCount提供的方法 可以修改第一个参数的数据
// 为函数组件提供状态(state)
const [count, setCount] = useState(1)
return (
<div>
{count}
<button onClick={() => { setCount(count + 1) }}>点击改变count</button>
</div>
);
}
- 参数:状态初始值。比如,传入 0 表示该状态的初始值为 0- 注意:此处的状态可以是任意值(比如,数值、字符串等),而 class 组件中的 state 必须是对象
- 返回值:数组,包含两个值:1 状态值(state) 2 修改该状态的函数(setState)
2.更新过程
函数组件使用 useState hook 后的执行过程,以及状态值的变化:
- 组件第一次渲染:1. 从头开始执行该组件中的代码逻辑2. 调用
useState(0)
将传入的参数作为状态初始值,即:03. 渲染组件,此时,获取到的状态 count 值为: 0 - 组件第二次渲染:1. 点击按钮,调用
setCount(count + 1)
修改状态,因为状态发生改变,所以,该组件会重新渲染2. 组件重新渲染时,会再次执行该组件中的代码逻辑3. 再次调用useState(0)
,此时 React 内部会拿到最新的状态值而非初始值,比如,该案例中最新的状态值为 14. 再次渲染组件,此时,获取到的状态 count 值为:1
注意:useState 的初始值(参数)只会在组件第一次渲染时生效。
也就是说,以后的每次渲染,useState 获取到都是最新的状态值。React 组件会记住每次最新的状态值!
3.添加多个状态
问题:如果一个函数组件需要多个状态,该如何处理? 回答:调用
useState
Hook 多次即可,每调用一次 useState Hook 可以提供一个状态。 注意:useState Hook 多次调用返回的 [state, setState] 相互之间,互不影响。
- 使用数组解构简化
useState
的使用- 约定:修改状态的函数名称以 set 开头,后面跟上状态的名称
// 解构出来的名称可以是任意名称
const [state, setState] = useState(0)
const [age, setAge] = useState(0)
const [count, setCount] = useState(0)
4.状态读取和修改
状态的使用:1 读取状态 2 修改状态
- 读取状态:该方式提供的状态,是函数内部的局部变量,可以在函数内的任意位置使用
- 修改状态:
setCount(newValue)
是一个函数,参数表示:新的状态值- 调用该函数后,将使用新的状态值
替换
旧值 - 修改状态后,因为状态发生了改变,所以,该组件会重新渲染
5.使用规则
1-1 React Hooks 只能直接出现在 函数组件 中,不能嵌套在 if/for/其他普通函数中! 1-2 React Hooks 必须要每次组件渲染时,按照相同的顺序来调用所有的 Hooks。 为什么会有这样的规则? 因为 React 是按照 Hooks 的调用顺序来识别每一个 Hook,如果每次调用的顺序不同,导致 React 无法知道是哪一个 Hook
5.useEffect
1.side Effect--副作用
1-1 使用场景:当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了 1-2 作用:处理函数组件中的副作用(side effect)
理解:副作用是相对于主作用来说的,一个功能(比如,函数)除了主作用,其他的作用就是副作用 对于 React 组件来说,主作用就是根据数据(state/props)渲染 UI,除此之外都是副作用(比如,手动修改 DOM)
React 组件的公式:UI = f(state)
常见的副作用(side effect)
- 数据(Ajax)请求、手动修改 DOM、localStorage 操作等
// 不带副作用的情况:
// 该函数的(主)作用:计算两个数的和
function add(a, b) {
return a + b
}
// 带副作用的情况:
let c = 1
function add(a, b) {
// 因为此处修改函数外部的变量值,而这一点不是该函数的主作用,因此,就是:side effect(副作用)
c = 2
return a + b
}
// 带副作用的情况:
function add(a, b) {
// 因为 console.log 会导致控制台打印内容,所以,也是对外部产生影响,所以,也是:副作用
console.log(a)
return a + b
}
// 没有副作用:
function getName(obj) {
return obj.name + Math.random()
}
// 有副作用:
function getName(obj) {
// 此处直接修改了参数的值,也是一个副作用
obj.name = '大飞哥'
return obj.name
}
const o = { name: '小马哥' }
fn(o)
2.基本使用
使用场景:当你想要在函数组件中,处理副作用(side effect)时,就要使用 useEffect Hook 了
作用:处理函数组件中的副作用(side effect) 注意:在实际开发中,副作用是不可避免的。因此,react 专门提供了 useEffect Hook 来处理函数组件中的副作用
useEffect
Hook 看做
componentDidMount
,
componentDidUpdate
和
componentWillUnmount
这三个函数的组合。
//useEffect初体验
import React, { useState, useEffect } from 'react'
export default function App() {
const [count, setCount] = useState(1)
//调用useEffect useEffect是一个自动执行的钩子函数 生命周期的阶段会执行
//语法:useEffect(回调函数)
useEffect(() => {
document.title = count
//挂载+更新+销毁的时候都会自动执行
console.log('我被执行了');
})
return (
<div>
{count}
<button onClick={() => { setCount(count + 1) }}>点击改变count</button>
</div>
);
}
解释:
- 参数:回调函数(称为 effect),就是在该函数中写副作用代码
- 执行时机:该 effect 会在每次组件更新(DOM更新)后执行
3.依赖项
1. []代表挂载时 只执行一次 使用场景:发请求 监听事件 开启定时器 useEffect(() => { document.title = count console.log('我被执行了'); }, []) 2. [依赖项] 代表:挂载时和更新时二合一,相当于componentDidMount和componentDidUpdate二合一 只会监听依赖项的改变 非依赖项不会触发 使用场景:缓存 3. 不传 不同:监听全部依赖项的变化 数据大时 会导致卡顿
//1.不传时
import React, { useState, useEffect } from 'react'
export default function App() {
const [count, setCount] = useState(1)
//调用useEffect useEffect是一个自动执行的钩子函数 生命周期的阶段会执行
//语法:useEffect(回调函数)
useEffect(() => {
document.title = count
console.log('我被执行了');
})
return (
<div>
{count}
<button onClick={() => { setCount(count + 1) }}>点击改变count</button>
</div>
);
}
//2.传空数组时
//useEffect初体验
import React, { useState, useEffect } from 'react'
export default function App() {
const [count, fn] = useState(1)
//调用useEffect useEffect是一个自动执行的钩子函数 生命周期的阶段会执行
//语法:useEffect(回调函数)
// 1. []代表挂载时 只执行一次
//使用场景:发请求 监听事件 开启定时器
useEffect(() => {
document.title = count
console.log('我被执行了');
}, [])
return (
<div>
{count}
<button onClick={() => { fn(count + 1) }}>点击改变count</button>
</div>
);
}
//3.[依赖项]
//useEffect初体验
import React, { useState, useEffect } from 'react'
export default function App() {
const [count, fn] = useState(1)
//调用useEffect useEffect是一个自动执行的钩子函数 生命周期的阶段会执行
//语法:useEffect(回调函数)
// 2. [依赖项] 代表:挂载时和更新时二合一,相当于componentDidMount和componentDidUpdate二合一
// 只会监听依赖项的改变 非依赖项不会触发 使用场景:缓存
useEffect(() => {
document.title = count
console.log('我被执行了');
}, [count])//类似vue中的侦听器 开了immediately
return (
<div>
{count}
<button onClick={() => { fn(count + 1) }}>点击改变count</button>
</div>
);
}
4.监听多个状态
// 1、第一种写法:分开监听
useEffect(() => {
console.log('每次拿到最新的count', count);
}, [count]);
useEffect(() => {
console.log('每次拿到最新的count2', count2);
}, [count2]);
// 2. 第二种写法:数组内写多个变量
useEffect(() => {
console.log('每次拿到最新的count 和 count2', count + count2);
}, [count, count2]);
5.发送请求
准备工作:
- 安装
axios
。 - 准备
utils/request.js
- 可能需要重启项目。
在组件中,使用 useEffect Hook 发送请求获取数据(side effect):
// ✅正确写法
useEffect(() => {
const loadData = async () => {
await ...
}
loadData()
}, [])
// ❌错误演示:
// 不要给 useEffect 第一级函数添加 async
useEffect(async () => {}, [])
解释:
- 注意:useEffect 只能是一个同步函数,不能使用 async
- 因为 effect 的返回值应该是一个清理函数,React 会在组件卸载或者 effect 的依赖项变化时重新执行
- 但如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用
- 如果延迟调用清理函数,也就没有机会忽略过时的请求结果或取消请求
- 为了使用 async/await 语法,可以在 useEffect 内部或外部创建 async 函数,并调用
6.组件挂载+卸载时
import React, { useState, useEffect } from 'react'
export default function App() {
const [isShow, setIsShow] = useState(true)
return (
<div>
<button onClick={() => setIsShow(!isShow)}>点我</button>
{isShow && <Child></Child>}
</div>
);
}
function Child() {
//在子组件挂载的时候监听窗口改变事件
const reziseFn = () => {
console.log('我被触发了');
}
useEffect(() => {
//开启监听事件
window.addEventListener('resize', reziseFn)
//useEffect接收的回调函数中 返回一个函数 代表卸载是执行该函数
return () => {
//子组件卸载时触发
console.log('我被卸载了');
//清除监听事件
window.removeEventListener('resize', reziseFn)
}
}, [])
return (
<div>我是子组件</div>
);
}
解释:
- effect 的返回值也是可选的,可省略。也可以返回一个清理函数,用来执行事件解绑等清理操作
- 清理函数的执行时机:- 1【空数组没有依赖】组件卸载时- 此时,相当于 class 组件的 componentWillUnmount 钩子函数的作用- 2 【有依赖项】effect 重新执行前(暂时知道即可)
- 推荐:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect
- 优势:- 根据业务逻辑来拆分,相同功能的业务逻辑放在一起,而不是根据生命周期方法名称来拆分代码- 编写代码时,关注点集中;而不是上下翻滚来查看代码
7.useEffect踩坑注意
1. useEffect第二个参数,需要先声明依赖项
export default function App() {
// ❌错误写法,type 和 list 还没创建,无法使用
useEffect(() => {
console.log('数据更新时 - 设置本地存储数据')
}, [list, type])
const [list, setList] = useState([])
const [type, setType] = useState('all')
2.书写多个 useEffect 时,注意先后书写顺序。(先书写组件挂载时,再书写组件更新时)
// 数据更新时 - 设置本地存储数据
useEffect(() => {
localStorage.setItem('todos', JSON.stringify(list))
localStorage.setItem('todos-type', type)
}, [list, type])
// ❌错误写法:组件挂载时应该要写在更新时前面。
useEffect(() => {
const list = JSON.parse(localStorage.getItem('todos')) || []
const type = localStorage.getItem('todos-type') || 'all'
setList(list)
setType(type)
}, [])
6.useRef
1-1 作用:保存数据 获取DOM 获取组件实例对象 1-2 useRef和React.createRef完全相同:创建Ref
//useEffect初体验
import React, { useState, useEffect, useRef } from 'react'
export default function App() {
const [isShow, setIsShow] = useState(true)
return (
<div>
<button onClick={() => setIsShow(!isShow)}>点我</button>
{isShow && <Child></Child>}
</div>
);
}
function Child() {
// ref作用:3. 保存数据 1. 获取DOM 2 获取组件实例对象
// 🔔useRef的作用和React.createRef完全相同:创建ref
// 1。 创建ref
const timeRef = useRef()
console.log(timeRef);
useEffect(() => {
// 挂载时,开启定时器 使用ref保存数据
timeRef.current = setInterval(() => {
console.log('我计时了');
}, 1000)
// 卸载时,卸载定时器
return () => {
clearInterval(timeRef.current)
console.log('我被停止了');
}
}, [])
return (
<div>我是子组件</div>
);
}
7.useHistory
1-1 作用:创建htistory对象 1-2 用途:操纵history跳转路由
//引入实例对象
import { useHistory } from "react-router-dom";
function HomeButton() {
//创建history
const history = useHistory();
function handleClick() {
//跳转路由
history.push("/home");
}
return (
<button type="button" onClick={handleClick}>
Go home
</button>
);
8.useParams
1-1 作用:获取动态路由的参数
9.useRouteMatch
1-1 作用:获取match对象
10.useLocation
1-1 作用:获取location对象
版权归原作者 无情的代码机器aa 所有, 如有侵权,请联系我们删除。