写点什么

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

作者:beifeng1996
  • 2023-02-28
    浙江
  • 本文字数:8178 字

    阅读完需:约 27 分钟

什么是 Props

Props 是 React 中属性的简写。它们是只读组件,必须保持纯,即不可变。它们总是在整个应用中从父组件传递到子组件。子组件永远不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。

React.Component 和 React.PureComponent 的区别

PureComponent 表示一个纯组件,可以用来优化 React 程序,减少 render 函数执行的次数,从而提高组件的性能。


在 React 中,当 prop 或者 state 发生变化时,可以通过在 shouldComponentUpdate 生命周期函数中执行 return false 来阻止页面的更新,从而减少不必要的 render 执行。React.PureComponent 会自动执行 shouldComponentUpdate。


不过,pureComponent 中的 shouldComponentUpdate() 进行的是浅比较,也就是说如果是引用数据类型的数据,只会比较不是同一个地址,而不会比较这个地址里面的数据是否一致。浅比较会忽略属性和或状态突变情况,其实也就是数据引用指针没有变化,而数据发生改变的时候 render 是不会执行的。如果需要重新渲染那么就需要重新开辟空间引用数据。PureComponent 一般会用在一些纯展示组件上。


使用 pureComponent 的好处:当组件更新时,如果组件的 props 或者 state 都没有改变,render 函数就不会触发。省去虚拟 DOM 的生成和对比过程,达到提升性能的目的。这是因为 react 自动做了一层浅比较。

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

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

  • 场景

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

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,因为组件不会重新渲染。

如果创建了类似于下面的 Icketang 元素,那么该如何实现 Icketang 类?

<Icketang username="雨夜清荷">{(user) => (user ? <Info user={user} /> : <Loading />)}</Icketang>;import React, { Component } from "react";export class Icketang extends Component {  //请实现你的代码}
复制代码


在上面的案例中,一个组件接受一个函数作为它的子组件。Icketang 组件的子组件是一个函数,而不是一个常用的组件。这意味着在实现 Icketang 组件时,需要将 props. children 作为一个函数来处理。具体实现如下。


import React, { Component } from "react";class Icketang extends Component {  constructor(props) {    super(props);    this.state = {      user: props.user,    };  }  componentDidMount() {    //模拟异步获取数据操作,更新状态    setTimeout(      () =>        this.setstate({          user: "有课前端网",        }),      2000    );  }  render() {    return this.props.children(this.state.user);  }}class Loading extends Component {  render() {    return <p>Loading.</p>;  }}class Info extends Component {  render() {    return <h1> {this.props.user}</h1>;  }}
复制代码


调用 Icketang 组件,并传递给 user 属性数据,把 props.children 作为一个函数来处理。这种模式的好处是,我们已经将父组件与子组件分离了,父组件管理状态。父组件的使用者可以决定父组件以何种形式渲染子组件。为了演示这一点,在渲染 Icketang 组件时,分别传递和不传递 user 属性数据来观察渲染结果。


import { render } from "react-dom";render(<Icketang>{(user) => (user ? <Info user={user} /> : <Loading />)}</Icketang>, ickt);
复制代码


上述代码没有为 Icketang 组件传递 user 属性数据,因此将首先渲染 Loading 组件,当父组件的 user 状态数据发生改变时,我们发现 Info 组件可以成功地渲染出来。


render(<Icketang user="雨夜清荷">{(user) => (user ? <Info user={user} /> : <Loading />)}</Icketang>, ickt);
复制代码


上述代码为 Icketang 组件传递了 user 属性数据,因此将直接渲染 Info 组件,当父组件的 user 状态数据发生改变时,我们发现 Info 组件产生了更新,在整个过程中, Loading 组件都未渲染。

state 和 props 区别是啥?

propsstate是普通的 JS 对象。虽然它们都包含影响渲染输出的信息,但是它们在组件方面的功能是不同的。即


  • state 是组件自己管理数据,控制自己的状态,可变;

  • props 是外部传入的数据参数,不可变;

  • 没有state的叫做无状态组件,有state的叫做有状态组件;

  • 多用 props,少用 state,也就是多写无状态组件。


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

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

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

React setState 笔试题,下面的代码输出什么

class Example extends React.Component {  constructor() {  super()  this.state = {    val: 0  }}componentDidMount() {  this.setState({ val: this.state.val + 1 })  console.log(this.state.val)  // 第 1 次 log  this.setState({ val: this.state.val + 1 })  console.log(this.state.val)  // 第 2 次 log  setTimeout(() => {    this.setState({ val: this.state.val + 1 })    console.log(this.state.val)    // 第 3 次 log    this.setState({ val: this.state.val + 1 })    console.log(this.state.val)    // 第 4 次 log    }, 0)  }  render() {    return null  }}
// 答:0, 0, 1, 2
复制代码

redux 中间件

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


常见的中间件:


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

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

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

  • actionCreator 的返回值是 promise

根据下面定义的代码,可以找出存在的两个问题吗 ?

请看下面的代码:


图片描述


答案:1.在构造函数没有将 props 传递给 super,它应该包括以下行


constructor(props) {super(props);// ...}
复制代码


2.事件监听器(通过addEventListener()分配时)的作用域不正确,因为 ES6 不提供自动绑定。因此,开发人员可以在构造函数中重新分配clickHandler来包含正确的绑定:


constructor(props) {super(props);this.clickHandler = this.clickHandler.bind(this);// ...}
复制代码

React 中的 useState() 是什么?

下面说明useState(0)的用途:


const [count, setCounter] = useState(0);const [moreStuff, setMoreStuff] = useState();
const setCount = () => { setCounter(count + 1); setMoreStuff();};
复制代码


useState 是一个内置的 React Hook。useState(0) 返回一个元组,其中第一个参数count是计数器的当前状态,setCounter 提供更新计数器状态的方法。咱们可以在任何地方使用setCounter方法更新计数状态-在这种情况下,咱们在setCount函数内部使用它可以做更多的事情,使用 Hooks,能够使咱们的代码保持更多功能,还可以避免过多使用基于类的组件。

React 性能优化

  • shouldCompoentUpdate

  • pureComponent 自带 shouldCompoentUpdate 的浅比较优化

  • 结合 Immutable.js 达到最优

react 性能优化是在哪个生命周期函数中

在 shouldComponentUpdate 这个方法中,这个方法主要用来判断是否需要调用 render 方法重绘 DOM 因为 DOM 的描绘非常消耗性能,如果能够在 shouldComponentUpdate 方法中能写出更优化的 diff 算法,极大的提高性能

什么是 React Hooks?

Hooks 是 React 16.8 中的新添加内容。它们允许在不编写类的情况下使用state和其他 React 特性。使用 Hooks,可以从组件中提取有状态逻辑,这样就可以独立地测试和重用它。Hooks 允许咱们在不改变组件层次结构的情况下重用有状态逻辑,这样在许多组件之间或与社区共享 Hooks 变得很容易。

Redux 请求中间件如何处理并发

使用 redux-Saga redux-saga 是一个管理 redux 应用异步操作的中间件,用于代替 redux-thunk 的。它通过创建 Sagas 将所有异步操作逻辑存放在一个地方进行集中处理,以此将 react 中的同步操作与异步操作区分开来,以便于后期的管理与维护。 redux-saga 如何处理并发:


  • takeEvery


可以让多个 saga 任务并行被 fork 执行。


import {    fork,    take} from "redux-saga/effects"
const takeEvery = (pattern, saga, ...args) => fork(function*() { while (true) { const action = yield take(pattern) yield fork(saga, ...args.concat(action)) }})
复制代码


  • takeLatest


takeLatest 不允许多个 saga 任务并行地执行。一旦接收到新的发起的 action,它就会取消前面所有 fork 过的任务(如果这些任务还在执行的话)。在处理 AJAX 请求的时候,如果只希望获取最后那个请求的响应, takeLatest 就会非常有用。


import {    cancel,    fork,    take} from "redux-saga/effects"
const takeLatest = (pattern, saga, ...args) => fork(function*() { let lastTask while (true) { const action = yield take(pattern) if (lastTask) { yield cancel(lastTask) // 如果任务已经结束,则 cancel 为空操作 } lastTask = yield fork(saga, ...args.concat(action)) }})
复制代码

React 有哪些优化性能的手段

类组件中的优化手段


  • 使用纯组件 PureComponent 作为基类。

  • 使用 React.memo 高阶函数包装组件。

  • 使用 shouldComponentUpdate 生命周期函数来自定义渲染逻辑。


方法组件中的优化手段


  • 使用 useMemo

  • 使用 useCallBack


其他方式


  • 在列表需要频繁变动时,使用唯一 id 作为 key,而不是数组下标。

  • 必要时通过改变 CSS 样式隐藏显示组件,而不是通过条件判断显示隐藏组件。

  • 使用 Suspense 和 lazy 进行懒加载,例如:


import React, { lazy, Suspense } from "react";
export default class CallingLazyComponents extends React.Component { render() { var ComponentToLazyLoad = null;
if (this.props.name == "Mayank") { ComponentToLazyLoad = lazy(() => import("./mayankComponent")); } else if (this.props.name == "Anshul") { ComponentToLazyLoad = lazy(() => import("./anshulComponent")); }
return ( <div> <h1>This is the Base User: {this.state.name}</h1> <Suspense fallback={<div>Loading...</div>}> <ComponentToLazyLoad /> </Suspense> </div> ) }}
复制代码

React 父组件如何调用子组件中的方法?

  1. 如果是在方法组件中调用子组件(>= react@16.8),可以使用 useRef 和 useImperativeHandle:


const { forwardRef, useRef, useImperativeHandle } = React;
const Child = forwardRef((props, ref) => { useImperativeHandle(ref, () => ({ getAlert() { alert("getAlert from Child"); } })); return <h1>Hi</h1>;});
const Parent = () => { const childRef = useRef(); return ( <div> <Child ref={childRef} /> <button onClick={() => childRef.current.getAlert()}>Click</button> </div> );};
复制代码


  1. 如果是在类组件中调用子组件(>= react@16.4),可以使用 createRef:


const { Component } = React;
class Parent extends Component { constructor(props) { super(props); this.child = React.createRef(); }
onClick = () => { this.child.current.getAlert(); };
render() { return ( <div> <Child ref={this.child} /> <button onClick={this.onClick}>Click</button> </div> ); }}
class Child extends Component { getAlert() { alert('getAlert from Child'); }
render() { return <h1>Hello</h1>; }}
复制代码

React 事件机制

<div onClick={this.handleClick.bind(this)}>点我</div>
复制代码


React 并不是将 click 事件绑定到了 div 的真实 DOM 上,而是在 document 处监听了所有的事件,当事件发生并且冒泡到 document 处的时候,React 将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。


除此之外,冒泡到 document 上的事件也不是原生的浏览器事件,而是由 react 自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用 event.preventDefault()方法,而不是调用 event.stopProppagation()方法。 JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。


另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault


实现合成事件的目的如下:


  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;

  • 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

mobox 和 redux 有什么区别?

(1)共同点


  • 为了解决状态管理混乱,无法有效同步的问题统一维护管理应用状态;

  • 某一状态只有一个可信数据来源(通常命名为 store,指状态容器);

  • 操作更新状态方式统一,并且可控(通常以 action 方式提供更新状态的途径);

  • 支持将 store 与 React 组件连接,如 react-redux,mobx- react;


(2)区别 Redux 更多的是遵循 Flux 模式的一种实现,是一个 JavaScript 库,它关注点主要是以下几方面∶


  • Action∶ 一个 JavaScript 对象,描述动作相关信息,主要包含 type 属性和 payload 属性∶

  • Reducer∶ 定义应用状态如何响应不同动作(action),如何更新状态;

  • Store∶ 管理 action 和 reducer 及其关系的对象,主要提供以下功能∶

  • 异步流∶ 由于 Redux 所有对 store 状态的变更,都应该通过 action 触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入 React 组件中,就需要使用其他框架配合管理异步任务流程,如 redux-thunk,redux-saga 等;


Mobx 是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩∶


  • Action∶定义改变状态的动作函数,包括如何变更状态;

  • Store∶ 集中管理模块状态(State)和动作(action)

  • Derivation(衍生)∶ 从应用状态中派生而出,且没有任何其他影响的数据


对比总结:


  • redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中

  • redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作

  • redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改

  • mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用

  • mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易

对 Redux 的理解,主要解决什么问题

React 是视图层框架。Redux 是一个用来管理数据状态和 UI 状态的 JavaScript 应用工具。随着 JavaScript 单页应用(SPA)开发日趋复杂, JavaScript 需要管理比任何时候都要多的 state(状态), Redux 就是降低管理难度的。(Redux 支持 React、Angular、jQuery 甚至纯 JavaScript)。


在 React 中,UI 以组件的形式来搭建,组件之间可以嵌套组合。但 React 中组件间通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件传递数据,而下层组件不能向上层组件传递数据,兄弟组件之间同样不能。这样简单的单向数据流支撑起了 React 中的数据可控性。


当项目越来越大的时候,管理数据的事件或回调函数将越来越多,也将越来越不好管理。管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。如果这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等。state 的管理在大项目中相当复杂。


Redux 提供了一个叫 store 的统一仓储库,组件通过 dispatch 将 state 直接传入 store,不用通过其他的组件。并且组件通过 subscribe 从 store 获取到 state 的改变。使用了 Redux,所有的组件都可以从 store 中获取到所需的 state,他们也能从 store 获取到 state 的改变。这比组件之间互相传递数据清晰明朗的多。


主要解决的问题: 单纯的 Redux 只是一个状态机,是没有 UI 呈现的,react- redux 作用是将 Redux 的状态机和 React 的 UI 呈现绑定在一起,当你 dispatch action 改变 state 的时候,会自动更新页面。


用户头像

beifeng1996

关注

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

还未添加个人简介

评论

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