React hooks 介绍和使用
useId
useId 是 React 18
新增的 hooks
, 可以生成独一无二的 id。
当我们的组件 html 里面包含 id 时,多个组件同时渲染,会表现为多个元素拥有同一个 id,导致语法上不合理。虽然我们可以用随机数或者时间戳的方式生成 id, 但是在函数组件中每次更新都会更改 id,所以官方出了 useId
.
官方的文档也说明了 useId
出现的作用
- 当第三方库需要独一无二的 id 时
- 处理服务端渲染后,和客户端 hydrate 不匹配的问题
具体使用:
function Checkbox() { const id = useId(); return ( <> <label htmlFor={id}>Do you like React?</label> <input id={id} type="checkbox" name="react"/> </> ); };
这里比较有意思的是,生成的 id 并不是合法的 selector id, 也就是说无法通过 DOM
匹配都该元素,毕竟 React
推荐使用 ref
来操作操作 DOM
useState
useState
可以传入一个初始值,然后返回一个值和一个改变值方法的数组
const [count, setCount] = useState(0)
使用 setMethod
会触发组件进行更新,组件函数会重新执行。
这里需要注意:
- 如果传入的值和之前的值相等,则不会触发重新更新
- 一个逻辑里多次触发 setState, 只会触发最后一个
- 执行 setState 函数后,当前 state 的值依旧是旧的值,各种闭包使用的都是旧值。这个符合 React 的预期,每次组件更新都会重新执行函数,产生新的值
- 引用类型的值,直接更改值组件不会更新。将当前值更改然后传入 setState 函数,也不会触发更新。因为引用类型地址一样,比较相同无法触发更新。所以对于引用类型需要将值进行拷贝后再进传入
- 不能在条件语句中使用
useEffect
我们在组件中请求数据,更改 DOM 或者其他的操作逻辑,都是 side effect . 这些 effect 会影响到其他组件,而且在组件渲染时无法正常完成。
useEffect -- 副作用的 hook。每当我们更改数据,或者组件状态变化时想要进行特定的逻辑,都可以使用 useEffect.
useEffect 拥有两个参数,一个是副作用的回调,一个是依赖项。每当依赖项更改时,副作用回调都会重新执行
useEffect 的依赖传入不同的值会有不同的作用。相当于 Class 组件的 componentDidMount, componentDidUpdate, componentWillUnmount.
- 依赖传空数组,相当于 componentDidMount,只会执行一次
- 依赖不传,相当于 componentDidUpdate,每次组件更新都会执行
- 回调函数返回一个函数,且无依赖项 相当于 componentWillUnmount
注意点:
- 当 useEffect 内部使用到其他值时记得将其添加到依赖,避免闭包陷阱
- 绑定事件,订阅,定时器等操作,记得在返回函数进行取消
当我们开发时有安装 lint 时,如果 useEffect 传空数组会进行格式提醒的。但是业务场景只需要组件挂载时才执行,而不是每次更新, 这样很容易导致死循环。这个时候我们需要结合其他 hook 一起使用。
useContext
使用 useContext 可以让我们使用 createContext 中定义 Context。
const themes = { light: { foreground: "#000000", background: "#eeeeee" }, dark: { foreground: "#ffffff", background: "#222222" } }; const ThemeContext = React.createContext(themes.light); function App() { return ( <ThemeContext.Provider value={themes.dark}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } function ThemedButton() { const theme = useContext(ThemeContext); return ( <button style={{ background: theme.background, color: theme.foreground }}> I am styled by theme context! </button> ); }
useContext(MyContext) 会在当前组件的祖先组件中,找到离自己最近的 MyContext.Provider, 获取其中的值,没有则使用默认值。
每当组件使用的 context 的值变化时,组件就会重新渲染。
相比于 Class 组件,useContext 可以不需要使用 MyContext.consumer 来获取值。
在简单的业务场景,可以通过这个 hook 进行状态管理,推荐使用 unstated-next 来增强 context 的能力
useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
传入 reducer 和初始化的值,第三个参数式很少使用,用来返回默认值,可以根据使用场景来处理默认值
返回状态和 dispacth 函数,可以通过 dispatch reducer 中定义操作来更改状态
function init(initialCount) { return {count: initialCount}; } function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; case 'reset': return init(action.payload); default: throw new Error(); } } function Counter({initialCount}) { const [state, dispatch] = useReducer(reducer, initialCount, init); return ( <> Count: {state.count} <button onClick={() => dispatch({type: 'reset', payload: initialCount})}> Reset </button> <button onClick={() => dispatch({type: 'decrement'})}>-</button> <button onClick={() => dispatch({type: 'increment'})}>+</button> </> ); }
需要注意的是, 和 reducx 使用一样,更改值时不能直接对引用进行更改,应该返回一个新的状态。
useReducer 可以和 useContext 一起处理比较复杂的业务状态,进行状态管理。
useMemo 和 useCallback
这两个 hook 都是返回一个缓存的值,当依赖值更新时才会进行更新.
useMemo 返回的值为传入回调的返回值, useCallback 返回值为传入的回调
useCallback(fn, [...deps])
等同于 useMemo(() => fn,[...deps])
在 useEffect 中我们提到 useEffect 使用外部定义函数时,空数组会被 linter 警告。这种情况时,我们可以使用 useCallback 定义函数,提供给 useEffect 使用
useMemo 可以理解为 Vue 中的 computed.
useRef
const refContainer = useRef(initialValue);
useRef 是一个特殊的 hook, 它返回一个包含 current 属性的对象。
useRef 的值初始化后会永远存在,组件刷新不会更改当前的值,常用于访问 dom
const divEl = useRef(null); ... <div ref={divEl}></div>
同时 ref 的返回值更改后不会触发 组件的刷新,我们可以定义一些不需要组件刷新的数据。
useImperativeHandle
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle 用于子组件暴露方法给父组件使用, 同时可以定义依赖项,当依赖项改变后,handle 中暴露的属性也会变化。
useImperativeHandle 需要结合 forwardRef 一起使用
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
useImperativeHandle 主要是为了避免将 ref 透传,即子组件使用父组件定义的 ref 绑定 dom.
useLayoutEffect
useLayoutEffect 和 useEffect 的确有相似之处,但是区别也比较大。
区别:
- useLayoutEffect 是同步执行, useEffect 是异步执行的
- useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前。
一些 dom 渲染的操作可以放在 useLayoutEffect 中执行,避免异步执行造成界面闪烁。
useLayoutEffect 会阻塞页面渲染,所以尽量优先使用 useEffect
在 SSR 模式 useLayoutEffect 可能会导致实际渲染的内容和服务端首屏渲染的内容不一致,所以尽量避免在 SSR 模式使用该 hook
useDebug 和 useDeferredValue
useDebug 用于在 React Devtools 将所在 hook 作为 label 进行展示
useDeferredValue 用于延迟值的获取,降低优先级
const deferredValue = useDeferredValue(value);
例如我们在使用筛选时,输入变化时会更新列表。如果列表的数据量较多时渲染可能就会阻塞交互,导致输入卡顿。
当我们使用 useDeferredValue 将值进行包裹后,列表渲染的值会有延迟,保证不会阻塞输入的交互。这个和防抖有点像,但是这个 hook 更多是在当前组件空闲时进行渲染,在特定的场景还是比较有用的。
useTransition
const [isPending, startTransition] = useTransition();
useTransition 提供一个状态和一个 handler. 我们可以在 handle 中执行数据的变化。
当数据 setState 时,isPending 会变为 true, 数据异步修改完成后,isPending 会变为 false。
可以使用 useTransition 处理一些加载的逻辑
function App() { const [isPending, startTransition] = useTransition(); const [count, setCount] = useState(0); function handleClick() { startTransition(() => { setCount(c => c + 1); }) } return ( <div> {isPending && <Spinner />} <button onClick={handleClick}>{count}</button> </div> ); }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论