写点什么

前端经典 react 面试题(持续更新中)

作者:xiaofeng
  • 2023-03-15
    浙江
  • 本文字数:7164 字

    阅读完需:约 24 分钟

React 必须使用 JSX 吗?

React 并不强制要求使用 JSX。当不想在构建环境中配置有关 JSX 编译时,不在 React 中使用 JSX 会更加方便。


每个 JSX 元素只是调用 React.createElement(component, props, ...children) 的语法糖。因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。


例如,用 JSX 编写的代码:


class Hello extends React.Component {  render() {    return <div>Hello {this.props.toWhat}</div>;  }}ReactDOM.render(  <Hello toWhat="World" />,  document.getElementById('root'));
复制代码


可以编写为不使用 JSX 的代码:


class Hello extends React.Component {  render() {    return React.createElement('div', null, `Hello ${this.props.toWhat}`);  }}ReactDOM.render(  React.createElement(Hello, {toWhat: 'World'}, null),  document.getElementById('root'));
复制代码

用户不同权限 可以查看不同的页面 如何实现?

  1. Js 方式

  2. 根据用户权限类型,把菜单配置成 json, 没有权限的直接不显示

  3. react-router 方式 在 route 标签上 添加 onEnter 事件,进入路由之前替换到首页


<Route path="/home" component={App} onEnter={(nexState,replace)=>{      if(nexState.location.pathname!=='/'){         var  sid = UtilsMoudle.getSidFromUrl(nexState);         if(!sid){            replace("/")         }else{            console.log(sid);         }      }    }}>
复制代码


  1. 自己封装一个 privateRouter 组件 里面判断是否有权限,有的话返回

  2. 没有权限的话 component 返回一个提示信息的组件。

  3. 扩展一下,如果是根据用权限来判断是否隐藏组件该怎么做呢?

  4. react 可以使用高阶组件,在高阶组件里面判断是否有权限,然后判断是否返回组件,无权限返回 null

  5. vue 可以使用自定义指令,如果没有权限移除组件


// 需要在入口处添加自定义权限指令v-auth,显示可操作组件Vue.directive('auth', {    bind: function (el, binding, vnode) {        // 用户权限表        const rules = auths        for (let i = 0; i < rules.length; i++) {            const item = rules[i]            if(!binding.value || (binding.value == item.auth)){                // 权限允许则显示组件                return true            }        }        // 移除组件        el.parentNode.removeChild(el)    }})// 使用<template>  <div>    <Button v-auth="admin_user_add">添加用户</Button>    <Button v-auth="admin_user_del">删除用户</Button>    <Button v-auth="admin_user_edit">编辑用户</Button>  </div></template>
复制代码

react 的渲染过程中,兄弟节点之间是怎么处理的?也就是 key 值不一样的时候

通常我们输出节点的时候都是 map 一个数组然后返回一个ReactNode,为了方便react内部进行优化,我们必须给每一个reactNode添加key,这个key prop在设计值处不是给开发者用的,而是给 react 用的,大概的作用就是给每一个reactNode添加一个身份标识,方便 react 进行识别,在重渲染过程中,如果 key 一样,若组件属性有所变化,则react只更新组件对应的属性;没有变化则不更新,如果 key 不一样,则 react 先销毁该组件,然后重新创建该组件

调用 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 元素

key 的作用

是给每一个 vnode 的唯一 id,可以依靠 key,更准确,更快的拿到 oldVnode 中对应的 vnode 节点


<!-- 更新前 --><div>  <p key="ka">ka</p>  <h3 key="song">song</he></div>
<!-- 更新后 --><div> <h3 key="song">song</h3> <p key="ka">ka</p></div>
复制代码


如果没有 key,React 会认为 div 的第一个子节点由 p 变成 h3,第二个子节点由 h3 变成 p,则会销毁这两个节点并重新构造。


但是当我们用 key 指明了节点前后对应关系后,React 知道 key === "ka" 的 p 更新后还在,所以可以复用该节点,只需要交换顺序。


key 是 React 用来追踪哪些列表元素被修改、被添加或者被移除的辅助标志。


在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React diff 算法中,React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重新渲染。同时,React 还需要借助 key 来判断元素与本地状态的关联关系。

说说你用 react 有什么坑点?

1. JSX 做表达式判断时候,需要强转为 boolean 类型


如果不使用 !!b 进行强转数据类型,会在页面里面输出 0


render() {  const b = 0;  return <div>    {      !!b && <div>这是一段文本</div>    }  </div>}
复制代码


2. 尽量不要在 componentWillReviceProps 里使用 setState,如果一定要使用,那么需要判断结束条件,不然会出现无限重渲染,导致页面崩溃


3. 给组件添加 ref 时候,尽量不要使用匿名函数,因为当组件更新的时候,匿名函数会被当做新的 prop 处理,让 ref 属性接受到新函数的时候,react 内部会先清空 ref,也就是会以 null 为回调参数先执行一次 ref 这个 props,然后在以该组件的实例执行一次 ref,所以用匿名函数做 ref 的时候,有的时候去 ref 赋值后的属性会取到 null


4. 遍历子节点的时候,不要用 index 作为组件的 key 进行传入


参考 前端进阶面试题详细解答

refs 的作用是什么,你在什么样的业务场景下使用 refs

  • 操作 DOM,为什么操作 DOM?

  • 场景

  • 图片渲染好后,操作图片宽高。比如做个放大镜功能

为什么虚拟 dom 会提高性能

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


具体实现步骤如下:


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

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

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

setState 到底是异步还是同步?

先给出答案: 有时表现出异步,有时表现出同步


  • setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的

  • setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数setState(partialState, callback)中的callback拿到更新后的结果

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setStatesetState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新

react-router4 的核心

  • 路由变成了组件

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

React 生命周期函数

挂载阶段


挂载阶段也可以理解为初始化阶段,也就是把我们的组件插入到 DOM 中。


  • constructor

  • getDerivedStateFromProps

  • ~~UNSAFE_componentWillMount~~

  • render

  • (React Updates DOM and refs)

  • componentDidMount


  1. constructor


组件的构造函数,第一个被执行。显式定义构造函数时,需要在第一行执行 super(props),否则不能再构造函数中拿到 this


在构造函数中,我们一般会做两件事:


  • 初始化 state

  • 对自定义方法进行 this 绑定


  1. getDerivedStateFromProps


是一个静态函数,所以不能在这里使用 this,也表明了 React 官方不希望调用方滥用这个生命周期函数。每当父组件引发当前组件的渲染过程时,getDerivedStateFromProps 都会被调用,这样我们有机会根据新的 props 和当前的 state 来调整一个新的 state。


这个函数会在收到新的 props,调用了 setState 或 forceUpdate 时被调用。


  1. render


React 最核心的方法,class 组件中必须实现的方法。


当 render 被调用时,它会检查 this.propsthis.state 的变化并返回一下类型之一:


  • 原生的 DOM,如 div

  • React 组件

  • 数组或 Fragment

  • Portals(传送门)

  • 字符串或数字,被渲染成文本节点

  • 布尔值或 null,不会渲染任何东西


  1. componentDidMount


在组件挂载之后立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。这个方法比较适合添加订阅的地方,如果添加了订阅,请记得在卸载的时候取消订阅。


你可以在 componentDidMount 里面直接调用 setState,它将触发额外渲染,但此渲染会发生在浏览器更新屏幕之前,如此保证了即使 render 了两次,用户也不会看到中间状态。


更新阶段


更新阶段是指当组件的 props 发生了改变,或者组件内部调用了 setState 或者发生了 forceUpdate,这个阶段的过程包括:


  • UNSAFE_componentWillReceiveProps

  • getDerivedStateFromProps

  • sholdComponentUpdate

  • UNSAFE_componentWIllUpdate

  • render

  • getSnapshotBeforeUpdate

  • (React Updates DOM and refs)

  • componentDidUpdate


  1. shouldComponentUpdate


它有两个参数,根据此函数的返回值来判断是否重新进行渲染,首次渲染或者是当我们调用了 forceUpdate 时并不会触发此方法,此方法仅用于性能优化。


但是官方提倡我们使用内置的 PureComponent 而不是自己编写 shouldComponentUpdate。


  1. getSnapshotBeforeUpdate


这个生命周期函数发生在 render 之后,在更新之前,给了一个机会去获取 DOM 信息,计算得到并返回一个 snapshot,这个 snapshot 会作为 componentDidUpdate 第三个参数传入。


  1. componentDidUpdate


这个函数会在更新后被立即调用,首次渲染不会执行此方法。在这个函数中我们可以操作 DOM,可以发起请求,还可以 setState,但注意一定要用条件语句,否则会导致无限循环。


卸载阶段


  1. componentWillUnmount


这个生命周期函数会在组件卸载销毁之前被调用,我们可以在这里执行一些清除操作。不要在这里调用 setState,因为组件不会重新渲染。

Hooks 可以取代 render props 和高阶组件吗?

通常,render props和高阶组件仅渲染一个子组件。React 团队认为,Hooks 是服务此用例的更简单方法。这两种模式仍然有一席之地(例如,一个虚拟的 scroller 组件可能有一个 renderItem prop,或者一个可视化的容器组件可能有它自己的 DOM 结构)。但在大多数情况下,Hooks 就足够了,可以帮助减少树中的嵌套。

setState 是同步的还是异步的

有时表现出同步,有时表现出异步


  1. setState 只有在 React 自身的合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步的

  2. setState 的异步并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的异步。当然可以通过 setState 的第二个参数中的 callback 拿到更新后的结果

  3. setState 的批量更新优化也是建立在异步(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在异步中如果对同一个值进行多次 setState,setState 的批量更新策略会对其进行覆盖,去最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新


  • 合成事件中是异步

  • 钩子函数中的是异步

  • 原生事件中是同步

  • setTimeout 中是同步

Redux 实现原理解析

为什么要用 redux


React中,数据在组件中是单向流动的,数据从一个方向父组件流向子组件(通过props),所以,两个非父子组件之间通信就相对麻烦,redux的出现就是为了解决state里面的数据问题


Redux 设计理念


Redux是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图



Redux 三大原则


  • 唯一数据源


整个应用的 state 都被存储到一个状态树里面,并且这个状态树,只存在于唯一的 store 中


  • 保持只读状态


state是只读的,唯一改变state的方法就是触发actionaction是一个用于描述以发生时间的普通对象


  • 数据改变只能通过纯函数来执行


使用纯函数来执行修改,为了描述action如何改变state的,你需要编写reducers


Redux 源码


let createStore = (reducer) => {    let state;    //获取状态对象    //存放所有的监听函数    let listeners = [];    let getState = () => state;    //提供一个方法供外部调用派发action    let dispath = (action) => {        //调用管理员reducer得到新的state        state = reducer(state, action);        //执行所有的监听函数        listeners.forEach((l) => l())    }    //订阅状态变化事件,当状态改变发生之后执行监听函数    let subscribe = (listener) => {        listeners.push(listener);    }    dispath();    return {        getState,        dispath,        subscribe    }}let combineReducers=(renducers)=>{    //传入一个renducers管理组,返回的是一个renducer    return function(state={},action={}){        let newState={};        for(var attr in renducers){            newState[attr]=renducers[attr](state[attr],action)
} return newState; }}export {createStore,combineReducers};
复制代码

讲讲什么是 JSX ?

Facebook 第一次发布 React 时,他们还引入了一种新的 JS 方言 JSX,将原始 HTML 模板嵌入到 JS 代码中。JSX 代码本身不能被浏览器读取,必须使用Babelwebpack等工具将其转换为传统的 JS。很多开发人员就能无意识使用 JSX,因为它已经与 React 结合在一直了。


class MyComponent extends React.Component {  render() {    let props = this.props;    return (      <div className="my-component">        <a href={props.url}>{props.name}</a>      </div>    );  }}
复制代码

如何避免重复发起 ajax 获取数据?

  • 数据放在 redux 里面

什么是高阶组件(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>); } }}
复制代码

什么是虚拟 DOM?

虚拟 DOM (VDOM)是真实 DOM 在内存中的表示。UI 的表示形式保存在内存中,并与实际的 DOM 同步。这是一个发生在渲染函数被调用和元素在屏幕上显示之间的步骤,整个过程被称为调和。

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 进行来区分


  • 如果没有 id 来进行区分,一旦有插入动作,会导致插入位置之后的列表全部重新渲染

  • 这也是为什么渲染列表时为什么要使用唯一的 key。

在 React 中元素( element)和组件( component)有什么区别?

简单地说,在 React 中元素(虛拟 DOM)描述了你在屏幕上看到的 DOM 元素。换个说法就是,在 React 中元素是页面中 DOM 元素的对象表示方式。在 React 中组件是一个函数或一个类,它可以接受输入并返回一个元素。注意:工作中,为了提高开发效率,通常使用 JSX 语法表示 React 元素(虚拟 DOM)。在编译的时候,把它转化成一个 React. createElement 调用方法。


用户头像

xiaofeng

关注

努力写代码中 2022-08-18 加入

努力写代码中

评论

发布
暂无评论
前端经典react面试题(持续更新中)_前端_xiaofeng_InfoQ写作社区