- 使用
useEffect实现页面打开自动请求接口,解决无限循环坑 - 利用
useEffect依赖项,实现输入框实时搜索课程 - 封装公共 Loading 加载组件,优化网络请求用户体验
前置:Expo 项目,本地后端接口
http://localhost:3000/search,支持q查询参数做搜索。
一、useEffect:页面挂载自动请求接口
之前我们写的案例,需要点击按钮才触发请求。真实 App,打开页面就要自动加载数据。
如果你写过 Vue,初始化请求会放在 onMounted;在 React / React Native 中对应的就是 useEffect 这个 Hook。 useEffect 能力很强,可以模拟 Vue 的 onMounted、onUpdated,甚至 watch,最常用场景就是组件挂载后请求接口。
1.1 基础写法(会踩坑版本)
先引入 Hook:
<span>import</span> { useState, useEffect } <span>from</span> <span>'react'</span>;
把接口请求函数放进 useEffect:
<span>const</span> [courses, setCourses] = <span>useState</span>([]);
<span>const</span> <span>fetchData</span> = <span>async</span> (<span></span>) => {
<span>const</span> res = <span>await</span> <span>fetch</span>(<span>'http://localhost:3000/search'</span>);
<span>const</span> { data } = <span>await</span> res.<span>json</span>();
<span>setCourses</span>(data.<span>courses</span>);
};
<span>// ❌ 不写依赖数组,会无限循环</span>
<span>useEffect</span>(<span>() =></span> {
<span>fetchData</span>();
});
渲染列表,删掉按钮:
<span>return</span> (
<span><span><<span>View</span> <span>style</span>=<span>{styles.container}</span>></span>
{courses.map((course) => (
<span><<span>Text</span> <span>key</span>=<span>{course.id}</span>></span>{course.name}<span></<span>Text</span>></span>
))}
<span></<span>View</span>></span></span>
);
运行后打开调试工具(终端按j),你会发现接口疯狂重复请求,无限循环。
问题根源
useEffect 默认规则:组件每一次重新渲染,都会执行 useEffect
- 组件初次渲染 → 执行 useEffect → 请求接口
setCourses修改 state → 组件重渲染- 重渲染后再次执行 useEffect → 再次请求,循环往复
✅ 解决方案:给 useEffect 第二个参数,**空依赖数组 **[]
<span>// ✅ 空数组:仅组件挂载时执行一次</span>
<span>useEffect</span>(<span>() =></span> {
<span>fetchData</span>();
}, []);
核心知识点:
- 不写第二个参数:每次组件渲染都会执行
[]空数组:只在组件首次挂载执行一次
完整基础版 App.js
<span>import</span> { <span>StyleSheet</span>, <span>Text</span>, <span>View</span> } <span>from</span> <span>'react-native'</span>;
<span>import</span> { useState, useEffect } <span>from</span> <span>'react'</span>;
<span>export</span> <span>default</span> <span>function</span> <span>App</span>(<span></span>) {
<span>const</span> [courses, setCourses] = <span>useState</span>([]);
<span>const</span> <span>fetchData</span> = <span>async</span> (<span></span>) => {
<span>const</span> res = <span>await</span> <span>fetch</span>(<span>'http://localhost:3000/search'</span>);
<span>const</span> { data } = <span>await</span> res.<span>json</span>();
<span>setCourses</span>(data.<span>courses</span>);
};
<span>useEffect</span>(<span>() =></span> {
<span>fetchData</span>();
}, []);
<span>return</span> (
<span><span><<span>View</span> <span>style</span>=<span>{styles.container}</span>></span>
{courses.map((course) => (
<span><<span>Text</span> <span>key</span>=<span>{course.id}</span>></span>{course.name}<span></<span>Text</span>></span>
))}
<span></<span>View</span>></span></span>
);
}
<span>const</span> styles = <span>StyleSheet</span>.<span>create</span>({
<span>container</span>: {
<span>flex</span>: <span>1</span>,
<span>backgroundColor</span>: <span>'#fff'</span>,
<span>alignItems</span>: <span>'center'</span>,
<span>justifyContent</span>: <span>'center'</span>,
},
});
二、useEffect 依赖项:实现输入框实时搜索
学会挂载请求,我们进阶:利用依赖项实现搜索。
2.1 接口说明
后端接口支持查询参数 q
http://localhost:3000/search?q=node→ node 相关课程http://localhost:3000/search?q=react→ react 相关课程
2.2 TextInput 输入框组件
React Native 使用 TextInput 实现输入框。
onChangeText:监听输入变化,拿到输入文本placeholder:输入框提示文字
<span>import</span> { <span>TextInput</span>, <span>StyleSheet</span>, <span>Text</span>, <span>View</span> } <span>from</span> <span>'react-native'</span>;
<span>import</span> { useState, useEffect } <span>from</span> <span>'react'</span>;
<span>export</span> <span>default</span> <span>function</span> <span>App</span>(<span></span>) {
<span>const</span> [courses, setCourses] = <span>useState</span>([]);
<span>// 搜索关键词state</span>
<span>const</span> [keyword, setKeyword] = <span>useState</span>(<span>''</span>);
<span>const</span> <span>fetchData</span> = <span>async</span> (<span></span>) => {
<span>// 把keyword拼接到url参数</span>
<span>const</span> res = <span>await</span> <span>fetch</span>(<span>`http://localhost:3000/search?q=<span>${keyword}</span>`</span>);
<span>const</span> { data } = <span>await</span> res.<span>json</span>();
<span>setCourses</span>(data.<span>courses</span>);
};
<span>// 🎯 依赖项设置为keyword</span>
<span>useEffect</span>(<span>() =></span> {
<span>fetchData</span>();
}, [keyword]);
<span>return</span> (
<span><span><<span>View</span> <span>style</span>=<span>{styles.container}</span>></span>
<span><<span>Text</span>></span>您搜索的关键词是:{keyword}<span></<span>Text</span>></span>
<span><<span>TextInput</span>
<span>style</span>=<span>{styles.input}</span>
<span>placeholder</span>=<span>"请填写要搜索的课程!"</span>
<span>onChangeText</span>=<span>{(text)</span> =></span> setKeyword(text)}
defaultValue={keyword}
/>
{courses.map((course) => (
<span><<span>Text</span> <span>key</span>=<span>{course.id}</span>></span>{course.name}<span></<span>Text</span>></span>
))}
<span></<span>View</span>></span></span>
);
}
<span>const</span> styles = <span>StyleSheet</span>.<span>create</span>({
<span>container</span>: {
<span>flex</span>: <span>1</span>,
<span>backgroundColor</span>: <span>'#fff'</span>,
<span>alignItems</span>: <span>'center'</span>,
<span>justifyContent</span>: <span>'center'</span>,
},
<span>input</span>: {
<span>height</span>: <span>40</span>,
<span>width</span>: <span>300</span>,
<span>margin</span>: <span>12</span>,
<span>padding</span>: <span>10</span>,
<span>borderWidth</span>: <span>1</span>,
<span>borderColor</span>: <span>'#ccc'</span>,
<span>borderRadius</span>: <span>5</span>,
},
});
placeholder,就和HTML里表单的一样,是用来显示提示信息的。onChangeText,看名字就知道了,和JS里的onchange事件一个意思。- 当输入框的数据改变了,可以用
text来获取到用户填写的内容。 - 再调用
setKeyword将用户填写的内容,设置给keyword状态。 defaultValue就是表单里的默认值。- 为了确认到底获取到了没有?可以在最上面,加个显示
keyword的代码。
2.3 依赖项原理
useEffect 第二个数组就是依赖项数组:
数组里面的变量一旦发生改变,useEffect 就自动重新执行。
这里我们写 [keyword]: 用户打字 → keyword 更新 → 触发 useEffect → 重新请求接口,刷新列表。 打开调试网络面板可以看到:每输入一个字符就发起一次请求。
知识点汇总
- 无第二个参数:每次渲染执行
[]:仅挂载执行一次[xxx]:xxx 变量改变,自动执行副作用
三、封装 Loading 加载组件,优化请求体验
网络请求是异步耗时操作,数据回来之前,页面空白会很影响体验。我们加上 loading 转圈动画。
3.1 增加 loading 状态
新增 loading state,初始值为 true,页面打开默认加载中。 使用 try...finally,无论请求成功 / 失败,finally 都会执行,关闭 loading。
<span>const</span> [loading, setLoading] = <span>useState</span>(<span>true</span>);
<span>const</span> <span>fetchData</span> = <span>async</span> (<span></span>) => {
<span>try</span> {
<span>const</span> res = <span>await</span> <span>fetch</span>(<span>`http://localhost:3000/search?q=<span>${keyword}</span>`</span>);
<span>const</span> { data } = <span>await</span> res.<span>json</span>();
<span>setCourses</span>(data.<span>courses</span>);
} <span>finally</span> {
<span>setLoading</span>(<span>false</span>);
}
};
- 给里面的内容,加上了
try...fianlly。 - 当请求完成后,在
finally里面,将加载中状态设置为false。 - 因为读取的是本地接口,请求的速度会非常非常快,会导致根本看不到什么效果。
- 我们可以在请求之前后,加上个延迟 2 秒,便于观察效果。
3.2 ActivityIndicator 原生加载指示器
React Native 内置 ActivityIndicator,原生转圈加载动画。 增加判断:如果loading=true,直接返回加载组件,不再渲染搜索和列表。
3.3 抽离公共 Loading 组件
加载组件项目多个页面都会使用,直接抽成独立组件,避免重复代码。
新建文件 components/shared/Loading.js
<span>import</span> { <span>ActivityIndicator</span>, <span>StyleSheet</span> } <span>from</span> <span>'react-native'</span>;
<span>/**
* 全局Loading加载组件
*/</span>
<span>export</span> <span>default</span> <span>function</span> <span>Loading</span>(<span></span>) {
<span>return</span> <span><span><<span>ActivityIndicator</span> <span>size</span>=<span>"small"</span> <span>color</span>=<span>"#1f99b0"</span> <span>style</span>=<span>{styles.loading}</span> /></span></span>;
}
<span>const</span> styles = <span>StyleSheet</span>.<span>create</span>({
<span>loading</span>: {
<span>backgroundColor</span>: <span>'#fff'</span>,
<span>position</span>: <span>'absolute'</span>,
<span>top</span>: <span>0</span>,
<span>left</span>: <span>0</span>,
<span>right</span>: <span>0</span>,
<span>bottom</span>: <span>0</span>,
<span>zIndex</span>: <span>1</span>,
},
});
3.4 最终完整版 App.js
<span>import</span> { <span>TextInput</span>, <span>StyleSheet</span>, <span>Text</span>, <span>View</span> } <span>from</span> <span>'react-native'</span>;
<span>import</span> { useState, useEffect } <span>from</span> <span>'react'</span>;
<span>import</span> <span>Loading</span> <span>from</span> <span>'./components/shared/Loading'</span>;
<span>export</span> <span>default</span> <span>function</span> <span>App</span>(<span></span>) {
<span>const</span> [courses, setCourses] = <span>useState</span>([]);
<span>const</span> [keyword, setKeyword] = <span>useState</span>(<span>''</span>);
<span>const</span> [loading, setLoading] = <span>useState</span>(<span>true</span>);
<span>const</span> <span>fetchData</span> = <span>async</span> (<span></span>) => {
<span>try</span> {
<span>const</span> res = <span>await</span> <span>fetch</span>(<span>`http://localhost:3000/search?q=<span>${keyword}</span>`</span>);
<span>const</span> { data } = <span>await</span> res.<span>json</span>();
<span>setCourses</span>(data.<span>courses</span>);
} <span>finally</span> {
<span>setLoading</span>(<span>false</span>);
}
};
<span>useEffect</span>(<span>() =></span> {
<span>fetchData</span>();
}, [keyword]);
<span>// 加载中渲染Loading组件</span>
<span>if</span> (loading) {
<span>return</span> <span><span><<span>Loading</span> /></span></span>;
}
<span>return</span> (
<span><span><<span>View</span> <span>style</span>=<span>{styles.container}</span>></span>
<span><<span>Text</span>></span>您搜索的关键词是:{keyword}<span></<span>Text</span>></span>
<span><<span>TextInput</span>
<span>style</span>=<span>{styles.input}</span>
<span>placeholder</span>=<span>"请填写要搜索的课程!"</span>
<span>onChangeText</span>=<span>{(text)</span> =></span> setKeyword(text)}
defaultValue={keyword}
/>
{courses.map((course) => (
<span><<span>Text</span> <span>key</span>=<span>{course.id}</span>></span>{course.name}<span></<span>Text</span>></span>
))}
<span></<span>View</span>></span></span>
);
}
<span>const</span> styles = <span>StyleSheet</span>.<span>create</span>({
<span>container</span>: {
<span>flex</span>: <span>1</span>,
<span>backgroundColor</span>: <span>'#fff'</span>,
<span>alignItems</span>: <span>'center'</span>,
<span>justifyContent</span>: <span>'center'</span>,
},
<span>input</span>: {
<span>height</span>: <span>40</span>,
<span>width</span>: <span>300</span>,
<span>margin</span>: <span>12</span>,
<span>padding</span>: <span>10</span>,
<span>borderWidth</span>: <span>1</span>,
<span>borderColor</span>: <span>'#ccc'</span>,
<span>borderRadius</span>: <span>5</span>,
},
});
⚠️ 踩坑提醒:Android 模拟器访问
localhost会请求失败,接口地址要替换成电脑本机 IP。ActivityIndicator在 iOS 和 Android 原生样式略有差异,属于正常现象。
总结
-
useEffect是 RN 函数组件处理副作用的核心,依赖数组是灵魂- 空数组
[]:挂载执行一次 - 写入变量:变量变更,自动执行请求
- 空数组
-
TextInput实现输入框,onChangeText监听输入内容,配合 useEffect 依赖项轻松实现实时搜索 -
网络请求增加 loading 状态,使用
ActivityIndicator;通用 UI 抽离为独立组件,减少重复代码,提升复用性
适合正在学习 React Native 函数组件与 Hooks 的开发者,内容从踩坑到封装循序渐进,能快速掌握挂载请求、实时搜索和 Loading 复用,实用性强。