答案见最下方:
一、请简单介绍一下React18有哪些更新。
二、JSX是什么,它和JS有什么区别?
三、为什么在文件中没有使用React ,也要在文件顶部import React from “react”
四、请说一下React 事件机制和JS原生事件机制的区别?
五、React 事件与原生事件的执行顺序?
六、请说说React Hooks解决了什么问题? 函数组件与类组件的区别
七、为什么 useState 要使用数组而不是对象
八、说一下常用的Hook
九、Hook 的使用限制有哪些?
十、useEffect 与 useLayoutEffect 的区别
十一、如何自定义Hook
十二、React 高阶组件是什么,和普通组件有什么区别,适用什么场景
十三、React 高阶组件、Render.props、Hooks 有什么区别,为什么要不断迭代
十四、React组件通信有哪些方式
十五、React中props和state有什么区别?React中的props为什么是只读的?
十六、React 16.X 中 props 改变后在哪个生命周期中处理
十七、React 性能优化在哪个生命周期?它优化的原理是什么
十八、React 中 keys 的作用是什么?
十九、React 中 refs 的作用是什么?
二十、说说React diff 算法
二十一、React 与 Vue 的 diff 算法有何不同?
二十二、React setState 调用的原理
二十三、setState 第一个参数有两种传递方式 1.一个对象 2. 一个函数 这两种写法有什么区别呢?useState呢
二十四、React Hooks 工作机制深度解析
二十五、React中setState的第二个参数作用是什么?
二十六、React中的setState和replaceState的区别是什么?
二十七、React中的setState批量更新的过程是什么?
二十八、React 中 setState 什么时候是同步的,什么时候是异步的?
二十九、React Hooks 设计动机与设计模式解析
三十、React实现缓存的方式有哪些?他们有什么区别?
三十一、对React中Fragment的理解,它的使用场景是什么?
三十二、什么是受控组件和非控组件
三十三、React.Component 和 React.PureComponent 的区别
三十四、对有状态组件和无状态组件的理解及使用场景
三十五、useffect 模拟生命周期
三十六、哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?
三十七、hooks父组件怎么调用子组件的方法?
三十八、React的严格模式如何使用,有什么用处?
三十九、React的设计思想是什么
四十、为什么React自定义组件首字母要大写
四十一、React组件为什么不能返回多个元素
四十二、简述React的生命周期
四十三、对React的插槽(Portals)的理解,如何使用,有哪些使用场景
四十四、父组件和子组件的生命周期执行顺序
四十五、React声明组件有哪几种方法,有什么不同?
四十六、React组件的构造函数有什么作用?它是必须的吗?
四十七、React性能优化手段
四十八、在React中如何避免不必要的render?
四十九、React-Router工作原理
五十、对 Redux 的理解,主要解决什么问题
五十一、Redux 原理及工作流程
五十二、Redux 中异步的请求怎么处理
五十三、Fiber架构
五十四、React错误处理?
五十五、数据双向绑定的原理
五十六、怎么动态导入组件,按需加载,代码分割?
五十七、React的状态提升是什么?使用场景有哪些?
五十八、对 React 和 Vue 的理解,它们的异同
五十九、对React SSR的理解
六十、react-redux
中的connect怎么实现?
六十一、diff
的时间复杂度是多少,为什么?
一、请简单介绍一下React18有哪些更新。
1. 并发渲染
并发渲染实际上不是一个功能,而是React的新的底层渲染机制,使得React可以同时准备多套UI。
并发模式的一个最重要的特性渲染可中断。React18之前的页面渲染是一个一个同步事务进行处理,是一旦开始就无法中断。
并发模式意味着,所有的UI显示任务都是可以被中断的,它可以先执行某个渲染更新然后挂起等待。这意味着渲染必须在DOM树计算完毕以后再执行。React就可以在这期间准备新的更新,而不影响操作的执行。
另一个特性则是可重用性,React可以在渲染某个更新时移除一部分UI,然后在稍后的更新中把它再加回来。
2. 过渡更新
通常为了区分紧急更新和非紧急更新,一般来说紧急更新主要是比如输入、按压、点击等需要马上表现出来的更新,过渡更新则是用于查询结果之类的不那么需要紧急表现得更新操作,通常情况下一般的更新操作都是紧急的,如果需要开启过渡更新,则需要调用例如useStransition
和startStransition
,紧急更新会中断过渡更新,多个过渡更新被中断,react会抛弃之前的更新内容,仅仅渲染最新的内容。
3. 自动批处理
React18之前,react不会对promise
、setTimeout
、事件处理之内的操作进行批处理,只会对promise
、setTimeout
、事件处理以外的操作进行批处理,react18则优化了这一部分,使得在React的任何地方都可以进行自动批处理,当然你也可以使用flushSync
退出批处理.
flushSync(()=>setState(val=>val+1))
对于class有影响,因为在class中可以在18之前在setTimeout
和promise
中是不会自动批处理的,则在setTimeout
中setState
之后是可以拿到this.state
最新的值的,但是在react18因为是自动批处理则现在是拿不到this.state
的最新的值的,如果需要获取的话则需要用flushSync
.
setTimeout(()=>{
this.setState({count}=>count+1);
console.log(this.state.count) //1
this.setState({age}=>{age:20})
},100) //17
setTimeout(()=>{
this.setState({count}=>count+1);
console.log(this.state.count) //0
this.setState({age}=>{age:20})
},100) //18
setTimeout(()=>{
flushSync(()=> {
this.setState({count}=>count+1)}
}
console.log(this.state.count) //1
this.setState({age}=>{age:20})
},100) //18
4. useId
Hook
用于客户端和服务端渲染生成唯一的Id。
5. startTransition
Api / useStransition
Hook
开启过渡渲染
6. useDeferredValue
Hook
用于延迟更新某些UI部分。比如结合Suspense
做异步加载.
你可以将useDeferredValue
作为性能优化的手段,当你的某个UI渲染很慢,你又希望其避免阻塞其他渲染,则你就可以用useDeferredVaue
。
7. Suspense
支持SSR
底层实现依赖错误边界组件,当Suspense
捕获到子组件的promise
,会优先展示fallback
的ui,直到返回resolved
状态再重新渲染。
二、JSX是什么,它和JS有什么区别?
1、语法:JSX的语法类似于HTML; JS是一门编程语言。
2、功能:JSX是定义React组件的UI;JS是用于逻辑的控制和操作。
3、处理:JSX会被转换为JS,不可直接执行;JS是可直接执行;
4、表达式:JSX中{}中可以嵌入JS表达式;
5、不用JSX怎么生成React组件呢?
React.createElement('div','组件')
三、为什么在文件中没有使用React ,也要在文件顶部import React from “react”
因为使用了JSX;
四、请说一下React 事件机制和JS原生事件机制的区别?
1、绑定方式不一样:一个是绑定到JSX上面,一个是绑定到HTML上、动态绑定或者adeventListener;
2、事件对象不一样:React做了兼容处理,可以兼容所有的浏览器;原生需要自己做兼容处理;
3、事件传播不一样:React事件默认只支持冒泡,捕获阶段通过 onClickCapture 监听;JS原生支持冒泡和捕获;
4、事件处理不一样:React是把所以事件集中在一起,更加节约性能;JS原生是每个事件绑定到对应的节点上面;
5、解绑不一样:React不需要解绑操作;JS原生需要解绑操作;
五、React 事件与原生事件的执行顺序?
原生事件优于React事件,首先执行捕获阶段、然后是冒泡阶段,先执行原生的捕获,然后是React的捕获,然后是原生的冒泡,再是React的冒泡。无论是原生还是React,捕获都是优于冒泡;React的事件是委托到根节点的而不是节点本身。
六、请说说React Hooks解决了什么问题? 函数组件与类组件的区别
1、复杂性 当class类组件过于庞大难于理解,Hooks提供了更加简洁易于理解的代码来管理状态和生命周期;
2、逻辑复用,方便你在任何地方使用;
3、冗余代码,可以减少大量冗余代码;
函数组件和类组件的区别:
1、定义方式:使用js函数来定义;使用ES6的class类来定义
2、状态管理:使用useState
管理;使用this.state
,setState
管理
3、生命周期:使用useEffect
管理;使用componeDidMount
等;
4、性能优化:使用useMemo
等;使用React.memo
,shouldComponentUpdate
等
5、代码复用:逻辑交互可以在任何地方复用;代码复用性差;
七、为什么 useState 要使用数组而不是对象
主要原因是可以自定义命名;
八、说一下常用的Hook
1、useState
2、useEffect
3、useMemo
4、useCallback
5、useRef
6、useReducer
7、useLayoutEffect
8、useContext
九、Hook 的使用限制有哪些?
只能在函数组件的顶部使用;不可在for循环、条件语句中使用;不可在JS文件、class组件中使用;
十、useEffect 与 useLayoutEffect 的区别
1、执行时机不同:useEffect是在页面加载和渲染完毕以后异步执行;useLayoutEffect是在页面更新和渲染之前执行,是同步执行
2、性能不同:useEffect因为是异步的,是不会阻塞页面加载;useLayoutEffect可能会导致页面阻塞
3、使用场景:一般用于ajax请求、事件监听等不影响页面布局的操作;一般用于需要获取DOM节点的大小位置等操作;
十一、如何自定义Hook
const useFetch = (url)=>{
const [loading,setLoading] = useState(false);
const [error,setError] = useState('');
const [result,setResult] = useState(null);
useEffect(()=>{
try{
setLoading(true)
axios.get(url).then((res)=>{
setResult(res);
setLoading(false)
}).catch((err)=>{
setError(err);
setLoading(false)
})
catch(error){
setError(error);
setLoading(false)
}
},[url])
return {loading,error,result}
}
const [loading,error,result] = useFetch('http:172.27.1.1/api/test/')
十二、React 高阶组件是什么,和普通组件有什么区别,适用什么场景
是一种高级的组件设计模式,而不是ReactApi的一部分,接受一个组件然后返回一个新的组件,缺点是会污染传入组件的props
,难以访问原组件的状态和生命周期
特点:
1、高复用性:HOC使得组件之间的逻辑代码可以复用
2、状态抽象:HOC可以封装和管理组件状态
3、增强功能:HOC可以轻松的给组件增强功能;
适用于:
权限管理、数据处理、状态管理
十三、React 高阶组件、Render.props
、Hooks 有什么区别,为什么要不断迭代
Render.props
是一种把React作为元素返回的组件,会导致嵌套地狱;
class MouseTracker extends React.Component {
constructor(props) {
super(props);
this.state = { x: 0, y: 0 };
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
<MouseTracker render={({ x, y }) => (
<MouseTracker render={({ x, y }) => (
<h1 style={{ color: 'red' }}>鼠标位置: ({x}, {y})</h1>
)}/>
)}/>
Hooks是在函数组件中使用,并且解决了Hoc和Render.props
的缺点。
十四、React组件通信有哪些方式
1、父传子
props
2、子传父
props传函数进来,refs获取子组件实例
// 父组件
function Parent() {
const handleChildEvent = (data) => {
console.log("Received data from child:", data);
};
return <Child onEvent={handleChildEvent} />;
}
// 子组件
function Child({ onEvent }) {
return <button onClick={() => onEvent("Child Data")}>Send Data</button>;
}
// 父组件
function Parent() {
const childRef = useRef(null);
return (
<div>
<Child ref={childRef} />
<button onClick={() => childRef.current.focusInput()}>
聚焦子组件输入框
</button>
</div>
);
}
// 子组件(需使用 forwardRef 转发 ref)
const Child = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focusInput: () => inputRef.current.focus(),
}));
return <input ref={inputRef} />;
});
3、跨层级
Context
Api
//context.js
import React, { createContext, useContext, useState } from 'react';
// 创建Context
export default const MessageContext = createContext();
// 父组件
import MessageContext from './context.js'
const ParentComponent = () => {
const [message, setMessage] = useState("Hello from Context!");
return (
<MessageContext.Provider value={{ message, setMessage }}>
<ChildOne />
<ChildTwo />
</MessageContext.Provider>
);
};
// 子组件1
import MessageContext from './context.js'
const ChildOne = () => {
const { setMessage } = useContext(MessageContext);
const updateMessage = () => {
setMessage("Updated message from Child One!");
}
return (
<div>
<button onClick={updateMessage}>Update Message</button>
</div>
);
};
// 子组件2
import MessageContext from './context.js'
const ChildTwo = () => {
const { message } = useContext(MessageContext);
return (
<div>
<h2>Message: {message}</h2>
</div>
);
};
export default ParentComponent;
4、兄弟层级
通过公用的父组件来传递参数
5、全局状态
Redux
、Zustand
等
十五、React中props
和state
有什么区别?React中的props
为什么是只读的?
props
是只读的不可编辑,state可以编辑
props
是父组件传入的属性数据,state是组件内部的属性数据,在constructor
中初始化;
props
在组件初始化后就不可变,state是可以在组件内不同生命周期改变
保证React单向数据流的设计模式,父组件可以向不同的组件传递props。
十六、React 16.X 中 props 改变后在哪个生命周期中处理
1、componentReceviedProps()
(16.3以后已经废弃);
2、componentDidUpdate()
3、getDerivedStateFromProps()
十七、React 性能优化在哪个生命周期?它优化的原理是什么
在shouldComponentUpdate
;此外在16.3以后还加入了React.PureComponent
和React.memo
shouldComponentUpdate
可以对state
、props
的更新做处理,如果不需要则返回false
React.PureComponent
的功能是对state
、props
做浅比较,如果浅比较都不需要更新,那么就不更新
React.memo
和React.PureComponent
的功能类似,只是是用于函数组件
优化的原理:
主要原理就是就是为了减少渲染以及减少diff算法,因为diff算法是非常宝贵的,减少diff算法和算法就可以优化极大的性能。
十八、React 中 keys 的作用是什么?
帮助React高效的识别和精准的跟踪DOM中的变化,比如删除、添加、修改等操作,特别是动态列表和动态排序的时候,帮助React判断哪些节点需要移除或者重新排序,减少DOM操作。
十九、React 中 refs 的作用是什么?
用于直接访问DOM元素或者组件实例。
二十、说说React diff 算法
React的diff算法是基于索引的比较,是新旧虚拟DOM树之间进行比较,尽可能的减少真实的DOM操作,提高性能,React会先比较组件的类型和属性,只有在必要的时候才更新其内部元素;
Diff算法的关键策略:
1、分层比较:将整个UI拆分成树状结构,比较每一个层级,只会比较不同类型的组件,如果是不同类型的组件会直接删除替换;
2、统计比较:统计层级的节点会根据key值进行比较,key帮助react精确识别每个元素的位置,优化重排;
3、元素更新与删除:如果两个节点具有相同的key,react会更新他们,否则会删除并创建新的节点;
4、避免不必要的更新:如果state和props没有变化,则不会重新渲染组件。
具体步骤:
1、先比较类型:如果类型不同则直接销毁旧节点,创建新节点。
2、同类型比较:如果类型是相同的,那么react会根据其属性、子节点,比较他们的差异并更新;
3、递归对比子节点:React会递归的对比所有的子节点,重复以上的步骤,并且尽量重用已有的key;
key的作用:
用于标识哪个节点发生了变化,帮助React判断哪些节点需要移除或者重新排序,减少DOM操作;
二十一、React 与 Vue 的 diff 算法有何不同?
React的diff算法是基于索引的比较,主要是对新旧虚拟DOM树进行比较,尽可能的减少DOM操作,提高性能,React会优先比较组建的类型和属性,只有在必要的时候才会更新其内部元素;
Vue的diff算法是基于节点的比较,而且是采用双端的算法,从前后两端先进行比较,然后再向中间进行比较;Vue对于Key的使用较少,在没有Key的时候,Vue会默认按照位置进行节点重排,所以会有些性能问题。
二十二、React setState 调用的原理
1、触发更新
当调用setState
时React会将这个状态更新表记为待处理。
2、合并状态
在ReactsetState
是异步的,所以当有多个setState
操作时,这些调用会被合并成为一个。
3、调度更新
React使用了调度来更新这些状态,在16和之后的版本中因为引入了Fiber
架构。当调用setState
的时候,React会将该更新加入到Fiber
的更新队列中。
4、执行渲染
在浏览器的下一个事件循环中,React会从Fiber
的更新队列中取出需要更新的组件,并根据新的状态和属性进行渲染逻辑。这一步会调用组件的render方法,同时会比较新旧的虚拟DOM的差异,最终只对差异的部分进行实际的DOM更新。
二十三、setState 第一个参数有两种传递方式 1.一个对象 2. 一个函数 这两种写法有什么区别呢?useState呢
// 连续调用两次,结果可能只加 1(而非预期的加 2)
handleClick = () => {
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
};
// 连续调用两次,结果会正确加 2
handleClick = () => {
this.setState((state) => ({ count: state.count + 1 }));
this.setState((state) => ({ count: state.count + 1 }));
};
useState
道理也是一样的
二十四、React Hooks 工作机制深度解析
1、Hooks
和Fiber
架构的绑定机制;
状态存储位置:
每个Hook
创建时,会在组件的Fiber
节点的memoizedState
属性中以链表行书存储。
每个Hook
节点包含memoizedState
(上一次渲染的状态值)、quene
(更新队列)和next
(指向下一个Hook
)字段。
执行顺序的强制性:
Hooks
必须保证每次渲染的调用顺序一致,否则会因链表节点错位导致状态错乱给,所以不能再if
、for
循环中使用Hook
if (condition) {
const [count] = useState(0); // 破坏 Hook 链表顺序
}
2、核心Hooks
实现原理
useState
状态管理机制
首次渲染:初始化Hook
节点,将初始值存入memoizedState
;
更新阶段:通过dispatch
函数将更新操作加入队列,触发重新渲染并计算新状态。
闭包陷阱:异步回调如定时器、事件监听,直接使用state可能引用旧值,可能导致更新丢失(需通过函数式更新解决或者用useRef
同步最新的值)
setCount(prev => prev + 1); // ✅ 正确方式
setCount(count + 1); // ❌ 可能依赖过期闭包值
useEffect
副作用调度
副作用函数在提交阶段异步执行,避免阻塞渲染流程
依赖数组对比,空数组只执行一次
清理机制:返回的清理函数会在组件卸载或依赖变化前执行。
3、Hooks
渲染流程
挂载阶段:
创建Fiber
节点并创建Hook
链表;
执行组件函数,触发Hooks
初始化逻辑;
更新阶段:
遍历Hook
链表,依次应用更新队列中的操作;
生成新虚拟DOM,通过Diff算法更新视图;
卸载阶段:
执行useEffect
返回的清理函数;
移除Fiber
节点及关联的Hook
链表
二十五、React中setState的第二个参数作用是什么?
回调函数,可以用来做执行完setState后需要的操作,比如打印日志、获取最新的状态之类的。
二十六、React中的setState和replaceState的区别是什么?
setState
只是做浅合并,不会影响到其他属性,提高性能。
replaceState
是完全覆盖,可能会删除某些属性(不推荐使用)。
二十七、React中的setState批量更新的过程是什么?
调用setState
,组件的state
不会立即更新,setState
只是把需要修改的state
放到一个队列里面,并且是只要还有同步任务,就会一直把需要更新的state
放到队列里面去,同时会把多次setState
的状态最终合并到一起,这就是批量更新。
二十八、React 中 setState 什么时候是同步的,什么时候是异步的?
react18之前在setTimeOut
、promise
、setInterval
、原生adeventListener
中是同步的
合成事件中、生命周期中是异步的。
在React18之后都是异步的
二十九、React Hooks 设计动机与设计模式解析
1、设计动机
逻辑复用困境:
类组件通过HOC或者Render props实现逻辑复用,导致组件嵌套层级过深,代码耦合度增加
共享状态逻辑需要依赖复杂的设计模式,难以跨组件高效复用
生命周期分散性
关联逻辑被拆分到不同的生命周期比如componentDidMount
和componentDidUpdate
,代码冗余易遗漏清洗操作。
类组件中的this
绑定问题与实例方法管理复杂度高
函数组件的增强需求
函数组件无法管理状态和副作用,导致其能力不足,所以用useState
和useEffect
来补齐短板
2、设计模式
状态和UI分离模式,拆分组件样式和组件逻辑代码成为自定义的hooks
,减少代码耦合。
副作用统一管理
useEffect
整合类组件的componentDidMount
、componentDidUpdate
和componentWillUnmount
生命周期,通过依赖数组控制执行时机;
清理机制:返回清理函数避免内存泄漏(如取消网络请求、移除事件监听)
性能优化模式
缓存策略:useMemo
缓存计算结果,useCallback
缓存函数引用,减少不必要的渲染
惰性初始化:传递函数给useState初始化复杂状态,避免重复计算
三十、React实现缓存的方式有哪些?他们有什么区别?
React Query
、SWR
localStorage
、sessionStorage
React.memo
、React.PureComponent
useMemo
、useCallback
持久化:只有localStorage
、sessionStorage
是长期持久化的,sessionStorage
在关闭窗口后也会消失,但是刷新不会消失,其他的都会消失。
使用场景:
localStorage
、sessionStorage
存放需要持久化存放的数据
React.memo
、React.PureComponent
主要用于class组件的缓存或者复杂计算结果的缓存。
React Query
、SWR
主要用于异步存储数据和缓存。
useMemo
、useCallback
主要是hooks中使用对函数组件的缓存
三十一、对React中Fragment的理解,它的使用场景是什么?
Fragment
组件实际上是因为React不能直接返回多个节点,如果需要返回多个节点又不想同时新增其他实际的节点去包裹他们,那么就可以用Fragment
节点包裹需要返回的子节点,它还可以写为<></>;
三十二、什么是受控组件和非控组件
使用React来管理表单状态数据的组件称为受控组件
不需要React来管理表单状态数据的组件成为非受控组件,用ref来获取表单的内容
三十三、React.Component 和 React.PureComponent 的区别
PureComponent
组件会对props
和state
进行浅比较,避免不必要的更新,实际上是实现了shouldComponetUpdate
的功能;
三十四、对有状态组件和无状态组件的理解及使用场景
有状态组件:
有生命周期、class组件、有this、有继承、内部使用state管理状态
无状态组件:
可以是calss组件也可以是函数组件、无this、不使用生命周期、无state管理,完全依赖外部的props做展示作用
三十五、useffect 模拟生命周期
componentDidMount
:
useEffect(()=>{
//加载
},[])
componentWillUnmount
:
useEffect(()=>{
return ()=>{
//卸载
}
})
componentDidUpdate
:
useEffect(()=>{
//没有任何等于监听所有的更新
})
三十六、哪些方法会触发 React 重新渲染?重新渲染 render 会做些什么?
1.setState()
方法被调用
2.父组件重新渲染
3.props
更新
4.Context
Api
5.全局状态管理
重新渲染render首先会比较新旧DOM树,根据比较出来的结果计算出需要更新的差异,然后动态更新需要更新的部分。
三十七、hooks父组件怎么调用子组件的方法?
首先需要forwardRef
包裹子组件
const child = forwardRef((ref)=>{
//然后需要使用useImperativeHandle暴露对应的方法给父组件
useImperativeHandle(ref,()=>({
someMethod:()=>{
}
})
})
const parent = ()=>{
const ref = useRef(null)
const childMethd = ()=>{
if(ref.current){
ref.current.someMethod();
}
}
return (
...
<Child ref={ref} />
)
}
三十八、React的严格模式如何使用,有什么用处?
通过React.strictMode
启用,帮助检验不规范的代码,提升代码质量,只在开发环境中生效。
三十九、React的设计思想是什么
组件化:把用户界面分解成可复用的组件
虚拟DOM:避免直接的DOM操作,虚拟DOM是真实DOM的映射关系,使用虚拟DOM优化更新渲染的性能,避免大量且频繁的DOM操作
数据驱动视图:数据更新就重新渲染页面,不需要额外的DOM操作
四十、为什么React自定义组件首字母要大写、
因为如果是小写的会被当做HTML来处理,所以需要首字母大写。
四十一、React组件为什么不能返回多个元素
因为这是为了虚拟DOM,虚拟DOM只有一个根节点时,才能够更加高效容易的计算出差异并进行最小化的DOM更新。
四十二、简述React的生命周期
分为三个阶段:
组件装载阶段:挂载阶段组件会被创建,然后被插入到DOM中,完成组件的渲染,该过程只会发生一次;
该过程中会执行constructor
、getDerivedStateFormProps
、render
、componentDidMount
;
组件更新阶段:当组件的props
、state
等触发更新时,会重新渲染,这个过程可能会执行很多次
该过程中会执行:
getDerivedStateFromProps
、shouldComponentUpdate
、render
、getSnapShotBeforeUpdate
、componentDidUpdate
getSnapShotBeforeUpdate
:这个是在render
之后执行,在componentDidUpdate
之前调用必须和componentDidUpdate
一起使用,有两个参数一个是prePorps
和propsState
表示更新之前的state
、props
;典型场景包括:
记录滚动位置,以便更新后恢复
获取元素尺寸或位置等瞬时状态
与 componentDidUpdate 配合使用
该方法返回的值会作为第三个参数传递给 componentDidUpdate,形成数据传递链路:
getSnapshotBeforeUpdate(prevProps, prevState) {
return { scrollTop: document.getElementById('list').scrollTop };
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot) {
// 根据快照恢复滚动位置
}
}
组件卸载阶段:
只有一个componentWillUnmount
会卸载和销毁组件的所有内容,比如定时器和网络请求等。
四十三、对React的插槽(Portals)的理解,如何使用,有哪些使用场景
插槽指的是可以将子组件渲染到除父组件之外的DOM节点中,主要用于模态框、提示框等。
const Modal = ()=>{
return ReactDOM.createPortal(
<div>提示框</div>,
document.getElementById('root')
)
}
四十四、父组件和子组件的生命周期执行顺序
父conctructor
子conctructor
父render
子render
父componentDidMount
子componentDidMount
四十五、React声明组件有哪几种方法,有什么不同?
1.React.createClass
、React.Component
、函数组件
React.createClass
、React.Component
React.createClass
:是ES5的写法现在已经不推荐了
React.Component
:是ES6的写法他和函数组件的区别主要是,拥有生命周期,拥有this
,拥有构造函数,可以继承,不支持hooks
,拥有状态管理。
四十六、React组件的构造函数有什么作用?它是必须的吗?
是用来初始化组件和绑定事件处理的方法,不是必须的。
四十七、React性能优化手段
缓存比如React.memo
、useMemo
、useCallback
等来做缓存
避免不必要的重新渲染,可以使用React.fragment
、shouldComponentUpdate
、React.PureComponent
列表加key:渲染列表提供一个唯一的key,帮助React在Diff算法的时候更加精准高效的找到对应的差异,并精确更新。
懒加载和代码分割:使用懒加载来加载页面,可以减少加载的时间,提高性能。
计算密集型任务:可以使用Web Workers
,可以避免阻塞UI页面。
服务端渲染SSR
四十八、在React中如何避免不必要的render?
缓存比如React.memo
、useMemo
、useCallback
等来做缓存
避免不必要的重新渲染,可以使用React.fragment
、shouldComponentUpdate
、React.PureComponent
四十九、React-Router工作原理
原理:监听URL变化,动态渲染组件,从而实现页面的跳转和切换。
两种路由模式:
hash
:使用window.location.hash
监听hash
的变化 路径中带有#;通过window.onhashchange
监听
history
:使用window.history.pushState
和window.history.replaceState
修改URl的变化,路径中没有#;通过window.onpopstate
监听;
使用Link
和useNavigate
更改路由地址;
五十、对 Redux 的理解,主要解决什么问题
是一个用于javascript
的应用的状态管理库,主要是解决了复杂单页面应用开发中状态管理非常困难的问题,Redux
通过提供一个集中式的状态管理,将整个应用的状态保存在一个地方,便于管理和维护。
Redux
的核心概念和设计理念
1、单一数据源:所有数据都存储在单一的store
中
2、状态是只读的:唯一可以改变状态的方法是通过发送一个action,意味着每次操作都是可记录和追踪的
3、使用纯函数来执行状态的变化:通过reducers
纯函数来描述如何根据action
更新状态,确保状态更新的可预测性。
主要解决的问题:
集中式管理状态:集中的管理所有状态,便于管理与维护
状态可预测性:状态更改需要action
操作,就可以追溯所有的操作
方便的状态共享:所有组件都可以访问Redux
里面的状态
时间旅行调试:Redux
结合了不可变的数据和纯函数的概念,使得所有的操作都是可记录。
中间件的支持: 可以方便的扩展功能
五十一、Redux 原理及工作流程
Redux
的工作流程:
1、派发Action
:用户操作会派发一个action
,这个action
是一个描述事件的对象
2、处理Action
: Reducers
是一个纯函数没接收当前的state
和action
,返回新的state
3、更新Store
:store
接受到新的state
,更新其内部状态,所有组件都可以从store
获取到最新的状态。
一、三大核心要素
Store(单一数据源)
全局唯一的状态容器,存储整个应用的所有共享状态
通过 store.getState() 获取当前状态,通过 store.dispatch(action) 触发状态更新
示例结构:
{
user: { name: 'Alice' },
todos: ['Task 1', 'Task 2']
}
Action(状态变化的描述)
一个普通 JavaScript 对象,必须包含 type 字段描述操作类型
可选 payload 字段携带数据,例如:
{
type: 'ADD_TODO',
payload: 'Learn Redux'
}
Reducer(状态变化的纯函数)
接收当前 state 和 action,返回新状态(不可直接修改原状态)
示例:
const todoReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, action.payload];
default:
return state;
}
};
二、核心原则
单一数据源
整个应用状态仅存储在一个 Store 中,便于调试和管理
状态只读
唯一修改状态的方式是派发 Action,禁止直接修改 Store 数据
纯函数修改
Reducer 必须是纯函数(相同输入必定得到相同输出,无副作用)
五十二、Redux 中异步的请求怎么处理
可以使用中间件,比如redux-thunk
、redux-saga
五十三、Fiber架构
Fiber
是一种基于链表的数据结构,他将渲染过程拆分成多个小的任务单元。这种机制允许React在执行这些任务单元时,根据浏览器空闲的时间暂停、恢复和重新安排任务的执行顺序。这样当浏览器有需要紧急处理的交互或者更新时,React可以暂停当前的渲染任务,将控制权交给浏览器,等浏览器空闲的时候再继续执行渲染任务,从而避免长时间占用主线程,提高页面的响应性。
Fiber
解决的问题:
1、可中断和恢复的渲染:Fiber
允许渲染过程在需要时暂停和恢复,避免长时间占用主线程,提高页面的响应性。
2、优先级调度:Fiber
引入了优先级调度的概念,不同类型的更新可以被赋予不同的优先级。高优先级的更新如用户的交互,会被优先处理,确保用户交互能够及时响应,同时合理安排低优先级的任务的执行。
3、更细粒度的渲染控制:通过Fiber
,react可以对渲染过程进行更细粒度的控制,开发者可以根据具体需求设置不同的有优先级和调度策略。
Fiber
的优势:
可中断和恢复的机制:根据浏览器的空闲情况,可以暂停和恢复任务。
增量渲染:拆分任务为多个任务,根据任务的优先级等进行调度。
更好的用户体验:优先处理用户的交互事件
五十四、React错误处理?
try-catch
ErrorBoundary
class ErrorBoundary extends React.Component {
state = { hasError: false };
componentDidCatch(error, info) {
this.setState({ hasError: true });
logErrorToService(error, info.componentStack);
}
render() {
return this.state.hasError
? <FallbackUI />
: this.props.children;
}
}
<ErrorBoundary>
<UnstableComponent />
</ErrorBoundary>
Susponse
五十五、数据双向绑定的原理
1、React是通过props
和state
来实现状态和单项数据流的,当props
和state
改变都会触发页面的重新渲染,
2、受控的组件是根据state
和表单中的value
和onChange
事件来实现数据双向绑定的效果。
五十六、怎么动态导入组件,按需加载,代码分割?
React.lazy
动态加载组件,并且配合Suspense
来动态展示组件的内容。
也可以在React router
中使用React.lazy
和Suspense
来处理。
代码分割则可以使用Webpack
来把应用分解为更小、更易管理的块。
一、动态导入基础方案
使用 React.lazy + Suspense
适用于组件级懒加载,需配合 fallback 占位
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
动态导入函数模块
通过 import() 语法实现非组件代码分割
import('./math').then(math => {
console.log(math.add(2, 3));
});
二、路由级按需加载
React Router 集成
结合路由配置实现页面级懒加载
const Home = React.lazy(() => import('./routes/Home'));
const About = React.lazy(() => import('./routes/About'));
function RouterConfig() {
return (
<Routes>
<Route path="/" element={<Suspense fallback={...}><Home /></Suspense>} />
<Route path="/about" element={<Suspense fallback={...}><About /></Suspense>} />
</Routes>
);
}
代码分割:
SplitChunks 配置
分离第三方库和公共代码
// webpack.config.js
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10
}
}
}
}
五十七、React的状态提升是什么?使用场景有哪些?
就是通过在父组件中保存状态和修改状态的方法,通过props
把状态值和更改状态的方法一起传给子组件,子组件然后再调用父组件的的方法,从而修改负组件中状态,并共享给其他组件,这个就是状态提升。
场景:
表单联动
全局状态管理
五十八、对 React 和 Vue 的理解,它们的异同
相同点:
组件化开发
响应式数据绑定
虚拟DOM
不同点:
创建方式:JSX HTML
数据流 单向数据流 数据双向绑定
五十九、对React SSR的理解
服务端渲染时把数据和模板组成的HTML一起返回。
优点:
对SEO友好
所有模板图片资源都存储到服务器
一个html返回所有的数据
减少了http请求
响应快、用户体验好
缺点:
服务器压力大
生命周期只会执行到componentDidMount
之前
六十、react-redux
中的connect怎么实现?
connect 是 react-redux 的核心 API,用于将 React 组件与 Redux store 连接起来。它的实现主要基于高阶组件(HOC)模式,通过订阅 store 的状态变化并映射到组件的 props
从 Context 获取 Store
connect 通过 React 的 context 获取由 Provider 注入的 Redux store。
映射 State 和 Dispatch
mapStateToProps(state, ownProps?): 将 store 的 state 映射到组件的 props。
mapDispatchToProps(dispatch, ownProps?): 将 action creators 绑定到 dispatch,并作为 props 传递给组件。
合并 Props
将 mapStateToProps、mapDispatchToProps 和组件自身的 ownProps 合并为最终传递给子组件的 props。
订阅 Store 变化
在组件的生命周期(如 componentDidMount)中订阅 store 的变化,并在状态更新时触发重新渲染。
返回高阶组件
connect 返回一个函数,该函数接收原始组件并返回一个包装后的新组件
function connect(mapStateToProps, mapDispatchToProps) {
return function(WrappedComponent) {
return class ConnectedComponent extends React.Component {
static contextTypes = {
store: PropTypes.object
};
componentDidMount() {
this.unsubscribe = this.context.store.subscribe(() => {
this.forceUpdate();
});
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { store } = this.context;
const stateProps = mapStateToProps(store.getState(), this.props);
const dispatchProps = mapDispatchToProps(store.dispatch, this.props);
return (
<WrappedComponent
{...this.props}
{...stateProps}
{...dispatchProps}
/>
);
}
};
};
}
六十一、diff
的时间复杂度是多少,为什么?
传统Diff算法因全局对比和复杂编辑操作导致O(n³)复杂度,而React、Vue等框架通过层级限制与节点复用策略将复杂度降至O(n),同时兼顾性能与开发效率