react和typescript面试题

446 阅读10分钟

1.React响应式原理

  • 使用 diff 算法实现,React 在 state 或 props 改变时,会调用 render() 方法,生成一个虚拟 DOM 树,React 会将这棵树与上一次生成的树进行比较,找出其中的差异,并更新差异的部分

2.使用redux的步骤

  • 思路:应用的整体全局状态以对象树的方式存放于单个 store唯一改变状态树(state tree)的方法是创建 action,一个描述发生了什么的对象,并将其 dispatch 给 store。 要指定状态树如何响应 action 来进行更新,你可以编写纯 reducer 函数,这些函数根据旧 state 和 action 来计算新 state

  • 总结:redux通过dispacth调用action的方式通知store以何种方式更新状态

  • 安装Redux Toolkit

    • # NPM
      npm install @reduxjs/toolkit
      ​
      # Yarn
      yarn add @reduxjs/toolkit
      
    • 定义全局的store,一般是分不同的模块,最后挂载到index.js全局入口

    • import { configureStore } from '@reduxjs/toolkit';
      import counterReducer from '../features/counter/counterSlice';
      ​
      export const store = configureStore({
        reducer: {
          counter: counterReducer,
        },
      });
      //
      import { Provider } from 'react-redux';
      import { store } from './app/store';
      ​
      root.render(
        <React.StrictMode>
          <Provider store={store}>
            <App />
          </Provider>
        </React.StrictMode>
      );
      
    • 通过redux/toolkit提供的createSlice同步方法定义子模块和createAsyncThunk异步方法定义子模块\

    • import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
      ​
      export const incrementAsync = createAsyncThunk(
        'counter/fetchCount',
        async (amount) => {
          const response = await fetchCount(amount);
          // The value we return becomes the `fulfilled` action payload
          return response.data;
        }
      );
      ​
      export const counterSlice = createSlice({
        name: 'counter',
        initialState:{
          value: 0,
          status: 'idle',
        },
        reducers: {
          increment: (state) => {
            state.value += 1;
          },
          decrement: (state) => {
            state.value -= 1;
          },
          incrementByAmount: (state, action) => {
            state.value += action.payload;
          },
        },
        extraReducers: (builder) => {
          builder
            .addCase(incrementAsync.pending, (state) => {
              state.status = 'loading';
            })
            .addCase(incrementAsync.fulfilled, (state, action) => {
              state.status = 'idle';
              state.value += action.payload;
            });
        },
      ​
      //导出子模块的actions
      export const { increment, decrement, incrementByAmount } = counterSlice.actions;
      ​
      //导出子模块的reducer
      export default counterSlice.reducer;
      
    • 在需要使用仓库数据的js文件中使用

      • 通过react-redux提供的useSelector调用仓库的变量
      • 通过react-redux提供的useDispatch调用仓库的方法来修改变量
    • import { useSelector, useDispatch } from 'react-redux';
      import {
        decrement,
        increment,
        incrementByAmount,
        incrementAsync,
        incrementIfOdd,
        selectCount,
      } from './counterSlice';
      ​
      export function Counter() {
        const count = useSelector(selectCount);
        // const count = useSelector(state => state.counter.value);
        const dispatch = useDispatch();
        const [incrementAmount, setIncrementAmount] = useState('2');
      ​
        const incrementValue = Number(incrementAmount) || 0;
      ​
        return (
          <div>
            <div className={styles.row}>
              {/* 加号和减号 */}
              <button
                className={styles.button}
                aria-label="Decrement value"
                onClick={() => dispatch(decrement())}
              >
                -
              </button>
              <span className={styles.value}>{count}</span>
              <button
                className={styles.button}
                aria-label="Increment value"
                onClick={() => dispatch(increment())}
              >
                +
              </button>
            </div>
            <div className={styles.row}>
              {/* 输入框 */}
              <input
                className={styles.textbox}
                aria-label="Set increment amount"
                value={incrementAmount}
                onChange={(e) => setIncrementAmount(e.target.value)}
              />
              <button
                className={styles.button}
                onClick={() => dispatch(incrementByAmount(incrementValue))}
              >
                Add Amount
              </button>
              <button
                className={styles.asyncButton}
                onClick={() => dispatch(incrementAsync(incrementValue))}
              >
                Add Async
              </button>
              <button
                className={styles.button}
                onClick={() => dispatch(incrementIfOdd(incrementValue))}
              >
                Add If Odd
              </button>
            </div>
          </div>
        );
      }
      

3.Hooks使用局限

  • 只能在函数组件中使用
  • 自定义 Hooks 命名规范:自定义 Hooks 必须以 use 开头,这是 React 对其的命名约定
  • 可能导致性能问题:如果不小心使用过多的 Hooks 或不正确地使用它们,可能会导致性能问题。比如在 useEffect 中不正确地使用依赖数组,会导致重复渲染或内存泄漏。
  • 没有生命周期方法
  • 顺序和条件性调用:Hooks 必须按照规定的顺序调用,并且不能在循环、条件语句或嵌套函数中调用

4.React常用的hooks

  • useState

  • useEffect:用于在函数组件中执行副作用操作,它在组件渲染完成后执行,并可以在组件更新时根据指定的依赖项进行重新执行。

    useEffect 接受两个参数:一个函数和一个可选的依赖项数组。函数参数表示需要执行的副作用操作,而依赖项数组表示需要监视的状态或变量。如果依赖项数组发生变化,React 将会重新执行副作用操作

5.type和interface的区别

  • 总结:interface是接口声明,type是自定义类型声明,接口可能是上下文参数的声明,而类型可能是接口中某个对象的属性类型
  • interface定义数据的具体数据结构如何,有哪些属性;type定义数据的具体值是什么类型
  • interface可以被class继承和实现,也可以继承class;type不行
  • interface不能作为交叉、联合类型的产物,而type没有限制
  • 定义两个同名的type会报异常,而interface会合并

6.React响应式原理

  • 使用 diff 算法实现,React 在 state 或 props 改变时,会调用 render() 方法,生成一个虚拟 DOM 树,React 会将这棵树与上一次生成的树进行比较,找出其中的差异,并更新差异的部分

7.TypeScript常用工具集

  • Partial:可以将类型的所有属性都变为 可选 的
interface User {
  name: string;
  age: number;
}
​
type PartialUser = Partial<User>; // { name?: string; age?: number; }
  • Required:可以将类型的所有属性都变为 必填 的(与 Partial 工具类型相反)
interface User {
  name?: string;
  age?: number;
}
​
type RequiredUser = Required<User>; // { name: string; age: number; }
  • Readonly:可以将类型的所有属性设为 只读 ,这意味着属性不可以被重新赋值
interface User {
  name: string;
  age: number;
}
​
type ReadonlyUser = Readonly<User>;
  • Pick:可以从类型中选取出指定的键值,然后组成一个新的类型
interface User {
  name: string;
  age: number;
  email: string;
}
​
type BasicUserInfo = Pick<User, 'name' | 'age'>; // { name: string; age: number; }
  • Omit:返回去除指定的键值之后返回的新类型(与 Pick 类型相反)
interface User {
  name: string;
  age: number;
  email: string;
}

type UserInfoWithoutEmail = Omit<User, 'email'>; // { name: string; age: number; }
  • Record:创建一个具有键类型和值类型的对象类型
type Weekday = 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday';
interface WorkSchedule {
  [day: string]: string;
}

const schedule: Record<Weekday, string> = {
  Monday: 'Work from home',
  Tuesday: 'Client meeting',
  Wednesday: 'Team lunch',
  Thursday: 'Work on project',
  Friday: 'Company event',
};
  • Exclude:从联合类型中去除指定的类型
type AllowedColors = 'red' | 'green' | 'blue';
type PrimaryColors = 'red' | 'blue';

type NonPrimaryColors = Exclude<AllowedColors, PrimaryColors>; // 'green'
  • Extract:从联合类型中提取指定的类型,类似于操作接口类型中的 Pick 类型(和Exclude相反)
type AllowedColors = 'red' | 'green' | 'blue';
type PrimaryColors = 'red' | 'blue';

type OnlyPrimaryColors = Extract<AllowedColors, PrimaryColors>; // 'red' | 'blue'

8. TypeScript

  • 介绍

    • TypeScript是JavaScript类型的超集,他可以编译成纯JavaScript代码
    • TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的
  • 编译代码

    • tsc xxx.ts
  • 类型注解

    • TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式
  • 接口

    • 在TypeScript里允许我们在实现接口时候只要保证包含了接口要求的结构,而不必明确地使用 implements语句
    • interface Person {
          firstName: string;
          lastName: string;
      }
      
      function greeter(person: Person) {
          return "Hello, " + person.firstName + " " + person.lastName;
      }
      
      let user = { firstName: "Jane", lastName: "User" };
      
      document.body.innerHTML = greeter(user)
      
    • TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。
    • class Student {
          fullName: string;
          constructor(public firstName, public middleInitial, public lastName) {
              this.fullName = firstName + " " + middleInitial + " " + lastName;
          }
      }
      
      interface Person {
          firstName: string;
          lastName: string;
      }
      
      function greeter(person : Person) {
          return "Hello, " + person.firstName + " " + person.lastName;
      }
      
      let user = new Student("Jane", "M.", "User");
      
      document.body.innerHTML = greeter(user);
      

9.typescript元组

  • 元组( Tuple )表示一个 已知数量类型 的数组
  • 元组用于表示一个结果,且有预定的长度,每一项可以是不同的类型
// 记录一个人的姓名、性别和年龄
let alex = ['Alex', 'male', 20];
let lucy = ['Lucy', 'female', 18];
// 我们在给元组某一项赋值的时候,只能赋值这一项的类型。比如,第 0 项是 string,那么我们就无法赋值为 number
alex[0] = 'Alexa';
// 如果我们想赋值一个下标超出了元组范围的话,某一项的话,那么这一项就可以既赋值为 string,又可以赋值为 number,但是无法赋值为其他类型
alex[3] = 3;
// 另外,我们直接给 alex 赋值的时候,不能够只赋值其中某一项,必须把这三项全部赋值给他才行,下面这样的话,就会报错
alex = ['Alex', 'male'];

10.useMemo和useCallback的区别

  • 总结:useMemouseCallback都是用来优化性能的Hooks,useMemo一般通过减少不必要的复杂计算来优化性能,useCallback一般用于给子组件传递回调函数时,减少子组件的渲染次数,从而优化性能

  • useMemo(factory, Dependencies)

    • 其中factory是工厂函数, Dependencies是其所有依赖值的数组
    • 在组件初始化的时候会调用factory函数,并将返回值存储起来。当组件需要重新渲染时,react会首先依次判断每个依赖项的值是否发生了变化。如果没有变化,则直接使用之前的存储的返回值,而不会再次调用factory,如果依赖项发生变化,则会使用更新后的依赖项调用factory函数,返回最新的值
  • useCallback(callback, Dependencies)

    • callback是一个函数对象,Dependencies是依赖项数组
    • 默认情况下,每当一个组件中重新渲染,其中的函数都会重新声明。这样就会导致一个问题:如果该组件将这个函数传递给子组件,那么子组件也会重新渲染,但有时这样的渲染是不必要的

11.react组件通信

  • 父传子

    • 在父组件中的子组件标签上定义自定义属性,子组件中通过props接收
  • 子传父

    • 在父组件中的子组件标签上定义自定义属性和方法,子组件通过调用父组件方法进行通信
  • 兄弟组件

    • 将共享状态提升到最近的公共父组件中,由公共父组件管理这个状态
  • 跨组件通信

    • 导入并调用createContext方法,得到Context对象,导出
    • import { createContext } from 'react'
      export const Context = createContext()
      
    • 使用 Provider 组件包裹根组件,并通过 value 属性提供要共享的数据
    • return (
       <Context.Provider value={ 这里放要传递的数据 }>
             <根组件的内容/>
       </Provider>
      )
        
      
    • 在任意后代组件中,如果希望获取公共数据: 导入useContext;调用useContext(第一步中导出的context) 得到value的值
    • import React, { useContext } from 'react'
      import { Context } from './index'
       const 函数组件 = () => {
           const 公共数据 = useContext(Context)
           return ( 函数组件的内容 )
       }
        
      

12.react和vue的区别

  • 核心思想不同:React推崇函数式编程(纯组件),数据不可变以及单向数据流,Vue则灵活易用的渐进式框架,进行数据拦截/代理,它对侦测数据的变化更敏感、更精确

  • 组件写法差异:React推荐的做法是JSX,也就是把 HTML 和 CSS 全都写进 JavaScript 中,Vue 推荐的做法是 template 的单文件组件格式,即 html,css,JS 写在同一个文件

  • diff算法不同:

    • 总结:

      • 传统Diff算法是循环递归每一个节点,算法复杂度为O(n*3)
      • 采用虚拟DOM,算法复杂度为O(n)
      • 不同的组件产生不同的DOM结构,type不同,就会直接删除旧DOM,创建新的DOM
      • 同一层次的一组子节点,可以通过唯一的 key 区分
    • React的Diff算法核心实现

      • react首先对新集合进行遍历
      • 通过唯一key来判断老集合中是否存在相同的节点。如果没有的话创建,
      • 如果有的话,会将节点在新集合中的位置和在老集合中lastIndex进行比较
      • 如果渲染的节点位置小于老位置,进行移动操作,否则不进行移动操作
      • 如果遍历的过程中,发现在新集合中没有,但在老集合中有的节点,会进行删除操作
    • Vue的Diff算法核心实现

      • 旧children新children各有两个头尾的变量StartIdxEndIdx,它们的2个变量相互比较,一共有4种比较方式
      • 如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明旧children新children至少有一个已经遍历完了,就会结束比较
  • 响应式原理不同:

    • Vue

      • Vue依赖收集,自动优化,数据可变,
      • Vue递归监听data的所有属性,直接修改
      • 当数据改变时,自动找到引用组件重新渲染
    • React

      • React基于状态机,手动优化,数据不可变,需要setState驱动新的state替换老的state,数据改变时,React 中会需要 shouldComponentUpdate 这个生命周期函数方法来进行控制