写点什么

百度前端常见 react 面试题

作者:beifeng1996
  • 2023-02-06
    浙江
  • 本文字数:7099 字

    阅读完需:约 23 分钟

Dva 工作原理

集成redux+redux-saga


工作原理


改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过 dispatch 发起一个 action,如果是同步行为会直接通过 Reducers 改变 State ,如果是异步行为(副作用)会先触发 Effects 然后流向 Reducers 最终改变 State

我现在有一个 button,要用 react 在上面绑定点击事件,要怎么做?

class Demo {  render() {    return <button onClick={(e) => {      alert('我点击了按钮')    }}>      按钮    </button>  }}
复制代码


你觉得你这样设置点击事件会有什么问题吗?


由于onClick使用的是匿名函数,所有每次重渲染的时候,会把该onClick当做一个新的prop来处理,会将内部缓存的onClick事件进行重新赋值,所以相对直接使用函数来说,可能有一点的性能下降


修改


class Demo {
onClick = (e) => { alert('我点击了按钮') }
render() { return <button onClick={this.onClick}> 按钮 </button> }
复制代码

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

该函数会在 setState 函数调用完成并且组件开始重渲染的时候被调用,我们可以用该函数来监听渲染是否完成:


this.setState(  { username: 'tylermcginnis33' },  () => console.log('setState has finished and the component has re-rendered.'))
复制代码


this.setState((prevState, props) => {  return {    streak: prevState.streak + props.count  }})
复制代码

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

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

React- Router 有几种形式?

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

setState 方法的第二个参数有什么用?使用它的目的是什么?

它是一个回调函数,当 setState 方法执行结束并重新渲染该组件时调用它。在工作中,更好的方式是使用 React 组件生命周期之——“存在期”的生命周期方法,而不是依赖这个回调函数。


export class App extends Component {  constructor(props) {    super(props);    this.state = {      username: "雨夜清荷",    };  }  render() {    return <div> {this.state.username}</div>;  }  componentDidMount() {    this.setstate(      {        username: "有课前端网",      },      () => console.log("re-rendered success. ")    );  }}
复制代码


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

如何创建 refs

Refs 是使用 React.createRef() 创建的,并通过 ref 属性附加到 React 元素。在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。


class MyComponent extends React.Component {  constructor(props) {    super(props);    this.myRef = React.createRef();  }  render() {    return <div ref={this.myRef} />;  }}
复制代码


或者这样用:


class UserForm extends Component {  handleSubmit = () => {    console.log("Input Value is: ", this.input.value);  };  render() {    return (      <form onSubmit={this.handleSubmit}>        <input type="text" ref={(input) => (this.input = input)} /> // Access DOM input in handle submit        <button type="submit">Submit</button>      </form>    );  }}
复制代码

这段代码有什么问题吗?

这段代码有什么问题:


this.setState((prevState, props) => {  return {    streak: prevState.streak + props.count,  };});
复制代码


答案:没有什么问题。这种方式很少被使用,咱们可以将一个函数传递给setState,该函数接收上一个 state 的值和当前的props,并返回一个新的状态,如果咱们需要根据以前的状态重新设置状态,推荐使用这种方式。

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,    })
复制代码

state 和 props 区别是啥?

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

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

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

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

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 组件中怎么做事件代理?它的原理是什么?

React 基于 Virtual DOM 实现了一个 SyntheticEvent 层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合 W3C 标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。


在 React 底层,主要对合成事件做了两件事:


  • 事件委派: React 会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。

  • 自动绑定: React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。

state 和 props 触发更新的生命周期分别有什么区别?

state 更新流程: 这个过程当中涉及的函数:


  1. shouldComponentUpdate: 当组件的 state 或 props 发生改变时,都会首先触发这个生命周期函数。它会接收两个参数:nextProps, nextState——它们分别代表传入的新 props 和新的 state 值。拿到这两个值之后,我们就可以通过一些对比逻辑来决定是否有 re-render(重渲染)的必要了。如果该函数的返回值为 false,则生命周期终止,反之继续;


注意:此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()


  1. componentWillUpdate:当组件的 state 或 props 发生改变时,会在渲染之前调用 componentWillUpdate。componentWillUpdate 是 React16 废弃的三个生命周期之一。过去,我们可能希望能在这个阶段去收集一些必要的信息(比如更新前的 DOM 信息等等),现在我们完全可以在 React16 的 getSnapshotBeforeUpdate 中去做这些事;

  2. componentDidUpdate:componentDidUpdate() 会在 UI 更新后会被立即调用。它接收 prevProps(上一次的 props 值)作为入参,也就是说在此处我们仍然可以进行 props 值对比(再次说明 componentWillUpdate 确实鸡肋哈)。



props 更新流程: 相对于 state 更新,props 更新后唯一的区别是增加了对 componentWillReceiveProps 的调用。关于 componentWillReceiveProps,需要知道这些事情:


  • componentWillReceiveProps:它在 Component 接受到新的 props 时被触发。componentWillReceiveProps 会接收一个名为 nextProps 的参数(对应新的 props 值)。该生命周期是 React16 废弃掉的三个生命周期之一。在它被废弃前,可以用它来比较 this.props 和 nextProps 来重新 setState。在 React16 中,用一个类似的新生命周期 getDerivedStateFromProps 来代替它。

useEffect 和 useLayoutEffect 的区别

useEffect


基本上 90%的情况下,都应该用这个,这个是在 render 结束后,你的 callback 函数执行,但是不会 block browser painting,算是某种异步的方式吧,但是 class 的 componentDidMount 和 componentDidUpdate 是同步的,在 render 结束后就运行,useEffect 在大部分场景下都比 class 的方式性能更好.


useLayoutEffect


这个是用在处理 DOM 的时候,当你的 useEffect 里面的操作需要处理 DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题, useLayoutEffect 里面的 callback 函数会在**DOM 更新完成后立即执行,但是会在浏览器进行任何绘制之前运行完成,**阻塞了浏览器的绘制.

对 React Hook 的理解,它的实现原理是什么

React-Hooks 是 React 团队在 React 组件开发实践中,逐渐认知到的一个改进点,这背后其实涉及对类组件函数组件两种组件形式的思考和侧重。


(1)类组件: 所谓类组件,就是基于 ES6 Class 这种写法,通过继承 React.Component 得来的 React 组件。以下是一个类组件:


class DemoClass extends React.Component {  state = {    text: ""  };  componentDidMount() {    //...  }  changeText = (newText) => {    this.setState({      text: newText    });  };
render() { return ( <div className="demoClass"> <p>{this.state.text}</p> <button onClick={this.changeText}>修改</button> </div> ); }}

复制代码


可以看出,React 类组件内部预置了相当多的“现成的东西”等着我们去调度/定制,state 和生命周期就是这些“现成东西”中的典型。要想得到这些东西,难度也不大,只需要继承一个 React.Component 即可。


当然,这也是类组件的一个不便,它太繁杂了,对于解决许多问题来说,编写一个类组件实在是一个过于复杂的姿势。复杂的姿势必然带来高昂的理解成本,这也是我们所不想看到的。除此之外,由于开发者编写的逻辑在封装后是和组件粘在一起的,这就使得类组件内部的逻辑难以实现拆分和复用。


(2)函数组件:函数组件就是以函数的形态存在的 React 组件。早期并没有 React-Hooks,函数组件内部无法定义和维护 state,因此它还有一个别名叫“无状态组件”。以下是一个函数组件:


function DemoFunction(props) {  const { text } = props  return (    <div className="demoFunction">      <p>{`函数组件接收的内容:[${text}]`}</p>    </div>  );}
复制代码


相比于类组件,函数组件肉眼可见的特质自然包括轻量、灵活、易于组织和维护、较低的学习成本等。


通过对比,从形态上可以对两种组件做区分,它们之间的区别如下:


  • 类组件需要继承 class,函数组件不需要;

  • 类组件可以访问生命周期方法,函数组件不能;

  • 类组件中可以获取到实例化后的 this,并基于这个 this 做各种各样的事情,而函数组件不可以;

  • 类组件中可以定义并维护 state(状态),而函数组件不可以;


除此之外,还有一些其他的不同。通过上面的区别,我们不能说谁好谁坏,它们各有自己的优势。在 React-Hooks 出现之前,类组件的能力边界明显强于函数组件。


实际上,类组件和函数组件之间,是面向对象和函数式编程这两套不同的设计思想之间的差异。而函数组件更加契合 React 框架的设计理念: React 组件本身的定位就是函数,一个输入数据、输出 UI 的函数。作为开发者,我们编写的是声明式的代码,而 React 框架的主要工作,就是及时地把声明式的代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中去。这就意味着从原则上来讲,React 的数据应该总是紧紧地和渲染绑定在一起的,而类组件做不到这一点。函数组件就真正地将数据和渲染绑定到了一起。函数组件是一个更加匹配其设计理念、也更有利于逻辑拆分与重用的组件表达形式。


为了能让开发者更好的的去编写函数式组件。于是,React-Hooks 便应运而生。


React-Hooks 是一套能够使函数组件更强大、更灵活的“钩子”。


函数组件比起类组件少了很多东西,比如生命周期、对 state 的管理等。这就给函数组件的使用带来了非常多的局限性,导致我们并不能使用函数这种形式,写出一个真正的全功能的组件。而 React-Hooks 的出现,就是为了帮助函数组件补齐这些(相对于类组件来说)缺失的能力。


如果说函数组件是一台轻巧的快艇,那么 React-Hooks 就是一个内容丰富的零部件箱。“重装战舰”所预置的那些设备,这个箱子里基本全都有,同时它还不强制你全都要,而是允许你自由地选择和使用你需要的那些能力,然后将这些能力以 Hook(钩子)的形式“钩”进你的组件里,从而定制出一个最适合你的“专属战舰”。

对于 store 的理解

Store 就是把它们联系到一起的对象。Store 有以下职责:


  • 维持应用的 state;

  • 提供 getState() 方法获取 state;

  • 提供 dispatch(action) 方法更新 state;

  • 通过 subscribe(listener)注册监听器;

  • 通过 subscribe(listener)返回的函数注销监听器

refs 是什么

  • refs 是 react 中引用的简写,有主语存储特定 React 元素或组件的引用的属性,它将由组件渲染配置函数返回

  • 当我们需要输入框的内容,触发动画等时候可以使用 refs

在使用 React Router 时,如何获取当前页面的路由或浏览器中地址栏中的地址?

在当前组件的 props 中,包含 location 属性对象,包含当前页面路由地址信息,在 match 中存储当前路由的参数等数据信息。可以直接通过 this .props 使用它们。

React 的生命周期方法有哪些?

  • componentWillMount:在渲染之前执行,用于根组件中的 App 级配置。

  • componentDidMount:在第一次渲染之后执行,可以在这里做 AJAX 请求,DOM 的操作或状态更新以及设置事件监听器。

  • componentWillReceiveProps:在初始化render的时候不会执行,它会在组件接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染

  • shouldComponentUpdate:确定是否更新组件。默认情况下,它返回true。如果确定在 stateprops 更新后组件不需要在重新渲染,则可以返回false,这是一个提高性能的方法。

  • componentWillUpdate:在shouldComponentUpdate返回 true 确定要更新组件之前件之前执行。

  • componentDidUpdate:它主要用于更新 DOM 以响应propsstate更改。

  • componentWillUnmount:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器。

React 的请求应该放在哪个⽣命周期中?

React 的异步请求到底应该放在哪个⽣命周期⾥,有⼈认为在 componentWillMount 中可以提前进⾏异步请求,避免⽩屏,其实这个观点是有问题的。


由于 JavaScript 中异步事件的性质,当您启动 API 调⽤时,浏览器会在此期间返回执⾏其他⼯作。当 React 渲染⼀个组件时,它不会等待 componentWillMount 它完成任何事情。React 继续前进并继续 render,没有办法“暂停”渲染以等待数据到达。


⽽且在 componentWillMount 请求会有⼀系列潜在的问题。⾸先,在服务器渲染时,如果在 componentWillMount⾥获取数据,fetch data 会执⾏两次,⼀次在服务端⼀次在客户端,这造成了多余的请求。其次,在 React 16 进⾏React Fiber 重写后, componentWillMount 可能在⼀次渲染中多次调⽤。


⽬前官⽅推荐的异步请求是在 componentDidmount 中进⾏。如果有特殊需求需要提前请求,也可以在特殊情况下在 constructor 中请求。react 17 之后 componentWillMount 会被废弃,仅仅保留 UNSAFE_componentWillMount。


用户头像

beifeng1996

关注

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

还未添加个人简介

评论

发布
暂无评论
百度前端常见react面试题_React_beifeng1996_InfoQ写作社区