写点什么

前端 react 面试题(边面边更)

作者:beifeng1996
  • 2023-02-13
    浙江
  • 本文字数:6150 字

    阅读完需:约 20 分钟

diff 算法是怎么运作

每一种节点类型有自己的属性,也就是 prop,每次进行 diff 的时候,react 会先比较该节点类型,假如节点类型不一样,那么 react 会直接删除该节点,然后直接创建新的节点插入到其中,假如节点类型一样,那么会比较 prop 是否有更新,假如有 prop 不一样,那么 react 会判定该节点有更新,那么重渲染该节点,然后在对其子节点进行比较,一层一层往下,直到没有子节点

react 和 vue 的区别

相同点:


  1. 数据驱动页面,提供响应式的试图组件

  2. 都有 virtual DOM,组件化的开发,通过 props 参数进行父子之间组件传递数据,都实现了 webComponents 规范

  3. 数据流动单向,都支持服务器的渲染 SSR

  4. 都有支持 native 的方法,react 有 React native, vue 有 wexx


不同点:


  1. 数据绑定:Vue 实现了双向的数据绑定,react 数据流动是单向的

  2. 数据渲染:大规模的数据渲染,react 更快

  3. 使用场景:React 配合 Redux 架构适合大规模多人协作复杂项目,Vue 适合小快的项目

  4. 开发风格:react 推荐做法 jsx + inline style 把 html 和 css 都写在 js 了


vue 是采用 webpack +vue-loader 单文件组件格式,html, js, css 同一个文件

在哪个生命周期中你会发出 Ajax 请求?为什么?

Ajax 请求应该写在组件创建期的第五个阶段,即 componentDidMount 生命周期方法中。原因如下。在创建期的其他阶段,组件尚未渲染完成。而在存在期的 5 个阶段,又不能确保生命周期方法一定会执行(如通过 shouldComponentUpdate 方法优化更新等)。在销毀期,组件即将被销毁,请求数据变得无意义。因此在这些阶段发岀 Ajax 请求显然不是最好的选择。在组件尚未挂载之前,Ajax 请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。在 componentDidMount 方法中,执行 Ajax 即可保证组件已经挂载,并且能够正常更新组件。

简述 flux 思想

Flux 的最大特点,就是数据的"单向流动"。


  • 用户访问 View

  • View发出用户的 Action

  • Dispatcher 收到Action,要求 Store 进行相应的更新

  • Store 更新后,发出一个"change"事件

  • View 收到"change"事件后,更新页面

说说 React 组件开发中关于作用域的常见问题。

在 EMAScript5 语法规范中,关于作用域的常见问题如下。(1)在 map 等方法的回调函数中,要绑定作用域 this(通过 bind 方法)。(2)父组件传递给子组件方法的作用域是父组件实例化对象,无法改变。(3)组件事件回调函数方法的作用域是组件实例化对象(绑定父组件提供的方法就是父组件实例化对象),无法改变。在 EMAScript6 语法规范中,关于作用域的常见问题如下。(1)当使用箭头函数作为 map 等方法的回调函数时,箭头函数的作用域是当前组件的实例化对象(即箭头函数的作用域是定义时的作用域),无须绑定作用域。(2)事件回调函数要绑定组件作用域。(3)父组件传递方法要绑定父组件作用域。总之,在 EMAScript6 语法规范中,组件方法的作用域是可以改变的。

如何用 React 构建( build)生产模式?

通常,使用 Webpack 的 DefinePlugin 方法将 NODE ENV 设置为 production。这将剥离 propType 验证和额外的警告。除此之外,还可以减少代码,因为 React 使用 Uglify 的 dead-code 来消除开发代码和注释,这将大大减少包占用的空间。


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

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

  • 数据放在 redux 里面

React 如何进行组件/逻辑复用?

抛开已经被官方弃用的 Mixin,组件抽象的技术目前有三种比较主流:


  • 高阶组件:

  • 属性代理

  • 反向继承

  • 渲染属性

  • react-hooks

setState 到底是异步还是同步?

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


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

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

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

Redux 中间件原理

  • 指的是 action 和 store 之间,沟通的桥梁就是 dispatch,action 就是个对象。比如你用了 redux-thunk,action 也可以是个函数,怎么实现这个过程,就是通过中间件这个桥梁帮你实现的。action 到达 store 之前会走中间件,这个中间件会把函数式的 action 转化为一个对象,在传递给 store

HOC(高阶组件)

HOC(Higher Order Componennt) 是在 React 机制下社区形成的一种组件模式,在很多第三方开源库中表现强大。


简述:


  • 高阶组件不是组件,是 增强函数,可以输入一个元组件,返回出一个新的增强组件;

  • 高阶组件的主要作用是 代码复用,操作 状态和参数;


用法:


  • 属性代理 (Props Proxy): 返回出一个组件,它基于被包裹组件进行 功能增强;


  1. 默认参数: 可以为组件包裹一层默认参数;


function proxyHoc(Comp) {    return class extends React.Component {        render() {            const newProps = {                name: 'tayde',                age: 1,            }            return <Comp {...this.props} {...newProps} />        }    }}
复制代码


  1. 提取状态: 可以通过 props 将被包裹组件中的 state 依赖外层,例如用于转换受控组件:


function withOnChange(Comp) {    return class extends React.Component {        constructor(props) {            super(props)            this.state = {                name: '',            }        }        onChangeName = () => {            this.setState({                name: 'dongdong',            })        }        render() {            const newProps = {                value: this.state.name,                onChange: this.onChangeName,            }            return <Comp {...this.props} {...newProps} />        }    }}
复制代码


使用姿势如下,这样就能非常快速的将一个 Input 组件转化成受控组件。


const NameInput = props => (<input name="name" {...props} />)export default withOnChange(NameInput)
复制代码


包裹组件: 可以为被包裹元素进行一层包装,


function withMask(Comp) {  return class extends React.Component {      render() {          return (              <div>                  <Comp {...this.props} />                    <div style={{                      width: '100%',                      height: '100%',                      backgroundColor: 'rgba(0, 0, 0, .6)',                  }}               </div>          )      }  }}
复制代码


反向继承 (Inheritance Inversion): 返回出一个组件,继承于被包裹组件,常用于以下操作


function IIHoc(Comp) {    return class extends Comp {        render() {            return super.render();        }    };}
复制代码


渲染劫持 (Render Highjacking)


条件渲染: 根据条件,渲染不同的组件


function withLoading(Comp) {    return class extends Comp {        render() {            if(this.props.isLoading) {                return <Loading />            } else {                return super.render()            }        }    };}
复制代码


可以直接修改被包裹组件渲染出的 React 元素树


操作状态 (Operate State) : 可以直接通过 this.state 获取到被包裹组件的状态,并进行操作。但这样的操作容易使 state 变得难以追踪,不易维护,谨慎使用。


应用场景:


权限控制,通过抽象逻辑,统一对页面进行权限判断,按不同的条件进行页面渲染:


function withAdminAuth(WrappedComponent) {    return class extends React.Component {        constructor(props){            super(props)            this.state = {                isAdmin: false,            }        }         async componentWillMount() {            const currentRole = await getCurrentUserRole();            this.setState({                isAdmin: currentRole === 'Admin',            });        }        render() {            if (this.state.isAdmin) {                return <Comp {...this.props} />;            } else {                return (<div>您没有权限查看该页面,请联系管理员!</div>);            }        }    };}
复制代码


性能监控 ,包裹组件的生命周期,进行统一埋点:


function withTiming(Comp) {    return class extends Comp {        constructor(props) {            super(props);            this.start = Date.now();            this.end = 0;        }        componentDidMount() {            super.componentDidMount && super.componentDidMount();            this.end = Date.now();            console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);        }        render() {            return super.render();        }    };}
复制代码


代码复用,可以将重复的逻辑进行抽象。


使用注意:


  • 纯函数: 增强函数应为纯函数,避免侵入修改元组件;

  • 避免用法污染: 理想状态下,应透传元组件的无关参数与事件,尽量保证用法不变;

  • 命名空间: 为 HOC 增加特异性的组件名称,这样能便于开发调试和查找问题;

  • 引用传递 : 如果需要传递元组件的 refs 引用,可以使用 React.forwardRef;

  • 静态方法 : 元组件上的静态方法并无法被自动传出,会导致业务层无法调用;解决:

  • 函数导出

  • 静态方法赋值

  • 重新渲染: 由于增强函数每次调用是返回一个新组件,因此如果在 Render 中使用增强函数,就会导致每次都重新渲染整个 HOC,而且之前的状态会丢失;

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 <BaseComponent {...this.props} {...this.state}/>    }  }  return DerivedClass;}const MyComponent = (props) => {  return <div>Window width is: {props.windowWidth}</div>};export default withWindowWidth(MyComponent);
复制代码


装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下:


@testable   class MyTestableClass {}
复制代码

在生命周期中的哪一步你应该发起 AJAX 请求

我们应当将 AJAX 请求放到 componentDidMount 函数中执行,主要原因有下


  • React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componentWillMount。如果我们将 AJAX 请求放到 componentWillMount 函数中,那么显而易见其会被触发多次,自然也就不是好的选择。

  • 如果我们将AJAX 请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 请求则能有效避免这个问题

你理解“在 React 中,一切都是组件”这句话。

组件是 React 应用 UI 的构建块。这些组件将整个 UI 分成小的独立并可重用的部分。每个组件彼此独立,而不会影响 UI 的其余部分。

组件更新有几种方法

  • this.setState() 修改状态的时候 会更新组件

  • this.forceUpdate() 强制更新

  • 组件件 render 之后,子组件使用到父组件中状态,导致子组件的 props 属性发生改变的时候 也会触发子组件的更新

组件之间传值

  • 父组件给子组件传值

  • 在父组件中用标签属性的=形式传值

  • 在子组件中使用 props 来获取值

  • 子组件给父组件传值

  • 在组件中传递一个函数

  • 在子组件中用 props 来获取传递的函数,然后执行该函数

  • 在执行函数的时候把需要传递的值当成函数的实参进行传递

  • 兄弟组件之间传值

  • 利用父组件

  • 先把数据通过 【子组件】===》【父组件】

  • 然后在数据通过 【父组件】===〉【子组件】

  • 消息订阅

  • 使用 PubSubJs 插件

(在构造函数中)调用 super(props) 的目的是什么

super() 被调用之前,子类是不能使用 this 的,在 ES2015 中,子类必须在 constructor 中调用 super()。传递 propssuper() 的原因则是便于(在子类中)能在 constructor 访问 this.props

diff 虚拟 DOM 比较的规则

  • 【旧虚拟 DOM】 与 【新虚拟 DOM】中相同 key

  • 若虚拟 DOM 中的内容没有发生改变,直接使用旧的虚拟 DOM

  • 若虚拟 DOM 中的内容发生改变了,则生成新真实的 DOM,随后替换页面中之前的真实 DOM

  • 【旧虚拟 DOM】 中未找到 与 【新虚拟 DOM】相同的 key

  • 根据数据创建真实 DOM,随后渲染到页面

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 只深拷贝的数据的第一层,所以说不是最完美的解决办法:


const o2 = Object.assign({},this.state.obj)    o2.student.count = '00000';    this.setState({        obj: o2,    })
复制代码


(2)使用 JSON.parse(JSON.stringfy())进行深拷贝,但是遇到数据为 undefined 和函数时就会错。


const o2 = JSON.parse(JSON.stringify(this.state.obj))    o2.student.count = '00000';    this.setState({        obj: o2,    })
复制代码

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

  • 方便 react 销毁组件、重新渲染的时候去清空 refs 的东西,防止内存泄露


用户头像

beifeng1996

关注

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

还未添加个人简介

评论

发布
暂无评论
前端react面试题(边面边更)_React_beifeng1996_InfoQ写作社区