前端 react 面试题(边面边更)
diff 算法是怎么运作
每一种节点类型有自己的属性,也就是 prop,每次进行 diff 的时候,react 会先比较该节点类型,假如节点类型不一样,那么 react 会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较 prop 是否有更新,假如有 prop 不一样,那么 react 会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点
react 和 vue 的区别
相同点:
数据驱动页面,提供响应式的试图组件
都有 virtual DOM,组件化的开发,通过 props 参数进行父子之间组件传递数据,都实现了 webComponents 规范
数据流动单向,都支持服务器的渲染 SSR
都有支持 native 的方法,react 有 React native, vue 有 wexx
不同点:
数据绑定:Vue 实现了双向的数据绑定,react 数据流动是单向的
数据渲染:大规模的数据渲染,react 更快
使用场景:React 配合 Redux 架构适合大规模多人协作复杂项目,Vue 适合小快的项目
开发风格:react 推荐做法 jsx + inline style 把 html 和 css 都写在 js 了
vue 是采用 webpack +vue-loader 单文件组件格式,html, js, css 同一个文件
在哪个生命周期中你会发出 Ajax 请求?为什么?
Ajax 请求应该写在组件创建期的第五个阶段,即 componentDidMount 生命周期方法中。原因如下。在创建期的其他阶段,组件尚未渲染完成。而在存在期的 5 个阶段,又不能确保生命周期方法一定会执行(如通过 shouldComponentUpdate 方法优化更新等)。在销毀期,组件即将被销毁,请求数据变得无意义。因此在这些阶段发岀 Ajax 请求显然不是最好的选择。在组件尚未挂载之前,Ajax 请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。在 componentDidMount 方法中,执行 Ajax 即可保证组件已经挂载,并且能够正常更新组件。
简述 flux 思想
Flux
的最大特点,就是数据的"单向流动"。
用户访问
View
View
发出用户的Action
Dispatcher
收到Action
,要求Store
进行相应的更新Store
更新后,发出一个"change"
事件View
收到"change"
事件后,更新页面
说说 React 组件开发中关于作用域的常见问题。
在 EMAScript5 语法规范中,关于作用域的常见问题如下。(1)在 map 等方法的回调函数中,要绑定作用域 this(通过 bind 方法)。(2)父组件传递给子组件方法的作用域是父组件实例化对象,无法改变。(3)组件事件回调函数方法的作用域是组件实例化对象(绑定父组件提供的方法就是父组件实例化对象),无法改变。在 EMAScript6 语法规范中,关于作用域的常见问题如下。(1)当使用箭头函数作为 map 等方法的回调函数时,箭头函数的作用域是当前组件的实例化对象(即箭头函数的作用域是定义时的作用域),无须绑定作用域。(2)事件回调函数要绑定组件作用域。(3)父组件传递方法要绑定父组件作用域。总之,在 EMAScript6 语法规范中,组件方法的作用域是可以改变的。
如何用 React 构建( build)生产模式?
通常,使用 Webpack 的 DefinePlugin 方法将 NODE ENV 设置为 production。这将剥离 propType 验证和额外的警告。除此之外,还可以减少代码,因为 React 使用 Uglify 的 dead-code 来消除开发代码和注释,这将大大减少包占用的空间。
参考 前端进阶面试题详细解答
如何避免重复发起 ajax 获取数据?
数据放在 redux 里面
React 如何进行组件/逻辑复用?
抛开已经被官方弃用的 Mixin,组件抽象的技术目前有三种比较主流:
高阶组件:
属性代理
反向继承
渲染属性
react-hooks
setState 到底是异步还是同步?
先给出答案: 有时表现出异步,有时表现出同步
setState
只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout
中都是同步的setState
的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback)
中的callback
拿到更新后的结果setState
的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout
中不会批量更新,在“异步”中如果对同一个值进行多次setState
,setState
的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState
多个不同的值,在更新时会对其进行合并批量更新
Redux 中间件原理
指的是 action 和 store 之间,沟通的桥梁就是 dispatch,action 就是个对象。比如你用了 redux-thunk,action 也可以是个函数,怎么实现这个过程,就是通过中间件这个桥梁帮你实现的。action 到达 store 之前会走中间件,这个中间件会把函数式的 action 转化为一个对象,在传递给 store
HOC(高阶组件)
HOC(Higher Order Componennt) 是在 React 机制下社区形成的一种组件模式,在很多第三方开源库中表现强大。
简述:
高阶组件不是组件,是 增强函数,可以输入一个元组件,返回出一个新的增强组件;
高阶组件的主要作用是 代码复用,操作 状态和参数;
用法:
属性代理 (Props Proxy): 返回出一个组件,它基于被包裹组件进行 功能增强;
默认参数: 可以为组件包裹一层默认参数;
提取状态: 可以通过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件:
使用姿势如下,这样就能非常快速的将一个 Input 组件转化成受控组件。
包裹组件: 可以为被包裹元素进行一层包装,
反向继承 (Inheritance Inversion): 返回出一个组件,继承于被包裹组件,常用于以下操作
渲染劫持 (Render Highjacking)
条件渲染: 根据条件,渲染不同的组件
可以直接修改被包裹组件渲染出的 React 元素树
操作状态 (Operate State) : 可以直接通过 this.state 获取到被包裹组件的状态,并进行操作。但这样的操作容易使 state 变得难以追踪,不易维护,谨慎使用。
应用场景:
权限控制,通过抽象逻辑,统一对页面进行权限判断,按不同的条件进行页面渲染:
性能监控 ,包裹组件的生命周期,进行统一埋点:
代码复用,可以将重复的逻辑进行抽象。
使用注意:
纯函数: 增强函数应为纯函数,避免侵入修改元组件;
避免用法污染: 理想状态下,应透传元组件的无关参数与事件,尽量保证用法不变;
命名空间: 为 HOC 增加特异性的组件名称,这样能便于开发调试和查找问题;
引用传递 : 如果需要传递元组件的 refs 引用,可以使用 React.forwardRef;
静态方法 : 元组件上的静态方法并无法被自动传出,会导致业务层无法调用;解决:
函数导出
静态方法赋值
重新渲染: 由于增强函数每次调用是返回一个新组件,因此如果在 Render 中使用增强函数,就会导致每次都重新渲染整个 HOC,而且之前的状态会丢失;
React 中的高阶组件运用了什么设计模式?
使用了装饰模式,高阶组件的运用:
装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下:
在生命周期中的哪一步你应该发起 AJAX 请求
我们应当将 AJAX 请求放到
componentDidMount
函数中执行,主要原因有下
React
下一代调和算法Fiber
会通过开始或停止渲染的方式优化应用性能,其会影响到componentWillMount
的触发次数。对于componentWillMount
这个生命周期函数的调用次数会变得不确定,React
可能会多次频繁调用componentWillMount
。如果我们将AJAX
请求放到componentWillMount
函数中,那么显而易见其会被触发多次,自然也就不是好的选择。如果我们将
AJAX
请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState
函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在componentDidMount
函数中进行AJAX
请求则能有效避免这个问题
你理解“在 React 中,一切都是组件”这句话。
组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。
组件更新有几种方法
this.setState() 修改状态的时候 会更新组件
this.forceUpdate() 强制更新
组件件 render 之后,子组件使用到父组件中状态,导致子组件的 props 属性发生改变的时候 也会触发子组件的更新
组件之间传值
父组件给子组件传值
在父组件中用标签属性的=形式传值
在子组件中使用 props 来获取值
子组件给父组件传值
在组件中传递一个函数
在子组件中用 props 来获取传递的函数,然后执行该函数
在执行函数的时候把需要传递的值当成函数的实参进行传递
兄弟组件之间传值
利用父组件
先把数据通过 【子组件】===》【父组件】
然后在数据通过 【父组件】===〉【子组件】
消息订阅
使用 PubSubJs 插件
(在构造函数中)调用 super(props) 的目的是什么
在 super()
被调用之前,子类是不能使用 this
的,在 ES2015 中,子类必须在 constructor
中调用 super()
。传递 props
给 super()
的原因则是便于(在子类中)能在 constructor
访问 this.props
。
diff 虚拟 DOM 比较的规则
【旧虚拟 DOM】 与 【新虚拟 DOM】中相同 key
若虚拟 DOM 中的内容没有发生改变,直接使用旧的虚拟 DOM
若虚拟 DOM 中的内容发生改变了,则生成新真实的 DOM,随后替换页面中之前的真实 DOM
【旧虚拟 DOM】 中未找到 与 【新虚拟 DOM】相同的 key
根据数据创建真实 DOM,随后渲染到页面
React 性能优化在哪个生命周期?它优化的原理是什么?
react 的父级组件的 render 函数重新渲染会引起子组件的 render 方法的重新渲染。但是,有的时候子组件的接受父组件的数据没有变动。子组件 render 的执行会影响性能,这时就可以使用 shouldComponentUpdate 来解决这个问题。
使用方法如下:
shouldComponentUpdate 提供了两个参数 nextProps 和 nextState,表示下一次 props 和一次 state 的值,当函数返回 false 时候,render()方法不执行,组件也就不会渲染,返回 true 时,组件照常重渲染。此方法就是拿当前 props 中值和下一次 props 中的值进行对比,数据相等时,返回 false,反之返回 true。
需要注意,在进行新旧对比的时候,是浅对比,也就是说如果比较的数据时引用数据类型,只要数据的引用的地址没变,即使内容变了,也会被判定为 true。
面对这个问题,可以使用如下方法进行解决:(1)使用 setState 改变数据之前,先采用 ES6 中 assgin 进行拷贝,但是 assgin 只深拷贝的数据的第一层,所以说不是最完美的解决办法:
(2)使用 JSON.parse(JSON.stringfy())进行深拷贝,但是遇到数据为 undefined 和函数时就会错。
ref 是一个函数又有什么好处?
方便 react 销毁组件、重新渲染的时候去清空 refs 的东西,防止内存泄露
评论