React 面试题

React

1. react 中 dom

虚拟dom

虚拟dom相当于在js和真实dom之间加了个缓存,使用虚拟 DOM + Diffing 算法,尽量减少与真实 DOM 的交互。

虚拟dom和real dom区别 性能差异
减少DOM的操作:虚拟dom可以将多次操作合并为一次操作,减少DOM操作的次数

真实 dom虚拟 dom
更新慢更新快
可以直接更新 html无法直接更新 html
如果元素更新,则创建新的DOM如果元素更新,则更新JSX
DOM操作代价高DOM操作简单
消耗内存多消耗内存少
2. 组件之间通信
  1. 父组件向子组件通信:父组件更新状态,通过props传给子组件,子组件得到后更新
  2. 子组件向父组件通信:通过props回调函数
    父组件把回调函数放在props,传给子组件,子组件就利用这个回调函数将信息传递给父组件
  3. 兄弟组件:找到它们的共同父节点,结合上面两种方式通信
  4. 跨级组件之间通信:通过props一层一层传,或通过context提供一个全局态的store
  5. 非嵌套组件: 发布订阅模式:。发布者发布事件,订阅者监听事件并做出反应,通过引入event模块进行通信
  6. 全局状态管理工具:借助 reduxMobx 等全局状态管理工具,进行通信。这种工具会维护一个全局状态中心store,并根据不同的事件产生新的状态。
3. redux 原理

作用: 集中式管理 react 应用中多个组件共享的状态

数据如何通过 redux 流动?

  1. 首先,用户(View)发出 Action,发出方式就用 dispatch
  2. 之后,Store 自动调用 Reducer,并且传入两个参数:当前state 和 收到的 Action, Reducer会返回新的State
  3. State一旦有变化,Store就会调用监听函数,来更新View。
    在这里插入图片描述

二,核心概念
Store:保存程序的状态,并提供一些方法来访问状态、调度操作和注册侦听器。
在 redux 里面只有一个Store,整个应用需要管理的数据都在这个Store里面。这个Store我们不能直接去改变,我们只能通过返回一个新的Store去更改它。redux提供了一个createStore来创建state

import { createStore } from 'redux'
const store = createStore(reducer)  //创建state

action 是视图层发起的一个操作,告诉Store 我们需要改变。比如用户点击了按钮,我们就要去请求列表,列表的数据就会变更。每个 action 必须有一个 type 属性,这表示 action 的名称,然后还可以有一个 payload 属性,这个属性可以带一些参数,用作 Store 变更:

const action = {
  type: 'ADD_ITEM',
  payload: 'new item', // 可选属性
}

Reducer
在上面定义了一个Action,但是Action不会自己主动发出变更操作到Store,所以这里我们需要一个叫dispatch的东西,它专门用来发出action,这个dispatch不需要我们自己定义和实现,redux已经帮我们写好了,在redux里面,store.dispatch()是 View发出 Action 的唯一方法。

store.dispatch({
  type: 'ADD_ITEM',
  payload: 'new item', // 可选属性
})

当 dispatch 发起了一个 action 之后,会到达 reducer,那么这个 reducer 用来干什么呢?
reducers 通过接受先前的状态和 action 来工作,然后它返回一个新的状态。它根据操作的类型确定需要执行哪种更新,然后返回新的值。如果不需要完成任务,它会返回原来的状态。

const reducer = function(prevState, action) {
  ...
  return newState;
};

我们在创建store的时候,我们在createStore里面传入了一个reducer参数,在这里,我们就是为了,每次store.dispatch发送一个新的action,redux都会自动调用reducer,返回新的state。

Redux 由以下组件组成:

Action – 这是一个用来描述发生了什么事情的对象。
Reducer – 这是一个确定状态将如何变化的地方。
Store – 整个程序的状态/对象树保存在Store中。
View – 只显示 Store 提供的数据。

redux遵循的三个原则?
  • 单一事实来源:Redux 使用 “Store” 将程序的整个状态存储在同一个地方。因此所有组件的状态都存储在 Store 中,并且它们从 Store 本身接收更新。
    单一状态树可以更容易地跟踪随时间的变化,并调试或检查程序。
  • 状态是只读的:改变状态的唯一方法是去触发一个动作。
  • 使用纯函数进行更改:为了指定状态树如何通过操作进行转换,你需要纯函数。纯函数是那些返回值仅取决于其参数值的函数
redux 优点
  1. 结果的可预测性:由于总是存在于一个真实来源store,所以不存在当前状态和其他部分同步的问题
  2. 可维护性
  3. 服务器端渲染:只需要将服务器的store传到客户端即可,对初始渲染非常有用,可以优化应用性能
  4. 易于测试,对开发人员非常方便:从操作到状态更改,开发人员可以实时跟踪应用中发生的所有事情。
react-rudex

在这里插入图片描述

4. react生命周期各个阶段

生命周期分为三个阶段:挂载阶段、更新阶段、卸载阶段。

  1. 初始渲染阶段:这是组件即将开始并进入dom的阶段
  2. 更新阶段:一旦组件被添加到dom中,只有在prop或状态发生改变时才能更新和重新渲染
  3. 卸载阶段:组件被销毁且从dom中删除
详细解释 React 组件的生命周期方法

1.挂载阶段

  • constructor:构造函数,最先被执行,初始化state对象或者绑定this
  • getDerivedStateFromProps:当我们接收到新的属性想去修改state,可以使用
  • render:返回需要渲染的东西。可以返回原生的DOM、React组件、Fragment、Portals、字符串和数字、Boolean和null等内容
  • componentDidMount

更新阶段:

  • getDerivedStateFromProps: 此方法在更新个挂载阶段都可能会调用
  • shouldComponentUpdate:返回一个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回true
  • render: 更新阶段也会触发此生命周期
  • getSnapshotBeforeUpdate:这个方法在render之后,componentDidUpdate之前调用,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有一个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此生命周期必须与componentDidUpdate搭配使用
  • componentDidUpdate:该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,然后在 componentDidUpdate 中统一触发回调或更新状态。

卸载阶段:

  • componentWillUnmount: 当我们的组件被卸载或者销毁了就会调用,我们可以在这个函数里去清除一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作

错误处理
当渲染过程,生命周期或子组件的构造函数中抛出错误时,会调用如下方法:
static getDerivedStateFromProps()
componentDidCatch()

React 16之后有三个生命周期被废弃(但并未删除)
componentWillMount
componentWillReceiveProps
componentWillUpdate
官方计划在17版本完全删除这三个函数,只保留UNSAVE_前缀的三个函数,目的是为了向下兼容

5. route 路由

React 路由是一个构建在 React 之上的路由库,有助于向应用程序添加新的屏幕和流。这使 URL 与网页上显示的数据保持同步。

Router用于定义多个路由,当用户定义特定的URL时,如果此URL和Router内定义的任何’路由’相匹配,则用户将重定向到该路由。
所以基本上我们在自己的应用中添加一个Router库,允许创建多个路由,每个路由都会向我们提供不同的视图

switch:switch会按顺序将已定义的URL与已定义的路由进行匹配,找到第一个匹配项后,将渲染指定的路径,从而绕过其他路线。

React Router 的优点

  • 可以将 Router 可视化为单个根组件(),其中我们将特定的子路由()包起来。
  • 不需要手动设置历史值,我们要做的就是将路由包装在组件中
  • 包是分开的:共有三个包,分别用于 Web、Native 和 Core。这使我们应用更加紧凑。基于类似的编码风格很容易进行切换。
HashRouter 和 HistoryRouter 区别与原理

单页应用 是在移动互联时代诞生的,目标是不刷新浏览器,而通过感知地址栏中的变化来决定内容区域显示什么内容。要达成这个目标,我们要用到前端路由技术,具体来说有两种方式来实现:hash模式和history模式。hash模式是通过监听hashChange事件来实现的,history模式是通过pushState方法+popstate事件来实现的。

它们俩是前端路由的两种模式

HashRouter —— 即地址栏 URL 中的 # 符号
比如这个 URL:http://www.aaa.com/#/hello,hash 的值为 #/hello
特点:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。

HistoryRouter —— 利用了 HTML5 中新增的 pushState()replaceState() 方法。这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
特点:丢掉了丑陋的#,但也有个问题:不怕前进,不怕后退,就怕刷新,因为刷新是实实在在地去请求服务器的

在hash模式下,前端路由修改的是#中的信息,而浏览器请求时不会将 # 后面的数据发送到后台,所以没有问题。但是在history下,你可以自由的修改path,当刷新时,如果服务器中没有相应的响应或者资源,则会刷新出来404页面。

6. hooks 的优缺点

优点:

  1. 更容易复用代码:它通过自定义hooks来复用状态
  2. 给函数组件加上了state,让它拥有自己的状态和生命周期
  3. 一个React组件就是一个JS函数,更容易复用代码和实现逻辑复用
  4. 不需要老是纠结this指向

缺点:

  • class组件的三个生命周期函数合并在一个生命周期函数内
  • hooks只能在React函数组件中调用,不能在普通JS函数中调用,且不能在循环,条件判断调用hooks
7. setState 是同步还是异步

setState 本身是同步的,但是因为react的批处理下体现为异步。
所以在react的生命周期函数和合成时间中为异步,在原生环境下为同步
(在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新)

state 和 props 区别

都是普通的JS对象

  • state 是组件自己管理数据,控制自己的状态,可变
  • props 是外部传入的数据参数,不可变
  • 没有state叫做 无状态参数,有 state叫有状态参数
  • 多用 props,少用state,也就是多写无状态组件
8. refs

refs是用来访问DOM节点的方式

一般情况,react很少操作真实DOM,而是在render里编写页面结构,再由react组织真实DOM更新。
但是少数情况需要我们对页面真实DOM直接进行操作,这就要求我们有直接访问真实DOM的能力,就是refs
官方文档对Refs的描述是:Refs提供了一种方式,允许我们访问DOM节点,或在render方法中创建的React元素。

何时使用?
1)管理焦点,文本选择,媒体播放
2)触发强制动画
3)集成第三方DOM库
即:在React无法控制局面的时候,就需要直接操作refs

refs 有哪些使用方式?
1)字符串形式的refs:不推荐
2)回调形式的refs
3)使用React.createRef()创建,并通过ref属性附加到React元素

class Demo extends Component{
    constructor(props){
        super(props)
        // React.creatRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
        this.myRef=React.createRef()
    }
    showData=()=>{
       console.log(this.myRef)//输出myRef容器
       console.log(this.myRef.current)//输出input节点
       console.log(this.myRef.current.value)//输出input中的值
    }
    render(){ 
        return(
            <div>
                <input ref={this.myRef} type="text" placeholder='点击按钮提示数据'></input>&nbsp;
            </div>
     ) }}
9. react 事件绑定

React 中事件绑定都不是绑定在对应的真实DOM上,而是统一绑定document上,采用事件冒泡的形式向上传递,当事件发生并且冒泡至root处时,react将事件内容封装并交由真正的处理函数运行。

采用合成事件的原因:
① 兼容所有的浏览器和实现跨平台开发 ;
② 统一挂载,减少内存消耗,方便在组件挂载/卸载时,统一订阅和移除事件 ;
③ 方便事件统一管理

另外,冒泡到root上的事件也不是原生浏览器事件,而是react自己实现的合成事件。因此如果我们不想要事件冒泡的话,调用event.stopPropagation是无效的,而应该调用event.preventDefault
在这里插入图片描述

10. diff 算法

diff 算法作用:渲染真实DOM开销太大,,有时候修改某个数据会引起整个DOM树的修改,但是我们只希望更新我们修改的那部分,而不是整个dom,diff算法就实现这点
diff算法本质:找到两个对象之间的差异,尽可能做到节点复用
diff算法本质是对比,对比旧虚拟DOM和新虚拟DOM,对比出是哪个虚拟节点改变,找到这个虚拟节点,并只更新这个虚拟节点所对应的真实节点,而不用更新其他,这就做到了精准更新DOM

  1. Tree diff:对树分层比较,两棵树只对同一层次节点比较,如果该节点不存在,则该节点及其子节点会被完全删除,不会再进行比较
    (当出现节点跨层级移动时,并不会出现想象中的移动操作,而是会进行删除,重新创建的动作,这是一种很影响React性能的操作。因此,官方建议不要进行DOM节点跨层级操作,可以通过css隐藏、显示节点,而不是真正地移除、添加DOM节点)
  2. Component diff:拥有相同类的两个组件 生成相似的树形结构,拥有不同类的两个组件 生成不同的树形结构。
    (1)同一类型的两个组件,按原策略(层级比较)继续比较Virtual DOM树即可。
    (2)同一类型的两个组件,组件A变化为组件B时,可能Virtual DOM没有任何变化,如果知道这点(变换的过程中,Virtual DOM没有改变),可节省大量计算时间,所以用户可以通过 shouldComponentUpdate() 来判断是否需要判断计算。
    (3)不同类型的组件,将一个(将被改变的)组件判断为dirtycomponent(脏组件),从而替换整个组件的所有节点。
    注意:如果组件D和G的结构相似,但是React判断是不同类型的组件,则不会比较其结构,而是删除组件D及其子节点,创建G及其子节点。
  3. Element diff:当节点处于同一层级时,diff提供三种节点操作:删除、插入、移动。
    例如:组件D已经在集合(A、B、C、D)里了,且集合更新时,D没有发生更新,只是位置改变,如新集合(A、D、B、C),则(对同一层级的同组子节点)添加唯一的key进行区分,移动即可
11. 简述一下 React 的源码实现
  1. React 的实现主要分为Component和Element;
  2. Component属于 React实例,在创建实例的过程中会在实例中注册state和props属性,还会依次调用内置的生命周期函数;
  3. Component中有一个render函数,render函数要求返回一个Element对象(或null);
  4. Element对象分为原生Element对象和组件式对象,原生Element+ 组件式对象会被一起解析成虚拟 DOM 树,并且内部使用的state和props也以 AST 的形式注入到这棵虚拟 DOM 树之中;
  5. 在渲染虚拟 DOM 树的前后,会触发 React Component 的一些生命周期钩子函数,比如componentWillMount和componentDidMount,在虚拟 DOM 树解析完成后将被渲染成真实 DOM 树;
  6. 调用setState时,会调用更新函数更新state,并且触发内部的一个updater,调用render生成新的虚拟 DOM 树,利用 diff 算法与旧的虚拟 DOM 树进行比对,比对以后利用最优的方案进行 DOM 节点的更新,这也是 React 单向数据流的原理(与 Vue 的 MVVM 不同之处)。
12. 框架的好处和弊端

优势:

  • 组件化:高度的组件化有利于维护,有利于组合扩展
  • 天然分层:现代框架不管是MVC、MVVM模式都可以帮我们进行分层,代码解耦更易于读写;
  • 生态:现在的主流框架都自带生态,无论是数据流管理架构或UI都有成熟方案
  • 开发效率:现在框架都默认自动更新DOM,无需手动

缺点:

  • 兼容性问题,SEO不行
  • 有场景要求,开发自由度低
  • 框架本身也有出错风险
13. 模块化、组件化、工程化

模块化:一个模块就是一个实现特定功能的文件,有了模块就更好使用别人的代码,用什么功能就加载什么模块

js模块化方案很多有AMD、CommonJS、UMD、ES6 Module等。css模块化开发大多数是在less、sass、stylus等预处理器的import、minxin特性支持下实现。
模块化优势:

避免变量污染,命名冲突
提高代码复用率
提高维护性
依赖关系的管理

组件化
页面上每个独立的,可视/可交互区域视为一个组件。
每个组件对应一个工程目录,组件所需的各种资源都在这个目录下就近维护。
由于组件具有独立性,所以组件与组件之间可以自由组合
页面不过是组件的容器,负责组合组件形成功能完善的界面
工程化
工程化是将前端项目当成一项系统工程进行分析、组织和构建从而达到项目结构清晰、分工明确、团队配合默契、开发效率提高的目的。

14. dva 数据流流向

数据的改变发生通常是通过用户交互行为或者浏览器行为(如路由跳转等)触发的,当此类行为会改变数据的时候可以通过dispatch发起一个 action,如果是同步行为会直接通过Reducers改变State,如果是 异步行为(副作用)会先触发Effects然后流向Reducers最终改变State
在这里插入图片描述

Models
(1) State---------------State 表示 Model 的状态数据,通常表现为一个 javascript 对象(当然它可以是任何值);操作的时候每次都要当作不可变数据(immutable data)来对待,保证每次都是全新对象,没有引用关系,这样才能保证 State 的独立性,便于测试和追踪变化。
(2)Action---------------Action 是一个普通 javascript 对象,它是改变 State 的唯一途径。无论是网络回调,还是 WebSocket 等数据源所获得的数据,最终都会通过 dispatch 函数调用一个 action,从而改变对应的数据。action 必须带有 type 属性指明具体的行为,其它字段可以自定义,如果要发起一个 action 需要使用 dispatch 函数;需要注意的是 dispatch 是在组件 connect Models以后,通过 props 传入的。
(3)dispatch 函数------------dispatching function 是一个用于触发 action 的函数,action 是改变 State 的唯一途径,但是它只描述了一个行为,而 dipatch 可以看作是触发这个行为的方式,而 Reducer 则是描述如何改变数据的。
(4)Reducer-------------Reducer函数接受两个参数:之前已经累积运算的结果和当前要被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值,在 dva 中,reducers 聚合积累的结果是当前 model 的 state 对象。通过 actions 中传入的值,与当前 reducers 中的值进行运算获得新的值(也就是新的 state)。需要注意的是 Reducer 必须是纯函数,所以同样的输入必然得到同样的输出,它们不应该产生任何副作用。并且,每一次的计算都应该使用immutable data,这种特性简单理解就是每次操作都是返回一个全新的数据(独立,纯净),所以热重载和时间旅行这些功能才能够使用.
(5)Effect------------dva 为了控制副作用的操作,底层引入了redux-sagas做异步流程控制,由于采用了generator的相关概念,所以将异步转成同步写法,从而将effects转为纯函数。

15. 防抖 节流

防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。多次触发但触发只生效最后一次的场景
思路:每次触发事件时都取消之前的延时调用方法。
使用的本质:不允许某一行为触发。
应用场景:search搜索联想,用户在不断输入值时,用防抖来节约请求资源
在这里插入图片描述
设计思路:事件触发后开启一个定时器,如果事件在这个定时器限定的时间内再次触发,则清除定时器,在写一个定时器,定时时间到则触发。

function debounce(fn, delay){
	let timer = null;
	return function(){
		clearTimeout(timer);
		timer = setTimeout(()=> {
			fn.apply(this, arguments);
		}, delay)
}}

节流: 高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。
思路:每次触发事件时都判断当前是否有等待执行的延时函数。
应用:鼠标不断点击发送,但单位时间只触发一次
使用的本质:允许某一行为触发,但是触发的频率不能太高。
在这里插入图片描述
设计思路:我们可以设计一种类似控制阀门一样定期开放的函数,事件触发时让函数执行一次,然后关闭这个阀门,过了一段时间后再将这个阀门打开,再次触发事件。

function throttle(fn, delay){
	let valid = true;
	return function(){
		if(valid) { //如果阀门已经打开,就继续往下
			setTimeout(()=> {
				fn.apply(this, arguments);//定时器结束后执行
				valid = true;//执行完成后打开阀门
			}, delay)
			valid = false;//关闭阀门
		}
	}
}
// 刚开始valid为true,然后将valid重置为false,进入了定时器,在定时器的时间期限之后,才会将valid重置为true,valid为true之后,之后的点击才会生效
// 在定时器的时间期限内,valid还没有重置为true,会一直进入return,就实现了在N秒内多次点击只会执行一次的效果

//用法:
function fn(value){
  console.log(value);
}
var throttleFunc = throttle(fn,2000);//节流函数
 //事件处理函数,按钮点击事件
btn.addEventListener("click",function(){    
    throttleFunc(Math.random());// 给节流函数传参
})
  • 14
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值