react中hooks使用
总结
1、react 中的hook是16.8引入的函数,不需要编写组件类。可以理解成是函数式编程。
2、使用的前提:只能在react函数组件中使用或者Hook 中调用 hook,不能在javascript中使用。
3、useCallback 的功能可以直接用 useMemo。
4、useEffect 的副作用,会导致异步问题,需要注意,文中有提供场景。
5、useEffect 和 useEffectLayout 调用的时机不同。
6、useLayoutEffect 和原来的 ComponentDidMount 和 ComponentDidUpdate 一致,是 react 完成 DOM 更新后马上同步调用代码,会阻塞页面渲染。
7、useEffect 是整个页面渲染完成才会调用代码。官方推荐是useEffect 。
一、常用的方法
1、useState
1.1 定义
useState 是保存组件状态,修改的值触发组件重新渲染。
使用场景:常用的数据请求前先loading,然后请求成功后loading关闭。简单理解就是存储状态的。
1.2 知识点
1、在类组件中我们是用this.state 来保存组件的状态。
2、使用 useState 参数后,有一个默认状态和改变状态。useState 是不会帮你处理状态的,修改状态需要用setState 覆盖式更新状态。
3、基础使用如下:
cons [demo, setDemo] = useState(1)// demo设置初始值,在某些条件或者 场景下要改变demo的值
setDemo(22)
2、useRef
2.1 定义
用于访问 DOM 节点或在渲染间保持一个可变的值。
useRef返回的是一个 ref 对象,.current 属性是初始化传入的参数,使用这个更新不会引起当前组件或者子组件的更新。
1、但使用 React.createRef ,每次重新渲染组件都会重新创建 ref。
2、如果使用useState定义值的时候有异步问题,就可以用useRef。
const demo = useRef()// 如果要给demo中添加值
demo.current={name: 'karla',year: 18
}
3、useEffect
3.1 定义
用于数据获取、订阅、请求数据
1、useEffect 不能瞎更新,如果监听某个值,这个值一直变化,就会进入死循环,临时状态可以用useRef 来存储,避免不必要的更新。
2、生命周期的写法,一些重复的功能都得在componentDidMount 和 componentDidUpdate 重复写,useEffect 不需要 。
3、useEffect 对于函数的保存状态,异步的问题解决不了。
4、在实际使用时如果想避免页面抖动(在useEffect
里修改DOM很有可能出现)的话,可以把需要操作DOM的代码放在useLayoutEffect
里。关于使用useEffect
导致页面抖动。
不过useLayoutEffect
在服务端渲染时会出现一个warning,要消除的话得用useEffect
代替或者推迟渲染时机。
5、基础使用如下:
useEffect(() => {// 副作用代码return () => {// 清理操作};
}, [dependency]); // 依赖项数组,用于指定何时重新运行副作用
3.2 知识点
3.2.1 异步解决方法
在处理函数和异步的时候可能遇到的问题如下
1、函数引用的变化:每次渲染都会创建实例,导致无必要的 useEffect 执行
2、异步状态更新:异步时导致闭包
3、过时闭包:effect 回调的时候 获取的是创建时的状态,非最新状态
解决文案有以下:
(1)稳定函数引用
// 使用 useCallback 避免函数引用变化
const fetchData = useCallback(async () => {const response = await fetch('/api/data');setData(await response.json());
}, []); // 依赖项为空表示函数不会变化useEffect(() => {fetchData();
}, [fetchData]);
(2)处理异步操作
useEffect(() => {let isMounted = true; // 清理标记const fetchData = async () => {try {const response = await fetch('/api/data');if (isMounted) {setData(await response.json());}} catch (error) {if (isMounted) {setError(error);}}};fetchData();return () => {isMounted = false; // 清理时标记为未挂载};
}, []); // 空依赖表示只运行一次
(3)获取最新状态
// 使用 ref 保存最新状态
const [state, setState] = useState(initialState);
const stateRef = useRef(state);useEffect(() => {stateRef.current = state; // 每次更新时同步到 ref
}, [state]);useEffect(() => {const timer = setInterval(() => {console.log(stateRef.current); // 总是获取最新值}, 1000);return () => clearInterval(timer);
}, []);
(4)reducer 管理复杂的状态
function reducer(state, action) {switch (action.type) {case 'FETCH_SUCCESS':return { ...state, data: action.payload, loading: false };case 'FETCH_ERROR':return { ...state, error: action.payload, loading: false };default:return state;}
}function MyComponent() {const [state, dispatch] = useReducer(reducer, {data: null,error: null,loading: false});useEffect(() => {const fetchData = async () => {dispatch({ type: 'FETCH_START' });try {const response = await fetch('/api/data');dispatch({ type: 'FETCH_SUCCESS', payload: await response.json() });} catch (error) {dispatch({ type: 'FETCH_ERROR', payload: error });}};fetchData();}, []);
}
3.3 场景
3.3.1 请求接口
import React, { useState, useEffect } from 'react';const Index = () => {// 定义一个名为 data 的 state,初始值为 nullconst [data, setData] = useState(null);// 使用 useEffect 进行数据获取useEffect(() => {// 模拟一个异步的数据获取操作const fetchData = async () => {const response = await fetch('XXXXXXXXXXXX');// 将获取到的数据更新到 data state 中setData(response );};// 调用数据获取函数fetchData();// 可以返回一个清理函数,用于在组件卸载时执行一些清理操作,比如取消订阅等return () => {console.log('组件卸载了');};}, []); // 传入空数组,表示这个 useEffect 只在组件第一次渲染后执行return (<div>{data? (// 如果 data 不为 null,显示数据的标题<p>{data.title}</p>) : (// 如果 data 为 null,显示加载中<p>加载中...</p>)}</div>);
}export default Index;
3.3.2 调用异步写法
import { useEffect, useRef } from 'react';
const approveStatusList = useRef([])/** 写法一 */
useEffect(() => {const fetch = async () => {/** 获取接口 */const res = await getList()approveStatusList.current = res|| [];};fetch();
}, []);/** 写法二 */
useEffect(() => {(async function() {const res = await getList()approveStatusList.current = res|| [];})();
}, []);
3.3.3 监听某个值
useEffect(() => {const params = {tabsList};handleParams(params);}, [tabsList]);
4、useCallback
4.1 定义
用于优化性能,避免在组件重新渲染时不必要的函数重建
在react 中,重新渲染时,内部定义的函数都会重新创建,可能会导致:
1、不必要的子组件重新渲染(当函数作为 props 传递给子组件)
2、依赖该函数的 useEffect 被 不必要的触发
使用场景
1、函数作为 props 传递给子组件(特别是用 React.memo 优化的子组件)2、函数作为其他 Hook 的依赖项(如 useEffect、useMemo 等)
3、在自定义 Hook 中返回稳定的函数引用
注意事项
1、不要过度使用 useCallback - 只有在确实需要优化性能时才使用2、正确设置依赖项 - 遗漏依赖项会导致闭包问题
3、useCallback 不会使函数运行更快 - 它只是避免不必要的重新创建
函数式组件可以理解为 class 组件 render 函数的语法糖,每交重新渲染的时候,函数式组件内部会把代码都重新执行一遍。useCallback 能获取一个记忆后的函数。
const Index = () => {const handleClick= useCallback(() => {console.log('处理逻辑')}, []); // 空数组代表无论什么情况下该函数都不会发生改变return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}export default Index
4.1 基础示例
4.1.1 计数器示例
带 useCallback
import React, { useState, useCallback } from 'react';function Counter() {const [count, setCount] = useState(0);// 使用 useCallback 缓存函数const increment = useCallback(() => {setCount(c => c + 1);}, []); // 空依赖数组表示函数不会改变const decrement = useCallback(() => {setCount(c => c - 1);}, []);return (<div><p>Count: {count}</p><button onClick={increment}>Increment</button><button onClick={decrement}>Decrement</button></div>);
}
4.1.2 与子组件配合使用
import React, { useState, useCallback } from 'react';// 子组件(使用 React.memo 优化)
const Button = React.memo(({ onClick, children }) => {console.log(`Button ${children} rendered`);return <button onClick={onClick}>{children}</button>;
});function ParentComponent() {const [count, setCount] = useState(0);const [value, setValue] = useState('');// 使用 useCallback 缓存函数const increment = useCallback(() => {setCount(c => c + 1);}, []);return (<div><p>Count: {count}</p><Button onClick={increment}>Increment</Button><input value={value}onChange={(e) => setValue(e.target.value)}/></div>);
}
在这个例子中,即使 ParentComponent 因为 value 状态变化而重新渲染,increment 函数也不会重新创建,因此 Button 组件不会不必要地重新渲染。
5、useMemo
5.1 定义
对一个计算过程进行记忆化,只有当依赖项改变时才会重新计算。
useCallback 的功能完全可以用 useMemo 取代,如果想用 useMemo 返回一个记忆函数也查完全可以的。下面是使用方法:
const Index = () => {const handleClick= useMemo(() => {console.log('处理逻辑')}, []); // 空数组代表无论什么情况下该函数都不会发生改变return <SomeComponent onClick={handleClick}>Click Me</SomeComponent>;
}export default Index
1、useMemo 和 useCallback 唯一的区别:useCallback 不会执行第一个参数函数,是将穹返回给你, useMemo 是会执行第一个函数并将结果返给你。
2、useCallback 是一个记忆事件函数,生成记忆后的事件函数传给子组件使用。useMemo 适合经过函数计算得到的一个确定的值。比如记忆组件,请看下面代码:
const demo = ({ a, b }) => {const child1 = useMemo(() => <Child1 a={a} />, [a]);const child2 = useMemo(() => <Child2 b={b} />, [b]);return (<>{child1}{child2}</>)
}
当 a/b 改变时,child1/child2 才会重新渲染。从例子可以看出来,只有在第二个参数数组的值发生变化时,才会触发子组件的更新
6、useImperativeHandle
6.1 定义
用于在使用
forwardRef
时自定义暴露给父组件的实例值。(简单理解:透传 ref)useImperativeHandle(ref, handle)
- ref:父组件传递的引用。
- handler:返回一个对象,定义要暴露给父组件的实例值。
6.2 基础使用
import React, { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';/** 子组件,使用 forwardRef 包装以便父组件可以引用子组件的 DOM 元素 */
const ChildInputComponent = forwardRef((props, ref) => {// 1、使用 useRef 创建一个引用对象const inputRef = useRef(null);// 2、使用 useImperativeHandle 自定义暴露给父组件的实例值useImperativeHandle(ref, () => ({focus: () => {inputRef.current.focus(); // 调用原生 input 的 focus 方法}}));// 3、返回一个 input 元素,并将 inputRef 附加到它上面return <input type="text" name="child input" ref={inputRef} />;
});/** 父组件 */
const App = () => {// 1、使用 useRef 创建一个引用对象const inputRef = useRef(null);// 2、使用 useEffect 在组件加载时自动聚焦到子组件的 input 元素useEffect(() => {if (inputRef.current) {inputRef.current.focus(); // 调用子组件暴露的 focus 方法}}, []);// 3、返回一个包含子组件的 JSXreturn (<p><ChildInputComponent ref={inputRef} /></p>);
};export default App;
forwardRef
是一个 React 高阶函数,用于创建一个 React 组件,这个组件能够将其接受的 ref 属性转发到另一个组件中。在这个例子中,
ChildInputComponent
是一个函数组件,它使用forwardRef
包装,以便父组件可以通过ref
引用子组件的 DOM 元素。
6.3 场景使用
情景:子组件的table中数据更新出暴露出来给父级,父亲能接收到这个更新后的数据,父级中定义ref,然后获取子级更新的数据。
/** 父组件 **/
import { useRef, useState } from "react";
import CoverModel from "./coverModel";const Index = (props, ref) => {const coverModelRef = useRef<any>(null); // 客户团覆盖机型const handleSave = () => {// 获取子组件中的console.log(coverModelRef?.current?.table);};return (<><CoverModel ref={coverModelRef} /></>);
};export default Index;/** 子组件 */
import React, {forwardRef,useImperativeHandle,useMemo,useState,
} from "react";
import { observer } from "mobx-react";
import {Button, DataSet, Table} from 'choerodon-ui/pro';const Index = observer(forwardRef((props: any, ref: any) => {useImperativeHandle(ref, () => {return {table: tableDs.toData(),};});/** table ds */const tableDs = useMemo(() =>new DataSet({...tableList(id),events: {load: () => {// 数据加载完成后更新 ref,父级中会接收到if (ref) {ref.current = {table: tableDs.toData(),};}},},}),[id]);const columns = useMemo(() => {return [{ name: "name" },{ name: "code" },{ name: "manageIndustryName" },];}, []);return (<><TabledataSet={tableDs}columns={columns}buttons={tableButtons}pagination={false}style={{ marginBottom: "16px" }}renderEmpty={() => {return <div>暂无数据</div>;}}/></>);})
);export default Index;
7、useContext
7.1 定义
8、useReducer
8.1 定义
useReducer 这个 Hooks 在使用上几乎跟 Redux/React-Redux 一模一样,唯一缺少的就是无法使用 redux 提供的中间件。
可以理解成一个 mini 的 redux
9、useLayoutEffect
9.1 定义
其功能与
useEffect
相同,但它会在所有的 DOM 变更之后同步调用,可以用于读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect
内部的更新计划将被同步刷新。
useLayoutEffect(() => {// 读取 DOM 布局并同步触发重渲染的代码return () => {// 清理操作};
}, [dependency]); // 依赖项数组,用于指定何时重新运行副作用
9.2 知识点
大部分情况下,使用 useEffect 可以帮我们处理组件的副作用,但想要同步调用一些副作用,例如操作DOM,可以使用 useEffectLayoutEffect,这个会在DOM更新后同步更新。
9.3 场景使用
在下面的 demo 中,useLayoutEffect 会在 render,DOM 更新之后同步触发函数,会优于 useEffect 异步触发函数。
import React, { useState, useLayoutEffect, useRef } from 'react';function App() {const [width, setWidth] = useState(0);const titleRef = useRef(null); // 使用 useRef 来存储 DOM 元素的引用const lastWidthRef = useRef(0); // 使用 useRef 来存储上一次的宽度值useLayoutEffect(() => {if (titleRef.current) {const titleWidth = titleRef.current.getBoundingClientRect().width;console.log("useLayoutEffect");if (lastWidthRef.current !== titleWidth) {lastWidthRef.current = titleWidth; // 更新上一次的宽度值setWidth(titleWidth); // 更新状态}}});useEffect(() => {console.log("useEffect");});return (<p><h1 id="title" ref={titleRef}>hello</h1> {/* 使用 ref 而不是 querySelector */}<h2>{width}</h2></p>);
}export default App;
10、useDebugValue
用于在 React DevTools 中显示自定义 Hook 的标签。这个 Hook 通常不直接使用在生产代码中,主要用于调试。
useDebugValue(value); // 在 DevTools 中显示 value 的值或标签。