前端面试题详解整理77| state 与 props,key 的作用,如何使用 rollup 的,react 项目并实现一个 TodoList,

68 阅读6分钟

base深圳 冰智科技 前端实习面经

投递渠道:BOSS

时间线

  • 2024-02-16 打招呼投简历
  • 2024-02-18 一面
  • 2024-02-24 二面

一面

  1. 自我介绍与实习经历介绍

1. Vue3 生命周期

在Vue 3中,生命周期钩子函数和Vue 2有一些变化,主要是针对Composition API的引入以及对Options API的改进。以下是Vue 3中的主要生命周期钩子函数:

  1. beforeCreate

    • 在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。
  2. created

    • 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测 (data observer),属性和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el属性目前不可见。
  3. beforeMount

    • 在挂载开始之前被调用:相关的render函数首次被调用。
  4. mounted

    • el 被新创建的 vm.el替换,并挂载到实例上去之后调用该钩子函数。如果根实例挂载到了一个文档内的元素上,当mounted被调用时vm.el 替换,并挂载到实例上去之后调用该钩子函数。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.el 也在文档内。
  5. beforeUpdate

    • 数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。这里适合在更新之前访问现有 DOM,比如手动移除已添加的事件监听器。
  6. updated

    • 由于数据更改导致的虚拟 DOM 重新渲染和打补丁后调用。调用时,组件 DOM 已更新,可以执行依赖于DOM的操作。
  7. beforeUnmount

    • 在卸载之前调用。在这个阶段,实例仍然完全可用。
  8. unmounted

    • 在卸载之后调用。这个时候,实例的所有指令已经解绑,所有的事件监听器已经移除。
  9. errorCaptured

    • 当捕获一个来自子孙组件的错误时被调用。

需要注意的是,Vue 3中移除了Vue 2中的beforeDestroydestroyed钩子函数,取而代之的是beforeUnmountunmounted,以更贴合Composition API的语义。

3. v-if 与 v-show 的区别

v-ifv-show都是Vue.js中用于条件渲染的指令,但它们之间有一些重要的区别:

  1. v-if
    • v-if是“条件性地渲染”一个元素。即根据表达式的真假来决定是否渲染该元素及其内部内容。
    • 当表达式为真时,元素会被渲染到DOM中;当表达式为假时,元素不会被渲染到DOM中。
    • 在切换时,v-if条件块内的事件监听器和子组件适当地被销毁和重建,有助于节省内存消耗。
<div v-if="isDisplayed">
  Content to be displayed
</div>
  1. v-show
    • v-show是“条件性地显示或隐藏”一个元素。即根据表达式的真假来控制元素的显示或隐藏。
    • 当表达式为真时,元素会显示在页面上;当表达式为假时,元素会被设置为display: none,仍然存在于DOM中。
    • 在切换时,v-show元素不会被销毁和重建,因此它的事件监听器和子组件保持在DOM中,但是可能会影响页面的性能。
<div v-show="isDisplayed">
  Content to be displayed
</div>

主要区别总结如下:

  • v-if:在切换时条件块内的事件监听器和子组件会被销毁和重建,有助于节省内存消耗,适用于不经常切换的场景。
  • v-show:在切换时元素只是简单地切换了display属性,不会被销毁和重建,但可能会影响页面性能,适用于经常切换的场景。

因此,通常情况下,如果需要频繁切换元素的显示和隐藏,则可以使用v-show;如果只是需要根据条件动态渲染元素,可以使用v-if

  1. 什么是虚拟 DOM

4. useState 和 useEffect 如何使用

在React中,useStateuseEffect是两个常用的Hooks,用于管理组件的状态和副作用。下面分别介绍它们的使用方法:

使用 useState:

useState用于在函数式组件中添加状态。它接收一个初始状态作为参数,并返回一个状态变量和一个更新状态的函数,通常约定为数组解构赋值的形式。

import React, { useState } from 'react';

function MyComponent() {
  // 使用useState声明状态变量和更新函数
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      {/* 点击按钮时调用setCount更新状态 */}
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

使用 useEffect:

useEffect用于在函数式组件中执行副作用操作,比如订阅数据、操作DOM等。它接收一个回调函数作为参数,函数体内可以执行副作用操作,并返回一个清除副作用的函数(可选)。

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

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

  // 使用useEffect执行副作用操作
  useEffect(() => {
    // 更新文档标题
    document.title = `You clicked ${count} times`;

    // 返回清除副作用的函数(可选)
    return () => {
      document.title = 'React App';
    };
  }, [count]); // 第二个参数是依赖数组,表示什么状态变量发生变化时执行副作用

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

在上面的例子中,useEffect中的回调函数会在组件渲染后执行,并且每次count状态变化时都会执行。在这个例子中,我们使用useEffect来更新文档标题,并在组件卸载时清除副作用。

6. React 中 state 与 props 有什么区别(通常 state 的改变由网络请求或者用户操作引起,props 由父组件传递)

在React中,state和props都是用于管理组件数据的机制,但它们有一些重要的区别:

State(状态):

  1. 定义位置

    • state是由组件自身管理的数据,可以在组件内部通过useState或者this.state来定义和更新。
  2. 可变性

    • state是可变的,组件可以通过调用setState方法来更新state,并且更新state会触发组件的重新渲染。
  3. 作用范围

    • state的作用范围是局部的,即只能在定义它的组件内部访问和修改。
  4. 数据源

    • state通常用于存储组件内部的状态数据,例如用户输入、组件的展开状态等。

Props(属性):

  1. 定义位置

    • props是由父组件传递给子组件的数据,子组件无法直接修改props,只能通过父组件重新传递props来更新。
  2. 不可变性

    • props是不可变的,子组件无法直接修改props的值,只能通过父组件重新传递新的props来更新。
  3. 作用范围

    • props的作用范围是组件之间的通信,通过props可以将数据从父组件传递给子组件。
  4. 数据源

    • props通常用于传递数据给子组件,例如配置信息、初始化数据等。

总的来说,state和props都是用于管理组件数据的机制,但它们的定义位置、可变性、作用范围和数据源等方面有所不同。在React中,通常将组件的内部状态存储在state中,而将来自父组件的数据传递给子组件通过props实现。

8. key 的作用

在React中,key是用于帮助React识别列表中各个元素的唯一标识符。它主要的作用包括:

  1. 提高列表渲染性能: 当列表中的元素发生变化时,React会根据元素的key来识别哪些元素发生了变化、被添加或被移除,从而最小化DOM操作,提高渲染性能。

  2. 帮助React进行组件重用: 当列表中的元素发生变化时,React会尽可能地复用已经存在的组件实例,而不是重新创建一个新的实例。通过key的唯一性,React可以准确地判断哪些组件需要被更新,哪些组件需要被重新渲染。

  3. 维护组件的状态: 在列表中的元素发生变化时,如果没有为每个元素指定唯一的key,React会导致组件的状态混乱,可能会出现错误的更新或重新渲染。

  4. 优化动画效果: 在列表中元素发生变化时,通过key的唯一性,可以帮助React更好地处理动画效果,使得动画的过渡更加流畅自然。

总的来说,key是React中用于列表渲染优化和组件重用的重要机制,是帮助React识别和管理列表中各个元素的关键。因此,在使用React进行列表渲染时,应该为每个列表元素指定唯一的key,以确保React能够正确地识别和管理列表中的元素。

  1. 实习:如何进行项目规范搭建
  2. 实习:如何进行性能优化

11. 项目:介绍一下项目中如何使用 rollup 的

在项目中使用Rollup主要是为了打包JavaScript代码,它是一个功能强大的打包工具,特别适合用于构建JavaScript库和模块。

下面是使用Rollup的基本流程和一些常见的配置方式:

安装 Rollup:

npm install rollup --save-dev

创建配置文件:

创建一个名为rollup.config.js的配置文件,用于配置Rollup的打包规则和插件。

// rollup.config.js

import babel from '@rollup/plugin-babel';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default {
  input: 'src/main.js',
  output: {
    file: 'dist/bundle.js',
    format: 'umd'
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({
      exclude: 'node_modules/**' // 只编译我们的源代码
    })
  ]
};

编写源代码:

编写你的JavaScript源代码,通常放在src目录下。

运行 Rollup 打包:

运行以下命令来执行Rollup打包:

rollup -c

或者将其添加到package.json的scripts中:

{
  "scripts": {
    "build": "rollup -c"
  }
}

然后可以通过以下命令执行打包:

npm run build

使用插件:

Rollup本身只能处理ES模块,但是大部分的项目可能会包含CommonJS或AMD模块,所以需要使用一些插件来处理这些模块的引入。在配置文件中,我们使用了@rollup/plugin-node-resolve插件来帮助解析node_modules中的模块,以及@rollup/plugin-commonjs插件来将CommonJS模块转换为ES模块。

另外,我们还使用了@rollup/plugin-babel插件来使用Babel进行代码转换,使得我们可以在代码中使用ES6+语法,并且可以通过配置选项指定需要排除的文件或目录。

总的来说,Rollup是一个强大而灵活的打包工具,可以帮助我们将JavaScript代码打包成各种不同格式的文件,同时也支持各种插件来处理不同类型的模块和语法。

12. 手写代码:现场其一个 react 项目并实现一个 TodoList

以下是一个简单的React项目,实现了一个TodoList:

import React, { useState } from 'react';

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState('');

  const handleInputChange = (e) => {
    setInputValue(e.target.value);
  };

  const handleAddTodo = () => {
    if (inputValue.trim() !== '') {
      setTodos([...todos, inputValue]);
      setInputValue('');
    }
  };

  const handleDeleteTodo = (index) => {
    const newTodos = [...todos];
    newTodos.splice(index, 1);
    setTodos(newTodos);
  };

  return (
    <div>
      <h2>Todo List</h2>
      <input
        type="text"
        value={inputValue}
        onChange={handleInputChange}
        placeholder="Enter a new todo"
      />
      <button onClick={handleAddTodo}>Add Todo</button>
      <ul>
        {todos.map((todo, index) => (
          <li key={index}>
            {todo}
            <button onClick={() => handleDeleteTodo(index)}>Delete</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

在这个例子中,我们使用了useState来管理TodoList中的状态,包括todo列表和输入框的值。当用户输入新的todo时,通过handleAddTodo函数将其添加到todo列表中;当用户点击某个todo的删除按钮时,通过handleDeleteTodo函数删除对应的todo。