0


前端面试八股文(详细版)—中

上篇详细讲解了 HTML 、CSS 、JavaScript 、计算机网络知识等方面的内容,本文将详细讲解 React 、git 、webpack等内容,预祝各位成功上岸!

React框架


使用 react 脚手架搭建项目

npx create-react-app app-name

class 和 function 的区别

function 定义的组件中可以使用 hooks 来设置状态和模拟生命周期,没有 this 指向问题。代码更简单易懂,全是函数式组件,非常好理解。

现在用的react的是17版本

17 和 16 有啥区别?

等同react16.8之后有什么变化?

详细介绍:
https://segmentfault.com/a/1190000040453119?utm_source=sf-similar-article

常用UI库

  • antd组件库 版本不同写法不一样 Ant Design - 一套企业级 UI 设计语言和 React 组件库
  • umi antd pro

添加富文本

dangerouslySetInnerHTML={{ __html: this.state.newHtml }}

组件绑定和js原生绑定事件哪个先执行?

先执行js原生绑定事件,再执行合成事件,因为合成事件是发生在冒泡阶段

React中的合成事件是什么?

合成事件是围绕浏览器原生事件充当跨浏览器包装器的对象。它们将不同浏览器的行为合并为一个 API。这样做是为了确保事件在不同浏览器中显示一致的属性;

react中组件分为那俩种?

  • 类组件
  • 函数组件

hooks

hooks的优点

代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护

  useState,定义局部状态
  useEffect,模拟生命周期
  useContext,获取上下文
  useCallback,缓存一个function
  useMemo,缓存一个值
  useReducer,又一种存储数据的形式

 当组件的属性或者数据改变的时候组件会重新刷新(就是组件的 function 会重新被执行)

useState,定义局部状态

可以在 function 定义的组件中定义局部状态数据,当数据改变的时候组件会重新渲染

返回一个数组,数组的第一项是一个变量名,第二项是改变数据的方法。需要注意的是参数二

改变数据的方法有两种使用方式:

    1. 直接是一个值
    1. 可以设置一个 function,function 中默认传递一个 state 状态数据的上一次的值,function 的返回值会作为 state 数据的最新值。建议使用这种方式改变 state 数据

useEffect,模拟生命周期

可以在 function 定义的组件中模拟组件的生命周期钩子函数

参数一 是一个回调函数 参数一 可以返回一个function,这个返回的方法在组件销毁的时候执行

参数二 是一个依赖数组。分为三种形式

    1. 参数二不存在,那么每一次组件更新都会执行回调函数;
    1. 参数二为空数组,那么只有在初始化的时候执行一次;
    1. 参数二中的数组存在数据,那么数组中的每一项改变的时候都会执行回调函数;

useMemo,缓存一个值

作用是缓存一个值,它接收两个参数,参数一是一个被缓存的值,参数二是一个依赖项;

useCallback

作用是缓存一个 function,它接收两个参数,参数一是一个回调函数,参数二是一个依赖项,当参数二中的依赖数组不发生改变的时候,function 不会重新被定义;

useReducer

作用的作用是做一个数据管理,它是基于单向数据流的。

useReducer 可以理解为简化版的 redux

接收两个参数:参数一是一个 function,参数二是一个初始值

返回一个数组:第一项为 state 数据,第二项为 dispatch 方法

如果要改变数据只能通过 dispatch 派发一个 action 实现

memo

memo 是一个 function,接收一个组件作为参数,返回一个新的组件。作用是对组件做缓存,当组件接收到的属性不发生改变的时候组件不会重新渲染

memo 这个 API 在开发的时候建议使用,便于组件性能优化

memo 的作用是对组件做缓存,组件的属性做浅比较。当属性没有改变的时候组件不更新。作用是便于组件做性能优化和 pureComponent 一样的效果

Component 和 PureComponent 区别

Component 做的是深比较(每一层都比较)

PureComponent 做的是浅比较(只比较第一次)

相对来说 PureComponent 组件的性能更好

如果是普通component,当组件接收到新的props时,render都会重新渲染,如果是pureComponent,那么当传入新的props时会跟旧的props进行一个浅比较,如果没有变化,那么render函数不会重新渲染。

useRef

作用是获取 react 组件中的 dom 元素

useMemo 和 useCallback 的区别

useMemo 缓存一个值

useCallback 是缓存一个 function

他们的第二个参数是依赖数组,当依赖数组中的数据改变的时候,被缓存的值会重新更新

useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。 所以 useCallback 常用记忆事件函数,生成记忆后的事件函数并传递给子组件使用。而 useMemo 更适合经过函数计算得到一个确定的值,比如记忆组件。

hooks注意事项:

React 官方的文档里有讲过,不能在条件/循环里使用hooks,只能写在函数式组件顶部/自定义hooks中,他有顺序问题;

什么是自定义hooks

自己定义一个useXxx方法。自定义hooks,可以对业务逻辑代码进行剥离封装

为了界面更加美观。获取浏览器窗口的尺寸是一个经常使用的功能,这样经常使用的功能,就可以封装成一个自定义Hooks函数。
新建一个文件Example9.js,然后编写一个 useWinSize,编写时我们会用到useState、useEffect和useCallback所以先用import进行引入
import React, { useState ,useEffect ,useCallback } from 'react';
然后编写函数,函数中先用 useState 设置size状态,然后编写一个每次修改状态的方法onResize,这个方法使用useCallback,目的是为了缓存方法(useMemo 是为了缓存变量)。
然后在第一次进入方法时用useEffect来注册resize监听时间。为了防止一直监听所以在方法移除时,使用 return 的方式移除监听。
最后返回 size 变量就可以了。
https://zhuanlan.zhihu.com/p/264304340

class

class组件的生命周期:

  1. 初始化阶段constructor(初始化的时候执行设置属性和状态数据)
  2. 挂载阶段componentWillmount(挂载之前执行)【重要】componentDidMount(挂载完成之后执行):在这个生命周期钩子函数中执行数据获取操作,调接口取数据
  3. 更新阶段 componentWillReceiveProps(接收到新的属性时执行)【重要】shouldComponentUpdate(判断组件是否需要更新):这个方法常常用在组件性能优化的时候使用这个方法接收两个参数(新的属性和新的状态),返回一个bool值。这个方法常常用在组件性能优化的时候使用 componentWillUpdate(组件将要更新) componentDIdUpdate(组件更新完成) shouldComponentUpdate(np, ns) { // 接受两个参数 // 参数一:最新的属性信息 属性二:最新的状态数据 console.log(np, ns); // if (this.state.count === ns.count) { return false } console.log("shouldComponentUpdate---组件是否需要更新"); // 很重要 需要返回一个布尔值 // true 后续更新阶段的钩子函数会执行 // false 后续更新阶段的钩子函数不会执行 页面不会重新渲染 // 这个方法在组件性能优化的时候使用,我们可以自行判断是否需要在页面上面展示,如果不需要可以返回false return true }
  4. 销毁阶段 componentWillUnmount(组件销毁的时候执行)

16.3之后删除了三个带will的生命周期钩子函数

  • componentWillMount 将要挂载
  • componentWillReceiveProps 将要接收新的属性
  • componentWillUpdate 将要更新

新增生命周期函数:

getDerivedStateFromProps 组件接收到新的属性时触发 ,根据当前的 props 来更新组件的 state getSnapshotBeforeUpdate 组件更新之前触发 ,保证读取的状态和componentDidUpdate中一致

useEffect和class的对应关系

useEffect 可以在 function 定义的组件中模拟组件的生命周期钩子函数,他接收两个参数 参数一 是一个回调函数 参数一 可以返回一个 function,这个返回的方法在组件销毁的时候执行

参数二 是一个依赖数组。分为三种形式

componentDidMount,useEffect 参数二为空数组,那么只有在初始化的时候执行一次 挂载完成

componentDidUpdate,useEffect 参数二中的数组存在数据,那么数组中的每一项改变的时候都会执行回调函数 更新完成

componentWillUnmount,useEffect 参数二不存在,那么每一次组件更新都会执行回调函数 将要卸载

function Demo() {
...
React.useEffect(()=>{
  return () => {
    console.log('组件销毁的时候执行')
  }
}, [])
...
}

为什么需要在组件挂载成功之后获取数据?因为数据获取的时候可能是异步的,在组件没有挂载完成之前数据获取完了,就会导致组件更新不完整。为了保证组件的完全更新,一般都在组件挂载完成之后再获取数据;

嵌套组件生命周期:

会执行父组件的将要挂载和render方法,当执行render的时候开始解析所有的子组件。当子组件都挂载完成之后会执行父组件的挂载完成。

this指向

class定义的组件中this指向问题?为什么要改变this执行?如何做?

因为class定义的组件中在初始化的时候this是指向当前实例的,当事件调用的时候,this已经变为了全局值,在react中为null。为了解决这个问题需要在class定义的组件中改变this指向。我们可以使用三种方式:

  1. 在constructor初始化的时候为function绑定this(建议使用,因为这种方式只在初始化的时候执行一次)
  2. 在调用赋值的时候使用箭头函数
  3. 在事件赋值的时候直接bind一下this

因为在render的时候,当前上下问的this是当前组件,但是执行事件响应的时候this已经不存在了,

所以需要为class定义的组件绑定一下this

setState

在代码中调用 setState 函数之后,React 会将传入的参数对象与组件当前的状态合并,然后触发所谓的调和过程(Reconciliation)。经过这个调和过程,React 就开始重新渲染整个 UI 界面

改变数据,通过方法setState,setState是异步的,可以接收两个参数,参数一 表示改变之后的新数据;参数二 表示数据改变之后的回调函数,数据改变成功之后执行,一般情况下都不需要参数二。可以再回调函数中获取改变之后的最新数据

setState是异步的,你在什么时候遇到过坑?

  • setState是异步的,导致在setState之后获取refs会是上一次的值。
  • 放在setState中的第二个回调函数中就可以解决这个问题。

setState是同步的还是异步的?

  • 看具体情况:
  • 首先如果直接在setState后面获取state的值是获取不到的,在原生环境中是异步的。
  • 在异步请求(ajax中)或者setInterval,setTimeout,setState就是同步更新的。

执行两次setState,render几次,会不会立即触发

只执行一次,不会立即触发,因为react中有批处理机制

React会把setState的调用合并为一个来执行,也就是说,当执行setState的时候,state中的数据并不会马上更新,会按照先进先出,按顺序进行执行,但是在 Ajax、setTimeout 等异步方法中,每 setState 一次,就会 render 一次。

调用setState之后发生了什么?

  • 调用setState之后会将传入的参数与当前的状态合并,也就是真实dom和虚拟dom的同步过程,就是调和过程。
  • 经过调和过程,react会以高效的方式根据最新的数据状态构建一颗新的dom树,然后重新渲染页面。
  • 在react构建新的dom树的过程中,会根据新旧dom的差异进行局部渲染,按需更新

为什么直接修改this.state无效

setState本质是通过一个队列机制实现state更新的。 执行setState时,会将需要更新的state放入状态队列,而不会立刻更新state,队列机制可以批量更新state。 如果不通过setState而直接修改this.state,那么这个state不会放入状态队列中,下次调用setState时对状态队列进行合并时,会忽略之前直接被修改的state,这样我们就无法合并了,而且实际也没有把你想要的state更新上去。

render方法

class定义的组件都需要一个render方法,这个方法返回一个当前组件展示的dom数据,这个方法再每一次数据或者属性改变的时候会重新执行

  • class定义组件需要继承Component如果使用了extends实现了继承,那么在constructor的第一行,一定要显示调用一下super(),super()表示父类的构造函数
  • 每一个class定义的组件需要一个render方法,这个方法需要一个返回值,返回的是当前组件的内容
  • 每一次state数据或者属性改变的时候组件都会执行这个render方法
  • 不能再render方法中改变state数据

React 中 render() 的目的

每个React组件强制要求必须有一个 **render()**。它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 、`` 等。此函数必须保持纯净,即必须每次调用时都返回相同的结果。

state 和 props 区别是啥?

  • state 是组件自己管理数据,控制自己的状态,可变;

  • props 是外部传入的数据参数,不可变;

  • 没有state的叫做无状态组件,有state的叫做有状态组件;

  • 多用 props,少用 state,也就是多写无状态组件。

高阶组件

高阶组件就是把组件当参数传递给你一个 function,返回一个新的组件。在新的组件中会添加一些其他的属性和方法。常见的高级组件有 connect 和 withRouter

connect 可以把 redux 中的 state 和 dispatch 映射到组件的属性上

withRouter 可以为组件添加路由相关的属性信息

- 高阶组件就是一个没有副作用的纯函数。
- 高阶组件是把一个组件当做参数,返回值为新组件的函数。
- 高阶组件实际上就是高阶函数,如 map、forEach
- 常用的高阶组件:withRouter(路由中的),connect(react-redux 中的)

高阶组件的用处:

  • 代码重用,逻辑和引导抽象

  • 渲染劫持

  • 状态抽象和控制

  • Props 控制

什么是纯函数?

  • 返回一个新值,并且没有任何副作用。

  • 1.相同的输入永远返回相同的输出,2.不修改函数的输入值,3.不依赖外部环境状态。4.无任何副作用。

reducer 是纯函数吗?为什么?

  • reducer 必须是一个纯函数,Redux 只通过比较新旧两个对象的存储位置来比较新旧两个对象是否相同。如果你在 reducer 内部直接修改旧的 state 对象的属性值,那么新的 state 和旧的 state 将都指向同一个对象。因此 Redux 认为没有任何改变,返回的 state 将为旧的 state。

受控组件和非受控组件

主要是表单的 value 是否受 state 控制

受控组件:受控组件是必须要有 value;input、textarea、select 获取 value

非受控组件:通过 dom 元素获取表单的值 type="file"

受控组件就是为某个 form 表单组件添加value属性;非受控组件就是没有添加value属性的组件

react 中常用的插件有哪些?

路由、styled-components、react-redux、umi 等等

组件之间传参

父传子使用 props 属性 ,父传子将父组件中的属性传入子组件,子组件可以用 props 直接接收并使用。

子传父使用方法调用

非相关组件之间使用 context 上下文或者 redux 状态管理插件

通过 createContext 创建一个 context 实例,它上面有一个属性 Provider 数据提供者,可以为他设置 value 属性。使用 Provider 组件把所有的组件包含起来,内部的组件可以使用 value 中提供的数据。在组件内部使用 useContext 获取数据值;

为什么循环的时候需要加 key

简答:

key 的作用是提高 diff 算法节点查找的速度。相当于为每一个节点添加一个唯一的主键,能够快速的判断当前节点是否需要更新;

详解:

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

  • key 属性的使用,它涉及到 diff 算法中同级节点的对比策略,当我们指定 key 值时,key 值会作为当前组件的 id,diff 算法会根据这个 id 来进行匹配。如果遍历新的 dom 结构时,发现组件的 id 在旧的 dom 结构中存在,那么 react 会认为当前组件只是位置发生了变化,因此不会将旧的组件销毁重新创建,只会改变当前组件的位置,然后再检查组件的属性有没有发生变化,然后选择保留或修改当前组件的属性。

  • 用 key 是为了区分在前后两次渲染中元素的对应关系,防止发生不必要的更新操作。

组件如何做性能优化

1. 使用 memo 或者 PureComponent 关键词进行优化;

2. 为循环添加 key;

3. 使用 useCallback 对 function 做优化;

4. 优化组件的数据,使用 redux 对一些全局数据做同一的管理;

5. 如果是 class 定义的组件当更新的数据不需要展示的时候可以在 shouldComponentUpdate 中做处理;
- function定义的组件可以使用memo进行缓存操作,在组件内部使用useMemo和useCallback进行优化

- 给每一个组件包一个memo,在组件内通过useCallBack做缓存

- class定义的组件可以继承自PureComponent类

  PureComponent对属性或者状态数据进行的是浅比较,Component对属性或者数据进行的是深比较,所 以  相对来说PureComponent的性能更优,它的作用类似于function定义组件中的**memo**,可以**对组件做性能优化**。当组件接收到的属性不发生改变的话,组件不会重新渲染。建议组件定义的时候使用它

在 react 中如何获取最新的值

  1. class 定义的组件

    this.setState({ 此处是改变的数据 }, function() {

    ​ // 数据改变成功之后执行

    })

  2. function 定义的组件

    可以使用 useEffect 获取最新的值

react 中获取 dom 元素

function 组件中使用 useRef

class 组件中使用 ref class 定义的组件中通过使用refs可以获取所有设置了 ref 属性的标签

使用场景:比如当一个图片加载完毕的时候,我想获取这个图片的宽和高,就要用到 refs,比如放大镜。

memo()和 PureComponent 组件异同:

  • 异:React.memo()是函数组件,React.PureComponent 是类组件。

  • 同:都是对接收的 props 参数进行浅比较,解决组件在运行时的效率问题,优化组件的重渲染行为。

  • useMemo()是定义一段函数逻辑是否重复执行。

  • 若第二个参数为空数组,则只会在渲染组件时执行一次,传入的属性值的更新也不会有作用。

    所以 useMemo()的第二个参数,数组中需要传入依赖的参数。

hooks 的使用有什么注意事项

(1)只能在React函数式组件或自定义Hook中使用Hook。
(2)不要在循环,条件或嵌套函数中调用Hook,必须始终在React函数的顶层使用Hook。
这是因为React需要利用调用顺序来正确更新相应的状态,以及调用相应的钩子函数。一旦在循环或条件分支语句中调用Hook,就容易导致调用顺序的不一致性,从而产生难以预料到的后果。
-#自定义 Hook 是一种自然遵循 Hook 设计的约定,而并不是 React 的特性。
#必须以 “use” 开头。
这个约定非常重要。不遵循的话,由于无法判断某个函数是否包含对其内部 Hook 的调用,React 将无法自动检查你的 Hook 是否违反了 Hook 的规则。
-#在两个组件中使用相同的 Hook 会共享 state 吗?
不会。自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。
#自定义 Hook 如何获取独立的 state?
每次调用 Hook,它都会获取独立的 state。

组件销毁的时候需要做什么?

  1. 清除组件中的定时器

  2. 取消没有成功的网络请求

    https://www.jianshu.com/p/22b49e6ad819 网上的一篇终止网络请求的代码例子

redux

什么是 redux?讲一些 redux 的数据流向?

redux 是一个比较热门的前端开发库。是用来做全局状态管理的,用来操作数据的,它可以结合任何一个 js 库来使用(vue,安哥拉)等,react 是一个针对视图层的 library,redux 一般结合 react 来使用。

redux 是一个数据管理插件。redux 使用的是单向数据流的机制,所谓单向数据流就是:数据是单向流动的,在 view 视图中通过 dispatch 派发一个 action 改变数据,数据改变之后 view 视图重新渲染。

redux 中的数据流向:在 view 中通过 dispatch 派发一个 action,reducer 会接收到 action,根据 action 的 type 对数据做改变。数据改变之后页面重新渲染。每一个 reducer 都返回一个新的 state 数据;

需要注意:action必须包含一个type属性,并且不能重复。为了解决这个问题可以使用前置命名空间或者symbol这种数据类型。因为每一次dispatch派发的时候,所有的reducer都会接收到这个action,所以action的type不能重复

reducer是用来改变数据的地方,所有的数据改变都在这里。每一个reducer都需要返回一个新的state状态,在reducer中通过action的type判断以什么方式改变数据。所以action的type必须唯一,要不然就没办法区分数据怎么改变了

每一个reducer都是一个function,这个function接收两个参数(state, action),返回一个新的state状态数据

如果需要在react项目中使用redux需要使用插件react-redux

在组件中获取redux数据可以使用:useSelector或者connect实现

在组件中获取redux的dispatch方法,可以使用:useDispatch或者connect
useSelector或者useDispatch只能在function定义的组件中使用。
redux的action只能是简单的对象,如果要处理异步需要使用redux-thunk

常用组件,几个部分

  1. Action – 用来组织数据,传递给 reducer 来进行数据的改变操作。

    如何定义 action:首先必须具有 type 属性,表示正在执行的 action 的类型。必须将它们定义为字符串常量,并且还可以向其添加更多的属性

  2. Reducer – 这是一个确定状态将如何变化的地方。

    Reducers 是一个纯函数(返回一个新值,并且没有任何副作用)。它通过接受先前的状态和 action 来工作,返回一个新的对象作为数据,目的是不进行原始数据的对比,使性能更高。

  3. Store – 整个程序的状态/对象树保存在 Store 中。

    Store 是一个 JavaScript 对象,它可以保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。应用程序的整个状态/对象树保存在单一存储中。因此,Redux 非常简单且是可预测的。我们可以将中间件传递到 store 来处理数据,并记录改变存储状态的各种操作。所有操作都通过 reducer 返回一个新状态。

  4. View – 只显示 Store 提供的数据。

它的工作流程是在页面中通过 dispatch 派发一个 action,reducer 中接收到 action 之后改变数据。数据改变之后页面重新更新渲染。

redux 使用流程

  • 在脚手架中安装 react-redux

  • 使用 createStore 去创建一个全局的 store,用来保存所有的 state,createStore 接收一个 reducer 作为参数,你可以使用 combineReducers 传入多个 reducer。

  • 在 reducer 中,接收两个参数,第一个参数表示数据的初始状态,第二个参数表示 action,并且 reducer 会返回一个新的对象作为数据,这样的话可以不进行原始对象的比较,性能会提高。

  • 想要改变数据的话,就是 view 通过 dispatch 去派发一个 action 去执行相应的 reducer,并且在 store 中进行更新,store 改变的话,view 就会重新渲染。

  • 我们可能要使用 react-redux 中的 connect 和 Provider 方法 去关联我们的数据。Provider 通过 context 上下文向子组件提供 store,connect 把 redux 中的数据和组件中的 props 做关联,这里用到的方法是 mapStateToProps 把 store 中的数据去映射到组件的 props 中,这样在组件中就可以通过 props 去访问到 redux 中的数据。

  • 如果需要发送异步请求的话,还需要 react-thunk 插件,需要在 creaceStore 中做一个配置。

Flux

Flux 是一种强制单向数据流的架构模式。它控制派生数据,并使用具有所有数据权限的中心 store 实现多个组件之间的通信。整个应用中的数据更新必须只能在此处进行。 Flux 为应用提供稳定性并减少运行时的错误。

redux 中的 action 必须是一个 plain object(简单的对象),不能是一个 function。为了解决异步 action 的问题,我们需要使用插件

redux-thunk 插件的作用是判断当前我们 dispatch 的 action 是什么类型,如果是一个对象那么直接执行;如果是一个 function,那么会把 dispatch 当参数传递的 function 的内部进行派发

redux 中间件的原理

  • 中间件实际是对 dispatch 的改装

  • 中间件指的是 action 和 store 之间对 dispatch 的改装

  • 派发 action 的时候,先走中间件,再去 store 中

  • 从 action (中间件)-> store -> reducer -> store

redux-toolkit

是官方提供的一个 redux 的插件集,可以让我们非常方便的在项目中使用 redux 做开发,无需再做额外的配置。它内置了一些常用的插件,比如:dev-tools(开发者调试工具),redux-thunk(处理异步操作),immutable(不可变数据类型)等

npm install @reduxjs/toolkit react-redux # 安装依赖

createSlice

这个 API 的作用是创建每一个 reducer 数据,通过指定 name、initialState、reducers 和 extraReducers 进行组织

  • name 表示命名空间

  • initialState 表示初始值

  • reducers 定义的改变数据的方式,可以直接导出为 actions

  • extraReducers 监听其他的 reducer,非当前 slice 中同步改变数据的操作

configStore

作用是配置或者创建一个 store 数据源

createAsyncThunk

作用是创建一个异步的 action,创建成功之后会具有三个状态

pending,进行中

rejected,失败

fulfilled,成功。它接收两个参数,参数一为当前的 state 数据、参数二为 action

redux 遵循的三个原则

#单一数据源
整个应用的 state 被储存在 一棵 object tree 中,并且这个 object tree 只存在于 唯一一个 store 中。
#状态(State) 是只读的
唯一改变 state 的方法就是触发 action。
这样确保了 视图 和 网络请求 都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 竞态条件的出现。
#使用纯函数来修改state
为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数。

redux 优点:

  • 结果的可预测性 - 由于总是存在一个真实来源,即 store ,因此不存在如何将当前状态与动作和应用的其他部分同步的问题。

  • 可维护性 - 代码变得更容易维护,具有可预测的结果和严格的结构。

  • 服务器端渲染 - 你只需将服务器上创建的 store 传到客户端即可。这对初始渲染非常有用,并且可以优化应用性能,从而提供更好的用户体验。

  • 开发人员工具 - 从操作到状态更改,开发人员可以实时跟踪应用中发生的所有事情。

  • 社区和生态系统 - Redux 背后有一个巨大的社区,这使得它更加迷人。一个由才华横溢的人组成的大型社区为库的改进做出了贡献,并开发了各种应用。

  • 易于测试 - Redux 的代码主要是小巧、纯粹和独立的功能。这使代码可测试且独立。

  • 组织 - Redux 准确地说明了代码的组织方式,这使得代码在团队使用时更加一致和简单

redux 缺点:

  • 一个组件所需要的数据,必须由父组件传过来,而不能像 flux 中直接从 store 取。

  • 当一个组件相关数据更新时,即使父组件不需要用到这个组件,父组件还是会重新 render,可能会有效率影响,或者需要写复杂的 shouldComponentUpdate 进行判断。

redux 中的数据管理

你会把数据统一放到 redux 中管理,还是把共享数据放在 redux 中管理?

  • 所有的数据都应该放在 redux 中去管理。

  • 当一个组件中既有 state 又有 props 和 redux 存储来存储数据。一个组件二三百行,查错不知道去哪儿查,这样不便于维护。

  • 当业务逻辑需要拓展的时候,state 中存储数据,当别的组件需要你这个数据的时候,会很麻烦,redux 更方便。

  • 不必担心 redux 消耗内存,redux 内存 5 个 G(chrome 浏览器)

redux 中 action 的 type 是否可以相同?为什么?如何解决?

不能重复。因为每一次 dispatch 一个 action 的时候所有的 reducer 都会接收到这个 action,如果 action 的 type 一样了,那么就没办法区分是哪一个做的改变。可以为 type 设置命名空间,比如:"xx/type",xx 表示明明空间,type 表示 action 的 type,这样就可以区分开。或者使用 symbol 类型进行定义。一定要保证 action 的 type 不重复

redux 中获取 state 中的数据有哪些方法?

connect 和 useSelector

(在构造函数中)调用 super(props) 的目的是什么

在 super() 被调用之前,子类是不能使用 this 的,在 ES2015 中,子类必须在 constructor 中调用 super()。传递 props 给 super() 的原因则是便于(在子类中)能在 constructor 访问 this.props。

在js原生事件中 onclick 和 jsx 里 onclick 的区别

js原生中 onclick添加事件处理函数是在全局环境下执行,污染了全局环境, 且给很多dom元素添加onclick事件,影响网页的性能, 同时如果动态的从dom树种删除了该元素,还要手动注销事件处理器,不然就可能造成内存泄露;

jsx里的onClick 挂载的函数都控制在组件范围内,不会污染全局空间 jsx中不是直接使用onclick,而是采取了事件委托的方式,挂载最顶层DOM节点,所有点击事件被这个事件捕获,然后根据具体组件分配给特定函数,性能当然比每个onClick都挂载一个事件处理函数要高 加上React控制了组件的生命周期,在unmount的时候自然能够清楚相关的所有事件处理函数,内存泄露不再是一个问题;

单项数据流

单向数据流(Unidirectional data flow)方式使用一个上传数据流和一个下传数据流进行双向数据通信,两个数据流之间相互独立。单向数据流指只能从一个方向来修改状态。

优点:

  1. 所有状态的改变可记录、可跟踪,源头易追溯;

  2. 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性;

  3. 一旦数据变化,就去更新页面(data-页面),但是没有(页面-data);

  4. 如果用户在页面上做了变动,那么就手动收集起来(双向是自动),合并到原有的数据中。

缺点:

  1. HTML 代码渲染完成,无法改变,有新数据,就须把旧 HTML 代码去掉,整合新数据和模板重新渲染;

  2. 代码量上升,数据流转过程变长,出现很多类似的样板代码;

虚拟 dom 和 diff 算法

虚拟 dom 就是一个 js 表达的 dom 树结构,它最终会生成真实的 dom。它是一个 js 对象,所以更新比较的效率很高,在渲染生成真实的 dom 树的时候会进行局部更新,而不是更新整个页面。这样子便于提升页面的渲染速度和优化页面性能

diff 算法是一个比较算法,作用是比较虚拟 dom 的变化。找到哪一个位置发生了改变,进行替换操作。它是按照逐层进行比较的,遇到改变的节点,直接替换整棵树;

diff 算法

计算出虚拟 DOM 中真正变化的部分,并只针对该部分进行原生 DOM 操作,而非重新渲染整个页面。

(1)什么是调和?

将虚拟 DOM 树转换成真实 DOM 树的最少操作的过程 称为 调和 。

(2)什么是 React diff 算法?

diff 算法是调和的具体实现原理:

  • 把树形结构按照层级分解,只比较同级元素。

  • 给列表结构的每个单元添加唯一的 key 属性,方便比较。

  • React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)

  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.

  • 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。

虚拟 dom

  • 虚拟 dom 实际上是一个 js 对象。是真实 dom 在内存中的一种表现形式,虚拟 dom 和真实的 dom 是同步的,这个同步的过程就是调和过程。

  • 虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。

  • 真实 dom 进行比对的时候,非常复杂,真实 dom 树上会有方法,有属性,有事件,会非常消耗性能。通过将真实 dom 对象转化成 js 中的对象,就不具有真实 dom 中的特性,单纯比较 js 对象性能就会比较快。

react 中的错误边界?react 对页面 render 异常捕获

  • React16.X 中引入了错误边界(Error Boundaries)概念。

  • 它可以捕获它的子组件中产生的错误,类似于 try-catch,只不过是以 react 组件的形式来实现的。

  • 有了错误边界,即使某个组件的结果有错误,整个 React 程序挂载也不会被挂掉。只有出错的那个组件会显示一个后备界面,而整个程序仍然完全正常运行。

  • 这里的 componentDidCatch()函数使用方法和 JavaScript 中的 catch {}代码块差不多,但只能用于组件。只有类组件才可以成为错误边界。

  • 在 componentDidCatch()函数内部我们把 hasError 状态设置为 true。然后在渲染方法中检查那个状态。如果出错状态是真,就渲染后备界面;如果是 false 就把想渲染的 React 组件界面当作子组件界面渲染出来。

尽管如此,以下错误 Error Boundaries 依旧无法捕获:

  • 事件错误

  • Error Boundaries 本身的错误

  • 异步代码

可控组件和非可控组件

  • 可控组件与非可控组件的核心区别就是在于输入的值是否与 state 的值对应

  • 非可控组件获取值时需要使用 ref 来从 dom 节点中拿值

  • 主要是表单的 value 是否受 state 控制

  • 需要自行监听 onChange,更新 state

reselect 是什么

  • 类似于 vue 中的 computed 计算属性

  • 派生数据

  • 存在值的缓存,为了做性能优化

ref 和 refs

  • ref 用来获取 dom 元素

  • Refs 是 React 中引用的简写。它是一个有助于存储对特定的 React 元素或组件的引用的属性,它将由组件渲染配置函数返回。用于对 render() 返回的特定元素或组件的引用。当需要进行 DOM 测量或向组件添加方法时,就需要用到 refs。

    使用 refs 的情况:

    • 需要管理焦点、选择文本或媒体播放时

    • 触发式动画

    • 与第三方 DOM 库集成

  • 比如当一个图片加载完毕的时候,我想获取这个图片的宽和高,就要用到 refs,比如放大镜。

dva

dva框架本质上是 蚂蚁金服基于 React技术栈封装的一套框架,

特点在于免去了新手手动搭建完整开发环境(Redux,React-Router等的安装)的麻烦,一键安装即可项目开发,虽然降低了上手难度,但不可避免的是反而使扩展成本变得更高(如需要单独配置特定的webpack配置项,而这些配置项不在roadhog可配置范围时),也不必担忧,系统复杂度不高的web应用使用dva框架开发,使用封装好的配置项已绰绰有余。

将 model 注册进入dva实例。在页面中使用 connect函数连接 redux的store和页面组件,将store中的state当做props传递进入页面组件。并在组件挂载成功后发起异步请求获取远程数据。编写处理这个action对应的reducer和发起异步请求的异步action。

react 中的混合事件和原生 dom 事件有什么不同

react 中的事件是被框架做二次封装处理的,和原生 dom 事件不一样。他们不是直接添加在 dom 元素上的

DOM事件模型分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播。事件传播分为三个阶段:

捕获(Capture):事件对象从window对象传递到目标对象的过程。

目标(target):目标节点在处理事件的过程。

冒泡(Bubble):事件对象从目标对象传递到window对象的过程。

React中的事件机制与原生的完全不同,事件没有绑定在原生DOM上,发出的事件也是对原生事件的包装。React内部事件系统可以分为两个阶段:事件注册和事件触发。

事件注册:React将所有的DOM事件全部注册到document节点上。

事件触发:事件执行时,document上绑定事件会对事件进行分发,根据之前存储的类型和组件标识找到触发事件的组件。

React事件机制的优点:

1、减少内存消耗,提升性能,一种事件类型只在document上注册一次

2、统一规范,解决ie事件兼容问题,简化事件逻辑

3、对开发者友好

webpack


webpack

前端模块化开发打包工具,它的主要目标是将 JavaScript 文件打包在一起,我们一直有用到,但是所有的配置我都是查文档的。

webpack 中,是借助 loader 完成 JSX 的转化?还是 babel?

因为 react 的代码是没办法直接在浏览器中运行的,是借助脚手架将代码转化为 ES5,才得以在浏览器中运行,(在 vue 中是借助 webpack 中的 vue-loader 转化)react 是借助 babel 中的 preset-react 来转化的。

webpack 打包原理

打包原理:webpack 是把项目当作一个整体,通过给定的一个主文件,webpack 将从这个主文件开始找到你项目当中的所有依赖的文件,使用 loader 来处理它们,最后打包成一个或多个浏览器可识别的 js 文件

webpack 基本功能和工作原理

1、代码转换:TypeScript 编译成 js、scss 编译成 css 等

2、文件优化:压缩 js、css、HTML 代码,压缩合并图片等

3、代码分割:提取多个页面的公共代码,提取首屏不需要执行部分的代码,让其异步加载

4、模块合并:某个模块引用其他的模块和文件,需要构建功能,把模块合并成一个文件

5、热更新:监听本地原代码变化,自动构建,更新内容

webpack 配置、webpack.config.js

const path = require("path"); //引入path
module.exports = {
  entry: "./path/to/my/entry/file.js", //入口路径,从哪开始打包
  output: {
    path: path.resolve(__dirname, "dist"), //__dirname指webpack.config.js所在的目录,dist指输出路径
    filename: "my-first-webpack.bundle.js", //输出文件名
  },
};
  • mode: ‘development’ 开发模式
  • 入口(entry) - 入口路径,告诉 webpack 从哪开始打包
  • 输出(output) - output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。

代码管理工具git


可参考往期文章:http://t.csdn.cn/cuIdz


本文转载自: https://blog.csdn.net/leoxingc/article/details/127857631
版权归原作者 旺旺大力包 所有, 如有侵权,请联系我们删除。

“前端面试八股文(详细版)—中”的评论:

还没有评论