字节前端必会 react 面试题
React 中 keys 的作用是什么?
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。
在 React 中渲染集合时,向每个重复的元素添加关键字对于帮助 React 跟踪元素与数据之间的关联非常重要。key 应该是唯一 ID,最好是 UUID 或收集项中的其他唯一字符串:
在集合中添加和删除项目时,不使用键或将索引用作键会导致奇怪的行为。
diff 算法?
把树形结构按照层级分解,只比较同级元素。
给列表结构的每个单元添加唯一的
key
属性,方便比较。React
只会匹配相同class
的component
(这里面的class
指的是组件的名字)合并操作,调用
component
的setState
方法的时候,React
将其标记为 -dirty
.到每一个事件循环结束,React
检查所有标记dirty
的component
重新绘制.选择性子树渲染。开发人员可以重写
shouldComponentUpdate
提高diff
的性能
父子组件的通信方式?
父组件向子组件通信:父组件通过 props 向子组件传递需要的信息。
子组件向父组件通信:: props+回调的方式。
React-Router 的路由有几种模式?
React-Router 支持使用 hash(对应 HashRouter)和 browser(对应 BrowserRouter) 两种路由规则, react-router-dom 提供了 BrowserRouter 和 HashRouter 两个组件来实现应用的 UI 和 URL 同步:
BrowserRouter 创建的 URL 格式:xxx.com/path
HashRouter 创建的 URL 格式:xxx.com/#/path
(1)BrowserRouter
它使用 HTML5 提供的 history API(pushState、replaceState 和 popstate 事件)来保持 UI 和 URL 的同步。由此可以看出,BrowserRouter 是使用 HTML 5 的 history API 来控制路由跳转的:
其中的属性如下:
basename 所有路由的基准 URL。basename 的正确格式是前面有一个前导斜杠,但不能有尾部斜杠;
等同于
forceRefresh 如果为 true,在导航的过程中整个页面将会刷新。一般情况下,只有在不支持 HTML5 history API 的浏览器中使用此功能;
getUserConfirmation 用于确认导航的函数,默认使用 window.confirm。例如,当从 /a 导航至 /b 时,会使用默认的 confirm 函数弹出一个提示,用户点击确定后才进行导航,否则不做任何处理;
需要配合
<Prompt>
一起使用。
KeyLength 用来设置 Location.Key 的长度。
(2)HashRouter
使用 URL 的 hash 部分(即 window.location.hash)来保持 UI 和 URL 的同步。由此可以看出,HashRouter 是通过 URL 的 hash 属性来控制路由跳转的:
其参数如下:
basename, getUserConfirmation 和
BrowserRouter
功能一样;hashType window.location.hash 使用的 hash 类型,有如下几种:
slash - 后面跟一个斜杠,例如 #/ 和 #/sunshine/lollipops;
noslash - 后面没有斜杠,例如 # 和 #sunshine/lollipops;
hashbang - Google 风格的 ajax crawlable,例如 #!/ 和 #!/sunshine/lollipops。
用户不同权限 可以查看不同的页面 如何实现?
Js 方式
根据用户权限类型,把菜单配置成 json, 没有权限的直接不显示
react-router 方式 在 route 标签上 添加 onEnter 事件,进入路由之前替换到首页
自己封装一个 privateRouter 组件 里面判断是否有权限,有的话返回
没有权限的话 component 返回一个提示信息的组件。
扩展一下,如果是根据用权限来判断是否隐藏组件该怎么做呢?
react 可以使用高阶组件,在高阶组件里面判断是否有权限,然后判断是否返回组件,无权限返回 null
vue 可以使用自定义指令,如果没有权限移除组件
对于 store 的理解
Store 就是把它们联系到一起的对象。Store 有以下职责:
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过 subscribe(listener)注册监听器;
通过 subscribe(listener)返回的函数注销监听器
参考 前端进阶面试题详细解答
高阶组件
高阶函数:如果一个函数接受一个或多个函数作为参数或者返回一个函数就可称之为高阶函数。
高阶组件:如果一个函数 接受一个或多个组件作为参数并且返回一个组件 就可称之为 高阶组件。
react 中的高阶组件
React 中的高阶组件主要有两种形式:属性代理和反向继承。
属性代理 Proxy
操作
props
抽离
state
通过
ref
访问到组件实例用其他元素包裹传入的组件
WrappedComponent
反向继承
会发现其属性代理和反向继承的实现有些类似的地方,都是返回一个继承了某个父类的子类,只不过属性代理中继承的是 React.Component
,反向继承中继承的是传入的组件 WrappedComponent
。
反向继承可以用来做什么:
1.操作 state
高阶组件中可以读取、编辑和删除WrappedComponent
组件实例中的state
。甚至可以增加更多的state
项,但是非常不建议这么做因为这可能会导致state
难以维护及管理。
2.渲染劫持(Render Highjacking)
条件渲染通过 props.isLoading 这个条件来判断渲染哪个组件。
修改由 render() 输出的 React 元素树
指出(组件)生命周期方法的不同
componentWillMount
-- 多用于根组件中的应用程序配置componentDidMount
-- 在这可以完成所有没有 DOM 就不能做的所有配置,并开始获取所有你需要的数据;如果需要设置事件监听,也可以在这完成componentWillReceiveProps
-- 这个周期函数作用于特定的 prop 改变导致的 state 转换shouldComponentUpdate
-- 如果你担心组件过度渲染,shouldComponentUpdate
是一个改善性能的地方,因为如果组件接收了新的prop
, 它可以阻止(组件)重新渲染。shouldComponentUpdate 应该返回一个布尔值来决定组件是否要重新渲染componentWillUpdate
-- 很少使用。它可以用于代替组件的componentWillReceiveProps
和shouldComponentUpdate
(但不能访问之前的 props)componentDidUpdate
-- 常用于更新 DOM,响应 prop 或 state 的改变componentWillUnmount
-- 在这你可以取消网络请求,或者移除所有与组件相关的事件监听器
vue 或者 react 优化整体优化
虚拟 dom
为什么虚拟 dom 会提高性能?(必考)
虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异把 2 所记录的差异应用到步骤 1 所构建的真正的 DOM 树上,视图就更新了。
React 组件的 state 和 props 有什么区别?
(1)props
props 是一个从外部传进组件的参数,主要作为就是从父组件向子组件传递数据,它具有可读性和不变性,只能通过外部组件主动传入新的 props 来重新渲染子组件,否则子组件的 props 以及展现形式不会改变。
(2)state
state 的主要作用是用于组件保存、控制以及修改自己的状态,它只能在 constructor 中初始化,它算是组件的私有属性,不可通过外部访问和修改,只能通过组件内部的 this.setState 来修改,修改 state 属性会导致组件的重新渲染。
(3)区别
props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)。
props 是不可修改的,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
state 是在组件中创建的,一般在 constructor 中初始化 state。state 是多变的、可以修改,每次 setState 都异步更新的。
React 组件的构造函数有什么作用?它是必须的吗?
构造函数主要用于两个目的:
通过将对象分配给 this.state 来初始化本地状态
将事件处理程序方法绑定到实例上
所以,当在 React class 中需要设置 state 的初始值或者绑定事件时,需要加上构造函数,官方 Demo:
构造函数用来新建父类的 this 对象;子类必须在 constructor 方法中调用 super 方法;否则新建实例时会报错;因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法;子类就得不到 this 对象。
注意:
constructor () 必须配上 super(), 如果要在 constructor 内部使用 this.props 就要 传入 props , 否则不用
JavaScript 中的 bind 每次都会返回一个新的函数, 为了性能等考虑, 尽量在 constructor 中绑定事件
使用箭头函数(arrow functions)的优点是什么
作用域安全:在箭头函数之前,每一个新创建的函数都有定义自身的
this
值(在构造函数中是新对象;在严格模式下,函数调用中的this
是未定义的;如果函数被称为“对象方法”,则为基础对象等),但箭头函数不会,它会使用封闭执行上下文的this
值。简单:箭头函数易于阅读和书写
清晰:当一切都是一个箭头函数,任何常规函数都可以立即用于定义作用域。开发者总是可以查找 next-higher 函数语句,以查看
this
的值
react 生命周期
初始化阶段:
getDefaultProps:获取实例的默认属性
getInitialState:获取每个实例的初始化状态
componentWillMount:组件即将被装载、渲染到页面上
render:组件在这里生成虚拟的 DOM 节点
componentDidMount:组件真正在被装载之后
运行中状态:
componentWillReceiveProps:组件将要接收到属性的时候调用
shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更新,阻止 render 调用,后面的函数不会被继续执行了)
componentWillUpdate:组件即将更新不能修改属性和状态
render:组件重新描绘
componentDidUpdate:组件已经更新
销毁阶段:
componentWillUnmount:组件即将销毁
shouldComponentUpdate 是做什么的,(react 性能优化是哪个周期函数?)
shouldComponentUpdate 这个方法用来判断是否需要调用 render 方法重新描绘 dom。因为 dom 的描绘非常消耗性能,如果我们能在 shouldComponentUpdate 方法中能够写出更优化的 dom diff 算法,可以极大的提高性能。
在 react17 会删除以下三个生命周期
componentWillMount,componentWillReceiveProps , componentWillUpdate
Redux 中的 connect 有什么作用
connect 负责连接 React 和 Redux
(1)获取 state
connect 通过 context 获取 Provider 中的 store,通过 store.getState()
获取整个 store tree 上所有 state
(2)包装原组件
将 state 和 action 通过 props 的方式传入到原组件内部 wrapWithConnect 返回—个 ReactComponent 对 象 Connect,Connect 重 新 render 外部传入的原组件 WrappedComponent ,并把 connect 中传入的 mapStateToProps,mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent
(3)监听 store tree 变化
connect 缓存了 store tree 中 state 的状态,通过当前 state 状态 和变更前 state 状态进行比较,从而确定是否调用 this.setState()
方法触发 Connect 及其子组件的重新渲染
React 中的 setState 和 replaceState 的区别是什么?
(1)setState() setState()用于设置状态对象,其语法如下:
nextState,将要设置的新状态,该状态会和当前的 state 合并
callback,可选参数,回调函数。该函数会在 setState 设置成功,且组件重新渲染后调用。
合并 nextState 和当前 state,并重新渲染组件。setState 是 React 事件处理函数中和请求回调函数中触发 UI 更新的主要方法。
(2)replaceState() replaceState()方法与 setState()类似,但是方法只会保留 nextState 中状态,原 state 不在 nextState 中的状态都会被删除。其语法如下:
nextState,将要设置的新状态,该状态会替换当前的 state。
callback,可选参数,回调函数。该函数会在 replaceState 设置成功,且组件重新渲染后调用。
总结: setState 是修改其中的部分状态,相当于 Object.assign,只是覆盖,不会减少原来的状态。而 replaceState 是完全替换原来的状态,相当于赋值,将原来的 state 替换为另一个对象,如果新状态属性减少,那么 state 中就没有这个状态了。
为什么 useState 要使用数组而不是对象
useState 的用法:
可以看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢?
这里用到了解构赋值,所以先来看一下 ES6 的解构赋值:
数组的解构赋值
对象的解构赋值
看完这两个例子,答案应该就出来了:
如果 useState 返回的是数组,那么使用者可以对数组中的元素命名,代码看起来也比较干净
如果 useState 返回的是对象,在解构对象的时候必须要和 useState 内部实现返回的对象同名,想要使用多次的话,必须得设置别名才能使用返回值
下面来看看如果 useState 返回对象的情况:
这里可以看到,返回对象的使用方式还是挺麻烦的,更何况实际项目中会使用的更频繁。 总结:useState 返回的是 array 而不是 object 的原因就是为了降低使用的复杂度,返回数组的话可以直接根据顺序解构,而返回对象的话要想使用多次就需要定义别名了。
Redux 中异步的请求怎么处理
可以在 componentDidmount 中直接进⾏请求⽆须借助 redux。但是在⼀定规模的项⽬中,上述⽅法很难进⾏异步流的管理,通常情况下我们会借助 redux 的异步中间件进⾏异步处理。redux 异步流中间件其实有很多,当下主流的异步中间件有两种 redux-thunk、redux-saga。
(1)使用 react-thunk 中间件
redux-thunk 优点:
体积⼩: redux-thunk 的实现⽅式很简单,只有不到 20⾏代码
使⽤简单: redux-thunk 没有引⼊像 redux-saga 或者 redux-observable 额外的范式,上⼿简单
redux-thunk 缺陷:
样板代码过多: 与 redux 本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的
耦合严重: 异步操作与 redux 的 action 偶合在⼀起,不⽅便管理
功能孱弱: 有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装
使用步骤:
配置中间件,在 store 的创建中配置
添加一个返回函数的 actionCreator,将异步请求逻辑放在里面
生成 action,并发送 action
(2)使用 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,然后执行一个函数,那么可以把异步代码放在这个函数中,使用步骤如下:
配置中间件
将异步请求放在 sagas.js 中
发送 action
React 高阶组件是什么,和普通组件有什么区别,适用什么场景
官方解释∶
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由 react 自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。
1)HOC 的优缺点
优点∶ 逻辑服用、不影响被包裹组件的内部逻辑。
缺点∶hoc 传递给被包裹组件的 props 容易和被包裹后的组件重名,进而被覆盖
2)适用场景
代码复用,逻辑抽象
渲染劫持
State 抽象和更改
Props 更改
3)具体应用例子
权限控制: 利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别和 页面元素级别
组件渲染性能追踪: 借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录∶
注意:withTiming 是利用 反向继承 实现的一个高阶组件,功能是计算被包裹组件(这里是 Home 组件)的渲染时间。
页面复用
区分状态和 props
state 和 props 触发更新的生命周期分别有什么区别?
state 更新流程: 这个过程当中涉及的函数:
shouldComponentUpdate: 当组件的 state 或 props 发生改变时,都会首先触发这个生命周期函数。它会接收两个参数:nextProps, nextState——它们分别代表传入的新 props 和新的 state 值。拿到这两个值之后,我们就可以通过一些对比逻辑来决定是否有 re-render(重渲染)的必要了。如果该函数的返回值为 false,则生命周期终止,反之继续;
注意:此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。应该考虑使用内置的 PureComponent 组件,而不是手动编写
shouldComponentUpdate()
componentWillUpdate:当组件的 state 或 props 发生改变时,会在渲染之前调用 componentWillUpdate。componentWillUpdate 是 React16 废弃的三个生命周期之一。过去,我们可能希望能在这个阶段去收集一些必要的信息(比如更新前的 DOM 信息等等),现在我们完全可以在 React16 的 getSnapshotBeforeUpdate 中去做这些事;
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 来代替它。
评论