面试题 129. React.forwardRef是什么?它有什么作用?
参考回答:
React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:
1 转发 refs 到 DOM 组件
2 在高阶组件中转发 refs
面试题 130. 简述React的状态提升是什么?使用场景有哪些?
参考回答:
React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。
概括来说就是将多个组件需要共享的状态提升到它们最近的父组件上,在父组件上改变这个状态然后通过props分发给子组件。
一个简单的例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值,这就需要用到状态提升。
class Father extends React.Component {
constructor(props) {
super(props)
this.state = {
Value1: '',
Value2: ''
}
}
value1Change(aa) {
this.setState({
Value1: aa
})
}
value2Change(bb) {
this.setState({
Value2: bb
})
}
render() {
return (
)
}
}
class Child1 extends React.Component {
constructor(props) {
super(props)
}
changeValue(e) {
this.props.onvalue1Change(e.target.value)
}
render() {
return (
{this.props.Value1}
)
}
}
class Child2 extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
{this.props.value2}
)
}
}
ReactDOM.render(
,
document.getElementById('root')
)
面试题 131. React 中的高阶组件运用了什么设计模式?
参考回答:
使用了装饰模式,高阶组件的运用:
function withWindowWidth(BaseComponent) {
class DerivedClass extends React.Component {
state = {
windowWidth: window.innerWidth,
}
onResize = () => {
this.setState({
windowWidth: window.innerWidth,
})
}
componentDidMount() {
window.addEventListener('resize', this.onResize)
}
componentWillUnmount() {
window.removeEventListener('resize', this.onResize);
}
render() {
return
}
}
return DerivedClass;
}
const MyComponent = (props) => {
return
Window width is: {props.windowWidth}
};
export default withWindowWidth(MyComponent);
装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下:
@testable
class MyTestableClass {
}
面试题 132. React中constructor和getInitialState的区别?
参考回答:
两者都是用来初始化state的。前者是ES6中的语法,后者是ES5中的语法,新版本的React中已经废弃了该方法。
getInitialState是ES5中的方法,如果使用createClass方法创建一个Component组件,可以自动调用它的getInitialState方法来获取初始化的State对象,
var APP = React.creatClass ({
getInitialState() {
return {
userName: 'hi',
userId: 0
};
}
})
React在ES6的实现中去掉了getInitialState这个hook函数,规定state在constructor中实现,如下:
Class App extends React.Component{
constructor(props){
super(props);
this.state={};
}
}
面试题 133. React 如何实现强制刷新?
参考回答:
component.forceUpdate() 一个不常用的生命周期方法, 它的作用就是强制刷新
官网解释如下:
默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。
调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate() 方法。如果标记发生变化,React 仍将只更新 DOM。
通常你应该避免使用 forceUpdate(),尽量在 render() 中使用 this.props 和 this.state。
shouldComponentUpdate 在初始化 和 forceUpdate 不会执行
面试题 134. 简述React 之 高低版本区别 ?
参考回答:
1、引入文件不同:
高版本:react.production.js、 react-dom.production.min.js、browser.min.js
低版本:react.min.js、react-dom.min.js、browser.min.js
2、创建组件方式不同:
高版本:通过 类 的继承—class xx extends React.Component {
}
低版本:React.createClass {
}
3、生命周期所用钩子函数不同:
高版本(3个):componentWillMount、render、componentDidMount
低版本(5个):getDefaultProps、getInitialState、componentWillMount、render、componentDidMount
4、属性之间传值:
高版本:用组件defaultProps构造器传值
低版本:用钩子函数getDefaultProps传值
5、状态state不同:
高版本:在constructor中–用this.state= {
}
初始状态,调用this.setState()需要在constructor中通过bind绑定this指向
低版本:用getInitialState()初始状态,用this.setState()更新组件的状态并在其内bind绑定this指向
面试题 135. React setState 笔试题,下面的代码输出什么 ?
参考回答:
class Example extends React.Component {
constructor() {
super()
this.state = {
val: 0
}
}
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 1 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 2 次 log
setTimeout(() => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 3 次 log
this.setState({ val: this.state.val + 1 })
console.log(this.state.val)
// 第 4 次 log
}, 0)
}
render() {
return null
}
}
面试题 136. 简述React触发多次setstate,那么render会执⾏⼏次 ?
参考回答:
多次setState会合并为⼀次render,因为setState并不会⽴即改变state的值,⽽是将其放到⼀个任务队列⾥,最终将多个setState合并,⼀次性更新⻚⾯。所以我们可以在代码⾥多次调⽤setState,每次只需要关注当前修改的字段即可
面试题 137. 简述原⽣事件和React事件的区别 ?
参考回答:
React 事件使⽤驼峰命名,⽽不是全部⼩写。
通过 JSX , 你传递⼀个函数作为事件处理程序,⽽不是⼀个字符串。
在 React 中你不能通过返回 false 来阻⽌默认⾏为。必须明确调⽤ preventDefault 。
面试题 138. React ⾼阶组件、Render props、hooks 有什么区别,为什么要 不断迭代 ?
参考回答:
这三者是⽬前react解决代码复⽤的主要⽅式:
⾼阶组件(HOC)是 React 中⽤于复⽤组件逻辑的⼀种⾼级技巧。HOC ⾃身不是 React API 的
⼀部分,它是⼀种基于 React 的组合特性⽽形成的设计模式。具体⽽⾔,⾼阶组件是参数为组件,
返回值为新组件的函数。
render props是指⼀种在 React 组件之间使⽤⼀个值为函数的 prop 共享代码的简单技术,更
具体的说,render prop 是⼀个⽤于告知组件需要渲染什么内容的函数 prop。
通常,render props 和⾼阶组件只渲染⼀个⼦节点。让 Hook 来服务这个使⽤场景更加简单。这
两种模式仍有⽤武之地,(例如,⼀个虚拟滚动条组件或许会有⼀个 renderltem 属性,或是⼀个
可⻅的容器组件或许会有它⾃⼰的 DOM 结构)。但在⼤部分场景下,Hook ⾜够了,并且能够帮助
减少嵌套。
(1)HOC 官⽅解释∶
⾼阶组件(HOC)是 React 中⽤于复⽤组件逻辑的⼀种⾼级技巧。HOC ⾃身不是 React API
的⼀部分,它是⼀种基于 React 的组合特性⽽形成的设计模式。
简⾔之,HOC是⼀种组件的设计模式,HOC接受⼀个组件和额外的参数(如果需要),返回⼀个新的组
件。HOC 是纯函数,没有副作⽤。
HOC的优缺点∶
// hoc的定义
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = {
data: selectData(DataSource, props)
};
}
// ⼀些通⽤的逻辑处理
render() {
// ... 并使⽤新数据渲染被包装的组件!
return ;
}
};
// 使⽤
const BlogPostWithSubscription = withSubscription(BlogPost,
(DataSource, props) => DataSource.getBlogPost(props.id));
复制代码
优点∶ 逻辑服⽤、不影响被包裹组件的内部逻辑。
缺点∶ hoc传递给被包裹组件的props容易和被包裹后的组件重名,进⽽被覆盖
(2)Render props 官⽅解释∶
"render prop"是指⼀种在 React 组件之间使⽤⼀个值为函数的 prop 共享代码的简单技术
具有render prop 的组件接受⼀个返回React元素的函数,将render的渲染逻辑注⼊到组件内部。在
这⾥,"render"的命名可以是任何其他有效的标识符。
由此可以看到,render props的优缺点也很明显∶
优点:数据共享、代码复⽤,将组件内的state作为props传递给调⽤者,将渲染逻辑交给调⽤者。
缺点:⽆法在 return 语句外访问数据、嵌套写法不够优雅
(3)Hooks 官⽅解释∶
// DataProvider组件内部的渲染逻辑如下
class DataProvider extends React.Components {
state = {
name: 'Tom'
}
render() {
return (
共享数据组件⾃⼰内部的渲染逻辑
{ this.props.render(this.state) }
);
}
}
// 调⽤⽅式
(
Hello {data.name}
)}/>
复制代码
Hook是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使⽤ state 以及其
他的 React 特性。通过⾃定义hook,可以复⽤代码逻辑。
以上可以看出,hook解决了hoc的prop覆盖的问题,同时使⽤的⽅式解决了render props的嵌套地狱
的问题。hook的优点如下∶
使⽤直观;
解决hoc的prop 重名问题;
解决render props 因共享数据 ⽽出现嵌套地狱的问题;
能在return之外使⽤数据的问题。
需要注意的是:hook只能在组件顶层使⽤,不可在分⽀语句中使⽤。
总结∶∶ Hoc、render props和hook都是为了解决代码复⽤的问题,但是hoc和render props都有特
定的使⽤场景和明显的缺点。hook是react16.8更新的新的API,让组件逻辑复⽤更简洁明了,同时也
解决了hoc和render props的⼀些缺点
面试题 139. 对React-Fiber的理解,它解决了什么问题?
参考回答:
React V15 在渲染时,会递归⽐对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, ⼀
⽓呵成。这个过程期间, React 会占据浏览器资源,这会导致⽤户触发的事件得不到响应,并且会导致
掉帧,导致⽤户感觉到卡顿。
// ⾃定义⼀个获取订阅数据的hook
function useSubscription() {
const data = DataSource.getComments();
return [data];
}
//
function CommentList(props) {
const {data} = props;
const [subData] = useSubscription();
...
}
// 使⽤
复制代码
为了给⽤户制造⼀种应⽤很快的“假象”,不能让⼀个任务⻓期霸占着资源。 可以将浏览器的渲染、布
局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执⾏视作操作系统的“进程”,需要通过某些调
度策略合理地分配 CPU 资源,从⽽提⾼浏览器的⽤户响应速率, 同时兼顾任务执⾏效率。
所以 React 通过Fiber 架构,让这个执⾏过程变成可被中断。“适时”地让出 CPU 执⾏权,除了可以
让浏览器及时地响应⽤户的交互,还有其他好处:
分批延时对DOM进⾏操作,避免⼀次性操作⼤量 DOM 节点,可以得到更好的⽤户体验;
给浏览器⼀点喘息的机会,它会对代码进⾏编译优化(JIT)及进⾏热代码优化,或者对 reflow
进⾏修正。
核⼼思想: Fiber 也称协程或者纤程。它和线程并不⼀样,协程本身是没有并发或者并⾏能⼒的(需要
配合线程),它只是⼀种控制流程的让出机制。让出 CPU 的执⾏权,让 CPU 能在这段时间执⾏其他的
操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给⾼优先级的任务,浏览器空闲后再恢复
渲染
面试题 140. 简述对componentWillReceiveProps 的理解 ?
参考回答:
该⽅法当props发⽣变化时执⾏,初始化render时不执⾏,在这个回调函数⾥⾯,你可以根据属性的变化,通过调⽤this.setState()来更新你的组件状态,旧的属性还是可以通过this.props来获取,这
⾥调⽤更新状态是安全的,并不会触发额外的render调⽤。
使⽤好处: 在这个⽣命周期中,可以在⼦组件的render函数执⾏前获取新的props,从⽽更新⼦组件⾃⼰的state。 可以将数据请求放在这⾥进⾏执⾏,需要传的参数则从componentWillReceiveProps(nextProps)中获取。⽽不必将所有的请求都放在⽗组件中。于是该请求只会在该组件渲染时才会发出,从⽽减轻请求负担。
componentWillReceiveProps在初始化render的时候不会执⾏,它会在Component接受到新的状态(Props)时被触发,⼀般⽤于⽗组件状态更新时⼦组件的重新渲染。
面试题 141. 哪些⽅法会触发 React 重新渲染?重新渲染 render 会做些什么 ?
参考回答:
(1)哪些⽅法会触发 react 重新渲染?
setState()⽅法被调⽤
setState 是 React 中最常⽤的命令,通常情况下,执⾏ setState 会触发 render。但是这⾥有
个点值得关注,执⾏ setState 的时候不⼀定会重新渲染。当 setState 传⼊ null 时,并不会触
发 render。
return />;
}
}
}
// pages/page-a.js
export default withFetching(fetching('science-fiction'))(MovieList);
// pages/page-b.js
export default withFetching(fetching('action'))(MovieList);
// pages/page-other.js
export default withFetching(fetching('some-other-type'))(MovieList);
复制代码
class App extends React.Component {
⽗组件重新渲染
只要⽗组件重新渲染了,即使传⼊⼦组件的 props 未发⽣变化,那么⼦组件也会重新渲染,进⽽触发
render
(2)重新渲染 render 会做些什么?
会对新旧 VNode 进⾏对⽐,也就是我们所说的Diff算法。
对新旧两棵树进⾏⼀个深度优先遍历,这样每⼀个节点都会⼀个标记,在到深度遍历的时候,每遍历
到⼀和个节点,就把该节点和新的节点树进⾏对⽐,如果有差异就放到⼀个对象⾥⾯
遍历差异对象,根据差异的类型,根据对应对规则更新VNode
React 的处理 render 的基本思维模式是每次⼀有变动就会去重新渲染整个应⽤。在 Virtual DOM
没有出现之前,最简单的⽅法就是直接调⽤ innerHTML。Virtual DOM厉害的地⽅并不是说它⽐直接
操作 DOM 快,⽽是说不管数据怎么变,都会尽量以最⼩的代价去更新 DOM。React 将 render 函数
返回的虚拟 DOM 树与⽼的进⾏⽐较,从⽽确定 DOM 要不要更新、怎么更新。当 DOM 树很⼤时,遍历
state = {
a: 1
};
render() {
console.log("render");
return (
{this.state.a}
onClick={() => {
this.setState({ a: 1 }); // 这⾥并没有改变 a 的值
}}
>
Click me
this.setState(null)}>setState null
);
}
}
复制代码
两棵树进⾏各种⽐对还是相当耗性能的,特别是在顶层 setState ⼀个微⼩的修改,默认会去遍历整棵
树。尽管 React 使⽤⾼度优化的 Diff 算法,但是这个过程仍然会损耗性能
面试题 142. 简述为什么React并不推荐优先考虑使⽤Context?
参考回答:
Context⽬前还处于实验阶段,可能会在后⾯的发⾏版本中有很⼤的变化,事实上这种情况已经发⽣了,所以为了避免给今后升级带来⼤的影响和麻烦,不建议在app中使⽤context。
尽管不建议在app中使⽤context,但是独有组件⽽⾔,由于影响范围⼩于app,如果可以做到⾼内聚,不破坏组件树之间的依赖关系,可以考虑使⽤context
对于组件之间的数据通信或者状态管理,有效使⽤props或者state解决,然后再考虑使⽤第三⽅的成熟库进⾏解决,以上的⽅法都不是最佳的⽅案的时候,在考虑context。
context的更新需要通过setState()触发,但是这并不是很可靠的,Context⽀持跨组件的访问,但是如果中间的⼦组件通过⼀些⽅法不影响更新,⽐如 shouldComponentUpdate() 返回false那么不能保证Context的更新⼀定可以使⽤Context的⼦组件,因此,Context的可靠性需要关注
面试题 143. React中的setState批量更新的过程是什么?
参考回答:
调⽤ setState 时,组件的 state 并不会⽴即改变, setState 只是把要修改的 state 放⼊⼀个队列, React 会优化真正的执⾏时机,并出于性能原因,会将 React 事件处理程序中的多次React 事件处理程序中的多次 setState 的状态修改合并成⼀次状态修改。 最终更新只产⽣⼀次组件及其⼦组件的重新渲染,这对于⼤型应⽤程序中的性能提升⾄关重要
面试题 144. 简述React中setState的第⼆个参数作⽤是什么 ?
参考回答:
setState 的第⼆个参数是⼀个可选的回调函数。这个回调函数将在组件重新渲染后执⾏。等价于在componentDidUpdate ⽣命周期内执⾏。通常建议使⽤ componentDidUpdate 来代替此⽅式。在这个回调函数中你可以拿到更新后 state 的值:
this.setState({
key1: newState1,
key2: newState2,
...
}, callback) // 第⼆个参数是 state 更新完成后的回调函数
面试题 145. 简述React中的setState和replaceState的区别是什么 ?
参考回答:
(1)setState() setState()⽤于设置状态对象,其语法如下:
nextState,将要设置的新状态,该状态会和当前的state合并
callback,可选参数,回调函数。该函数会在setState设置成功,且组件重新渲染后调⽤。
合并nextState和当前state,并重新渲染组件。setState是React事件处理函数中和请求回调函数中
触发UI更新的主要⽅法。
var ShowTitle = React.createClass({
getDefaultProps:function(){
return{
title : "React"
}
},
render : function(){
return
{this.props.title}
}
});
复制代码
this.setState({
key1: newState1,
key2: newState2,
...
}, callback) // 第⼆个参数是 state 更新完成后的回调函数
复制代码
setState(object nextState[, function callback])
复制代码
(2)replaceState() replaceState()⽅法与setState()类似,但是⽅法只会保留nextState中
状态,原state不在nextState中的状态都会被删除。其语法如下:
nextState,将要设置的新状态,该状态会替换当前的state。
callback,可选参数,回调函数。该函数会在replaceState设置成功,且组件重新渲染后调⽤。
总结: setState 是修改其中的部分状态,相当于 Object.assign,只是覆盖,不会减少原来的状
态。⽽replaceState 是完全替换原来的状态,相当于赋值,将原来的 state 替换为另⼀个对象,如
果新状态属性减少,那么 state 中就没有这个状态
面试题 146. 简述React中的props为什么是只读的 ?
参考回答:
this.props是组件之间沟通的⼀个接⼝,原则上来讲,它只能从⽗组件流向⼦组件。React具有浓重的函数式编程的思想。
提到函数式编程就要提⼀个概念:纯函数。它有⼏个特点:
给定相同的输⼊,总是返回相同的输出。
过程没有副作⽤。
不依赖外部状态。
this.props就是汲取了纯函数的思想。props的不可以变性就保证的相同的输⼊,⻚⾯显示的内容是⼀样的,并且不会产⽣副作⽤
面试题 147. 在React中组件的props改变时更新组件的有哪些⽅法 ?
参考回答:
在⼀个组件传⼊的props更新时重新渲染该组件常⽤的⽅法是在componentWillReceiveProps中将新
的props更新到组件的state中(这种state被成为派⽣状态(Derived State)),从⽽实现重新渲
染。React 16.3中还引⼊了⼀个新的钩⼦函数getDerivedStateFromProps来专⻔实现这⼀需求。
(1)componentWillReceiveProps(已废弃)
在react的componentWillReceiveProps(nextProps)⽣命周期中,可以在⼦组件的render函数执
⾏前,通过this.props获取旧的属性,通过nextProps获取新的props,对⽐两次props是否相同,从
⽽更新⼦组件⾃⼰的state。
这样的好处是,可以将数据请求放在这⾥进⾏执⾏,需要传的参数则从
componentWillReceiveProps(nextProps)中获取。⽽不必将所有的请求都放在⽗组件中。于是该请
求只会在该组件渲染时才会发出,从⽽减轻请求负担。
(2)getDerivedStateFromProps(16.3引⼊)
这个⽣命周期函数是为了替代componentWillReceiveProps存在的,所以在需要使
⽤componentWillReceiveProps时,就可以考虑使⽤getDerivedStateFromProps来进⾏替代。
两者的参数是不相同的,⽽getDerivedStateFromProps是⼀个静态函数,也就是这个函数不能通过
this访问到class的属性,也并不推荐直接访问属性。⽽是应该通过参数提供的nextProps以及
prevState来进⾏判断,根据新传⼊的props来映射到state。
需要注意的是,如果props传⼊的内容不需要影响到你的state,那么就需要返回⼀个null,这个返回值
是必须的,所以尽量将其写到函数的末尾:
12. React中怎么检验props?验证props的⽬的是什么?
React为我们提供了PropTypes以供验证使⽤。当我们向Props传⼊的数据⽆效(向Props传⼊的数据类
型和验证的数据类型不符)就会在控制台发出警告信息。它可以避免随着应⽤越来越复杂从⽽出现的问
题。并且,它还可以让程序变得更易读。
static getDerivedStateFromProps(nextProps, prevState) {
const {type} = nextProps;
// 当传⼊的type发⽣变化的时候,更新state
if (type !== prevState.type) {
return {
type,
};
}
// 否则,对于state不进⾏任何操作
return null;
}
面试题 148. 简述React中怎么检验props?验证props的⽬的是什么 ?
参考回答:
React为我们提供了PropTypes以供验证使⽤。当我们向Props传⼊的数据⽆效(向Props传⼊的数据类型和验证的数据类型不符)就会在控制台发出警告信息。它可以避免随着应⽤越来越复杂从⽽出现的问题。并且,它还可以让程序变得更易读。
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
Hello, {this.props.name}
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
当然,如果项⽬汇中使⽤了TypeScript,那么就可以不⽤PropTypes来校验,⽽使⽤TypeScript定义接⼝来校验props。
面试题 149. 简述React 废弃了哪些⽣命周期?为什么 ?
参考回答:
被废弃的三个函数都是在render之前,因为fber的出现,很可能因为⾼优先级任务的出现⽽打断现有任
务导致它们会被执⾏多次。另外的⼀个原因则是,React想约束使⽤者,好的框架能够让⼈不得已写出容
易维护和扩展的代码,这⼀点⼜是从何谈起,可以从新增加以及即将废弃的⽣命周期分析⼊⼿
1) componentWillMount
⾸先这个函数的功能完全可以使⽤componentDidMount和 constructor来代替,异步获取的数据的情
况上⾯已经说明了,⽽如果抛去异步获取数据,其余的即是初始化⽽已,这些功能都可以在
constructor中执⾏,除此之外,如果在 willMount 中订阅事件,但在服务端这并不会执⾏
willUnMount事件,也就是说服务端会导致内存泄漏所以componentWilIMount完全可以不使⽤,但使
⽤者有时候难免因为各 种各样的情况在 componentWilMount中做⼀些操作,那么React为了约束开发
者,⼲脆就抛掉了这个API
2) componentWillReceiveProps
在⽼版本的 React 中,如果组件⾃身的某个 state 跟其 props 密切相关的话,⼀直都没有⼀种很
优雅的处理⽅式去更新 state,⽽是需要在 componentWilReceiveProps 中判断前后两个 props
是否相同,如果不同再将新的 props更新到相应的 state 上去。这样做⼀来会破坏 state 数据的单
⼀数据源,导致组件状态变得不可预测,另⼀⽅⾯也会增加组件的重绘次数。类似的业务需求也有很多,
如⼀个可以横向滑动的列表,当前⾼亮的 Tab 显然⾪属于列表⾃身的时,根据传⼊的某个值,直接定位
到某个 Tab。为了解决这些问题,React引⼊了第⼀个新的⽣命周期:
getDerivedStateFromProps。它有以下的优点∶
getDSFP是静态⽅法,在这⾥不能使⽤this,也就是⼀个纯函数,开发者不能写出副作⽤的代码
开发者只能通过prevState⽽不是prevProps来做对⽐,保证了state和props之间的简单关系以
及不需要处理第⼀次渲染时prevProps为空的情况
基于第⼀点,将状态变化(setState)和昂贵操作(tabChange)区分开,更加便于 render 和
commit 阶段操作或者说优化。
3) componentWillUpdate
与 componentWillReceiveProps 类似,许多开发者也会在 componentWillUpdate 中根据
props 的变化去触发⼀些回调 。 但不论是 componentWilReceiveProps 还 是
componentWilUpdate,都有可能在⼀次更新中被调⽤多次,也就是说写在这⾥的回调函数也有可能会
被调⽤多次,这显然是不可取的。与 componentDidMount 类 似, componentDidUpdate 也不存
在这样的问题,⼀次更新中 componentDidUpdate 只会被调⽤⼀次,所以将原先写在
componentWillUpdate 中 的 回 调 迁 移 ⾄ componentDidUpdate 就可以解决这个问题。
另外⼀种情况则是需要获取DOM元素状态,但是由于在fber中,render可打断,可能在wilMount中获
取到的元素状态很可能与实际需要的不同,这个通常可以使⽤第⼆个新增的⽣命函数的解决
getSnapshotBeforeUpdate(prevProps, prevState)
4) getSnapshotBeforeUpdate(prevProps, prevState)
返回的值作为componentDidUpdate的第三个参数。与willMount不同的是,
getSnapshotBeforeUpdate会在最终确定的render执⾏之前执⾏,也就是能保证其获取到的元素状态
与didUpdate中获取到的元素状态相同。官⽅参考代码:
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我们是否在 list 中添加新的 items ?
// 捕获滚动位置以便我们稍后调整滚动位置。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
// 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
//(这⾥的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
面试题 150. React 16.X 中 props 改变后在哪个⽣命周期中处理 ?
参考回答:
在getDerivedStateFromProps中进⾏处理。
这个⽣命周期函数是为了替代componentWillReceiveProps存在的,所以在需要使
⽤componentWillReceiveProps时,就可以考虑使⽤getDerivedStateFromProps来进⾏替代。
两者的参数是不相同的,⽽getDerivedStateFromProps是⼀个静态函数,也就是这个函数不能通过
this访问到class的属性,也并不推荐直接访问属性。⽽是应该通过参数提供的nextProps以及
prevState来进⾏判断,根据新传⼊的props来映射到state。
需要注意的是,如果props传⼊的内容不需要影响到你的state,那么就需要返回⼀个null,这个返回值
是必须的,所以尽量将其写到函数的末尾:
static getDerivedStateFromProps(nextProps, prevState) {
const {type} = nextProps;
// 当传⼊的type发⽣变化的时候,更新state
if (type !== prevState.type) {
return {
type,
};
}
// 否则,对于state不进⾏任何操作
return null;
}
面试题 151. React 性能优化在哪个⽣命周期?它优化的原理是什么?
参考回答:
react的⽗级组件的render函数重新渲染会引起⼦组件的render⽅法的重新渲染。但是,有的时候⼦组件的接受⽗组件的数据没有变动。⼦组件render的执⾏会影响性能,这时就可以使⽤shouldComponentUpdate来解决这个问题。
使⽤⽅法如下
shouldComponentUpdate(nexrProps) {
if (this.props.num === nexrProps.num) {
return false
}
return true;
}
shouldComponentUpdate提供了两个参数nextProps和nextState,表示下⼀次props和⼀次state
的值,当函数返回false时候,render()⽅法不执⾏,组件也就不会渲染,返回true时,组件照常重渲
染。此⽅法就是拿当前props中值和下⼀次props中的值进⾏对⽐,数据相等时,返回false,反之返回
true。
需要注意,在进⾏新旧对⽐的时候,是浅对⽐,也就是说如果⽐较的数据时引⽤数据类型,只要数据的引
⽤的地址没变,即使内容变了,也会被判定为true。
⾯对这个问题,可以使⽤如下⽅法进⾏解决:
(1)使⽤setState改变数据之前,先采⽤ES6中assgin
进⾏拷⻉,但是assgin只深拷⻉的数据的第⼀层,所以说不是最完美的解决办法:
(2)使⽤JSON.parse(JSON.stringfy())进⾏深拷⻉,但是遇到数据为undefined和函数时就会
错。
shouldComponentUpdate(nexrProps) {
if (this.props.num === nexrProps.num) {
return false
}
return true;
}
const o2 = Object.assign({},this.state.obj)
o2.student.count = '00000';
this.setState({
obj: o2,
})
const o2 = JSON.parse(JSON.stringify(this.state.obj))
o2.student.count = '00000';
this.setState({
obj: o2,
})
面试题 152. 简述state 和 props 触发更新的⽣命周期分别有什么区别?
参考回答:
state 更新流程 这个过程当中涉及的函数:
1. shouldComponentUpdate: 当组件的 state 或 props 发⽣改变时,都会⾸先触发这个⽣命周
期函数。它会接收两个参数:nextProps, nextState——它们分别代表传⼊的新 props 和新的
state 值。拿到这两个值之后,我们就可以通过⼀些对⽐逻辑来决定是否有 re-render(重渲
染)的必要了。如果该函数的返回值为 false,则⽣命周期终⽌,反之继续;
注意:此⽅法仅作为性能优化的⽅式⽽存在。不要企图依靠此⽅法来“阻⽌”渲染,因为这可能会产
⽣ bug。应该考虑使⽤内置的 PureComponent 组件,⽽不是⼿动编写
shouldComponentUpdate()
1. componentWillUpdate:当组件的 state 或 props 发⽣改变时,会在渲染之前调⽤
componentWillUpdate。componentWillUpdate 是 React16 废弃的三个⽣命周期之⼀。过
去,我们可能希望能在这个阶段去收集⼀些必要的信息(⽐如更新前的 DOM 信息等等),现在我们
完全可以在 React16 的 getSnapshotBeforeUpdate 中去做这些事;
2. componentDidUpdate:componentDidUpdate() 会在UI更新后会被⽴即调⽤。它接收
prevProps(上⼀次的 props 值)作为⼊参,也就是说在此处我们仍然可以进⾏ props 值对⽐
(再次说明 componentWillUpdate 确实鸡肋哈)
props 更新流程:
相对于 state 更新,props 更新后唯⼀的区别是增加了对 componentWillReceiveProps 的调
⽤。关于 componentWillReceiveProps,需要知道这些事情:
componentWillReceiveProps:它在Component接受到新的 props 时被触发。
componentWillReceiveProps 会接收⼀个名为 nextProps 的参数(对应新的 props 值)。
该⽣命周期是 React16 废弃掉的三个⽣命周期之⼀。在它被废弃前,可以⽤它来⽐较
this.props 和 nextProps 来重新setState。在 React16 中,⽤⼀个类似的新⽣命周期
getDerivedStateFromProps 来代替它
面试题 153. 简述React中发起⽹络请求应该在哪个⽣命周期中进⾏?为什么 ?
参考回答:
对于异步请求,最好放在componentDidMount中去操作,对于同步的状态改变,可以放在
componentWillMount中,⼀般⽤的⽐较少。
如果认为在componentWillMount⾥发起请求能提早获得结果,这种想法其实是错误的,通常
componentWillMount⽐componentDidMount早不了多少微秒,⽹络上任何⼀点延迟,这⼀点差异都
可忽略不计。
react的⽣命周期: constructor() -> componentWillMount() -> render() ->
componentDidMount()
上⾯这些⽅法的调⽤是有次序的,由上⽽下依次调⽤。
constructor被调⽤是在组件准备要挂载的最开始,此时组件尚未挂载到⽹⻚上。
componentWillMount⽅法的调⽤在constructor之后,在render之前,在这⽅法⾥的代码调⽤
setState⽅法不会触发重新render,所以它⼀般不会⽤来作加载数据之⽤。
componentDidMount⽅法中的代码,是在组件已经完全挂载到⽹⻚上才会调⽤被执⾏,所以可以保
证数据的加载。此外,在这⽅法中调⽤setState⽅法,会触发重新渲染。所以,官⽅设计这个⽅法
就是⽤来加载外部数据⽤的,或处理其他的副作⽤代码。与组件上的数据⽆关的加载,也可以在
constructor⾥做,但constructor是做组件state初绐化⼯作,并不是做加载数据这⼯作的,
constructor⾥也不能setState,还有加载的时间太⻓或者出错,⻚⾯就⽆法加载出来。所以有副
作⽤的代码都会集中在componentDidMount⽅法⾥。
总结:
跟服务器端渲染(同构)有关系,如果在componentWillMount⾥⾯获取数据,fetch data会执
⾏两次,⼀次在服务器端⼀次在客户端。在componentDidMount中可以解决这个问题,
componentWillMount同样也会render两次。
在componentWillMount中fetch data,数据⼀定在render后才能到达,如果忘记了设置初始状
态,⽤户体验不好。
react16.0以后,componentWillMount可能会被执⾏多次。
面试题 154. 简述⾮嵌套关系组件的通信⽅式 ?
参考回答:
即没有任何包含关系的组件,包括兄弟组件以及不在同⼀个⽗级中的⾮兄弟组件。
可以使⽤⾃定义事件通信(发布订阅模式)
可以通过redux等进⾏全局状态管理
如果是兄弟组件通信,可以找到这两个兄弟节点共同的⽗节点, 结合⽗⼦间通信⽅式进⾏通信
面试题 155. 简述如何解决 props 层级过深的问题 ?
参考回答:
使⽤Context API:提供⼀种组件之间的状态共享,⽽不必通过显式组件树逐层传递props;
使⽤Redux等状态库。
面试题 156. 简述React-Router的实现原理是什么 ?
参考回答:
客户端路由实现的思想:
基于 hash 的路由:通过监听
事件,感知 hash 的变化
改变 hash 可以直接通过 location.hash=xxx
基于 H5 history 路由:
改变 url 可以通过 history.pushState 和 resplaceState 等,会将URL压⼊堆栈,同
时能够应⽤ history.go() 等 API
监听 url 的变化可以通过⾃定义事件触发实现
react-router 实现的思想:
基于 history 库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器
差异,上层⽆感知
通过维护的列表,在每次 URL 发⽣变化的回收,通过配置的 路由路径,匹配到对应的
Component,并且 render
面试题 157. 简述React-Router怎么设置重定向?
参考回答:
使⽤组件实现路由的重定向:
当请求 /users/:id 被重定向去 '/users/profile/:id':
属性 from: string:需要匹配的将要被重定向路径。
属性 to: string:重定向的 URL 字符串
属性 to: object:重定向的 location 对象
属性 push: bool:若为真,重定向操作将会把新地址加⼊到访问历史记录⾥⾯,并且⽆法回退到
前⾯的⻚⾯。
面试题 158. 简述 react-router ⾥的 Link 标签和 a 标签的区别 ?
参考回答:
从最终渲染的 DOM 来看,这两者都是链接,都是 标签,区别是∶ 是react-router ⾥实现
路由跳转的链接,⼀般配合 使⽤,react-router接管了其默认的链接跳转⾏为,区别于传统
的⻚⾯跳转, 的“跳转”⾏为只会触发相匹配的 对应的⻚⾯内容更新,⽽不会刷新整个
⻚⾯。
做了3件事情:
有onclick那就执⾏onclick
click的时候阻⽌a标签默认事件
根据跳转href(即是to),⽤history (web前端路由两种⽅式之⼀,history & hash)跳转,此
// location = { pathname: '/react' }
React
// React
时只是链接变了,并没有刷新⻚⾯⽽标签就是普通的超链接了,⽤于从当前⻚⾯跳转到href指
向的另⼀ 个⻚⾯(⾮锚点情况)。
a标签默认事件禁掉之后做了什么才实现了跳转?
let domArr = document.getElementsByTagName('a')
[...domArr].forEach(item=>{
item.addEventListener('click',function () {
location.href = this.href
})
})
面试题 159. 简述React-Router如何获取URL的参数和历史对象 ?
参考回答:
(1)获取URL的参数
get传值
路由配置还是普通的配置,如:'admin',传参⽅式如:'admin?id='1111''。通过
this.props.location.search获取url获取到⼀个字符串'?id='1111' 可以⽤url,qs,
querystring,浏览器提供的api URLSearchParams对象或者⾃⼰封装的⽅法去解析出id的值。
动态路由传值
路由需要配置成动态路由:如path='/admin/:id',传参⽅式,如'admin/111'。通过
this.props.match.params.id 取得url中的动态路由id部分的值,除此之外还可以通过
useParams(Hooks)来获取
通过query或state传值
传参⽅式如:在Link组件的to属性中可以传递对
象{pathname:'/admin',query:'111',state:'111'};。通过this.props.location.state
或this.props.location.query来获取即可,传递的参数可以是对象、数组等,但是存在缺点就是只
要刷新⻚⾯,参数就会丢失。
(2)获取历史对象
如果React >= 16.8 时可以使⽤ React Router中提供的Hooks
let domArr = document.getElementsByTagName('a')
[...domArr].forEach(item=>{
item.addEventListener('click',function () {
location.href = this.href
})
})
import { useHistory } from "react-router-dom";
let history = useHistory()
2.使⽤this.props.history获取历史对象
let history = this.props.history;
面试题 160. 简述React-Router 4怎样在路由变化时重新渲染同⼀个组件 ?
参考回答:
当路由变化时,即组件的props发⽣了变化,会调⽤componentWillReceiveProps等⽣命周期钩⼦。那需要做的只是: 当路由改变时,根据路由,也去请求数据
class NewsList extends Component {
componentDidMount () {
this.fetchData(this.props.location);
}
fetchData(location) {
const type = location.pathname.replace('/', '') || 'top'
this.props.dispatch(fetchListData(type))
}
componentWillReceiveProps(nextProps) {
if (nextProps.location.pathname != this.props.location.pathname) {
this.fetchData(nextProps.location);
}
}
render () {
...
}
}利⽤⽣命周期componentWillReceiveProps,进⾏重新render的预处理操作