2024金三银四react面试必知

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

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

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

类组件:

js 复制代码
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;

函数组件:

js 复制代码
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 执行机制,写出下面代码运行的结果

javascript 复制代码
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一样触发页面变化。

javascript 复制代码
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 的基本用法示例:

js 复制代码
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 将数据传递到每个组件。

javascript 复制代码
const context = React.createContext();

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

javascript 复制代码
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()
javascript 复制代码
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中设置回调函数
javascript 复制代码
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.讲了这么多,直接来道题吧!不会做你就危险了!!!要恶补!!!!!!!

javascript 复制代码
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 的理解:

javascript 复制代码
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');

输出:

javascript 复制代码
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'。

最后再来一道

javascript 复制代码
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)

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

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

相关推荐
我爱加班、、15 分钟前
动静分离具体是怎么实现的?
前端·nginx
小远yyds1 小时前
鸿蒙手势密码
前端·华为·harmonyos·arkts
高木的小天才1 小时前
HarmonyOS应用开发中的页面路由与数据传输
前端·华为·typescript·harmonyos
木子七1 小时前
vue3-Pinia
前端·vue
前端金熊2 小时前
前端实现画中画超简单,documentPictureInPicture详细教程
前端·javascript
前端青山2 小时前
vue常用特性
前端·javascript·vue.js·前端框架
huazi992 小时前
全面解析:HTML页面的加载全过程(七)--浏览器渲染之绘制\分块\ 光栅化\画
前端
sp42a3 小时前
老旧前端项目如何升级工程化的项目
前端·webpack·重构
李小白202002023 小时前
Linux 生成/proc/config.gz
linux·服务器·前端
前端小臻3 小时前
后台管理-动态路由配置以及用户权限管理(vue3+element plus+koa+Sequelize )
前端·网络·node.js·koa