本篇文章主要学习内容
- useState回调函数形式的参数
- useEffect清理副作用
- useRef操作DOM
- useContext组件通讯
Hooks的注意事项(熟记)
- react提供的hooks只能在 函数组件内 和 自定义Hooks中使用
- 自定义Hooks其实就是 函数,但是和普通函数的区别就是,命名使用use开头
1. useState回调函数形式的参数
useState 的两种格式
格式一: 传入值
useState(0) or useState('abc')
格式二: 传入回调
useState((=>{ return 初始值}))
- 回调函数的返回值就是作为状态的当前值
- 回调函数只能触发一次
一般情况下我们都是直接传入值,但是你也可以传入一个回调函数,在回调函数中,对值进行一个处理后,作为当前的状态
使用场景
格式一: 传入值
- 如果状态就是一个普通的数据(比如, 字符串 , 数字 , 数组等) 都可以直接使用
useState(普通数据)
格式二: 传入回调
- 初始状态需要经过一些计算得到 , useState(()=>{这里可以进行计算 , return 结果})
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
export default function App () {
// 示例
const [count, setCount] = useState(() => 1 + 1)
console.log(setCount)
return <div>通过回调函数计算处理后的值-count:{count}</div>
}
ReactDOM.render(<App />, document.getElementById('root'))
2. useEffect副作用
useEffect发送请求
正确示范:
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import axios from 'axios'
export default function App () {
useEffect(() => {
const fn = async () => {
const res = await axios({
url: 'xxxxx',
method: 'GET'
})
console.log(res)
}
fn() // 调用函数发送请求
}, [])
return <div />
}
ReactDOM.render(<App />, document.getElementById('root'))
错误示范:
// 不要给 effect 添加 async
useEffect(async () => {
const res = awiat xxx()
return ()=> {
}
}, [])
注意: effect 只能是一个同步函数,不能使用 async
3. useEffect-副作用函数的返回值-清理副作用
副作用的返回值
useEffect(() => {
// 副作用函数的内容
return 副作用函数的返回值
}, [])
副作用函数的返回值是可选的,可省略。也可以返回一个清理函数,用来清理副作用,类似类组件中生命周期钩子函数的 componentWillUnMount()
useEffect(() => {
// 副作用函数的内容
return ()=>{ /* 做清理工作*/ } // 清理函数
}, [])
清理函数的执行时机:
- 清理函数会在组件卸载时以及下一次副作用函数调用之前执行
-用代码来验证一下它的执行时机-
import React, { useEffect, useState } from 'react'
import ReactDOM from 'react-dom'
export function Com () {
useEffect(() => {
console.log('子组件的副作用函数执行....')
return () => {
console.log('子组件的副作用函数的清理函数执行...')
}
}, [])
return <div />
}
export function App () {
const [isShow, setIsShow] = useState(true)
const [count, SetCount] = useState(0)
useEffect(() => {
console.log('父组件的副作用函数的内容')
return () => {
console.log('父组件的副作用函数的清理函数的内容')
}
}, [count])
return (
<>
{isShow && <Com/>}
count:{count}
<button onClick={() => { setIsShow(!isShow) }}>子组件销毁/创建</button>
<button onClick={() => { SetCount(count + 1) }}>count+1</button>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
子组件
-
当页面刷新第一次加载的时候,执行一次 副作用函数
-
当删除了 子组件后,触发了子组件副作用函数中的清理函数,输出结果
父组件
- 同样, 当页面刷新创建时, 第一次执行了 副作用函数
- 当我点击 Count+1 的按钮时,改变了 Count的值时(依赖项Count) , 先执行了一次 副作用函数的清理函数, 随后执行了副作用函数
- 因为改变了Count的值, 又导致触发了一次副作用函数
清理函数会在组件卸载时以及下一次副作用函数调用之前执行
总结
情况1:不带第二个参数。执行时机:初始执行,每次更新之后都要执行。
模拟 componentDidmount()
+ componentDidUpdate()
useEffect(() => {
// 副作用函数的内容
})
情况2:带第二个参数,参数是空数组。执行时机:只执行第一次。
模拟 componentDidMount
useEffect(() => {
// 副作用函数的内容
}, [])
使用场景:1 事件绑定 2 发送请求获取数据 等。
情况3:带第二个参数(数组格式),并指定了依赖项。
执行时机:1.初始执行一次 2. 依赖项的值变化了,执行一次
useEffect(() => {
// 副作用函数的内容
}, [依赖项1,依赖项2,....])
情况4:依赖项为空,没有具体的副作用函数,有副作用函数的清理函数
模拟:componentWillUnMount()
useEffect(() => {
return () => {
// 副作用函数的清理函数,模拟 componentWillUnMount
}
}, [])
下面通过一个小的案例,来测试一下,是否掌握了清理副作用函数
道题导入
删除子组件之后,事件还存在吗?
import React, { useEffect, useState } from 'react'
import ReactDom from 'react-dom'
function Com1 () {
useEffect(() => {
window.addEventListener('mousemove', (e) => {
console.log(e.pageX, e.pageY)
})
}, [])
return <div>子组件</div>
}
export default function App () {
const [isShow, setIsShow] = useState(true)
return (
<div>
{isShow && <Com1 />}
<button onClick={() => setIsShow(!isShow)}>切换</button>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
很明显,就算子组件被销毁了,事件还会存在 , 所以要清除副作用
解决
import React, { useEffect, useState } from 'react'
import ReactDom from 'react-dom'
function Com1 () {
useEffect(() => {
const fn = (e) => {
console.log(e.pageX, e.pageY)
}
window.addEventListener('mousemove', fn)
return () => { // 在清理副作用函数中,清除mousemove事件
window.removeEventListener('mousemove', fn)
}
}, [])
return <div>子组件</div>
}
export default function App () {
const [isShow, setIsShow] = useState(true)
return (
<div>
{isShow && <Com1 />}
<button onClick={() => setIsShow(!isShow)}>切换</button>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
在这次的删除子组件后,滑动鼠标,就没有在打印鼠标位置了, 说明事件已经被移除了
4. useRef-操作DOM
作用:返回一个带有 current 属性的可变对象,通过该对象就可以进行 DOM 操作了
语法 : const inputRef = useRef(null)
- 参数:在获取 DOM 时,一般都设置为 null (或者直接不填,为空)
- 返回值:包含 current 属性的对象。
- 注意:只要在 React 中进行 DOM 操作,都可以通过 useRef Hook 来获取 DOM(比如,获取 DOM 的宽高等)。
- 注意:useRef不仅仅可以用于操作DOM,还可以操作组件
示例
import React, { useRef } from 'react'
import ReactDOM from 'react-dom'
export default function App () {
const inputRef = useRef(null)
return (
<>
<input ref={inputRef} type="text" />
<button onClick={() => {
console.log(inputRef) // 打印inputRef对象
console.log(inputRef.current.value) // 输出value值
}}>点击获取input的value值</button>
</>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
打印 inputRef
5. useRef-在多次渲染之间共享数据-停止定时器
函数组件虽然非常直观,简化了思考 UI 实现的逻辑,但是比起 Class 组件,还缺少了一个很重要的能力:在多次渲染之间共享数据。
问题导入
import React, { useEffect, useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
const [count, setCount] = useState(0)
let timeId = null
console.log(timeId)
useEffect(() => {
timeId = setInterval(() => {
setCount((count) => count + 1)
}, 1000)
console.log(timeId)
}, [])
const hClick = () => {
console.log(timeId)
clearInterval(timeId)
}
return (
<div>
count:{count}
<button onClick={hClick}>点击停止定时器</button>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
执行以上的代码,当我们执行 停止定时器的时候,发现,定时器并没有被停止,为什么会这样呢?
分析
setCount会导致组件重新渲染,而重新渲染时,会重复执行如下代码
let timeId = null
所以,无法保存timeId
解决
import React, { useEffect, useRef, useState } from 'react'
import ReactDom from 'react-dom'
export default function App () {
const [count, setCount] = useState(0)
const timeRef = useRef(null)
console.log(timeRef.current)
useEffect(() => {
timeRef.current = setInterval(() => {
setCount((count) => count + 1)
}, 1000)
console.log(timeRef.current)
}, [])
const hClick = () => {
console.log(timeRef.current)
clearInterval(timeRef.current)
}
return (
<div>
count:{count}
<button onClick={hClick}>点击停止定时器</button>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
6. useContext-全局状态
目标
要求: 在根组件中点击按钮 , 将所有组件用到red 的值都改变
代码
index.js
import React, { useState} from 'react'
import Father from './Father'
import ReactDOM from 'react-dom'
export default function App () {
const [color, setColor] = useState('red')
return (
<div style={{ border: '1px solid #ccc', margin: 10 }}>
根组件:color:{color}
<button
onClick={() => {
setColor('green')
}}>
改成 green
</button>
<Father />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
father.js
import Son from './Son.js'
const Father = () => {
return (
<div style={{ border: '1px solid #ccc', margin: 10 }}>
父组件
<Son />
</div>
)
}
export default Father
Son.js
const Son = () => {
return (
<div style={{border:'1px solid #ccc', margin:10}}>
子组件: color: {'red'}</div>)
}
export default Son
useContext步骤
共3步:
- 导入并调用createContext方法,得到Context对象,导出
import { createContext } from 'react'
export const Context = createContext()
- 使用 Provider 组件包裹根组件,并通过 value 属性提供要共享的数据
return (
<Context.Provider value={ 这里放要传递的数据 }>
<根组件的内容/>
</Provider>
)
- 在任意后代组件中,如果希望获取公共数据:
导入useContext;调用useContext(第一步中导出的context) 得到value的值
import React, { useContext } from 'react'
import { Context } from './index'
const 函数组件 = () => {
const 公共数据 = useContext(Context)
return ( 函数组件的内容 )
}
改造后的代码
index.js
import React, { useState, createContext } from 'react'
import Father from './Father'
import ReactDOM from 'react-dom'
export const Content = createContext()
export default function App () {
const [color, setColor] = useState('red')
return (
<Content.Provider value={color}>
<div style={{ border: '1px solid #ccc', margin: 10 }}>
根组件:color:{color}
<button
onClick={() => {
setColor('green')
}}>
改成 green
</button>
<Father />
</div>
</Content.Provider>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
father.js
import Son from './Son.js'
const Father = () => {
return (
<div style={{ border: '1px solid #ccc', margin: 10 }}>
父组件
<Son />
</div>
)
}
export default Father
Son.js
import { useContext } from 'react'
import { Content } from './index'
const Son = () => {
const color = useContext(Content)
return (
<div style={{ border: '1px solid #ccc', margin: 10 }}>
子组件: color: {color}
</div>
)
}
export default Son
成功 实现在根组件中改变所有组件的值