该掌握的八个React Hooks
1.useState
在函数组件中,可以使用useState
来定义函数组件的状态。使用useState
来创建状态
- 1.引入
- 2.接收一个参数作为初始值
- 3.返回一个数组,第一个值为状态,第二个值为改变状态的函数
代码如下:
import React,{ useState } from 'react'
function StateFunction () {
const [name, setName] = useState('函数')
// 类名,修改函数名 初始值
return (
<div onClick={ () => setName('我使用hooks变成这样了') }>
// setName也可以写入方法,如setName( val => val+'xxxx' )
这是一个函数式组件————{name}
</div>
)
}
export default StateFunction
2.useEffect
useEffect
又称副作用hooks
。作用:给没有生命周期的组件,添加结束渲染的信号。执行时机:在渲染结束之后执行
-
什么是副作用?
- 副作用 ( side effect ): 数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用
- 因为我们渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用
-
使用:
- 1.第一个参数,接收一个函数作为参数
- 2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
- 3.返回一个函数,先执行返回函数,再执行参数函数
代码如下:
import React,{ useEffect, useState } from 'react'
function StateFunction () {
const [num, setNum] = useState(0)
const [val,setVal] = useState(0)
//不带参数
useEffect( () => {
console.log('2222函数式组件结束渲染')
})
//带空参数
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[])
//带参数的
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[参数])
//带两个参数
useEffect( () => {
console.log('2222函数式组件结束渲染')
},[num,val])
return (
<div onClick={ () => setNum( num => num+1 ) }>
这是一个函数式组件————{num}
这是一个函数式组件————{val}
</div>
)
}
3.useLayoutEffect
一般将useLayoutEffect
称为有DOM
操作的副作用hooks
。作用是在DOM
更新完成之后执行某个操作。执行时机:在DOM
更新之后执行
与useEffect
对比
- 相同点
- 1.第一个参数,接收一个函数作为参数
- 2.第二个参数,接收【依赖列表】,只有依赖更新时,才会执行函数
- 3.返回一个函数,先执行返回函数,再执行参数函数
- (所以说执行过程的流程是一样的)
- 不同点
- 执行时机不同。
useLayoutEffect
在DOM
更新之后执行;useEffect
在render
渲染结束后执行。执行示例代码会发现useLayoutEffect
永远比useEffect
先执行,这是因为DOM
更新之后,渲染才结束或者渲染还会结束
- 执行时机不同。
代码如下:
const [num, setNum] = useState(0)
//在类组件中用componentWillMount生命周期来实现
useLayoutEffect( () => {
console.log('useLayoutEfffect')
// 也可以在此进行事件绑定
return () => {
// 也可以在此进行事件绑定移除
console.log(1)
}
},[num])
useEffect( () => {
console.log('useEffect')
},[num])
return (
<div onClick={ () => setNum( num => num+1 ) }>
这是一个函数式组件————{num}
</div>
)
4.useMemo
使用useMemo
可以传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。简单来说,作用是让组件中的函数跟随状态更新(即优化函数组件中的功能函数)。
- 使用:
- 1.接收一个函数作为参数
- 2.同样接收第二个参数作为依赖列表(可以与useEffect、useLayoutEffect进行对比学习)
- 3.返回的是一个值。返回值可以是任何,函数、对象等都可以
代码如下:
const [num, setNum] = useState(1)
const [age, setAge] = useState(18)
function getDoubleNum () {
console.log(`获取双倍Num${num}`)
return 2 * num // 假设为复杂计算逻辑
}
return (
<div onClick={ () => { setAge( age => age+1 )} }>
<br></br>
这是一个函数式组件————{ getDoubleNum() }
<br></br>
age的值为————{ age }
<br></br>
</div>
)
5.useCallback
useCallback
缓存的是一个函数,可以对父子组件传参渲染的问题进行优化。简单来说就是,父组件的传入函数不更新,就不会触发子组件的函数重新执行
例如示例代码,我们将getDoubleNum
传入子组件,此时点击div
区域改变的是num
的值,我们使用父组件useCallback
配合子组件的useEffect
来优化,只有当父组件的num
改变导致传入子组件的getDoubleNum
改变的时候,我们才会执行子组件某些需要更新的操作(即注释标注处代码),这样就可以避免子组件一些没必要的更新操作反复执行而影响页面性能
代码如下:
function Parent () {
const [num, setNum] = useState(1)
const [age, setAge] = useState(18)
const getDoubleNum = useCallback( () => {
console.log(`获取双倍Num${num}`)
return 2 * num
},[num] )
return (
<div onClick={ () => {setNum( num => num+1 )} }>
这是一个函数式组件————num:{ getDoubleNum() }
<br></br>
age的值为————age:{ age }
<br></br>
set.size:{set.size}
<Child callback={ getDoubleNum() }></Child>
</div>
)
}
function Child(props) {
useEffect( () => {
console.log('callback更新了') //这里代表的是需要跟随传入内容的改变而同步进行的操作
},[props.callback])
return (
<div>
子组件的getDoubleNum{props.callback}
</div>
)
}
简单总结使用场景判断:
- 在子组件不需要父组件的值和函数的情况下,只需要使用
memo
函数包裹子组件即可 - 如果有函数传递给子组件,使用
useCallback
- 缓存一个组件内的复杂计算逻辑需要返回值时,使用
useMemo
- 如果有值传递给子组件,使用
useMemo
参考详解文章: - 上述是我个人结合查阅资料的一些理解,如果你觉得我讲的还是不太清楚,可以参考下这两篇写得不错的文章,或许会更清晰
6.useRef
简单来说useRef
就是返回一个子元素索引,此索引在整个生命周期中保持不变。作用也就是:长久保存数据。注意事项,保存的对象发生改变,不通知。属性变更不会重新渲染
- 未使用
useRef
,如果我们有这样一个需求如下,需要当某个定时器自增的值达到限制条件后就清除该定时器,如下代码。此时以下的代码其实是没有办法完成给出的需求的,当num
大于10
后,会发现不停的打印大于10了,清除定时器
,而其实是定时器没有清除掉的,所以会一直执行这两个打印内容,但是会发现打印出来的timer
显示undefined
,这是为什么呢?因为我们每次渲染都是通过setInterval
重新返回的timer
,timer
也在更新,也就丢失了timer
这个数据,导致无法准确清除某个需要清除的定时器
代码如下:
const [num, setNum] = useState(0)
let timer
useEffect( () => {
timer = setInterval( () => {
setNum( num => num+1 )
},400 )
},[] )
useEffect( () => {
if(num > 10){
console.log('大于10了,清除定时器')
console.log('timer:',timer)
// 因为每一个timer都是独立render的,所以获取不到
clearTimeout(timer)
}
},[num] )
return (
<div>
这是一个函数式组件————num:{ num }
</div>
)
使用useRef
后,代码如下。我们可以看到num
自增到11后
就打印了一次大于10了,清除定时器
以及ref.current 1
,然后就停止自增了,因为定时器被清除了。ref
是一个对象,ref.current
存储了该定时器在整个生命周期中的id
值,所以当清除定时器的时候,可以准确清除这个定时器
- 保存一个值,在整个生命周期中维持不变
const [num, setNum] = useState(0)
const ref = useRef()
useEffect( () => {
ref.current = setInterval( () => {
setNum( num => num+1 )
},400 )
// ref.current = '111'
},[] )
useEffect( () => {
if(num > 10){
console.log('大于10了,清除定时器')
console.log('ref.current',ref.current)
clearTimeout(ref.current)
}
},[num] )
return (
<div>
这是一个函数式组件————num:{ num }
</div>
)
- 重新赋值
ref.current
不会主动触发页面重新渲染。当我们将代码修改成下面这样,会在控制台打印发现ref.current
的值打印为111
,但是页面视图上显示的还是空,这是因为ref
保存的对象发生改变,不会主动通知,属性变更不会重新渲染
const [num, setNum] = useState(0)
const ref = useRef()
useEffect( () => {
ref.current = '111'
console.log('ref.current',ref.current)
},[] )
return (
<div>
这是ref.current的值——ref.current:{ ref.current }
<br></br>
这是一个函数式组件————num:{ num }
</div>
)
7.useContext
useContext
是让子组件之间共享父组件传入的状态的。作用通俗地说是带着子组件去流浪。
- 未使用
useContext
,我们有下列这样一个场景,我们父组件有传入一个值到不同的子组件中,示例给出的代码是2
个这样的子组件,但是如果我需要添加的子组件特别多呢?总不能总是一个一个这样添加写入吧,而且如果传入的同一个变量名如果发生改变,还得一个个去改,所以我们可以用useContext
优化一下代码
代码如下:
function StateFunction () {
const [num, setNum] = useState(1)
return (
<div>
<button onClick={ ()=> setNum(num => num+1) }>增加num的值+1</button>
<br></br>
这是一个函数式组件——num:{ num }
<Item1 num={num}></Item1>
<Item2 num={num}></Item2>
// ......
</div>
)
}
function Item1 (props) {
return (
<div>
子组件1 num:{ props.num }
</div>
)
}
function Item2 (props) {
return (
<div>
子组件2 num:{ props.num }
</div>
)
}
使用useContext
优化后,代码如下,这样我们只需要在子组件中使用useContext(Context句柄)
来获取数据即可,添加同类子组件时不需要再关注父组件中子组件定义时的props
传入值,使用方法如下
- 需要引入
useContetx
,createContext
两个内容 - 通过
createContext
创建一个context句柄 Context.Provider
来确定数据共享范围- 通过
value
来分发内容 - 在子组件中,通过
useContext(Context句柄)
来获取数据 - 注意事项,上层数据发生改变,肯定会触发重新渲染(点击
button
按钮触发父组件更新传入的num
值能看到子组件重新渲染)
const Context = createContext(null)
function StateFunction () {
const [num, setNum] = useState(1)
return (
<div>
<button onClick={ ()=> setNum(num => num+1) }>增加num的值+1</button>
<br></br>
这是一个函数式组件——num:{ num }
<Context.Provider value={num}>
<Item3></Item3>
<Item4></Item4>
</Context.Provider>
</div>
)
}
function Item3 () {
const num = useContext(Context)
return (
<div>
子组件3: { num }
</div>
)
}
function Item4 () {
const num = useContext(Context)
return (
<div>
子组件4: { num+2 }
</div>
)
}
8.useReducer
以前是只能在类组件中使用Redux
,现在我们可以通过useReducer
在函数式组件中使用Redux
。作用是可以从状态管理的工具中获取到想要的状态。
- 如何使用
useReducer
。Redux
必须要有的内容就是仓库store
和管理者reducer
。而useReducer
也是一样的,需要创建数据仓库store
和管理者reducer
,即示例代码注释处。然后我们就可以通过①
处的定义一个数组获取状态和改变状态的动作,触发动作的时候需要传入type
类型判断要触发reducer
哪个动作,然后进行数据的修改。需要注意的地方是,在reducer
中return
的对象中,需要将state
解构,否则状态就剩下一个num
值了
代码如下:
const store = {
age:18,
num:1
} // 数据仓库
const reducer = (state, action) => {
switch(action.type){
case 'add':
return {
...state,
num: action.num+1
}
default:
return {
...state
}
}
} // 管理者
function StateFunction () {
const [state,dispacth] = useReducer(reducer,store) // ①
return (
<div>
<button onClick={ () => {
dispacth({
type: 'add',
num: state.num
})
} }>
增加num的值+1
</button>
<br></br>
这是一个函数式组件——num:{ state.num }
</div>
)
}
总结:
看完了吗?不知道你看完后是否有所收获,本文是从使用层面及一些日常应用示例上去分析的,并没有深层次剖析。如果你想要深层次了解掌握这几个Hooks
,建议还是去看官方的源码哦。