React类组件 的生命周期分为 三个阶段: 挂载 更新 和卸载
- 挂载阶段
- 更新阶段
- 卸载阶段
- 移除的方法
- 错误捕获阶段
挂载阶段(4):
1. constructor() 构造函数
2. getDerivedStateFromProps(props,state) :挂载和更新阶段都会执行
3. render() 渲染
4. componentDidMount():在render之后执行,数据获取、事件监听
问题1:constructor中一般用来做哪些事情
- 使用super(props)获取父组件的props
- this.state的初始化赋值
- 在this上挂载方法:如this.xxx = this.xxx.bind(this)
问题2: super 里不传递props会怎么样
会导致在构造函数结束前 this.props的值是undefiend;
问题3: getDerivedStateFromProps 可以访问this吗
不可以,他是个
静态方法,不能访问组件实例
问题4: getDerivedStateFromProps(props,state) 返回什么?
该方法需要返回一个新的对象作为新的
state或者返回null表示state状态不需要更新 该生命周期 无论props发生变化还是state变化都会调用
问题5: render中要注意什么?
不要使用setState 会导致死循环
更新阶段(5)
1. getDerivedStateFromProps(props,state)
2. shouldComponentUpdate(nextProps,nextState)
3. render()
4. getSnapshotBeforeUpdate(prevProps,prevState)
5. componentDidUpdate()
问题1: shouldComponentUpdate(nextProps,nextState) 性能优化
默认返回true,如果不需要更新 返回false表示不需要更新; 不建议进行深层比较会影响效率
对于复杂的逻辑,可以考虑使用
PureComponent或React.memo(对于函数组件),它们内部实现了浅比较props和state的shouldComponentUpdate逻辑。
⚠️ 不能在内部使用setState 会导致死循环
问题5: getSnapshotBeforUpdate 一般用来做什么?
在render之后,但是dom元素还没有被更新;返回一个snapshot的值,作为componentDidUpdate的第三个参数传入;
用于获取组件更新前的一些信息,如组件滚动条位置之类的;在组件更新后可以恢复一些ui视觉上的状态
卸载阶段(1)
1. componentWillUnMount()
不安全的生命周期(3)-v16 之后已废弃
1. componentWillmount
它可能会被调用多次,因为在某些情况下(如服务器端渲染),组件可能会
被多次实例化和挂载。对于异步操作,在 componentWillMount 中
调用异步操作可能会导致问题,因为它不会等待操作完成就开始渲染,可能导致组件渲染时数据还未准备好。
2. componentWillRecevieProps(nextProps):初始渲染时不会触发
这个方法允许你在组件接收到新
props时执行某些操作,比如根据新props来更新状态(state)或执行一些副作用操作。
componentWillReceiveProps的行为·有时会导致混淆·,因为它会在组件接收到新的props 时被调用,而
不管这些 props是否实际发生了变化。这可能会导致不必要的状态更新和性能开销
3. componentWillUpdate():组件即将更新前调用
componentWillUpdate在异步渲染环境中可能会导致不一致的行为和难以调试的问题,因为它可能在不可预测的时机被触发。
错误捕获(2)-V16后引入的
在 React 16 之前,React
没有内置的机制来捕获组件渲染期间发生的错误,一旦渲染过程中出现错误,会导致整个 React 应用崩溃。componentDidCatch的引入是为了解决这个问题,它允许开发者在组件树中捕获错误,从而提供了一种更友好的错误处理方式,避免应用崩溃,同时可以显示降级的 UI(fallback UI)或进行错误日志记录等操作。v16引入的:
- getDerivedStateFromError() 捕获后代组件中的JS错误并更新组件状态。
- compnentDidCatch()你可以将错误信息发送到错误日志服务,例如 Sentry 或自己的后端服务
术语:统一错误处理可以 提高应用的健壮性和用户体验,避免因捕获到的错误导致整个应用的崩溃
1. static getDerivedStateFromError():统一误处理的工具
getDerivedStateFromError是一个静态方法,这意味着你不能在该方法中使用
this关键字,因为它在组件实例化之前被调用。
2. compnentDidCatch: 配合1使用 记录错误
-
componentDidCatch主要是捕获渲染过程中的错误,不会捕获以下情况的错误:事件处理程序中的错误,需要使用 try-catch来捕获:onClick异步代码中的错误,需要使用 try-catch 或.catch()方法:setTimeout,Promise,await服务端渲染中的错误,需要使用其他机制:SSR
React 函数组件Hooks
React函数组件没有生命周期,常用的hooks有下面几个:
基础:
- 状态usestate():设置state
- 异步 useEffect():执行副作用
- 同步 useLayoutEffect(): 在所有dom变更后同步执行,在浏览器绘制之前
useEffect中的副作用操作会在
浏览器渲染完成后异步执行,不会阻塞页面的渲染。获取数据
useLayoutEffect 中的操作会在
DOM 变更后立即同步执行,在浏览器绘制之前运行。以避免布局抖动或闪烁
面试题😁:useLayoutEffect 怎么实现的同步?会在dom变更后 浏览器执行前 同步绘制
useEffect(回调函数,依赖数组):
- 回调函数内部返回一个清理函数:该函数会在组件卸载或者重新渲染前被调用,用于清理上一次的副作用操作:如计时器,订阅
- 第二个参数:
- []: 仅在组件挂载和卸载时执行一次
- 不提供数组:组件每次渲染都会更新
- [xx,xx2]: 当xx和xx2变化的时候,副作用会重新执行
副作用操作:除了组件渲染以外的操作,如 数据获取,订阅,手动修改dom,记录日志等;
性能优化:
- useMemo():缓存变量,返回一个计算结果;进行
复杂数据处理时使用 - useCallBack():缓存函数,返回一个函数;回调函数是
子组件的事件处理器时使用;
useMemo(fn,依赖项) : 避免在每次渲染时都进行复杂的计算和重新创建对象。通过记住上一次的计算结果,只有在依赖项发生变化时才重新计算,从而提高性能。;通常用于避免在每次渲染时都执行昂贵的计算,例如,在渲染大量数据时进行复杂的数学运算或数据处理。
useCallback(fn, 依赖项): 避免在每次渲染时都重新创建函数。通过记住上一次创建的函数,只有在依赖项发生变化时才重新创建新的函数,从而提高性能。;useCallback则通常用于避免在每次渲染时都重新创建函数,特别是在回调函数作为子组件的事件处理器时,可以避免不必要的重新渲染。
上下文对象:
- useContext():共享数据
import { createContext } from "react";
const MyContext = createContext(null);
export default MyContext;
// import 上面的 MyContext;
<MyContext.Provide value={{a:"sss",b:"sss1"}}>
// 子组件
</MyContext.Provid>
// 子组件
const {a,b} = useContext(MyContext)
// 如果是类组件
const valueObject = this.context;
ref:
- useRef():用于创建对dom的引用
创建ref的方法有4种:
- 字符串 不推荐了
- 类组件使用React.createRef()
- 函数式组件使用React.UseRef()
- 传入函数
ref = {element => this.myref = element}
- useImperativeHandle(): 结合forwardRef,向父组件暴露方法或者属性,可通过ref.current.xx 获取
问题1:为什么需要结合forwardRef?
因为默认情况下, 函数式组件 不能像类组件一样接收ref;为了让函数式组件可以接收red,需要使用
React.forwardRef来转发refforwardRef 是一个高阶函数,它接收一个函数式组件作为参数。
// 这样是会报错的,因为不允许在函数式组件上使用ref
<myComponent ref={this.myRef}>
// 子组件中使用forwardRef包裹
const myComponent = (props,ref)=>{
// 如果父组件中需要使用子组件中的方法 就需要使用 useImperativeHandle
useImperativeHandle(ref, ()=>{
scroll: listRef.current.scroll
setScroll: (top)=>{
listRef.current.scroll = top;
}
})
}
export default forwardRef(myComponent)
- 状态管理useReducer: react 内置的
用于管理复杂的状态逻辑,接受一个reducer函数 和一个初始状态,并返回一个状态值 和dispatch函数;适合在组件内部有复杂状态更新逻辑时使用
import { useReducer } from "react";
// 定义 action 类型
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 定义 reducer 函数
function counterReducer(state, action) {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
throw new Error();
}
}
const Count = () =>{
const [state,dispatch] = useReducer(counterReducer,{count:0})
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: INCREMENT })}>+</button>
<button onClick={() => dispatch({ type: DECREMENT })}>-</button>
</div>
);
}
export default Count;
Redux hooks:
- useDispatch():分发action
- useSelector():读取数据
- useStore():获取整个redux store 实例
React router hooks:
- useHistory
- useNavigate: 在reactRouterV6 中取代了 useHistory
- useLocation: 返回当前location对象
- useParams: 动态参数获取
区别:
1. useMemo和useCallback的区别 ?
useMemo 缓存计算结果,useCallback缓存函数
2. useEffect 和 useLayoutEffect 的区别?
useEffect 异步,useLayoutEffect 同步
- 执行时机不同
- useEffect是浏览器绘制后异步执行;
- useLayoutEffect是在dom更新后 同步执行,在浏览器绘制前;
- 性能上
- 前者不会阻塞渲染;
- 后者阻塞渲染;
- 使用场景:
- 前者是用户数据获取、订阅、异步操作、非关键的 DOM 修改等
- 直接的 DOM 操作,如测量布局、同步更新布局样式等,需要在布局完成后立即进行操作,以保证布局的一致性。
3. useDispatch 和useReducer()区别?
useReducer内置的hooks,组件内状态更新; useDispatch() redux提供的,全局更新
- 来源:一个是redux提供的 ;一个是react内置的
- 用途:前者用于在react组件中派发actions到redux store; 后者用于管理复杂状态的逻辑
- 工作原理:
- useReducer当你调用 dispatch 函数并传入一个 action 时,reducer 函数会根据 action 的类型来更新状态。
- useDispatch 当你调用 dispatch 函数并传入一个 action 时,Redux store 会根据当前的 state 和传入的 action 来计算下一个 state
- 使用场景:
- 适合在全局状态管理(如应用设置、用户信息等)时使用,特别是当状态需要在多个组件之间共享时。
- 适合在组件内部有复杂状态更新逻辑时使用。
- 状态管理范围:
- useReducer 主要用于组件内部的状态管理;
- 而 useDispatch 用于全局状态管理,通过 Redux store 来共享状态。
- 状态更新机制:
- useReducer 通过 reducer 函数在组件内部更新状态;
- useDispatch 派发 actions 到 Redux store,由store根据 reducers 来更新全局状态。
react中的一些性能优化手段
- 类组件使用
shouldComponentUpdate或pureComponent进行精准控制,减少不必要的更新; - 函数组件使用
React.Memo
pureComponent || React.Memo()内部实现了浅比较 props 和state 的shouldComponentUpdate逻辑
- 使用useMemo 和useCallback 进行缓存
- 使用
useEffect的依赖数组优化 - 对于长列表或大量数据的渲染,使用虚拟化列表可以避免渲染不必要的元素,提高性能。
- 使用 React 的
React.lazy和Suspense实现代码拆分和懒加载,减少初始加载时间。 - 减少组件的嵌套层数,避免不必要的组件更新,可以使用
React.memo或shouldComponentUpdate来控制组件的重新渲染。 - 生产环境构建:使用生产环境构建提高性能。
- 性能分析:使用
Profiler分析性能,找出性能瓶颈。