likes
comments
collection
share

2024金三银四react面试葵花宝典,欲练此功。。。

作者站长头像
站长
· 阅读数 8

程序员的金三银四求职宝典

1.用类组件和函数组件分别编写react的不同 生命周期

在 React 中,类组件和函数组件的生命周期函数有所不同。以下是分别使用类组件和函数组件编写的挂载、更新和卸载阶段的生命周期函数示例:

类组件:

import React, { Component } from 'react';

class LifecycleComponent extends Component {
  constructor(props) {
    super(props);
    console.log('Constructor called');
  }

  componentDidMount() {
    console.log('Component did mount');
  }

  componentDidUpdate(prevProps, prevState) {
    console.log('Component did update');
  }

  componentWillUnmount() {
    console.log('Component will unmount');
  }

  render() {
    console.log('Render called');
    return (
      <div>
        Lifecycle Component
      </div>
    );
  }
}

export default LifecycleComponent;

函数组件:

import React, { useEffect } from 'react';

const FunctionComponent = () => {
  useEffect(() => {
    console.log('Component did mount');

    return () => {
      console.log('Component will unmount');
    };
  }, []);

  useEffect(() => {
    console.log('Component did update');
  });

  console.log('Render called');

  return (
    <div>
      Function Component
    </div>
  );
};

export default FunctionComponent;

在上面的示例中:

  • 类组件中的生命周期函数包括 constructorcomponentDidMountcomponentDidUpdatecomponentWillUnmount
  • 函数组件中使用 useEffect 钩子模拟了生命周期函数,第一个 useEffect 用于模拟 componentDidMountcomponentWillUnmount,第二个 useEffect 用于模拟 componentDidUpdate

无论是类组件还是函数组件,都能够实现 React 组件的生命周期功能。根据实际需求选择适合的编码方式。

2. useMemo,ReactMemo,useCallBack,三者的区别

在 React 中,useMemoReact.memouseCallback 是用于性能优化的钩子函数,它们的作用和用法略有不同。以下是它们的区别:

  1. useMemo

    • useMemo 用于在渲染过程中执行一些计算,并将计算结果进行缓存,只有当依赖项发生变化时才会重新计算。
    • 语法:const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    • useMemo 的第一个参数是一个函数,用于计算需要缓存的值,第二个参数是一个依赖数组,只有当依赖项发生变化时,才会重新计算值。
  2. React.memo

    • React.memo 是一个高阶组件,用于包裹函数组件,用于对组件进行浅比较的 PureComponent 的功能。
    • 当函数组件的 props 没有发生变化时,React.memo 将会使用之前的渲染结果,从而避免不必要的重新渲染。
    • 语法:const MemoizedComponent = React.memo(MyComponent);
    • React.memo 只接受一个参数,即需要进行性能优化的函数组件。
  3. useCallback

    • useCallback 用于缓存一个回调函数,并且只有在依赖项发生变化时才会重新创建这个回调函数。
    • 语法:const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
    • useCallback 的第一个参数是一个回调函数,第二个参数是一个依赖数组,只有当依赖项发生变化时,才会重新创建回调函数。

综上所述,useMemo​​和​​useCallback​​接收的参数都是一样,都是在其依赖项发生变化后才执行,都是返回缓存的值,区别在于​​useMemo​​返回的是函数运行的结果,​​useCallback​​返回的是函数。 useCallback(fn,deps)相当于 useMemo(()=> fn,deps) 。类似 shouldComponentUpdate, 判定该组件的 props 和 state 是否有变化,从而避免每次父组件render时都去重新渲染子组件 useCallback返回一个函数,当把它返回的这个函数作为子组件使用时,可以避免每次父组件更新时都重新渲染这个子组件,子组件一般配合 memo 使用

React.memo()useMemo() 的主要区别

从上面的例子中,我们可以看到 React.memo() 和 useMemo() 之间的主要区别:

React.memo() 是一个高阶组件,我们可以使用它来包装我们不想重新渲染的组件,除非其中的 props 发生变化 useMemo() 是一个 React Hook,我们可以使用它在组件中包装函数。 我们可以使用它来确保该函数中的值仅在其依赖项之一发生变化时才重新计算

虽然 memoization 似乎是一个可以随处使用的巧妙小技巧,但只有在绝对需要这些性能提升时才应该使用它。 Memoization 会占用运行它的机器上的内存空间,因此可能会导致意想不到的效果。

3.useEffectuseMemo 区别

useEffect是在DOM改变之后触发,useMemo在DOM渲染之前就触发 useMemo是在DOM更新前触发的,就像官方所说的,类比生命周期就是[shouldComponentUpdate] useEffect可以帮助我们在DOM更新完成后执行某些副作用操作,如数据获取,设置订阅以及手动更改 React 组件中的 DOM 等 4.不要在这个useMemo函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo 5.在useMemo中使用setState你会发现会产生死循环,并且会有警告,因为useMemo是在渲染中进行的,你在其中操作DOM后,又会导致触发memo

4.问题:既然memo对性能优化有好处,为什么不把每个组件都包一下?

因为memo有缓存,大量使用,造成大量的性能开销。其次,props的比较只是浅比较,会有一些坑(解决浅比较的方式有:重新定义一个对象或者数组比如{...object,1},不用pop或者push)

useCallback 和 useMemo 仅仅在后续渲染(也就是重渲染)中起作用,在初始渲染中它们反而是有害的 useCallback 和 useMemo 作用于 props 并不能避免组件重渲染。只有当每一个 prop 都被缓存,且组件本身也被缓存的情况下,重渲染才能被避免。 只要有一丁点疏忽,那么你做的一切努力就打水漂了。所以说,简单点,把它们都删了吧。把包裹了“纯 js 操作“的 useMemo 也都删了吧。 与组件本身的渲染相比,它缓存数据带来的耗时减少是微不足道的,并且会在初始渲染时消耗额外的内存,造成可以被观察到的延迟。

5. useEffectuseLayoutEffectuseInsertionEffect分别用来干什么?

1.useEffect: 用于副作用捕获,在 dom 元素渲染之后调用,常用于页面数据处理工作。 2.useLayoutEffect: useEffect 的一个版本,在 DOM 更新之后同步执行,但在浏览器绘制之前执行,常用于页面元素布局工作,可能会阻塞页面渲染。 3.useInsertionEffect: useEffect 的一个版本,在 DOM 更新前执行。常用于 CSS-in-JS 插入动态样式。

6. useEffect 执行机制,写出下面代码运行的结果

import React, { useState } from "react";
import ReactDOM from "react-dom";

function App() {
  const [n, setN] = useState(0);
  const onClick = () => {
    setN(n + 1);
  };
  React.useEffect(() => {
    console.log("App");
    return () => {
      console.log("App挂了");
    };
  });
  return (
    <div className="App">
      <h1>n: {n}</h1>
      <button onClick={onClick}>+1</button>
      {/* {n % 2 === 0 ? <B /> : ""} */}
      <B />
    </div>
  );
}

function B() {
  const [m, setM] = useState(0);
  const onClick = () => {
    setM(m + 1);
  };
  React.useEffect(() => {
    console.log("B");

    return () => {
      console.log("B挂了");
    };
  });
  useEffect(()=>{
    let tag = false
    api('').then(res=>{
      if(tag){
        setData(res.data)
      }
    })
    return ()=>{
      tag = true
    }
  },[search])
  return (
    <div>
      B组件
      <h1>m: {m}</h1>
      <button onClick={onClick}>+1</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  • 解析: 注意点:useEffect 是在render结束之后才执行的。

组件 App 首次渲染后,先执行 console.log("B"); 再执行 console.log("App")

当执行 n + 1 之后,先执行 console.log("B挂了"),再执行 console.log("B"), 再执行 console.log("App挂了"), 最后执行console.log("App"), 程序结束。

当执行 m + 1 之后,先执行 console.log("B挂了"),再执行console.log("B"), 程序结束。

当组件 App内,使用 useState 创建的变量,发生变化时,会造成重新render,也就导致原组件(包含子组件)的销毁,以及新组件(包含子组件)的诞生。

可以得出,每次重新渲染,都会导致原组件(包含子组件)的销毁,以及新组件(包含子组件)的诞生。

  • 结论:

1、首先渲染,并不会执行 useEffect 中的 return

2、变量修改后,导致的重新render,会先执行 useEffect 中的 return,再执行useEffect内除了return部分代码。

3、return 内的回调,可以用来清理遗留垃圾,比如订阅或计时器 ID 等占用资源的东西。

7. 除了上述react常用的hooks,你还会用哪些hooks?

这个问题是送分题!随便写几个吧!

useRef

useRef返回的是一个可变的ref对象,其.current属性被初始化传入参数。返回的ref对象在组件的整个生命周期保持不变。

这个ref对象只有一个current属性,它的地址一直不会变。useRef变化不会主动使页面渲染,不会跟useState或者useReducer一样触发页面变化。

import React, { useRef } from "react";
export default function App() {
  const r = useRef(0);
  console.log(r);
  const add = () => {
    r.current += 1;
    console.log(`r.current:${r.current}`);
  };
  return (
    <div className="App">
      <h1>r的current:{r.current}</h1>
      <button onClick={add}>点击+1</button>
    </div>
  );
}

useReducer

useReducer 是 React 提供的一个钩子函数,用于在函数组件中管理状态,并以类似于 Redux 的方式进行状态管理。它是一个替代 useState 的选择,对于一些复杂的状态逻辑或者需要多个状态之间协同工作的情况下特别有用。

useReducer 接收一个 reducer 函数和一个初始状态,并返回一个包含当前状态值和 dispatch 函数的数组。reducer 函数接收两个参数:当前状态和要执行的操作(action),并返回新的状态。dispatch 函数用于触发执行 reducer 函数以更新状态。

以下是 useReducer 的基本用法示例:

import React, { useReducer } from 'react';

// 定义 reducer 函数
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
};

// 初始状态
const initialState = { count: 0 };

// App 组件
function App() {
  // 使用 useReducer 来管理状态
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}

在上面的示例中,我们首先定义了一个 reducer 函数,它接收当前状态和操作,并返回新的状态。然后,我们使用 useReducer 钩子来管理状态,并传入 reducer 函数和初始状态。最后,在组件中使用 dispatch 函数来分发操作,并根据操作类型更新状态。

useReducer 适用于需要复杂状态逻辑、多个状态之间有依赖关系、或者需要根据前一个状态计算下一个状态的情况。相比于 useState,它提供了更灵活的状态管理方式,并且可以更好地处理复杂的状态更新逻辑。

useContext

用于在函数组件中访问上下文(context)的值。上下文允许您在组件树中传递数据,而不必手动地通过 props 将数据传递到每个组件。

const context = React.createContext();

redux中常用的api: createStore, compose, applyMiddleware,connect, mapStateToProps将state映射到UI组件参数, mapDispatchToProps负责输出逻辑,将用户对ui组件的操作映射成action

import {connect} from 'react-redux';
const mapStateToProps = (state, ownProps) => {
    return {
        prop: state.prop
    }
}
const  mapDispatchToProps = (dispatch, ownProps) => {
    return {
        dispatch1: () => {
            dispatch(actionCreator)
        }
    }
}
const visible = connect(mapStateToProps,mapDispatchToProps)(Todolist)

8.react实现同步任务的方式有哪些?

  1. React 控制之外的的事件中 setState 同步更新, 比如原生 js 绑定事件,异步执行的 setTimeout/setInterval, Promise.then()
onClick = () => {
    setTimeout(() => {
      console.log('state 1');
      this.setState({
        num: this.state.num + 1,
      });
      console.log('state 2');
      this.setState({
        times: this.state.times +1,
      });
      console.log('state 3');
    }, 1000);
  }
  1. setState中设置回调函数
this.setState(preState => ({
      num: preState.num + 1,
    }),() => {
      console.log(this.state.num);
    })
  1. 使用async/await 首先我们要理解 async/await 。 async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到触发的异步操作完成,再接着执行函数体内后面的语句( await 通常用来等待一个 Promise ,但是也可以等待一般的函数表达式,等待一般表达式时相当于使用 Promise.resolve() 包装其返回值)。也就是说函数体中 await 后的语句都是异步触发,此时已经脱离了 React 的调度,所以 setState 变成了同步更新。

9.讲了这么多,直接来道题吧!不会做你就危险了!!!要恶补!!!!!!!

onClick = () => {
    //a
    this.setState({
      num: this.state.num + 1,
    })
    console.log('1:',this.state.num);
    //b
    this.setState({
      num: this.state.num + 1,
    })
    console.log('2:',this.state.num);
    setTimeout(() => {
      //c
      this.setState({
        num: this.state.num + 1,
      });
      console.log('3:',this.state.num);
    }, 0);
    //d
    this.setState(preState => ({
      num: preState.num + 1,
    }),() => {
      console.log('4:',this.state.num);
    })
    //e
    this.setState(preState => ({
      num: preState.num + 1,
    }))
    console.log('5:',this.state.num);
}

更新 c 在 setTimeout 中,即使延迟时间为 0 ,也属于宏任务;其他 4 次更新会合并,所以总共实际更新两次。d 中的 log 放在回调函数中,属于微任务,所以 5 次 log 的顺序时 1, 2, 5, 4, 3 。 第一次更新中,a, b 两次 setState 中,this.state.num 都为 1 ,所以更新后 num 为 2, d, e 两次 setState 中,preState.num 都可以拿到即时更新结果,分别为 2 ,3 所以更新后 num 为 4 。 第二次更新中,this.state.num 已经是 4 了,故更新后 num 为 5。


附一道简单的 async/await 面试题,加深一下对 async/await 的理解:

async function async1(){
  console.log('async1 start');
  let res = await async2();
  console.log(res);
  console.log('async1 end');
}

async function async2(){
  console.log('async2 start');
  return 'async2 end'
}

console.log('script start');
setTimeout(() => {
  console.log('setTimeout');
}, 0);
async1();
new Promise((resolve,reject) => {
  console.log('Promise start');
  resolve();
}).then(() => {
  console.log('Promise end');
})
console.log('script end');

输出:

script start
VM91:2 async1 start
VM91:9 async2 start
VM91:19 Promise start
VM91:24 script end
VM91:4 async2 end
VM91:5 async1 end
VM91:22 Promise end
undefined
VM91:15 setTimeout

解析:

主线程首先打印 'script start' 后遇到 setTimeout ,函数体进入宏任务; 执行函数 async1 ,打印 'async1 start', 遇到 await ,进入函数 async2,打印 'async2 start', 返回值 'async2 end' 会被 Promsie.resolve() 包装,进入微任务; 进入 new Promsie(), 打印 'Promise start', resolve('Promise end') 进入微任务 打印 'script end' ,主线程结束。 微任务1, 函数 async2 返回, 函数 async2 等待结束,打印 'async2 end', 'async1 end'。 微任务2,Promise.then(), 打印 'Promise end'。微任务清空。 执行宏任务,打印 'setTimeout'。

最后再来一道

console.log(1)
setTimeout(() => {
    console.log(2)
    new Promise((resolve, reject) => {
        console.log(3)
        resolve(4)
    }).then((res) => {
        console.log(res) 
    })
}, 0)
new Promise((resolve, reject) => {
    console.log(5)
    resolve()
}).then(() => {
    console.log(6)
    setTimeout(() => {
        console.log(7)
    })
    return Promise.resolve(8)
}).then(res => {
    console.log(res);
})
console.log(9)

你会做了吗?写出你的答案吧!

10. 深度解析react中hooks的底层原理和fiber架构原理

关于fiber这里想聊的比较多,具体可以去看我的另一篇文,

11. react的diff算法的原理,以及和vue的diff算法的区别

React 和 Vue 都使用了虚拟 DOM 和 diff 算法来提高页面更新的性能,但它们的 diff 算法在实现上有一些区别。

React 的 diff 算法原理:

  1. 虚拟 DOM: React 使用虚拟 DOM 来表示真实 DOM 的结构,每次更新时先生成新的虚拟 DOM 树,然后与旧的虚拟 DOM 树进行比较,找出需要更新的部分。

  2. 差异计算: React 使用一种叫做“双端比较”的策略来计算差异。它会先将新旧虚拟 DOM 树的根节点进行比较,然后分别比较子节点,找出节点间的差异。

  3. 高效更新: React 通过遍历虚拟 DOM 树的节点并进行比较,将差异记录下来,并最终将这些差异应用到真实 DOM 上,从而实现高效的更新。

Vue 的 diff 算法原理:

  1. 虚拟 DOM: 和 React 类似,Vue 也使用虚拟 DOM 来表示真实 DOM 的结构。

  2. 双指针算法: Vue 使用一种叫做“双指针”或“同层比较”的策略来进行 diff。它会将旧虚拟 DOM 树和新虚拟 DOM 树进行同层级比较,找出节点的差异。

  3. 节点复用: Vue 会尽可能地复用相同类型的节点,而不是简单地替换整个节点,从而减少了更新时的操作。

区别:

  1. 策略不同: React 使用双端比较策略,而 Vue 使用双指针或同层比较策略。

  2. 节点复用: Vue 更注重节点的复用,尽可能地复用相同类型的节点,而 React 则更倾向于替换整个节点。

  3. 性能差异: 由于实现策略不同,React 和 Vue 的 diff 算法在性能上可能会有所不同,但具体的性能差异会受到多种因素的影响,如虚拟 DOM 树的大小、节点的层级结构等。

总的来说,React 和 Vue 的 diff 算法都是为了实现高效的页面更新,它们在实现上有一些差异,但都能够很好地满足前端开发的需求。

12. redux

本来不想聊这个,但怕总结的不够全,还是多嘴说一下吧。已经知道的同学,就不用看了。

Redux 是一个用于 JavaScript 应用程序状态管理的开源库,它提供了一种可预测性、可维护性和高效性的状态管理方案。Redux 主要用于管理应用程序的状态(state),并提供了一套规范的工作流程,使得状态的变化变得可控、可追踪和可调试。

Redux 的核心概念:

  1. Store(仓库): 存放应用程序状态的容器,是 Redux 的核心。它包含了整个应用程序的状态树,并提供了一些方法来获取、更新和订阅状态的变化。

  2. Action(动作): 用于描述状态变化的对象。它是一个普通的 JavaScript 对象,必须包含一个 type 属性来描述动作的类型,可以携带一些额外的数据(payload)来描述动作的具体内容。

  3. Reducer(归纳器): 用于描述状态树如何响应动作并更新状态的函数。它是一个纯函数,接收当前状态和一个动作对象作为参数,然后返回新的状态。Reducer 函数是不可变的,它不能修改原来的状态,而是返回一个全新的状态。

  4. Dispatch(派发): 将动作发送到仓库的过程。调用仓库的 dispatch 方法并传入一个动作对象,仓库会调用相应的 reducer 来处理动作,并更新状态。

  5. Middleware(中间件): 是一个扩展 Redux 功能的机制。它允许在派发动作和 reducer 之间执行额外的逻辑,如日志记录、异步操作等。常见的中间件有 Redux Thunk、Redux Saga 等。

Redux 的工作流程:

  1. 应用程序通过 dispatch 方法发送一个动作对象。
  2. 仓库接收到动作对象,并调用相应的 reducer 函数来处理动作。
  3. reducer 函数根据动作类型和当前状态,生成新的状态,并返回给仓库。
  4. 仓库接收到新的状态,并通知所有订阅者(通过 subscribe 方法注册的回调函数)。
  5. 订阅者接收到状态的变化,并执行相应的更新操作,例如重新渲染界面。

Redux 的优势:

  • 单一数据源: 整个应用程序的状态被存储在单一的数据源中,使得状态管理更加统一和可控。
  • 可预测性: 所有的状态变化都是通过派发动作来触发的,可以准确地追踪状态的变化和管理应用程序的行为。
  • 可扩展性: Redux 提供了一套灵活的中间件机制,可以轻松地扩展功能,满足各种复杂的业务需求。
  • 方便调试: Redux 提供了强大的开发者工具,可以在开发过程中方便地调试状态的变化、动作的派发等。

以下是一个简单的 Redux 示例,演示了如何创建一个基本的 Redux 应用,并使用 Redux 来管理状态:

  1. 首先,安装 Redux 库:
npm install redux
  1. 创建 Redux store 和 reducer:
// reducers.js
const initialState = {
  count: 0
};

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    default:
      return state;
  }
};

export default counterReducer;
// store.js
import { createStore } from 'redux';
import counterReducer from './reducers';

const store = createStore(counterReducer);

export default store;
  1. 在应用程序中使用 Redux:
// App.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import store from './store';

function App() {
  const count = useSelector(state => state.count);
  const dispatch = useDispatch();

  const handleIncrement = () => {
    dispatch({ type: 'INCREMENT' });
  };

  const handleDecrement = () => {
    dispatch({ type: 'DECREMENT' });
  };

  return (
    <div>
      <h1>Counter: {count}</h1>
      <button onClick={handleIncrement}>Increment</button>
      <button onClick={handleDecrement}>Decrement</button>
    </div>
  );
}

export default App;
  1. 将 Redux store 与 React 应用程序连接:
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

在这个示例中,我们创建了一个简单的 Redux 应用程序,包括一个计数器的状态和两个动作(INCREMENT 和 DECREMENT)。通过使用 useSelectoruseDispatch 钩子函数,我们可以在 React 组件中直接访问 Redux store 中的状态,并派发动作来更新状态。最后,通过 Provider 组件将 Redux store 注入到整个应用程序中,使得所有的子组件都能够访问到 Redux store。

总的来说,Redux 是一个强大而灵活的状态管理库,适用于各种规模的 JavaScript 应用程序,它提供了一种统一且可预测的状态管理方案,使得应用程序的状态变化变得更加可控和可维护。

最后但也是全文最重要的,码字不易,如果觉得帮到你,欢迎关注收藏!!!!!你们的鼓励是我持之以恒码字的动力,感谢感谢感谢!!!!祝你面试必过,收获满意offer!!!✌️