React Hooks 的高级用法
useState 回调函数参数
用法: 能够给useState通过回调函数的形式给useState提供初始参数。
介绍:
useState 的参数可以有两种形式:
1. useState(普通的数据) => useState(0) / useState('abc')
2. useState(回调函数) => useState(() => { return 初始值 })
在 useState(回调函数)
中,回调函数的返回值就是状态的初始值, 该回调函数只会触发一次。
- 使用 回调函数 来为 useState 初始化默认值
- 回调函数的返回值就是状态的初始值!
- 注意:该回调函数只会触发一次
const [list, setList] = useState(() => {
return JSON.parse(localStorage.getItem('comments')) || comments
})
该使用哪种形式?
- 如果状态就是一个普通的数据(比如,字符串、数字、数组等)都可以直接使用
useState(普通的数据)
- 如果状态是经过一些计算得到的,此时,推荐使用
useState(回调函数)
第一种:
const [list, setList] = useState(
JSON.parse(localStorage.getItem('list')) || arr
)
可以转化为:
这种情况下,只要组件更新,此处的 localStorage 等操作就会重复执行
const initList = JSON.parse(localStorage.getItem('list')) || comments
const [list, setList] = useState(initList)
第二种:
这种方式,因为回调函数只会执行一次,所以,此处的 localStorage 等操作代码只会执行一次
const [list, setList] = useState(() => {
return JSON.parse(localStorage.getItem('comments')) || comments
})
所以在这种情况下,推荐使用第二种方式
useEffect清理副作用
用法: 能够在组件卸载的时候,清除注册的事件
介绍: useEffect 的返回值是可选的,可省略。也可以返回一个清理函数,用来执行事件解绑等清理操作。
清理函数的执行时机:
-
清理函数会在组件卸载时以及下一次副作用回调函数调用的时候执行,用于清除上一次的副作用。
-
如果依赖项为空数组,那么会在组件卸载时会执行。相当于组件的
componetWillUnmount
建议:一个 useEffect 只处理一个功能,有多个功能时,使用多次 useEffect
useEffect(() => {
const handleResize = () => {}
window.addEventListener('resize', handleResize)
// 这个返回的函数,会在该组件卸载时来执行
// 因此,可以去执行一些清理操作,比如,解绑 window 的事件、清理定时器 等
return () => window.removeEventListener('resize', handleResize)
})
获取当前鼠标位置
案例: 能够实现让图片跟随鼠标移动
介绍:
- 通过useState提供状态
- 通过useEffect给document注册鼠标移动事件
- 在组件销毁的时候清理副作用
import { useEffect, useState } from 'react'
import img from './1.gif'
export default function Move() {
const [position, setPosition] = useState({
x: 0,
y: 0
})
useEffect(() => {
const move = (e) => {
console.log('开始运动')
setPosition({
x: e.pageX,
y: e.pageY
})
}
document.addEventListener('mousemove', move)
console.log('触发注册事件')
return function () {
document.removeEventListener('mousemove', move)
}
}, [])
return (
<div>
<img
src={img}
style={{
position: 'absolute',
top: position.y + 1,
left: position.x + 1
}}
alt=""
/>
</div>
)
}
自定义hooks
目标: 能够使用自定义hooks实现状态的逻辑复用
内容: 除了使用内置的 Hooks 之外,还可以创建自己的 Hooks(自定义 Hooks)。
useXxx 使用场景: 将组件状态逻辑提取到可重用的函数(自定义 Hooks)中,实现状态逻辑复用。
内置 Hooks 为函数组件赋予了 class 组件的功能;在此之上,自定义 Hooks 针对不同组件实现不同状态逻辑复用。
- 自定义 Hooks 是一个函数,约定函数名称必须以 use 开头,React 就是通过函数名称是否以 use 开头来判断是不是 Hooks
- Hooks 只能在函数组件中或其他自定义 Hooks 中使用,否则,会报错!
- 自定义 Hooks 用来提取组件的状态逻辑,根据不同功能可以有不同的参数和返回值(就像使用普通函数一样)
// 使用hooks实现猫跟着鼠标移动
import { useEffect, useState } from 'react'
export default function useMouse() {
const [position, setPosition] = useState({
x: 0,
y: 0,
})
useEffect(() => {
const move = (e) => {
setPosition({
x: e.pageX,
y: e.pageY,
})
}
document.addEventListener('mousemove', move)
return () => {
document.removeEventListener('mousemove', move)
}
}, [])
return position
}
useEffect发送请求
目的: 能够在函数组件中通过useEffect发送ajax请求
内容: 在组件中,使用 useEffect Hook 发送请求获取数据(side effect):
- 注意:effect 只能是一个同步函数,不能使用 async
- 如果 effect 是 async 的,此时返回值是 Promise 对象。这样的话,就无法保证清理函数被立即调用
- 为了使用 async/await 语法,可以在 effect 内部创建 async 函数,并调用
错误演示:
// 不要给 effect 添加 async
useEffect(async () => {
const res = awiat xxx()
return ()=> {
}
}, [])
正确使用:
useEffect(() => {
async function fetchMyAPI() {
let url = 'http://something/' + productId
let config = {}
const response = await myFetch(url)
}
fetchMyAPI()
}, [productId])
useRef 操作DOM
目标: 能够使用useRef操作DOM
内容: 在 React 中进行 DOM 操作时,用来获取 DOM
作用:返回一个带有 current 属性的可变对象,通过该对象就可以进行 DOM 操作了。
const inputRef = useRef(null)
解释:
- 参数:在获取 DOM 时,一般都设置为 null
- 返回值:包含 current 属性的对象。
- 注意:只要在 React 中进行 DOM 操作,都可以通过 useRef Hook 来获取 DOM(比如,获取 DOM 的宽高等)。
- 注意:useRef不仅仅可以用于操作DOM,还可以操作组件
/*
1. 使用useRef能够创建一个ref对象, 有current属性 {current: null}
const xxRef = useRef(null)
2. 通过ref属性关联到某个DOM对象上 {current: DOM}
<div ref={xxRef}></div>
3. 可以通过 xxRef.current访问到对应的DOM
*/
const App = () => {
const inputRef = useRef(null)
const add = () => {
console.log(inputRef.current.value)
}
return (
<section className="todoapp">
<input type="text" placeholder="请输入内容" ref={inputRef} />{' '}
<button onClick={add}>添加</button>
</section>
)
}
export default App
useContext-context
目标:实现跨级组件通讯
内容
useContext
是一个 Hook,它可以在函数组件内部使用,以获取 Context 中的值。- 它通常与
Context
一起使用,以便在函数组件中方便地访问 Context 数据。
useContext 的用法
- 参数:
useContext
接受一个参数,即通过createContext
创建的 Context 对象。 - 返回值: 返回当前 Context 的
value
属性的值。如果没有对应的 Provider,则返回默认值(如果定义了的话)。
useContext 与 <Context.Consumer>
的区别
1、 <Context.Consumer>
:在 JSX 标签内获取 Context 数据。
2、 useContext
: 在 JavaScript 代码块中获取 Context 数据。
使用场景
- 跨组件共享数据: 当应用程序中需要将数据从较高级别的组件传递给较低级别的多个子组件时,为了避免层层传递props的繁琐,可以使用React的Context API。
Context 的作用
- 简化数据传递: Context允许你在组件树中无须通过中间组件显式地传递props,就可以将数据向下传递给任何层级的组件。
Context 对象包含的两个主要部分
-
Provider 组件
- 用于提供数据给其下的所有子组件。
- 通过
value
属性指定要传递的数据。
-
Consumer 组件
- 用于订阅并获取 Provider 提供的数据。
- 可以通过
render-props
模式在 JSX 中直接获取 Context 中的数据。
Consumer 组件行为
- 有 Provider 时: 如果一个 Consumer 组件位于一个 Provider 的后代组件中,则它将获取到该 Provider 的
value
属性的值。 - 无 Provider 时: 如果一个 Consumer 组件没有被任何 Provider 包裹,则它将获取到
createContext
创建时提供的defaultValue
。
示例
为了更好地理解如何使用 Context
和 useContext
,下面是一个简单的示例:
import React, { createContext, useContext } from 'react';
// 创建 Context
const ThemeContext = createContext();
// Provider 组件
function ThemeProvider({ children }) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
);
}
// 使用 useContext 的函数组件
function Navbar() {
const theme = useContext(ThemeContext);
return (
<div>
<h1>The current theme is: {theme}</h1>
</div>
);
}
// 主组件
function App() {
return (
<ThemeProvider>
<Navbar />
</ThemeProvider>
);
}
export default App;
Context 作用是实现跨组件传递数据,而不必在每个级别手动传递 props,简化组件之间的数据传递过程,那么使用Context 和不使用的区别在哪里呢 ?
不使用Context的情况(传统的Props Drilling)
-
父组件 (Parent Component)
- 包含一些状态或数据。
- 需要将这些状态或数据传递给较深的子组件。
-
中间组件 (Intermediate Component)
- 仅作为数据的传递者。
- 通常不会使用这些数据,但需要将它们继续向下传递给更深层的组件。
-
子组件 (Child Component)
- 实际上需要使用这些数据的状态或数据。
这种情况下,数据必须从父组件层层传递到最底层的子组件,即使某些中间组件并不关心这些数据。这被称为“props drilling”。
描述
Parent Component
|
|--- Props
|
+-- Intermediate Component
|
|--- Props
|
+-- Child Component
|
|--- Uses Props
使用Context的情况
-
父组件 (Parent Component)
- 包含一些状态或数据。
- 通过Context Provider将这些状态或数据提供给所有的子组件。
-
中间组件 (Intermediate Component)
- 不再需要传递这些数据。
- 可以完全忽略这些数据,因为它不再通过props传递。
-
子组件 (Child Component)
- 可以直接从Context中消费数据。
- 不再依赖于特定的父组件或者兄弟组件。
在这种情况下,数据通过Context Provider传递给整个组件树中的所有组件,不需要显式地在每一层都传递props。
描述
Parent Component
|
|--- Context Provider
|
+-- Intermediate Component
|
+-- Child Component
|
|--- useContext to access data
总结
- 不使用Context:数据需要通过多个层级的props传递,这可能导致代码冗余和难以维护。
- 使用Context:数据直接提供给整个组件树,无需在中间组件中传递props,从而简化了数据流。