本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(5)

react学习:自定义hook

发布于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>
	)
}

学习更多

react学习地图




所属网站分类: 技术文章 > 博客

作者:大师兄

链接:http://www.qianduanheidong.com/blog/article/116043/559883cda96f0314a845/

来源:前端黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

12 0
收藏该文
已收藏

评论内容:(最多支持255个字符)