前端常见面试题之react基础

前端常见面试题之react基础

1. react事件为何需要bind this

React事件需要绑定this,是因为React中的事件处理函数是普通函数,并不是类的方法。在JavaScript中,普通函数在被调用时,其this关键字会指向调用它的对象。而在React中,事件处理函数并不是在其所属的组件实例的上下文中被调用的,所以如果不绑定this,this将会指向undefined或者全局对象。

为了确保事件处理函数中的this能够指向组件实例,需要将事件处理函数绑定到组件实例。方法有很多种,常见的有在构造函数中使用bind绑定this,在事件处理函数中使用箭头函数,或者使用类属性的箭头函数来定义事件处理函数。这样做可以确保事件处理函数中的this指向组件实例,可以正确地使用组件的状态和方法。

举例来说明,假设在一个React组件中有一个按钮,点击按钮时需要调用组件实例的方法并传入参数:

class My***ponent extends React.***ponent {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment</button>
      </div>
    );
  }
}

在上面的例子中,由于React事件处理函数默认是在全局作用域下执行的,若不绑定this,点击按钮时会出现错误。通过在构造函数中使用.bind(this)绑定this,确保了在事件处理函数中可以正确访问组件实例的属性及方法。

解决方法

(1)箭头函数

一种常见的解决办法是使用箭头函数,在箭头函数中,this会指向箭头函数所在的上下文,而不受外部函数调用的影响,因此可以解决React事件处理函数中this指向错误的问题。例如:

class My***ponent extends React.***ponent {
  handleClick = () => {
    console.log(this); // this指向My***ponent实例
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

(2)bind改变this指向

另一种解决办法是显式绑定this,在事件处理函数中使用bind方法将this指定为当前实例。例如:

class My***ponent extends React.***ponent {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(this); // this指向My***ponent实例
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

(3)构造函数中使用箭头函数绑定this

还有一种解决办法是在构造函数中使用箭头函数绑定this,这种方法与第二种方法类似,也是通过绑定this来解决问题。例如:

class My***ponent extends React.***ponent {
  constructor(props) {
    super(props);
    this.handleClick = () => {
      console.log(this); // this指向My***ponent实例
    }
  }

  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

这些都是常见的解决React事件处理函数中this指向问题的方法,可以根据个人习惯和项目需求来选择使用哪种方法。

2. react事件和dom事件的区别

React事件和DOM事件的主要区别在于它们的处理方式和绑定方法。

React事件使用了合成事件(SyntheticEvent),这是一个在React中封装的事件系统,它屏蔽了不同浏览器之间的事件差异,提供了统一的事件处理方式。React事件绑定可以直接在JSX中使用onXxx属性来绑定事件处理函数。

DOM事件是浏览器原生的事件系统,通过addEventListener方法来绑定事件处理函数。DOM事件处理中需要特别注意跨浏览器的兼容性,因为不同浏览器可能会有不同的事件名称或参数。

举例来说,对于一个按钮点击事件的处理:
React事件:

class My***ponent extends React.***ponent {
  handleClick = () => {
    alert('Button clicked!');
  }
  render() {
    return <button onClick={this.handleClick}>Click me</button>;
  }
}

DOM事件:

document.getElementById('myButton').addEventListener('click', () => {
  alert('Button clicked!');
});
<div id="myButton">Click me</div>

在React中,我们可以直接在JSX中使用onClick属性来绑定点击事件处理函数,而在DOM中则需要通过addEventListener方法来实现相同的效果。React的事件处理方式更加简洁和易用,同时也提供了更好的跨浏览器兼容性。

3. react事件中的event参数

React事件中的event参数并不是浏览器原生的event,而是由React封装而成的合成事件(SyntheticEvent)。合成事件是React对浏览器原生事件的一个跨浏览器封装,可以提供一致的API和行为。

区别

  1. 合成事件提供了一致的跨浏览器API,不需要手动处理浏览器兼容性问题。
  2. 合成事件可以使用一些额外的功能,比如可以调用preventDefault()来阻止默认行为,调用stopPropagation()来停止事件冒泡。
  3. 合成事件是一个以JavaScript对象为基础的事件系统,相比原生事件更轻量级,性能更高。

举例说明:

function handleClick(event) {
  event.preventDefault(); // 阻止默认行为
  console.log(event.target); // 获取触发事件的元素
  console.log(event.pageX, event.pageY); // 获取鼠标点击位置
}

<button onClick={handleClick}>Click me</button>

在上面的例子中,handleClick函数接受一个合成事件作为参数,可以通过该事件对象来阻止默认行为、获取触发事件的元素以及鼠标点击的位置等信息。

4. react事件中的自定义参数

在React中,可以通过箭头函数的方式将自定义参数传递给事件处理程序。例如,如果想要在点击按钮时传递一个特定的值,可以像下面这样编写代码:

import React from 'react';

class My***ponent extends React.***ponent {

  handleClick = (value) => {
    console.log(`Clicked with value: ${value}`);
  }

  render() {
    return (
      <div>
        <button onClick={() => this.handleClick('customValue')}>Click me</button>
      </div>
    );
  }
}

export default My***ponent;

在上面的代码中,当点击按钮时,会触发 handleClick 方法并输出 “Clicked with value: customValue”。这样就可以很方便地传递自定义参数给React事件处理程序。

5. 自定义参数和event参数共存

如果需要在React事件中传递自定义参数并且还需要使用event参数,可以使用箭头函数来定义事件函数。在箭头函数中,可以手动传递自定义参数,并且在调用事件处理函数时,React会自动传递event参数

举个例子,假设有一个按钮组件,点击按钮时需要传递一个自定义参数id并且需要获取event对象:

import React from 'react';

class CustomButton extends React.***ponent {
  handleClick = (id, event) => {
    console.log('Clicked ID:', id);
    console.log('Event:', event);
  };

  render() {
    const id = 123; // 自定义参数

    return (
      <button onClick={(event) => this.handleClick(id, event)}>
        Click Me
      </button>
    );
  }
}

export default CustomButton;

在上面的例子中,当点击按钮时,会调用handleClick事件处理函数,并传递自定义参数id和React自动传递的event参数。在handleClick函数中,可以分别打印出传递的自定义参数和event对象。

6. 受控组件和非受控组件

在React中,受控组件和非受控组件是以组件的状态是否由React来管理为区别的两种组件。

  1. 受控组件(Controlled ***ponents):在受控组件中,组件的状态(例如输入框的值)由React的state来管理,并通过props传递到组件中。当用户与组件交互时,组件的状态会随着用户输入的变化而更新。受控组件可以实现数据的双向绑定,即组件中的状态变化会同时影响到视图,以及用户输入的变化也会实时更新到状态中。

举例:

class Controlled***ponent extends React.***ponent {
  constructor(props) {
    super(props);
    this.state = { value: '' };
  }

  handleChange = (event) => {
    this.setState({ value: event.target.value });
  };

  render() {
    return (
      <input 
        type="text" 
        value={this.state.value} 
        onChange={this.handleChange} 
      />
    );
  }
}

React的受控组件其实所实现的效果和Vue的v-model是一样的

Vue的v-model指的是直接在表单元素上使用v-model指令来实现数据的双向绑定。例如,在Vue中,一个input元素可以这样定义:

<template>
  <input type="text" v-model="value" />
</template>

<script>
export default {
  data() {
    return {
      value: ''
    };
  }
}
</script>

总的来说,React的受控组件和Vue的v-model都是通过数据绑定来实现双向绑定的效果,只是具体的实现方式和语法有所不同。

  1. 非受控组件(Uncontrolled ***ponents):在非受控组件中,组件的状态不由React的state管理,而是由DOM本身来管理。通过ref属性可以获取到DOM节点,并直接操作DOM节点的值。非受控组件通常用于一些简单组件或与外部库集成时的情况。

举例:

class Uncontrolled***ponent extends React.***ponent {
  constructor(props) {
    super(props);
    this.inputRef = React.createRef();
  }

  handleClick = () => {
    alert(this.inputRef.current.value);
  };

  render() {
    return (
      <div>
        <input type="text" ref={this.inputRef} />
        <button onClick={this.handleClick}>Show Value</button>
      </div>
    );
  }
}

7. props实现父子组件通信

在React中,父组件可以通过props将数据传递给子组件,从而实现父子组件之间的通信。而子组件可以通过回调函数将数据传递给父组件,实现子传父的通信。

1. 父传子

父组件可以通过props将数据传递给子组件。例如,父组件可以将一个名为data的变量传递给子组件:

// Parent***ponent.js
import React from 'react';
import Child***ponent from './Child***ponent';

class Parent***ponent extends React.***ponent {
  render() {
    const data = 'Hello from Parent***ponent';
    return (
      <Child***ponent data={data} />
    );
  }
}

export default Parent***ponent;
// Child***ponent.js
import React from 'react';

class Child***ponent extends React.***ponent {
  render() {
    return (
      <div>{this.props.data}</div>
    );
  }
}

export default Child***ponent;

在上面的例子中,父组件Parent***ponent通过props将data传递给子组件Child***ponent,子组件通过this.props.data获取数据并进行渲染。

2. 子传父

子组件可以通过回调函数将数据传递给父组件。例如,子组件可以传递一个名为handleClick的回调函数给父组件,当子组件中的按钮被点击时,通过这个回调函数将数据传递给父组件:

// Parent***ponent.js
import React from 'react';
import Child***ponent from './Child***ponent';

class Parent***ponent extends React.***ponent {
  constructor(props) {
    super(props);
    this.state = {
      childData: ''
    };
  }

  handleChildData = (data) => {
    this.setState({ childData: data });
  }

  render() {
    return (
      <div>
        <Child***ponent onChildData={this.handleChildData} />
        <p>Child Data: {this.state.childData}</p>
      </div>
    );
  }
}

export default Parent***ponent;
// Child***ponent.js
import React from 'react';

class Child***ponent extends React.***ponent {
  sendDataToParent = () => {
    const data = 'Hello from Child***ponent';
    this.props.onChildData(data);
  }

  render() {
    return (
      <div>
        <button onClick={this.sendDataToParent}>Send Data</button>
      </div>
    );
  }
}

export default Child***ponent;

在上面的例子中,子组件Child***ponent通过props将handleChildData这个回调函数传递给父组件Parent***ponent,当子组件中的按钮被点击时,会调用这个回调函数并传递数据,父组件会通过这个回调函数接收子组件传递的数据并更新状态。

8.setState第一个参数的两种形式

在React中,使用setState更新state中的某个值时,可以传入一个对象或者一个函数两种方式。

传入对象的形式更新state的值时,React会将传入的对象与原来的state进行浅合并(shallow merge),只更新传入对象中的属性,不会对其他属性进行改变。

例如:

// 传入对象
this.setState({count: this.state.count + 1});

传入函数的形式更新state的值时,接受两个参数:prevState(代表上一个state的值)和props(代表组件的props)。函数会返回一个新的state对象,React会使用此新的state对象更新state。

例如:

// 传入函数
this.setState((prevState, props) => {
  return {count: prevState.count + 1};
});

区别

  1. 当使用对象形式更新state时,由于React的批处理机制,如果多次调用setState并传入对象,React会将这些传入的对象合并成一个对象后再更新state,因此可能导致不可预测的结果。
  2. 当使用函数形式更新state时,可以保证每次更新都是基于最新的state值进行的计算,避免了对象形式的不可预测问题。此外,函数形式的更新也可以保证在异步更新时,获取到最新的state值。

为什么传入对象会合并呢? *

这是因为React通过批处理机制来提高性能。当调用setState时,React并不会立即更新state,而是将所有的setState操作放入队列中,然后在队列中的所有操作执行完成后再统一更新state。在这个过程中,React会将所有传入的对象合并成一个对象,然后再更新state。这样做可以减少不必要的渲染次数,提高性能。如果需要在setState之后使用新的state值进行计算或者操作,可以使用回调函数的方式传入setState,这样可以确保在state更新之后再执行相关操作。

9. setState的第二个参数

在React中,调用setState方法的第二个参数是一个回调函数,它会在setState方法执行完并且组件重新渲染之后被调用。

使用setState的第二个参数主要用于在setState完成后执行一些需要在更新状态之后立即进行的操作。这种情况通常是因为setState是一个异步方法,所以如果需要确保在setState完成后执行一些操作,可以使用第二个参数的回调函数。

举一个例子:

import React, { ***ponent } from 'react';

class Example***ponent extends ***ponent {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  handleClick = () => {
    this.setState({ count: this.state.count + 1 }, () => {
      console.log('Count updated:', this.state.count);
    });
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.handleClick}>Increment Count</button>
      </div>
    );
  }
}

export default Example***ponent;

在上面的例子中,当按钮被点击时,handleClick方法会调用setState来增加count的值。第二个参数是一个回调函数,用于在更新count之后立即输出新的count值。这样可以确保我们在状态更新完成后执行额外的逻辑。

与之类似,在Vue中,$nextTick函数也可以用于在DOM更新之后执行一些操作。$nextTick会在下次DOM更新循环结束后执行传入的回调函数。

举例来说,在React中可以这样使用setState的第二个参数:

this.setState({ count: this.state.count + 1 }, () => {
  console.log('State updated');
});

在Vue中,则可以这样使用$nextTick函数:

this.count = this.count + 1;
this.$nextTick(() => {
  console.log('DOM updated');
});

总的来说,React中的setState的第二个参数和Vue中的$nextTick函数都可以用于在状态更新完成并且DOM重新渲染后执行一些操作,但是具体用法和实现机制有一些细微差别。

10. setState是同步的还是异步的

在React中,setState的更新有时候是同步的,有时候是异步的。具体来说:

  1. 当在组件生命周期方法(如***ponentDidMount、***ponentDidUpdate)或者事件处理函数中调用setState时,更新是异步的。这是因为React会将setState调用放入更新队列中,然后一次性执行所有更新,以提高性能

  2. 当在原生事件处理函数(如setTimeout、addEventListener)中调用setState时,更新是同步的。这是因为原生事件处理函数是在React的更新之外执行的,React无法控制更新的时机

总的来说,大多数情况下setState的更新是异步的,但在某些特定情况下会是同步的。为了避免不必要的问题,最好遵循React官方的建议,不要直接依赖于setState的同步或异步特性,而是使用回调函数或者生命周期方法来处理更新后的逻辑

(1). 异步更新的情况:

import React, { ***ponent } from 'react';

class Example extends ***ponent {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  ***ponentDidMount() {
    this.setState({ count: this.state.count + 1 });
    console.log(this.state.count); // 输出0,因为更新是异步的
  }

  render() {
    return (
      <div>{this.state.count}</div>
    );
  }
}

export default Example;

(2). 同步更新的情况:

import React, { ***ponent } from 'react';

class Example extends ***ponent {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  handleClick = () => {
    setTimeout(() => {
      this.setState({ count: this.state.count + 1 });
      console.log(this.state.count); // 输出1,因为更新是同步的
    }, 0);
  }

  render() {
    return (
      <button onClick={this.handleClick}>Click me</button>
    );
  }
}

export default Example;

在第一个例子中,***ponentDidMount中调用setState进行更新是异步的,所以在输出状态之前打印状态会得到之前的状态。在第二个例子中,通过setTimeout中调用setState进行更新是同步的,因为setTimeout是在React更新之外执行的,所以可以立即看到状态的变化。

* 注意

这一特性是在react版本小于18以前,在18版本之后,都是异步的。 Automatic Batching 自动批处理

11. react的生命周期

React组件的生命周期可以分为三个阶段:挂载阶段、更新阶段和卸载阶段。每个阶段有不同的生命周期方法可以供开发者重写并在组件生命周期中做一些特定的操作。

  1. 挂载阶段
  • constructor:组件被实例化时调用,用于初始化组件的状态。
  • getDerivedStateFromProps:在props发生变化时调用,用于更新组件的状态。
  • render:渲染界面的方法,必须实现。
  • ***ponentDidMount:组件挂载完成后调用,可以进行一些异步操作、数据请求等操作。
  1. 更新阶段
  • getDerivedStateFromProps:在props发生变化时调用,用于更新组件的状态。
  • should***ponentUpdate:在组件更新之前调用,可以控制组件是否需要重新渲染。
  • render:渲染界面的方法,必须实现。
  • getSnapshotBeforeUpdate:在组件更新之后调用,可以获取更新前的DOM状态信息。
  • ***ponentDidUpdate:组件更新完成后调用,可以进行一些DOM操作、数据请求等操作。
  1. 卸载阶段
  • ***ponentWillUnmount:组件将被卸载时调用,可以进行一些清理工作,如清除定时器、取消订阅等操作。

在React版本16.4之后,更新了组件生命周期方法,引入了新的生命周期方法getDerivedStateFromPropsgetSnapshotBeforeUpdate,取代了之前的***ponentWillReceiveProps***ponentWillUpdate方法。需要根据实际情况选择合适的生命周期方法来实现组件的功能。

12. 函数组件和类组件的区别

函数组件和类组件是 React 中两种常见的组件类型,它们之间的区别如下:

  1. 语法:函数组件是以函数的形式来定义的,而类组件是以 ES6 类的形式来定义的。

  2. 状态管理:在函数组件中,可以使用 React Hooks 来管理状态,如 useState 和 useEffect。而在类组件中,使用 this.state 和 this.setState 来管理组件的状态。

  3. 生命周期方法:在类组件中,可以使用一系列的生命周期方法,如 ***ponentDidMount、***ponentDidUpdate、***ponentWillUnmount 等来处理组件的生命周期,而函数组件中使用 useEffect 来模拟这些生命周期方法的功能。

  4. this 关键字:在类组件中,需要使用 this 关键字来访问组件的属性和方法,而函数组件中没有 this 关键字。

总的来说,随着 React Hooks 的引入,函数组件已经可以完全替代类组件,并且函数组件更加简洁、易于理解和维护。因此,在新的 React 项目中,推荐使用函数组件来编写组件。

大家可以看看这篇react中的函数组件和类组件

转载请说明出处内容投诉
CSS教程_站长资源网 » 前端常见面试题之react基础

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买