React

1. 对react的理解?有哪些特性?优势? 和vue的区别

React是一个简单的javascript UI库,用于构建高效、快速的用户界面。它是一个轻量级库,因此很受欢迎。它遵循组件设计模式、声明式编程范式函数式编程概念,以使前端应用程序更高效。它使用虚拟DOM来有效地操作DOM。它遵循从父组件到子组件的单向数据流。

特点:

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

优势:

通过上面的初步了解,可以感受到 React 存在的优势:

  • 高效灵活
  • 声明式的设计,简单使用
  • 组件式开发,提高代码复用率
  • 单向响应的数据流会比双向绑定的更安全,速度更快

react采用单向数据绑定,推崇结合immutable来实现数据不可变;vue思想是 响应式的,双向数据绑定

vue 采用了template, react采用了jsx

很多人认为 React 是 MVC 中的 V(视图),vue采用的是mvvm架构模式

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

是什么?

Real DOM,真实 DOM,意思为文档对象模型,是一个结构化文本的抽象,在页面渲染出的每一个结点都是一个真实 DOM 结构。

Virtual Dom,本质上是以 JavaScript 对象形式存在的对 DOM 的描述。

创建虚拟 DOM 目的就是为了更好将虚拟的节点渲染到页面视图中,虚拟 DOM 对象的节点与真实 DOM 的属性一一照应。

两者的区别如下:

  • 虚拟 DOM 不会进行排版与重绘操作,而真实 DOM 会频繁重排与重绘
  • 虚拟 DOM 的总损耗是“虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘”,真实 DOM 的总损耗是“真实 DOM 完全增删改+排版与重绘”

优缺点:

真实 DOM 的优势:

  • 易用

缺点:

  • 效率低,解析速度慢,内存占用量过高
  • 性能差:频繁操作真实 DOM,易于导致重绘与回流

使用虚拟 DOM 的优势如下:

  • 简单方便:如果使用手动操作真实 DOM 来完成页面,繁琐又容易出错。
  • 性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能
  • 跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行(将真实DOM映射为JavaScript对象,可以使代码不仅仅局限于对浏览器DOM的操作,只要支持JavaScript即可使用。)
  • 防范xss攻击

缺点:

  • 在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化
  • 首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢

3. react中的diff算法

React 的 diff 策略,将 将传统的复杂度为O(n3) 的问题转换成复杂度为 O(n) 的问题

diff策略分为三个层级

tree diff

只会对树相同层级的节点进行比较。只有删除、创建的操作

component diff

如果是同一类型的组件,则会继续往下diff运算。

不同类型的组件,则将该组件判断为 dirty component,删除组件和其所有子节点。

element diff

当节点处于同一层级时,每个节点在对应的层级用唯一的key作为标识。react diff提供了三种结点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。

面对全新的节点时,执行插入操作, 面对多余的节点时,执行删除操作, 面对换位的节点时,执行移动操作

移动的细节:

新旧节点会遍历后对比下标,新的下标称为lastIndex,旧的称为index,如果lastIndex大于index,需要将节点旧的节点移动到新的位置,相反则位置不变,index的值赋给lastIndex(lastIndex=index)。
如果没有找到对应位置节点,则执行新增; 如果旧的节点在新的节点组用不到,则执行删除;一般是在最后做删除操作。

经典面试题:
1). react/vue中的key有什么作用?(key的内部原理是什么?)
2). 为什么遍历列表时,key最好不要用index?

  1. 虚拟DOM中key的作用:
      1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用。

      2). 详细的说: 当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】, 
                    随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

      ​        a. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
      ​              (1).若虚拟DOM中内容没变, 直接使用之前的真实DOM
      ​              (2).若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM

      ​        b. 旧虚拟DOM中未找到与新虚拟DOM相同的key
      ​              根据数据创建新的真实DOM,随后渲染到到页面
      ​        
        2. 用index作为key可能会引发的问题:

                  1. 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
                     会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

                        2. 如果结构中还包含输入类的DOM:
                    会产生错误DOM更新 ==> 界面有问题。
                        3. 注意!如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,
                              仅用于渲染列表用于展示,使用index作为key是没有问题的。

        3. 开发中如何选择key?:

                  1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
                  2. 如果确定只是简单的展示数据,用index也是可以的。

4. 生命周期

可以分成三个阶段:

  • 创建阶段
  • 更新阶段
  • 卸载阶段

创建阶段

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

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount

更新阶段

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

  • getDerivedStateFromProps 当state需要从props初始化时,使用
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate 查看更新前 state 对象的值
  • componentDidUpdate

卸载阶段

componentWillUnmount

此方法用于组件卸载前,清理一些定时器,或者取消订阅的网络请求等

一旦一个组件实例被卸载,其不会被再次挂载,而只可能是被重新创建

react生命周期(新)
react生命周期(新)

5. state 和 props 有什么区别?

一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是 state,外部参数就是props

区别

相同点

  • 两者都是 JavaScript 对象
  • 两者都是用于保存信息
  • props 和 state 都能触发渲染更新

不同点

  • props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
  • props 在组件内部是不可修改的,但 state 在组件内部可以进行修改
  • state 是多变的、可以修改

6. 调用setState/useState发生异步吗?

为了提高性能React将setState设置为批次更新,即是异步操作函数,将 setState() 视为请求而不是立即更新组件的命令。为了更好的感知性能,React 会延迟调用它,然后通过一次传递更新多个组件。React 并不会保证 state 的变更会立即生效。setState() 并不总是立即更新组件。它会批量推迟更新。这使得在调用 setState() 后立即读取 this.state 成为了隐患

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
constructor(props) {

super(props)

this.state = {

count:1,

}

}

this.setState({

count: this.state.count + 1

}, () => {

console.log(this.state.count);//2

console.log('加载完成')

});

useState

useState 返回的更新对象的方法是异步的,要在下次重绘才能获取新值,不要试图在更改状态之后立即获取状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//方法一 不确定是否有效
const [data, setData] = useState(0)
setData((prev) => prev + 1); // prev 是data 改变之前的值,return 返回的值会作为新状态覆盖data
//方法二
//先使用 useRef 进行存储数据,再使用 useEffect 监听数据变化,并进行更新。
import React, { useState, useEffect, useRef } from 'react';

const Index = () => {
const [info, setInfo] = useState()
const infoRef = useRef()
useEffect(() => {
infoRef.current = info
}, [info])
}
// 在之后需要使用 info 数据的地方只需要获取 infoRef.current 即可获取最新的 info 数据内容。

7. react事件机制

react自身实现了一套事件机制叫合成事件。合成事件是 React模拟原生 DOM事件所有能力的一个事件对象。

总结:

React 16上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM

React17注册的事件绑定容器结点,比如root

如果想要获得原生DOM事件,可以通过e.nativeEvent属性获取

React 自身实现了一套事件冒泡机制,阻止合成事件向上的冒泡,用e.stopPropagation()。阻止合成事件本级间的冒泡以及向上冒泡,用e.nativeEvent.stopImmediatePropagation()

react16事件执行顺序。先执行原生事件,再执行react事件。原生捕获->原生冒泡->react捕获->react冒泡

react17事件执行顺序。react捕获->原生捕获->原生冒泡->react冒泡

8. react构建组件的方式

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

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

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

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

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

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

9. React refs的理解?

ref 一种访问DOM节点的方式。

  1. 字符串形式的ref
1
2
3
<p ref='wenben'>文本</p>
console.log(this.refs.wenben)
// 已经过时了
  1. React.createRef()创建
1
2
3
this.myRef = React.createRef();
<div ref={this.myRef} />;
const node = this.myRef.current;
  1. useRef中的hooks

保存一个值,在整个生命周期中维持不变

1
2
3
4
5
6
7
8
9
function App(props) {
const myref = useRef()
const node = myref.current;
return (
<>
<div ref={myref}></div>
</>
)
}

10. 类组件和函数式组件的区别?

类组件,顾名思义,也就是通过使用ES6类的编写形式去编写组件,该类必须继承React.Component

如果想要访问父组件传递过来的参数,可通过this.props的方式去访问

在组件中必须实现render方法,在return中返回React对象

1
2
3
4
5
6
7
8
9
10
11
12
class Welcome extends React.Component {
constructor(props) {
super(props)
this.state = {
msg: 'hello 2005',
count: 1
}
}
render() {
return <h1>Hello, {this.props.name}</h1>
}
}

函数式组件:就是通过函数编写的形式去实现一个React组件,是React中定义组件最简单的方式

函数第一个参数为props用于接收父组件传递过来的参数

区别:

函数组件语法更短、更简单,这使得它更容易开发、理解和测试

而类组件也会因大量使用 this而让人感到困惑

11. 受控组件与非受控组件

受控组件的状态全程响应外部数据的变化,给input提供onchange事件,一旦检测到文本框内容有变化,马上执行onchange事件获取表单的内容。

非受控组件将真实数据存在 DOM 节点中,通过ref来获取。,ref属性接受一个回调函数,返回一个element节点 , 通过节点获取到数据 ref={(element)=>this.addressElement = element }

12. 高阶组件

高阶组件是一个函数:即接受一个或多个组件作为参数并且返回一个组件。

1
2
3
4
5
6
7
8
9
10
import React, { Component } from 'react';

export default (WrappedComponent) => {
return class EnhancedComponent extends Component {
// do something
render() {
return <WrappedComponent />;
}
}
}

通过对传入的原始组件 WrappedComponent 做一些你想要的操作(比如操作 props,提取 state,给原始组件包裹其他元素等),从而加工出想要的组件 EnhancedComponent

好处:高阶组件能够提高代码的复用性

13. 引入css的方式有哪几种?

  • 在组件内直接使用
  • 组件中引入 .css 文件
  • 组件中引入 .module.css 文件
  • CSS in JS

区别

  • 在组件内直接使用css该方式编写方便,容易能够根据状态修改样式属性,但是大量的演示编写容易导致代码混乱
  • 组件中引入 .css 文件符合我们日常的编写习惯,但是作用域是全局的,样式之间会层叠(样式污染
  • 引入.module.css 文件能够解决局部作用域问题。
  • 通过css in js 这种方法,可以满足大部分场景的应用,可以类似于预处理器一样样式嵌套、定义、修改状态等

至于使用react用哪种方案引入css,并没有一个绝对的答案,可以根据各自情况选择合适的方案

14. react组件间过度动画如何实现?

在日常开发中,页面切换时的转场动画是比较基础的一个场景,当一个组件在显示与消失过程中存在过渡动画,可以很好的增加用户的体验。在react中实现过渡动画效果会有很多种选择,如react-transition-groupreact-motionAnimated,以及原生的CSS都能完成切换动画

react中,react-transition-group是一种很好的解决方案,其可以导出3个组件:

  • CSSTransition:在前端开发中,结合 CSS 来完成过渡动画效果

  • SwitchTransition:两个组件显示和隐藏切换时,使用该组件

  • TransitionGroup:将多个动画组件包裹在其中,一般用于列表中元素的动画

15. redux的工作原理

redux可以对react组件做全局的状态管理。

三大原则

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

三大核心 store,action,reducer

store:数据state都放在 store中统一管理 (将state、action、reducer联系在一起的对象

reducer: 是个纯函数,根据传入的action,去返回新的state更新store中的数据

action:UI每一次状态的改变都会产生一个action

简易执行流程:组件的状态存放在store中,UI状态改变生成一个action,reducer接受到action然后进行处理产生新的state, 放到了store中,UI又从store中取得新状态。

API

  • createStore可以帮助创建 store
  • store.dispatch 帮助派发 action , action 会传递给 store
  • store.getState 这个方法可以帮助获取 store 里边所有的数据内容
  • store.subscrible 方法订阅 store 的改变,只要 store 发生改变, store.subscrible 这个函数接收的这个回调函数就会被执行

16. redux中间件

那么如果需要支持异步操作,或者支持错误处理、日志监控,这个过程需要用上中间件

其本质上一个函数,对store.dispatch方法进行了改造,在发出 Action和执行 Reducer这两步之间,添加了其他功能。

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const store = createStore(
reducer,
applyMiddleware(thunk, logger)
);
// 异步逻辑
const getHomeMultidataAction = () => {
return (dispatch,getState) => {
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));
})
}
}

17. 说说你对React Router的理解?

可以实现无刷新的条件下切换显示不同的页面。路由的本质就是页面的URL发生改变时,页面的显示结果可以根据URL的变化而变化,但是页面不会刷新。因此,可以通过前端路由可以实现单页(SPA)应用

组件:

Hooks

useRoutes() 使用路由表

useNavigate() 作用:返回一个函数用来实现编程式导航。

useParams() 返回当前匹配路由的params参数,类似于5.x中的match.params

useSearchParams() 用于读取和修改当前位置的 URL 中的查询字符串。返回一个包含两个值的数组,内容分别为:当前的seaech参数、更新search的函数。

useLocation() 获取当前 location 信息

18. React Router的模式是什么

hash 模式 对应HashRouter组件

history 模式 对应BrowserRouter组件

19. immutable 的理解?如何应用在react项目中?

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

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

也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免深拷贝把所有的节点都复制一遍带来的性能损耗,immutable使用了结构共享,即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其他节点则进行共享

使用Immutable对象最主要的库是immutable.js

react中的应用

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

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

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

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

主要优势:
1、节省CPU
避免深拷贝,复杂对象比较
2、节省内存
结构共享,复用已有结构

20. 说说React render方法的原理?在什么时候触发更新

  • render函数里面可以编写JSX,经过bable转化成createElement这种形式,用于生成虚拟DOM,最终转化成真实DOM
  • 在React 中,类组件只要执行了 setState 方法,就一定会触发 render 函数执行,函数组件使用useState更改状态不一定导致重新render比如 当数组的地址值不发生改变,就不会触发render。解决方法重新创建数组
  • 组件的props 改变了,不一定触发 render 函数的执行,但是如果 props 的值来自于父组件或者祖先组件的 state。

父组件发生了render子组件就一定会render

21.如何提高组件的渲染效率的?在React中如何避免不必要的render?

我们了解到render的触发时机,简单来讲就是类组件通过调用setState方法, 就会导致render,父组件一旦发生render渲染,子组件一定也会执行render渲染

父组件渲染导致子组件渲染,子组件并没有发生任何改变,这时候就可以从避免无谓的渲染,具体实现的方式有如下:

  • shouldComponentUpdate
  • PureComponent
  • React.memo

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

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

PureComponent和shouldComponentUpdate差不多。

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

23.React Jsx转换成真实DOM过程?

书写JSX代码 => Babel编译JSX =>生成React.createElement的js形式 => 生成虚拟Dom => render渲染生成真实Dom

24. React性能优化的手段?

React凭借virtual DOMdiff算法拥有高效的性能,但是某些情况下,性能明显可以进一步提高。

  • 避免不必要的render 通过shouldComponentUpdatePureComponentReact.memo useCallback、useMemo等
  • 避免使用内联函数 使用内联函数,则每次调用render函数时都会创建一个新的函数实例
  • 使用 React.Fragments 避免额外标记 使用空标签<>
  • 使用 Immutable shouldComponentUpdate时复杂对象比较 redux的reducer避免深拷贝浪费性能
  • 懒加载组件 路由懒加载就是加载首页时不加载其他组件 使用到了Suspenselazy组件
  • 利用debounce、throttle 避免重复回调 在搜索组件中,当 input 中内容修改时就触发搜索回调。
  • 服务端渲染 1 利于SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面 2 加速首屏加载,解决首屏白屏问题

25. usememo和useCallback的区别与应用场景

react性能的优化点在于:

  1. 调用setState,就会触发组件的重新渲染,无论前后的state是否不同
  2. 父组件更新,子组件也会自动的更新

useCallback和useMemo的参数跟useEffect一致,他们之间最大的区别有是useEffect会用于处理副作用,而前两个hooks不能。

use Memo 和 useCallback 接收的参数都是一样,第一个参数为回调 第二个参数为要依赖的数据

共同作用:
1.仅仅 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。

两者区别:
1.useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 如果你的页面上展示的数据是通过某个(某些)state计算得来的一个数据,那么你每次这个组件里面无关的state变化引起的重新渲染,都会去计算一下这个数据,这时候就需要用useMemo(()=>{}, [])去包裹你的计算的方法体,这样那些无关的state改变引起的渲染不会重新计算这个方法体,而是返回之前计算的结果,达到一种缓存的效果。
2.useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 父组件中需要传递函数到子组件,父组件每次更新都会重新声明内部的函数,导致传递给子组件的函数变化,子组件也会进行没有必要的更新,这种情况就可以利用 useCallback 处理传递给子组件的函数,避免每次父组件更新导致子组件更新的情况,因为只要useCallback 的依赖项没有发生变化,传递给子组件的函数始终都是缓存的同一个函数。

26、react-redux模型图

(1)所有的UI组件都应该包裹一个容器组件,他们是父子关系;

(2)容器组件是真正和redux打交道的,里面可以随意使用redux的api;负责处理业务逻辑,向UI组件传递参数。

(3)UI组件中不能使用任何redux的api;只负责渲染页面,没有逻辑功能。

(4)容器组件会传给UI组件:1.redux中保存的状态。 2.用于操作状态的方法;

(5)备注:容器给UI传递:状态、操作状态的方法,均通过props传递;