面试-React

220

React 面试知识点

  • React 使用 Virtual DOM 来更新真正的 DOM,通过比较两个虚拟DOM 差异,并将这些变化更新到实际DOM。
  • JSX是javascript的语法扩展。它就像一个拥有javascript全部功能的模板语言。在JSX中,我们结合了javascript和HTML,并生成了可以在DOM中呈现的react元素。
  • 函数/无状态/展示组件,函数或无状态组件是一个纯函数,它可接受接受参数,并返回react元素。这些都是没有任何副作用的纯函数。这些组件没有状态或生命周期方法。
  • 类/有状态组件,类或有状态组件具有状态和生命周期方可能通过setState()方法更改组件的状态。
  • 容器组件,容器组件是处理获取数据、订阅 redux 存储等的组件。它们包含展示组件和其他容器组件。
  • 高阶组件,高阶组件是将组件作为参数并生成另一个组件的组件。 Redux connect是高阶组件的示例。 这是一种用于生成可重用组件的强大技术。
  • Props 和 State 构造函数中定义了props和state,每当使用this.setState() 修改状态时,将再次调用 render( ) 函数来更改UI中组件的输出。
  • fiber js执行会占据主线程时间较长,会导致页面响应度变差,使得react在动画、手势等应用中效果比较差。为了解决这个问题,react团队经过两年的工作,重写了react中核心算法——reconciliation。并在v16版本中发布了这个新的特性。为了区别之前和之后的reconciler,通常将之前的reconciler称为stack reconciler,重写后的称为fiber reconciler,简称为Fiber。
  • PropTypes 随着时间的推移,应用程序会变得越来越大,因此类型检查非常重要。PropTypes为组件提供类型检查,并为其他开发人员提供很好的文档。如果react项目不使用 Typescript,建议为组件添加 PropType
  • 如何更新状态以及如何不更新 你不应该直接修改状态。可以在构造函数中定义状态值。直接使用状态不会触发重新渲染。React 使用this.setState()时合并状态。使用this.setState()的第二种形式总是更安全的,因为更新的props和状态是异步的。这里,我们根据这些 props 更新状态。
// 错误方式
this.setState({
    timesVisited: this.state.timesVisited + this.props.count
})
// 正确方式
this.setState((state, props) => {
    timesVisited: state.timesVisited + props.count
});
  • Reconciliation 同步,一气呵成

  • Fiber Fiber把任务切成很小的片,每执行一片就把控制权交还给主线程,待主线程忙完手头的活再来执行剩下的任务。当然如果某一片的执行时间就很长(比如死循环),那就没主线程什么事了,该崩溃崩溃。

  • 组件生命周期方法

    • constructor 继承自Component类或者PureComponent类,初始化state
    • [👽这是React不再推荐使用的API。]componentWillMount 这是组件挂载到DOM之前的生命周期钩子。废弃的原因1、本来它就没什么用。估计当初是为了成双成对所以才创造了它吧。如果它声明了定时器或者订阅器,在服务端渲染中,componentWillUnmount生命周期钩子中的清除代码不会生效。因为如果组件没有挂载成功,componentWillUnmount是不会执行的。姚明说的:没有挂载就没有卸载。
    • [👽这是React v16.3.0发布的API。]static getDerivedStateFromProps(props, state) 为什么要设计成静态方法呢?这样开发者就访问不到this也就是实例了,也就不能在里面调用实例方法或者setsState了。这个生命周期钩子的使命是根据父组件传来的props按需更新自己的state,这种state叫做衍生state。返回的对象就是要增量更新的state。它被设计成静态方法的目的是保持该方法的纯粹,它就是用来定义衍生state的,除此之外不应该在里面执行任何操作。
    • render() 作为一个组件,最核心的功能就是把元素挂载到DOM上,所以render生命周期钩子是一定会用到的。render生命周期钩子怎么接收模板呢?当然是你return给它。千万不要在render生命周期钩子里调用this.setState,因为this.setState会引发render,这下就没完没了了。主公,有内奸。
    • componentDidMount() 这是组件挂载到DOM之后的生命周期钩子。
    • [👽这是React不再推荐使用的API。] componentWillReceiveProps(nextProps) 该声明周期函数可能在两种情况下被触发:该声明周期函数可能在两种情况下被触发:组件接收到了新的属性。组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。初始化时并不会触发该生命周期钩子。同样,因为Fiber机制的引入,这个生命周期钩子有可能会多次触发。
    • shouldComponentUpdate(nextProps, nextState) 判断是否需要更新,主要用来优化性能。 有一个例外,如果开发者调用this.forceUpdate强制更新,React组件会无视这个钩子。shouldComponentUpdate生命周期钩子默认返回true。也就是说,默认情况下,只要组件触发了更新,组件就一定会更新。React把判断的控制权给了开发者。不过周到的React还提供了一个PureComponent基类,它与Component基类的区别是PureComponent自动实现了一个shouldComponentUpdate生命周期钩子。对于组件来说,只有状态发生改变,才需要重新渲染。所以shouldComponentUpdate生命周期钩子暴露了两个参数,开发者可以通过比较this.props和nextProps、this.state和nextState来判断状态到底有没有发生改变,再相应的返回true或false。
    • [👽这是React不再推荐使用的API。] componentWillUpdate(nextProps, nextState) shouldComponentUpdate生命周期钩子返回true,或者调用this.forceUpdate之后,会立即执行该生命周期钩子。要特别注意,componentWillUpdate生命周期钩子每次更新前都会执行,所以在这里调用this.setState非常危险,有可能会没完没了
    • [👽这是React v16.3.0发布的API。] getSnapshotBeforeUpdate(prevProps, prevState) 它会在组件即将挂载时调用,注意,是即将挂载。它甚至调用的比render还晚,由此可见render并没有完成挂载操作,而是进行构建抽象UI的工作。getSnapshotBeforeUpdate执行完就会立即调用componentDidUpdate生命周期钩子。getSnapshotBeforeUpdate生命周期钩子返回的值会被componentDidUpdate的第三个参数接收,我们可以利用这个通道保存一些不需要持久化的状态,用完即可舍弃。很显然,它是用来取代componentWillUpdate生命周期钩子的。
    • componentDidUpdate(nextProps, nextState, snapshot) 搭配getSnapshotBeforeUpdate生命周期钩子使用的时候,第三个参数是getSnapshotBeforeUpdate的返回值。同样的,componentDidUpdate生命周期钩子每次更新后都会执行,所以在这里调用this.setState也非常危险,有可能会没完没了。
    • componentWillUnmount() 这是组件卸载之前的生命周期钩子。React的最佳实践是,组件中用到的事件监听器、订阅器、定时器都要在这里销毁。当然我说的事件监听器指的是这种:
    componentDidMount() {
        document.addEventListener('click', () => {});
    }
    
    • [👽这是React v16.3.0发布的API。] componentDidCatch(error, info) 它主要用来捕获错误并进行相应处理,所以它的用法也比较特殊。定制一个只有componentDidCatch生命周期钩子的ErrorBoundary组件,它只做一件事:如果捕获到错误,则显示错误提示,如果没有捕获到错误,则显示子组件。将需要捕获错误的组件作为ErrorBoundary的子组件渲染,一旦子组件抛出错误,整个应用依然不会崩溃,而是被ErrorBoundary捕获。
    • 这么多生命周期钩子,实际上总结起来只有三个过程:挂载 更新 卸载
    • 一个完整的React组件生命周期会依次调用如下钩子:
      • 挂载
        • constructor
        • componentWillMount(16.3用getDerivedStateFromProps代替)
        • render
        • componentDidMount
      • 更新
        • componentWillReceiveProps(16.3用getDerivedStateFromProps代替)
        • shouldComponentUpdate
        • componentWillUpdate
        • render
        • componentDidUpdate
      • 卸载
        • componentWillUnmount
    • 组件树生命周期调用栈 应用初次挂载时,我们以render和componentDidMount为例,React首先会调用根组件的render钩子,如果有子组件的话,依次调用子组件的render钩子,调用过程其实就是递归的顺序。等所有组件的render钩子都递归执行完毕,这时候执行权在最后一个子组件手里,于是开始触发下一轮生命周期钩子,调用最后一个子组件的componentDidMount钩子,然后调用栈依次往上递归。。
  • 如何在React中应用样式 将样式应用于React组件有三种方法。

    • 外部样式表 在此方法中,你可以将外部样式表导入到组件使用类中。 但是你应该使用className而不是class来为React元素应用样式, 这里有一个例子。
    • 内联样式 在这个方法中,我们可以直接将 props 传递给HTML元素,属性为style。这里有一个例子。这里需要注意的重要一点是,我们将javascript对象传递给style,这就是为什么我们使用 backgroundColor 而不是CSS方法backbackground -color。
    • 定义样式对象并使用它 因为我们将javascript对象传递给style属性,所以我们可以在组件中定义一个style对象并使用它。下面是一个示例,你也可以将此对象作为 props 传递到组件树中。
  • 什么是Redux及其工作原理 Redux 是 React的一个状态管理库,它基于flux。 Redux简化了React中的单向数据流。 Redux将状态管理完全从React中抽象出来。 如何工作:

  • 在React中,组件连接到 redux ,如果要访问 redux,需要派出一个包含 id和负载(payload) 的 action。action 中的 payload 是可选的,action 将其转发给 Reducer。

  • 当reducer收到action时,通过 swithc...case 语法比较 action 中type。 匹配时,更新对应的内容返回新的 state。

  • 当Redux状态更改时,连接到Redux的组件将接收新的状态作为props。当组件接收到这些props时,它将进入更新阶段并重新渲染 UI。

    Action: Action 只是一个简单的json对象,type 和有payload作为键。type 是必须要有的,payload是可选的。下面是一个 action 的例子。

// action

{ 
  type:"SEND_EMAIL", 
  payload: data
};

Action Creators:这些是创建Actions的函数,因此我们在派发action时不必在组件中手动编写每个 action。 以下是 action creator 的示例。

Reducers:Reducers 是纯函数,它将 action和当前 state 作为参数,计算必要的逻辑并返回一个新的state。 这些 Reducers 没有任何副作用。 它不会改变 state 而是总是返回 state 。

export default function emailReducer(state = [], action){
 
  switch(action.type) {
      case "SEND_EMAIL":  return Object.assign({}, state, {
       email: action.payload
      });
      default: return state;
  }
}

组件如何与 redux 进行连接

mapStateToProps:此函数将state映射到 props 上,因此只要state发生变化,新 state 会重新映射到 props。 这是订阅store的方式。 mapDispatchToProps:此函数用于将 action creators 绑定到你的props 。以便我们可以在第12行中使用This . props.actions.sendemail()来派发一个动作。 connect和bindActionCreators来自 redux。 前者用于连接 store ,如第22行,后者用于将 action creators 绑定到你的 props ,如第20行。

// import connect
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

// import action creators
import * as userActions from '../../../actions/userActions';

export class User extends React.Component {
  
    handleSubmit() {
        // dispatch an action
        this.props.actions.sendEmail(this.state.email);
    }
  
}

// you are mapping you state props
const mapStateToProps = (state, ownProps) => ({user: state.user})
// you are binding your action creators to your props
const mapDispatchToProps = (dispatch) => ({actions: bindActionCreators(userActions, dispatch)})

export default connect(mapStateToProps, mapDispatchToProps)(User);
  • 什么是 React Router Dom 及其工作原理 react-router-dom是应用程序中路由的库。 React库中没有路由功能,需要单独安装react-router-dom。

react-router-dom 提供两个路由器BrowserRouter和HashRoauter。前者基于url的pathname段,后者基于hash段。

  • 什么是错误边界 在 React 中,我们通常有一个组件树。如果任何一个组件发生错误,它将破坏整个组件树。没有办法捕捉这些错误,我们可以用错误边界优雅地处理这些错误。

错误边界有两个作用

  • 如果发生错误,显示回退UI
  • 记录错误

下面是ErrorBoundary类的一个例子。如果类实现了 getDerivedStateFromError或componentDidCatch 这两个生命周期方法的任何一下,那么这个类就会成为ErrorBoundary。前者返回{hasError: true}来呈现回退UI,后者用于记录错误。

import React from 'react'

export class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false };
    }
  
    static getDerivedStateFromError(error) {
      // Update state so the next render will show the fallback UI.
      return { hasError: true };
    }
  
    componentDidCatch(error, info) {
      // You can also log the error to an error reporting service
      console.log('Error::::', error);
    }
  
    render() {
      if (this.state.hasError) {
        // You can render any custom fallback UI
        return <h1>OOPS!. WE ARE LOOKING INTO IT.</h1>;
      }
  
      return this.props.children; 
    }
  }

以下是我们如何在其中一个组件中使用ErrorBoundary。使用ErrorBoundary类包裹 ToDoForm和ToDoList。 如果这些组件中发生任何错误,我们会记录错误并显示回退UI。

import React from 'react';
import '../App.css';
import { ToDoForm } from './todoform';
import { ToDolist } from './todolist';
import { ErrorBoundary } from '../errorboundary';

export class Dashboard extends React.Component {

  render() {
    return (
      <div className="dashboard"> 
        <ErrorBoundary>
          <ToDoForm />
          <ToDolist />
        </ErrorBoundary>
      </div>
    );
  }
}

什么是 Fragments

在React中,我们需要有一个父元素,同时从组件返回React元素。有时在DOM中添加额外的节点会很烦人。使用 Fragments,我们不需要在DOM中添加额外的节点。我们只需要用 React.Fragment 或才简写 <> 来包裹内容就行了。如下 所示:

 // Without Fragments   
return (
    <div>
       <CompoentA />
       <CompoentB />
       <CompoentC />
    </div>
)

// With Fragments   
return (
    <React.Fragment>
       <CompoentA />
       <CompoentB />
       <CompoentC />
    </React.Fragment>
)

// shorthand notation Fragments   
return (
    <>
       <CompoentA />
       <CompoentB />
       <CompoentC />
    </>
)
  • 什么是 Hooks Hooks 是React版本16.8中的新功能。 请记住,我们不能在函数组件中使用state ,因为它们不是类组件。Hooks 让我们在函数组件中可以使用state 和其他功能。

目前没有重大变化,我们不必放弃类组件。

Hook 不会影响你对 React 概念的理解。 恰恰相反,Hook 为已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命周期。稍后我们将看到,Hook 还提供了一种更强大的方式来组合他们。 我们可以使用一些钩子,例如useState,useEffect,useContext,useReducer等。

下面是 Hooks 的基本规则 Hooks 应该在外层使用,不应该在循环,条件或嵌套函数中使用 Hooks 应该只在函数组件中使用。

  • 如何提高性能
    • 适当地使用shouldComponentUpdate生命周期方法。它避免了子组件的不必要的渲染。如果树中有100个组件,则不重新渲染整个组件树来提高应用程序性能。
    • 使用create-react-app来构建项目,这会创建整个项目结构,并进行大量优化。
    • 不可变性是提高性能的关键。不要对数据进行修改,而是始终在现有集合的基础上创建新的集合,以保持尽可能少的复制,从而提高性能。
    • 在显示列表或表格时始终使用 Keys,这会让 React 的更新速度更快
    • 代码分离是将代码插入到单独的文件中,只加载模块或部分所需的文件的技术。

什么时候使用状态管理器?

  • 1.用户的使用方式复杂
  • 2.不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 3.多个用户之间可以协作
  • 4.与服务器大量交互,或者使用了WebSocket
  • 5.View要从多个来源获取数据

从组件角度看

  • 1.某个组件的状态,需要共享
  • 2.某个状态需要在任何地方都可以拿到
  • 3.一个组件需要改变全局状态
  • 4.一个组件需要改变另一个组件的状态

render函数中return如果没有使用()会有什么问题?

我们在使用JSX语法书写react代码时,babel会将JSX语法编译成js,同时会在每行自动添加分号(;),如果return后换行了,那么就会变成 return; 一般情况下会报错:

componentWillUpdate可以直接修改state的值吗?

不行, 这样会无限循环下去

说说你对React的渲染原理的理解

自上而下的单向数据流,通过diff 算法对照两次dom不同的部分进行渲染。

什么渲染劫持?

你有使用过React Intl吗?

一种react国际化的解决方案

说说Context有哪些属性?

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

setState有三种用法

// 对象
this.setState({

})

// 函数,一般是用于在setState之前做一些操作
this.setState(
  () => {
    // TODO
    console.log('')
    return {
      a:300
    }
  }
)

// 第二个参数,一般是用于在setState之后做一些操作
this.setState({
  a:300
}, () => {
  // TODO
})

setState可能是同步的

  • setState 在react里的合成事件和钩子函数中是“异步”的。
  • setState 在原生事件和 setTimeout 中是同步的。

阻止事件默认行为

  • 在 React 中你不能通过返回 false 来阻止默认行为。必须明确调用 preventDefault 。

在 componentWillUnmount 里面去除副作用的函数

  • 清除 EventListener
  • 中止数据请求
  • 清除定时器

第三方库函数的使用

/*
 * Echart 用于代绘制图表,但当其自身发生错误时,可能影响到业务代码的执行
 */
// bad
const iniDom = document.getElementById('init-container');
const echartObj = echarts.init(iniDom);
this.setState(
  {
    echartObj
  },
  () => {
    const { echartObj } = this.state;
    // 更新图表
    echartObj.setOption(CHART_CONFIG, true);
  }
);

// good
try {
  const iniDom = document.getElementById('init-container');
  const echartObj = echarts.init(iniDom);
  this.setState(
    {
      echartObj
    },
    () => {
      const { echartObj } = this.state;
      // 更新图表
      echartObj.setOption(CHART_CONFIG, true);
    }
  );
} catch (error) {
  // TODO
}

防止 xss 攻击

  • input,textarea 等标签,不要直接把 html 文本直接渲染在页面上,使用 xssb 等过滤之后再输出到标签上;
import { html2text } from 'xss';
render(){
  <div
  dangerouslySetInnerHTML={{
    __html: html2text(htmlContent)
  }}
/>
}