useState - 添加/修改state
- useState 是最常用的 Hook,用于在函数组件中添加 state。
const [count, setCount] = useState(0);
- 用法:
jsx
import React, { useState } from 'react';
function Counter() {
// 声明一个叫做 "count" 的 state 变量,初始值为 0 5
const [count, setCount] = useState ( 0 );
return (<div><p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>);
}
useReducer
- 出自 Redux 作者,可以理解为 React 内部基于 Hooks 实现的类似 Redux 状态管理逻辑。
- useReducer 是 useState 的替代方案,适用于 state 逻辑较复杂且包含多个子值,或下一个 state 依赖于之前的 state 的情况。
const [state, dispatch] = useReducer(reducer, initialState)
💡 其实,useState 是 useReducer 的语法糖,在实现上,useState 共用 useReducer 的底层实现。
- 用法:
jsx
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>);
}
useEffect - 监听依赖执行副作用 - 异步
useEffect(() => {/*副作用*/ return ()=>{/*清除副作用*/}}, [/*依赖项数组*/]);
- useEffect 用于在函数组件中执行副作用操作,例如数据获取、订阅或手动更改 DOM。
useEffect
还可以返回一个函数 ,这个返回的函数会在组件卸载时执行,用于清理副作用。- 依赖项为 [] 时,副作用func只在组件挂载时执行
useEffect(() => {/***挂载时**执行*/,return()=>{/***卸载时**执行*/}}, **[]** );
- 依赖项不为空时,挂载时执行,依赖项中任意值更新(发生变化)时触发函数执行
useEffect(() => { () => {/***挂载时、count改变时**执行*/}, [count]);
- 用法:
jsx
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似于 componentDidMount 和 componentDidUpdate
useEffect(() => {
// 使用浏览器 API 更新文档标题
document.title = `You clicked ${count} times`;
// 清除副作用
return () => {
document.title = 'React App';
};
}, [count]); // 仅在 count 更改时重新运行
return (<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>);
}
useContext - 获取上下文-组件树之间共享数据
- useContext 接受一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。用于在函数组件中访问 React 的上下文(Context)。
- React Context 提供了一种在组件树中共享数据的方式,使得数据能够在多个组件之间传递,而无需通过层层传递 props(属性)
- 创建 -
const MyContext = React.createContext('initState')
- 提供者 -
<MyContext.Provider value={{name:'zh'}}>...
- 消费者 -
const state = useContext(MyContext)
- 用法:
jsx
import React, { useReducer, useContext } from 'react';
const ThemeContext = React.createContext('light');// 创建上下文
// 可修改值传送,一般 和 useReducer 或 useState 配合
const themeReducer = (state, action) => {// 创建 reducer
switch (action.type) {
case 'TOGGLE_THEME':
return {...state, theme: state.theme === 'light'? 'dark' : 'light' };
default:
return state;
}
};
const initialState = { theme: 'light' };// 设置初始值
/**
通常是在根组件或者接近根组件的位置,
提供者:上下文 ThemeContext.Provider value="传递值"
value直接传递单值 或 传递obj数据,如:value={{name:'name',age:age}}
**/
function App() {
const [state, dispatch] = useReducer(themeReducer, initialState);// 可变传送
return (
<ThemeContext.Provider value={{fixedThem:'dark',state,dispatch}}>
<ThemeButton />
</ThemeContext.Provider>
);
}
function ThemeButton() {
const {fixedThem,state,dispatch} = useContext(ThemeContext);// 使用上下文
// fixedThem 固定值 dark
// state.them可编辑值,可使用dispatch修改
const handleClick = () => {dispatch({ type: 'TOGGLE_THEME' });};
return <button style={{ background: state.theme }} onClick={handleClick}>Theme Button</button>;
}
useMemo/useCallback - 根据依赖缓存 计算结果/函数
- useMemo 和 useCallback 用于性能优化,分别用于记忆化计算结果 和记忆化函数。
- 用法:
jsx
// useMemo(func,[依赖数据]) 缓存计算结果,依赖变化时重新计算
import React, { useMemo } from 'react';
function ExpensiveCalculationComponent({ number }) {
const compute = (n) => {
console.log('Expensive computation');
return n * 2;
};
const result = useMemo( () => compute (number) , [number]);
return <div>Result: {result}</div>;
}
// useCallback(func,[依赖数据]) 缓存方法,依赖变化时重新创建方法。
import React, { useState, useCallback } from 'react';
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback( () => {
setCount (count + 1 );
} , [count]);
return (<div>
<p>Count: {count}</p>
<Button onClick={handleClick}>Increment</Button>
</div>);
}
useRef
- useRef 返回一个可变的 ref 对象,该对象的 .current 属性被初始化为传入的参数(initialValue)。
- useRef 常用于访问 DOM 元素 直接或存储某个可变值,该值在组件的整个生命周期内保持不变。
- 用法:
jsx
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// 使用原生 DOM API 聚焦文本输入
inputEl.current.focus();
};
return (<div><input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</div>);
}
useImperativeHandle - 暴露属性和方法
- 自定义 ref,暴露给父组件。
- 用法:
jsx
import React, { useRef, forwardRef, useImperativeHandle } from 'react';
// forwardRef是 React 中的一个高级特性,它用于将父组件创建的ref对象转发给子组件。
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
// 接收 ref 关联父组件创建的 ref
useImperativeHandle(ref, () => ({
focus: () => { inputRef.current.focus(); }
}));
return <input ref={inputRef} />;
});
function App() {
const inputRef = useRef();
return (<div>
<FancyInput ref={inputRef} />
<button onClick={() => inputRef.current.focus()}>Focus the input</button>
</div>);
}
- 在这个例子中,子组件
FancyInput
通过forwardRef
接收父组件-App
创建的ref
并将其绑定到input
元素上。在App
中,通过useRef
创建inputRef
,将其传递给FancyInput
,然后在onClick
方法中可以通过inputRef.current.focus()
让输入框获取焦点。 - 通常情况下,
useImperativeHandle
和forwardRef
一起使用,因为useImperativeHandle
需要一个ref
来进行操作,而这个ref
通常是由forwardRef
传递进来的。
useId
- useId 是一个用于生成唯一 ID 的 Hook,通常用于无障碍特性(例如绑定 label 和 input)。
- 用法:
jsx
import React, { useId } from 'react';
function Form() {
const id = useId();
return (<div>
<label htmlFor={id}>Name</label>
<input id={id} type="text" />
</div>);
}
useLayoutEffect - 同步执行于 DOM 更新后
useLayoutEffect 类似于 useEffect,但在所有 DOM 变更之后同步调用。
- 用法:
jsx
import React, { useLayoutEffect, useRef } from 'react';
function LayoutEffectExample() {
const ref = useRef(null);
useLayoutEffect(() => {
console.log('useLayoutEffect');
ref.current.style.backgroundColor = 'yellow';
});
return <div ref={ref}>Layout Effect</div>;
}
- 同步执行于 DOM 更新后:
useLayoutEffect
的执行时机是在 DOM 更新之后,浏览器进行绘制(paint)之前。这意味着它会在组件的 DOM 更新完成后立即执行 ,并且是同步阻塞的。如果useLayoutEffect
内部的操作耗时较长,会阻塞浏览器的绘制,导致页面出现短暂的 "卡顿" 现象。 - 示例场景 - 计算元素位置和尺寸:假设要根据一个元素的新位置来进行一些布局调整。如果在
useLayoutEffect
中获取元素的位置并进行布局计算,能够保证在用户看到更新后的元素位置之前完成这些计算,从而避免布局的闪烁。
jsx
import React, { useState, useLayoutEffect } from 'react';
function LayoutComponent() {
const [width, setWidth] = useState(100);
useLayoutEffect(() => {
const element = document.getElementById('myElement');
if (element) {
const newWidth = element.offsetWidth;
console.log('元素宽度:', newWidth);
}
}, [width]);
return (
<div>
<div id="myElement" style={{ width: `${width}px` }}>
这是一个元素
</div>
<button onClick={() => setWidth(width + 10)}>增加宽度</button>
</div>
);
}
- 在这个例子中,每次点击按钮增加元素宽度后,
useLayoutEffect
会在 DOM 更新后同步执行。它获取更新后的元素宽度并打印出来,这个操作是在浏览器绘制之前完成的。
### useTransition - **非紧急更新的,返回 isPending、startTransitionisPen**
- useTransition 是一个用于处理 UI 过渡的 Hook,允许你将某些更新标记为过渡,从而避免阻塞界面。
- 用法:
jsx
import React, { useState, useTransition } from 'react';
function App() {
const [isPending, startTransition] = useTransition();
const [count, setCount] = useState(0);
const handleClick = () => {
startTransition(() => {
setCount(c => c + 1);
});
};
return (<div><button onClick={handleClick}>Increment</button>
{isPending ? 'Loading...' : <p>Count: {count}</p>}
</div>);
}
- 非紧急更新的标记与调度 :
useTransition
用于标记更新为非紧急更新,它本身并不会直接执行代码,而是返回一个用于启动过渡更新的函数和一个表示过渡是否正在进行的布尔值。当使用useTransition
返回的函数来触发更新时,React 会将这个更新视为低优先级任务。 - 与正常更新的关系:在 React 的并发模式下,正常的高优先级更新(如用户输入等紧急更新)会优先处理。而
useTransition
标记的更新会在浏览器空闲时间或者在高优先级更新处理完成后才会执行。例如,在一个搜索组件中,用户输入搜索词是高优先级更新,而根据搜索词更新搜索结果列表可以标记为非紧急更新,通过useTransition
来调度。 - 示例场景 - 搜索框和搜索结果更新:
jsx
import React, { useState, useTransition } from 'react';
function SearchComponent() {
const [inputValue, setInputValue] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [isPending, startTransition] = useTransition();
const handleInputChange = (e) => {
setInputValue(e.target.value);
startTransition(() => {
// 模拟获取搜索结果
const results = getSearchResults(e.target.value);
setSearchResults(results);
});
};
return (
<div>
<input type="text" value={inputValue} onChange={handleInputChange} />
{isPending && <div>正在更新搜索结果...</div>}
<ul>
{searchResults.map((result, index) => (
<li key={index}>{result}</li>
))}
</ul>
</div>
);
}
- 在这个例子中,用户输入搜索词时,输入值的更新是高优先级的。而更新搜索结果的操作被
startTransition
标记为非紧急更新。isPending
可以用于显示一个加载指示器,当标记的更新正在进行时,会显示这个加载指示器,并且更新会在合适的时候完成,不会阻塞用户输入。
### useDeferredValue - 延迟更新 用于一个值
- useDeferredValue 是一个用于延迟更新的 Hook,它会返回一个新的值,这个值会在某个时间点更新到最新的值,用于优化性能。
- 用法:
jsx
import React, { useState, useDeferredValue } from 'react';
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
const handleChange = (e) => {
setText(e.target.value);
};
return (<div><input type="text" value={text} onChange={handleChange} />
<p>{deferredText}</p>
</div>);
}
- 延迟更新的触发:
useDeferredValue
用于创建一个延迟更新的值。它接受一个值作为参数,并返回一个新的延迟值。当原始值发生变化时,React 会先处理其他高优先级的更新,然后在适当的时候更新这个延迟值。 - 与
useTransition
的相似性和区别:和useTransition
类似,useDeferredValue
也是用于处理非紧急更新。不同的是,useDeferredValue
是直接应用于一个值,自动延迟其更新。而useTransition
需要手动标记更新的过程为非紧急更新。 - 示例场景 - 输入框和延迟显示的内容更新:
jsx
import React, { useState, useDeferredValue } from 'react';
function DeferredComponent() {
const [inputValue, setInputValue] = useState('');
const deferredValue = useDeferredValue(inputValue);
const handleInputChange = (e) => {
setInputValue(e.target.value);
};
return (
<div>
<input type="text" value={inputValue} onChange={handleInputChange} />
<div>延迟显示的值: {deferredValue}</div>
</div>
);
}
- 在这个例子中,当用户在输入框中输入时,
inputValue
会立即更新,但是deferredValue
会延迟更新。这意味着如果有其他高优先级的更新(比如与输入框本身相关的更新,如输入框的样式调整等),会先处理这些更新,然后再更新deferredValue
,从而避免对用户输入等紧急操作的干扰。
### useSyncExternalStore - 外部存储函数,当前数据源快照,SSR数据源快照
- redux源码中使用useSyncEternalStore
- useSyncExternalStore 是一个用于订阅外部存储的 Hook,确保外部存储的值与 React 的渲染保持同步。
- 用法:
jsx
import React, { useSyncExternalStore } from 'react';
function useWindowWidth() {
return useSyncExternalStore(
(cb) => {
window.addEventListener('resize', cb);
return () => window.removeEventListener('resize', cb);
},
() => window.innerWidth,
() => window.innerWidth
);
}
function WindowWidth() {
const width = useWindowWidth();
return <div>Window width: {width}</div>;
}
-
基本概念与用途
-
定义 :
useSyncExternalStore
是React提供的一个Hook,用于订阅外部数据源,并在数据源发生变化时强制组件重新渲染。它主要用于在React组件和外部状态管理系统之间建立连接,确保组件能够获取最新的外部状态。 -
目的 :在实际开发中,经常会遇到需要使用外部状态(如Redux存储、浏览器本地存储等)的情况。
useSyncExternalStore
使得React组件能够高效、安全地与这些外部存储进行交互,并且能够根据存储内容的变化及时更新组件的显示内容。
-
-
参数与工作原理
-
语法结构 :
const snapshot = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
。 -
subscribe
函数 :这是一个用于订阅外部数据源变化的函数。当外部数据源发生变化时,这个函数会被调用,通知React组件需要重新渲染。例如,在订阅Redux存储时,subscribe
函数可能会调用store.subscribe
来注册一个监听器。这个函数接收一个callback
参数,当外部数据源变化时,应该调用这个callback
来触发React组件的重新渲染。 -
getSnapshot
函数 :用于获取外部数据源的当前"快照"(snapshot)。这个快照是一个数据表示,用于让React组件知道外部数据源的当前状态。例如,在使用Redux存储时,getSnapshot
函数可能会返回store.getState()
,以获取Redux存储的当前状态。 -
getServerSnapshot
函数(可选) :用于在服务器端渲染(SSR)时获取初始快照。如果应用支持SSR,这个函数可以提供服务器端的初始状态快照,确保在客户端和服务器端渲染的一致性。
-
-
使用场景与示例
-
与Redux集成(简化示例)
- 假设已经有一个Redux存储(
store
),想要在React组件中获取和响应Redux状态的变化。
jsximport React from 'react'; import { useSyncExternalStore } from 'react'; function ReduxConnectedComponent() { const state = useSyncExternalStore( store => store.subscribe(callback => setMyComponentNeedsUpdate(callback)), store => store.getState() ); // 根据state进行组件渲染 return <div>{state.someData}</div>; }
- 在这个例子中,
subscribe
函数通过store.subscribe
注册了一个监听器,当Redux存储发生变化时,会调用setMyComponentNeedsUpdate
(这个函数可以是自定义的,用于触发组件重新渲染)。getSnapshot
函数通过store.getState()
获取Redux存储的当前状态,这样组件就可以根据Redux状态进行渲染,并且在状态变化时自动更新。
- 假设已经有一个Redux存储(
-
与浏览器本地存储集成
- 当需要在React组件中读取和响应本地存储(
localStorage
)的变化时,useSyncExternalStore
也很有用。
jsximport React from 'react'; import { useSyncExternalStore } from 'react'; function LocalStorageComponent() { const getLocalStorageSnapshot = () => JSON.parse(localStorage.getItem('myData')); const subscribeToLocalStorage = (callback) => { window.addEventListener('storage', callback); return () => window.removeEventListener('storage', callback); }; const localStorageData = useSyncExternalStore( subscribeToLocalStorage, getLocalStorageSnapshot ); return <div>{localStorageData.someProperty}</div>; }
- 在这里,
getLocalStorageSnapshot
函数用于获取本地存储中数据的当前快照。subscribeToLocalStorage
函数用于订阅本地存储的变化,它通过window.addEventListener('storage', callback)
来监听storage
事件,当本地存储变化时,会触发callback
,通知组件重新渲染。当组件卸载时,返回的函数用于移除事件监听器,避免内存泄漏。
- 当需要在React组件中读取和响应本地存储(
-
-
优势与注意事项
-
优势
- 性能优化 :
useSyncExternalStore
通过只在外部数据源真正发生变化时重新渲染组件,避免了不必要的渲染。这有助于提高应用的性能,特别是在处理大型数据集或者频繁变化的外部数据源时。 - 数据一致性:确保了React组件与外部数据源之间的数据一致性。无论外部数据源是如何更新的,组件都能够获取最新的状态并正确地更新显示内容。
- 性能优化 :
-
注意事项
- 订阅和取消订阅的平衡 :在
subscribe
函数中,要注意正确地处理订阅和取消订阅的操作。如果没有正确地取消订阅(例如,在组件卸载时没有移除事件监听器),可能会导致内存泄漏或者不必要的更新。 - 外部数据源的兼容性 :要确保
getSnapshot
函数返回的数据格式与组件的渲染逻辑相匹配。不同的外部数据源可能有不同的数据结构和更新方式,需要仔细考虑如何获取和处理快照数据。同时,对于不支持的外部数据源类型,可能需要进行适当的包装或者转换才能使用useSyncExternalStore
。
- 订阅和取消订阅的平衡 :在
-