乐趣区

关于前端:阿里前端二面react面试题边面边更

shouldComponentUpdate 有什么用?为什么它很重要?

组件状态数据或者属性数据产生更新的时候,组件会进入存在期,视图会渲染更新。在生命周期办法 should ComponentUpdate 中,容许抉择退出某些组件(和它们的子组件)的和解过程。
和解的最终目标是依据新的状态,以最无效的形式更新用户界面。如果咱们晓得用户界面的某一部分不会扭转,那么没有理由让 React 弄清楚它是否应该更新渲染。通过在 shouldComponentUpdate 办法中返回 false, React 将让以后组件及其所有子组件放弃与以后组件状态雷同。

React 组件生命周期有哪些不同阶段?

在组件生命周期中有四个不同的阶段:

  1. Initialization:在这个阶段,组件筹备设置初始化状态和默认属性。
  2. Mounting:react 组件曾经筹备好挂载到浏览器 DOM 中。这个阶段包含 componentWillMountcomponentDidMount生命周期办法。
  3. Updating:在这个阶段,组件以两种形式更新,发送新的 props 和 state 状态。此阶段包含 shouldComponentUpdatecomponentWillUpdatecomponentDidUpdate生命周期办法。
  4. Unmounting:在这个阶段,组件曾经不再被须要了,它从浏览器 DOM 中卸载下来。这个阶段蕴含 componentWillUnmount 生命周期办法。
    除以上四个罕用生命周期外,还有一个错误处理的阶段:
    Error Handling:在这个阶段,不管在渲染的过程中,还是在生命周期办法中或是在任何子组件的构造函数中产生谬误,该组件都会被调用。这个阶段蕴含了 componentDidCatch 生命周期办法。

React 的虚构 DOM 和 Diff 算法的外部实现

传统 diff 算法的工夫复杂度是 O(n^3),这在前端 render 中是不可承受的。为了升高工夫复杂度,react 的 diff 算法做了一些斗争,放弃了最优解,最终将工夫复杂度升高到了 O(n)。

那么 react diff 算法做了哪些斗争呢?,参考如下:

  1. tree diff:只比照同一层的 dom 节点,疏忽 dom 节点的跨层级挪动

如下图,react 只会对雷同色彩方框内的 DOM 节点进行比拟,即同一个父节点下的所有子节点。当发现节点不存在时,则该节点及其子节点会被齐全删除掉,不会用于进一步的比拟。

这样只须要对树进行一次遍历,便能实现整个 DOM 树的比拟。

这就意味着,如果 dom 节点产生了跨层级挪动,react 会删除旧的节点,生成新的节点,而不会复用。

  1. component diff:如果不是同一类型的组件,会删除旧的组件,创立新的组件
  1. element diff:对于同一层级的一组子节点,须要通过惟一 id 进行来辨别
  2. 如果没有 id 来进行辨别,一旦有插入动作,会导致插入地位之后的列表全副从新渲染
  3. 这也是为什么渲染列表时为什么要应用惟一的 key。

生命周期调用办法的程序是什么?

React 生命周期分为三大周期,11 个阶段,生命周期办法调用程序别离如下。
(1)在创立期的五大阶段,调用办法的程序如下。

  • getDetaultProps:定义默认属性数据。
  • getInitialState:初始化默认状态数据。
  • component WillMount:组件行将被构建。
  • render:渲染组件。
  • componentDidMount:组件构建实现

(2)在存在期的五大阶段,调用办法的程序如下。

  • componentWillReceiveProps:组件行将接管新的属性数据。
  • shouldComponentUpdate:判断组件是否应该更新。
  • componnent WillUpdate:组件行将更新。
  • render:渲染组件。
  • componentDidUpdate:组件更新实现。

(3)在销毁期的一个阶段,调用办法 componentWillUnmount,示意组件行将被销毀。

在哪个生命周期中你会收回 Ajax 申请?为什么?

Ajax 申请应该写在组件创立期的第五个阶段,即 componentDidMount 生命周期办法中。起因如下。
在创立期的其余阶段,组件尚未渲染实现。而在存在期的 5 个阶段,又不能确保生命周期办法肯定会执行(如通过 shouldComponentUpdate 办法优化更新等)。在销毀期,组件行将被销毁,申请数据变得无意义。因而在这些阶段发岀 Ajax 申请显然不是最好的抉择。
在组件尚未挂载之前,Ajax 申请将无奈执行结束,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount 办法中,执行 Ajax 即可保障组件曾经挂载,并且可能失常更新组件。

为什么要应用 React. Children. map(props. children,()=>)而不是 props. children. map (() => )?

因为不能保障 props. children 将是一个数组。
以上面的代码为例。

<Parent>
    <h1> 有课前端网 </h1>
</Parent>

在父组件外部,如果尝试应用 props.children. map 映射子对象,则会抛出谬误,因为 props. children 是一个对象,而不是一个数组。
如果有多个子元素,React 会使 props.children 成为一个数组,如下所示。

<Parent>
  <h1> 有课前端网 </h1>
  <h2> 前端技术学习平台 </h2>
</Parent>;
// 不倡议应用如下形式,在这个案例中会抛出谬误。class Parent extends Component {render() {return <div> {this.props.children.map((obj) => obj)}</div>;
  }
}

倡议应用如下形式,防止在上一个案例中抛出谬误。

class Parent extends Component {render() {return <div> {React.Children.map(this.props.children, (obj) => obj)}</div>;
  }
}

参考 前端进阶面试题具体解答

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 的渲染性能

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

在应用 Genymotion 时,首先须要在 SDK 的 platform-tools 中退出环境变量,而后在 Genymotion 中单击 Setting,抉择 ADB 选项卡,单击 Use custom Android SDK tools,浏览本地 SDK 的地位,单击 OK 按钮就能够了。启动虛拟机后,在 cmd 中输出 adb devices 能够查看设施。

调用 setState 之后产生了什么

在代码中调用 setState 函数之后,React 会将传入的参数与之前的状态进行合并,而后触发所谓的和谐过程(Reconciliation)。通过和谐过程,React 会以绝对高效的形式依据新的状态构建 React 元素树并且着手从新渲染整个 UI 界面。在 React 失去元素树之后,React 会计算出新的树和老的树之间的差别,而后依据差别对界面进行最小化从新渲染。通过 diff 算法,React 可能准确制导哪些地位产生了扭转以及应该如何扭转,这就保障了按需更新,而不是全副从新渲染。

  • 在 setState 的时候,React 会为以后节点创立一个 updateQueue 的更新列队。
  • 而后会触发 reconciliation 过程,在这个过程中,会应用名为 Fiber 的调度算法,开始生成新的 Fiber 树,Fiber 算法的最大特点是能够做到异步可中断的执行。
  • 而后 React Scheduler 会依据优先级高下,先执行优先级高的节点,具体是执行 doWork 办法。
  • 在 doWork 办法中,React 会执行一遍 updateQueue 中的办法,以取得新的节点。而后比照新旧节点,为老节点打上 更新、插入、替换 等 Tag。
  • 以后节点 doWork 实现后,会执行 performUnitOfWork 办法取得新节点,而后再反复下面的过程。
  • 当所有节点都 doWork 实现后,会触发 commitRoot 办法,React 进入 commit 阶段。
  • 在 commit 阶段中,React 会依据后面为各个节点打的 Tag,一次性更新整个 dom 元素

ref 是一个函数又有什么益处?

  • 不便 react 销毁组件、从新渲染的时候去清空 refs 的货色,避免内存泄露

为什么虚构 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 操作的更快,而是门路最简略

这段代码有什么问题?

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=” 前端技术业余学习平台 ”></App>,ickt)
在页面中失常输入“有课前端网 - 前端技术业余学习平台”。然而这种写法很少应用,并不是罕用的写法。React 容许对 setState 办法传递一个函数,它接管到先前的状态和属性数据并返回一个须要批改的状态对象,正如咱们在下面所做的那样。它岂但没有问题,而且如果依据以前的状态(state)以及属性来批改以后状态,举荐应用这种写法。

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

第二个参数是一个函数,该函数会在 setState 函数调用实现并且组件开始重渲染时调用,能够用该函数来监听渲染是否实现。

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

react-router 里的 <Link> 标签和 <a> 标签有什么区别

比照 <a>,Link 组件防止了不必要的重渲染

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

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

高阶组件存在的问题

  • 静态方法失落(必须将静态方法做拷贝)
  • refs 属性不能透传 (如果你向一个由高阶组件创立的组件的元素增加ref 援用,那么 ref 指向的是最外层容器组件实例的,而不是被包裹的 WrappedComponent 组件。)
  • 反向继承不能保障残缺的子组件树被解析
    React 组件有两种模式,别离是 class 类型和 function 类型(无状态组件)。

咱们晓得反向继承的渲染劫持能够管制 WrappedComponent 的渲染过程,也就是说这个过程中咱们能够对 elements treestatepropsrender() 的后果做各种操作。

然而如果渲染 elements tree 中蕴含了 function 类型的组件的话,这时候就不能操作组件的子组件了。

React 中什么是受控组件和非控组件?

(1)受控组件 在应用表单来收集用户输出时,例如<input><select><textearea> 等元素都要绑定一个 change 事件,当表单的状态发生变化,就会触发 onChange 事件,更新组件的 state。这种组件在 React 中被称为 受控组件,在受控组件中,组件渲染出的状态与它的 value 或 checked 属性绝对应,react 通过这种形式打消了组件的部分状态,使整个状态可控。react 官网举荐应用受控表单组件。

受控组件更新 state 的流程:

  • 能够通过初始 state 中设置表单的默认值
  • 每当表单的值发生变化时,调用 onChange 事件处理器
  • 事件处理器通过事件对象 e 拿到扭转后的状态,并更新组件的 state
  • 一旦通过 setState 办法更新 state,就会触发视图的从新渲染,实现表单组件的更新

受控组件缺点: 表单元素的值都是由 React 组件进行治理,当有多个输入框,或者多个这种组件时,如果想同时获取到全副的值就必须每个都要编写事件处理函数,这会让代码看着很臃肿,所以为了解决这种状况,呈现了非受控组件。

(2)非受控组件 如果一个表单组件没有 value props(单选和复选按钮对应的是 checked props)时,就能够称为非受控组件。在非受控组件中,能够应用一个 ref 来从 DOM 取得表单值。而不是为每个状态更新编写一个事件处理程序。

React 官网的解释:

要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你能够应用 ref 来从 DOM 节点中获取表单数据。
因为非受控组件将实在数据贮存在 DOM 节点中,所以在应用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码好看性,并且心愿疾速编写代码,应用非受控组件往往能够缩小你的代码量。否则,你应该应用受控组件。

例如,上面的代码在非受控组件中接管单个属性:

class NameForm extends React.Component {constructor(props) {super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
  }
  handleSubmit(event) {alert('A name was submitted:' + this.input.value);
    event.preventDefault();}
  render() {
    return (<form onSubmit={this.handleSubmit}>
        <label>
          Name:          <input type="text" ref={(input) => this.input = input} />        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

总结: 页面中所有输出类的 DOM 如果是现用现取的称为非受控组件,而通过 setState 将输出的值保护到了 state 中,须要时再从 state 中取出,这里的数据就受到了 state 的管制,称为受控组件。

父子组件的通信形式?

父组件向子组件通信:父组件通过 props 向子组件传递须要的信息。

// 子组件: Child
const Child = props =>{return <p>{props.name}</p>
}
// 父组件 Parent
const Parent = ()=>{return <Child name="react"></Child>}

子组件向父组件通信:: props+ 回调的形式。

// 子组件: Child
const Child = props =>{
  const cb = msg =>{return ()=>{props.callback(msg)
      }
  }
  return (<button onClick={cb("你好!")}> 你好 </button>
  )
}
// 父组件 Parent
class Parent extends Component {callback(msg){console.log(msg)
    }
    render(){return <Child callback={this.callback.bind(this)}></Child>    
    }
}

React- Router 有几种模式?

有以下几种模式。
HashRouter,通过散列实现,路由要带 #。
BrowerRouter,利用 HTML5 中 history API 实现,须要服务器端反对,兼容性不是很好。

什么是高阶组件(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>);
      }
  }
}
退出移动版