react面试题

1.了解redux吗?

redux 是一个应用数据流框架,主要解决了组件之间状态共享问题,原理是集中式管理,主要有三个核心方法:action store reduce

工作流程
view 调用store的dispatch 接受action传入的store,reduce进行state操作

view通过store提供的getState获取最新的数据

redux的优点:

新增的state 对状态的管理更加明确

流程更加规范,减少手动编写代码,提高编码效率

redux的缺点:

当数据更新是有时候组件不需要,也要重新绘制,影响效率

2.react生命周期

3.React 中 keys 的作用是什么?

Keys是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识

  • 在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性

4.传入 setState 函数的第二个参数的作用是什么?

该函数会在 setState 函数调用完成并且组件开始重渲染的时候被调用,我们可以用该函数来监听渲染是否完成:

this.setState(
  { username: 'tylermcginnis33' },
  () => console.log('setState has finished and the component has re-rendered.')
)
this.setState((prevState, props) => {
  return {
    streak: prevState.streak + props.count
  }
})

4.React 中 refs 的作用是什么

  • Refs 是 React 提供给我们的安全访问 DOM元素或者某个组件实例的句柄
  • 可以为元素添加ref属性然后在回调函数中接受该元素在 DOM 树中的句柄,该值会作为回调函数的第一个参数返回

5.在生命周期中的哪一步你应该发起 AJAX 请求

我们应当将AJAX 请求放到 componentDidMount 函数中执行,主要原因有下

  • React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componentWillMount。如果我们将 AJAX 请求放到 componentWillMount 函数中,那么显而易见其会被触发多次,自然也就不是好的选择。
  • 如果我们将AJAX 请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 请求则能有效避免这个问题

6.什么是高阶组件(HOC)

  • 高阶组件(Higher Order Componennt)本身其实不是组件,而是一个函数,这个函数接收一个元组件作为参数,然后返回一个新的增强组件,高阶组件的出现本身也是为了逻辑复用,举个例子
function withLoginAuth(WrappedComponent) {
  return class extends React.Component {

      constructor(props) {
          super(props);
          this.state = {
            isLogin: false
          };
      }

      async componentDidMount() {
          const isLogin = await getLoginStatus();
          this.setState({ isLogin });
      }

      render() {
        if (this.state.isLogin) {
            return <WrappedComponent {...this.props} />;
        }

        return (<div>您还未登录...</div>);
      }
  }
}

属性代理 (Props Proxy) 在我看来属性代理就是提取公共的数据和方法到父组件,子组件只负责渲染数据,相当于设计模式里的模板模式,这样组件的重用性就更高了

function proxyHoc(WrappedComponent) {
    return class extends React.Component {
        render() {
            const newProps = {
                count: 1
            }
            return <WrappedComponent {...this.props} {...newProps} />
        }
    }
}

反向继承

const MyContainer = (WrappedComponent)=>{
    return class extends WrappedComponent {
        render(){
            return super.render();
        }
    }
}

7.react性能优化

类组件中的优化手段

  • 使用纯组件 PureComponent 作为基类。
  • 使用 React.memo 高阶函数包装组件。
  • 使用 shouldComponentUpdate 生命周期函数来自定义渲染逻辑。
    父组件状态的每次更新,都会导致子组件的重新渲染,即使是传入相同props。但是这里的重新渲染不是说会更新DOM,而是每次都会调用diif算法来判断是否需要更新DOM。这对于大型组件例如组件树来说是非常消耗性能的。
    在这里我们就可以使用React.PureComponent , shouldComponentUpdate生命周期来确保只有当组件props状态改变时才会重新渲染。如下例子:
export default function ParentComponent(props) {
  return (
    <div>
      <SomeComponent someProp={props.somePropValue}
    <div>
      <AnotherComponent someOtherProp={props.someOtherPropValue} />
    </div>
   </div>
 )
}

export default function SomeComponent(props) {
  return (
    <div>{props.someProp}</div>  
  )
}

// 只要props.somePropValue 发生变化,不论props.someOtherPropValue是否发生变化该组件都会发生变化
export default function AnotherComponent(props) {
  return (
    <div>{props.someOtherProp}</div>  
  )
}

我们可以使用React.PureComponent 或shouldComponentUpdate 进行如下优化:

// 第一种优化
class AnotherComponent extends React.PureComponent {
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}

//第二种优化
class AnotherComponent extends Component {
  shouldComponentUpdate(nextProps) {
    return this.props !== nextProps
  }
  render() {
    return <div>{this.props.someOtherProp}</div>   
  }
}

PureComponent会进行浅比较来判断组件是否应该重新渲染,对于传入的基本类型props,只要值相同,浅比较就会认为相同,对于传入的引用类型props,浅比较只会认为传入的props是不是同一个引用,如果不是,哪怕这两个对象中的内容完全一样,也会被认为是不同的props。
需要注意的是在对于那些可以忽略渲染时间的组件或者是状态一直变化的组件则要谨慎使用PureComponent,因为进行浅比较也会花费时间,这种优化更适用于大型的展示组件上。大型组件也可以拆分成多个小组件,并使用memo来包裹小组件,也可以提升性能。

至于react.memo()
父组件在每次状态更新时,都会导致子组件重新渲染,即使传入子组件的状态没有发生变更。

为了减少重复渲染,我们可以使用 React.memo 来缓存组件,这样只有当传入组件的状态只发生变化时才会重新渲染,如果传入的只和上一次没有发生变化,则返回缓存的组件:

import React from "react";

export default React.memo((props) => <div>{props.value}</div>);

函数组件中的优化手段

  • 使用 useMemo。(计算属性)
// 只有 `props.item` 改变时someProp的值才会被重新计算
function Component(props) {
  const someProp = useMemo(() => heavyCalculation(props.item), [props.item]);
  return <AnotherComponent someProp={someProp} /> 
}
  • 使用 useCallBack。缓存方法,避免重复申明。
    image

其他方式

  • 在列表需要频繁变动时,使用唯一 id 作为 key,而不是数组下标。
  • 必要时通过改变 CSS 样式隐藏显示组件,而不是通过条件判断显示隐藏组件。
  • 使用 Suspense 和 lazy 进行懒加载,例如:
import React, { lazy, Suspense } from "react";

export default class CallingLazyComponents extends React.Component {
  render() {
    var ComponentToLazyLoad = null;

    if (this.props.name == "Mayank") {
      ComponentToLazyLoad = lazy(() => import("./mayankComponent"));
    } else if (this.props.name == "Anshul") {
      ComponentToLazyLoad = lazy(() => import("./anshulComponent"));
    }

    return (
      <div>
        <h1>This is the Base User: {this.state.name}</h1>
        <Suspense fallback={<div>Loading...</div>}>
          <ComponentToLazyLoad />
        </Suspense>
      </div>
    )
  }
}

8.在调用setState 之后发生了什么

  • 状态合并,触发调和:

  • setState函数之后,会将传入的参数对象与当前的状态合并,然后出发调用过程

  • 根据新的状态构建虚拟dom树

  • 经过调和过程,react会高效的根据新的状态构建虚拟DOM树,准备渲染整个UI页面

  • 计算新老树节点差异,最小化渲染

  • 得倒新的虚拟DOM树后,会计算出新老树的节点差异,会根据差异对界面进行最小化渲染

  • 按需更新

  • 在差异话计算中,react可以相对准确的知道哪些位置发生了改变以及该如何改变,这保证按需更新,而不是宣布重新渲染

9.redux中间件

中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer。这种机制可以让我们改变数据流,实现如异步action ,action 过滤,日志输出,异常报告等功能

10.类组件(Class component)和函数式组件(Functional component)之间有何不同

类组件可以使用其他特性,如状态 state 和生命周期钩子。

  • 当组件只是接收 props 渲染到页面时,就是无状态组件,就属于函数组件,也被称为哑组件或展示组件。
  • 函数组件和类组件当然是有区别的,而且函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

11.简述flux 思想

Flux 的最大特点,就是数据的"单向流动"。

  • 用户访问 View
  • View发出用户的 Action
  • Dispatcher 收到Action,要求 Store 进行相应的更新
  • Store 更新后,发出一个"change"事件
  • View 收到"change"事件后,更新页面

12.受控组件与非受控组件区别

13.React 父组件如何调用子组件中的方法?

如果是在方法组件中调用子组件(>= react@16.8),可以使用 useRef 和 useImperativeHandle:

默认情况下,不能在函数组件上使用 ref 属性,因为它们没有实例.如果要在函数组件中使用 ref,你可以使用 forwardRef(可与 useImperativeHandle 结合使用),或者可以将该组件转化为 class 组件。

const { forwardRef, useRef, useImperativeHandle } = React;

const Child = forwardRef((props, ref) => {
  useImperativeHandle(ref, () => ({
    getAlert() {
      alert("getAlert from Child");
    }
  }));
  return <h1>Hi</h1>;
});

const Parent = () => {
  const childRef = useRef();
  return (
    <div>
      <Child ref={childRef} />
      <button onClick={() => childRef.current.getAlert()}>Click</button>
    </div>
  );
};

如果是在类组件中调用子组件(>= react@16.4),可以使用 createRef:

const { Component } = React;

class Parent extends Component {
  constructor(props) {
    super(props);
    this.child = React.createRef();
  }

  onClick = () => {
    this.child.current.getAlert();
  };

  render() {
    return (
      <div>
        <Child ref={this.child} />
        <button onClick={this.onClick}>Click</button>
      </div>
    );
  }
}

class Child extends Component {
  getAlert() {
    alert('getAlert from Child');
  }

  render() {
    return <h1>Hello</h1>;
  }
}

14.react和vue的区别

相同点:

  • 数据驱动页面,提供响应式的试图组件
  • 都有virtual DOM,组件化的开发,通过props参数进行父子之间组件传递数据,都实现了webComponents规范
  • 数据流动单向,都支持服务器的渲染SSR
  • 都有支持native的方法,react有React native, vue有wexx

不同点:

  • 数据绑定:Vue实现了双向的数据绑定,react数据流动是单向的
  • 数据渲染:大规模的数据渲染,react更快
  • 使用场景:React配合Redux架构适合大规模多人协作复杂项目,Vue适合小快的项目
  • 开发风格:react推荐做法jsx + inline style把html和css都写在js了

14.如何解决 props 层级过深的问题

  • 使用Context API:提供一种组件之间的状态共享,而不必通过显式组件树逐层传递props;
  • 使用Redux等状态库。

15.React Hook 的使用限制有哪些?

React Hooks 的限制主要有两条:

  • 不要在循环、条件或嵌套函数中调用 Hook;

  • 在 React 的函数组件中调用 Hook。
    那为什么会有这样的限制呢?Hooks 的设计初衷是为了改进 React 组件的开发模式。在旧有的开发模式下遇到了三个问题。

  • 组件之间难以复用状态逻辑。过去常见的解决方案是高阶组件、render props 及状态管理框架。

  • 复杂的组件变得难以理解。生命周期函数与业务逻辑耦合太深,导致关联部分难以拆分。

  • 人和机器都很容易混淆类。常见的有 this 的问题,但在 React 团队中还有类难以优化的问题,希望在编译优化层面做出一些改进。
    这三个问题在一定程度上阻碍了 React 的后续发展,所以为了解决这三个问题,Hooks 基于函数组件开始设计。然而第三个问题决定了 Hooks 只支持函数组件。

那为什么不要在循环、条件或嵌套函数中调用 Hook 呢?因为 Hooks 的设计是基于数组实现。在调用时按顺序加入数组中,如果使用循环、条件或嵌套函数很有可能导致数组取值错位,执行错误的 Hook。当然,实质上 React 的源码里不是数组,是链表。

这些限制会在编码上造成一定程度的心智负担,新手可能会写错,为了避免这样的情况,可以引入 ESLint 的 Hooks 检查插件进行预防。

16.React中的setState和replaceState的区别是什么?

(1)setState() setState()用于设置状态对象,其语法如下:

setState(object nextState[, function callback])
  • nextState,将要设置的新状态,该状态会和当前的state合并
  • callback,可选参数,回调函数。该函数会在setState设置成功,且组件重新渲染后调用。

合并nextState和当前state,并重新渲染组件。setState是React事件处理函数中和请求回调函数中触发UI更新的主要方法。
(2)replaceState() replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除。其语法如下:

replaceState(object nextState[, function callback])
  • nextState,将要设置的新状态,该状态会替换当前的state。
  • callback,可选参数,回调函数。该函数会在replaceState设置成功,且组件重新渲染后调用。

总结: setState 是修改其中的部分状态,相当于 Object.assign,只是覆盖,不会减少原来的状态。而replaceState 是完全替换原来的状态,相当于赋值,将原来的 state 替换为另一个对象,如果新状态属性减少,那么 state 中就没有这个状态了。

17.React的严格模式如何使用,有什么用处?

StrictMode 是一个用来突出显示应用程序中潜在问题的工具。与 Fragment 一样,StrictMode 不会渲染任何可见的 UI。它为其后代元素触发额外的检查和警告。
可以为应用程序的任何部分启用严格模式。例如:

import React from 'react';
function ExampleApplication() {
  return (
    <div>
      <Header />
      <React.StrictMode>        
        <div>
          <ComponentOne />
          <ComponentTwo />
        </div>
      </React.StrictMode>      
      <Footer />
    </div>
  );
}

在上述的示例中,不会对 Header 和 Footer 组件运行严格模式检查。但是,ComponentOne 和 ComponentTwo 以及它们的所有后代元素都将进行检查。

StrictMode 目前有助于:

  • 识别不安全的生命周期
  • 关于使用过时字符串 ref API 的警告
  • 关于使用废弃的 findDOMNode 方法的警告
  • 检测意外的副作用
  • 检测过时的 context API

18.react组件传值

19.概述一下 React中的事件处理逻辑。

为了解决跨浏览器兼容性问题, React会将浏览器原生事件( Browser Native Event)封装为合成事件( Synthetic Event)并传入设置的事件处理程序中。
这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外, React并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理(基于事件委托原理)。
这样 React在更新DOM时就不需要考虑如何处理附着在DOM上的事件监听器,最终达到优化性能的目的。

20.在 React中元素( element)和组件( component)有什么区别?

简单地说,在 React中元素(虛拟DOM)描述了你在屏幕上看到的DOM元素。
换个说法就是,在 React中元素是页面中DOM元素的对象表示方式。在 React中组件是一个函数或一个类,它可以接受输入并返回一个元素。
注意:工作中,为了提高开发效率,通常使用JSX语法表示 React元素(虚拟DOM)。在编译的时候,把它转化成一个 React. createElement调用方法。

21.redux有什么缺点

一个组件所需要的数据,必须由父组件传过来,而不能像flux中直接从store取。
当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新render,可能会有效率影响,或者需要写复杂的shouldComponentUpdate进行判断。

22.在构造函数调用 super 并将 props 作为参数传入的作用是啥?

在调用 super() 方法之前,子类构造函数无法使用this引用,ES6 子类也是如此。将 props 参数传递给 super() 调用的主要原因是在子构造函数中能够通过this.props来获取传入的 props。
传递 props

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    console.log(this.props); // { name: 'sudheer',age: 30 }
  }
}

没传递 props

class MyComponent extends React.Component {
  constructor(props) {
    super();
    console.log(this.props); // undefined
    // 但是 Props 参数仍然可用
    console.log(props); // Prints { name: 'sudheer',age: 30 }
  }
  render() {
    // 构造函数外部不受影响
    console.log(this.props); // { name: 'sudheer',age: 30 }
  }
}

上面示例揭示了一点。props 的行为只有在构造函数中是不同的,在构造函数之外也是一样的。

posted @ 2022-11-04 10:20  yunChuans  阅读(32)  评论(1编辑  收藏  举报