React Hooks 快速入门:从一个数据请求开始
Posted 凯小默:树上的男爵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React Hooks 快速入门:从一个数据请求开始相关的知识,希望对你有一定的参考价值。
前言
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
早期的写法以 Class 类组件为主,附带一些纯用于展示的函数组件,但是函数组件是不能控制自身的状态的。Hooks 写法引入之后,函数组件的写法开始流行起来。
函数组件引入了多种钩子函数如 useEffect、useState、useRef、useCallback、useMemo、useReducer
等等,通过这些钩子函数来管理函数组件的各自状态。
下面就通过一些例子来简单了解部分 hook。
1、新建 hooks-demo 工程项目
我的 node 版本是 v12.13.0
,npm 版本是 6.12.0
。
执行下面命令:
npm init @vitejs/app hooks-demo --template react
执行完之后,安装依赖运行
npm install
npm run dev
2、基础 Hook
useState
函数内声明变量,可以通过 useState
方法,它接受一个参数,可以为默认值,也可以为一个函数。
修改 App.jsx
,改成下面代码:
import React, useState from 'react'
function App()
const [data, setData] = useState([1, 2, 3, 4, 5])
return (
<div className="kaimo-app">
data.map((item, index) => <div key=index>kaimo:item</div>)
</div>
)
export default App
页面显示结果如下:
useEffect
useEffect()
的作用就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,副效应函数也会执行。
无需清除的 effect
在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
需要清除的 effect
还有一些副作用是需要清除的。例如订阅外部数据源。这种情况下,清除工作是非常重要的,可以防止引起内存泄露!
useEffect 的用法
我们先通过 useEffect
副作用,模拟请求一个接口数据。
import React, useState, useEffect from 'react'
// 模拟数据接口,2 秒后返回数据
const getDataList = () =>
return new Promise((resolve, reject) =>
setTimeout(() =>
resolve([6, 7, 8, 9, 10])
, 2000)
)
function App()
const [data, setData] = useState([1, 2, 3, 4, 5])
useEffect(() =>
(async() =>
const kaimo = await getDataList()
console.log('useEffect:kaimo', kaimo)
setData(kaimo)
)()
)
return (
<div className="kaimo-app">
data.map((item, index) => <div key=index>kaimo:item</div>)
</div>
)
export default App
页面显示结果:
我们发现函数组件默认进来之后,会执行 useEffect
中的回调函数,但是当 setData
执行之后,App 组件再次刷新,刷新之后会再次执行 useEffect
的回调函数,变成一个死循环了。
useEffect 的第二个参数
有时候,我们不希望 useEffect()
每次渲染都执行,这时可以使用它的第二个参数,使用一个数组指定副效应函数的依赖项,只有依赖项发生变化,才会重新渲染。
function Welcome(props)
useEffect(() =>
document.title = `Hello, $props.name`;
, [props.name]);
return <h1>Hello, props.name</h1>;
上面例子中,useEffect()
的第二个参数是一个数组,指定了第一个参数(副效应函数)的依赖项(props.name)。只有该变量发生变化时,副效应函数才会执行。
如果第二个参数是一个空数组,就表明副效应参数没有任何依赖项。
因此,副效应函数这时只会在组件加载进入 DOM 后执行一次,后面组件重新渲染,就不会再次执行。这很合理,由于副效应不依赖任何变量,所以那些变量无论怎么变,副效应函数的执行结果都不会改变,所以运行一次就够了。
我们把副作用的部分加上空数组试试:
useEffect(() =>
(async() =>
const kaimo = await getDataList()
console.log('useEffect:kaimo', kaimo)
setData(kaimo)
)()
, [])
我们发现只会执行一次,不会再出现死循环了。
给请求一个 query 参数
下面我们给请求加一个 query 参数,代码如下:
import React, useState, useEffect from 'react'
// 模拟数据接口,2 秒后返回数据
const getDataList = (query) =>
return new Promise((resolve, reject) =>
setTimeout(() =>
console.log('kaimo-query', query);
resolve([6, 7, 8, 9, 10])
, 2000)
)
function App()
const [data, setData] = useState([1, 2, 3, 4, 5])
const [query, setQuery] = useState('')
useEffect(() =>
(async() =>
const kaimo = await getDataList(query)
console.log('useEffect:kaimo', kaimo)
setData(kaimo)
)()
, [query])
return (
<div className="kaimo-app">
<input type="text" placeholder='请输入查询参数' onChange=e => setQuery(e.target.value) />
data.map((item, index) => <div key=index>kaimo:item</div>)
</div>
)
export default App
然后我输入 1 改变 query 的值,副作用函数便会被执行,结果如下:
如果你的接口有查询参数,可以将参数设置在 useEffect 的第二个参数的数组值中,这样改变查询变量的时候,副作用便会再次触发执行,相应的函数也会重新带着最新的参数,获取接口数据。
useContext
const value = useContext(MyContext);
接收一个 context 对象(React.createContext
的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider>
的 value prop
决定。
当组件上层最近的 <MyContext.Provider>
更新时,该 Hook 会触发重渲染,并使用最新传递给 MyContext provider
的 context value
值。即使祖先使用 React.memo
或 shouldComponentUpdate
,也会在组件本身使用 useContext
时重新渲染。
下面修改 App.jsx
代码为:
import React, useContext from 'react'
const themes =
light:
foreground: "#fff",
background: "orange"
,
dark:
foreground: "#000",
background: "green"
;
const ThemeContext = React.createContext(themes.light);
function ThemedButton()
const theme = useContext(ThemeContext);
console.log('ThemedButton', theme)
return (
<button style= background: theme.background, color: theme.foreground >
Kaimo Theme Button
</button>
);
function Toolbar()
return (
<div>
<ThemedButton />
</div>
);
function App()
return (
<ThemeContext.Provider value=themes.dark>
<Toolbar />
</ThemeContext.Provider>
);
export default App
我们发现按钮用的是 dark 的主题:
3、自定义 Hook
接下来,我们把上面的模拟接口请求抽离成一个自定义 hook
,新建 useKaimoApi.jsx
,在里面添加
import useState, useEffect from 'react'
// 模拟数据接口,2 秒后返回数据
const getDataList = (query) =>
return new Promise((resolve, reject) =>
setTimeout(() =>
console.log('kaimo-query', query);
resolve([6, 7, 8, 9, 10])
, 2000)
)
// 自定义 hook
const useKaimoApi = () =>
const [data, setData] = useState([1, 2, 3, 4, 5])
const [query, setQuery] = useState('')
useEffect(() =>
(async() =>
const kaimo = await getDataList(query)
console.log('useEffect:kaimo', kaimo)
setData(kaimo)
)()
, [query])
return [data, setQuery]
export default useKaimoApi
App.jsx
中也修改一下:
import React from 'react'
import useKaimoApi from './useKaimoApi'
function App()
const [data, setQuery] = useKaimoApi()
return (
<div className="kaimo-app">
<input type="text" placeholder='请输入查询参数' onChange=e => setQuery(e.target.value) />
data.map((item, index) => <div key=index>kaimo:item</div>)
</div>
)
export default App
结果是一样的:
我们可通过自定义 Hook 的形式,把公共逻辑抽离出来复用,这也是之前 Class 类组件不能做到的。
4、额外的 Hook
useMemo
返回一个 memoized
值。
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized
值。这种优化有助于避免在每次渲染时都进行高开销的计算。
下面我们尝试修改一下 App.jsx
里的代码:
import React, useState, useEffect from 'react'
// 在内部新增一个子组件,子组件接收父组件传进来的一个对象,作为子组件的 useEffect 的第二个依赖参数。
function Child(data)
useEffect(() =>
console.log('kaimo-child:查询条件', data)
, [data])
return <div className='kaimo-child'>子组件</div>
function App()
const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [kw, setKw] = useState('')
const kaimoData =
name,
phone
return (
<div className="kaimo-app">
<input type="text" placeholder='请输入姓名' onChange=e => setName(e.target.value) />
<input type="text" placeholder='请输入电话' onChange=e => setPhone(e.target.value) />
<input type="text" placeholder='请输入关键词' onChange=e => setKw(e.target.value) />
<Child data=kaimoData />
</div>
)
export default App
下面我们依次在输入框里输入1,2,3,发现输入 kw 为 3 的时候,发现也执行了 useEffect 内的回调函数,而子组件并没有监听 kw 的变化。
这个时候我们可以通过 useMemo
将 data 包装一下,告诉 data 它需要监听的值。
import React, useState, useEffect, useMemo from 'react'
...
const kaimoData = useMemo(() => (
name,
phone
), [name, phone])
...
添加 useMemo 之后,我们发现 kw 输入 3 的时候,不会在触发。它相当于把父组件需要传递的参数做了一个标记,无论父组件其他状态更新任何值,都不会影响要传递给子组件的对象。
useCallback
const memoizedCallback = useCallback(
() =>
doSomething(a, b);
,
[a, b],
);
返回一个 memoized 回调函数。
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。
useCallback 也是和 useMemo 有类似的功能。
下面我们传递一个函数给子组件,修改 App.jsx
如下所示:
import React, useState, useEffect from 'react'
// 在内部新增一个子组件,子组件接收父组件传进来的一个对象,作为子组件的 useEffect 的第二个依赖参数。
function Child(callback)
useEffect(() =>
callback();
, [callback])
return <div className='kaimo-child'>子组件</div>
function App()
const [name, setName] = useState('')
const [phone, setPhone] = useState('')
const [kw, setKw] = useState('')
const kaimoCallback = () =>
console.log('kaimo-child:查询条件',
name,
phone
)
return (
<div className="kaimo-app">
<input type="text" placeholder='请输入姓名' onChange=e => setName(e.target.value) />
<input type="text" placeholder='请输入电话' onChange=e => setPhone(e.target.value) />
<input type="text" placeholder='请输入关键词' onChange=e => setKw(e.target.value) />
<Child callback=kaimoCallback />
</div>
)
export default App
当我们修改任何状态值,都会触发子组件的回调函数执行。
我们给要传递的函数,包裹一层 useCallback,如下所示:
import React, useState, useEffect, useCallback from 'react'
...
const kaimoCallback = useCallback(() =>
console.log('kaimo-child:查询条件',
name,
phone
)
, [])
...
无论修改其他任何属性,都不会触发子组件的副作用:
useCallback 的第二个参数同 useEffect 和 useMemo 的第二个参数,它是用于监听你需要监听的变量,如在数组内添加 name、phone、kw 等参数,当改变其中有个,都会触发子组件副作用的执行。
其他更多可以查看Hook API 索引
5、重新认识 useEffect
先来看一个例子:我们修改一下 App.jsx
import React, useEffect, useState from 以上是关于React Hooks 快速入门:从一个数据请求开始的主要内容,如果未能解决你的问题,请参考以下文章
入门React 17 + Vite + ECharts 实现疫情数据可视化「03 学习 React Hooks」
使用 JSON 数据输出数组数组而不是一个数组来反应 useState Hooks