当前位置: 首页 > Web前端 > HTML

阿里前端双面React面试常问题(必填)

时间:2023-03-28 19:37:56 HTML

谈谈React组件开发中scope的常见问题。在EMAScript5语法规范中,关于作用域的常见问题如下。(1)在map等方法的回调函数中,必须绑定this的作用域(通过bind方法)。(2)父组件传递给子组件的方法作用域是父组件的实例化对象,不能改变。(3)组件事件回调函数方法的作用域为组件实例化对象(绑定父组件提供的方法为父组件实例化对象),不可更改。在EMAScript6语法规范中,关于作用域的常见问题如下。(1)当使用箭头函数作为map等方法的回调函数时,箭头函数的作用域为当前组件的实例化对象(即箭头函数的作用域为定义的作用域),并且不需要绑定范围。(2)事件回调函数必须绑定到组件作用域。(3)父组件的传递方法必须绑定父组件的作用域。简而言之,在EMAScript6语法规范中,组件方法的范围是可以改变的。描述如何在React中处理事件。为了解决跨浏览器兼容性问题,React中的事件处理程序将传递SyntheticEvent的实例,它是跨浏览器事件的包装器。这些SyntheticEvents具有与您习惯的本机事件相同的界面,并且它们在所有浏览器中都兼容。React实际上并没有将事件附加到子节点本身。相反,使用单个事件侦听器通过事件委托模式在顶层侦听所有事件。这对性能有好处。这也意味着React无需担心在DOM更新时跟踪事件监听器。React的虚拟dom是怎么实现的?首先说一下为什么要使用VirturlDOM,因为操作真实DOM的性能成本太高,所以react内部使用js实现了一套dom结构,在每次操作和真实dom之前,使用实现良好的diff算法比较虚拟dom,递归找出变化的dom节点,然后更新。为了实现虚拟DOM,我们需要将每个节点类型抽象成一个对象。每个节点类型都有自己的属性,即prop。每次执行diff时,react都会首先比较节点类型。如果节点类型不同相同,那么react会直接删除该节点,然后直接新建一个节点插入进去。如果节点类型相同,则比较prop是否更新。如果prop不同,那么react会判断该节点已经更新,然后重新渲染该节点,然后逐层比较它的子节点,直到没有子节点为止React的虚拟DOM和内部实现的差异算法。传统diff算法的时间复杂度为O(n^3),这在前端渲染中是不能接受的。为了降低时间复杂度,react的diff算法做了一些折衷,放弃了最优解,最终将时间复杂度降低到O(n)。那么reactdiff算法做了哪些妥协呢?,参考如下:treediff:只比较同层的dom节点,忽略dom节点的跨层移动。当发现该节点不存在时,该节点及其子节点将被彻底删除,不再用于进一步的比较。这样只需要遍历一次树就可以完成整个DOM树的比较。这意味着如果dom节点跨层移动,react将删除旧节点并生成新节点而不会重复使用。Componentdiff:如果不是同类型的component,则删除旧的component,创建一个新的component。elementdiff:对于同一层级的一组子节点,需要通过唯一的id来区分。如果没有id区分,一旦有插入动作,就会导致插入位置之后的所有列表重新渲染,这就是为什么在渲染列表的时候要用一个唯一的key。reactforcerefreshcomponent.forceUpdate()一个不常用的生命周期方法,它的作用是强制刷新官网解释如下默认情况下,当组件的state或props发生变化时,组件会重新渲染。如果render()方法依赖于其他数据,您可以调用forceUpdate()强制组件重新渲染。调用forceUpdate()将导致组件调用render()方法,该方法会跳过组件的shouldComponentUpdate()。但是,它的子组件会触发正常的生命周期方法,包括shouldComponentUpdate()方法。如果标记发生变化,React仍然只会更新DOM。通常,您应该避免使用forceUpdate()并在render()中使用this.props和this.state。shouldComponentUpdate不会在初始化和forceUpdate的时候执行。在React16.X中,在getDerivedStateFromProps中处理props变化后,将处理哪个生命周期。这个生命周期函数的存在是为了替代componentWillReceiveProps,所以当你需要使用componentWillReceiveProps时,可以考虑使用getDerivedStateFromProps代替。两者的参数不同,getDerivedStateFromProps是静态函数,即这个函数不能通过this访问类的属性,不建议直接访问属性。而是应该通过参数提供的nextProps和prevState进行判断,根据新传入的props映射到state。需要注意的是,如果props传入的内容不需要影响你的state,那么你需要返回一个null。这个返回值是必须的,所以尽量写在函数的末尾:staticgetDerivedStateFromProps(nextProps,prevState){const{type}=nextProps;//当传入的类型改变时,更新状态if(type!==prevState.type){return{type,};}//否则,状态什么都不做returnnull;}参考前端高级面试题详细答案组件是什么?什么是类?什么样的组件被编译成一个类?它指的是页面的一部分。它本质上是一个类,最本质的是一个构造函数。该类被编译成构造函数。React-Router的路由有几种模式?React-Router支持使用hash(对应HashRouter)和browser(对应BrowserRouter)两种路由规则。react-router-dom提供了BrowserRouter和HashRouter两个组件来实现应用UI和URL同步:BrowserRouter创建的URL格式:xxx.com/pathHashRouter创建的URL格式:xxx.com/#/path(1)BrowserRouter使用历史HTML5提供的API(pushState、replaceState和popstate事件)使UI和URL保持同步。由此可见,BrowserRouter使用了HTML5的historyAPI来控制路由跳转:属性如下:basename所有路由的基本URL。basename的正确格式是前面有一个前导斜杠,但没有尾随斜杠;等同于forceRefresh如果为true,导航时会刷新整个页面.一般情况下,该功能仅在不支持HTML5historyAPI的浏览器中使用;getUserConfirmation是用来确认导航的函数,默认使用window.confirm。例如,从/a导航到/b时,会弹出提示,使用默认的确认功能,用户点击确定后导航,否则什么都不做;//这是默认的确认函数constgetConfirmation=(message,callback)=>{constallowTransition=window.confirm(message);callback(allowTransition);需要和一起使用。KeyLength用于设置Location.Key的长度。(2)HashRouter使用URL的哈希部分(即window.location.hash)来保持UI和URL的同步。从这里可以看出,HashRouter是通过URL的hash属性来控制路由跳转的:与BrowserRouter功能相同;hashTypewindow.location.hash使用以下类型的哈希:斜杠-后跟斜杠,例如#/和#/sunshine/lollipops;noslash-没有斜杠,例如#和#sunshine/lollipops;hashbang-Google风格的ajax可抓取,如#!/和#!/sunshine/lollipops.传递给setstate函数的第二个参数的目的是什么?第二个参数是一个函数,当调用setState函数并且组件开始重新渲染时会调用该函数。您可以使用此功能来监视渲染是否完成。this.setstate({username:"优客前端网络",},()=>console.log("重新渲染成功。"));对Redux的理解主要解决React作为视图层框架有哪些问题。Redux是一个用于管理数据状态和UI状态的JavaScript应用程序工具。随着JavaScript单页应用(SPA)开发变得越来越复杂,JavaScript需要管理比以往更多的状态(state),而Redux降低了管理难度。(Redux支持React、Angular、jQuery甚至纯JavaScript)。在React中,UI是以组件的形式构建的,组件可以嵌套组合。然而,React中组件之间的数据流是单向的。顶层组件可以通过props属性给下层组件传递数据,而下层组件不能给上层组件传递数据,兄弟组件也不能。如此简单的单向数据流支持了React中的数据可控性。当项目越来越大时,管理数据的事件或回调函数会越来越多,管理起来也会越来越困难。管理不断变化的状态非常困难。如果一个模型的变化引起另一个模型的变化,那么当视图发生变化时,可能会引起相应模型和另一个模型的变化,进而可能引起另一个视图的变化。直到你搞不清是怎么回事。什么时候、出于什么原因以及状态如何变化是无法控制的。当系统变得复杂时,就很难重现问题或添加新功能。如果这还不够糟糕,考虑一下来自前端开发领域的一些新需求,比如更新调优、服务器端渲染、路由跳转前请求数据等。在大型项目中,状态管理可能相当复杂。Redux提供了一个统一的存储库,称为store。组件通过dispatch直接将状态传递给store,而不需要经过其他组件。并且组件通过订阅从商店中获取状态变化。借助Redux,所有组件都可以从store中获取所需的状态,也可以从store中获取状态变化。这比在组件之间传递数据要清楚得多。主要要解决的问题:PureRedux只是一个没有UI呈现的状态机。react-redux的作用是将Redux状态机和ReactUI展示绑定在一起。当您发送更改状态的操作时,它会自动更新页面。React组件中的state和props有什么区别?(1)propsprops是从外部传入组件的参数。它的主要功能是将数据从父组件传递给子组件。它是可读的和不变的。它只能通过主动从外部组件传入新的道具来重新渲染孩子。组件,否则子组件的道具和展示形式不会改变。(2)statestate的主要作用是保存、控制和修改组件的状态。它只能在构造函数中初始化。它被视为组件的私有属性,不能从外部访问或修改。只能在组件内部通过this访问。setState来修改,修改state属性会导致组件重新渲染。(3)不同的是,props是传递给组件的(类似于函数的形式参数),而state在组件内部由组件自己管理(类似于函数内部声明的变量)。Props是不可变的,所有React组件都必须像纯函数一样保护它们的props不被更改。状态在组件中创建,状态一般在构造函数中初始化。state是可变的,可以被修改,并且在每次设置setState时异步更新。React的slots(Portals)的理解,如何使用,以及有哪些使用场景React官方对Portals的定义:Portal提供了一个优秀的解决方案,用于将子节点渲染到存在于父组件之外的DOM节点。传送门是React16官方提供的解决方案,可以让组件挂载在DOM树的任意位置,无需父组件层级。通俗地说,我们渲染了一个组件,但是这个组件的DOM结构并不在这个组件中。Portals语法如下:ReactDOM.createPortal(child,container);第一个参数child是一个可渲染的React子元素,例如元素、字符串或片段;第二个参数container是一个DOM元素。一般来说,组件的render函数返回的元素会被挂载到它的父组件上:importDemoComponentfrom'./DemoComponent';render(){//DemoComponent元素会挂载到id为parent的div上return(

);}但是有些元素需要挂载在a更高层次。最典型的应用场景:当父组件有overflow:hidden或z-index样式设置时,组件可能会被其他元素挡住。这时候可以考虑是否使用Portal让组件挂载到父组件之外。例如:对话框、模态窗口。importDemoComponentfrom'./DemoComponent';render(){//DemoComponent元素会挂载到id为parent的div元素上return(
);}传入setState函数的第二个参数的作用是什么?当setState函数调用完成并且组件开始重新渲染时,将调用此函数。我们可以使用这个函数来监控渲染是否完成:setState((prevState,props)=>{return{streak:prevState.streak+props.count}})组件从父到子通信的方式有哪些组件通信:父组件可以通过传递子组件的道具。子组件与父组件通信:props+回调方法。作用域是父组件本身的函数,子组件调用这个函数,子组件要传递的信息,作为参数传递给父组件作用域内的兄弟组件通信:find公共Parent节点,结合以上两种方式,父节点转发信息,实现跨层通信:Context旨在共享对于一个组件树来说是“全局”的数据,比如当前认证的用户、topic或者preferred语言,对于跨多个层的全局数据通过Context进行通信,发布-订阅模式是完美的:发布者发布事件,订阅者监听事件并做出反应。我们可以通过引入事件模块进行全局通信Statemanagementtools:与Redux或Mobx等全局状态管理工具进行通信,会维护一个全局的状态中心Store,根据不同的事件生成新的状态来解释React中的render()目的。每个React组件都必须有一个render()。它返回一个代表原生DOM组件的React元素。如果需要呈现多个HTML元素,则必须将它们分组在单个封闭标记中,例如
等。此函数必须是纯函数,即每次都必须返回相同的结果叫做。哪个生命周期发送ajaxcomponentWillMount在新版本的react中已经被废弃。做ssr项目时,componentWillMount需要获取服务端数据,不能被占用。那么componentDidMount请求的是什么呢?PropsProps是React中属性的简写。它们是只读组件,必须保持纯净,即不可变。它们始终在整个应用程序中从父组件传递到子组件。子组件永远不能将props发送回父组件。这有助于保持单向数据流,通常用于呈现动态生成的数据。React-Router4如何在路由变化时重新渲染同一个组件?当路由发生变化,即组件的props发生变化时,会调用componentWillReceiveProps等生命周期钩子。需要做的就是:当路由发生变化时,根据路由,也请求数据:}fetchData(位置){常量类型=位置。路径名.replace('/','')||'top'this.props.dispatch(fetchListData(type))}componentWillReceiveProps(nextProps){if(nextProps.location.pathname!=this.props.location.pathname){this.fetchData(nextProps.location);}}render(){...}}使用生命周期componentWillReceiveProps进行re-render的预处理操作。componentWillReceiveProps调用时机已被丢弃。它仅在props更改时调用。当子组件第二次接收到props的时候,再介绍一下React。之前没有jquery的时候,我们大概的流程就是通过ajax从后台获取数据,然后使用。jquery生成dom结果并更新到页面,但是随着业务的发展,我们的项目可能会越来越复杂。每次我们请求数据或者数据发生变化,我们都需要重新组装dom结构。然后更新页面,这样手动同步dom和数据的成本越来越高,而且dom的频繁操作也慢慢降低了我们页面的性能。这时候mvvm出现了。mvvm的双向数据绑定让我们在修改数据的同时同步dom的更新。dom的更新也可以直接同步我们数据的变化。这样可以大大降低手动维护dom更新的成本,mvvm是react的特性之一。React虽然属于单数据流,但是我们需要手动实现双向数据绑定。有mvvm是不够的,因为如果每次数据有变化我们就全量更新dom结构,没办法解决我们频繁操作dom结构的问题(降低页面性能).为了解决这个问题,react内部实现了一套虚拟dom结构,也就是一套用js实现的dom结构。它的作用是在js中为真正的dom做一套缓存。每次有数据变化,react内部首先使用的算法,就是著名的diff算法,比较dom结构,找到我们需要增、更新、删的dom节点,然后更新真实的DOM在一次,大大减少了对dom的操作次数。那么差异算法是如何工作的呢?首先,对于不同类型的节点,diff会直接判断原来需要卸载的节点的位置,并使用一个新的节点来加载卸载的节点;对于相同节点类型的节点,它会比较这个节点的所有节点的属性,如果节点的所有属性都相同,则确定该节点不需要更新,如果节点属性不相同相同,则判断该节点需要更新,react会更新并重新渲染该节点。在react设计之初,主要负责UI层的渲染。虽然每个组件都有自己的状态,但状态代表组件的状态。当状态需要改变时,我们需要使用setState来更新我们的组件。但是,我们想要使用一个组件重新渲染它的兄弟组件,就需要将组件的状态提升到父组件,让父组件的状态来控制这两个组件的重新渲染。当我们的组件层级越来越深,state需要一路下载,这无疑会增加我们代码的复杂度。我们需要一个状态管理中心来帮助我们管理我们的状态。这时候就出现了redux,我们可以把所有的状态都交给redux来管理。当我们的某个状态发生变化时,依赖于这个状态的组件会被重新渲染,这样就解决了我们的问题。问题是状态需要一直传递下去。Redux有action和reducer的概念。Action是修改状态的唯一源,reducer是唯一决定状态如何变化的入口。这使得redux的数据流非常规范,同时也暴露了redux代码的复杂性,就是这么简单,却需要完成这么多代码。后来社区又出现了一套解决方案,就是mobx,提倡代码简单易懂。它只需要定义一个observable对象,然后哪个组使用这个observable对象,这个对象的数据有Change,那么这个组件就会重新渲染,mobx也做了一个是否重新渲染的生命周期组件shouldUpdateComponent,不建议开发者去改动,这让我们在开发项目的时候可以使用mobx来快速方便的完成很多功能,连redux的作者都推荐使用mobx来进行项目开发。但是随着项目的不断壮大,mobx也不断暴露出自己的缺点,即数据流向过于随机,出现bug后数据流向难以追溯。这个缺点正好体现了redux的优点,所以对于小项目,社区推荐mobx,大项目推荐redux