react

react权威面试题

  • 1.jsx转化过程
  • 2.fiber架构的理解,解决了什么问题?
      • 理解
      • fiber是什么
  • 3.react diff原理
        • tree diff
        • component diff
        • element diff
  • 4.如何提高组件渲染效率
      • shouldComponentUpdate
      • PureComponent
      • React.memo
  • 5.react中render方法原理,触发方式
  • 6.说说你对immutable的理解?如何应用在react项目中?
      • 是什么
      • 如何用
  • 7.说说React Router有几种模式?实现原理?
    • 使用
      • 原理
      • 具体实现
  • 8.你在React项目中是如何使用Redux的? 项目结构是如何划分的?
      • Provider
      • connection
      • mapStateToProps
      • mapDispatchToProps
  • 9.说说对Redux中间件的理解?常用的中间件有哪些?实现原理?
      • 中间件是什么
      • 常用的中间件
  • 10.说说你对Redux的理解?其工作原理?
      • 是什么
  • 11.说说对React Hooks的理解?解决了什么问题?
  • 12.说说对高阶组件的理解?应用场景?
  • 13.说说对受控组件和非受控组件的理解?应用场景?
      • 受控组件(Controlled Components):
      • 受控组件的应用场景:
      • 非受控组件(Uncontrolled Components):
      • 非受控组件的应用场景:
  • 14.什么是 redux
  • 15.说说对React refs 的理解?应用场景?
    • 二、如何使用
      • 传入字符串
      • 传入对象
      • 传入函数
      • 传入hook
      • 三、应用场景
  • 16.React中组件之间如何通信?
      • 父组件向子组件传递
      • 子组件向父组件传递)子组件向父组件传递
      • 兄弟组件之间的通信
      • 父组件向后代组件传递)父组件向后代组件传递
  • 17.说说React的事件机制?
  • 18.说说 Real DOM 和 Virtual DOM 的区别?优缺点?
  • 19.说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?
      • 创建阶段
      • 更新阶段
      • 卸载阶段
    • componentWillUnmount
  • 20.说说对 React 的理解?有哪些特性?
      • 特性
  • 21.React 和 Vue 在技术层面有哪些区别?
  • 22.useRef / ref / forwardsRef 的区别是什么?
  • 23.React 中的 ref 有什么用?
  • 24.react18新特性?
  • 25.使用 React hooks 怎么实现类里面的所有生命周期?
  • 26.React.memo() 和 useMemo() 的用法是什么,有哪些区别?
      • 什么是 React.memo()?
      • 什么是 useMemo()?
      • 总结:React.memo() 和 useMemo() 的主要区别
  • 27.说说你对 useReducer 的理解
  • 28.说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?
  • 29.我们应该在什么场景下使用 useMemo 和 useCallback ?
  • 30.setState 是同步,还是异步的?
      • react18之前。
      • react18之后。
  • 31.说说你对自定义hook的理解
  • 32.说说你对 useMemo 的理解
  • 33.说说你对 useContext 的理解
        • 使用
  • 34.为什么不能在循环、条件或嵌套函数中调用 Hooks?
  • 35.useEffect 与 useLayoutEffect 有什么区别?
      • 共同点
      • 不同点
  • 36.为什么 useState 返回的是数组而不是对象?
  • 37.Redux中的connect有什么作用?
      • 获取state
      • 包装原组件
      • 监听store tree变化
  • 38.Redux 状态管理器和变量挂载到 window 中有什么区别?
  • 39.Redux 中异步的请求怎么处理
      • 使用react-thunk中间件
          • redux-thunk优点:
          • redux-thunk缺陷:
      • 使用redux-saga中间件
          • redux-saga优点:
          • redux-saga缺陷:
  • 40.React构建组件的方式有哪些?有什么区别?
    • 一、是什么
    • 二、如何构建
      • 函数式创建
      • 通过 React.createClass 方法创建
      • 继承 React.Component 创建
      • 区别
  • 41.说说你在React项目是如何捕获错误的?
  • 42.react中懒加载的实现原理是什么?
      • 使用 React.lazy
  • 43.React有哪些性能优化的方法?
  • 44.React Fiber 是如何实现更新过程可控?
    • 任务拆分
    • 任务挂起、恢复、终止
      • 挂起
      • 恢复
      • 终止
    • 任务具备优先级
  • 45.不同版本的 React 都做过哪些优化?
      • React 15 架构
      • React 16 架构
      • React 17 优化
  • 46.虚拟DOM一定更快吗
      • 虚拟DOM/domDiff
      • 虚拟DOM不一定更快
  • 47.为什么不能用数组下标来作为react组件中的key?
  • 48.React Hooks当中的useEffect是如何区分生命周期钩子的
  • 49.使用React Hooks有什么优势?
  • 50.React Hooks带来了什么便利?
  • 51.React中的VM 一定会提高性能吗?
  • 52.为什么React的 VM 可以提高性能?
  • 53.react 的虚拟dom是怎么实现的?
  • 54.在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState 会发生什么?
  • 55.setstate后发生了什么
  • 56.React中为什么要给组件设置 key?
  • 57.React 的事件代理机制和原生事件绑定有什么区别?
  • 58.React 的事件代理机制和原生事件绑定混用会有什么问题?
  • 59.为什么不能直接使用 this.state 改变数据?
  • 60.什么是高阶组件?
  • 61.React中的类组件和函数组件之间有什么区别
    • 类组件(Class components)
    • 函数组件(functional component)
    • 区别
  • 62.什么是虚拟DOM?
  • 63.react新增的生命周期

1.jsx转化过程

  • 使用React.createElement或JSX编写React组件,实际上所有的 JSX 代码最后都会转换成React.createElement(…) ,Babel帮助我们完成了这个转换的过程。
  • createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
  • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM

2.fiber架构的理解,解决了什么问题?

理解

JavaScript引擎和页面渲染引擎两个线程是互斥的,当其中一个线程执行时,另一个线程只能挂起等待

如果 JavaScript 线程长时间地占用了主线程,那么渲染层面的更新就不得不长时间地等待,界面长时间不更新,会导致页面响应度变差,用户可能会感觉到卡顿

fiber是什么

React Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现。从Facebook在 React Conf 2017 会议上确认,React Fiber 在React 16 版本发布

react中,主要做了以下的操作:

  • 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
  • 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
  • dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行

3.react diff原理

diff 算法主要基于三个规律:

  • DOM 节点的跨层级移动的操作特别少,可以忽略不计
  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  • 对于同一层级的一组子节点,可以通过唯一的 id 进行区分

tree diff

因为上面的三个策略中的第一点, DOM 节点的跨级操作比较少,那么 diff 算法只会对相同层级的 DOM 节点进行比较。如果发现节点不存在 那么会将该节点以及其子节点完全删除,不会再继续比较。如果出现了 DOM 节点的跨层级的移动操作,那么会删除改节点以及其所有的子节点,然后再移动后的位置重新创建。

component diff

如果是同一类型的组件,那么会继续对比 VM 数

如果不是同一类型的组件,那么会将其和其子节点完全替换,不会再进行比对

同一类型的组件,有可能 VM 没有任何的变化,如果可以确定的知道这点,那么就可以节省大量的 diff 时间,所以用户可以设置 shouldComponentUpdate() 来判断是否需要进行 diff 算法。

element diff

当节点处于同一层级的时候时,有三种操作:INSERT_MAKEUP插入、 MOVE_EXISTING 移动、 REMOVE_NODE 删除

这里 React 有一个优化策略,对于同一层级的同组子节点,添加唯一的 key 进行区分。这样的话,就可以判断出来是否是移动节点。通过 key 发现新旧集合中的节点都是相同的节点,就只需要进行移动操作就可以。

4.如何提高组件渲染效率

  • shouldComponentUpdate
  • PureComponent
  • React.memo

shouldComponentUpdate

通过shouldComponentUpdate生命周期函数来比对 stateprops,确定是否要重新渲染

默认情况下返回true表示重新渲染,如果不希望组件重新渲染,返回 false 即可

PureComponent

shouldComponentUpdate原理基本一致,通过对 propsstate的浅比较结果来实现

React.memo

React.memo用来缓存组件的渲染,避免不必要的更新,其实也是一个高阶组件,与 PureComponent 十分类似。但不同的是, React.memo 只能用于函数组件

5.react中render方法原理,触发方式

首先,render函数在react中有两种形式:

在类组件中,指的是render方法:

class Foo extends React.Component {
    render() {
        return 

Foo

; } }

在函数组件中,指的是函数组件本身:

function Foo() {
    return <h1> Foo </h1>;
}

触发方式

  • 类组件调用 setState 修改状态

  • 函数组件通过useState hook修改状态

  • 类组件重新渲染

  • 函数组件重新渲染

总结:

render函数里面可以编写JSX,转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM

React 中,类组件只要执行了 setState 方法,就一定会触发 render 函数执行,函数组件使用useState更改状态不一定导致重新render

组件的props 改变了,不一定触发 render 函数的执行,但是如果 props 的值来自于父组件或者祖先组件的 state

在这种情况下,父组件或者祖先组件的 state 发生了改变,就会导致子组件的重新渲染

所以,一旦执行了setState就会执行render方法,useState 会判断当前值有无发生改变确定是否执行render方法,一旦父组件发生渲染,子组件也会渲染

6.说说你对immutable的理解?如何应用在react项目中?

是什么

Immutable,不可改变的,在计算机中,即指一旦创建,就不能再被更改的数据

Immutable对象的任何修改或添加删除操作都会返回一个新的 Immutable对象

Immutable 实现的原理是 Persistent Data Structure(持久化数据结构):

  • 用一种数据结构来保存数据
  • 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费

也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变,同时为了避免 deepCopy把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享)

如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享

如何用

使用 Immutable可以给 React 应用带来性能的优化,主要体现在减少渲染的次数

在做react性能优化的时候,为了避免重复渲染,我们会在shouldComponentUpdate()中做对比,当返回true执行render方法

Immutable通过is方法则可以完成对比,而无需像一样通过深度比较的方式比较

在使用redux过程中也可以结合Immutable,不使用Immutable前修改一个数据需要做一个深拷贝

7.说说React Router有几种模式?实现原理?

在单页应用中,一个web项目只有一个html页面,一旦页面加载完成之后,就不用因为用户的操作而进行页面的重新加载或者跳转,其特性如下:

  • 改变 url 且不让浏览器像服务器发送请求
  • 在不刷新页面的前提下动态改变浏览器地址栏中的URL地址

其中主要分成了两种模式:

  • hash 模式:在url后面加上#,如http://127.0.0.1:5500/home/#/page1
  • history 模式:允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录

使用

React Router对应的hash模式和history模式对应的组件为:

  • HashRouter
  • BrowserRouter

这两个组件的使用都十分的简单,作为最顶层组件包裹其他组件,如下所示

// 1.import { BrowserRouter as Router } from "react-router-dom";
// 2.import { HashRouter as Router } from "react-router-dom";

import React from 'react';
import {
  BrowserRouter as Router,
  // HashRouter as Router  
  Switch,
  Route,
} from "react-router-dom";
import Home from './pages/Home';
import Login from './pages/Login';
import Backend from './pages/Backend';
import Admin from './pages/Admin';


function App() {
  return (
    
        
        
        
        
    
  );
}

export default App;

原理

路由描述了 URLUI之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)

下面以hash模式为例子,改变hash值并不会导致浏览器向服务器发送请求,浏览器不发出请求,也就不会刷新页面

hash 值改变,触发全局 window 对象上的 hashchange 事件。所以 hash 模式路由就是利用 hashchange 事件监听 URL 的变化,从而进行 DOM 操作来模拟页面跳转

react-router也是基于这个特性实现路由的跳转

具体实现

HashRouter包裹了整应用,

通过window.addEventListener('hashChange',callback)监听hash值的变化,并传递给其嵌套的组件

然后通过contextlocation数据往后代组件传递, Router组件主要做的是通过BrowserRouter传过来的当前值,通过props传进来的pathcontext传进来的pathname进行匹配,然后决定是否执行渲染组件

8.你在React项目中是如何使用Redux的? 项目结构是如何划分的?

我们了解到redux是用于数据状态管理,而react是一个视图层面的库

如果将两者连接在一起,可以使用官方推荐react-redux库,其具有高效且灵活的特性

react-redux将组件分成:

  • 容器组件:存在逻辑处理
  • UI 组件:只负责现显示和交互,内部不处理逻辑,状态由外部控制

通过redux将整个应用状态存储到store中,组件可以派发dispatch行为actionstore

其他组件通过订阅store中的状态state来更新自身的视图

使用react-redux分成了两大核心:

  • Provider
  • connection

Provider

redux中存在一个store用于存储state,如果将这个store存放在顶层元素中,其他组件都被包裹在顶层元素之上

那么所有的组件都能够受到redux的控制,都能够获取到redux中的数据

使用方式如下:

<Provider store = {store}>
    <App />
<Provider>

connection

connect方法将store上的getStatedispatch包装成组件的props

导入conect如下:

import { connect } from "react-redux";

用法如下:

connect(mapStateToProps, mapDispatchToProps)(MyComponent)

可以传递两个参数:

  • mapStateToProps
  • mapDispatchToProps

mapStateToProps

redux中的数据映射到react中的props中去

如下:

const mapStateToProps = (state) => {
    return {
        // prop : state.xxx  | 意思是将state中的某个数据映射到props中
        foo: state.bar
    }
}

组件内部就能够通过props获取到store中的数据

class Foo extends Component {
    constructor(props){
        super(props);
    }
    render(){
        return(
         // 这样子渲染的其实就是state.bar的数据了
            
this.props.foo
) } } Foo = connect()(Foo) export default Foo

mapDispatchToProps

redux中的dispatch映射到组件内部的props

const mapDispatchToProps = (dispatch) => { // 默认传递参数就是dispatch
  return {
    onClick: () => {
      dispatch({
        type: 'increatment'
      });
    }
  };
}
class Foo extends Component {
    constructor(props){
        super(props);
    }
    render(){
        return(
         
             <button onClick = {this.props.onClick}>点击increase</button>
        )
    }
}
Foo = connect()(Foo);
export default Foo;

9.说说对Redux中间件的理解?常用的中间件有哪些?实现原理?

中间件是什么

中间件(Middleware)是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务(功能),衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的

常用的中间件

有很多优秀的redux中间件,如:

  • redux-thunk:用于异步操作
  • redux-logger:用于日志记录

上述的中间件都需要通过applyMiddlewares进行注册,作用是将所有的中间件组成一个数组,依次执行

然后作为第二个参数传入到createStore

const store = createStore(
  reducer,
  applyMiddleware(thunk, logger)
);

redux-thunk

redux-thunk是官网推荐的异步处理中间件

默认情况下的dispatch(action)action需要是一个JavaScript的对象

redux-thunk中间件会判断你当前传进来的数据类型,如果是一个函数,将会给函数传入参数值(dispatch,getState)

  • dispatch函数用于我们之后再次派发action
  • getState函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态

所以dispatch可以写成下述函数的形式:

const getHomeMultidataAction = () => {
  return (dispatch) => {
    axios.get("http://xxx.xx.xx.xx/test").then(res => {
      const data = res.data.data;
      dispatch(changeBannersAction(data.banner.list));
      dispatch(changeRecommendsAction(data.recommend.list));
    })
  }
}

redux-logger

如果想要实现一个日志功能,则可以使用现成的redux-logger

import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();

const store = createStore(
  reducer,
  applyMiddleware(logger)
);

10.说说你对Redux的理解?其工作原理?

是什么

React是用于构建用户界面的,帮助我们解决渲染DOM的过程

而在整个应用中会存在很多个组件,每个组件的state是由自身进行管理,包括组件定义自身的state、组件之间的通信通过props传递、使用Context实现数据共享

如果让每个组件都存储自身相关的状态,理论上来讲不会影响应用的运行,但在开发及后续维护阶段,我们将花费大量精力去查询状态的变化过程

这种情况下,如果将所有的状态进行集中管理,当需要更新状态的时候,仅需要对这个管理集中处理,而不用去关心状态是如何分发到每一个组件内部的

redux就是一个实现上述集中管理的容器,遵循三大基本原则:

  • 单一数据源
  • state 是只读的
  • 使用纯函数来执行修改

注意的是,redux并不是只应用在react中,还与其他界面库一起使用,如Vue

11.说说对React Hooks的理解?解决了什么问题?

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

  • useState

  • useEffect

  • useReducer

  • useCallback

  • useMemo

  • useRef

通过对上面的初步认识,可以看到hooks能够更容易解决状态相关的重用的问题:

  • 每调用useHook一次都会生成一份独立的状态
  • 通过自定义hook能够更好的封装我们的功能

编写hooks为函数式编程,每个功能都包裹在函数中,整体风格更清爽,更优雅

hooks的出现,使函数组件的功能得到了扩充,拥有了类组件相似的功能,在我们日常使用中,使用hooks能够解决大多数问题,并且还拥有代码复用机制,因此优先考虑hooks

12.说说对高阶组件的理解?应用场景?

高阶函数(Higher-order function),至少满足下列一个条件的函数

  • 接受一个或多个函数作为输入
  • 输出一个函数

React中,高阶组件即接受一个或多个组件作为参数并且返回一个组件,本质也就是一个函数,并不是一个组件

const EnhancedComponent = highOrderComponent(WrappedComponent);

上述代码中,该函数接受一个组件WrappedComponent作为参数,返回加工过的新组件EnhancedComponent

13.说说对受控组件和非受控组件的理解?应用场景?

受控组件(Controlled Components):

受控组件是指其值由组件本身的state(或props)进行控制和管理的组件。组件的值存储在state(或props)中,并通过事件处理函数来更新。当用户与受控组件进行交互时,组件的值会发生改变,并且通常由父组件的state来管理这个变化过程。

受控组件的应用场景:

  • 表单元素:在表单中使用输入框、复选框、下拉菜单等组件时,可以利用受控组件的特性来实现实时输入验证、数据收集等功能。
  • 多个组件之间需要共享和同步状态的场景。
  • 需要对用户输入进行实时处理和验证的场景。

非受控组件(Uncontrolled Components):

非受控组件是指其值由DOM本身进行管理的组件,组件的值存储在DOM节点上,并通过ref来获取其值。在非受控组件中,React不管理组件的值,而是让DOM自行处理。

非受控组件的应用场景:

  • 获取表单数据但不需要对其进行实时验证或处理的场景。
  • 访问和修改DOM元素的值而无需通过组件状态管理。

14.什么是 redux

redux 是一个应用数据流框架,主要是解决了组件间状态共享的问题,原理是集中式管理,主要有三个核心方法,action,store,reducer,工作流程是 view 调用 store 的 dispatch 接收 action 传入 store,reducer 进行 state 操作,view 通过 store 提供的 getState 获取最新的数据,flux 也是用来进行数据操作的,有四个组成部分 action,dispatch,view,store,工作流程是 view 发出一个 action,派发器接收 action,让 store 进行数据更新,更新完成以后 store 发出 change,view 接受 change 更新视图。Redux 和 Flux 很像。主要区别在于 Flux 有多个可以改变应用状态的 store,在 Flux 中 dispatcher 被用来传递数据到注册的回调事件,但是在 redux 中只能定义一个可更新状态的 store,redux 把 store 和 Dispatcher 合并,结构更加简单清晰
新增 state,对状态的管理更加明确,通过 redux,流程更加规范了,减少手动编码量,提高了编码效率,同时缺点时当数据更新时有时候组件不需要,但是也要重新绘制,有些影响效率。一般情况下,我们在构建多交互,多数据流的复杂项目应用时才会使用它们

15.说说对React refs 的理解?应用场景?

React 中的 Refs提供了一种方式,允许我们访问 DOM节点或在 render方法中创建的 React元素

本质为ReactDOM.render()返回的组件实例,如果是渲染组件则返回的是组件实例,如果渲染dom则返回的是具体的dom节点

二、如何使用

创建ref的形式有三种:

  • 传入字符串,使用时通过 this.refs.传入的字符串的格式获取对应的元素
  • 传入对象,对象是通过 React.createRef() 方式创建出来,使用时获取到创建的对象中存在 current 属性就是对应的元素
  • 传入函数,该函数会在 DOM 被挂载时进行回调,这个函数会传入一个 元素对象,可以自己保存,使用时,直接拿到之前保存的元素对象即可
  • 传入hook,hook是通过 useRef() 方式创建,使用时通过生成hook对象的 current 属性就是对应的元素

传入字符串

只需要在对应元素或组件中ref属性

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return 
; } }

访问当前节点的方式如下:

this.refs.myref.innerHTML = "hello";

传入对象

refs通过React.createRef()创建,然后将ref属性添加到React元素中,如下:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return 
; } }

ref 被传递给 render 中的元素时,对该节点的引用可以在 refcurrent 属性中访问

const node = this.myRef.current;

传入函数

ref传入为一个函数的时候,在渲染过程中,回调函数参数会传入一个元素对象,然后通过实例将对象进行保存

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return 
this.myref = element} />; } }

获取ref对象只需要通过先前存储的对象即可

const node = this.myref 

传入hook

通过useRef创建一个ref,整体使用方式与React.createRef一致

function App(props) {
  const myref = useRef()
  return (
    <>
      
) }

获取ref属性也是通过hook对象的current属性

const node = myref.current;

上述三种情况都是ref属性用于原生HTML元素上,如果ref设置的组件为一个类组件的时候,ref对象接收到的是组件的挂载实例

注意的是,不能在函数组件上使用ref属性,因为他们并没有实例

三、应用场景

  • 对Dom元素的焦点控制、内容选择、控制
  • 对Dom元素的内容设置及媒体播放
  • 对Dom元素的操作和对组件实例的操作
  • 集成第三方 DOM 库

16.React中组件之间如何通信?

组件传递的方式有很多种,根据传送者和接收者可以分为如下:

  • 父组件向子组件传递
  • 子组件向父组件传递
  • 兄弟组件之间的通信
  • 父组件向后代组件传递
  • 非关系组件传递

父组件向子组件传递

由于React的数据流动为单向的,父组件向子组件传递是最常见的方式

父组件在调用子组件的时候,只需要在子组件标签内传递参数,子组件通过props属性就能接收父组件传递过来的参数

function EmailInput(props) {
  return (
    
  );
}

const element = ;

子组件向父组件传递)子组件向父组件传递

子组件向父组件通信的基本思路是,父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值

父组件对应代码如下:

class Parents extends Component {
  constructor() {
    super();
    this.state = {
      price: 0
    };
  }

  getItemPrice(e) {
    this.setState({
      price: e
    });
  }

  render() {
    return (
      
price: {this.state.price}
{/* 向子组件中传入一个函数 */}
); } }

子组件对应代码如下:

class Child extends Component {
  clickGoods(e) {
    // 在此函数中传入值
    this.props.getPrice(e);
  }

  render() {
    return (
      
); } }

兄弟组件之间的通信

如果是兄弟组件之间的传递,则父组件作为中间层来实现数据的互通,通过使用父组件传递

class Parent extends React.Component {
  constructor(props) {
    super(props)
    this.state = {count: 0}
  }
  setCount = () => {
    this.setState({count: this.state.count + 1})
  }
  render() {
    return (
      
); } }

父组件向后代组件传递)父组件向后代组件传递

父组件向后代组件传递数据是一件最普通的事情,就像全局数据一样

使用context提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据

通过使用React.createContext创建一个context

 const PriceContext = React.createContext('price')

context创建成功后,其下存在Provider组件用于创建数据源,Consumer组件用于接收数据,使用实例如下:

Provider组件通过value属性用于给后代组件传递数据:



如果想要获取Provider传递的数据,可以通过Consumer组件或者或者使用contextType属性接收,对应分别如下:

class MyClass extends React.Component {
  static contextType = PriceContext;
  render() {
    let price = this.context;
    /* 基于这个值进行渲染工作 */
  }
}

Consumer组件:


    { /*这里是一个函数*/ }
    {
        price => 
price:{price}
}

17.说说React的事件机制?

React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等

  • React 所有事件都挂载在 document 对象上
  • 当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件
  • 所以会先执行原生事件,然后处理 React 事件
  • 最后真正执行 document 上挂载的事件

18.说说 Real DOM 和 Virtual DOM 的区别?优缺点?

Real DOM(真实DOM)和Virtual DOM(虚拟DOM)是在网页开发中常用的两种DOM表示方式。它们有一些区别和各自的优缺点。

  1. Real DOM(真实DOM):
    Real DOM是指浏览器中实际存在的DOM树,它是由HTML文档解析而来的,包含了网页中所有的元素和其属性。当DOM发生改变时,浏览器会重新渲染整个Real DOM树,这是一种比较昂贵的操作。

优点:

  • 原生操作性:可以直接操作Real DOM进行增删改查等操作。
  • 适合少量变化:对于小规模的DOM变动,Real DOM可以及时更新,保证显示的准确性。

缺点:

  • 性能开销大:操作Real DOM需要重新计算样式、布局和绘制,性能开销较大,特别是在大规模或频繁的DOM操作情况下。
  • 不灵活:频繁的操作Real DOM可能导致页面卡顿和用户体验不佳。
  1. Virtual DOM(虚拟DOM):
    Virtual DOM是在内存中构建的一层抽象,它是Real DOM的轻量级副本。当数据发生变化时,React会使用虚拟DOM进行对比并计算出最小的变更,然后将这些变更操作应用到Real DOM上,减少了对Real DOM的直接操作次数。

优点:

  • 效率提升:Virtual DOM可以对比变更,只计算最小化的操作,然后一次性更新到Real DOM上,减少了真实DOM操作的次数。
  • 灵活性:Virtual DOM可以在JavaScript中进行操作,对一些复杂逻辑的操作更加方便。
  • 跨平台:Virtual DOM并不局限于浏览器环境,可以在不同的平台上使用。

缺点:

  • 额外内存消耗:需要在内存中构建和维护虚拟DOM树,占用一定的内存资源。
  • 学习成本:使用虚拟DOM需要学习额外的语法和概念。

总的来说,Real DOM适合于小规模的DOM变动,并可以直接操作DOM进行增删改查。而Virtual DOM通过优化更新操作,提高了性能和灵活性,尤其适用于大规模或频繁的DOM变动场景。React使用Virtual DOM,通过智能的比较和差异计算,使得重新渲染的成本减少,提高了性能和开发效率。

19.说说 React 生命周期有哪些不同阶段?每个阶段对应的方法是?

创建阶段

创建阶段主要分成了以下几个生命周期方法:

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount

更新阶段

该阶段的函数主要为如下方法:

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate

卸载阶段

componentWillUnmount

20.说说对 React 的理解?有哪些特性?

React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案

遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效

使用虚拟 DOM 来有效地操作 DOM,遵循从高阶组件到低阶组件的单向数据流

特性

React 特性有很多,如:

  • JSX 语法
  • 单向数据绑定
  • 虚拟 DOM
  • 声明式编程
  • Component

21.React 和 Vue 在技术层面有哪些区别?

React 和 Vue 是当前比较流行的前端框架,它们在技术层面有以下区别:

  • 组件化方式不同:React 是基于组件实现的,组件包含了状态和行为,所有组件共享一个状态树。Vue 也是基于组件实现的,但是每个组件都有自己的状态,并且可以很容易地将数据和行为绑定在一起。
  • 数据驱动方式不同:React 使用单向数据流来管理数据,即从父组件到子组件的传递,所以 React 中组件之间的数据交互相对更加复杂。Vue 则使用双向数据绑定来管理数据,使得组件之间的数据交互更加简洁。
  • 模板语法不同:React 使用 JSX 语法,将 HTML 和 JavaScript 结合在一起,使得编写组件更加直观和灵活。Vue 则使用模板语法,并且支持模板内的表达式和指令,使得编写组件具有更高的可读性和可维护性。
  • 生命周期不同:React 组件的生命周期分为三个阶段:初始化、更新和卸载。Vue 组件的生命周期分为八个阶段:创建、挂载、更新、销毁等。
  • 状态管理方式不同:React 使用 Redux 或者 MobX 来管理应用程序的状态。Vue 则提供了自己的状态管理库 Vuex,可以更方便地管理组件之间的共享状态。
  • 性能优化方式不同:React 使用虚拟 DOM 技术来实现高效的渲染性能,可以减少每次渲染时需要操作真实 DOM 的次数。Vue 则使用模板编译和响应式系统来实现高效的渲染性能,并且还提供了一些优化技术,例如懒加载和缓存等。

22.useRef / ref / forwardsRef 的区别是什么?

useRef 和 ref 都是 React 中用于操作 DOM 元素或自定义组件实例的工具,而 forwardRef 则是用于访问嵌套子组件中的 DOM 元素或自定义组件实例。

它们之间的区别如下:

  1. useRef 是一个 hook 函数,可以在函数组件中使用;ref 是一个对象属性,只能在类组件中使用。
  2. useRef 返回一个可变的 ref 对象,可以在组件的整个生命周期内保持不变,也就是说不会因为重新渲染而改变。而 ref 每次渲染都会被重新创建。
  3. useRef 主要用于存储和更新组件内部状态,以及操作 DOM 元素。而 ref 主要用于获取 DOM 元素或自定义组件实例。
  4. forwardRef 是用于将 ref 属性“向下传递”给一个函数式子组件或自定义组件的工具函数。它允许父组件调用子组件中的 DOM 元素或自定义组件实例。

综上所述,useRef 和 ref 都是用于操作 DOM 元素或自定义组件实例的工具,与之相比,forwardRef 则是一个更高级的工具,用于处理专门的情况,即访问嵌套子组件中的 DOM 元素或自定义组件实例。

23.React 中的 ref 有什么用?

用 refs 获取。组件被调用时会新建一个该组件的实例。refs 会指向这个实例,可以是一个回调函数,回调函数会在组件被挂载后立即执行。

如果把 refs 放到原生 DOM 组件的 input 中,我们就可以通过 refs 得到 DOM 节点;如果把 refs 放到 React 组件中,那么我们获得的就是组件的实例,因此就可以调用实例的方法(如果想访问该组件的真实 DOM,那么可以用 React.findDOMNode 来找到 DOM 节点,但是不推崇此方法)。

refs 无法用于无状态组件,无状态组件挂载时只是方法调用,没有新建实例。在 v16 之后,可以使用 useRef。

24.react18新特性?

React 18是React框架的最新版本,预计将引入以下一些新特性和改进:

  1. Concurrent Mode(并发模式):Concurrent Mode旨在提高React应用的性能和用户体验。它使用一种新的渲染调度器,可以在多个优先级下进行渲染,使得React应用更加响应性,并且使得长任务不会阻塞主线程。
  2. Server Components(服务器组件):Server Components是一种全新的React组件类型,它可以在服务器上渲染和运行,可以实现基于React的全栈开发。Server Components可以通过Shadow DOM在客户端进行增量更新,并且可以实现服务器端控制组件逻辑和状态。
  3. Automatic Batching(自动批量更新):React 18将引入自动批量更新机制,即不再需要手动调用batch函数来进行批量更新,而是由React自动处理。这样可以减少更新的次数,提高渲染性能。
  4. New JSX Transform(新的JSX转换):React 18将引入一种新的JSX转换,即React JSX Transform。这种转换方式可以将JSX代码转换为普通的JavaScript代码,不再需要依赖Babel进行转换,从而提高编译性能。
  5. ESLint Plugin Improvements(ESLint插件增强):React 18将改进React ESLint插件,使其更加智能和高效。插件将提供更准确的诊断和推荐,以帮助开发者编写更好的React代码。

25.使用 React hooks 怎么实现类里面的所有生命周期?

  • useState 只在初始化时执行一次,后面不再执行;
  • useEffect 相当于是 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合,可以通过传参及其他逻辑,分别模拟这三个生命周期函数;
  • useEffect 第二个参数是一个数组,如果数组为空时,则只执行一次(相当于componentDidMount);如果数组中有值时,则该值更新时,useEffect 中的函数才会执行;如果没有第二个参数,则每次render时,useEffect 中的函数都会执行;
  • React 保证了每次运行 effect 的同时,DOM 都已经更新完毕,也就是说 effect 中的获取的 state 是最新的,但是需要注意的是,effect 中返回的函数(其清除函数)中,获取到的 state 是更新前的。
  • 传递给 useEffect 的函数在每次渲染中都会有所不同,这是刻意为之的。事实上这正是我们可以在 effect 中获取最新的 count 的值,而不用担心其过期的原因。每次我们重新渲染,都会生成新的 effect,替换掉之前的。某种意义上讲,effect 更像是渲染结果的一部分 —— 每个 effect “属于”一次特定的渲染。
  • effect 的清除阶段(返回函数)在每次重新渲染时都会执行,而不是只在卸载组件的时候执行一次。它会在调用一个新的 effect 之前对前一个 effect 进行清理,从而避免了我们手动去处理一些逻辑 。为了说明这一点,下面按时间列出一个可能会产生的订阅和取消订阅操作调用序列:

26.React.memo() 和 useMemo() 的用法是什么,有哪些区别?

什么是 React.memo()?

React.memo() 随 React v16.6 一起发布。 虽然类组件已经允许您使用 PureComponent 或 shouldComponentUpdate 来控制重新渲染,但 React 16.6 引入了对函数组件执行相同操作的能力。

React.memo() 是一个高阶组件 (HOC),它接收一个组件A作为参数并返回一个组件B,如果组件B的 props(或其中的值)没有改变,则组件 B 会阻止组件 A 重新渲染 。

什么是 useMemo()?

React.memo() 是一个 HOC,而 useMemo() 是一个 React Hook。 使用 useMemo(),我们可以返回记忆值来避免函数的依赖项没有改变的情况下重新渲染。

为了在我们的代码中使用 useMemo(),React 开发者有一些建议给我们:

  • 您可以依赖 useMemo() 作为性能优化,而不是语义保证
  • 函数内部引用的每个值也应该出现在依赖项数组中

总结:React.memo() 和 useMemo() 的主要区别

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

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

27.说说你对 useReducer 的理解

useReducer 是 React hooks 中的一个函数,用于管理具有复杂状态和逻辑的组件。它类似于 Redux 中的 reducer 函数,可以通过调度不同的 action 来更新组件的状态。

使用 useReducer 需要传入一个 reducer 函数和初始状态作为参数。reducer 函数接收当前状态和 action 作为参数,并返回新的状态。它根据 action 的类型来确定应该如何更新状态。

调用 useReducer 会返回一个数组,包含当前状态和一个 dispatch 函数。dispatch 函数用于触发状态的更新,将一个 action 对象传递给它,然后由 reducer 函数处理,并得出新的状态。

相比于 useStateuseReducer 更适用于需要管理复杂状态、有多个相关操作和逻辑的场景。它能够将相关的状态和操作逻辑封装在一起,使代码更加清晰和可维护。

使用 useReducer 可以帮助我们遵循单一数据源原则,将状态和变更逻辑统一管理,减少了状态修改代码的分散性和耦合性。同时,在组件层面可以更好地支持状态的共享、复用和组合。

总之,useReducer 提供了一种在复杂状态管理和逻辑处理方面更灵活、可扩展的解决方案,适用于构建中大型或复杂的交互式组件。

28.说说你对 React Hook的闭包陷阱的理解,有哪些解决方案?

React Hook 的闭包陷阱指的是在使用 React Hook 时,由于闭包的特性,可能会导致状态不正确地被捕获或访问。这可能会导致意外的行为和错误。

当在 React 组件中定义一个函数,并在该函数内部使用了某个状态变量或其他 Hook,但没有将其作为依赖项传递给 useEffect 或 useCallback 等 Hook,就会遇到闭包陷阱。

解决闭包陷阱有以下几种常见的方案:

  1. 将相关的状态变量或其他 Hook 作为依赖项传递:在使用 useEffect、useCallback 等 Hook 时,根据需要将所依赖的状态变量或其他 Hook 作为依赖项传递进去。这会确保 Hook 在依赖项发生变化时被更新。
  2. 使用 useCallback 包裹函数组件:如果在函数组件中定义了回调函数,并且这些回调函数依赖于某个状态变量或其他 Hook,可以使用 useCallback 包裹这些回调函数,将其作为依赖项传递给 useEffect 或其他 Hook。
  3. 使用 useRef 获取最新的状态值:如果只需要获取最新的状态值而不关心其变化过程,可以使用 useRef 来获取状态的引用,并在需要的地方读取该引用。因为 useRef 创建的引用始终保持稳定,不会触发组件重新渲染。
  4. 使用 useReducer 替代 useState:在某些情况下,可以使用 useReducer 来替代 useState。useReducer 将状态和更新逻辑封装在 reducer 函数中,并返回一个 dispatch 函数用于触发状态的更新。由于 dispatch 函数是稳定的,不会在组件重新渲染时发生变化,因此可以避免闭包陷阱的问题。
  5. 使用自定义 Hook 进行状态管理:如果存在更复杂的状态管理需求,可以考虑使用自定义 Hook 来封装相关的状态和操作逻辑。自定义 Hook 可以通过将状态存储在局部变量中,独立于组件的作用域,从而避免了闭包陷阱。

29.我们应该在什么场景下使用 useMemo 和 useCallback ?

  1. 大部分的 useMemo 和 useCallback 都应该移除,他们可能没有带来任何性能上的优化,反而增加了程序首次渲染的负担,并增加程序的复杂性。
  2. 使用 useMemo 和 useCallback 优化子组件 re-render 时,必须同时满足以下条件才有效
    1. 子组件已通过 React.memo 或 useMemo 被缓存
    2. 子组件所有的 prop 都被缓存
  3. 不推荐默认给所有组件都使用缓存,大量组件初始化时被缓存,可能导致过多的内存消耗,并影响程序初始化渲染的速度。

30.setState 是同步,还是异步的?

react18之前。

setState在不同情况下可以表现为异步或同步。

在Promise的状态更新、js原生事件、setTimeout、setInterval…中是同步的。

在react的合成事件中,是异步的。


react18之后。

setState都会表现为异步(即批处理)。

31.说说你对自定义hook的理解

所谓的自定义Hook,实际上就是把很多重复的逻辑都放在一个函数里面,通过闭包的方式给return出来,这是非常高级的方式,程序员崇尚代码简洁,如果说以后业务开发时需要大量的重复代码,我们就可以将它封装成自定义Hook。

32.说说你对 useMemo 的理解

  • 使用memo可以帮助我们优化性能,让react没必要执行不必要的函数
  • 由于复杂数据类型的地址可能发生改变,于是传递给子组件的props也会发生变化,这样还是会执行不必要的函数,所以就用到了useMemo这个api
  • useCallbackuseMemo的语法糖

33.说说你对 useContext 的理解

context(上下文)可以看成是扩大版的props,它可以将全局的数据通过provider接口传递value给局部的组件,让包围在provider中的局部组件可以获取到全局数据的读写接口

全局变量可以看成是全局的上下文

而上下文则是局部的全局变量,因为只有包围在provider中的局部组件才可以获取到这些全局变量的读写接口

使用

  • 使用creacteContext创建一个上下文
  • 设置provider并通过value接口传递state数据
  • 局部组件从value接口中传递的数据对象中获取读写接口

34.为什么不能在循环、条件或嵌套函数中调用 Hooks?

不能在循环、条件或嵌套函数中调用 Hooks,是因为 React 需要依赖 Hooks 在组件的每次渲染之间保持稳定的身份。以下是原因的解释:

  1. Hooks 只能在 React 函数组件的顶层调用:Hooks 是一种需要遵循特定规则的特殊函数。React 依赖于 Hooks 的调用顺序和数量来确定每个组件实例的状态。如果在循环、条件语句或嵌套函数中使用 Hooks,无法保证 Hooks 按照固定的顺序调用以及正确地与组件实例关联。
  2. 循环、条件或嵌套函数会破坏 Hook 调用顺序:在循环中使用 Hooks 会导致 Hook 的调用顺序不可预测,可能会产生错误的状态更新。同样,在条件语句或嵌套函数中使用 Hooks 也会破坏 Hook 的调用顺序,造成意外行为。
  3. 使用闭包可能导致无法预料的问题:Hooks 是基于闭包的机制来捕获组件的状态,并通过索引来跟踪状态。如果在循环、条件或嵌套函数中使用 Hooks,并在闭包中捕获状态,很可能会导致多个闭包共享了相同的状态,从而导致状态难以预测和维护。

为了避免这些问题,React 对 Hooks 的使用做了限制:Hooks 只能在 React 函数组件的顶层调用,确保在每次渲染中 Hooks 的调用顺序一致。这样可以保证组件状态的稳定性,有效避免错误的行为和难以排查的问题。

35.useEffect 与 useLayoutEffect 有什么区别?

共同点

  • 运用效果: useEffect 与 useLayoutEffect 两者都是用于处理副作用,这些副作用包括改变 DOM、设置订阅、操作定时器等。在函数组件内部操作副作用是不被允许的,所以需要使用这两个函数去处理。
  • 使用方式: useEffect 与 useLayoutEffect 两者底层的函数签名是完全一致的,都是调用的 mountEffectImpl方法,在使用上也没什么差异,基本可以直接替换。

不同点

  • 使用场景: useEffect 在 React 的渲染过程中是被异步调用的,用于绝大多数场景;而 useLayoutEffect 会在所有的 DOM 变更之后同步调用,主要用于处理 DOM 操作、调整样式、避免页面闪烁等问题。也正因为是同步处理,所以需要避免在 useLayoutEffect 做计算量较大的耗时任务从而造成阻塞。
  • 使用效果: useEffect是按照顺序执行代码的,改变屏幕像素之后执行(先渲染,后改变DOM),当改变屏幕内容时可能会产生闪烁;useLayoutEffect是改变屏幕像素之前就执行了(会推迟页面显示的事件,先改变DOM后渲染),不会产生闪烁。useLayoutEffect总是比useEffect先执行。

36.为什么 useState 返回的是数组而不是对象?

useState 的用法:

const [count, setCount] = useState(0)

可以看到 useState 返回的是一个数组,那么为什么是返回数组而不是返回对象呢?

要回答这个问题得弄明白 ES6 的解构赋值(destructring assignment)语法 , 来看 2 个简单的示例:

  • 数组的解构赋值:
const foo = ['one', 'two', 'three'];

const [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
  • 对象的解构赋值:
const user = {
    id: 42,
    is_verified: true
};

const { id, is_verified } = user;

console.log(id); // 42
console.log(is_verified); // true 

搞清楚了解构赋值,那上面的问题就比较好解释了。

如果 useState 返回数组,那么你可以顺便对数组中的变量命名,代码看起来也比较干净。而如果是对象的话返回的值必须和 useState 内部实现返回的对象同名,这样你只能在 function component 中使用一次,想要多次使用 useState 必须得重命名返回值。

// 第一次使用
const { state, setState } = useState(false)
// 第二次使用
const { state: counter, setState: setCounter} = useState(0)

当然事情总是有两面性的,使用 array 也存在一些问题:

  • 返回值强顺序,灵活性比较低。array[0] 为值,array[1] 为改变值的方法。
  • 返回的值基本都得使用,对于有些返回值不想使用的话代码看起来有些怪,比如只想用 setState, 就得这么写:const [, setState] = useState(false)
  • 返回的参数不能太多,否则处理上面 2 个场景会很麻烦。

如果在自定义的Hook中遇到了以上几个问题,不妨试试返回 object。

简单总结一下,在自定义 hook 的时候可以遵循一个简单原则:当参数大于 2 个的时候返回值的类型返回 object, 否则返回数组。

37.Redux中的connect有什么作用?

connect负责连接React和Redux

获取state

connect 通过 context获取 Provider 中的 store,通过 store.getState() 获取整个store tree 上所有state

包装原组件

将state和action通过props的方式传入到原组件内部 wrapWithConnect 返回—个 ReactComponent 对象 Connect,Connect重新 render 外部传入的原组件 WrappedComponent ,并把 connect 中传入的 mapStateToPropsmapDispatchToProps与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent

监听store tree变化

connect缓存了store tree中state的状态,通过当前state状态 和变更前 state 状态进行比较,从而确定是否调用 this.setState()方法触发 Connect 及其子组件的重新渲染

38.Redux 状态管理器和变量挂载到 window 中有什么区别?

两者都是存储数据以供后期使用。但是Redux状态更改可回溯——Time travel,数据多了的时候可以很清晰的知道改动在哪里发生,完整的提供了一套状态管理模式。

随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等。

管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。

如果这还不够糟糕,考虑一些来自前端开发领域的新需求,如更新调优、服务端渲染、路由跳转前请求数据等等。前端开发者正在经受前所未有的复杂性,难道就这么放弃了吗?当然不是。

这里的复杂性很大程度上来自于:我们总是将两个难以理清的概念混淆在一起:变化和异步。 可以称它们为曼妥思和可乐。如果把二者分开,能做的很好,但混到一起,就变得一团糟。一些库如 React 视图在视图层禁止异步和直接操作 DOM来解决这个问题。美中不足的是,React 依旧把处理 state 中数据的问题留给了你。Redux就是为了帮你解决这个问题。

39.Redux 中异步的请求怎么处理

一般的异步请求,可以在 componentDidmount 中直接进⾏请求,⽆须借助redux。

但是在⼀定规模的项⽬中,上述⽅法很难进⾏异步流的管理,通常情况下我们会借助redux的异步中间件进⾏异步处理。

redux异步流中间件其实有很多,当下主流的异步中间件有两种redux-thunkredux-saga

使用react-thunk中间件

redux-thunk优点:
  • 体积⼩: redux-thunk的实现⽅式很简单,只有不到20⾏代码
  • 使⽤简单: redux-thunk没有引⼊像redux-saga或者redux-observable额外的范式,上⼿简单
redux-thunk缺陷:
  • 样板代码过多: 与redux本身⼀样,通常⼀个请求需要⼤量的代码,⽽且很多都是重复性质的
  • 耦合严重: 异步操作与redux的action偶合在⼀起,不⽅便管理
  • 功能孱弱: 有⼀些实际开发中常⽤的功能需要⾃⼰进⾏封装

使用redux-saga中间件

redux-saga优点:
  • 异步解耦: 异步操作被被转移到单独 saga.js 中,不再是掺杂在 action.js 或 component.js 中
  • action摆脱thunk function: dispatch 的参数依然是⼀个纯粹的 action (FSA),⽽不是充满 “⿊魔法” thunk function
  • 异常处理: 受益于 generator function 的 saga 实现,代码异常/请求失败 都可以直接通过 try/catch 语法直接捕获处理
  • 功能强⼤: redux-saga提供了⼤量的 Saga 辅助函数和 Effect 创建器供开发者使⽤,开发者⽆须封装或者简单封装即可使⽤
  • 灵活: redux-saga可以将多个Saga可以串⾏/并⾏组合起来,形成⼀个⾮常实⽤的异步flow
  • 易测试,提供了各种case的测试⽅案,包括mock task,分⽀覆盖等等
redux-saga缺陷:
  • 额外的学习成本: redux-saga不仅在使⽤难以理解的 generator function,⽽且有数⼗个API,学习成本远超redux-thunk。最重要的是你的额外学习成本是只服务于这个库的,与redux-observable不同,redux-observable虽然也有额外学习成本但是背后是rxjs和⼀整套思想
  • 体积庞⼤: 体积略⼤,代码近2000⾏,min版25KB左右
  • 功能过剩: 实际上并发控制等功能很难⽤到,但是我们依然需要引⼊这些代码
  • ts⽀持不友好: yield⽆法返回TS类型

redux-saga可以捕获action,然后执行一个函数,那么可以把异步代码放在这个函数中。

40.React构建组件的方式有哪些?有什么区别?

一、是什么

组件就是把图形、非图形的各种逻辑均抽象为一个统一的概念(组件)来实现开发的模式

React中,一个类、一个函数都可以视为一个组件

组件所存在的优势:

  • 降低整个系统的耦合度,在保持接口不变的情况下,我们可以替换不同的组件快速完成需求,例如输入框,可以替换为日历、时间、范围等组件作具体的实现
  • 调试方便,由于整个系统是通过组件组合起来的,在出现问题的时候,可以用排除法直接移除组件,或者根据报错的组件快速定位问题,之所以能够快速定位,是因为每个组件之间低耦合,职责单一,所以逻辑会比分析整个系统要简单
  • 提高可维护性,由于每个组件的职责单一,并且组件在系统中是被复用的,所以对代码进行优化可获得系统的整体升级

二、如何构建

React目前来讲,组件的创建主要分成了三种方式:

  • 函数式创建
  • 通过 React.createClass 方法创建
  • 继承 React.Component 创建

函数式创建

React Hooks出来之前,函数式组件可以视为无状态组件,只负责根据传入的props来展示视图,不涉及对state状态的操作

大多数组件可以写为无状态组件,通过简单组合构建其他组件

通过 React.createClass 方法创建

React.createClass是react刚开始推荐的创建组件的方式,目前这种创建方式已经不怎么用了

像上述通过函数式创建的组件的方式,最终会通过babel转化成React.createClass这种形式

继承 React.Component 创建

同样在react hooks出来之前,有状态的组件只能通过继承React.Component这种形式进行创建

有状态的组件也就是组件内部存在维护的数据,在类创建的方式中通过this.state进行访问

当调用this.setState修改组件的状态时,组价会再次会调用render()方法进行重新渲染

通过继承React.Component创建一个时钟

区别

由于React.createClass 创建的方式过于冗杂,并不建议使用

而像函数式创建和类组件创建的区别主要在于需要创建的组件是否需要为有状态组件:

  • 对于一些无状态的组件创建,建议使用函数式创建的方式
  • 由于react hooks的出现,函数式组件创建的组件通过使用hooks方法也能使之成为有状态组件,再加上目前推崇函数式编程,所以这里建议都使用函数式的方式来创建组件

在考虑组件的选择原则上,能用无状态组件则用无状态组件

41.说说你在React项目是如何捕获错误的?

  • 使用了 static getDerivedStateFromError()
  • 使用了 componentDidCatch()

下面这些情况无法捕获到异常:

  • 事件处理
  • 异步代码
  • 服务端渲染
  • 自身抛出来的错误

react 16版本之后,会把渲染期间发生的所有错误打印到控制台

除了错误信息和 JavaScript 栈外,React 16 还提供了组件栈追踪。现在你可以准确地查看发生在组件树内的错误信息:

42.react中懒加载的实现原理是什么?

使用 React.lazy

在实际的使用中,首先是引入组件方式的变化:

// 不使用 React.lazy
import OtherComponent from './OtherComponent';
// 使用 React.lazy
const OtherComponent = React.lazy(() => import('./OtherComponent'))

React.lazy 接受一个函数作为参数,这个函数需要调用 import() 。它需要返回一个 Promise,该 Promise 需要 resolve 一个 defalut export 的 React 组件。

43.React有哪些性能优化的方法?

  1. 使用生产环境构建:在部署应用之前,确保使用 React 的生产环境构建,这样可以启用代码压缩、移除开发环境下的调试工具和日志输出等,减小应用的文件大小和加载时间。
  2. 虚拟化长列表:当渲染大量数据时,考虑使用虚拟化技术,例如 React Virtualized 或 react-window 这样的库。它们只渲染当前可见区域内的内容,而不是全部渲染,从而大幅减少 DOM 元素的数量,提高性能。
  3. 使用 memoization 优化组件:通过使用 React.memo() 函数或 useMemo() 钩子,可以记忆组件的渲染输出,避免不必要的重渲染。这对于接收相同属性的函数组件尤其有用。
  4. 使用 shouldComponentUpdate() 或 PureComponent:为类组件时,在 shouldComponentUpdate() 方法中手动比较传入的属性和状态,决定是否进行渲染更新。另外,也可以使用 PureComponent 类来自动执行浅层比较以确定是否重新渲染。
  5. 懒加载组件:将应用拆分成多个异步加载的代码块,懒加载那些在初始渲染时不需要的组件。这样可以减小初始加载的文件大小,提高应用的响应速度。
  6. 避免不必要的渲染:确保只有在属性或状态发生变化时才触发组件的重新渲染。避免无关的更新可以显著提高性能。可以使用浅层比较、使用不可变数据、避免在 render 方法中创建新对象等方式来实现。
  7. 使用 React DevTools 进行性能分析:React DevTools 是一款强大的工具,可以帮助你检查组件的渲染次数、更新时间和组件树结构等信息,从而找到性能瓶颈并进行优化。
  8. 使用 webpack 或其他打包工具进行代码分割:通过将应用的代码拆分成多个块,并按需加载非核心代码,可以减小初始加载量,提高页面加载速度。
  9. 使用 memo 或 useMemo 缓存计算结果:对于一些开销较大的计算任务,可以使用 memo 或 useMemo 来缓存计算结果,避免重复计算,提高性能。

44.React Fiber 是如何实现更新过程可控?

更新过程的可控主要体现在下面几个方面:

  • 任务拆分
  • 任务挂起、恢复、终止
  • 任务具备优先级

任务拆分

在 React Fiber 机制中,它采用"化整为零"的思想,将调和阶段(Reconciler)递归遍历 VDOM 这个大任务分成若干小任务,每个任务只负责一个节点的处理

任务挂起、恢复、终止

  • workInProgress tree

workInProgress 代表当前正在执行更新的 Fiber 树。在 render 或者 setState 后,会构建一颗 Fiber 树,也就是 workInProgress tree,这棵树在构建每一个节点的时候会收集当前节点的副作用,整棵树构建完成后,会形成一条完整的副作用链。

  • currentFiber tree

currentFiber 表示上次渲染构建的 Filber 树。在每一次更新完成后 workInProgress 会赋值给 currentFiber。在新一轮更新时 workInProgress tree 再重新构建,新 workInProgress 的节点通过 alternate 属性和 currentFiber 的节点建立联系。

在新 workInProgress tree 的创建过程中,会同 currentFiber 的对应节点进行 Diff 比较,收集副作用。同时也会复用和 currentFiber 对应的节点对象,减少新创建对象带来的开销。也就是说无论是创建还是更新、挂起、恢复以及终止操作都是发生在 workInProgress tree 创建过程中的。workInProgress tree 构建过程其实就是循环的执行任务和创建下一个任务。

挂起

当第一个小任务完成后,先判断这一帧是否还有空闲时间,没有就挂起下一个任务的执行,记住当前挂起的节点,让出控制权给浏览器执行更高优先级的任务。

恢复

在浏览器渲染完一帧后,判断当前帧是否有剩余时间,如果有就恢复执行之前挂起的任务。如果没有任务需要处理,代表调和阶段完成,可以开始进入渲染阶段。

  • 如何判断一帧是否有空闲时间的呢?

使用前面提到的 RIC (RequestIdleCallback) 浏览器原生 API,React 源码中为了兼容低版本的浏览器,对该方法进行了 Polyfill。

  • 恢复执行的时候又是如何知道下一个任务是什么呢?

答案是在前面提到的链表。在 React Fiber 中每个任务其实就是在处理一个 FiberNode 对象,然后又生成下一个任务需要处理的 FiberNode。

终止

其实并不是每次更新都会走到提交阶段。当在调和过程中触发了新的更新,在执行下一个任务的时候,判断是否有优先级更高的执行任务,如果有就终止原来将要执行的任务,开始新的 workInProgressFiber 树构建过程,开始新的更新流程。这样可以避免重复更新操作。这也是在 React 16 以后生命周期函数 componentWillMount 有可能会执行多次的原因。

任务具备优先级

React Fiber 除了通过挂起,恢复和终止来控制更新外,还给每个任务分配了优先级。具体点就是在创建或者更新 FiberNode 的时候,通过算法给每个任务分配一个到期时间(expirationTime)。在每个任务执行的时候除了判断剩余时间,如果当前处理节点已经过期,那么无论现在是否有空闲时间都必须执行该任务。过期时间的大小还代表着任务的优先级。

任务在执行过程中顺便收集了每个 FiberNode 的副作用,将有副作用的节点通过 firstEffect、lastEffect、nextEffect 形成一条副作用单链表 A1(TEXT)-B1(TEXT)-C1(TEXT)-C1-C2(TEXT)-C2-B1-B2(TEXT)-B2-A。

其实最终都是为了收集到这条副作用链表,有了它,在接下来的渲染阶段就通过遍历副作用链完成 DOM 更新。这里需要注意,更新真实 DOM 的这个动作是一气呵成的,不能中断,不然会造成视觉上的不连贯(commit)

45.不同版本的 React 都做过哪些优化?

React 15 架构

React15架构可以分为两层:

  • Reconciler(协调器)—— 负责找出变化的组件;
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上;

在React15及以前,Reconciler采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,递归更新时间超过了16ms,用户交互就会卡顿。

为了解决这个问题,React16将递归的无法中断的更新重构为异步的可中断更新,由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是,全新的Fiber架构应运而生。

React 16 架构

为了解决同步更新长时间占用线程导致页面卡顿的问题,也为了探索运行时优化的更多可能,React开始重构并一直持续至今。重构的目标是实现Concurrent Mode(并发模式)。

从v15到v16,React团队花了两年时间将源码架构中的Stack Reconciler重构为Fiber Reconciler。

React16架构可以分为三层:

  • Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler;
  • Reconciler(协调器)—— 负责找出变化的组件:更新工作从递归变成了可以中断的循环过程。Reconciler内部采用了Fiber的架构;
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上。

React 17 优化

React16的expirationTimes模型只能区分是否>=expirationTimes决定节点是否更新。React17的lanes模型可以选定一个更新区间,并且动态的向区间中增减优先级,可以处理更细粒度的更新。

Lane用二进制位表示任务的优先级,方便优先级的计算(位运算),不同优先级占用不同位置的“赛道”,而且存在批的概念,优先级越低,“赛道”越多。高优先级打断低优先级,新建的任务需要赋予什么优先级等问题都是Lane所要解决的问题。

Concurrent Mode的目的是实现一套可中断/恢复的更新机制。其由两部分组成:

  • 一套协程架构:Fiber Reconciler
  • 基于协程架构的启发式更新算法:控制协程架构工作方式的算法

46.虚拟DOM一定更快吗

虚拟DOM/domDiff

我们常说的虚拟DOM是通过JS对象模拟出来的DOM节点,domDiff是通过特定算法计算出来一次操作所带来的DOM变化。react和vue中都使用了虚拟DOM,我们借着react聊聊虚拟DOM。

react中涉及到虚拟DOM的代码主要分为以下三部分,其中核心是第二步的domDiff算法:

  • 把render中的JSX(或者createElement这个API)转化成虚拟DOM
  • 状态或属性改变后重新计算虚拟DOM并生成一个补丁对象(domDiff)
  • 通过这个补丁对象更新视图中的DOM节点

虚拟DOM不一定更快

干前端的都知道DOM操作是性能杀手,因为操作DOM会引起页面的回流或者重绘。相比起来,通过多一些预先计算来减少DOM的操作要划算的多。

但是,“使用虚拟DOM会更快”这句话并不一定适用于所有场景。例如:一个页面就有一个按钮,点击一下,数字加一,那肯定是直接操作DOM更快。使用虚拟DOM无非白白增加了计算量和代码量。即使是复杂情况,浏览器也会对我们的DOM操作进行优化,大部分浏览器会根据我们操作的时间和次数进行批量处理,所以直接操作DOM也未必很慢。

那么为什么现在的框架都使用虚拟DOM呢?因为使用虚拟DOM可以提高代码的性能下限,并极大的优化大量操作DOM时产生的性能损耗。同时这些框架也保证了,即使在少数虚拟DOM不太给力的场景下,性能也在我们接受的范围内。

而且,我们之所以喜欢react、vue等使用了虚拟DOM框架,不光是因为他们快,还有很多其他更重要的原因。例如react对函数式编程的友好,vue优秀的开发体验等,目前社区也有好多比较这两个框架并打口水战的,我觉着还是在两个都懂的情况下多探究一下原理更有意义一些。

47.为什么不能用数组下标来作为react组件中的key?

react 使用diff算法,使用key来做同级比对。如果使用数组下标作为key,有以下情况:

  • 在数组头部或中部插入或删除元素: 所有key对应的节点的值发生更改,进行重新渲染。造成性能损耗
  • 而如果使用数组中唯一值来作为key:不管是在何处插入或删除节点,其他key对应的节点的值未发生更改,只需插入或删除操作的数组节点。

48.React Hooks当中的useEffect是如何区分生命周期钩子的

seEffect可以看成是 componentDidMountcomponentDidUpdatecomponentWillUnmount 三者的结合。

useEffect(callback, [source])接收两个参数,调用方式如下:

useEffect(() => {
   console.log('mounted');
   
   return () => {
       console.log('willUnmount');
   }
 }, [source]);

生命周期函数的调用主要是通过第二个参数[source]来进行控制,有如下几种情况:

  • [source]参数不传时,则每次都会优先调用上次保存的函数中返回的那个函数,然后再调用外部那个函数;
  • [source]参数传[]时,则外部的函数只会在初始化时调用一次,返回的那个函数也只会最终在组件卸载时调用一次;
  • [source]参数有值时,则只会监听到数组中的值发生变化后才优先调用返回的那个函数,再调用外部的函数。

49.使用React Hooks有什么优势?

hooks 是react 16.8 引入的特性,他允许你在不写class的情况下操作state 和react的其他特性。

React Hooks 要解决的问题是状态共享,是继 render-props 和 higher-order components 之后的第三种状态共享方案,不会产生 JSX 嵌套地狱问题。

这个状态指的是状态逻辑,所以称为状态逻辑复用会更恰当,因为只共享数据处理逻辑,不会共享数据本身。

50.React Hooks带来了什么便利?

在没有 hooks 之前,我们使用函数定义的组件中,不能使用 React 的 state、各种生命周期钩子类组件的特性。在 React 16.8 之后,推出了新功能: Hooks,通过 hooks 我们可以再函数定义的组件中使用类组件的特性。

好处:

  • 跨组件复用: 其实 render props / HOC 也是为了复用,相比于它们,Hooks 作为官方的底层 API,最为轻量,而且改造成本小,不会影响原来的组件层次结构和传说中的嵌套地狱;
  • 相比而言,类组件的实现更为复杂
    • 不同的生命周期会使逻辑变得分散且混乱,不易维护和管理;
    • 时刻需要关注this的指向问题;
    • 代码复用代价高,高阶组件的使用经常会使整个组件树变得臃肿;
  • 状态与 UI 隔离: 正是由于 Hooks 的特性,状态逻辑会变成更小的粒度,并且极容易被抽象成一个自定义 Hooks,组件中的状态和 UI 变得更为清晰和隔离。

注意:

  • 避免在 循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
  • 不能在useEffect中使用useState,React 会报错提示;
  • 类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存

51.React中的VM 一定会提高性能吗?

不一定,因为 VM 只是通过 diff 算法避免了一些不需要变更的 DOM 操作,最终还是要操作 DOM 的,并且 diff 的过程也是有成本的。

对于某些场景,比如都是需要变更 DOM 的操作,因为 VM 还会有额外的 diff 算法的成本在里面,所以 VM 的方式并不会提高性能,甚至比原生 DOM 要慢。

但是正如尤大大说的,这是一个性能 vs 可维护性的取舍。

框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。

没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。

针对任何一个 benchmark,都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,出于可维护性的考虑,不可能在每一个地方都去做手动优化。

52.为什么React的 VM 可以提高性能?

因为 VM 并不是真实的操作 DOM,通过 diff 算法可以避免一些不变要的 DOM 操作,从而提高了性能。

53.react 的虚拟dom是怎么实现的?

react 是把真实的 DOM 树转换为 JS 对象树,也就是 Virtual DOM。每次数据更新后,重新计算 VM,并和上一次生成的 VM 树进行对比,对发生变化的部分进行批量更新。除了性能之外,VM 的实现最大的好处在于和其他平台的集成。

54.在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState 会发生什么?

当调用 setState 的时候,实际上会将新的 state 合并到状态更新队列中,并对 partialState 以及 _pendingStateQueue 更新队列进行合并操作。最终通过 enqueueUpdate 执行 state 更新。

如果在 shouldComponentUpdate 或 componentWillUpdate 中使用 setState,会使得 state 队列(_pendingStateQueue)不为 null,从而调用 updateComponent 方法,updateComponent 中会继续调用 shouldComponentUpdate 和 componentWillUpdate,因此造成死循环。

55.setstate后发生了什么

简单版本: React 利用状态队列机制实现了 setState 的“异步”更新,避免频繁的重复更新 state。

首先将新的 state 合并到状态更新队列中,然后根据更新队列和 shouldComponentUpdate 的状态来判断是否需要更新组件。

复杂版本

  • enqueueSetState 将 state 放入队列中,并调用 enqueueUpdate 处理要更新的 Component
  • 如果组件当前正处于 update 事务中,则先将 Component 存入 dirtyComponent 中。否则调用batchedUpdates 处理。
  • batchedUpdates 发起一次 transaction.perform() 事务
  • 开始执行事务初始化,运行,结束三个阶段
    • 初始化:事务初始化阶段没有注册方法,故无方法要执行
    • 运行:执行 setSate 时传入的 callback 方法
    • 结束:更新 isBatchingUpdates 为 false,并执行 FLUSH_BATCHED_UPDATES 这个 wrapper 中的close方法,FLUSH_BATCHED_UPDATES在close阶段,会循环遍历所有的 dirtyComponents,调用updateComponent 刷新组件,并执行它的 pendingCallbacks, 也就是 setState 中设置的 callback。

56.React中为什么要给组件设置 key?

在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。

在 React Diff 算法中React 会借助元素的 Key 值来判断该元素是新创建的还是被移动而来的元素,从而减少不必要的元素重新渲染。

此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。

57.React 的事件代理机制和原生事件绑定有什么区别?

  • 需要 e.preventDefault() 即可,原生存在兼容性问题。
  • 事件类型:React 是 原生事件类型 的一个子集(React 只是实现了 DOM level3 的事件接口,有些事件 React 并没有实现,比如 window 的 resize 事件。)阻止 React 事件冒泡的行为只能用于 React 合成事件系统,但是 在原生事件中的阻止冒泡行为,却可以阻止 React 合成事件的传播。
  • 事件的绑定方式:原生事件系统中支持多种不同的绑定事件的方式,React 中只有一种
  • 事件对象:原生中存在 IE 的兼容性问题,React 做了兼容处理。

58.React 的事件代理机制和原生事件绑定混用会有什么问题?

我们在平时的开发中应该尽可能的避免 React 的事件代理机制和原生事件绑定混用。

React 的合成事件层,并没有将事件直接绑定到 DOM 元素上,所以使用 e.stopPropagation() 来阻止原生 DOM 的冒泡的行为是不行的。阻止 React 事件冒泡的行为只能用于 React 合成事件系统,但是 在原生事件中的阻止冒泡行为,却可以阻止 React 合成事件的传播

59.为什么不能直接使用 this.state 改变数据?

react中不能直接修改state,因为并不会重新触发render。

以如下方式更新状态,组件不会重新渲染。

//Wrong
This.state.message =”Hello world”;

而是需要使用setState()方法,状态改变时,组件通过重新渲染做出响应。

//Correct
This.setState({message: ‘Hello World’});

setState通过一个队列机制来实现 state 更新。当执行 setState 的时候,会将需要更新的 state 合并后放入状态队列,而不会立刻更新 this.state。队列机制可以高效的批量更新 state,如果不通过 setState 而直接修改 this.state,那么该 state 将不会被放入状态队列中,当下次调用 setState 并对状态队列进行合并时,将会忽略之前被直接修改的 state,而造成无法预知的错误。

60.什么是高阶组件?

高阶组件就是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。基本上,这是从React的组成性质派生的一种模式,我们称它们为“纯”组件, 因为它们可以接受任何动态提供的子组件,但它们不会修改或复制其输入组件的任何行为。

const EnhancedComponent = higherOrderComponent(WrappedComponent);
  • 高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧
  • 高阶组件的参数为一个组件返回一个新的组件
  • 组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件

61.React中的类组件和函数组件之间有什么区别

类组件(Class components)

  • 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props。
    • 所有 React 组件都必须是纯函数,并禁止修改其自身 props。
  • React是单项数据流,父组件改变了属性,那么子组件视图会更新。
    • 属性 props是外界传递过来的,状态 state是组件本身的,状态可以在组件中任意修改
    • 组件的属性和状态改变都会更新视图。
class Welcome extends React.Component {
  render() {
    return (
     <h1>Welcome { this.props.name }</h1>
    );
  }
}
ReactDOM.render(<Welcome name='react' />, document.getElementById('root'));

函数组件(functional component)

函数组件接收一个单一的 props 对象并返回了一个React元素

function Welcome (props) {
  return <h1>Welcome {props.name}</h1>
}
ReactDOM.render(<Welcome name='react' />, document.getElementById('root'));

区别

  • 语法上

两者最明显的不同就是在语法上,函数组件是一个纯函数,它接收一个props对象返回一个react元素。而类组件需要去继承React.Component并且创建render函数返回react元素,这将会要更多的代码,虽然它们实现的效果相同。

  • 状态管理

因为函数组件是一个纯函数,你不能在组件中使用setState(),这也是为什么把函数组件称作为无状态组件。

如果你需要在你的组件中使用state,你可以选择创建一个类组件或者将state提升到你的父组件中,然后通过props对象传递到子组件。

  • 生命周期钩子

你不能在函数组件中使用生命周期钩子,原因和不能使用state一样,所有的生命周期钩子都来自于继承的React.Component中。

因此,如果你想使用生命周期钩子,那么需要使用类组件。

注意:在react16.8版本中添加了hooks,使得我们可以在函数组件中使用useState钩子去管理state,使用useEffect钩子去使用生命周期函数。因此,2、3两点就不是它们的区别点。从这个改版中我们可以看出作者更加看重函数组件,而且react团队曾提及到在react之后的版本将会对函数组件的性能方面进行提升。

  • 调用方式

如果SayHi是一个函数,React需要调用它:

// 你的代码 
function SayHi() { 
    return <p>Hello, React</p> 
} 
// React内部 
const result = SayHi(props) // » 

Hello, React

如果SayHi是一个类,React需要先用new操作符将其实例化,然后调用刚才生成实例的render方法:

// 你的代码 
class SayHi extends React.Component { 
    render() { 
        return <p>Hello, React</p> 
    } 
} 
// React内部 
const instance = new SayHi(props) // » SayHi {} 
const result = instance.render() // » 

Hello, React

可想而知,函数组件重新渲染将重新调用组件方法返回新的react元素,类组件重新渲染将new一个新的组件实例,然后调用render类方法返回react元素,这也说明为什么类组件中this是可变的。

62.什么是虚拟DOM?

虚拟DOM(VDOM)它是真实DOM的内存表示,一种编程概念,一种模式。它会和真实的DOM同步,比如通过ReactDOM这种库,这个同步的过程叫做调和(reconcilation)。

虚拟DOM更多是一种模式,不是一种特定的技术。

63.react新增的生命周期

  1. static getDerivedStateFromProps: 这个静态方法在组件实例化、接收到新的 props 或者调用setState 方法时会被调用。它接收两个参数,props 和 state,并返回一个对象来更新组件的状态或者返回 null表示不需要更新。这个方法替代了旧的 componentWillReceiveProps 生命周期方法
  2. getSnapshotBeforeUpdate: 这个方法在更新发生之前被调用,并且在最终渲染之前可以捕获 DOM树上的一些信息(比如滚动位置)。它可以返回一个值作为后续 componentDidUpdate方法的第三个参数。这个方法提供了一种处理更新前后状态的机制。
  3. componentDidCatch: 这个生命周期方法用于处理在组件树中的子组件抛出的错误。当子组件抛出错误时,父组件的componentDidCatch 方法将会被调用,并传入错误信息作为参数。它可以用于显示错误界面或者记录错误。

你可能感兴趣的:(react.js,前端,前端框架)