目录
理解副作用
副作用(Side Effects;Adverse Reactions)系指应用治疗量的药物后所出现的治疗目的以外的药理作用事物的主要作用之外的,就是副作用。
感冒药:
-
主作用:用于感冒引起的头痛,发热,鼻塞,流涕,咽痛等
-
副作用:可见困倦、嗜睡、口渴、虚弱感
函数式组件:
主作用:就是根据数据(state/props)渲染 UI
副作用:数据(Ajax)请求、手动修改 DOM、开启定时器,清空定时器,添加事件监听,删除事件, localStorage 操作等
对于react组件来说,除了渲染UI之外的其他操作,都可以称之为副作用。
useEffect-基本使用
使用步骤
// 1. 导入useEffect
import { useEffect } from 'react'
// 2. 使用useEffect
useEffect(() => {
console.log('useEffect 1 执行了,可以做副作用')
})
useEffect(() => {
console.log('useEffect 2 执行了,可以做副作用')
})
执行时机
render工作完成之后,执行Effect;
如果定义了多个,则顺序执行;
export default function App () {
const [cotent, setCotent] = useState(1)
useEffect(() => {
console.log('useEffect 1 执行了,可以做副作用')
})
useEffect(() => {
console.log('useEffect 2 执行了,可以做副作用')
})
console.log('first======')
return (
<div>
{cotent}
<button onClick={() => { setCotent(cotent + 1) }}>+1</button>
</div>
)
}
useEffect-设置依赖项
useEffect有两个参数:
参数1: 副作用函数。
参数2:执行副作用函数的依赖项:它决定了什么时机执行参数1(副作用函数)
useEffect的完整格式
情况1:不带第二个参数。执行时机:每次更新之后都要执行
情况2:带第二个参数,参数是空数组。执行时机:只执行第一次
useEffect(() => {
// 副作用函数的内容
}, [ ])
使用场景:1 事件绑定 2 发送请求获取数据 等
情况3:带第二个参数(数组格式),并指定了依赖项。执行时机:(1)初始执行一次 (2)依赖项的值变化了,执行一次
useEffect(() => {
// 副作用函数的内容
}, [依赖项1,依赖项2,....])
这里的依赖项就是组件中定义的状态
useEffect数据持久化
// 获取
const [list, setList] = useState(() => {
return JSON.parse(localStorage.getItem('list')) || arr
})
useEffect(() => {
localStorage.setItem('list', JSON.stringify(list))
}, [list])
useEffect发送请求
在函数组件中通过useEffect发送ajax请求
格式
// 正确使用
useEffect(() => {
// 定义一个函数
async function fetchMyAPI() {
let url = 'http://something/' + productId
let config = {}
const response = await myFetch(url)
}
// 调用它
fetchMyAPI()
}, [productId])
注意:effect 只能是一个同步函数,不能使用 async
// 错误演示:
// 不要给 effect 添加 async
useEffect(async () => {
const res = awiat xxx()
return ()=> {
}
}, [])
useEffect-副作用函数的返回值-清理副作用
副作用函数的返回值
格式
useEffect(() => {
// 副作用函数的内容
return 副作用函数的返回值
}, [])
副作用函数的返回值是可选的,可省略。也可以返回一个清理函数,用来清理副作用
useEffect(() => {
// 副作用函数的内容
return ()=>{ /* 做清理工作*/ } // 清理函数
}, [])
清理函数的执行时机:
清理函数会在组件卸载时以及下一次副作用函数调用之前执行
示例
import { useEffect, useState } from 'react'
import ReactDom from 'react-dom'
function Com1 () {
const [count, setCcount] = useState(0)
console.log(4) //---------------------------4444444
useEffect(() => {
// console.log('子组件的副作用函数')
console.log(6) // ------------------------66666
return () => {
console.log('子组件的副作用函数的清理函数')
}
}, [])
console.log(5) // ----------------------------55555
return <div>
子组件{count}
<button onClick={() => { setCcount(() => count + 1) }}>更改子组件</button>
</div>
}
export default function App () {
const [isShow, setIsShow] = useState(true)
const [count, setCcount] = useState(0)
console.log(1) //----------------------------------11111111
useEffect(() => {
// console.log('父组件的副作用函数的内容')
console.log(7) // -----------------------------------77777
return () => {
console.log('父组件的副作用函数的 清理函数 的内容')
}
}, [count])
console.log(2) //------------------------------------2222
return (
<div>
{isShow && <Com1 />}
{console.log(3)} {/*-----------------------------33333*/}
<button onClick={() => setIsShow(!isShow)}>删除|创建子组件</button>
<div>父组件{count}</div>
<button onClick={() => setCcount(count + 1)}>点击+1, {count}</button>
</div>
)
}
ReactDom.render(<App />, document.getElementById('root'))
点击第一个按钮:
点击第二个按钮:
点击第三个按扭:
如果父组件拥有多个子组件,会优先依次执行react主线程, 执行完毕后先依次执行子组件中的useEffect, 最后执行父组件中的useEffect
示例-模拟 类组件生命周期
useEffect(()=>{
return ()=>{
// 做清理工作
}
}, [])
情况1:不带第二个参数。执行时机:初始执行,每次更新之后都要执行
模拟 componentDidmount + componentDidUpdate
useEffect(() => {
// 副作用函数的内容
})
情况2:带第二个参数,参数是空数组。执行时机:只执行第一次。
模拟 componentDidMount
useEffect(() => {
// 副作用函数的内容
}, [])
使用场景:1 事件绑定 2 发送请求获取数据 等
情况3:带第二个参数(数组格式),并指定了依赖项。执行时机:(1)初始执行一次 (2)依赖项的值变化了,执行一次
useEffect(() => {
// 副作用函数的内容
}, [依赖项1,依赖项2,....])
这里的依赖项就是组件中定义的状态。
情况4:依赖项为空,没有具体的副作用函数,有副作用函数的清理函数
模拟:componentWillUnMount
useEffect(() => {
return () => {
// 副作用函数的清理函数,模拟 componentWillUnMount
}
}, [])
useEffect-清理副作用-案例
鼠标移动
删除子组件之后,事件还有么?
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 () => {
window.removeEventListener('mousemove', fn)
console.log('组件删除了')
}
}, [])
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 { useState, useEffect } from 'react'
import reactDom from 'react-dom'
import img from './11.jpg'
export default function App () {
const [move, setMove] = useState({ x: 0, y: 0 })
useEffect(() => {
const fn = (e) => {
setMove(() => {
return { x: e.pageX, y: e.pageY }
})
}
document.addEventListener('mousemove', fn)
return () => {
document.removeEventListener('mousemove', fn)
}
}, [])
return (
<div>
<img src={img}
style={{
position: 'absolute',
top: move.y,
left: move.x,
width: '150px',
height: '200px',
transform: 'translate(-50%,-50%)'
}}/>
</div>
)
}
reactDom.render(<App></App>, document.querySelector('#root'))
自定义hooks
除了使用内置的 Hooks 之外,还可以创建自己的 Hooks(自定义 Hooks)。
自定义 Hooks 针对不同组件实现不同状态逻辑复用。
-
自定义 Hooks 是一个函数,约定函数名称必须以 use 开头,React 就是通过函数名称是否以 use 开头来判断是不是 Hooks
-
Hooks 只能在函数组件中或其他自定义 Hooks 中使用,否则,会报错!
-
自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(就像使用普通函数一样)
使用场景
将组件状态逻辑提取到可重用的函数(自定义 Hooks)中,实现状态逻辑复用
核心代码
import { useState, useEffect } from 'react'
export default function useMove () {
const [move, setMove] = useState({ x: 0, y: 0 })
useEffect(() => {
const fn = (e) => {
setMove(() => {
return { x: e.pageX, y: e.pageY }
})
}
document.addEventListener('mousemove', fn)
return () => {
document.removeEventListener('mousemove', fn)
}
}, [])
return move
}