前言
步入正题之前,我想先说点其他的.关于项目中的组件化,模块化,相信大家都已经明明白白,也明白为什么要这么做.那么,关于业务逻辑的复用,不知道有多少人明白呢
React hooks
概念
- React 一直都提倡使用函数组件,但是有时候需要使用 state 或者其他一些功能时,只能使用类组件,因为函数组件没有实例,没有生命周期函数,只有类组件才有
- 在React 16.8版本之前,class组件被称作有状态组件,函数组件被称作无状态组件,而16.8以后,随着React引入hooks的概念,使得函数组件有可以有状态了
- hooks本质上就是一个给函数组件中钩入状态和生命周期特性的函数,React给我们提供了一些hooks,我们也可以自定义hooks使用
hooks的优点
- 首先,hooks之所以如此火,有一点是因为Class组件大材小用了,例如class中的继承特性,以及创造一个新的实例,这些方法我们压根没有用到过
- 在组件中复用业务逻辑代码很困难,但是hooks可以帮助我们实现业务逻辑的复用(最重要的一点)
- hooks + 函数组件开发模式,不存在this指向复杂的问题
业务场景
介绍完hooks,我给大家出来两题自定义hooks,由简到难,想象一下下面的业务场景
简单业务场景1-获取鼠标位置
- 场景1:图片跟随鼠标的移动进行移动
业务代码
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
import Img from './logo192.png'
export default function App () {
// 设置鼠标位置状态
const [position, setposition] = useState({ x: 0, y: 0 })
useEffect(() => {
// 鼠标移动,获取鼠标位置,并把位置给到图片位置
const fn = (e) => {
setposition({
x: e.pageX,
y: e.pageY
})
}
window.addEventListener('mousemove', fn)
// 组件销毁,清除鼠标移动事件
return () => {
window.removeEventListener('mousemove', fn)
}
}, [])
return (
<div>
<img src={Img} style={{ position: 'absolute', left: position.x, top: position.y }} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
- 场景2:鼠标移动,页面实时展示鼠标位置信息
业务代码
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom'
export default function App () {
const [position, setposition] = useState({ x: 0, y: 0 })
useEffect(() => {
// 鼠标移动,获取鼠标位置
const fn = (e) => {
setposition({
x: e.pageX,
y: e.pageY
})
}
window.addEventListener('mousemove', fn)
// 组件销毁,清除鼠标移动事件
return () => {
window.removeEventListener('mousemove', fn)
}
}, [])
return (
<div>
打印鼠标位置{position.x}:{position.y}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
细心的小伙伴估计很快就发现了,这两个场景的业务代码可以说是一模一样.如果一个项目中要实现这两个功能,重新写一遍77业务代码会使项目变得冗余并且不方便维护,这个时候 ,复用业务逻辑的需求就来了.
分析核心业务逻辑
相同点:
- 都要设置监听鼠标移动事件获取鼠标位置 不同点:
- 场景1是给图片设置位置
- 场景2是获取鼠标位置就行
自定义hooks抽离代码
思路 我们可以把设置监听事件以及获取鼠标位置的代码抽离出来,自定义hooks,来复用逻辑
注意点:
1.自定义hooks名要use开头
2.hooks函数只能在函数组件,类组件以及hooks中使用
3.hooks不能用在if,for,等语句中,也不能写在事件里面
- 自定义的hooks代码
import { useState, useEffect } from 'react'
// 自定义useMove hooks
export default function useMove () {
// 设置鼠标位置状态
const [position, setposition] = useState({ x: 0, y: 0 })
useEffect(() => {
const fn = (e) => {
setposition({
x: e.pageX,
y: e.pageY
})
}
// 监听鼠标移动事件
window.addEventListener('mousemove', fn)
return () => {
// 当销毁组件的时候,清理鼠标移动事件
window.removeEventListener('mousemove', fn)
}
}, [])
// 把位置信息返回出去
return position
}
接下来我们把原先代码改一下,都导入自定义hooks,用hooks实现功能
场景1使用自定义hooks后的代码
import React from 'react'
import ReactDOM from 'react-dom'
import Img from './logo192.png'
import useMove from './MouseMove'
export default function App () {
const position = useMove()
return (
<div>
<img src={Img} style={{ position: 'absolute', left: position.x, top: position.y }} />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
场景2使用自定义hooks后的代码:
import React from 'react'
import useMove from './MouseMove'
import ReactDOM from 'react-dom'
export default function App () {
const position = useMove()
return (
<div>
打印鼠标位置{position.x}:{position.y}
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
上面这个场景,很明显的可以看出复用业务代码的好处,可以明显缩短我们的代码量,是代码更加易维护且优美,如果你还没感受到自定义hooks复用逻辑带来的好处,觉得这个业务太简单了,那么接下来我将提出另外一个场景
复杂业务场景2-倒计时案例
- 场景1:点击按钮倒计时,并且禁用按钮,倒计时结束解除禁用
功能代码
import React, { useState, useEffect, useRef } from 'react'
import ReactDOM from 'react-dom'
export default function App () {
const [cansend, setcansend] = useState(true)
const [timer, settimer] = useState(3)
const time = useRef(null)
// 点击事件
const send = () => {
// 初始赋值给3
settimer(3)
// 1.禁用按钮
setcansend(false)
// 2.开启定时器
time.current = setInterval(() => {
settimer((timer) => timer - 1)
}, 1000)
}
useEffect(() => {
console.log('当前timer为', timer)
// 如果时间为0
if (timer === 0) {
// 1.解除禁用
setcansend(true)
// 2.清除定时器
clearInterval(time.current)
}
}, [timer])
return (
<div>
<input type="text" name="" id="" />
<button disabled={!cansend} onClick={send}>{cansend ? '发送验证码' : timer + '秒后重发'}</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
- 场景2:页面刚打开自动倒计时,倒计时结束跳转至指定页面
功能代码
import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'
export default function App () {
const [n, setn] = useState(5)
const timeId = useRef(null)
useEffect(() => {
// 设置5秒后跳转
setn(5)
// 开启定时器
timeId.current = setInterval(() => {
setn((n) => n - 1)
}, 1000)
}, [])
useEffect(() => {
// 时间为0,跳转网页
if (n === 0) {
location.href = 'http://www.baidu.com'
// 清除定时器
clearInterval(timeId.current)
}
}, [n])
return (
<div>
404该页面不存在,{n}秒后将自动返回<a href="http://www.baidu.com">这里</a>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
这个场景比上一个场景稍微复杂,所以在自定义hooks前,我们要先分析清楚要实现什么功能,什么功能不一样
分析核心业务逻辑
相同点:
- 都是倒计时,到时间执行回调 不同点:
- 场景1是点击触发,场景2一开始就触发
- 场景1到时间后解禁按钮,场景2到时间跳转页面
- 倒计时的时间不同
通过上面的分析,得出不同点和相同点后,思路如下:
- 用户自定义倒计时时间和时间为0后执行的回调,传入到hooks中
- 自定义hooks用形参接收时间和回调函数,定时器为0执行相应回调
- 自定义hooks中把剩余时间和定时器启动方式返回给用户
自定义hooks实现
我们新建文件useCountDown.js,在里面自定义hooks
import { useEffect, useRef, useState } from 'react'
// 接受两个参数,一个是时间,一个是到时间后执行的回调
export default (initCount = 10, callback = () => {}) => {
const [count, setcount] = useState(initCount)
// 使用useRef清除定时器
const timeID = useRef(null)
// 设置启动函数
const start = () => {
// 设置倒计时时间
setcount(initCount)
// 开启定时器
timeID.current = setInterval(() => {
setcount((count) => count - 1)
}, 1000)
}
useEffect(() => {
// 时间为0,执行回调
if (count === 0) {
callback()
// 清除定时器
clearInterval(timeID.current)
}
}, [count, callback])
// 组件销毁的钩子,避免意外销毁组件,而定时器却没停止
useEffect(() => {
return () => {
clearInterval(timeID.current)
}
}, [])
// 返回剩余时间,以及启动函数
return {
count, start
}
}
在之前的两个场景中,都复用逻辑代码后
场景1-复用逻辑后
import React, { useState } from 'react'
import ReactDOM from 'react-dom'
import useCountdown from './useCountdown'
export default function App () {
const [cansend, setcansend] = useState(true)
const { count, start } = useCountdown(3, () => {
// 解禁按钮
setcansend(true)
})
const send = () => {
// 1.禁用按钮
setcansend(false)
start()
}
return (
<div>
<input type="text" name="" id="" />
<button disabled={!cansend} onClick={send}>{cansend ? '发送验证码' : count + '秒后重发'}</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
复用逻辑前后对比
场景2-复用逻辑后
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import useCountdown from './useCountdown'
export default function App () {
const { count: n, start } = useCountdown(5, () => {
location.href = 'http://www.baidu.com'
})
useEffect(() => {
start()
}, [])
return (
<div>
该页面不存在,{n}秒后将自动返回<a href="http://www.baidu.com">这里</a>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
复用逻辑前后对比
这个案例稍微比上一个复杂点,更能体现出复用逻辑的好处
总结
通过上面两个场景,我们可以发现复用逻辑的好处,在不使用hooks的情况下,我们很难去复用业务逻辑.有些小伙伴可能就会说了,上面第一个场景,我自定义一个函数也能做.我想说的是,实现是可以实现,但函数是没有生命周期这个概念的.也就是说,你开启定时器或开启监听事件以后,无法再去把定时器清除或移除事件.这点我们是很难办到的.不过hooks给函数组件带来了生命周期和状态的概念,让我们前端开发更为简单,维护起来更加容易
掌握好hooks以及学会复用逻辑代码,是我们成为高级前端的必经之路
- 码字不易,点个赞呗
- 在此祝各位看官年薪百万,天天摸鱼