发布于2021-05-30 12:51 阅读(1174) 评论(0) 点赞(12) 收藏(0)
问题导向
react自定义hook
如果你都有了答案,可以忽略本文章,或去react学习地图寻找更多答案
自定义hook(封装)
类型:函数
作用:逻辑复用,本质是函数,可以多次调用
语法:名称以use开头,函数内部可以调用其他的Hook
简单例子
一个获取年龄的自定义Hook
import {useState, useEffect} from 'react';
function useAge(){
const [age, setAge] = useState(0)
useEffect(() => {
模拟异步操作
setTimeout(() => {
setAge(20)
},2000)
})
return age
}
function App(){
const age = useAge()
return (
<>
<div>
{
<p>年龄:{age ? age : 'loading...'}</p>
}
</div>
</>
)
}
复杂例子
实现滚动加载分页 + 图片懒加载
实现逻辑:当页面上拉到某个节点时(需要监听元素),根据分页发送请求
比如:商品列表,一次请求有10条数据,当上拉到第10条时,再次发生请求,加载更多的数据
实现3个hook
一个可以监听元素进入可视区域的hook
一个可以根据分页发送请求的hook
一个图片懒加载hook
一个可以根据分页发送请求的hook
小知识点:useState的改变函数可以传递给其他函数,让其修改值,在这里传递给Http封装好的函数,请求成功再修改值
import { useEffect, useState } from 'react'
import { Http } from '@/utils'
export default function useHttpHook({
url,
method = 'post',
headers,
body = {},
watch = []
}) {
const [result, setResult] = useState() 传递给Http,得到结果时设置result
const [loading, setLoading] = useState(true) 传递给Http,请求开始时设置为true,请求结束时设置为false
useEffect(() => {
Http({
url,
method,
headers,
body,
setResult,
setLoading
})
}, watch)
return [result, loading]
}
http封装
import { Toast } from 'antd-mobile';
export default function Http({
url,
method = 'post',
headers = {},
body = {},
setLoading,
setResult,
}) {
setLoading && setLoading(true);
const token = localStorage.getItem('token');
let defaultHeader = {
'Content-Type': 'application/json',
};
defaultHeader = token ?
{
...defaultHeader,
token,
} :
defaultHeader;
let params;
if (method.toUpperCase() === 'GET') {
params = undefined;
} else {
params = {
headers: {
...defaultHeader,
...headers,
},
method,
body: JSON.stringify(body),
};
}
return new Promise((resolve, reject) => {
fetch('/api' + url, params)
.then((res) => res.json())
.then((res) => {
if (res.status === 200) {
resolve(res.data);
setResult && setResult(res.data);
} else if (res.status === 1001) {
location.href = '/login?from=' + location.pathname;
localStorage.clear();
} else {
Toast.fail(res.errMsg);
}
})
.catch((err) => {
console.log(err);
})
.finally(() => {
setLoading && setLoading(false);
});
});
}
一个可以监听元素进入页面的hook,使用IntersectionObserver
IntersectionObserver作用:监听元素是否进入可视区域,如果entries[0].isIntersecting为true,则已经进入可视区域
使用它实现上拉加载分页,当节点进入可视区域后,发送请求
封装
import { useEffect } from 'react'
let observer;
export default function useObserverHoot(ele, callback, watch = []) {
useEffect(() => {
const node = document.querySelector(ele) //获取节点
if (node) {
observer = new IntersectionObserver(entries => {
callback && callback(entries)
})
observer.observe(node) //监听节点
}
return () => {
if (observer && node) {
observer.unobserve(node) //解绑元素
observer.disconnect() //停止监听
}
}
}, watch)
}
图片懒加载hook
图片没有进入可视区域前,显示一张小的图片,进入可视区域后,才替换成真实图片
1.监听图片是否进入可视区域
2.如果进入,将src属性的值替换为真实的图片地址
3.停止监听当前的节点
import { useEffect } from 'react'
let observer;
export default function useImgHook(ele, callback, watch = []) {
useEffect(() => {
const nodes = document.querySelectorAll(ele)
if (nodes && nodes.length) {
observer = new IntersectionObserver((entries) => {
callback && callback(entries)
entries.forEach((item) => {
// 如果图片进入可视区域
if (item.isIntersecting) {
const dataSrc = item.target.getAttribute('data-src')
item.target.setAttribute('src', dataSrc)
observer.unobserve(item.target)
}
})
})
nodes.forEach((item) => {
observer.observe(item)
})
}
return () => {
if (nodes && nodes.length && observer) {
observer.disconnect()
}
}
}, watch)
}
showLoading组件
import React from 'react'
import PropTypes from 'prop-types'
import { Constants } from '@/config'
import './index.less'
export default function ShowLoading(props){
return (
<div>
{
props.showLoading
? <div id={Constants.LOADING_ID} className="loading-info">loading</div>
: <div className="loading-info">没有数据了</div>
}
</div>
)
}
ShowLoading.defaultProps = {
showLoading: true
}
ShowLoading.prototype = {
showLoading: PropTypes.bool
}
使用
监听showLoading中的节点是否显示,如果显示,改变pageNum
监听pageNum是否改变,如果改变,重新发送请求
监听loading是否为true(请求完成),如果完成,拼接旧数据 + 新数据
import { useHttpHook, useObserverHook, useImgHook} from '@/hooks'
function App(){
const [page, setPage] = useState({
pageSize: 10,
pageNum: 1
})
const [goodsList, setGoodsList] = useState([])
const [showLoading, setShowLoading] = useState(true)
监听节点
useObserverHook('#' + Constants.LOADING_ID, (entries) => {
if (!loading && entries[0].isIntersecting) {
setPage({
...page,
pageNum: page.pageNum + 1
})
}
}, null)
发生请求
const [goods, loading] = useHttpHook({
url: '/user',
body: {
...page
},
watch: [ page.pageNum ] 每当pageNum 发生改变时,发送请求
})
监听loading是否为true(请求完成),如果完成,拼接旧数据 + 新数据
useEffect(() => {
if (!loading && goods) {
//如果不是返回空数组
if (goods.length) {
setGoodsList([...goodsList, ...goods]) //拼接数据
//如果返回的数据 少于 每页的数据长度,证明没有数据了,改变setShowLoading为false,该值传递给ShowLoading组件
if (houses.length < page.pageSize) {
setShowLoading(false)
}
} else {
//如果返回空数据,证明没有数据了
setShowLoading(false)
}
}
}, [loading])
图片懒加载
useImgHook('.item-img', (entries) => { }, null)
return (
<div>
<div className="result">
{
!goodsList.length ? <ActivityIndicator toast /> :
goodsList.map((item) => (
<div className="item" key={item.id}>
<img
className="item-img"
alt='img'
src={require('../../assets/xxx.jpg')}
data-src={item.img}
/>
<div className="item-right">
<div className="title">{item.title}</div>
</div>
</div>
))
}
<ShowLoading showLoading={showLoading} />
</div>
</div>
)
}
学习更多
作者:大师兄
链接:http://www.qianduanheidong.com/blog/article/116043/559883cda96f0314a845/
来源:前端黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 前端黑洞网 All Rights Reserved 版权所有,并保留所有权利。 京ICP备18063182号-3
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!