2024 React面试题总结

本文围绕React展开,对比了React与Vue在设计理念、核心库、模板语法等方面的区别,介绍了React的JSX、组件、state和props等概念,阐述了生命周期方法、虚拟DOM、受控与非受控组件等知识,还探讨了性能优化、高阶组件、事件机制等内容。

1.react和vue有哪些区别

设计理念:

  • React: 更倾向于函数式编程思想,推崇组件的不可变性和单向数据流。
  • Vue: 结合了响应式编程和模板系统,致力于简化开发过程。

核心库与生态系统:

  • React: React本身只关注UI层,但它有一个庞大的生态系统,例如Redux、MobX等用于状态管理,以及React Router用于路由处理。
  • Vue: Vue是一个更完整的解决方案,它的核心库除了视图层,还内置了例如Vuex(状态管理)和Vue Router(路由管理)等解决方案。

模板语法:

  • React: 使用JSX(JavaScript XML),将标记语言与JavaScript逻辑混写。
  • Vue: 使用基于HTML的模板语法,允许开发者使用纯HTML、CSS和JavaScript。

数据绑定:

  • React: 主要采用单向数据流,组件状态通过setState方法更新。
  • Vue: 支持双向数据绑定(使用v-model指令),适合于简化表单输入等场景。

组件化:

  • React与Vue: 都支持基于组件的架构,但在定义组件方式上有所不同。React推崇函数式组件和Hooks,Vue则提供了一个选项式API。

状态管理:

  • React: 状态管理通常通过使用Context API或引入如Redux、MobX的库来实现。
  • Vue: Vue提供了Vuex作为官方的状态管理解决方案。

响应式系统:

  • React: 通过setState和useState等API显式地触发UI更新。
  • Vue: 通过其响应式系统自动追踪依赖并在数据变化时更新视图。

类型支持:

  • React: 原生支持JavaScript,但可以很好地与TypeScript结合。
  • Vue: Vue 3提供了更好的TypeScript支持。

2.React中的JSX是什么?

JSX是JavaScript XML的缩写,它允许我们在JavaScript代码中写类似HTML的语法。这些HTML样式的代码最终会被转换成React元素。

3.React中的组件是什么?

组件是React应用的构建块。它们是独立且可重用的代码片段,用于定义UI的一部分。组件可以是类组件或函数组件,并且可以维护自己的状态和生命周期。

4.React中的state和props的区别

在React中,state是组件内部的状态,可以被组件自身管理和更改。而props(属性)是从父组件传递给子组件的数据,子组件不能修改接收到的props(单向数据流)。

5.什么是生命周期方法,举例说明一些常用的生命周期方法

生命周期方法是React组件在其生命期内可以调用的一系列方法。常见的生命周期方法包括:

constructor:组件实例化时调用,用于初始化组件的状态和绑定事件。

render:根据组件的状态和属性生成虚拟DOM

componentDidMount:组件挂载后调用,通常用于发送网络请求或订阅事件。

componentDidUpdate:组件更新后调用,通常用于更新虚拟DOM或执行其他副作用操作。

componentWillUnmount:组件卸载前调用,用于清理定时器、取消订阅等操作。

这些生命周期方法用于在组件的不同阶段执行相应的操作,例如初始化组件的状态、发送网络请求、更新虚拟DOM等。开发者可以在这些方法中实现自己的逻辑,以满足组件的需求。​​​​​​​

6.解释React中的虚拟DOM是什么

虚拟DOM是内存中的DOM表示。React使用虚拟DOM来优化DOM的更新过程。它通过比较新旧虚拟DOM的差异,并仅更新实际DOM中改变的部分,提高应用性能。

7. 什么是React的受控组件和非受控组件?

React的受控组件和非受控组件是组件的两种状态。受控组件是指由React控制的组件,组件的状态和属性由React管理。通常通过将组件的状态绑定到表单元素的value属性,实现对输入框的控制。受控组件的优点是可以精确地控制组件的状态和行为,但缺点是需要编写更多的代码。

8.解释React中的hooks,简述项目中常用的hooks

Hooks是React 16.8引入的新特性,它允许你在不编写类的情况下使用state和其他React特性。常用的hooks包括useStateuseEffectuseContext

useState:

import React, { useState } from 'react';

function ExampleComponent() {
  // 使用useState创建一个新的state变量`count`和设置这个变量的方法`setCount`
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect:

import React, { useState, useEffect } from 'react';

function ExampleComponent() {
  const [count, setCount] = useState(0);

  // 使用useEffect来更新文档的标题
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useContext:

首先创建一个Context:

import React, { createContext } from 'react';

// 创建一个Context
const ThemeContext = createContext('light');

然后使用useContext Hook:

import React, { useContext } from 'react';

function ExampleComponent() {
  // 使用useContext获取当前的主题
  const theme = useContext(ThemeContext);

  return <div>Current theme: {theme}</div>;
}

9.描述一下React中的Context

Context在React中用于传递数据并在组件树中避免"道具逐层传递"的问题。它允许数据在组件树中直接传递,而不是一层层手动传递props。

10.如何优化React应用的性能?

优化React应用的性能可以通过多种方式实现,例如使用不可变数据结构、利用shouldComponentUpdateReact.memo避免不必要的渲染、使用懒加载组件以及合理使用hooks和Context等。

11.React中的高阶组件(HOC)是什么

高阶组件(HOC)是一种模式,它接收一个组件并返回一个新组件。HOC用于逻辑复用,它可以向组件添加额外的props或功能,而不必修改组件本身

12.解释React中的纯组件

纯组件(PureComponent)是React中的一种组件。与常规组件相比,纯组件通过对props和state的浅比较来避免不必要的渲染。当纯组件的props或state发生变化时,只有在这些变化不是浅层的时,组件才会重新渲染。

13.在React应用中实现全局状态管理

在React中实现全局状态管理的方法有多种,最常见的是使用Context API配合useReducer或useState。此外,也可以使用一些外部库,如Redux或MobX,这些库提供了更复杂的状态管理功能和中间件支持

14.在react中,当调用setState时,发生了什么

  1. 更新状态: 当调用setState时,React会将提供的对象与当前组件的状态进行合并(对于类组件)或替换(对于使用useState的函数组件)。这个操作会触发组件的重新渲染流程。
  2. 调用生命周期方法(如果是类组件): 如果组件是类组件,React将在更新过程中调用相关的生命周期方法。例如,shouldComponentUpdate可以在渲染发生前决定是否需要更新组件;componentWillUpdate在渲染之前被调用;componentDidUpdate在组件更新后被调用。
  3. 虚拟DOM比较: React接下来会使用新的状态来生成虚拟DOM节点,并与之前的虚拟DOM进行比较。这个过程称为"差异计算"(Diffing)。
  4. 确定更改: React通过比较新旧虚拟DOM,确定实际DOM需要进行的最小更改。这个步骤的目的是为了提高性能,避免不必要的DOM操作。
  5. 更新实际DOM: React将在这一步更新实际的DOM,以匹配虚拟DOM的状态。这个过程是高效的,因为React已经知道了具体需要更改的部分。
  6. 调用回调函数(如果有): 如果在setState方法中传递了回调函数,那么这个回调函数会在组件渲染和更新后被调用。

15.什么是Fiber

在React中,Fiber是一种用于构建和管理组件树的数据结构。它是React v16中引入的一项重大变更,旨在改善React的性能和可调度性。Fiber重新实现了React的调度算法,将协调工作分解为可中断的单元,从而实现了更灵活的调度和更好的用户体验。

以下是一个简单的React组件,其中包含了一些渲染和更新逻辑,以说明Fiber是如何在React中工作的:

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

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

export default Counter;

在这个例子中,useState是React提供的钩子函数,用于在函数式组件中添加状态管理。每次点击按钮时,count的状态都会增加。当状态发生变化时,React会重新渲染组件。

Fiber的工作原理是通过构建一个称为Fiber树的数据结构来实现的。在内部,React使用Fiber节点来表示组件树中的每个组件和元素。当组件需要进行更新时,React会创建一个新的Fiber树,并通过协调算法比较新旧树之间的差异,从而确定哪些部分需要更新、删除或添加。这种增量更新的方式使React能够更高效地管理组件树,并且可以在多个渲染周期之间分配和调度工作

16.什么是上下文Context

Context 通过组件树提供了一个传递数据的方法,从而避免了在每一个层级手动的传递 props 属性。

用法:在父组件上定义getChildContext方法,返回一个对象,然后它的子组件就可以通过this.context属性来获取

17.React-Router 4的Switch有什么用?

Switch 通常被用来包裹 Route,用于渲染与路径匹配的第一个子 <Route> 或 <Redirect>,它里面不能放其他元素。

假如不加 <Switch> :

import { Route } from 'react-router-dom'

<Route path="/" component={Home}></Route>
<Route path="/login" component={Login}></Route>

Route 组件的 path 属性用于匹配路径,因为需要匹配 / 到 Home,匹配 /login 到 Login,所以需要两个 Route,但是不能这么写。这样写的话,当 URL 的 path 为 “/login” 时,<Route path="/" /><Route path="/login" /> 都会被匹配,因此页面会展示 Home 和 Login 两个组件。这时就需要借助 <Switch> 来做到只显示一个匹配组件:

import { Switch, Route} from 'react-router-dom'

<Switch>
    <Route path="/" component={Home}></Route>
    <Route path="/login" component={Login}></Route>
</Switch>

此时,再访问 “/login” 路径时,却只显示了 Home 组件。这是就用到了exact属性,它的作用就是精确匹配路径,经常与<Switch> 联合使用。只有当 URL 和该 <Route> 的 path 属性完全一致的情况下才能匹配上:

import { Switch, Route} from 'react-router-dom'

<Switch>
   <Route exact path="/" component={Home}></Route>
   <Route exact path="/login" component={Login}></Route>
</Switch>

18. 为什么虚拟dom会提高性能

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

(一).具体实现步骤如下

  • 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中
  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异
  • 把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新

(二).虚拟DOM一定会提高性能吗?

很多人认为虚拟DOM一定会提高性能,一定会更快,其实这个说法有点片面,因为虚拟DOM虽然会减少DOM操作,但也无法避免DOM操作

  • 它的优势是在于diff算法和批量处理策略,将所有的DOM操作搜集起来,一次性去改变真实的DOM,但在首次渲染上,虚拟DOM会多了一层计算,消耗一些性能,所以有可能会比html渲染的要慢
  • 注意,虚拟DOM实际上是给我们找了一条最短,最近的路径,并不是说比DOM操作的更快,而是路径最简单

(三).当渲染一个列表时,何为 key?设置 key 的目的是什么

Keys 会有助于 React 识别哪些 items 改变了,被添加了或者被移除了。Keys 应该被赋予数组内的元素以赋予(DOM)元素一个稳定的标识,选择一个 key 的最佳方法是使用一个字符串,该字符串能惟一地标识一个列表项。很多时候你会使用数据中的 IDs 作为 keys,当你没有稳定的 IDs 用于被渲染的 items 时,可以使用项目索引作为渲染项的 key,但这种方式并不推荐,如果 items 可以重新排序,就会导致 re-render 变慢。

(四).React Hooks在平时开发中需要注意的问题和原因

(1)不要在循环,条件或嵌套函数中调用Hook,必须始终在 React函数的顶层使用Hook

这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。

(2)使用useState时候,使用push,pop,splice等直接更改数组对象的坑

使用push直接更改数组无法获取到新值,应该采用析构方式,但是在class里面不会有这个问题。代码示例:

function Indicatorfilter() {
  let [num,setNums] = useState([0,1,2,3])
  const test = () => {
    // 这里坑是直接采用push去更新num
    // setNums(num)是无法更新num的
    // 必须使用num = [...num ,1]
    num.push(1)
    // num = [...num ,1]
    setNums(num)
  }
return (
    <div className='filter'>
      <div onClick={test}>测试</div>
        <div>
          {num.map((item,index) => (              <div key={index}>{item}</div>
          ))}      </div>
    </div>
  )
}

class Indicatorfilter extends React.Component<any,any>{
  constructor(props:any){
      super(props)
      this.state = {
          nums:[1,2,3]
      }
      this.test = this.test.bind(this)
  }

  test(){
      // class采用同样的方式是没有问题的
      this.state.nums.push(1)
      this.setState({
          nums: this.state.nums
      })
  }

  render(){
      let {nums} = this.state
      return(
          <div>
              <div onClick={this.test}>测试</div>
                  <div>
                      {nums.map((item:any,index:number) => (                          <div key={index}>{item}</div>
                      ))}                  </div>
          </div>

      )
  }
}

(3)useState设置状态的时候,只有第一次生效,后期需要更新状态,必须通过useEffect

TableDeail是一个公共组件,在调用它的父组件里面,我们通过set改变columns的值,以为传递给TableDeail 的 columns是最新的值,所以tabColumn每次也是最新的值,但是实际tabColumn是最开始的值,不会随着columns的更新而更新:

const TableDeail = ({    columns,}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
}

// 正确的做法是通过useEffect改变这个值
const TableDeail = ({    columns,}:TableData) => {
    const [tabColumn, setTabColumn] = useState(columns) 
    useEffect(() =>{setTabColumn(columns)},[columns])
}

(4)善用useCallback

父组件传递给子组件事件句柄时,如果我们没有任何参数变动可能会选用useMemo。但是每一次父组件渲染子组件即使没变化也会跟着渲染一次。

(5)不要滥用useContext

可以使用基于 useContext 封装的状态管理工具。

19.类组件和函数组件有何不同?

在 React 16.8版本(引入钩子)之前,使用基于类的组件来创建需要维护内部状态或利用生命周期方法的组件(即componentDidMountshouldComponentUpdate)。基于类的组件是 ES6 类,它扩展了 React 的 Component 类,并且至少实现了render()方法。

类组件:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

函数组件是无状态的(同样,小于 React 16.8版本),并返回要呈现的输出。它们渲染 UI 的首选只依赖于属性,因为它们比基于类的组件更简单、更具性能。

函数组件:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

20. React 高阶组件是什么,和普通组件有什么区别,适用什么场景

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

高阶组件(HOC)就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件,它只是一种组件的设计模式,这种设计模式是由react自身的组合性质必然产生的。我们将它们称为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件中的任何行为。

// hoc的定义
function withSubscription(WrappedComponent, selectData) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        data: selectData(DataSource, props)
      };
    }
    // 一些通用的逻辑处理
    render() {
      // ... 并使用新数据渲染被包装的组件!
      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };

// 使用
const BlogPostWithSubscription = withSubscription(BlogPost,
  (DataSource, props) => DataSource.getBlogPost(props.id));

1)HOC的优缺点

  • 优点∶ 逻辑服用、不影响被包裹组件的内部逻辑。
  • 缺点∶hoc传递给被包裹组件的props容易和被包裹后的组件重名,进而被覆盖

2)适用场景

  • 代码复用,逻辑抽象
  • 渲染劫持
  • State 抽象和更改
  • Props 更改

3)具体应用例子

  • 权限控制: 利用高阶组件的 条件渲染 特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别和 页面元素级别
    // HOC.js
    function withAdminAuth(WrappedComponent) {
        return class extends React.Component {
            state = {
                isAdmin: false,
            }
            async UNSAFE_componentWillMount() {
                const currentRole = await getCurrentUserRole();
                this.setState({
                    isAdmin: currentRole === 'Admin',
                });
            }
            render() {
                if (this.state.isAdmin) {
                    return <WrappedComponent {...this.props} />;
                } else {
                    return (<div>您没有权限查看该页面,请联系管理员!</div>);
                }
            }
        };
    }
    
    // pages/page-a.js
    class PageA extends React.Component {
        constructor(props) {
            super(props);
            // something here...
        }
        UNSAFE_componentWillMount() {
            // fetching data
        }
        render() {
            // render page with data
        }
    }
    export default withAdminAuth(PageA);
    
    
    // pages/page-b.js
    class PageB extends React.Component {
        constructor(props) {
            super(props);
        // something here...
            }
        UNSAFE_componentWillMount() {
        // fetching data
        }
        render() {
        // render page with data
        }
    }
    export default withAdminAuth(PageB);
  • 组件渲染性能追踪: 借助父组件子组件生命周期规则捕获子组件的生命周期,可以方便的对某个组件的渲染时间进行记录∶
    class Home extends React.Component {
            render() {
                return (<h1>Hello World.</h1>);
            }
        }
        function withTiming(WrappedComponent) {
            return class extends WrappedComponent {
                constructor(props) {
                    super(props);
                    this.start = 0;
                    this.end = 0;
                }
                UNSAFE_componentWillMount() {
                    super.componentWillMount && super.componentWillMount();
                    this.start = Date.now();
                }
                componentDidMount() {
                    super.componentDidMount && super.componentDidMount();
                    this.end = Date.now();
                    console.log(`${WrappedComponent.name} 组件渲染时间为 ${this.end - this.start} ms`);
                }
                render() {
                    return super.render();
                }
            };
        }
    
        export default withTiming(Home);   
    
    注意:withTiming 是利用 反向继承 实现的一个高阶组件,功能是计算被包裹组件(这里是 Home 组件)的渲染时间。
  • 页面复用
    const withFetching = fetching => WrappedComponent => {
        return class extends React.Component {
            state = {
                data: [],
            }
            async UNSAFE_componentWillMount() {
                const data = await fetching();
                this.setState({
                    data,
                });
            }
            render() {
                return <WrappedComponent data={this.state.data} {...this.props} />;
            }
        }
    }
    
    // pages/page-a.js
    export default withFetching(fetching('science-fiction'))(MovieList);
    // pages/page-b.js
    export default withFetching(fetching('action'))(MovieList);
    // pages/page-other.js
    export default withFetching(fetching('some-other-type'))(MovieList);

4) 高阶组件存在的问题

  • 静态方法丢失(必须将静态方法做拷贝)
  • refs 属性不能透传(如果你向一个由高阶组件创建的组件的元素添加ref引用,那么ref指向的是最外层容器组件实例的,而不是被包裹的WrappedComponent组件。)
  • 反向继承不能保证完整的子组件树被解析
    React 组件有两种形式,分别是 class 类型和 function 类型(无状态组件)。

我们知道反向继承的渲染劫持可以控制 WrappedComponent 的渲染过程,也就是说这个过程中我们可以对 elements、treestateprops 或 render() 的结果做各种操作。

但是如果渲染 elements tree 中包含了 function 类型的组件的话,这时候就不能操作组件的子组件了

21. 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

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

  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
  • 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

22. hooks父子传值

父传子
在父组件中用useState声明数据
 const [ data, setData ] = useState(false)

把数据传递给子组件
<Child data={data} />

子组件接收
export default function (props) {
    const { data } = props
    console.log(data)
}
子传父
子传父可以通过事件方法传值,和父传子有点类似。
在父组件中用useState声明数据
 const [ data, setData ] = useState(false)

把更新数据的函数传递给子组件
<Child setData={setData} />

子组件中触发函数更新数据,就会直接传递给父组件
export default function (props) {
    const { setData } = props
    setData(true)
}
如果存在多个层级的数据传递,也可依照此方法依次传递

// 多层级用useContext
const User = () => {
 // 直接获取,不用回调
 const { user, setUser } = useContext(UserContext);
 return <Avatar user={user} setUser={setUser} />;
};

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值