前端面试题react技术栈

在接触前端的过程中 我们知道有两种平时经常用到的技术栈,第一是vue2/vue3,另一种是react,个人而言的话,我觉得vue的话是比较简单的,毕竟是已经写死的一些API,我们拿过来直接使用就好了,基本上都会实现我们想要的功能,vue的UI框架也是蛮多的算是比较方便的一个技术栈,react的话,个人觉的还是比较不错的,我更倾向于react技术栈。

vue和react的区别。

共同点:

  1. 数据驱动试图。

  1. 组件化。

  1. virtua DOM

不同点:

  1. 核心思想不同。

  1. 写法存在差异。

  1. diff算法不同。

  1. 响应式原理不同。

  1. 源码差异

  1. ........等等。

对react的理解。

React,用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案,遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效,使用虚拟DOM来有效地操作DOM,遵循从高阶组件到低阶组件的单向数据流,帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面.

它的优势:

- 高效灵活

- 声明式的设计,简单使用

- 组件式开发,提高代码复用率

- 单向响应的数据流会比双向绑定的更安全,速度更快

对React事件机制的理解。

React基于浏览器的事件机制自身实现了一套事件机制,包括事件注册、事件的合成、事件冒泡、事件派发等,在React中这套事件机制被称之为合成事件;

合成事件是 React模拟原生 DOM事件所有能力的一个事件对象,即浏览器原生事件的跨浏览器包装器

执行顺序

React 所有事件都挂载在 document 对象上

当真实 DOM 元素触发事件,会冒泡到 document 对象后,再处理 React 事件

所以会先执行原生事件,然后处理 React 事件

最后真正执行 document 上挂载的事件

总结

React 上注册的事件最终会绑定在document这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)

React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation()无效的原因。

React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback

React 有一套自己的合成事件 SyntheticEvent

react的试图更新。

因为react是一个单向数据流,数据驱动UI,所以在react中有几种试图更新的方法。

方法一:setState

setState 是众所周知最常见的更新视图的方式了,只需要给个初始 state,需要更新的时候调用 this.setState, 组件会经历 shoudlComponentUpdate => componentWillUpdate => render => componentDidUpdate 四个过程,如果没有在 shouldComponentUpdate 中手动 return false 的情况下,那么 ui 此时就会更新。

需要注意的是,尽管 this.setState({}) 参数为空对象时,react 一样会更新,调用上面的四个生命周期,只是 ui 视图不有会变化。

当然这些都要基于 class 组件,因为 setState 是 Component 原型上的方法,必须得是继承 Component的组件才能调用 this.setState。

方法二:useState();

通过useState来实现状态值的操作 只能在React函数中调用 通过传入 useState 参数后返回一个带有默认状态和改变状态函数的数组。通过传入新状态给函数来改变原本的状态值 经常和useEffect 一起使用。

方法三:forceUpdate

调用 forceUpdate 后当前组件会跳过 shouldComponentUpdate 这个钩子,尽管手动 return false,也一样会更新,单词字面理解也是强制更新,但是需要注意的是,子组件的更新还是会受到 shouldComponentUpdate 控制。

forceUpdate的使用场景一般是视图更新来源于其他非 state、props的数据,比如绑定在 组件实例上的属性,或者是直接修改 this.state.xxx = xxx ,然后调用 this.forceUpdate(),不过官方不建议这么做,正常情况下应该避免使用 forceUpdate,通过 state 或者 props 去更新视图。

方法四:原生操作DOM

在 react 中难免会有操作原生 dom 的时候,比如你用了第三方比如 jquery 这种需要获取 dom 的库,或者你需要实现一个拖拽,双指缩放的组件,对于这些,你也许可以用操作的 dom 的方式绕过 react 的 setState 再到 dom diff 一系列的计算,直接更新 dom ,提高些许性能。

以上三种更新 ui 的方式,需要注意的是,当通过 setState 更新改变颜色变红,在点击原生 dom 按钮改变颜色变绿,这时在点击 setState 的按钮,发现视图不更新,但是还是走了 render 函数,这是因为点击原生 dom 按钮前, this.state.backgroundColor 值是 red,原生操作是直接改变的 dom style,在点回 setState 按钮,其实 this.state.backgroundColor的值还是 red, 虽然走了更新逻辑,但是由于 react 的 新老 virtual dom 对比的时候,发现颜色并没改变, 导致没有 patch 到 ui 上。

方法五:dispatch action

上面的几种方式都是通过 react 本身自带的 state 去更新 ui, 当项目中使用 redux 时, 我们一般会通过 dispach 一个 action, 改变 store,然后更新 ui,dispatch action 是通过改变 props 去驱动视图的,大家在使用的时候有没有想过为什么 this.props.dispatch({ type: 'xxx' }), 就可以驱动 ui 视图更新呢?

这边简单的讲下,当我们dispatch 一个 action 的时候, 调用的其实是 store.dispatch,这个都没问题,store.dispatch 会去跑一遍所有注册在 createStore 中的 reducer, 找到对应的 type 更新数据,返回一个新的 state。

而我们的组件想拿到 store 的数据必须通过 connect(mapStateToProps, mapDispatchToProps)(App) 像这样,react-redux 中的 Connect 组件会在 componengDidMount 的时候去 调用 一个 trySubscribe 的方法,其内部调用 store.subscribe 去订阅一个 handleChange 的方法。

所以当你 dispatch action 的时候,就会触发 Connect 组件中的方法, Connect 组件中也维护了一个叫做 storeState 的 state,每次拿到新的 sotre 就去调用 setState, 触发 render 函数, render 函数会根据你 connect 中传入的 mapStateToProps, mapDispatchToProps,包括可选参数 mergeProps, 去做一个 props 的合并动作, 最终在 Connect 组件内部 return 出来一个 createElement(WrappedComponent,this.mergedProps) 这样的东西,而 createElement 第二个参数就是你组件的 props, 那么每次 props 变了,就会驱动视图的更新。

总结
  1. 通过调用 this.setState 改变 state 驱动视图。

  1. 通过使用hooks更新视图。

  1. 通过调用 this.forceUpdate 强制更新视图。

  1. 通过操作原生 dom 更新视图。

  1. 通过改变 props 驱动视图(redux 或者 修改父子组件传递 props )。

函数组件 hooks

1. 有了 Hooks 以后, 不能再把函数组件**称之为无状态组件了, 因为 Hooks可以为函数组织间提供了状态

2. Hooks 是一些可以让你在函数组件里"钩入状态及生命周期等特性

3. 函数组件只能做展示 ,因为没有状态,没有生命等等,所以能做的事儿只能展示

4. hook只能用于函数组件 ,把它的缺点补全了 ,可以理解为听过Hooks 为函数组件钩入 class 组件的特性useEffect为其中的代表

5. react 16.8 的时候出现了hook

6. 纯函数 render 函数组件都是纯函数 , 性能高 相同的参数一定返回相同的值

常用的hook

1 useState() 更改state

2 useEffect() 面试必问。return ()=>{} 当作生命周期使用

3 useCallback(()=>{},[]) 缓存函数

4 useMemo(()=> 必须有renturn,[]) 缓存数据

5 useRef. 面试几率高

6 useContext() 和context一起使用 来做发送和接收 组件通信

7 useReducer() 了解 react

8 useParams 动态路由传参

9 useLoaction 路由信息

10 useNavigate react-router-dom v6 编程式路由

11 useSelector

12 useDispatch redux

antd 就

message.useMessage()

Form.useForm

useState 函数组件的状态

通过useState来实现状态值的操作 只能在React函数中调用 通过传入 useState 参数后返回一个带有默认状态和改变状态函数的数组。通过传入新状态给函数来改变原本的状态值 经常和useEffect 一起使用,

useEffect

让函数组件模拟生命周期

1. 默认函数组件没有生命周期

2. 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期

3. 通过Effect hook把生命周期“钩”到纯函数中

4. 第一个参数 callback 执行的逻辑

()=>{

//业务逻辑

return ()=>{ 依赖改变 , 组件销毁执行}

}

// 第二参数

1 不写 ,相当于更新阶段 + 挂载阶段 默认执行一次,更新的时候也执行

2 [] 只创建执行一次 相当于挂载阶段生命周期

3 [依赖1,依赖2] 只要依赖发生改变就执行callback 回调 类似于 vue 监听

useMemo

计算属性 useMemo得到的返回的值,是返回的结果

useMemo是针对一个函数,是否多次执行 主要用来解决使用React hooks产生的无用渲染的性能问题

在方法函数,由于不能使用shouldComponentUpdate处理性能问题,react hooks新增了useMemo

useMemo使用

如果useMemo(fn, arr)第二个参数匹配,并且其值发生改变,才会多次执行执行,否则只执行一次,如果为空数组[],fn只执行一次

useCallback

首先它是一个高性能函数 也是一个闭包

函数式组件中,使用useCallback对函数进行缓存(被外层函数包裹,相当于闭包),组件再次更新时(函数重新执行)会根据依赖是否变化决定选用缓存函数【之前生成的函数】还是新函数【新生成的上下文】。

一般会在嵌套组件中,与函数式组件的memo和类组件的PureComponent一起使用【会对传入props参数逐个进行浅比较决定是否需要更新】,来提高页面性能。

UseRef

平时的时候 我们都是在div中渲染组件 但是这个时候 我们就有一个需要注意的点 那就是 当我们更新状态的时候,会导致组件重新渲染,所以我的组件第一次渲染,声明并改变了状态,然后又触发渲染,又重新声明改变状态,这会导致我的组件状态进入无限循环,在无限循环中建立自己…, 这样就会大大降低性能而且也会提高内存占用,所以我们这个时候就会用 useref 因为`useRef的值变化是不受渲染所影响的`,`不会重新更新

useParams 动态路由传参

const params = useParams()

navLink to="/topic/123"

path:"/topic/:id"

console.log(params,'params')

编程式路由 useNavigate

import {useNavigatet} from 'react-router-dom'

function App(){

const native = useNavigate()

}

navigate('地址') push,模式

navigate("/地址",{replace:true}) 没有后退记录,替换模式

navigate(1,-1,0) 前进 后退 刷新

路由信息 useLocation

1 理由路由信息来做导航守卫

2 location.pathname 来登录才能访问

//导航守卫

function App(){

const location = useLocation();

const navigate = useNavigate();

// 登录才能访问

useEffect(() => {

// console.log("进入到了:" + location.pathname);

if (isLogin) {

console.log("已经登录哦了");

} else {

navigate("/login");

}

// 导航守卫

}, [location.pathname]);

}

// 首页 关于 详情页面

/**

首页随意访问

其它的三个页面必须登录才能访问

本地存储存储状态 来判断是否登录

React Diff算法

diff算法**, 是 虚拟 DOM 产生的一个概念, 用来计算出虚拟DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面,从而**提高了页面渲染效率**。

调用 React 的 render() 方法,会创建一棵由 React 元素组成的树。在下一次 state 或 props 更新时,相同的 render() 方法会返回一棵不同的树。React 需要基于这两棵树之间的差别来更新 UI。

这个算法问题有一些通用的解决方案,即生成将一棵树转换成另一棵树的最小操作数。 然而,即使在最前沿的算法中,该算法的复杂程度为 O(n 3 ),其中 n 是树中元素的数量。

如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围。这个开销实在是太过高昂。

为了降低算法复杂度,react通过一些限制策略,提出了一套O(n) 的启发式算法。

谈谈JSX的编译原理

它在[React](https://so.csdn.net/so/search?q=React&spm=1001.2101.3001.7020)扮演着描述了UI界面、关联渲染逻辑的重要角色

JSX=javascript xml就是Javascript和XML结合的一种格式。是 JavaScript 的语法扩展

jsx 在结构上跟HTML很像,但是它的本质还是js语言。

babel工具 编译jsx语法

JSX 执行更快,因为它在编译为 JavaScript 代码后进行了优化。

它是类型安全的,在编译过程中就能发现错误。

使用 JSX 编写模板更加简单快速。

setSate

setSate接受两个参数,一个是state(值),另一个是[回调函数](https://so.csdn.net/so/search?q=回调函数&spm=1001.2101.3001.7020) 它是异步的

回调函数是这个值完成的时候调用这个回调函数

redux

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

遵循三大基本原则:单一数据源、state 是只读的、使用纯函数来执行修改

是一种共享的数据状态 三部分 action store reducer 总的来说 redux就是和组件通信有关的

不使用redux只能使用父子组件通讯 状态提升等react自带机制 处理非父子通信会很乏力 组件之间的数据流混乱 容易出现bug

使用redux 集中存储和管理应用状态 处理组件通讯问题 可以无视组件之间的层级关系 简化大型复杂应用中的组件之间的通讯问题 数据流清晰 易于等位bug

redux 通常和 react-redux 还有context 一起使用

redux的工作流:

通过dispatch 派发action方法给store, store接收到action方法之后,给reducers传入state以及action方法,reducers接收到state以及action方法之后,会修改其state,返回一个新的state值给store,

redux-中间件

应用派发任务(dispatch过程中,我想做一些上下文操作,给我提供个上下文环境

中间件操作的是dispatch这一步骤 ,执行异步 vuex actions

1 redux-logger 打印日志 ()

2 redux-thunk \

//首先store页面引入thunk使用

3 reudx-promise

dispatch(new Promise((resolve,reject)=>{}))

4 redux-saga

react-redux

react+redux 和 redux 一起在react项目里面使用

1 redux 更新问题 (并没有跟react同步)

2 引入到问题 使用起来有点麻烦每次都需要引入

npm i react-redux --save

生产者 Provider

需要main.tsx里面引入

import {Provider} from "react-redux"

Provider store={store}

App

消费者 connect()

redux持久化存储

一般是指页面刷新后,数据仍然能够保持原来的状态。

一般在前端当中,数据持久化,可以通过将数据存储到localstorage或Cookie中存起来,用到的时

候直接从本地存储中获取数据。而redux-persist是把redux中的数据在localstorage中存起来,起到

持久化的效果。

组件通信

一:父传子,子组件通过props接收。

二:自定义组件,来当作组件之间的桥梁进行传参 通讯。

三:redux持久化存储。

四:context。Context将数据提供在父组件上面,然后所有次组件的后代组件都可以共享状态和方法

react-router与react-router-dom

React-router:实现了路由的核心功能

**React-router-dom:基于React-router,加入了一些在浏览器运行下的一些功能,在使用React的大多数情况下,我们会想要通过操作dom来控制路由,例如点击一个-按钮完成跳转,这种时候使用React-router-dom就比较方便。 而且安装也是简单的

判断数组的类型

1.通过Array.isArray()判断

2.通过Object.prototype.toString.call()判断

3.instan of

promise

Promise是用来让代码按顺序执行的刚有的时候是为了解决回调地狱和异步请求的问题,他有两个参数,四个方法,resolve ,reject 这两个东西一个是成功调用的 一个是失败调用的,成功的时候会执行.then 失败的时候会执行.catch 在.then里边return 就能继续.then,下一个.then的参数就是上一个.then return 的值 就是利用这个特性来保证代码可以按我们想要的顺序去执行,这里说一下Promise的状态,他一共有三种,等待--成功--失败,在同一个时间戳内同一个Promise有且只有一种状态并且这个状态不可逆. 当Promise.then执行的时候实质上是new了一个新的Promise对象(Promise.prototype.then),就有了一个新的状态. Promise一共有四个常用方法 .then .catch .all .race

.then 和 .catch刚才介绍过就不说了-----Promise.race 传一个数组,数组里的一堆函数有一个成功了,他的状态就是成功的,(可能用于抢票,具体我没研究过)------Promise.all 传入一个数组,数组里的一堆函数必须全部执行完成才会成功

很多时候Promise会结合async--await使用,比如向A、B两个接口请求数据,用A接口请求到的数据当参数传给B接口,同时要求返回A 和 B共同请求到的数据拼接后的结果(应用场景可说可不说 扫码登录),await不能单独使用,async可以,声明一个async函数可以在函数体内一直await等待代码执行完后会执行下一个行代码,能保证代码按顺序执行,需要注意的一点,await只等待同步和微任务,宏任务依旧会添加到宏任务队列去等待,不会在主线程里等待执行----有一个缺点需要注意一下 因为js是单线程,await会等待代码执行就意味着会阻塞程序运行,影响性能,如果这个时候需要渲染数据或者做计算可以考虑使用Web Worker 新建js一个子线程去进行数据和运算的操作,或者在js的子线程里去请求数据,因为js的子线程不能操作页面的dom元素.async函数是没有返回值的,await函数的返回值是一个Promise对象,.then调用就OK

js和ts的区别

首先,它们都是脚本语言。JavaScript 是轻量级的解释性脚本语言,可嵌入到 HTML 页面中,在浏览器端执行。而TypeScript 是JavaScript 的超集(ts是微软开发的开源编程语言),即包含JavaScript 的所有元素,能运行JavaScript 的代码,并扩展了JavaScript 的语法。(ts包含了js的库和函数,ts上可以写任何的js,调用任何的js库,可以在ts中使用原生js语法)。相比于JavaScript ,它还增加了静态类型、类、模块、接口和类型注解方面的功能,更易于大项目的开发。

1、TypeScript 引入了 JavaScript 中没有的“类”概念

2、TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。

3、js没有重载概念,ts有可以重载

4、ts增加了接口interface、泛型、类、类的多态、继承等

js有的类型:boolean类型、number类型、string类型、array类型、undefined、null

ts新增的类型:tuple类型(元组类型)、enum类型(枚举类型)、any类型(任意类型)

void类型(没有任何类型)表示定义方法没有返回值

never类型:是其他类型(包括null和undefined)的子类型,代表从不会出现的值这意味着声明never变量只能被never类型所赋值

js变量是没有类型的,即age=18,age可以是任何类型的,可以继续给age赋值为age=”aaa”

Ts有明确的类型(即:变量名:number(数值类型)) eg:let age: number = 18

.类组件和函数组件的区别

主要区别是:

类组件有生命周期,函数组件没有;

类组件依赖class创建,函数组件通过return返回创建;

类组件有状态存储,函数组件需要依赖Hook完成状态存储;

类组件中有this指向问题需要特别注意,函数组件则没有this;

类组件消耗大,需要创建实例,且不能销毁;函数组件消耗少,得到结果就销毁

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

2. 类组件也会因大量使用 this 而让人感到困惑。使用功能组件可以很容易地避免这种缺点,保持代码整洁。

3. 当业务逻辑复杂,用类组件更易于我们维护,也相应降低了开发成本。

4. 函数组件多用于简单功能模块封装,便于复用

跨域问题及解决方法

跨域不一定会有跨域问题。

因为跨域问题是浏览器对于ajax请求的一种安全限制:一个页面发起的ajax请求,只能是于当前页同域名的路径,这能有效的阻止跨站攻击。跨域问题 是针对ajax的一种限制。但是这却给我们的开发带来了不便,而且在实际生产环境中,肯定会有很多台服务器之间交互,地址和端口都可能不同

解决方法:

CORS规范化的跨域请求解决方案,安全可靠。CORS是一个W3C标准,全称是"跨域资源共享" 它允许浏览器向跨源服务器,发出[`XMLHttpRequest`](http://www.ruanyifeng.com/blog/2012/09/xmlhttprequest_level_2.html)请求,从而克服了AJAX只能[同源](http://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html)使用的限制

Access-Control-Allow-Origin: http://manage.leyou.com

Access-Control-Allow-Origin:可接受的域,是一个具体域名或者*,代表任意Access-Control-Allow-Credentials: true

Access-Control-Allow-Credentials:是否允许携带cookie,默认情况下,cors不会携带cookie,除非这个值是true

前端性能优化

1.浅层渲染:比较基元和对象引用的开销比更新组件视图要低,因此,查找状态和 props 值的变化会比不必要的更新更快

2.使用 React.memo 进行组件记忆:如果输入 props 相同则跳过组件渲染,从而提升组件性能 它会记忆上次某个输入 prop 的执行输出并提升应用性能

3.使用 shouldComponentUpdate 生命周期事件:这是在重新渲染组件之前触发的其中一个生命周期事件。可以利用此事件来决定何时需要重新渲染组件

4.懒加载:Suspense 和 lazy

5.使用 React Fragments 避免额外标记

6.使用纯函数

7.css样式使用外联

8.减少React中的条件渲染

1、减少请求数量(1.图片:雪碧图,Base64,使用字体图标来代替图片。2.减少重定向(当页面发生了重定向,就会延迟整个HTML文档的传输。在HTML文档到达之前,页面中不会呈现任何东西,也没有任何组件会被下载,降低了用户体验,如果使用使用301永久重定向)3.不使用css@import 会造成额外的请求,4.避免使用空的src 和 href)

2、减少重绘回流

  1. 使用性能好的API(1.用对相应的选择器。2.使用使用requestAnimationFrame来替代setTimeout和setInterva,希望在每一帧开始的时候对页面进行更改,requestAnimationFrame就是告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,使用setTimeout或者setInterval来触发更新页面的函数,该函数可能在一帧的中间或者结束的时间点上调用,进而导致该帧后面需要进行的事情没有完成,引发丢帧)

宏任务和微任务

宏任务是宿主 浏览器发起的 后执行 \1. script (可以理解为外层同步代码)2. setTimeout/setInterval 3. promise().then 后运行

微任务是js引擎 \1. Promise2. MutaionObserver3. Object.observe(已废弃;Proxy 对象替代)4. process.nextTick 先运行

执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。

请求大量数据 页面出现卡顿

从前端考虑的话 分批加载,懒加载。运用worker,

worker因为js是单线程运行的,在遇到一些需要处理大量数据的js时,可能会阻塞页面的加载,造成页面的假死。这时我们可以使用worker来开辟一个独立于主线程的子线程来进行哪些大量运算。这样就不会造成页面卡死。

它有两个参数:

aURL(必须)是一个DOMString 表示worker 将执行的脚本的URL。它必须遵守同源策略。

options (可选)它的一个作用就是指定 Worker 的名称,用来区分多个 Worker 线程

猜你喜欢

转载自blog.csdn.net/weixin_42602736/article/details/128885624
今日推荐