1. 前言
react-infinite-scroll-component 是 React 生态中一款轻量、易用的无限滚动插件,核心目标是帮助开发者快速实现"滚动到底部自动加载更多"的交互效果。它无需手动监听滚动事件、计算滚动位置,而是通过封装好的组件化 API,简化无限滚动的实现逻辑,同时支持加载状态显示、无更多数据提示、自定义触发距离等实用功能。
相比原生实现或其他同类插件,其核心优势如下:
- 零冗余代码 :无需手动处理
scroll
事件监听、滚动高度计算等底层逻辑; - 高度可定制:支持自定义加载中组件、无更多数据提示、触发加载的距离阈值;
- 兼容性强 :适配 PC 端与移动端,支持滚动容器为
window
或自定义 DOM 元素; - 轻量无依赖:体积小巧(gzip 压缩后仅 ~3KB),无额外第三方依赖,不增加项目负担。
2. 快速上手:安装与基础使用
2.1 安装依赖
通过 npm 或 yarn 安装插件(最新版本可参考 npm 官网):
bash
# npm 安装
npm install react-infinite-scroll-component --save
# yarn 安装
yarn add react-infinite-scroll-component
2.2 基础示例:实现列表无限滚动
以下是最基础的使用场景------滚动 window
窗口到底部时,自动加载更多列表数据:
jsx
import React, { useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';
const BasicInfiniteScroll = () => {
// 1. 状态管理:列表数据、是否有更多数据
const [list, setList] = useState<number[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(1); // 分页参数
// 2. 初始化加载第一页数据
useEffect(() => {
fetchData(page);
}, [page]);
// 3. 模拟接口请求:获取列表数据
const fetchData = async (currentPage: number) => {
try {
// 模拟接口延迟(实际项目替换为真实接口请求)
const response = await new Promise<number[]>((resolve) => {
setTimeout(() => {
// 生成 10 条模拟数据
const newData = Array.from({ length: 10 }, (_, i) => (currentPage - 1) * 10 + i + 1);
resolve(newData);
}, 800);
});
// 更新列表数据
setList(prev => [...prev, ...response]);
// 控制是否还有更多数据(示例:最多加载 5 页)
if (currentPage >= 5) {
setHasMore(false);
}
} catch (err) {
console.error('数据加载失败:', err);
}
};
// 4. 加载更多回调:触发下一页数据请求
const fetchMoreData = () => {
setPage(prev => prev + 1); // 页码 +1,触发 useEffect 重新请求
};
return (
<div style={{ padding: '20px' }}>
<h2>基础无限滚动示例</h2>
{/* 5. 无限滚动组件 */}
<InfiniteScroll
dataLength={list.length} // 已加载数据的长度(用于判断是否触发加载)
next={fetchMoreData} // 滚动到底部时触发的"加载更多"回调
hasMore={hasMore} // 是否还有更多数据(false 时停止触发 next)
loader={<h4 style={{ textAlign: 'center', padding: '20px' }}>加载中...</h4>} // 加载中提示组件
endMessage={
<p style={{ textAlign: 'center', padding: '20px', color: '#666' }}>
<b>已加载全部数据</b>
</p>
} // 无更多数据时的提示
>
{/* 6. 列表内容 */}
<ul style={{ listStyle: 'none', padding: 0 }}>
{list.map((item) => (
<li
key={item}
style={{
padding: '15px',
margin: '10px 0',
border: '1px solid #eee',
borderRadius: '4px',
}}
>
列表项 {item}
</li>
))}
</ul>
</InfiniteScroll>
</div>
);
};
export default BasicInfiniteScroll;
核心逻辑说明:
dataLength
:插件通过对比"已加载数据长度"与"滚动容器高度",判断是否触发next
回调;next
:滚动到底部时执行,通常用于更新分页参数(如页码 +1),进而触发数据请求;hasMore
:控制插件是否继续监听滚动------当hasMore=false
时,不再触发next
,并显示endMessage
;loader
/endMessage
:分别对应"加载中"和"无更多数据"的 UI 提示,支持自定义组件。
3. 核心配置项详解
react-infinite-scroll-component 提供了丰富的配置项,可满足不同场景的需求。以下是常用配置项的分类说明:
3.1 核心功能配置
配置项 | 类型 | 作用 | 默认值 |
---|---|---|---|
dataLength |
number |
已加载数据的长度(必传),插件通过此值判断是否需要触发加载 | - |
next |
() => void |
滚动到底部时触发的"加载更多"回调(必传),用于请求下一页数据 | - |
hasMore |
boolean |
是否还有更多数据------false 时停止触发 next ,并显示 endMessage |
true |
loader |
ReactNode |
加载中的提示组件(如"加载中..."文字、Spinner 动画) | <h4>Loading...</h4> |
endMessage |
ReactNode |
无更多数据时的提示组件(hasMore=false 时显示) |
- |
3.2 滚动容器配置
默认情况下,插件监听 window
的滚动事件;若需监听自定义 DOM 容器的滚动(如带固定高度的列表),需配置以下参数:
配置项 | 类型 | 作用 | 默认值 |
---|---|---|---|
scrollableTarget |
string |
自定义滚动容器的 id (需给容器设置 id 和固定高度 + overflow: auto ) |
- |
height |
string/number |
滚动容器的高度(仅当未指定 scrollableTarget 时生效,如 500px ) |
- |
style |
CSSProperties |
滚动容器的自定义样式(如边框、内边距) | {} |
示例:自定义滚动容器
jsx
// 自定义滚动容器(固定高度 500px,超出滚动)
<div id="customScrollContainer" style={{ height: '500px', overflow: 'auto', border: '1px solid #eee' }}>
<InfiniteScroll
scrollableTarget="customScrollContainer" // 绑定自定义容器的 id
dataLength={list.length}
next={fetchMoreData}
hasMore={hasMore}
loader={<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>}
endMessage={<div style={{ textAlign: 'center', padding: '20px' }}>已加载全部</div>}
>
{/* 列表内容 */}
<ul>
{list.map(item => (
<li key={item} style={{ padding: '15px', borderBottom: '1px solid #eee' }}>
自定义容器列表项 {item}
</li>
))}
</ul>
</InfiniteScroll>
</div>
3.3 加载触发时机配置
默认情况下,滚动到"容器底部"时触发加载;若需提前触发(如滚动到距离底部 200px 时开始加载),可通过以下参数调整:
配置项 | 类型 | 作用 | 默认值 |
---|---|---|---|
threshold |
number |
触发加载的"提前距离"(单位:px)------距离底部小于该值时触发 next |
100 |
disableScroll |
boolean |
是否禁用滚动监听(如加载失败时,禁止继续触发加载) | false |
示例:提前触发加载
jsx
<InfiniteScroll
dataLength={list.length}
next={fetchMoreData}
hasMore={hasMore}
threshold={300} // 距离底部 300px 时触发加载(适合大列表或慢网络)
loader={<div>加载中...</div>}
>
{/* 列表内容 */}
</InfiniteScroll>
3.4 其他实用配置
配置项 | 类型 | 作用 | 默认值 |
---|---|---|---|
onScroll |
(e: UIEvent) => void |
滚动时的回调(可用于自定义滚动逻辑,如记录滚动位置) | - |
initialScrollY |
number |
初始滚动位置(单位:px)------组件挂载时自动滚动到指定位置 | 0 |
reverse |
boolean |
是否反向滚动(从顶部加载更多,适合聊天记录等场景) | false |
pullDownToRefresh |
boolean |
是否启用"下拉刷新"功能(需配合 pullDownToRefreshContent 和 onPullDownRefresh ) |
false |
pullDownToRefreshContent |
ReactNode |
下拉刷新时的提示组件(如"下拉可刷新") | <h3>Pull down to refresh</h3> |
releaseToRefreshContent |
ReactNode |
下拉到阈值后释放时的提示组件(如"释放即可刷新") | <h3>Release to refresh</h3> |
onPullDownRefresh |
() => void |
下拉刷新触发的回调(需手动调用 setPullDownToRefresh(false) 结束刷新) |
- |
4. 场景化进阶示例
4.1 反向滚动:聊天记录场景
聊天记录通常需要"从底部向上加载历史消息"(新消息在底部,滚动到顶部时加载更早的记录),可通过 reverse
配置实现:
jsx
import React, { useState, useEffect } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';
const ChatInfiniteScroll = () => {
const [messages, setMessages] = useState<string[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(1);
// 初始化加载最新消息(第 1 页)
useEffect(() => {
fetchChatHistory(page);
}, [page]);
// 模拟加载聊天历史记录(页号越小,消息越新)
const fetchChatHistory = async (currentPage: number) => {
await new Promise<void>((resolve) => {
setTimeout(() => {
const newMessages = Array.from({ length: 5 }, (_, i) =>
`历史消息 ${(currentPage - 1) * 5 + i + 1}(页 ${currentPage})`
);
setMessages(prev => [...newMessages, ...prev]); // 新消息添加到数组头部(反向)
// 最多加载 4 页历史消息
if (currentPage >= 4) {
setHasMore(false);
}
resolve();
}, 800);
});
};
// 滚动到顶部时加载更多历史消息
const fetchMoreHistory = () => {
setPage(prev => prev + 1);
};
return (
<div style={{ height: '600px', border: '1px solid #eee', borderRadius: '4px' }}>
<h3 style={{ padding: '10px', borderBottom: '1px solid #eee' }}>聊天窗口</h3>
{/* 反向滚动容器 */}
<InfiniteScroll
scrollableTarget="chatContainer" // 自定义滚动容器 id
dataLength={messages.length}
next={fetchMoreHistory}
hasMore={hasMore}
reverse={true} // 启用反向滚动(顶部加载更多)
loader={<div style={{ textAlign: 'center', padding: '10px' }}>加载更早的消息...</div>}
endMessage={<div style={{ textAlign: 'center', padding: '10px', color: '#666' }}>已加载全部历史消息</div>}
>
<div id="chatContainer" style={{ height: '540px', overflow: 'auto', padding: '10px' }}>
{messages.map((msg, index) => (
<div
key={index}
style={{
margin: '8px 0',
padding: '8px 12px',
backgroundColor: '#f5f5f5',
borderRadius: '8px',
maxWidth: '80%',
}}
>
{msg}
</div>
))}
</div>
</InfiniteScroll>
</div>
);
};
export default ChatInfiniteScroll;
核心逻辑:
reverse={true}
:插件监听"滚动到顶部"事件,触发next
加载历史数据;- 新加载的历史消息通过
[...newMessages, ...prev]
添加到数组头部,确保 UI 中"历史消息在上方,最新消息在下方"。
4.2 下拉刷新 + 无限滚动
结合"下拉刷新"(更新最新数据)和"无限滚动"(加载更多历史数据),满足列表的完整交互需求:
jsx
const PullRefreshAndInfinite = () => {
const [list, setList] = useState<number[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(1);
const [pullDownToRefresh, setPullDownToRefresh] = useState<boolean>(false); // 控制下拉刷新状态
// 初始化加载
useEffect(() => {
fetchData(page);
}, [page]);
// 加载列表数据
const fetchData = async (currentPage: number) => {
await new Promise<void>((resolve) => {
setTimeout(() => {
const newData = Array.from({ length: 10 }, (_, i) => (currentPage - 1) * 10 + i + 1);
setList(prev => currentPage === 1 ? newData : [...prev, ...newData]); // 第一页覆盖,后续页追加
setHasMore(currentPage < 5);
resolve();
}, 800);
});
};
// 加载更多(滚动到底部)
const fetchMoreData = () => {
setPage(prev => prev + 1);
};
// 下拉刷新(更新最新数据)
const handlePullDownRefresh = async () => {
setPullDownToRefresh(true); // 显示"刷新中"状态
await fetchData(1); // 重新请求第一页数据(最新数据)
setPullDownToRefresh(false); // 结束刷新
};
return (
<InfiniteScroll
dataLength={list.length}
next={fetchMoreData}
hasMore={hasMore}
loader={<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>}
endMessage={<div style={{ textAlign: 'center', padding: '20px' }}>已加载全部</div>}
// 下拉刷新配置
pullDownToRefresh={pullDownToRefresh}
pullDownToRefreshContent={<div style={{ textAlign: 'center', padding: '20px' }}>下拉可刷新</div>}
releaseToRefreshContent={<div style={{ textAlign: 'center', padding: '20px' }}>释放即可刷新</div>}
onPullDownRefresh={handlePullDownRefresh}
>
<ul style={{ listStyle: 'none', padding: '0 20px' }}>
{list.map(item => (
<li
key={item}
style={{ padding: '15px', margin: '10px 0', borderBottom: '1px solid #eee' }}
>
带下拉刷新的列表项 {item}
</li>
))}
</ul>
</InfiniteScroll>
);
};
export default PullRefreshAndInfinite;
核心逻辑:
pullDownToRefresh
:控制下拉刷新状态(true
时显示刷新中 UI,禁止重复触发);onPullDownRefresh
:下拉到阈值并释放后触发,通常用于重新请求第一页数据(获取最新内容);- 数据更新时需将
pullDownToRefresh
设为false
,否则刷新状态会一直保持。
4.3 加载失败重试
实际项目中可能出现接口请求失败的情况,需提供"重试加载"功能,可通过状态管理控制加载状态与重试逻辑:
jsx
const RetryOnFail = () => {
const [list, setList] = useState<number[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(1);
const [isLoading, setIsLoading] = useState<boolean>(false); // 加载中状态(防止重复请求)
const [loadError, setLoadError] = useState<boolean>(false); // 加载失败状态
// 初始化加载
useEffect(() => {
fetchData(page);
}, [page]);
// 数据请求逻辑(含失败处理)
const fetchData = async (currentPage: number) => {
setIsLoading(true);
setLoadError(false); // 重置失败状态
try {
const response = await new Promise<number[]>((resolve, reject) => {
setTimeout(() => {
// 模拟 30% 概率请求失败
if (Math.random() < 0.3) {
reject(new Error('接口请求失败'));
} else {
const newData = Array.from({ length: 10 }, (_, i) => (currentPage - 1) * 10 + i + 1);
resolve(newData);
}
}, 800);
});
setList(prev => [...prev, ...response]);
setHasMore(currentPage < 5);
} catch (err) {
console.error('加载失败:', err);
setLoadError(true); // 标记加载失败
} finally {
setIsLoading(false); // 结束加载状态
}
};
// 加载更多(仅当非加载中、非失败时触发)
const fetchMoreData = () => {
if (!isLoading && !loadError) {
setPage(prev => prev + 1);
}
};
// 重试加载当前页
const retryLoad = () => {
fetchData(page);
};
return (
<InfiniteScroll
dataLength={list.length}
next={fetchMoreData}
hasMore={hasMore && !loadError} // 失败时停止触发自动加载
disableScroll={isLoading || loadError} // 加载中/失败时禁用滚动触发
// 加载中/失败 UI 切换
loader={isLoading ? <div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div> : null}
endMessage={
hasMore ? null : (
<div style={{ textAlign: 'center', padding: '20px', color: '#666' }}>已加载全部数据</div>
)
}
>
<ul style={{ listStyle: 'none', padding: '0 20px' }}>
{list.map(item => (
<li
key={item}
style={{ padding: '15px', margin: '10px 0', border: '1px solid #eee', borderRadius: '4px' }}
>
支持重试的列表项 {item}
</li>
))}
{/* 加载失败提示与重试按钮 */}
{loadError && (
<div style={{ textAlign: 'center', padding: '20px' }}>
<p style={{ color: 'red' }}>加载失败,请重试</p>
<button
onClick={retryLoad}
style={{
padding: '8px 16px',
margin: '10px 0',
border: 'none',
backgroundColor: '#007bff',
color: 'white',
borderRadius: '4px',
cursor: 'pointer',
}}
>
重试加载
</button>
</div>
)}
</ul>
</InfiniteScroll>
);
};
export default RetryOnFail;
核心逻辑:
isLoading
:防止滚动时重复触发请求(加载中时禁用next
);loadError
:标记请求失败状态,显示重试按钮,同时停止自动加载;retryLoad
:重试时重新请求当前页数据,而非直接请求下一页,确保数据连续性。
5. 性能优化建议
无限滚动场景若处理不当,可能导致列表渲染性能下降(如 DOM 元素过多、重复渲染),以下是关键优化点:
5.1 实现列表项虚拟滚动
当列表数据量极大(如超过 100 条)时,直接渲染所有 DOM 元素会占用大量内存,导致页面卡顿。建议结合 react-window 或 react-virtualized 实现"虚拟滚动"(仅渲染可视区域内的列表项)。
示例:结合 react-window 优化
bash
# 安装 react-window
npm install react-window --save
jsx
import { FixedSizeList as List } from 'react-window';
import InfiniteScroll from 'react-infinite-scroll-component';
const VirtualizedInfiniteScroll = () => {
const [list, setList] = useState<number[]>([]);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(1);
// 数据请求逻辑(同前)
useEffect(() => {
fetchData(page);
}, [page]);
const fetchData = async (currentPage: number) => {
// 模拟数据请求...
const newData = Array.from({ length: 20 }, (_, i) => (currentPage - 1) * 20 + i + 1);
setList(prev => [...prev, ...newData]);
setHasMore(currentPage < 10);
};
const fetchMoreData = () => {
setPage(prev => prev + 1);
};
// 渲染单个列表项(react-window 要求)
const renderListItem = ({ index, style }: { index: number; style: React.CSSProperties }) => {
const item = list[index];
return (
<div style={{ ...style, padding: '15px', borderBottom: '1px solid #eee' }}>
虚拟滚动列表项 {item}
</div>
);
};
return (
<InfiniteScroll
dataLength={list.length}
next={fetchMoreData}
hasMore={hasMore}
loader={<div style={{ textAlign: 'center', padding: '20px' }}>加载中...</div>}
endMessage={<div style={{ textAlign: 'center', padding: '20px' }}>已加载全部</div>}
>
{/* 虚拟滚动列表:仅渲染可视区域内的项(高度 500px,每项高度 60px) */}
<List
height={500}
width="100%"
itemCount={list.length}
itemSize={60} // 每项固定高度
>
{renderListItem}
</List>
</InfiniteScroll>
);
};
export default VirtualizedInfiniteScroll;
优化原理:react-window 通过计算可视区域范围,仅渲染"能看到的列表项",即使列表有 1000 条数据,DOM 元素数量也仅为"可视区域高度 / 每项高度"(通常 10 条左右),大幅降低渲染压力。
5.2 避免重复请求
- 用
isLoading
状态控制:请求发起时设为true
,请求结束(成功/失败)后设为false
,next
回调中判断!isLoading
才触发下一次请求; - 限制请求频率:避免快速滚动时频繁触发
next
(插件内部已做防抖,但可结合threshold
增大提前加载距离,减少请求次数)。
5.3 优化数据更新逻辑
-
避免直接修改原数组:使用
setList(prev => [...prev, ...newData])
而非list.push(...newData)
,确保 React 能正确识别状态变化; -
分页数据去重:若接口可能返回重复数据(如分页参数异常),可在更新列表前通过
Set
或filter
去重:jsxsetList(prev => { const uniqueData = [...new Set([...prev, ...newData])]; return uniqueData; });
5.4 减少不必要的重渲染
-
列表项用
memo
包裹:若列表项为自定义组件,且 props 不变时无需重渲染,可通过React.memo
优化:jsxconst ListItem = React.memo(({ item }: { item: number }) => { return <div style={{ padding: '15px' }}>列表项 {item}</div>; });
-
避免在渲染中定义函数:将
fetchMoreData
、retryLoad
等函数用useCallback
包裹,防止每次渲染生成新函数导致子组件重渲染:jsxconst fetchMoreData = useCallback(() => { if (!isLoading && !loadError) { setPage(prev => prev + 1); } }, [isLoading, loadError]);
6. 常见问题与解决方案
6.1 滚动不触发加载?
- 检查
dataLength
:必须正确传递"已加载数据的长度",插件通过dataLength
判断"是否已滚动到可加载位置"(若dataLength=0
,可能因"无数据可滚动"无法触发); - 确认滚动容器:若用
scrollableTarget
,需确保容器设置了id
且overflow: auto
+ 固定高度(无固定高度则容器会被内容撑满,无法滚动); - 检查
hasMore
:若初始hasMore=false
,插件会直接显示endMessage
,不触发next
; - 查看控制台报错:若存在 JS 错误(如
fetchData
未定义),会导致next
回调执行失败,需优先修复错误。
6.2 加载后列表不滚动到底部?
-
反向滚动场景(
reverse=true
):加载历史消息后,需手动滚动到"加载前的位置"(避免每次加载都跳到顶部),可通过scrollableTarget
对应的 DOM 元素控制:jsxconst chatContainer = document.getElementById('chatContainer'); if (chatContainer) { const scrollTop = chatContainer.scrollTop; // 记录加载前的滚动位置 // 数据更新后恢复滚动位置 setTimeout(() => { chatContainer.scrollTop = scrollTop; }, 0); }
-
正常滚动场景:若需加载后自动滚动到底部,可在
setList
后调用scrollTo
:jsxsetList(prev => [...prev, ...newData], () => { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); });
6.3 下拉刷新不生效?
- 确保配置完整:需同时设置
pullDownToRefresh
(控制状态)、onPullDownRefresh
(回调)、pullDownToRefreshContent
(提示 UI); onPullDownRefresh
中必须重置状态:回调执行完后需将pullDownToRefresh
设为false
,否则刷新状态会一直保持;- 检查滚动容器:下拉刷新仅支持
window
滚动或"高度固定且可滚动的容器",若容器无固定高度,可能无法触发下拉逻辑。
6.4 移动端滚动不流畅?
-
关闭触摸事件阻止:若项目中存在
touchmove
事件阻止默认行为(如e.preventDefault()
),可能影响移动端滚动,需在滚动容器内放行触摸事件; -
优化列表项样式:避免使用复杂 CSS(如
box-shadow
、gradient
)或大量图片,可通过will-change: transform
提示浏览器提前优化渲染:css.list-item { will-change: transform; /* 其他样式 */ }
7. 总结
react-infinite-scroll-component 是一款"开箱即用"的无限滚动插件,其核心价值在于简化底层滚动逻辑,让开发者专注于数据处理与 UI 设计。通过本文的讲解,可掌握:
- 基础用法 :快速实现"滚动加载更多",配置
dataLength
、next
、hasMore
核心参数; - 场景进阶:处理反向滚动(聊天记录)、下拉刷新、加载失败重试等实际需求;
- 性能优化:结合虚拟滚动、避免重复请求、减少重渲染,确保大列表流畅运行;
- 问题排查:解决滚动不触发、加载异常等常见问题。
适用场景包括:商品列表、文章列表、聊天记录、数据报表等"需要批量加载数据且滚动查看"的界面。在实际项目中,建议根据数据量大小选择是否结合虚拟滚动,并始终关注"用户体验"(如加载状态提示、失败重试、避免卡顿),让无限滚动既实用又流畅。
本次分享就到这儿啦,我是鹏多多,深耕前端的技术创作者,如果您看了觉得有帮助,欢迎评论,关注,点赞,转发,我们下次见~
PS:在本页按F12,在console中输入document.getElementsByClassName('panel-btn')[0].click();有惊喜哦~
往期文章
- 纯前端人脸识别利器:face-api.js手把手深入解析教学
- 关于React父组件调用子组件方法forwardRef的详解和案例
- React跨组件数据共享useContext详解和案例
- Web图像编辑神器tui.image-editor从基础到进阶的实战指南
- 开发个人微信小程序类目选择/盈利方式/成本控制与服务器接入指南
- 前端图片裁剪Cropper.js核心功能与实战技巧详解
- 编辑器也有邪修?盘点VS Code邪门/有趣的扩展
- js使用IntersectionObserver实现目标元素可见度的交互
- Web前端页面开发阿拉伯语种适配指南
- 让网页拥有App体验?PWA 将网页变为桌面应用的保姆级教程PWA
- 使用nvm管理node.js版本以及更换npm淘宝镜像源
- 手把手教你搭建规范的团队vue项目,包含commitlint,eslint,prettier,husky,commitizen等等