React Hooks使用笔记
我使用React以前的Class写法以及对this的理解度,写起来实在是不习惯,我本人特别推崇函数式写法,而且我最近使用React官方文档的next.js模板,重新整理常用的 Hooks,进一步的学习使用React。
useState
useState
是一个 React Hook,可让您向组件添加状态变量。
const [state, setState] = useState(initialState);
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
// ...
传递更新函数
import { useState } from 'react';
export default function Counter() {
const [age, setAge] = useState(42);
function increment() {
setAge(a => a + 1);//注意setAge(age + 1)不能+3
}
return (
<>
<h1>Your age: {age}</h1>
<button onClick={() => {
increment();
increment();
increment();
}}>+3</button>
<button onClick={() => {
increment();
}}>+1</button>
</>
如果没有传递更新函数,所以“+3”按钮没有按预期工作。
useEffect
useEffect
可以让你在函数组件中执行副作用操作。可让您将组件与外部系统同步。
副作用是指一段和当前执行结果无关的代码,常用的副作用操作如数据获取、设置订阅、手动更改 React 组件中的 DOM。 useEffect 可以接收两个参数,第一个参数是要执行的函数 callback,第二个参数是可选的依赖项数组 dependencies。
useEffect(callback, dependencies)
import {useEffect} from 'react';
import {createConnection} from './chat.js';
function ChatRoom({roomId}) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
监听全局浏览器事件
import { useState, useEffect } from 'react';
export default function App() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
function handleMove(e) {
setPosition({ x: e.clientX, y: e.clientY });
}
window.addEventListener('pointermove', handleMove);
return () => {
window.removeEventListener('pointermove', handleMove);
};
}, []);
return (
<div style={{
position: 'absolute',
backgroundColor: 'pink',
borderRadius: '50%',
opacity: 0.6,
transform: `translate(${position.x}px, ${position.y}px)`,
pointerEvents: 'none',
left: -20,
top: -20,
width: 40,
height: 40,
}} />
);
}
用途
useEffect 提供了四种执行副作用的时机:(可同时存在)
- 每次 render 后执行:不提供第二个依赖项参数。比如 useEffect(() => {});
- 仅第一次 render 后执行:提供一个空数组作为依赖项。比如 useEffect(() => {}, []);
- 第一次以及依赖项发生变化后执行:提供依赖项数组。比如 useEffect(() => {}, [deps]);
- 组件 unmount 后执行:返回一个回调函数。比如 useEffect() => { return () => {} }, [])。
useCallback
useCallback 定义的回调函数只会在依赖项改变时重新声明这个回调函数,这样就保证了组件不会创建重复的回调函数。而接收这个回调函数作为属性的组件,也不会频繁地需要重新渲染。
import { useCallback } from 'react';
export default function ProductPage({ productId, referrer, theme }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', { referrer, orderDetails});
}, [productId, referrer]);
useMemo
useMemo
是一个 React Hook,可让您在重新渲染之间缓存计算结果。
useMemo 定义的创建函数只会在某个依赖项改变时才重新计算,有助于每次渲染时不会重复的高开销的计算,而接收这个计算值作为属性的组件,也不会频繁地需要重新渲染。
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
你会经常看到useMemo
旁边useCallback
。当您尝试优化子组件时,它们都很有用。他们让你记住(或者,换句话说,缓存)你传递的东西:
import {useMemo, useCallback} from 'react';
function ProductPage({productId, referrer}) {
const product = useData('/product/' + productId);
const requirements = useMemo(() => { // Calls your function and caches its result
return computeRequirements(product);
}, [product]);
const handleSubmit = useCallback((orderDetails) => { // Caches your function itself
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
return (
<div className={theme}>
<ShippingForm requirements={requirements} onSubmit={handleSubmit}/>
</div>
);
}
useRef
useRef
用于返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)
useRef
创建的 ref 对象就是一个普通的 JavaScript 对象,而 useRef()
和自建一个 {current: ...}
对象的唯一区别是,useRef
会在每次渲染时返回同一个 ref 对象
import { useRef } from 'react';
function MyComponent() {
const intervalRef = useRef(0);
const inputRef = useRef(null);
// ...
绑定 DOM 元素
使用 useRef
创建的 ref 对象可以作为访问 DOM 的方式,将 ref 对象以 <div ref={myRef} />
形式传入组件,React 会在组件创建完成后会将 ref 对象的 .current
属性设置为相应的 DOM 节点
聚焦文字输入框
import React, { useRef } from 'react'
export default function FocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
绑定可变值
useRef
创建的 ref 对象同时可以用于绑定任何可变值,通过手动给该对象的.current
属性设置对应的值即可
秒表
import { useState, useRef } from 'react';
export default function Stopwatch() {
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
const intervalRef = useRef(null);
function handleStart() {
setStartTime(Date.now());
setNow(Date.now());
clearInterval(intervalRef.current);
intervalRef.current = setInterval(() => {
setNow(Date.now());
}, 10);
}
function handleStop() {
clearInterval(intervalRef.current);
}
let secondsPassed = 0;
if (startTime != null && now != null) {
secondsPassed = (now - startTime) / 1000;
}
return (
<>
<h1>Time passed: {secondsPassed.toFixed(3)}</h1>
<button onClick={handleStart}>
Start
</button>
<button onClick={handleStop}>
Stop
</button>
</>
);
}
useContext
在组件的顶层调用useContext
来读取和订阅上下文。
import { useContext } from 'react';
function MyComponent() {
const theme = useContext(ThemeContext);
// ...
通过上下文 来更新数据
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
export default function MyApp() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Form />
<label>
<input
type="checkbox"
checked={theme === 'dark'}
onChange={(e) => {
setTheme(e.target.checked ? 'dark' : 'light')
}}
/>
Use dark mode
</label>
</ThemeContext.Provider>
)
}
function Form({ children }) {
return (
<Panel title="Welcome">
<Button>Sign up</Button>
<Button>Log in</Button>
</Panel>
);
}
function Panel({ title, children }) {
const theme = useContext(ThemeContext);
const className = 'panel-' + theme;
return (
<section className={className}>
<h1>{title}</h1>
{children}
</section>
)
}
function Button({ children }) {
const theme = useContext(ThemeContext);
const className = 'button-' + theme;
return (
<button className={className}>
{children}
</button>
);
}
useReducer
useReducer
是一个 React Hook,可让您向组件添加reducer 。
const [state, dispatch] = useReducer(reducer, initialArg, init?)
UseState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法
import { useReducer } from 'react';
function reducer(state, action) {
switch (action.type) {
case 'incremented_age': {
return {
name: state.name,
age: state.age + 1
};
}
case 'changed_name': {
return {
name: action.nextName,
age: state.age
};
}
}
throw Error('Unknown action: ' + action.type);
}
const initialState = { name: 'Taylor', age: 42 };
export default function Form() {
const [state, dispatch] = useReducer(reducer, initialState);
function handleButtonClick() {
dispatch({ type: 'incremented_age' });
}
function handleInputChange(e) {
dispatch({
type: 'changed_name',
nextName: e.target.value
});
}
return (
<>
<input
value={state.name}
onChange={handleInputChange}
/>
<button onClick={handleButtonClick}>
Increment age
</button>
<p>Hello, {state.name}. You are {state.age}.</p>
</>
);
}
自定义Hooks
1. 如何创建自定义 Hooks?
自定义 Hooks 就是函数,它有 2 个特征区分于普通函数:
- 名称以 “use” 开头;
- 函数内部调用其他的 Hook。
示例如下:
import { useState, useCallback } from 'react'
function useCounter() {
// 定义 count 这个 state 用于保存当前数值
const [count, setCount] = useState(0)
// 实现加 1 的操作
const increment = useCallback(() => setCount(count + 1), [count])
// 实现减 1 的操作
const decrement = useCallback(() => setCount(count - 1), [count])
// 重置计数器
const reset = useCallback(() => setCount(0), [])
// 将业务逻辑的操作 export 出去供调用者使用
return { count, increment, decrement, reset }
}
// 组件1
function MyComponent1() {
const { count, increment, decrement, reset } = useCounter()
}
// 组件2
function MyComponent2() {
const { count, increment, decrement, reset } = useCounter()
}
以上代码通过自定义 Hooks useCounter,轻松的在 MyComponent1 组件和 MyComponent2 组件之间复用业务逻辑。
2. 自定义 Hooks 库 - react-use
React 官方提供了 react-use 库,其中封装了大量可直接使用的自定义 Hooks,帮助我们简化组件内部逻辑,提高代码可读性、可维护性。
其中我们常用的自定义 Hooks 有:
- useLocation 和 useSearchParam:跟踪页面导航栏位置状态;
- useScroll:跟踪 HTML 元素的滚动位置;
- useScrolling:跟踪 HTML 元素是否正在滚动;
- useAsync, useAsyncFn, and useAsyncRetry:解析一个 async 函数;
- useTitle:设置页面的标题。
可至 react-use 官网学习使用。 React 官方提供了 react-use 库,其中封装了大量可直接使用的自定义 Hooks,帮助我们简化组件内部逻辑,提高代码可读性、可维护性。