写点什么

前端二面 react 面试题(附答案)

作者:beifeng1996
  • 2022-11-16
    浙江
  • 本文字数:6420 字

    阅读完需:约 21 分钟

什么是 React 的 refs?为什么它们很重要

refs 允许你直接访问 DOM 元素或组件实例。为了使用它们,可以向组件添加个 ref 属性。如果该属性的值是一个回调函数,它将接受底层的 DOM 元素或组件的已挂载实例作为其第一个参数。可以在组件中存储它。


export class App extends Component {  showResult() {    console.log(this.input.value);  }  render() {    return (      <div>        <input type="text" ref={(input) => (this.input = input)} />        <button onClick={this.showResult.bind(this)}>展示结果</button>      </div>    );  }}
复制代码


如果该属性值是一个字符串, React 将会在组件实例化对象的 refs 属性中,存储一个同名属性,该属性是对这个 DOM 元素的引用。可以通过原生的 DOM API 操作它。


export class App extends Component {  showResult() {    console.log(this.refs.username.value);  }  render() {    return (      <div>        <input type="text" ref="username" />        <button onClick={this.showResu1t.bind(this)}>展示结果</button>      </div>    );  }}
复制代码

react-router4 的核心

  • 路由变成了组件

  • 分散到各个页面,不需要配置 比如<link> <route></route>

传入 setstate 函数的第二个参数的作用是什么?

第二个参数是一个函数,该函数会在 setState 函数调用完成并且组件开始重渲染时调用,可以用该函数来监听渲染是否完成。


this.setstate(  {    username: "有课前端网",  },  () => console.log("re-rendered success. "));
复制代码

新版生命周期

在新版本中,React 官方对生命周期有了新的 变动建议:


  • 使用getDerivedStateFromProps替换componentWillMount;

  • 使用getSnapshotBeforeUpdate替换componentWillUpdate;

  • 避免使用componentWillReceiveProps


其实该变动的原因,正是由于上述提到的 Fiber。首先,从上面我们知道 React 可以分成 reconciliationcommit两个阶段,对应的生命周期如下:


reconciliation


  • componentWillMount

  • componentWillReceiveProps

  • shouldComponentUpdate

  • componentWillUpdate


commit


  • componentDidMount

  • componentDidUpdate

  • componentWillUnmount


Fiber 中,reconciliation 阶段进行了任务分割,涉及到 暂停 和 重启,因此可能会导致 reconciliation 中的生命周期函数在一次更新渲染循环中被 多次调用 的情况,产生一些意外错误


新版的建议生命周期如下:


class Component extends React.Component {  // 替换 `componentWillReceiveProps` ,  // 初始化和 update 时被调用  // 静态函数,无法使用 this  static getDerivedStateFromProps(nextProps, prevState) {}
// 判断是否需要更新组件 // 可以用于组件性能优化 shouldComponentUpdate(nextProps, nextState) {}
// 组件被挂载后触发 componentDidMount() {}
// 替换 componentWillUpdate // 可以在更新之前获取最新 dom 数据 getSnapshotBeforeUpdate() {}
// 组件更新后调用 componentDidUpdate() {}
// 组件即将销毁 componentWillUnmount() {}
// 组件已销毁 componentDidUnMount() {}}
复制代码


使用建议:


  • constructor初始化 state

  • componentDidMount中进行事件监听,并在componentWillUnmount中解绑事件;

  • componentDidMount中进行数据的请求,而不是在componentWillMount

  • 需要根据 props 更新 state 时,使用getDerivedStateFromProps(nextProps, prevState)

  • 旧 props 需要自己存储,以便比较;


public static getDerivedStateFromProps(nextProps, prevState) {    // 当新 props 中的 data 发生变化时,同步更新到 state 上    if (nextProps.data !== prevState.data) {        return {            data: nextProps.data        }    } else {        return null1    }}
复制代码


可以在 componentDidUpdate 监听 props 或者 state 的变化,例如:


componentDidUpdate(prevProps) {    // 当 id 发生变化时,重新获取数据    if (this.props.id !== prevProps.id) {        this.fetchData(this.props.id);    }}
复制代码


  • 在 componentDidUpdate 使用 setState 时,必须加条件,否则将进入死循环;

  • getSnapshotBeforeUpdate(prevProps, prevState)可以在更新之前获取最新的渲染数据,它的调用是在 render 之后, update 之前;

  • shouldComponentUpdate: 默认每次调用 setState,一定会最终走到 diff 阶段,但可以通过 shouldComponentUpdate 的生命钩子返回 false 来直接阻止后面的逻辑执行,通常是用于做条件渲染,优化渲染的性能。


参考:前端react面试题详细解答

React 的 Fiber 工作原理,解决了什么问题

  • React Fiber 是一种基于浏览器的单线程调度算法。


React Fiber 用类似 requestIdleCallback 的机制来做异步 diff。但是之前数据结构不支持这样的实现异步 diff,于是 React 实现了一个类似链表的数据结构,将原来的 递归 diff 变成了现在的 遍历 diff,这样就能做到异步可更新了

在 ReactNative 中,如何解决 adb devices 找不到连接设备的问题?

在使用 Genymotion 时,首先需要在 SDK 的 platform-tools 中加入环境变量,然后在 Genymotion 中单击 Setting,选择 ADB 选项卡,单击 Use custom Android SDK tools,浏览本地 SDK 的位置,单击 OK 按钮就可以了。启动虛拟机后,在 cmd 中输入 adb devices 可以查看设备。

什么是高阶组件(HOC)

  • 高阶组件(Higher Order Componennt)本身其实不是组件,而是一个函数,这个函数接收一个元组件作为参数,然后返回一个新的增强组件,高阶组件的出现本身也是为了逻辑复用,举个例子


function withLoginAuth(WrappedComponent) {  return class extends React.Component {
constructor(props) { super(props); this.state = { isLogin: false }; }
async componentDidMount() { const isLogin = await getLoginStatus(); this.setState({ isLogin }); }
render() { if (this.state.isLogin) { return <WrappedComponent {...this.props} />; }
return (<div>您还未登录...</div>); } }}
复制代码

React 中 refs 的作用是什么

  • RefsReact 提供给我们的安全访问 DOM元素或者某个组件实例的句柄

  • 可以为元素添加ref属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回

redux 中间件

中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过 滤,日志输出,异常报告等功能


常见的中间件:


  • redux-logger:提供日志输出;

  • redux-thunk:处理异步操作;

  • redux-promise: 处理异步操作;

  • actionCreator 的返回值是 promise

React 怎么做数据的检查和变化

Model改变之后(可能是调用了setState),触发了virtual dom的更新,再用diff算法来把virtual DOM比较real DOM,看看是哪个dom节点更新了,再渲染real dom

为什么虚拟 dom 会提高性能

虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能


具体实现步骤如下


  • JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中

  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异

  • 把 2 所记录的差异应用到步骤 1 所构建的真正的DOM树上,视图就更新


虚拟 DOM 一定会提高性能吗?


很多人认为虚拟 DOM 一定会提高性能,一定会更快,其实这个说法有点片面,因为虚拟 DOM 虽然会减少 DOM 操作,但也无法避免 DOM 操作


  • 它的优势是在于 diff 算法和批量处理策略,将所有的 DOM 操作搜集起来,一次性去改变真实的 DOM,但在首次渲染上,虚拟 DOM 会多了一层计算,消耗一些性能,所以有可能会比 html 渲染的要慢

  • 注意,虚拟 DOM 实际上是给我们找了一条最短,最近的路径,并不是说比 DOM 操作的更快,而是路径最简单

react diff 算法

我们知道 React 会维护两个虚拟 DOM,那么是如何来比较,如何来判断,做出最优的解呢?这就用到了 diff 算法



diff 算法的作用


计算出 Virtual DOM 中真正变化的部分,并只针对该部分进行原生 DOM 操作,而非重新渲染整个页面。


传统 diff 算法


通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ,n 是树的节点数,这个有多可怕呢?——如果要展示 1000 个节点,得执行上亿次比较。。即便是 CPU 快能执行 30 亿条命令,也很难在一秒内计算出差异。


React 的 diff 算法


  1. 什么是调和?


将 Virtual DOM 树转换成 actual DOM 树的最少操作的过程 称为 调和 。


  1. 什么是 React diff 算法?


diff算法是调和的具体实现。


diff 策略


React 用 三大策略 将O(n^3)杂度 转化为 O(n)复杂度


策略一(tree diff):


  • Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计

  • 同级比较,既然 DOM 节点跨层级的移动操作少到可以忽略不计,那么 React 通过 updateDepth 对 Virtual DOM 树进行层级控制,也就是同一层,在对比的过程中,如果发现节点不在了,会完全删除不会对其他地方进行比较,这样只需要对树遍历一次就 OK 了


策略二(component diff):


  • 拥有相同类的两个组件 生成相似的树形结构,

  • 拥有不同类的两个组件 生成不同的树形结构。


策略三(element diff):


对于同一层级的一组子节点,通过唯一 id 区分。


tree diff


  • React 通过 updateDepth 对 Virtual DOM 树进行层级控制。

  • 对树分层比较,两棵树 只对同一层次节点 进行比较。如果该节点不存在时,则该节点及其子节点会被完全删除,不会再进一步比较。

  • 只需遍历一次,就能完成整棵 DOM 树的比较。



那么问题来了,如果 DOM 节点出现了跨层级操作,diff 会咋办呢?


答:diff 只简单考虑同层级的节点位置变换,如果是跨层级的话,只有创建节点和删除节点的操作。



如上图所示,以 A 为根节点的整棵树会被重新创建,而不是移动,因此 官方建议不要进行 DOM 节点跨层级操作,可以通过 CSS 隐藏、显示节点,而不是真正地移除、添加 DOM 节点

component diff

React 对不同的组件间的比较,有三种策略


  1. 同一类型的两个组件,按原策略(层级比较)继续比较 Virtual DOM 树即可。

  2. 同一类型的两个组件,组件 A 变化为组件 B 时,可能 Virtual DOM 没有任何变化,如果知道这点(变换的过程中,Virtual DOM 没有改变),可节省大量计算时间,所以 用户 可以通过 shouldComponentUpdate() 来判断是否需要 判断计算。

  3. 不同类型的组件,将一个(将被改变的)组件判断为dirty component(脏组件),从而替换 整个组件的所有节点。


注意:如果组件 D 和组件 G 的结构相似,但是 React 判断是 不同类型的组件,则不会比较其结构,而是删除 组件 D 及其子节点,创建组件 G 及其子节点。

element diff

当节点处于同一层级时,diff 提供三种节点操作:删除、插入、移动。


  • 插入:组件 C 不在集合(A,B)中,需要插入

  • 删除:

  • 组件 D 在集合(A,B,D)中,但 D 的节点已经更改,不能复用和更新,所以需要删除 旧的 D ,再创建新的。

  • 组件 D 之前在 集合(A,B,D)中,但集合变成新的集合(A,B)了,D 就需要被删除。

  • 移动:组件 D 已经在集合(A,B,C,D)里了,且集合更新时,D 没有发生更新,只是位置改变,如新集合(A,D,B,C),D 在第二个,无须像传统 diff,让旧集合的第二个 B 和新集合的第二个 D 比较,并且删除第二个位置的 B,再在第二个位置插入 D,而是 (对同一层级的同组子节点) 添加唯一 key 进行区分,移动即可。

diff 的不足与待优化的地方

尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,会影响 React 的渲染性能

react 有什么优点

  • 提高应用性能

  • 可以方便的在客户端和服务端使用

  • 使用 jsx 模板进行数据渲染,可读性好

在 Redux 中使用 Action 要注意哪些问题?

在 Redux 中使用 Action 的时候, Action 文件里尽量保持 Action 文件的纯净,传入什么数据就返回什么数据,最妤把请求的数据和 Action 方法分离开,以保持 Action 的纯净。

shouldComponentUpdate 的作用

shouldComponentUpdate 允许我们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新

hooks 为什么不能放在条件判断里

以 setState 为例,在 react 内部,每个组件(Fiber)的 hooks 都是以链表的形式存在 memoizeState 属性中



update 阶段,每次调用 setState,链表就会执行 next 向后移动一步。如果将 setState 写在条件判断中,假设条件判断不成立,没有执行里面的 setState 方法,会导致接下来所有的 setState 的取值出现偏移,从而导致异常发生。

这段代码有什么问题?

class App extends Component {  constructor(props) {    super(props);    this.state = {      username: "有课前端网",      msg: " ",    };  }  render() {    return <div> {this.state.msg}</div>;  }  componentDidMount() {    this.setState((oldState, props) => {      return {        msg: oldState.username + " - " + props.intro,      };    });  }}
复制代码


render ( < App intro=" 前端技术专业学习平台">,ickt )在页面中正常输出“有课前端网-前端技术专业学习平台”。但是这种写法很少使用,并不是常用的写法。React 允许对 setState 方法传递一个函数,它接收到先前的状态和属性数据并返回一个需要修改的状态对象,正如我们在上面所做的那样。它不但没有问题,而且如果根据以前的状态( state)以及属性来修改当前状态,推荐使用这种写法。

React 中的状态是什么?它是如何使用的

状态是 React 组件的核心,是数据的来源,必须尽可能简单。基本上状态是确定组件呈现和行为的对象。与 props 不同,它们是可变的,并创建动态和交互式组件。可以通过 this.state() 访问它们。

跨级组件的通信方式?

父组件向子组件的子组件通信,向更深层子组件通信:


  • 使用 props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递 props,增加了复杂度,并且这些 props 并不是中间组件自己需要的。

  • 使用 context,context 相当于一个大容器,可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用 context 实现。


// context方式实现跨级组件通信 // Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据const BatteryContext = createContext();//  子组件的子组件 class GrandChild extends Component {    render(){        return (            <BatteryContext.Consumer>                {                    color => <h1 style={{"color":color}}>我是红色的:{color}</h1>                }            </BatteryContext.Consumer>        )    }}//  子组件const Child = () =>{    return (        <GrandChild/>    )}// 父组件class Parent extends Component {      state = {          color:"red"      }      render(){          const {color} = this.state          return (          <BatteryContext.Provider value={color}>              <Child></Child>          </BatteryContext.Provider>          )      }}
复制代码

对 React 中 Fragment 的理解,它的使用场景是什么?

在 React 中,组件返回的元素只能有一个根元素。为了不添加多余的 DOM 节点,我们可以使用 Fragment 标签来包裹所有的元素,Fragment 标签不会渲染出任何元素。React 官方对 Fragment 的解释:


React 中的一个常见模式是一个组件返回多个元素。Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。


import React, { Component, Fragment } from 'react'
// 一般形式render() { return ( <React.Fragment> <ChildA /> <ChildB /> <ChildC /> </React.Fragment> );}// 也可以写成以下形式render() { return ( <> <ChildA /> <ChildB /> <ChildC /> </> );}
复制代码


用户头像

beifeng1996

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
前端二面react面试题(附答案)_React_beifeng1996_InfoQ写作社区