个人站长网站应该如何定位,建设银行总行信息网站,怎么查域名的注册人,民治网站建设react权威面试题 1.jsx转化过程2.fiber架构的理解#xff0c;解决了什么问题#xff1f;理解fiber是什么 3.react diff原理tree diffcomponent diffelement diff 4.如何提高组件渲染效率shouldComponentUpdatePureComponentReact.memo 5.react中render方法原理#xff0c;触… react权威面试题 1.jsx转化过程2.fiber架构的理解解决了什么问题理解fiber是什么 3.react diff原理tree diffcomponent diffelement diff 4.如何提高组件渲染效率shouldComponentUpdatePureComponentReact.memo 5.react中render方法原理触发方式6.说说你对immutable的理解如何应用在react项目中是什么如何用 7.说说React Router有几种模式实现原理使用原理具体实现 8.你在React项目中是如何使用Redux的? 项目结构是如何划分的ProviderconnectionmapStateToPropsmapDispatchToProps 9.说说对Redux中间件的理解常用的中间件有哪些实现原理中间件是什么常用的中间件 10.说说你对Redux的理解其工作原理是什么 11.说说对React Hooks的理解解决了什么问题12.说说对高阶组件的理解应用场景?13.说说对受控组件和非受控组件的理解应用场景受控组件Controlled Components受控组件的应用场景非受控组件Uncontrolled Components非受控组件的应用场景 14.什么是 redux15.说说对React refs 的理解应用场景如何使用传入字符串传入对象传入函数传入hook应用场景 16.React中组件之间如何通信父组件向子组件传递子组件向父组件传递)子组件向父组件传递兄弟组件之间的通信父组件向后代组件传递)父组件向后代组件传递 17.说说React的事件机制18.说说 Real DOM 和 Virtual DOM 的区别优缺点19.说说 React 生命周期有哪些不同阶段每个阶段对应的方法是创建阶段更新阶段卸载阶段 componentWillUnmount 20.说说对 React 的理解有哪些特性特性 21.React 和 Vue 在技术层面有哪些区别22.useRef / ref / forwardsRef 的区别是什么?23.React 中的 ref 有什么用24.react18新特性25.使用 React hooks 怎么实现类里面的所有生命周期26.React.memo() 和 useMemo() 的用法是什么有哪些区别什么是 React.memo()什么是 useMemo()总结React.memo() 和 useMemo() 的主要区别 27.说说你对 useReducer 的理解28.说说你对 React Hook的闭包陷阱的理解有哪些解决方案29.我们应该在什么场景下使用 useMemo 和 useCallback 30.setState 是同步还是异步的react18之前。react18之后。 31.说说你对自定义hook的理解32.说说你对 useMemo 的理解33.说说你对 useContext 的理解使用 34.为什么不能在循环、条件或嵌套函数中调用 Hooks35.useEffect 与 useLayoutEffect 有什么区别共同点不同点 36.为什么 useState 返回的是数组而不是对象37.Redux中的connect有什么作用获取state包装原组件监听store tree变化 38.Redux 状态管理器和变量挂载到 window 中有什么区别39.Redux 中异步的请求怎么处理使用react-thunk中间件redux-thunk优点:redux-thunk缺陷: 使用redux-saga中间件redux-saga优点:redux-saga缺陷: 40.React构建组件的方式有哪些有什么区别一、是什么二、如何构建函数式创建通过 React.createClass 方法创建继承 React.Component 创建区别 41.说说你在React项目是如何捕获错误的42.react中懒加载的实现原理是什么使用 React.lazy 43.React有哪些性能优化的方法44.React Fiber 是如何实现更新过程可控任务拆分任务挂起、恢复、终止挂起恢复终止 任务具备优先级 45.不同版本的 React 都做过哪些优化React 15 架构React 16 架构React 17 优化 46.虚拟DOM一定更快吗虚拟DOMdomDiff虚拟DOM不一定更快 47.为什么不能用数组下标来作为react组件中的key48.React Hooks当中的useEffect是如何区分生命周期钩子的49.使用React Hooks有什么优势50.React Hooks带来了什么便利51.React中的VM 一定会提高性能吗52.为什么React的 VM 可以提高性能53.react 的虚拟dom是怎么实现的54.在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState 会发生什么55.setstate后发生了什么56.React中为什么要给组件设置 key57.React 的事件代理机制和原生事件绑定有什么区别58.React 的事件代理机制和原生事件绑定混用会有什么问题59.为什么不能直接使用 this.state 改变数据60.什么是高阶组件61.React中的类组件和函数组件之间有什么区别类组件Class components函数组件functional component区别 62.什么是虚拟DOM63.react新增的生命周期 1.jsx转化过程
使用React.createElement或JSX编写React组件实际上所有的 JSX 代码最后都会转换成React.createElement(…) Babel帮助我们完成了这个转换的过程。createElement函数对key和ref等特殊的props进行处理并获取defaultProps对默认props进行赋值并且对传入的孩子节点进行处理最终构造成一个虚拟DOM对象ReactDOM.render将生成好的虚拟DOM渲染到指定容器上其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化最终转换为真实DOM
2.fiber架构的理解解决了什么问题
理解
JavaScript引擎和页面渲染引擎两个线程是互斥的当其中一个线程执行时另一个线程只能挂起等待
如果 JavaScript 线程长时间地占用了主线程那么渲染层面的更新就不得不长时间地等待界面长时间不更新会导致页面响应度变差用户可能会感觉到卡顿
fiber是什么
React Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化是对 React 核心算法的一次重新实现。从Facebook在 React Conf 2017 会议上确认React Fiber 在React 16 版本发布
在react中主要做了以下的操作
为每个增加了优先级优先级高的任务可以中断低优先级的任务。然后再重新注意是重新执行优先级低的任务增加了异步任务调用requestIdleCallback api浏览器空闲的时候执行dom diff树变成了链表一个dom对应两个fiber一个链表对应两个队列这都是为找到被中断的任务重新执行
3.react diff原理
diff 算法主要基于三个规律
DOM 节点的跨层级移动的操作特别少可以忽略不计拥有相同类的两个组件将会生成相似的树形结构拥有不同类的两个组件将会生成不同的树形结构对于同一层级的一组子节点可以通过唯一的 id 进行区分
tree diff
因为上面的三个策略中的第一点 DOM 节点的跨级操作比较少那么 diff 算法只会对相同层级的 DOM 节点进行比较。如果发现节点不存在 那么会将该节点以及其子节点完全删除不会再继续比较。如果出现了 DOM 节点的跨层级的移动操作那么会删除改节点以及其所有的子节点然后再移动后的位置重新创建。
component diff
如果是同一类型的组件那么会继续对比 VM 数
如果不是同一类型的组件那么会将其和其子节点完全替换不会再进行比对
同一类型的组件有可能 VM 没有任何的变化如果可以确定的知道这点那么就可以节省大量的 diff 时间所以用户可以设置 shouldComponentUpdate() 来判断是否需要进行 diff 算法。
element diff
当节点处于同一层级的时候时有三种操作INSERT_MAKEUP插入、 MOVE_EXISTING 移动、 REMOVE_NODE 删除
这里 React 有一个优化策略对于同一层级的同组子节点添加唯一的 key 进行区分。这样的话就可以判断出来是否是移动节点。通过 key 发现新旧集合中的节点都是相同的节点就只需要进行移动操作就可以。
4.如何提高组件渲染效率
shouldComponentUpdatePureComponentReact.memo
shouldComponentUpdate
通过shouldComponentUpdate生命周期函数来比对 state和 props确定是否要重新渲染
默认情况下返回true表示重新渲染如果不希望组件重新渲染返回 false 即可
PureComponent
跟shouldComponentUpdate原理基本一致通过对 props 和 state的浅比较结果来实现
React.memo
React.memo用来缓存组件的渲染避免不必要的更新其实也是一个高阶组件与 PureComponent 十分类似。但不同的是 React.memo 只能用于函数组件
5.react中render方法原理触发方式
首先render函数在react中有两种形式
在类组件中指的是render方法
class Foo extends React.Component {render() {return h1 Foo /h1;}
}在函数组件中指的是函数组件本身
function Foo() {return h1 Foo /h1;
}触发方式 类组件调用 setState 修改状态 函数组件通过useState hook修改状态 类组件重新渲染 函数组件重新渲染
总结
render函数里面可以编写JSX转化成createElement这种形式用于生成虚拟DOM最终转化成真实DOM
在React 中类组件只要执行了 setState 方法就一定会触发 render 函数执行函数组件使用useState更改状态不一定导致重新render
组件的props 改变了不一定触发 render 函数的执行但是如果 props 的值来自于父组件或者祖先组件的 state
在这种情况下父组件或者祖先组件的 state 发生了改变就会导致子组件的重新渲染
所以一旦执行了setState就会执行render方法useState 会判断当前值有无发生改变确定是否执行render方法一旦父组件发生渲染子组件也会渲染
6.说说你对immutable的理解如何应用在react项目中
是什么
Immutable不可改变的在计算机中即指一旦创建就不能再被更改的数据
对 Immutable对象的任何修改或添加删除操作都会返回一个新的 Immutable对象
Immutable 实现的原理是 Persistent Data Structure持久化数据结构:
用一种数据结构来保存数据当数据被修改时会返回一个对象但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费
也就是使用旧数据创建新数据时要保证旧数据同时可用且不变同时为了避免 deepCopy把所有节点都复制一遍带来的性能损耗Immutable 使用了 Structural Sharing结构共享
如果对象树中一个节点发生变化只修改这个节点和受它影响的父节点其它节点则进行共享
如何用
使用 Immutable可以给 React 应用带来性能的优化主要体现在减少渲染的次数
在做react性能优化的时候为了避免重复渲染我们会在shouldComponentUpdate()中做对比当返回true执行render方法
Immutable通过is方法则可以完成对比而无需像一样通过深度比较的方式比较
在使用redux过程中也可以结合Immutable不使用Immutable前修改一个数据需要做一个深拷贝
7.说说React Router有几种模式实现原理
在单页应用中一个web项目只有一个html页面一旦页面加载完成之后就不用因为用户的操作而进行页面的重新加载或者跳转其特性如下
改变 url 且不让浏览器像服务器发送请求在不刷新页面的前提下动态改变浏览器地址栏中的URL地址
其中主要分成了两种模式
hash 模式在url后面加上#如http://127.0.0.1:5500/home/#/page1history 模式允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录
使用
React Router对应的hash模式和history模式对应的组件为
HashRouterBrowserRouter
这两个组件的使用都十分的简单作为最顶层组件包裹其他组件如下所示
// 1.import { BrowserRouter as Router } from react-router-dom;
// 2.import { HashRouter as Router } from react-router-dom;import React from react;
import {BrowserRouter as Router,// HashRouter as Router Switch,Route,
} from react-router-dom;
import Home from ./pages/Home;
import Login from ./pages/Login;
import Backend from ./pages/Backend;
import Admin from ./pages/Admin;function App() {return (RouterRoute path/login component{Login}/Route path/backend component{Backend}/Route path/admin component{Admin}/Route path/ component{Home}//Router);
}export default App;原理
路由描述了 URL 与 UI之间的映射关系这种映射是单向的即 URL 变化引起 UI 更新无需刷新页面
下面以hash模式为例子改变hash值并不会导致浏览器向服务器发送请求浏览器不发出请求也就不会刷新页面
hash 值改变触发全局 window 对象上的 hashchange 事件。所以 hash 模式路由就是利用 hashchange 事件监听 URL 的变化从而进行 DOM 操作来模拟页面跳转
react-router也是基于这个特性实现路由的跳转
具体实现
HashRouter包裹了整应用
通过window.addEventListener(hashChange,callback)监听hash值的变化并传递给其嵌套的组件
然后通过context将location数据往后代组件传递 Router组件主要做的是通过BrowserRouter传过来的当前值通过props传进来的path与context传进来的pathname进行匹配然后决定是否执行渲染组件
8.你在React项目中是如何使用Redux的? 项目结构是如何划分的
我们了解到redux是用于数据状态管理而react是一个视图层面的库
如果将两者连接在一起可以使用官方推荐react-redux库其具有高效且灵活的特性
react-redux将组件分成
容器组件存在逻辑处理UI 组件只负责现显示和交互内部不处理逻辑状态由外部控制
通过redux将整个应用状态存储到store中组件可以派发dispatch行为action给store
其他组件通过订阅store中的状态state来更新自身的视图
使用react-redux分成了两大核心
Providerconnection
Provider
在redux中存在一个store用于存储state如果将这个store存放在顶层元素中其他组件都被包裹在顶层元素之上
那么所有的组件都能够受到redux的控制都能够获取到redux中的数据
使用方式如下
Provider store {store}App /
Providerconnection
connect方法将store上的getState和 dispatch包装成组件的props
导入conect如下
import { connect } from react-redux;用法如下
connect(mapStateToProps, mapDispatchToProps)(MyComponent)可以传递两个参数
mapStateToPropsmapDispatchToProps
mapStateToProps
把redux中的数据映射到react中的props中去
如下
const mapStateToProps (state) {return {// prop : state.xxx | 意思是将state中的某个数据映射到props中foo: state.bar}
}组件内部就能够通过props获取到store中的数据
class Foo extends Component {constructor(props){super(props);}render(){return(// 这样子渲染的其实就是state.bar的数据了divthis.props.foo/div)}
}
Foo connect()(Foo)
export default FoomapDispatchToProps
将redux中的dispatch映射到组件内部的props中
const mapDispatchToProps (dispatch) { // 默认传递参数就是dispatchreturn {onClick: () {dispatch({type: increatment});}};
}class Foo extends Component {constructor(props){super(props);}render(){return(button onClick {this.props.onClick}点击increase/button)}
}
Foo connect()(Foo);
export default Foo;9.说说对Redux中间件的理解常用的中间件有哪些实现原理
中间件是什么
中间件Middleware是介于应用系统和系统软件之间的一类软件它使用系统软件所提供的基础服务功能衔接网络上应用系统的各个部分或不同的应用能够达到资源共享、功能共享的目的
常用的中间件
有很多优秀的redux中间件如
redux-thunk用于异步操作redux-logger用于日志记录
上述的中间件都需要通过applyMiddlewares进行注册作用是将所有的中间件组成一个数组依次执行
然后作为第二个参数传入到createStore中
const store createStore(reducer,applyMiddleware(thunk, logger)
);redux-thunk
redux-thunk是官网推荐的异步处理中间件
默认情况下的dispatch(action)action需要是一个JavaScript的对象
redux-thunk中间件会判断你当前传进来的数据类型如果是一个函数将会给函数传入参数值dispatchgetState
dispatch函数用于我们之后再次派发actiongetState函数考虑到我们之后的一些操作需要依赖原来的状态用于让我们可以获取之前的一些状态
所以dispatch可以写成下述函数的形式
const getHomeMultidataAction () {return (dispatch) {axios.get(http://xxx.xx.xx.xx/test).then(res {const data res.data.data;dispatch(changeBannersAction(data.banner.list));dispatch(changeRecommendsAction(data.recommend.list));})}
}redux-logger
如果想要实现一个日志功能则可以使用现成的redux-logger
import { applyMiddleware, createStore } from redux;
import createLogger from redux-logger;
const logger createLogger();const store createStore(reducer,applyMiddleware(logger)
);10.说说你对Redux的理解其工作原理
是什么
React是用于构建用户界面的帮助我们解决渲染DOM的过程
而在整个应用中会存在很多个组件每个组件的state是由自身进行管理包括组件定义自身的state、组件之间的通信通过props传递、使用Context实现数据共享
如果让每个组件都存储自身相关的状态理论上来讲不会影响应用的运行但在开发及后续维护阶段我们将花费大量精力去查询状态的变化过程
这种情况下如果将所有的状态进行集中管理当需要更新状态的时候仅需要对这个管理集中处理而不用去关心状态是如何分发到每一个组件内部的
redux就是一个实现上述集中管理的容器遵循三大基本原则
单一数据源state 是只读的使用纯函数来执行修改
注意的是redux并不是只应用在react中还与其他界面库一起使用如Vue
11.说说对React Hooks的理解解决了什么问题
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性 useState useEffect useReducer useCallback useMemo useRef
通过对上面的初步认识可以看到hooks能够更容易解决状态相关的重用的问题
每调用useHook一次都会生成一份独立的状态通过自定义hook能够更好的封装我们的功能
编写hooks为函数式编程每个功能都包裹在函数中整体风格更清爽更优雅
hooks的出现使函数组件的功能得到了扩充拥有了类组件相似的功能在我们日常使用中使用hooks能够解决大多数问题并且还拥有代码复用机制因此优先考虑hooks
12.说说对高阶组件的理解应用场景?
高阶函数Higher-order function至少满足下列一个条件的函数
接受一个或多个函数作为输入输出一个函数
在React中高阶组件即接受一个或多个组件作为参数并且返回一个组件本质也就是一个函数并不是一个组件
const EnhancedComponent highOrderComponent(WrappedComponent);上述代码中该函数接受一个组件WrappedComponent作为参数返回加工过的新组件EnhancedComponent
13.说说对受控组件和非受控组件的理解应用场景
受控组件Controlled Components
受控组件是指其值由组件本身的state或props进行控制和管理的组件。组件的值存储在state或props中并通过事件处理函数来更新。当用户与受控组件进行交互时组件的值会发生改变并且通常由父组件的state来管理这个变化过程。
受控组件的应用场景
表单元素在表单中使用输入框、复选框、下拉菜单等组件时可以利用受控组件的特性来实现实时输入验证、数据收集等功能。多个组件之间需要共享和同步状态的场景。需要对用户输入进行实时处理和验证的场景。
非受控组件Uncontrolled Components
非受控组件是指其值由DOM本身进行管理的组件组件的值存储在DOM节点上并通过ref来获取其值。在非受控组件中React不管理组件的值而是让DOM自行处理。
非受控组件的应用场景
获取表单数据但不需要对其进行实时验证或处理的场景。访问和修改DOM元素的值而无需通过组件状态管理。
14.什么是 redux
redux 是一个应用数据流框架主要是解决了组件间状态共享的问题原理是集中式管理主要有三个核心方法actionstorereducer工作流程是 view 调用 store 的 dispatch 接收 action 传入 storereducer 进行 state 操作view 通过 store 提供的 getState 获取最新的数据flux 也是用来进行数据操作的有四个组成部分 actiondispatchviewstore工作流程是 view 发出一个 action派发器接收 action让 store 进行数据更新更新完成以后 store 发出 changeview 接受 change 更新视图。Redux 和 Flux 很像。主要区别在于 Flux 有多个可以改变应用状态的 store在 Flux 中 dispatcher 被用来传递数据到注册的回调事件但是在 redux 中只能定义一个可更新状态的 storeredux 把 store 和 Dispatcher 合并,结构更加简单清晰 新增 state,对状态的管理更加明确通过 redux流程更加规范了减少手动编码量提高了编码效率同时缺点时当数据更新时有时候组件不需要但是也要重新绘制有些影响效率。一般情况下我们在构建多交互多数据流的复杂项目应用时才会使用它们
15.说说对React refs 的理解应用场景
React 中的 Refs提供了一种方式允许我们访问 DOM节点或在 render方法中创建的 React元素
本质为ReactDOM.render()返回的组件实例如果是渲染组件则返回的是组件实例如果渲染dom则返回的是具体的dom节点
如何使用
创建ref的形式有三种
传入字符串使用时通过 this.refs.传入的字符串的格式获取对应的元素传入对象对象是通过 React.createRef() 方式创建出来使用时获取到创建的对象中存在 current 属性就是对应的元素传入函数该函数会在 DOM 被挂载时进行回调这个函数会传入一个 元素对象可以自己保存使用时直接拿到之前保存的元素对象即可传入hookhook是通过 useRef() 方式创建使用时通过生成hook对象的 current 属性就是对应的元素
传入字符串
只需要在对应元素或组件中ref属性
class MyComponent extends React.Component {constructor(props) {super(props);this.myRef React.createRef();}render() {return div refmyref /;}
}访问当前节点的方式如下
this.refs.myref.innerHTML hello;传入对象
refs通过React.createRef()创建然后将ref属性添加到React元素中如下
class MyComponent extends React.Component {constructor(props) {super(props);this.myRef React.createRef();}render() {return div ref{this.myRef} /;}
}当 ref 被传递给 render 中的元素时对该节点的引用可以在 ref 的 current 属性中访问
const node this.myRef.current;传入函数
当ref传入为一个函数的时候在渲染过程中回调函数参数会传入一个元素对象然后通过实例将对象进行保存
class MyComponent extends React.Component {constructor(props) {super(props);this.myRef React.createRef();}render() {return div ref{element this.myref element} /;}
}获取ref对象只需要通过先前存储的对象即可
const node this.myref 传入hook
通过useRef创建一个ref整体使用方式与React.createRef一致
function App(props) {const myref useRef()return (div ref{myref}/div/)
}获取ref属性也是通过hook对象的current属性
const node myref.current;上述三种情况都是ref属性用于原生HTML元素上如果ref设置的组件为一个类组件的时候ref对象接收到的是组件的挂载实例
注意的是不能在函数组件上使用ref属性因为他们并没有实例
应用场景
对Dom元素的焦点控制、内容选择、控制对Dom元素的内容设置及媒体播放对Dom元素的操作和对组件实例的操作集成第三方 DOM 库
16.React中组件之间如何通信
组件传递的方式有很多种根据传送者和接收者可以分为如下
父组件向子组件传递子组件向父组件传递兄弟组件之间的通信父组件向后代组件传递非关系组件传递
父组件向子组件传递
由于React的数据流动为单向的父组件向子组件传递是最常见的方式
父组件在调用子组件的时候只需要在子组件标签内传递参数子组件通过props属性就能接收父组件传递过来的参数
function EmailInput(props) {return (labelEmail: input value{props.email} //label);
}const element EmailInput email123124132163.com /;子组件向父组件传递)子组件向父组件传递
子组件向父组件通信的基本思路是父组件向子组件传一个函数然后通过这个函数的回调拿到子组件传过来的值
父组件对应代码如下
class Parents extends Component {constructor() {super();this.state {price: 0};}getItemPrice(e) {this.setState({price: e});}render() {return (divdivprice: {this.state.price}/div{/* 向子组件中传入一个函数 */}Child getPrice{this.getItemPrice.bind(this)} //div);}
}子组件对应代码如下
class Child extends Component {clickGoods(e) {// 在此函数中传入值this.props.getPrice(e);}render() {return (divbutton onClick{this.clickGoods.bind(this, 100)}goods1/buttonbutton onClick{this.clickGoods.bind(this, 1000)}goods2/button/div);}
}兄弟组件之间的通信
如果是兄弟组件之间的传递则父组件作为中间层来实现数据的互通通过使用父组件传递
class Parent extends React.Component {constructor(props) {super(props)this.state {count: 0}}setCount () {this.setState({count: this.state.count 1})}render() {return (divSiblingAcount{this.state.count}/SiblingBonClick{this.setCount}//div);}
}父组件向后代组件传递)父组件向后代组件传递
父组件向后代组件传递数据是一件最普通的事情就像全局数据一样
使用context提供了组件之间通讯的一种方式可以共享数据其他数据都能读取对应的数据
通过使用React.createContext创建一个context const PriceContext React.createContext(price)context创建成功后其下存在Provider组件用于创建数据源Consumer组件用于接收数据使用实例如下
Provider组件通过value属性用于给后代组件传递数据
PriceContext.Provider value{100}
/PriceContext.Provider如果想要获取Provider传递的数据可以通过Consumer组件或者或者使用contextType属性接收对应分别如下
class MyClass extends React.Component {static contextType PriceContext;render() {let price this.context;/* 基于这个值进行渲染工作 */}
}Consumer组件
PriceContext.Consumer{ /*这里是一个函数*/ }{price divprice{price}/div}
/PriceContext.Consumer17.说说React的事件机制
React基于浏览器的事件机制自身实现了一套事件机制包括事件注册、事件的合成、事件冒泡、事件派发等
React 所有事件都挂载在 document 对象上当真实 DOM 元素触发事件会冒泡到 document 对象后再处理 React 事件所以会先执行原生事件然后处理 React 事件最后真正执行 document 上挂载的事件
18.说说 Real DOM 和 Virtual DOM 的区别优缺点
Real DOM真实DOM和Virtual DOM虚拟DOM是在网页开发中常用的两种DOM表示方式。它们有一些区别和各自的优缺点。
Real DOM真实DOM Real DOM是指浏览器中实际存在的DOM树它是由HTML文档解析而来的包含了网页中所有的元素和其属性。当DOM发生改变时浏览器会重新渲染整个Real DOM树这是一种比较昂贵的操作。
优点
原生操作性可以直接操作Real DOM进行增删改查等操作。适合少量变化对于小规模的DOM变动Real DOM可以及时更新保证显示的准确性。
缺点
性能开销大操作Real DOM需要重新计算样式、布局和绘制性能开销较大特别是在大规模或频繁的DOM操作情况下。不灵活频繁的操作Real DOM可能导致页面卡顿和用户体验不佳。
Virtual DOM虚拟DOM Virtual DOM是在内存中构建的一层抽象它是Real DOM的轻量级副本。当数据发生变化时React会使用虚拟DOM进行对比并计算出最小的变更然后将这些变更操作应用到Real DOM上减少了对Real DOM的直接操作次数。
优点
效率提升Virtual DOM可以对比变更只计算最小化的操作然后一次性更新到Real DOM上减少了真实DOM操作的次数。灵活性Virtual DOM可以在JavaScript中进行操作对一些复杂逻辑的操作更加方便。跨平台Virtual DOM并不局限于浏览器环境可以在不同的平台上使用。
缺点
额外内存消耗需要在内存中构建和维护虚拟DOM树占用一定的内存资源。学习成本使用虚拟DOM需要学习额外的语法和概念。
总的来说Real DOM适合于小规模的DOM变动并可以直接操作DOM进行增删改查。而Virtual DOM通过优化更新操作提高了性能和灵活性尤其适用于大规模或频繁的DOM变动场景。React使用Virtual DOM通过智能的比较和差异计算使得重新渲染的成本减少提高了性能和开发效率。
19.说说 React 生命周期有哪些不同阶段每个阶段对应的方法是
创建阶段
创建阶段主要分成了以下几个生命周期方法
constructorgetDerivedStateFromPropsrendercomponentDidMount
更新阶段
该阶段的函数主要为如下方法
getDerivedStateFromPropsshouldComponentUpdaterendergetSnapshotBeforeUpdatecomponentDidUpdate
卸载阶段
componentWillUnmount
20.说说对 React 的理解有哪些特性
React用于构建用户界面的 JavaScript 库只提供了 UI 层面的解决方案
遵循组件设计模式、声明式编程范式和函数式编程概念以使前端应用程序更高效
使用虚拟 DOM 来有效地操作 DOM遵循从高阶组件到低阶组件的单向数据流
特性
React 特性有很多如
JSX 语法单向数据绑定虚拟 DOM声明式编程Component
21.React 和 Vue 在技术层面有哪些区别
React 和 Vue 是当前比较流行的前端框架它们在技术层面有以下区别
组件化方式不同React 是基于组件实现的组件包含了状态和行为所有组件共享一个状态树。Vue 也是基于组件实现的但是每个组件都有自己的状态并且可以很容易地将数据和行为绑定在一起。数据驱动方式不同React 使用单向数据流来管理数据即从父组件到子组件的传递所以 React 中组件之间的数据交互相对更加复杂。Vue 则使用双向数据绑定来管理数据使得组件之间的数据交互更加简洁。模板语法不同React 使用 JSX 语法将 HTML 和 JavaScript 结合在一起使得编写组件更加直观和灵活。Vue 则使用模板语法并且支持模板内的表达式和指令使得编写组件具有更高的可读性和可维护性。生命周期不同React 组件的生命周期分为三个阶段初始化、更新和卸载。Vue 组件的生命周期分为八个阶段创建、挂载、更新、销毁等。状态管理方式不同React 使用 Redux 或者 MobX 来管理应用程序的状态。Vue 则提供了自己的状态管理库 Vuex可以更方便地管理组件之间的共享状态。性能优化方式不同React 使用虚拟 DOM 技术来实现高效的渲染性能可以减少每次渲染时需要操作真实 DOM 的次数。Vue 则使用模板编译和响应式系统来实现高效的渲染性能并且还提供了一些优化技术例如懒加载和缓存等。
22.useRef / ref / forwardsRef 的区别是什么?
useRef 和 ref 都是 React 中用于操作 DOM 元素或自定义组件实例的工具而 forwardRef 则是用于访问嵌套子组件中的 DOM 元素或自定义组件实例。
它们之间的区别如下
useRef 是一个 hook 函数可以在函数组件中使用ref 是一个对象属性只能在类组件中使用。useRef 返回一个可变的 ref 对象可以在组件的整个生命周期内保持不变也就是说不会因为重新渲染而改变。而 ref 每次渲染都会被重新创建。useRef 主要用于存储和更新组件内部状态以及操作 DOM 元素。而 ref 主要用于获取 DOM 元素或自定义组件实例。forwardRef 是用于将 ref 属性“向下传递”给一个函数式子组件或自定义组件的工具函数。它允许父组件调用子组件中的 DOM 元素或自定义组件实例。
综上所述useRef 和 ref 都是用于操作 DOM 元素或自定义组件实例的工具与之相比forwardRef 则是一个更高级的工具用于处理专门的情况即访问嵌套子组件中的 DOM 元素或自定义组件实例。
23.React 中的 ref 有什么用
用 refs 获取。组件被调用时会新建一个该组件的实例。refs 会指向这个实例可以是一个回调函数回调函数会在组件被挂载后立即执行。
如果把 refs 放到原生 DOM 组件的 input 中我们就可以通过 refs 得到 DOM 节点如果把 refs 放到 React 组件中那么我们获得的就是组件的实例因此就可以调用实例的方法如果想访问该组件的真实 DOM那么可以用 React.findDOMNode 来找到 DOM 节点但是不推崇此方法。
refs 无法用于无状态组件无状态组件挂载时只是方法调用没有新建实例。在 v16 之后可以使用 useRef。
24.react18新特性
React 18是React框架的最新版本预计将引入以下一些新特性和改进
Concurrent Mode并发模式Concurrent Mode旨在提高React应用的性能和用户体验。它使用一种新的渲染调度器可以在多个优先级下进行渲染使得React应用更加响应性并且使得长任务不会阻塞主线程。Server Components服务器组件Server Components是一种全新的React组件类型它可以在服务器上渲染和运行可以实现基于React的全栈开发。Server Components可以通过Shadow DOM在客户端进行增量更新并且可以实现服务器端控制组件逻辑和状态。Automatic Batching自动批量更新React 18将引入自动批量更新机制即不再需要手动调用batch函数来进行批量更新而是由React自动处理。这样可以减少更新的次数提高渲染性能。New JSX Transform新的JSX转换React 18将引入一种新的JSX转换即React JSX Transform。这种转换方式可以将JSX代码转换为普通的JavaScript代码不再需要依赖Babel进行转换从而提高编译性能。ESLint Plugin ImprovementsESLint插件增强React 18将改进React ESLint插件使其更加智能和高效。插件将提供更准确的诊断和推荐以帮助开发者编写更好的React代码。
25.使用 React hooks 怎么实现类里面的所有生命周期
useState 只在初始化时执行一次后面不再执行useEffect 相当于是 componentDidMountcomponentDidUpdate 和 componentWillUnmount 这三个函数的组合可以通过传参及其他逻辑分别模拟这三个生命周期函数useEffect 第二个参数是一个数组如果数组为空时则只执行一次相当于componentDidMount如果数组中有值时则该值更新时useEffect 中的函数才会执行如果没有第二个参数则每次render时useEffect 中的函数都会执行React 保证了每次运行 effect 的同时DOM 都已经更新完毕也就是说 effect 中的获取的 state 是最新的但是需要注意的是effect 中返回的函数其清除函数中获取到的 state 是更新前的。传递给 useEffect 的函数在每次渲染中都会有所不同这是刻意为之的。事实上这正是我们可以在 effect 中获取最新的 count 的值而不用担心其过期的原因。每次我们重新渲染都会生成新的 effect替换掉之前的。某种意义上讲effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染。effect 的清除阶段返回函数在每次重新渲染时都会执行而不是只在卸载组件的时候执行一次。它会在调用一个新的 effect 之前对前一个 effect 进行清理从而避免了我们手动去处理一些逻辑 。为了说明这一点下面按时间列出一个可能会产生的订阅和取消订阅操作调用序列
26.React.memo() 和 useMemo() 的用法是什么有哪些区别
什么是 React.memo()
React.memo() 随 React v16.6 一起发布。 虽然类组件已经允许您使用 PureComponent 或 shouldComponentUpdate 来控制重新渲染但 React 16.6 引入了对函数组件执行相同操作的能力。
React.memo() 是一个高阶组件 (HOC)它接收一个组件A作为参数并返回一个组件B如果组件B的 props或其中的值没有改变则组件 B 会阻止组件 A 重新渲染 。
什么是 useMemo()
React.memo() 是一个 HOC而 useMemo() 是一个 React Hook。 使用 useMemo()我们可以返回记忆值来避免函数的依赖项没有改变的情况下重新渲染。
为了在我们的代码中使用 useMemo()React 开发者有一些建议给我们
您可以依赖 useMemo() 作为性能优化而不是语义保证函数内部引用的每个值也应该出现在依赖项数组中
总结React.memo() 和 useMemo() 的主要区别
从上面的例子中我们可以看到 React.memo() 和 useMemo() 之间的主要区别
React.memo() 是一个高阶组件我们可以使用它来包装我们不想重新渲染的组件除非其中的 props 发生变化useMemo() 是一个 React Hook我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算
27.说说你对 useReducer 的理解
useReducer 是 React hooks 中的一个函数用于管理具有复杂状态和逻辑的组件。它类似于 Redux 中的 reducer 函数可以通过调度不同的 action 来更新组件的状态。
使用 useReducer 需要传入一个 reducer 函数和初始状态作为参数。reducer 函数接收当前状态和 action 作为参数并返回新的状态。它根据 action 的类型来确定应该如何更新状态。
调用 useReducer 会返回一个数组包含当前状态和一个 dispatch 函数。dispatch 函数用于触发状态的更新将一个 action 对象传递给它然后由 reducer 函数处理并得出新的状态。
相比于 useStateuseReducer 更适用于需要管理复杂状态、有多个相关操作和逻辑的场景。它能够将相关的状态和操作逻辑封装在一起使代码更加清晰和可维护。
使用 useReducer 可以帮助我们遵循单一数据源原则将状态和变更逻辑统一管理减少了状态修改代码的分散性和耦合性。同时在组件层面可以更好地支持状态的共享、复用和组合。
总之useReducer 提供了一种在复杂状态管理和逻辑处理方面更灵活、可扩展的解决方案适用于构建中大型或复杂的交互式组件。
28.说说你对 React Hook的闭包陷阱的理解有哪些解决方案
React Hook 的闭包陷阱指的是在使用 React Hook 时由于闭包的特性可能会导致状态不正确地被捕获或访问。这可能会导致意外的行为和错误。
当在 React 组件中定义一个函数并在该函数内部使用了某个状态变量或其他 Hook但没有将其作为依赖项传递给 useEffect 或 useCallback 等 Hook就会遇到闭包陷阱。
解决闭包陷阱有以下几种常见的方案
将相关的状态变量或其他 Hook 作为依赖项传递在使用 useEffect、useCallback 等 Hook 时根据需要将所依赖的状态变量或其他 Hook 作为依赖项传递进去。这会确保 Hook 在依赖项发生变化时被更新。使用 useCallback 包裹函数组件如果在函数组件中定义了回调函数并且这些回调函数依赖于某个状态变量或其他 Hook可以使用 useCallback 包裹这些回调函数将其作为依赖项传递给 useEffect 或其他 Hook。使用 useRef 获取最新的状态值如果只需要获取最新的状态值而不关心其变化过程可以使用 useRef 来获取状态的引用并在需要的地方读取该引用。因为 useRef 创建的引用始终保持稳定不会触发组件重新渲染。使用 useReducer 替代 useState在某些情况下可以使用 useReducer 来替代 useState。useReducer 将状态和更新逻辑封装在 reducer 函数中并返回一个 dispatch 函数用于触发状态的更新。由于 dispatch 函数是稳定的不会在组件重新渲染时发生变化因此可以避免闭包陷阱的问题。使用自定义 Hook 进行状态管理如果存在更复杂的状态管理需求可以考虑使用自定义 Hook 来封装相关的状态和操作逻辑。自定义 Hook 可以通过将状态存储在局部变量中独立于组件的作用域从而避免了闭包陷阱。
29.我们应该在什么场景下使用 useMemo 和 useCallback
大部分的 useMemo 和 useCallback 都应该移除他们可能没有带来任何性能上的优化反而增加了程序首次渲染的负担并增加程序的复杂性。使用 useMemo 和 useCallback 优化子组件 re-render 时必须同时满足以下条件才有效 子组件已通过 React.memo 或 useMemo 被缓存子组件所有的 prop 都被缓存 不推荐默认给所有组件都使用缓存大量组件初始化时被缓存可能导致过多的内存消耗并影响程序初始化渲染的速度。
30.setState 是同步还是异步的
react18之前。
setState在不同情况下可以表现为异步或同步。
在Promise的状态更新、js原生事件、setTimeout、setInterval…中是同步的。
在react的合成事件中是异步的。 react18之后。
setState都会表现为异步即批处理。
31.说说你对自定义hook的理解
所谓的自定义Hook实际上就是把很多重复的逻辑都放在一个函数里面通过闭包的方式给return出来这是非常高级的方式程序员崇尚代码简洁如果说以后业务开发时需要大量的重复代码我们就可以将它封装成自定义Hook。
32.说说你对 useMemo 的理解
使用memo可以帮助我们优化性能让react没必要执行不必要的函数由于复杂数据类型的地址可能发生改变于是传递给子组件的props也会发生变化这样还是会执行不必要的函数所以就用到了useMemo这个apiuseCallback是useMemo的语法糖
33.说说你对 useContext 的理解
context上下文可以看成是扩大版的props它可以将全局的数据通过provider接口传递value给局部的组件让包围在provider中的局部组件可以获取到全局数据的读写接口
全局变量可以看成是全局的上下文
而上下文则是局部的全局变量因为只有包围在provider中的局部组件才可以获取到这些全局变量的读写接口
使用
使用creacteContext创建一个上下文设置provider并通过value接口传递state数据局部组件从value接口中传递的数据对象中获取读写接口
34.为什么不能在循环、条件或嵌套函数中调用 Hooks
不能在循环、条件或嵌套函数中调用 Hooks是因为 React 需要依赖 Hooks 在组件的每次渲染之间保持稳定的身份。以下是原因的解释
Hooks 只能在 React 函数组件的顶层调用Hooks 是一种需要遵循特定规则的特殊函数。React 依赖于 Hooks 的调用顺序和数量来确定每个组件实例的状态。如果在循环、条件语句或嵌套函数中使用 Hooks无法保证 Hooks 按照固定的顺序调用以及正确地与组件实例关联。循环、条件或嵌套函数会破坏 Hook 调用顺序在循环中使用 Hooks 会导致 Hook 的调用顺序不可预测可能会产生错误的状态更新。同样在条件语句或嵌套函数中使用 Hooks 也会破坏 Hook 的调用顺序造成意外行为。使用闭包可能导致无法预料的问题Hooks 是基于闭包的机制来捕获组件的状态并通过索引来跟踪状态。如果在循环、条件或嵌套函数中使用 Hooks并在闭包中捕获状态很可能会导致多个闭包共享了相同的状态从而导致状态难以预测和维护。
为了避免这些问题React 对 Hooks 的使用做了限制Hooks 只能在 React 函数组件的顶层调用确保在每次渲染中 Hooks 的调用顺序一致。这样可以保证组件状态的稳定性有效避免错误的行为和难以排查的问题。
35.useEffect 与 useLayoutEffect 有什么区别
共同点
运用效果 useEffect 与 useLayoutEffect 两者都是用于处理副作用这些副作用包括改变 DOM、设置订阅、操作定时器等。在函数组件内部操作副作用是不被允许的所以需要使用这两个函数去处理。使用方式 useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的都是调用的 mountEffectImpl方法在使用上也没什么差异基本可以直接替换。
不同点
使用场景 useEffect 在 React 的渲染过程中是被异步调用的用于绝大多数场景而 useLayoutEffect 会在所有的 DOM 变更之后同步调用主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞。使用效果 useEffect是按照顺序执行代码的改变屏幕像素之后执行先渲染后改变DOM当改变屏幕内容时可能会产生闪烁useLayoutEffect是改变屏幕像素之前就执行了会推迟页面显示的事件先改变DOM后渲染不会产生闪烁。useLayoutEffect总是比useEffect先执行。
36.为什么 useState 返回的是数组而不是对象
useState 的用法
const [count, setCount] useState(0)可以看到 useState 返回的是一个数组那么为什么是返回数组而不是返回对象呢
要回答这个问题得弄明白 ES6 的解构赋值(destructring assignment)语法 , 来看 2 个简单的示例
数组的解构赋值
const foo [one, two, three];const [red, yellow, green] foo;
console.log(red); // one
console.log(yellow); // two
console.log(green); // three对象的解构赋值
const user {id: 42,is_verified: true
};const { id, is_verified } user;console.log(id); // 42
console.log(is_verified); // true 搞清楚了解构赋值那上面的问题就比较好解释了。
如果 useState 返回数组那么你可以顺便对数组中的变量命名代码看起来也比较干净。而如果是对象的话返回的值必须和 useState 内部实现返回的对象同名这样你只能在 function component 中使用一次想要多次使用 useState 必须得重命名返回值。
// 第一次使用
const { state, setState } useState(false)
// 第二次使用
const { state: counter, setState: setCounter} useState(0)当然事情总是有两面性的使用 array 也存在一些问题
返回值强顺序灵活性比较低。array[0] 为值array[1] 为改变值的方法。返回的值基本都得使用对于有些返回值不想使用的话代码看起来有些怪比如只想用 setState, 就得这么写const [, setState] useState(false)。返回的参数不能太多否则处理上面 2 个场景会很麻烦。
如果在自定义的Hook中遇到了以上几个问题不妨试试返回 object。
简单总结一下在自定义 hook 的时候可以遵循一个简单原则当参数大于 2 个的时候返回值的类型返回 object 否则返回数组。
37.Redux中的connect有什么作用
connect负责连接React和Redux
获取state
connect 通过 context获取 Provider 中的 store通过 store.getState() 获取整个store tree 上所有state
包装原组件
将state和action通过props的方式传入到原组件内部 wrapWithConnect 返回—个 ReactComponent 对象 ConnectConnect重新 render 外部传入的原组件 WrappedComponent 并把 connect 中传入的 mapStateToPropsmapDispatchToProps与组件上原有的 props 合并后通过属性的方式传给 WrappedComponent
监听store tree变化
connect缓存了store tree中state的状态通过当前state状态 和变更前 state 状态进行比较从而确定是否调用 this.setState()方法触发 Connect 及其子组件的重新渲染
38.Redux 状态管理器和变量挂载到 window 中有什么区别
Redux是一种用于JavaScript应用程序的状态管理库而将变量挂载到window中是指将变量作为全局对象挂载到浏览器的window对象上。
下面是Redux状态管理器和将变量挂载到window中之间的区别 作用范围Redux的状态管理器是为应用程序内部提供一个集中式的状态管理机制。它通过创建一个全局的状态存储库来管理应用程序的状态并且可以在应用程序的各个组件中共享和访问这些状态。而将变量挂载到window中则是将变量作为全局变量在整个页面的任何地方都可以访问和使用。 数据封装Redux通过Reducer函数和Actions来管理数据的流动和变化。它遵循了一定的设计模式和规范将状态的变更操作封装成一个个独立的Action并通过Reducer函数对这些Action进行处理和更新状态。而将变量挂载到window中则没有这样的封装层级直接操作全局变量可能会导致数据不受控、难以追踪和调试。 组件通信Redux通过Provider和Connect等机制实现组件之间的状态共享和通信。通过使用Redux提供的高阶组件(Higher-Order Components)或React Hooks可以方便地将某个组件连接到Redux状态树实现数据的双向绑定和组件的更新。而将变量挂载到window中则无法直接实现这种组件级别的通信需要手动进行变量的读写和同步容易引发代码耦合性和维护性的问题。 状态管理策略Redux提供了一系列的状态管理策略如immutable state、单向数据流和纯函数等以确保应用程序状态的可预测性和可维护性。而直接将变量挂载到window中则缺乏这样的约束和策略可能导致不可预测的状态变化和副作用。
综上所述Redux提供了更加规范和可控的状态管理机制适用于中大型复杂应用程序。而将变量挂载到window中则是一种简单直接的方式适用于小型应用或快速原型开发。
39.Redux 中异步的请求怎么处理
一般的异步请求可以在 componentDidmount 中直接进⾏请求⽆须借助redux。
但是在⼀定规模的项⽬中,上述⽅法很难进⾏异步流的管理,通常情况下我们会借助redux的异步中间件进⾏异步处理。
redux异步流中间件其实有很多当下主流的异步中间件有两种redux-thunk、redux-saga。
使用react-thunk中间件
redux-thunk优点:
体积⼩: redux-thunk的实现⽅式很简单只有不到20⾏代码使⽤简单: redux-thunk没有引⼊像redux-saga或者redux-observable额外的范式上⼿简单
redux-thunk缺陷:
样板代码过多: 与redux本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的耦合严重: 异步操作与redux的action偶合在⼀起,不⽅便管理功能孱弱: 有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装
使用redux-saga中间件
redux-saga优点:
异步解耦: 异步操作被被转移到单独 saga.js 中不再是掺杂在 action.js 或 component.js 中action摆脱thunk function: dispatch 的参数依然是⼀个纯粹的 action (FSA)⽽不是充满 “⿊魔法” thunk function异常处理: 受益于 generator function 的 saga 实现代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理功能强⼤: redux-saga提供了⼤量的 Saga 辅助函数和 Effect 创建器供开发者使⽤,开发者⽆须封装或者简单封装即可使⽤灵活: redux-saga可以将多个Saga可以串⾏/并⾏组合起来,形成⼀个⾮常实⽤的异步flow易测试提供了各种case的测试⽅案包括mock task分⽀覆盖等等
redux-saga缺陷:
额外的学习成本: redux-saga不仅在使⽤难以理解的 generator function⽽且有数⼗个API学习成本远超redux-thunk。最重要的是你的额外学习成本是只服务于这个库的与redux-observable不同redux-observable虽然也有额外学习成本但是背后是rxjs和⼀整套思想体积庞⼤: 体积略⼤,代码近2000⾏min版25KB左右功能过剩: 实际上并发控制等功能很难⽤到但是我们依然需要引⼊这些代码ts⽀持不友好: yield⽆法返回TS类型
redux-saga可以捕获action然后执行一个函数那么可以把异步代码放在这个函数中。
40.React构建组件的方式有哪些有什么区别
一、是什么
组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念组件来实现开发的模式
在React中一个类、一个函数都可以视为一个组件
组件所存在的优势
降低整个系统的耦合度在保持接口不变的情况下我们可以替换不同的组件快速完成需求例如输入框可以替换为日历、时间、范围等组件作具体的实现调试方便由于整个系统是通过组件组合起来的在出现问题的时候可以用排除法直接移除组件或者根据报错的组件快速定位问题之所以能够快速定位是因为每个组件之间低耦合职责单一所以逻辑会比分析整个系统要简单提高可维护性由于每个组件的职责单一并且组件在系统中是被复用的所以对代码进行优化可获得系统的整体升级
二、如何构建
在React目前来讲组件的创建主要分成了三种方式
函数式创建通过 React.createClass 方法创建继承 React.Component 创建
函数式创建
在React Hooks出来之前函数式组件可以视为无状态组件只负责根据传入的props来展示视图不涉及对state状态的操作
大多数组件可以写为无状态组件通过简单组合构建其他组件
通过 React.createClass 方法创建
React.createClass是react刚开始推荐的创建组件的方式目前这种创建方式已经不怎么用了
像上述通过函数式创建的组件的方式最终会通过babel转化成React.createClass这种形式
继承 React.Component 创建
同样在react hooks出来之前有状态的组件只能通过继承React.Component这种形式进行创建
有状态的组件也就是组件内部存在维护的数据在类创建的方式中通过this.state进行访问
当调用this.setState修改组件的状态时组价会再次会调用render()方法进行重新渲染
通过继承React.Component创建一个时钟
区别
由于React.createClass 创建的方式过于冗杂并不建议使用
而像函数式创建和类组件创建的区别主要在于需要创建的组件是否需要为有状态组件
对于一些无状态的组件创建建议使用函数式创建的方式由于react hooks的出现函数式组件创建的组件通过使用hooks方法也能使之成为有状态组件再加上目前推崇函数式编程所以这里建议都使用函数式的方式来创建组件
在考虑组件的选择原则上能用无状态组件则用无状态组件
41.说说你在React项目是如何捕获错误的
使用了 static getDerivedStateFromError()使用了 componentDidCatch()
下面这些情况无法捕获到异常
事件处理异步代码服务端渲染自身抛出来的错误
在react 16版本之后会把渲染期间发生的所有错误打印到控制台
除了错误信息和 JavaScript 栈外React 16 还提供了组件栈追踪。现在你可以准确地查看发生在组件树内的错误信息
42.react中懒加载的实现原理是什么
使用 React.lazy
在实际的使用中首先是引入组件方式的变化
// 不使用 React.lazy
import OtherComponent from ./OtherComponent;
// 使用 React.lazy
const OtherComponent React.lazy(() import(./OtherComponent))React.lazy 接受一个函数作为参数这个函数需要调用 import() 。它需要返回一个 Promise该 Promise 需要 resolve 一个 defalut export 的 React 组件。
43.React有哪些性能优化的方法
使用生产环境构建在部署应用之前确保使用 React 的生产环境构建这样可以启用代码压缩、移除开发环境下的调试工具和日志输出等减小应用的文件大小和加载时间。虚拟化长列表当渲染大量数据时考虑使用虚拟化技术例如 React Virtualized 或 react-window 这样的库。它们只渲染当前可见区域内的内容而不是全部渲染从而大幅减少 DOM 元素的数量提高性能。使用 memoization 优化组件通过使用 React.memo() 函数或 useMemo() 钩子可以记忆组件的渲染输出避免不必要的重渲染。这对于接收相同属性的函数组件尤其有用。使用 shouldComponentUpdate() 或 PureComponent为类组件时在 shouldComponentUpdate() 方法中手动比较传入的属性和状态决定是否进行渲染更新。另外也可以使用 PureComponent 类来自动执行浅层比较以确定是否重新渲染。懒加载组件将应用拆分成多个异步加载的代码块懒加载那些在初始渲染时不需要的组件。这样可以减小初始加载的文件大小提高应用的响应速度。避免不必要的渲染确保只有在属性或状态发生变化时才触发组件的重新渲染。避免无关的更新可以显著提高性能。可以使用浅层比较、使用不可变数据、避免在 render 方法中创建新对象等方式来实现。使用 React DevTools 进行性能分析React DevTools 是一款强大的工具可以帮助你检查组件的渲染次数、更新时间和组件树结构等信息从而找到性能瓶颈并进行优化。使用 webpack 或其他打包工具进行代码分割通过将应用的代码拆分成多个块并按需加载非核心代码可以减小初始加载量提高页面加载速度。使用 memo 或 useMemo 缓存计算结果对于一些开销较大的计算任务可以使用 memo 或 useMemo 来缓存计算结果避免重复计算提高性能。
44.React Fiber 是如何实现更新过程可控
更新过程的可控主要体现在下面几个方面
任务拆分任务挂起、恢复、终止任务具备优先级
任务拆分
在 React Fiber 机制中它采用化整为零的思想将调和阶段Reconciler递归遍历 VDOM 这个大任务分成若干小任务每个任务只负责一个节点的处理
任务挂起、恢复、终止
workInProgress tree
workInProgress 代表当前正在执行更新的 Fiber 树。在 render 或者 setState 后会构建一颗 Fiber 树也就是 workInProgress tree这棵树在构建每一个节点的时候会收集当前节点的副作用整棵树构建完成后会形成一条完整的副作用链。
currentFiber tree
currentFiber 表示上次渲染构建的 Filber 树。在每一次更新完成后 workInProgress 会赋值给 currentFiber。在新一轮更新时 workInProgress tree 再重新构建新 workInProgress 的节点通过 alternate 属性和 currentFiber 的节点建立联系。
在新 workInProgress tree 的创建过程中会同 currentFiber 的对应节点进行 Diff 比较收集副作用。同时也会复用和 currentFiber 对应的节点对象减少新创建对象带来的开销。也就是说无论是创建还是更新、挂起、恢复以及终止操作都是发生在 workInProgress tree 创建过程中的。workInProgress tree 构建过程其实就是循环的执行任务和创建下一个任务。
挂起
当第一个小任务完成后先判断这一帧是否还有空闲时间没有就挂起下一个任务的执行记住当前挂起的节点让出控制权给浏览器执行更高优先级的任务。
恢复
在浏览器渲染完一帧后判断当前帧是否有剩余时间如果有就恢复执行之前挂起的任务。如果没有任务需要处理代表调和阶段完成可以开始进入渲染阶段。
如何判断一帧是否有空闲时间的呢
使用前面提到的 RIC (RequestIdleCallback) 浏览器原生 APIReact 源码中为了兼容低版本的浏览器对该方法进行了 Polyfill。
恢复执行的时候又是如何知道下一个任务是什么呢
答案是在前面提到的链表。在 React Fiber 中每个任务其实就是在处理一个 FiberNode 对象然后又生成下一个任务需要处理的 FiberNode。
终止
其实并不是每次更新都会走到提交阶段。当在调和过程中触发了新的更新在执行下一个任务的时候判断是否有优先级更高的执行任务如果有就终止原来将要执行的任务开始新的 workInProgressFiber 树构建过程开始新的更新流程。这样可以避免重复更新操作。这也是在 React 16 以后生命周期函数 componentWillMount 有可能会执行多次的原因。
任务具备优先级
React Fiber 除了通过挂起恢复和终止来控制更新外还给每个任务分配了优先级。具体点就是在创建或者更新 FiberNode 的时候通过算法给每个任务分配一个到期时间expirationTime。在每个任务执行的时候除了判断剩余时间如果当前处理节点已经过期那么无论现在是否有空闲时间都必须执行该任务。过期时间的大小还代表着任务的优先级。
任务在执行过程中顺便收集了每个 FiberNode 的副作用将有副作用的节点通过 firstEffect、lastEffect、nextEffect 形成一条副作用单链表 A1(TEXT)-B1(TEXT)-C1(TEXT)-C1-C2(TEXT)-C2-B1-B2(TEXT)-B2-A。
其实最终都是为了收集到这条副作用链表有了它在接下来的渲染阶段就通过遍历副作用链完成 DOM 更新。这里需要注意更新真实 DOM 的这个动作是一气呵成的不能中断不然会造成视觉上的不连贯commit
45.不同版本的 React 都做过哪些优化
React 15 架构
React15架构可以分为两层
Reconciler协调器—— 负责找出变化的组件Renderer渲染器—— 负责将变化的组件渲染到页面上
在React15及以前Reconciler采用递归的方式创建虚拟DOM递归过程是不能中断的。如果组件树的层级很深递归会占用线程很多时间递归更新时间超过了16ms用户交互就会卡顿。
为了解决这个问题React16将递归的无法中断的更新重构为异步的可中断更新由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是全新的Fiber架构应运而生。
React 16 架构
为了解决同步更新长时间占用线程导致页面卡顿的问题也为了探索运行时优化的更多可能React开始重构并一直持续至今。重构的目标是实现Concurrent Mode并发模式。
从v15到v16React团队花了两年时间将源码架构中的Stack Reconciler重构为Fiber Reconciler。
React16架构可以分为三层
Scheduler调度器—— 调度任务的优先级高优任务优先进入ReconcilerReconciler协调器—— 负责找出变化的组件更新工作从递归变成了可以中断的循环过程。Reconciler内部采用了Fiber的架构Renderer渲染器—— 负责将变化的组件渲染到页面上。
React 17 优化
React16的expirationTimes模型只能区分是否expirationTimes决定节点是否更新。React17的lanes模型可以选定一个更新区间并且动态的向区间中增减优先级可以处理更细粒度的更新。 Lane用二进制位表示任务的优先级方便优先级的计算位运算不同优先级占用不同位置的“赛道”而且存在批的概念优先级越低“赛道”越多。高优先级打断低优先级新建的任务需要赋予什么优先级等问题都是Lane所要解决的问题。 Concurrent Mode的目的是实现一套可中断/恢复的更新机制。其由两部分组成
一套协程架构Fiber Reconciler基于协程架构的启发式更新算法控制协程架构工作方式的算法
46.虚拟DOM一定更快吗
虚拟DOMdomDiff
我们常说的虚拟DOM是通过JS对象模拟出来的DOM节点,domDiff是通过特定算法计算出来一次操作所带来的DOM变化。react和vue中都使用了虚拟DOM我们借着react聊聊虚拟DOM。
react中涉及到虚拟DOM的代码主要分为以下三部分其中核心是第二步的domDiff算法
把render中的JSX(或者createElement这个API)转化成虚拟DOM状态或属性改变后重新计算虚拟DOM并生成一个补丁对象(domDiff)通过这个补丁对象更新视图中的DOM节点
虚拟DOM不一定更快
干前端的都知道DOM操作是性能杀手因为操作DOM会引起页面的回流或者重绘。相比起来通过多一些预先计算来减少DOM的操作要划算的多。
但是“使用虚拟DOM会更快”这句话并不一定适用于所有场景。例如一个页面就有一个按钮点击一下数字加一那肯定是直接操作DOM更快。使用虚拟DOM无非白白增加了计算量和代码量。即使是复杂情况浏览器也会对我们的DOM操作进行优化大部分浏览器会根据我们操作的时间和次数进行批量处理所以直接操作DOM也未必很慢。
那么为什么现在的框架都使用虚拟DOM呢因为使用虚拟DOM可以提高代码的性能下限并极大的优化大量操作DOM时产生的性能损耗。同时这些框架也保证了即使在少数虚拟DOM不太给力的场景下性能也在我们接受的范围内。
而且我们之所以喜欢react、vue等使用了虚拟DOM框架不光是因为他们快还有很多其他更重要的原因。例如react对函数式编程的友好vue优秀的开发体验等目前社区也有好多比较这两个框架并打口水战的我觉着还是在两个都懂的情况下多探究一下原理更有意义一些。
47.为什么不能用数组下标来作为react组件中的key
react 使用diff算法使用key来做同级比对。如果使用数组下标作为key有以下情况
在数组头部或中部插入或删除元素 所有key对应的节点的值发生更改进行重新渲染。造成性能损耗而如果使用数组中唯一值来作为key不管是在何处插入或删除节点其他key对应的节点的值未发生更改只需插入或删除操作的数组节点。
48.React Hooks当中的useEffect是如何区分生命周期钩子的
seEffect可以看成是 componentDidMountcomponentDidUpdate 和 componentWillUnmount 三者的结合。
useEffect(callback, [source])接收两个参数调用方式如下
useEffect(() {console.log(mounted);return () {console.log(willUnmount);}}, [source]);生命周期函数的调用主要是通过第二个参数[source]来进行控制有如下几种情况
[source]参数不传时则每次都会优先调用上次保存的函数中返回的那个函数然后再调用外部那个函数[source]参数传[]时则外部的函数只会在初始化时调用一次返回的那个函数也只会最终在组件卸载时调用一次[source]参数有值时则只会监听到数组中的值发生变化后才优先调用返回的那个函数再调用外部的函数。
49.使用React Hooks有什么优势
hooks 是react 16.8 引入的特性他允许你在不写class的情况下操作state 和react的其他特性。
React Hooks 要解决的问题是状态共享是继 render-props 和 higher-order components 之后的第三种状态共享方案不会产生 JSX 嵌套地狱问题。
这个状态指的是状态逻辑所以称为状态逻辑复用会更恰当因为只共享数据处理逻辑不会共享数据本身。
50.React Hooks带来了什么便利
在没有 hooks 之前我们使用函数定义的组件中不能使用 React 的 state、各种生命周期钩子类组件的特性。在 React 16.8 之后推出了新功能 Hooks通过 hooks 我们可以再函数定义的组件中使用类组件的特性。
好处:
跨组件复用: 其实 render props / HOC 也是为了复用相比于它们Hooks 作为官方的底层 API最为轻量而且改造成本小不会影响原来的组件层次结构和传说中的嵌套地狱相比而言类组件的实现更为复杂 不同的生命周期会使逻辑变得分散且混乱不易维护和管理时刻需要关注this的指向问题代码复用代价高高阶组件的使用经常会使整个组件树变得臃肿 状态与 UI 隔离: 正是由于 Hooks 的特性状态逻辑会变成更小的粒度并且极容易被抽象成一个自定义 Hooks组件中的状态和 UI 变得更为清晰和隔离。
注意:
避免在 循环/条件判断/嵌套函数 中调用 hooks保证调用顺序的稳定不能在useEffect中使用useStateReact 会报错提示类组件不会被替换或废弃不需要强制改造类组件两种方式能并存
51.React中的VM 一定会提高性能吗
不一定因为 VM 只是通过 diff 算法避免了一些不需要变更的 DOM 操作最终还是要操作 DOM 的并且 diff 的过程也是有成本的。
对于某些场景比如都是需要变更 DOM 的操作因为 VM 还会有额外的 diff 算法的成本在里面所以 VM 的方式并不会提高性能甚至比原生 DOM 要慢。
但是正如尤大大说的这是一个性能 vs 可维护性的取舍。
框架的意义在于为你掩盖底层的 DOM 操作让你用更声明式的方式来描述你的目的从而让你的代码更容易维护。
没有任何框架可以比纯手动的优化 DOM 操作更快因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作它的实现必须是普适的。
针对任何一个 benchmark都可以写出比任何框架更快的手动优化但是那有什么意义呢在构建一个实际应用的时候出于可维护性的考虑不可能在每一个地方都去做手动优化。
52.为什么React的 VM 可以提高性能
因为 VM 并不是真实的操作 DOM通过 diff 算法可以避免一些不变要的 DOM 操作从而提高了性能。
53.react 的虚拟dom是怎么实现的
react 是把真实的 DOM 树转换为 JS 对象树也就是 Virtual DOM。每次数据更新后重新计算 VM并和上一次生成的 VM 树进行对比对发生变化的部分进行批量更新。除了性能之外VM 的实现最大的好处在于和其他平台的集成。
54.在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState 会发生什么
当调用 setState 的时候实际上会将新的 state 合并到状态更新队列中并对 partialState 以及 _pendingStateQueue 更新队列进行合并操作。最终通过 enqueueUpdate 执行 state 更新。
如果在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState会使得 state 队列_pendingStateQueue不为 null从而调用 updateComponent 方法updateComponent 中会继续调用 shouldComponentUpdate 和 componentWillUpdate因此造成死循环。
55.setstate后发生了什么
简单版本 React 利用状态队列机制实现了 setState 的“异步”更新避免频繁的重复更新 state。
首先将新的 state 合并到状态更新队列中然后根据更新队列和 shouldComponentUpdate 的状态来判断是否需要更新组件。
复杂版本
enqueueSetState 将 state 放入队列中并调用 enqueueUpdate 处理要更新的 Component如果组件当前正处于 update 事务中则先将 Component 存入 dirtyComponent 中。否则调用batchedUpdates 处理。batchedUpdates 发起一次 transaction.perform() 事务开始执行事务初始化运行结束三个阶段 初始化事务初始化阶段没有注册方法故无方法要执行运行执行 setSate 时传入的 callback 方法结束更新 isBatchingUpdates 为 false并执行 FLUSH_BATCHED_UPDATES 这个 wrapper 中的close方法FLUSH_BATCHED_UPDATES在close阶段会循环遍历所有的 dirtyComponents调用updateComponent 刷新组件并执行它的 pendingCallbacks, 也就是 setState 中设置的 callback。
56.React中为什么要给组件设置 key
在开发过程中我们需要保证某个元素的 key 在其同级元素中具有唯一性。
在 React Diff 算法中React 会借助元素的 Key 值来判断该元素是新创建的还是被移动而来的元素从而减少不必要的元素重新渲染。
此外React 还需要借助 Key 值来判断元素与本地状态的关联关系。
57.React 的事件代理机制和原生事件绑定有什么区别
需要 e.preventDefault() 即可原生存在兼容性问题。事件类型React 是 原生事件类型 的一个子集React 只是实现了 DOM level3 的事件接口有些事件 React 并没有实现比如 window 的 resize 事件。阻止 React 事件冒泡的行为只能用于 React 合成事件系统但是 在原生事件中的阻止冒泡行为却可以阻止 React 合成事件的传播。事件的绑定方式原生事件系统中支持多种不同的绑定事件的方式React 中只有一种事件对象原生中存在 IE 的兼容性问题React 做了兼容处理。
58.React 的事件代理机制和原生事件绑定混用会有什么问题
我们在平时的开发中应该尽可能的避免 React 的事件代理机制和原生事件绑定混用。
React 的合成事件层并没有将事件直接绑定到 DOM 元素上所以使用 e.stopPropagation() 来阻止原生 DOM 的冒泡的行为是不行的。阻止 React 事件冒泡的行为只能用于 React 合成事件系统但是 在原生事件中的阻止冒泡行为却可以阻止 React 合成事件的传播
59.为什么不能直接使用 this.state 改变数据
react中不能直接修改state因为并不会重新触发render。
以如下方式更新状态组件不会重新渲染。
//Wrong
This.state.message ”Hello world”;而是需要使用setState()方法状态改变时组件通过重新渲染做出响应。
//Correct
This.setState({message: ‘Hello World’});setState通过一个队列机制来实现 state 更新。当执行 setState 的时候会将需要更新的 state 合并后放入状态队列而不会立刻更新 this.state。队列机制可以高效的批量更新 state如果不通过 setState 而直接修改 this.state那么该 state 将不会被放入状态队列中当下次调用 setState 并对状态队列进行合并时将会忽略之前被直接修改的 state而造成无法预知的错误。
60.什么是高阶组件
高阶组件就是一个函数且该函数接受一个组件作为参数并返回一个新的组件。基本上这是从React的组成性质派生的一种模式我们称它们为“纯”组件 因为它们可以接受任何动态提供的子组件但它们不会修改或复制其输入组件的任何行为。
const EnhancedComponent higherOrderComponent(WrappedComponent);高阶组件HOC是 React 中用于复用组件逻辑的一种高级技巧高阶组件的参数为一个组件返回一个新的组件组件是将 props 转换为 UI而高阶组件是将组件转换为另一个组件
61.React中的类组件和函数组件之间有什么区别
类组件Class components
无论是使用函数或是类来声明一个组件它决不能修改它自己的 props。 所有 React 组件都必须是纯函数并禁止修改其自身 props。 React是单项数据流父组件改变了属性那么子组件视图会更新。 属性 props是外界传递过来的状态 state是组件本身的状态可以在组件中任意修改组件的属性和状态改变都会更新视图。
class Welcome extends React.Component {render() {return (h1Welcome { this.props.name }/h1);}
}
ReactDOM.render(Welcome namereact /, document.getElementById(root));函数组件functional component
函数组件接收一个单一的 props 对象并返回了一个React元素
function Welcome (props) {return h1Welcome {props.name}/h1
}
ReactDOM.render(Welcome namereact /, document.getElementById(root));区别
语法上
两者最明显的不同就是在语法上函数组件是一个纯函数它接收一个props对象返回一个react元素。而类组件需要去继承React.Component并且创建render函数返回react元素这将会要更多的代码虽然它们实现的效果相同。
状态管理
因为函数组件是一个纯函数你不能在组件中使用setState()这也是为什么把函数组件称作为无状态组件。
如果你需要在你的组件中使用state你可以选择创建一个类组件或者将state提升到你的父组件中然后通过props对象传递到子组件。
生命周期钩子
你不能在函数组件中使用生命周期钩子原因和不能使用state一样所有的生命周期钩子都来自于继承的React.Component中。
因此如果你想使用生命周期钩子那么需要使用类组件。
注意在react16.8版本中添加了hooks使得我们可以在函数组件中使用useState钩子去管理state使用useEffect钩子去使用生命周期函数。因此2、3两点就不是它们的区别点。从这个改版中我们可以看出作者更加看重函数组件而且react团队曾提及到在react之后的版本将会对函数组件的性能方面进行提升。
调用方式
如果SayHi是一个函数React需要调用它
// 你的代码
function SayHi() { return pHello, React/p
}
// React内部
const result SayHi(props) // » pHello, React/p如果SayHi是一个类React需要先用new操作符将其实例化然后调用刚才生成实例的render方法
// 你的代码
class SayHi extends React.Component { render() { return pHello, React/p }
}
// React内部
const instance new SayHi(props) // » SayHi {}
const result instance.render() // » pHello, React/p可想而知函数组件重新渲染将重新调用组件方法返回新的react元素类组件重新渲染将new一个新的组件实例然后调用render类方法返回react元素这也说明为什么类组件中this是可变的。
62.什么是虚拟DOM
虚拟DOMVDOM它是真实DOM的内存表示,一种编程概念一种模式。它会和真实的DOM同步比如通过ReactDOM这种库这个同步的过程叫做调和(reconcilation)。
虚拟DOM更多是一种模式不是一种特定的技术。
63.react新增的生命周期
static getDerivedStateFromProps: 这个静态方法在组件实例化、接收到新的 props 或者调用setState 方法时会被调用。它接收两个参数props 和 state并返回一个对象来更新组件的状态或者返回 null表示不需要更新。这个方法替代了旧的 componentWillReceiveProps 生命周期方法getSnapshotBeforeUpdate: 这个方法在更新发生之前被调用并且在最终渲染之前可以捕获 DOM树上的一些信息比如滚动位置。它可以返回一个值作为后续 componentDidUpdate方法的第三个参数。这个方法提供了一种处理更新前后状态的机制。componentDidCatch: 这个生命周期方法用于处理在组件树中的子组件抛出的错误。当子组件抛出错误时父组件的componentDidCatch 方法将会被调用并传入错误信息作为参数。它可以用于显示错误界面或者记录错误。