面试官最喜欢问☞Vue、React专题

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战


适合初次全面复习的同学,查缺补漏,知识面比较全,复习完成后,再按照本人整理的面试高频题配合复习,使得找工作事半功倍,一定要理解,不要死记硬背,对于一些概念性的和原理的内容要深入理解。

“你从头读,尽量往下读,直到你一窍不通时,再从头开始,这样坚持往下读,直到你完全读懂为止。”

Vue

使用框架一定比原生的或者jQuery好吗?为什么

使用框架的优点:
用户体验会更好
开发效率高,成本降低,便于后期维护
采用虚拟DOM操作,更新性能更高

使用框架的缺点:
代码臃肿,使用者使用框架的时候会将整个框架引入,而框架封装了很多功能和组件,使用者必须按照它的规则使用,而实际开发中很多功能和组件是用不到的。
前端框架迭代更新太快,需要时间熟悉

浅谈MVC、MVP、MVVM架构模式的区别和联系

一、MVC(Model-View-Controller)

MVC是比较直观的架构模式,用户操作->View(负责接收用户的输入操作)->Controller(业务逻辑处理)->Model(数据持久化)->View(将结果反馈给View)。

二、MVP(Model-View-Presenter)

MVP是把MVC中的Controller换成了Presenter(呈现),目的就是为了完全切断View跟Model之间的联系,由Presenter充当桥梁,做到View-Model之间通信的完全隔离。

三、MVVM(Model-View-ViewModel)

如果说MVP是对MVC的进一步改进,那么MVVM则是思想的完全变革。它是将“数据模型数据双向绑定”的思想作为核心,因此在View和Model之间没有联系,通过ViewModel进行交互,而且Model和ViewModel之间的交互是双向的,因此视图的数据的变化会同时修改数据源,而数据源数据的变化也会立即反应到View上。

简述MVVM

什么是MVVM?

视图模型双向绑定,是Model-View-ViewModel的缩写,也就是把MVC中的Controller演变成ViewModel。Model层代表数据模型,View代表UI组件,ViewModelViewModel层的桥梁,数据会绑定到viewModel层并自动将数据渲染到页面中,视图变化的时候会通知viewModel层更新数据。以前是操作DOM结构更新视图,现在是数据驱动视图

MVVM的优点:

1.低耦合。视图(View)可以独立于Model变化和修改,一个Model可以绑定到不同的View上,当View变化的时候Model可以不变化,当Model变化的时候View也可以不变;
2.可重用性。你可以把一些视图逻辑放在一个Model里面,让很多View重用这段视图逻辑。
3.独立开发。开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计。
4.可测试

说说Vue的MVVM实现原理

  1. Vue作为MVVM模式的实现库的2种技术

    a. 模板解析
    b. 数据绑定

  2. 模板解析:实现初始化显示

    a. 解析大括号表达式
    b. 解析指令

  3. 数据绑定:实现更新显示

    a. 通过数据劫持实现

创建了两种对象Observer和complie,先创建的Observer,后创建的complie,observer是为了监视/劫持data中所有层次的属性,同时还为每一种属性创建了另外一种对象dep,dep与data中的属性一一对应,complie作用是用来编译模版,初始化界面,调用update对象,complie还为每个表达式创建了对应的watcher同时指定了更新节点的回调函数,将watcher添加到所有对应的dep中,

说说你对 Vue 的理解

Vue 是一个构建数据驱动的渐进性框架,它的目标是通过 API 实现响应数据绑定和视图更新。

优点:
1、数据驱动视图,对真实 dom 进行抽象出 virtual dom, 并配合 diff 算法、响应式和观察者、异步队列等手段以最小代价更新 dom,渲染页面
2、组件化,组件用单文件的形式进行代码的组织编写,使得我们可以在一个文 件里编写 html\css\js 并且配合 Vue-loader 之后,支持更强大的预处理器等功能
3、强大且丰富的 API 提供一系列的 api 能满足业务开发中各类需求
4、由于采用虚拟 dom,但让 Vue ssr 先天不足
5、生命周期钩子函数,选项式的代码组织方式,写熟了还是蛮顺畅的,但仍然 有优化空间(Vue3 composition-api)
6、生态好,社区活跃

缺点:
1、由于底层基于 Object.defineProperty 实现响应式,而这个 api 本身不支持 IE8 及以下浏览器
2、csr 的先天不足,首屏性能问题(白屏)
3、seo 不友好

说说你对vue虚拟DOM的理解

什么是虚拟dom
说白了就是以js对象的形式去添加dom元素
本质上是优化了diff算法
虚拟dom本身也有自己的缺陷他更适合批量修改dom
尽量不要跨层级修改dom
设置key可以最大的利用节点,避免重复渲染

一、什么是vdom?

Virtual DOM 就是用JS对象来模拟真实DOM结构,然后用这个树构建一个真正的 DOM 树, 插到文档当中。当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树 进行比较,记录两棵树差异 把所记录的差异应用到所构建的真正的 DOM 树上,视图就更新了。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。【版本1.1】

建立一个与 dom 树对应的虚拟 dom 对象( js 对象),以对象嵌套的方式来表示 dom 树,那么每次 dom 的更改就变成了 js 对象的属性的更改,这样一来就能查找 js 对象 的属性变化要比查询 dom 树的性能开销小。【版本1.2】

总结起来就两点,一、用JS对象来模拟真实的DOM结构,用这个对象构建真正的DOM树,当状态变更时,重新构建一个新的对象树,然后新旧树进行对比,记录二者差异并应用到所构建的真正的树上,视图也就更新了。二、每次变更时由原来的操作真实的DOM变成了查找js对象的属性变化,直接在内存中操作js对象,性能开销更小,效率更高。【版本2】

总结起来就是,Virtual DOM 就是用JS对象来模拟真实DOM结构,然后用JS对象树构建真正的DOM树。当状态变更时,重新构建一棵新的对象树,然后新旧树通过diff算法进行比较,若存在差异则将差异应用到所构建的真正的树上,视图就更新了。这个比较过程,由原来的查询真实DOM树变成查找js对象属性,性能开销小了,效率也就高了。Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。【版本3】

二、为何要用vdom?

  1. 虚拟dom就是为了解决操作真是dom带来的性能问题而出现的,将DOM对比操作放在JS层,提高效率

  2. DOM结构的对比,放在JS层来做(图灵完备语言:能实现逻辑代码的语言)操作内存中的js显然效率更高

三、vdom核心函数有哪些

核心函数:
h(‘标签名’, {…属性名…}, […子元素…])
h(‘标签名’, {…属性名…}, ‘………’)
patch(container, vnode)
patch(vnode, newVnode)

你怎么理解Vue中的diff算法?

在js中,渲染真实DOM的开销是非常大的, 比如我们修改了某个数据,如果直接渲染到真实DOM, 会引起整个dom树的重绘和重排。那么有没有可能实现只更新我们修改的那一小块dom而不要更新整个dom呢?此时我们就需要先根据真实dom生成虚拟dom, 当虚拟dom某个节点的数据改变后会生成有一个新的Vnode, 然后新的Vnode和旧的Vnode作比较,发现有不一样的地方就直接修改在真实DOM上,然后使旧的Vnode的值为新的Vnode

diff的过程就是调用patch函数,比较新旧节点,一边比较一边给真实的DOM打补丁。在采取diff算法比较新旧节点的时候,比较只会在同层级进行。 在patch方法中,首先进行树级别的比较 new Vnode不存在就删除 old Vnode old Vnode 不存在就增加新的Vnode 都存在就执行diff更新 当确定需要执行diff算法时,比较两个Vnode,包括三种类型操作:属性更新,文本更新,子节点更新 新老节点均有子节点,则对子节点进行diff操作,调用updatechidren 如果老节点没有子节点而新节点有子节点,先清空老节点的文本内容,然后为其新增子节点 如果新节点没有子节点,而老节点有子节点的时候,则移除该节点的所有子节点 老新老节点都没有子节点的时候,进行文本的替换

updateChildrenVnode的子节点Vch和oldVnode的子节点oldCh提取出来。 oldCh和vCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和vCh至少有一个已经遍历完了,就会结束比较。

Vue底层实现原理

vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter和getter,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观

Observer(数据监听器):
Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher

Watcher(订阅者):
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:

  1. 在自身实例化时往属性订阅器(dep)里面添加自己
  2. 自身必须有一个update()方法
  3. 待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

Compile(指令解析器):
Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图

常用指令

  • v-if:判断是否隐藏;
  • v-for:数据循环出来;
  • v-bind:class:绑定一个属性;
  • v-model:实现双向绑定

v-model 是什么?有什么用呢?

参考回答: 一则语法糖,相当于 v-bind:value=”xxx” 和 @input,意思是绑定了一个 value 属性的值, 子组件可对 value 属性监听,通过$emit(‘input’, xxx)的方式给父组件通讯。自己实现 v-model 方式的组件也是这样的思路。

vue-loader解释下

vue-loader就是一个加载器,能把vue组件转化成javascript模块
为什么要转译vue组件?
可以动态的渲染一些数据,对三个标签template(结构)、style(表现)、script(行为)都做了优化,script中可以直接使用es6 style 也默认可以使用sass并且还给你提供作用域的选择,另外开发阶段还给你提供热加载
还可以如下使用:

<template src="../hello.vue"></template>
复制代码

导航钩子有哪些?它们有哪些参数

导航钩子翻译过来就是路由的生命周期函数(vue-router)
他其实主要分为两种全局和局部

全局的钩子函数
beforeEach:在路由切换开始时调用
afterEach:在路由切换离开是调用

局部到单个路由
beforeEnter

组件的钩子函数
beforeRouterEnter,
beforeRouterUpdate,
beforeRouterLeave

to:即将进入的目标对象
from:当前导航要高开的导航对象
next:是一个函数调用resolve执行下一步

谈谈对vue生命周期的理解及每个阶段做的事?

每个Vue实例在创建时都会经过一系列的初始化过程,vue的生命周期钩子,就是说在达到某一阶段或条件时去触发的函数,目的就是为了完成一些动作或者事件

  • create阶段:vue实例被创建
    beforeCreate: 创建前,此时data和methods中的数据都还没有初始化
    created: 创建完毕,data中有值,属性和方法的运算,初始化事件,$el属性还没有显示出来,未挂载,。

  • mount阶段: vue实例被挂载到真实DOM节点
    beforeMount:可以发起服务端请求,去数据,在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
    mounted: 此时可以操作DOM,在el被新创建的vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。

  • update阶段:当vue实例里面的data数据变化时,触发组件的重新渲染
    beforeUpdate :更新前,在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
    updated:更新后,在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

  • destroy阶段:vue实例被销毁
    beforeDestroy:在实例销毁之前调用。此时可以手动销毁一些方法,实例仍然完全可用。
    destroyed:销毁后,在实例销毁之后调用。调用后,所有的时间监听器都会被溢出,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

组件生命周期

生命周期(父子组件) 父组件beforeCreate –> 父组件created –> 父组件beforeMount –> 子组件beforeCreate –> 子组件created –> 子组件beforeMount –> 子组件 mounted –> 父组件mounted –>父组件beforeUpdate –>子组件beforeDestroy–> 子组件destroyed –> 父组件updated

加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

挂载阶段 父created->子created->子mounted->父mounted

父组件更新阶段 父beforeUpdate->父updated

子组件更新阶段 父beforeUpdate->子beforeUpdate->子updated->父updated

销毁阶段 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

computed与watch

通俗来讲,既能用 computed 实现又可以用 watch 监听来实现的功能,推荐用 computed, 重点在于 computed 的缓存功能 computed 计算属性是用来声明式的描述一个值依赖了其它的值,当所依赖的值或者变量 改变时,计算属性也会跟着改变; watch 监听的是已经在 data 中定义的变量,当该变量变化时,会触发 watch 中的方法。

watch 属性监听 是一个对象,键是需要观察的属性,值是对应回调函数,主要用来监听某些特定数据的变化,从而进行某些具体的业务逻辑操作,监听属性的变化,需要在数据变化时执行异步或开销较大的操作时使用

computed 计算属性 属性的结果会被缓存,当computed中的函数所依赖的属性没有发生改变的时候,那么调用当前函数的时候结果会从缓存中读取。除非依赖的响应式属性变化时才会重新计算,主要当做属性来使用 computed中的函数必须用return返回最终的结果 computed更高效,优先使用。data 不改变,computed 不更新。

使用场景 computed:当一个属性受多个属性影响的时候使用,例:购物车商品结算功能 watch:当一条数据影响多条数据的时候使用,例:搜索数据

组件中的data为什么是一个函数?

1.一个组件被复用多次的话,也就会创建多个实例。本质上,这些实例用的都是同一个构造函数。
2.如果data是对象的话,对象属于引用类型,会影响到所有的实例。所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。

为什么v-for和v-if不建议用在一起

1.当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费
2.这种场景建议使用 computed,先对数据进行过滤

v-for中key的作用

  • 当 Vue.js 用 v-for 更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。重复的key会造成渲染错误。

  • key的作用主要是为了让vue可以区分元素,更高效的对比更新虚拟DOM;

  • Vue在patch过程中判断两个节点是否是相同节点,key是一个必要条件,是唯一标识,如不定义key,Vue只能认为比较的两个节点是同一个,这导致了频繁更新元素,使得整个patch过程比较低效,影响性能;

  • 从源码中可以知道,Vue判断两个节点是否相同时主要判断两者的key和元素类型等,因此如果不设置key,它的值就是undefined,则可能永远认为这是两个相同的节点,只能去做更新操作,这造成了大量的dom更新操作,明显是不可取的。

vue组件的通信方式

  • 父子组件通信

    父->子props,子->父 $on、$emit 获取父子组件实例 parent、children Ref 获取实例的方式调用组件的属性或者方法 父->子孙 Provide、inject 官方不推荐使用,但是写组件库时很常用

  • 兄弟组件通信

    Event Bus 实现跨组件通信 Vue.prototype.$bus = new Vue()
    自定义事件

  • 跨级组件通信

    Vuex、$attrs、$listeners Provide、inject

双向绑定实现原理

当一个Vue实例创建时,Vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher重新计算,从而致使它关联的组件得以更新。

v-model的实现以及它的实现原理吗?

  1. vue中双向绑定是一个指令v-model,可以绑定一个动态值到视图,同时视图中变化能改变该值。v-model是语法糖,默认情况下相于:value和@input
  2. 使用v-model可以减少大量繁琐的事件处理代码,提高开发效率,代码可读性也更好
  3. 通常在表单项上使用v-model
  4. 原生的表单项可以直接使用v-model,自定义组件上如果要使用它需要在组件内绑定value并处理输入事件
  5. 我做过测试,输出包含v-model模板的组件渲染函数,发现它会被转换为value属性的绑定以及一个事件监听,事件回调函数中会做相应变量更新操作,这说明神奇魔法实际上是vue的编译器完成的。

nextTick的实现

  1. nextTickVue提供的一个全局API,是在下次DOM更新循环结束之后执行延迟回调,在修改数据之后使用$nextTick,则可以在回调中获取更新后的DOM
  2. Vue在更新DOM时是异步执行的。只要侦听到数据变化,Vue将开启1个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个watcher被多次触发,只会被推入到队列中-次。这种在缓冲时去除重复数据对于避免不必要的计算和DOM操作是非常重要的。nextTick方法会在队列中加入一个回调函数,确保该函数在前面的dom操作完成后才调用;
  3. 比如,我在干什么的时候就会使用nextTick,传一个回调函数进去,在里面执行dom操作即可;
  4. 我也有简单了解nextTick实现,它会在callbacks里面加入我们传入的函数,然后用timerFunc异步方式调用它们,首选的异步方式会是Promise。这让我明白了为什么可以在nextTick中看到dom操作结果。

nextTick的实现原理是什么?

在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后立即使用 nextTick 来获取更新后的 DOM。 nextTick主要使用了宏任务和微任务。 根据执行环境分别尝试采用Promise、MutationObserver、setImmediate,如果以上都不行则采用setTimeout定义了一个异步方法,多次调用nextTick会将方法存入队列中,通过这个异步方法清空当前队列。

使用过插槽么?用的是具名插槽还是匿名插槽或作用域插槽

vue中的插槽是一个非常好用的东西slot说白了就是一个占位的
在vue当中插槽包含三种一种是默认插槽(匿名)一种是具名插槽还有一种就是作用域插槽
匿名插槽就是没有名字的只要默认的都填到这里具名插槽指的是具有名字的

keep-alive的实现

作用:实现组件缓存,保持这些组件的状态,以避免反复渲染导致的性能问题。 需要缓存组件 频繁切换,不需要重复渲染

场景:tabs标签页 后台导航,vue性能优化

原理:Vue.js内部将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。

mixin

    mixin 项目变得复杂的时候,多个组件间有重复的逻辑就会用到mixin
    多个组件有相同的逻辑,抽离出来
    mixin并不是完美的解决方案,会有一些问题
    vue3提出的Composition API旨在解决这些问题【追求完美是要消耗一定的成本的,如开发成本】
    场景:PC端新闻列表和详情页一样的右侧栏目,可以使用mixin进行混合
    劣势:1.变量来源不明确,不利于阅读 2.多mixin可能会造成命名冲突 3.mixin和组件可能出现多对多的关系,使得项目复杂度变高
复制代码

vuex是什么?原理是什么?怎么使用?哪种功能场景使用它?

状态管理库,类似 React 中的 Rudux

关于vuex
vuex是一个专门为vue构建的状态集管理,主要是为了解决组件间状态共享的问题,强调的是数据的集中式管理,说白了主要是便于维护便于解耦所以不是所有的项目都适合使用vuex,如果你不是构建大型项目使用vuex反而使你的项目代码繁琐多余

vuex的核心
state mutations getters actions modules

Vuex的理解及使用场景

Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。

  1. Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候,

若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新
2. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation,
这样使得我们可以方便地跟踪每一个状态的变化
Vuex主要包括以下几个核心模块:

  1. State:定义了应用的状态数据
  2. Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),

就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,
且只有当它的依赖值发生了改变才会被重新计算
3. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数
4. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作
5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中

Vuex管理状态的机制

1)对Vuex基本理解1)是什么:Vuex是一个专为Vue.js应用程序开发的状态管理的vue插件2)作用:集中式管理vue多个组件共享的状态和从后台获取的数据states帮助组件管理状态的,基于state的还有一个计算属性数据getters,getters是从state中读取数据并计算的,他们两个的数据都是给组件去读,组件中读取state状态数据使用store.statemapState(),读取计算属性数据也有两个方法是store.state或mapState(),读取计算属性数据也有两个方法是store.getters和mapGetters();更新状态数据涉及到actions和mutations,通过$store.dispatch或mapAction()触发action的调用,然后actions会通过commit()触发mutations调用,mutations则直接更新状态;actions还可以同后台API进行双向通信。

为什么 Vuex的mutation中不能做异步操作?

Vuex中所有的状态更新的唯一途径都是mutation,异步操作通过 Action 来提交 mutation实现,这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。 每个mutation执行完成后都会对应到一个新的状态变更,这样devtools就可以打个快照存下来,然后就可以实现 time-travel 了。如果mutation支持异步操作,就没有办法知道状态是何时更新的,无法很好的进行状态的追踪,给调试带来困难。

单向数据流

“单向数据流”理念的极简示意:

  • state:驱动应用的数据源。
  • view:以声明方式将 state 映射到视图 。
  • actions:响应在 view 上的用户输入导致的状态变化

单向数据流过程:

图片[1]-面试官最喜欢问☞Vue、React专题-一一网
简单的单向数据流(unidirectional data flow)是指用户访问View,View发出用户交互的Action,在Action里对state进行相应更新。state更新后会触发View更新页面的过程。这样数据总是清晰的单向进行流动,便于维护并且可以预测

vue的diff算法

问题:渲染真实的DOM开销是很大的,修改了某个数据,如果直接渲染到真实dom上会引起整个DOM树的重绘和重排。
Virtual Dom 根据真实DOM生成的一个Virtual DOM,当Virtual DOM某个节点的数据改变后生成一个新的Vnode,然后Vnode和oldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode.
注意:在采取diff算法比较的时候,只会在同层级进行,不会跨层级比较。
当数据发生改变时,set方法会让调用Dep.notify()方法通知所有订阅者Watcher,订阅者就会调用patch函数给真实的DOM打补丁,更新响应的试图。

懒加载的原理

路由懒加载:
将不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

首先,可以将异步组件定义为返回一个 Promise 的工厂函数 (该函数返回的 Promise 应该 resolve 组件本身)。

第二,在 Webpack 2 中,我们可以使用动态 import 语法来定义代码分块点 (split point):
结合这两者,这就是如何定义一个能够被 Webpack 自动代码分割的异步组件。

图片懒加载原理实现:
getBoundingClientRect
DOM 元素包含一个getBoundingClientRect 方法, 执行该方法返回当前DOM节点相关的Css边框集合,其中有一个Top 属性代表当前DOM 节点距离浏览器窗口顶部的高度,只需判断top值是否小于当前浏览器窗口的高度(window.innerHeight),若小于说明已经进入用户视野,然后替换为真正的图片即可
另外使用getBoundingClientRect 作图片懒加载需要注意三点
1。 因为需要监听scroll 事件,不停的判断top 的值和浏览器高度的关系,请对监听事件进行函数节流
2. 当屏幕首次渲染时,不会触发scroll 事件,请主动调用一次事件处理程序,否则若用户不滚动则首屏的图片会一直使用懒加载的默认图片
3. 当所有需要懒加载 的图片都被加载完,需要移除事件监听,避免不必要的内存占用

intersectionObserver
intersectionObserver作为一个构造函数,传入一个回调函数作为参数,生成一个实例observer,
这个实例有一个observe方法用来观察指定元素是否进入了用户的可视范围,随即触发传入构造函数中的回调函数
同时给回调函数传入一个entries 的参数,记录着这个实例观察的所有元素的对象,其中intersectionRatio 属性表示图片已经进入可视范围百分比,大于0 表示已经有部分进入了用户视野
此时替换为真实的图片,并且调用实例的unobtrusive 将这个img 元素从这个实例的观察列表的去除

实现双向绑定 Proxy 与 Object.defineProperty 相比优劣如何?

  1. Object.definedProperty的作用是劫持一个对象的属性,劫持属性的getter和setter方法,在对象的属性发生变化时进行特定的操作。而 Proxy劫持的是整个对象。
  2. Proxy会返回一个代理对象,我们只需要操作新对象即可,而Object.defineProperty只能遍历对象属性直接修改。
  3. Object.definedProperty不支持数组,更准确的说是不支持数组的各种API,因为如果仅仅考虑arry[i] = value 这种情况,是可以劫持 的,但是这种劫持意义不大。而Proxy可以支持数组的各种API。
  4. 尽管Object.defineProperty有诸多缺陷,但是其兼容性要好于Proxy。

Vue项目中实现路由按需加载(路由懒加载)的3中方式:

  1. vue异步组件
  2. es6提案的import()
  3. webpack的require.ensure()

vue-router

  • 实现原理
  • hash(#)、history(会向服务器发请求吗)

Vue中的scoped实现原理

一个项目中的所有style标签全部加上了scoped,相当于实现了样式的模块化。
vue中的scoped属性的效果主要通过PostCSS转译实现,即:PostCSS给一个组件中的所有dom添加了一个独一无二的动态属性,然后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种做法使得样式只作用于含有该属性的dom——组件内部dom。

你知道Vue3有哪些新特性吗?它们会带来什么影响?

  • 性能提升

更小巧、更快速 支持自定义渲染器 支持摇树优化:一种在打包时去除无用代码的优化手段 支持Fragments和跨组件渲染

  • API变动

模板语法99%保持不变 原生支持基于class的组件,并且无需借助任何编译及各种stage阶段的特性 在设计时也考虑TypeScript的类型推断特性 重写虚拟DOM可以期待更多的编译时提示来减少运行时的开销 优化插槽生成可以单独渲染父组件和子组件 静态树提升降低渲染成本 基于Proxy的观察者机制节省内存开销

  • 不兼容IE11

检测机制更加全面、精准、高效,更具可调试式的响应跟踪

Vue3.0 编译做了哪些优化?

a. 生成 Block tree Vue.js 2.x 的数据更新并触发重新渲染的粒度是组件级的,单个组件内部 需要遍历该组 件的整个 vnode 树。在 2.0 里,渲染效率的快慢与组件大小成正相关:组件越大,渲染 效率越慢。并且,对于一些静态节点,又无数据更新,这些遍历都是性能浪费。 Vue.js 3.0 做到了通过编译阶段对静态模板的分析,编译生成了 Block tree。 Block tree 是一个将模版基于动态节点指令切割的嵌套区块,每个 区块内部的节点结构是固定的, 每个区块只需要追踪自身包含的动态节点。所以,在 3.0 里,渲染效率不再与模板大小 成正相关,而是与模板中动态节点的数量成正相关。

b. slot 编译优化 Vue.js 2.x 中,如果有一个组件传入了 slot,那么每次父组件更新的时候,会强制使子组 件 update,造成性能的浪费。 Vue.js 3.0 优化了 slot 的生成,使得非动态 slot 中属性的更新只会触发子组件的更新。 动态 slot 指的是在 slot 上面使用 v-if,v-for,动态 slot 名字等会导致 slot 产生运行时动 态变化但是又无法被子组件 track 的操作。 c. diff 算法优化

Vue3.0 是如何变得更快的?(底层,源码)

a. diff 方法优化 Vue2.x 中的虚拟 dom 是进行全量的对比。 Vue3.0 中新增了静态标记(PatchFlag):在与上次虚拟结点进行对比的时候,值对比 带有 patch flag 的节点,并且可以通过 flag 的信息得知当前节点要对比的具体内容化。
b. hoistStatic 静态提升 Vue2.x : 无论元素是否参与更新,每次都会重新创建。 Vue3.0 : 对不参与更新的元素,只会被创建一次,之后会在每次渲染时候被不停的复用。 c. cacheHandlers 事件侦听器缓存 默认情况下 onClick 会被视为动态绑定,所以每次都会去追踪它的变化但是因为是同一 个函数,所以没有追踪变化,直接缓存起来复用即可。 原作者姓名: 欧阳呀

2.0存在的问题 1.对原始数据进行克隆一份 2.需要分别给对象中的每个属性设置监听 3.0里面使用的是proxy监听对象中的所有的属性

Vue3.0 新特性

Composition API 与 React.js 中 Hooks 的异同点

a. React.js 中的 Hooks 基本使用 React Hooks 允许你 “勾入” 诸如组件状态和副作用处理等 React 功能中。Hooks 只能 用在函数组件中,并允许我们在不需要创建类的情况下将状态、副作用处理和更多东西 带入组件中。 React 核心团队奉上的采纳策略是不反对类组件,所以你可以升级 React 版本、在新组 件中开始尝试 Hooks,并保持既有组件不做任何更改。 案例:
useState 和 useEffect 是 React Hooks 中的一些例子,使得函数组件中也能增加状态和 运行副作用。 我们也可以自定义一个 Hooks,它打开了代码复用性和扩展性的新大门。

b. Vue Composition API 基本使用 Vue Composition API 围绕一个新的组件选项 setup 而创建。setup() 为 Vue 组件提供了 状态、计算值、watcher 和生命周期钩子。 并没有让原来的 API(Options-based API)消失。允许开发者 结合使用新旧两种 API (向下兼容)。

c. 原理 React hook 底层是基于链表实现,调用的条件是每次组件被 render 的时候都会顺序执行 所有的 hooks。 Vue hook 只会被注册调用一次,Vue 能避开这些麻烦的问题,原因在于它对数据的响 应是基于 proxy 的,对数据直接代理观察。(这种场景下,只要任何一个更改 data 的地 方,相关的 function 或者 template 都会被重新计算,因此避开了 React 可能遇到的性能 上的问题)。 React 中,数据更改的时候,会导致重新 render,重新 render 又会重新把 hooks 重新注 册一次,所以 React 复杂程度会高一些。
m

vue性能优化的方法 你都做过哪些Vue的性能优化?

编码阶段
尽量减少data中的数据及层次结构,data中的数据都会增加getter和setter,会收集对应的watcher
v-if和v-for不能连用
如果需要使用v-for给每项元素绑定事件时使用事件代理
SPA 页面采用keep-alive缓存组件
在更多的情况下,使用v-if替代v-show
key保证唯一
使用路由懒加载、异步组件
防抖、节流
第三方模块按需导入
长列表滚动到可视区域动态加载
图片懒加载
SEO优化
预渲染
服务端渲染SSR
打包优化
压缩代码
Tree Shaking/Scope Hoisting
使用cdn加载第三方模块
多线程打包happypack
splitChunks抽离公共文件
sourceMap优化
用户体验
骨架屏
PWA
还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。

vue与React比较

相同点:

  1. 都是组件化开发和虚拟DOM(Virtual Dom)
  2. 都支持通过props进行父子组件间数据通信
  3. 都支持数据驱动视图,不直接操作DOM,更新状态数据界面就自动更新
  4. 都支持服务端渲染
  5. 都支持native的方案,React的 React Native, Vue 的Weex

不同点:

  1. 数据绑定:vue实现了数据的双向绑定,react的数据流动是单向的
  2. 组件的写法不一样,React推荐的是JSX语法,也就是把HTML和CSS都写进JavaScript,即”all in js”;vue推荐的做法是webpack+vue+loader的单文件组件格式,即html,css,js写在同一个文件中;
  3. 数据状态管理不同,state对象在react应用中是不可变的,需要使用setState方法更新状态;在vue中state对象不是必须的,数据由data属性在vue对象中管理
  4. Virtual Dom不一样,vue会跟踪每个组件的依赖关系,不需要重新渲染整个组件树;而对于react而言,每当应用的状态改变时,全部的组件都会被渲染,所以react中会需要shouldComponentUpdate这个生命周期函数方法来进行控制
  5. React严格上只针对MVC的View层,Vue则是MVVM模式

React

React 组件通信方式

react组件间通信常见的几种情况:

    1. 父组件向子组件通信
    1. 子组件向父组件通信
    1. 跨级组件通信
    1. 非嵌套关系的组件通信

1)父组件向子组件通信

父组件通过 props 向子组件传递需要的信息。父传子是在父组件中直接绑定一个正常的属性,这个属性就是指具体的值,在子组件中,用props就可以获取到这个值

// 子组件: Child
const Child = props =>{
  return <p>{props.name}</p>
}

// 父组件 Parent
const Parent = ()=>{
    return <Child name="京程一灯"></Child>
}
复制代码

2)子组件向父组件通信

props+回调的方式,使用公共组件进行状态提升。子传父是先在父组件上绑定属性设置为一个函数,当子组件需要给父组件传值的时候,则通过props调用该函数将参数传入到该函数当中,此时就可以在父组件中的函数中接收到该参数了,这个参数则为子组件传过来的值

// 子组件: Child
const Child = props =>{
  const cb = msg =>{
      return ()=>{
          props.callback(msg)
      }
  }
  return (
      <button onClick={cb("京程一灯欢迎你!")}>京程一灯欢迎你</button>
  )
}

// 父组件 Parent
class Parent extends Component {
    callback(msg){
        console.log(msg)
    }
    render(){
        return <Child callback={this.callback.bind(this)}></Child>    
    }
}
复制代码

3)跨级组件通信

即父组件向子组件的子组件通信,向更深层子组件通信。

  • 使用props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递props,增加了复杂度,并且这些props并不是中间组件自己需要的。
  • 使用context,context相当于一个大容器,我们可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用context实现。
// context方式实现跨级组件通信 
// Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据

const BatteryContext = createContext();

//  子组件的子组件 
class GrandChild extends Component {
    render(){
        return (
            <BatteryContext.Consumer>
                {
                    color => <h1 style={{"color":color}}>我是红色的:{color}</h1>
                }
            </BatteryContext.Consumer>
        ) 
    }
}

//  子组件
const Child = () =>{
    return (
        <GrandChild/>
    )
}
// 父组件
class Parent extends Component {
      state = {
          color:"red"
      }
      render(){
          const {color} = this.state
          return (
          <BatteryContext.Provider value={color}>
              <Child></Child>
          </BatteryContext.Provider> 
          )
      }
}
复制代码

4)非嵌套关系的组件通信

即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。

    1. 可以使用自定义事件通信(发布订阅模式),使用pubsub-js
    1. 可以通过redux等进行全局状态管理
    1. 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
    1. 也可以new一个 Vue 的 EventBus,进行事件监听,一边执行监听,一边执行新增 VUE的eventBus 就是发布订阅模式,是可以在React中使用的;

setState 既存在异步情况也存在同步情况

1.异步情况 在React事件当中是异步操作

2.同步情况 如果是在setTimeout事件或者自定义的dom事件中,都是同步的

import React,{ Component } from "react";
class Count extends Component{
    constructor(props){
        super(props);
        this.state = {
            count:0
        }
    }

    render(){
        return (
            <>
                <p>count:{this.state.count}</p>
                <button onClick={this.btnAction}>增加</button>
            </>
        )
    }
    
    btnAction = ()=>{
        //不能直接修改state,需要通过setState进行修改
        //同步
        setTimeout(()=>{
            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count);
        })
    }
}

export default Count;

复制代码

3.同步情况 自定义dom事件

import React,{ Component } from "react";
class Count extends Component{
    constructor(props){
        super(props);
        this.state = {
            count:0
        }
    }

    render(){
        return (
            <>
                <p>count:{this.state.count}</p>
                <button id="btn">绑定点击事件</button>
            </>
        )
    }
    
    componentDidMount(){
        //自定义dom事件,也是同步修改
        document.querySelector('#btn').addEventListener('click',()=>{
            this.setState({
                count: this.state.count + 1
            });
            console.log(this.state.count);
        });
    }
}

export default Count;

复制代码

生命周期

安装
当组件的实例被创建并插入到 DOM 中时,这些方法按以下顺序调用:

constructor()
static getDerivedStateFromProps()
render()
componentDidMount()

更新中
更新可能由道具或状态的更改引起。当重新渲染组件时,这些方法按以下顺序调用:

static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()

卸载
当组件从 DOM 中移除时调用此方法:

componentWillUnmount()
复制代码

说一下 react-fiber

1)背景

react-fiber 产生的根本原因,是大量的同步计算任务阻塞了浏览器的 UI 渲染。默认情况下,JS 运算、页面布局和页面绘制都是运行在浏览器的主线程当中,他们之间是互斥的关系。如果 JS 运算持续占用主线程,页面就没法得到及时的更新。当我们调用setState更新页面的时候,React 会遍历应用的所有节点,计算出差异,然后再更新 UI。如果页面元素很多,整个过程占用的时机就可能超过 16 毫秒,就容易出现掉帧的现象。

2)实现原理

  • react内部运转分三层:

    • Virtual DOM 层,描述页面长什么样。
    • Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
    • Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。

Fiber 其实指的是一种数据结构,它可以用一个纯 JS 对象来表示:

const fiber = {
    stateNode,    // 节点实例
    child,        // 子节点
    sibling,      // 兄弟节点
    return,       // 父节点
}
复制代码
  • 为了实现不卡顿,就需要有一个调度器 (Scheduler) 来进行任务分配。优先级高的任务(如键盘输入)可以打断优先级低的任务(如Diff)的执行,从而更快的生效。任务的优先级有六种:

    • synchronous,与之前的Stack Reconciler操作一样,同步执行
    • task,在next tick之前执行
    • animation,下一帧之前执行
    • high,在不久的将来立即执行
    • low,稍微延迟执行也没关系
    • offscreen,下一次render时或scroll时才执行
  • Fiber Reconciler(react )执行过程分为2个阶段:

    • 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
    • 阶段二,将需要更新的节点一次过批量更新,这个过程不能被打断。
  • Fiber树:React 在 render 第一次渲染时,会通过 React.createElement 创建一颗 Element 树,可以称之为 Virtual DOM Tree,由于要记录上下文信息,加入了 Fiber,每一个 Element 会对应一个 Fiber Node,将 Fiber Node 链接起来的结构成为 Fiber Tree。Fiber Tree 一个重要的特点是链表结构,将递归遍历编程循环遍历,然后配合 requestIdleCallback API, 实现任务拆分、中断与恢复。

  • 从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情

React和Vue在虚拟DOM的diff 算法有什么不同

Portals

Portals 提供了一种一流的方式来将子组件渲染到存在于父组件的 DOM 层次结构之外的 DOM 节点中。结构不受外界的控制的情况下就可以使用portals进行创建

异步组件

// 异步懒加载
const Box = lazy(()=>import('./components/Box'));
// 使用组件的时候要用suspense进行包裹
<Suspense fallback={<div>loading...</div>}>
    {show && <Box/>}
</Suspense>
复制代码

immutable.js

immutable内部提供的所有数据类型,对其数据进行任意操作,操作得到的结果是修改后的值
并且修改后的值是一个新的对象,原来的对象没有发生任何变化。
immutable.js文档

github.com/immutable-j…

学习文档

rhadow.github.io/2015/05/10/…

const map1 = Map({a:1,b:2,c:3});
const map2 = map1.set('b',50);
console.log(map1);
console.log(map2);
复制代码

vuex 和 redux 之间的区别?

从实现原理上来说,最大的区别是两点:

Redux使用的是不可变数据,而Vuex的数据是可变的。Redux每次都是用新的state替换旧的state,而Vuex是直接修改

Redux在检测数据变化的时候,是通过diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过 getter/setter来比较的(如果看Vuex源码会知道,其实他内部直接创建一个Vue实例用来跟踪数据变化)

React 事件绑定原理

React并不是将click事件绑在该div的真实DOM上,而是在document处监听所有支持的事件,当事件发生并冒泡至document处时,React将事件内容封装并交由真正的处理函数运行。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。
另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件(SyntheticEvent)。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault。

react事件绑定原理

React 项目中有哪些细节可以优化?实际开发中都做过哪些性能优化

编译阶段 ->
路由阶段 ->
渲染阶段 ->
细节优化 ->
状态管理 ->
海量数据源,长列表渲染

编译阶段

① include 或 exclude 限制 loader 范围。

{
    test: /\.jsx?$/,
    exclude: /node_modules/,
    include: path.resolve(__dirname, '../src'),
    use:['happypack/loader?id=babel']
    // loader: 'babel-loader'
}
复制代码

② happypack多进程编译
除了上述改动之外,在plugin中

/* 多线程编译 */
new HappyPack({
    id:'babel',
    loaders:['babel-loader?cacheDirectory=true']
})
复制代码

③缓存babel编译过的文件

loaders:['babel-loader?cacheDirectory=true']

复制代码

④tree Shaking 删除冗余代码

⑤按需加载,按需引入。

路由懒加载,路由监听器

asyncRouter实际就是一个高级组件,将()=>import()作为加载函数传进来,然后当外部Route加载当前组件的时候,在componentDidMount生命周期函数,加载真实的组件,并渲染组件,我们还可以写针对路由懒加载状态定制属于自己的路由监听器beforeRouterComponentLoad和afterRouterComponentDidLoaded,类似vue中watch $route 功能。

// asyncRouter.js 文件
const routerObserveQueue = [] /* 存放路由卫视钩子 */
/* 懒加载路由卫士钩子 */
export const RouterHooks = {
  /* 路由组件加载之前 */
  beforeRouterComponentLoad: function(callback) {
    routerObserveQueue.push({
      type: 'before',
      callback
    })
  },
  /* 路由组件加载之后 */
  afterRouterComponentDidLoaded(callback) {
    routerObserveQueue.push({
      type: 'after',
      callback
    })
  }
}
/* 路由懒加载HOC */
export default function AsyncRouter(loadRouter) {
  return class Content extends React.Component {
    constructor(props) {
      super(props)
      /* 触发每个路由加载之前钩子函数 */
      this.dispatchRouterQueue('before')
    }
    state = {Component: null}
    dispatchRouterQueue(type) {
      const {history} = this.props
      routerObserveQueue.forEach(item => {
        if (item.type === type) item.callback(history)
      })
    }
    componentDidMount() {
      if (this.state.Component) return
      loadRouter()
        .then(module => module.default)
        .then(Component => this.setState({Component},
          () => {
            /* 触发每个路由加载之后钩子函数 */
            this.dispatchRouterQueue('after')
          }))
    }
    render() {
      const {Component} = this.state
      return Component ? <Component {
      ...this.props
      }
      /> : null
    }
  }
}
复制代码

使用

import AsyncRouter ,{ RouterHooks }  from './asyncRouter.js'
const { beforeRouterComponentLoad} = RouterHooks
const Index = AsyncRouter(()=>import('../src/page/home/index'))
const List = AsyncRouter(()=>import('../src/page/list'))
const Detail = AsyncRouter(()=>import('../src/page/detail'))
const index = () => {
  useEffect(()=>{
    /* 增加监听函数 */  
    beforeRouterComponentLoad((history)=>{
      console.log('当前激活的路由是',history.location.pathname)
    })
  },[])
  return <div >
    <div >
      <Router  >
      <Meuns/>
      <Switch>
          <Route path={'/index'} component={Index} ></Route>
          <Route path={'/list'} component={List} ></Route>
          <Route path={'/detail'} component={ Detail } ></Route>
          <Redirect from='/*' to='/index' />
       </Switch>
      </Router>
    </div>
  </div>
}
复制代码

受控性组件颗粒化 ,独立请求服务渲染单元

可控性组件颗粒化,独立请求服务渲染单元是笔者在实际工作总结出来的经验。目的就是避免因自身的渲染更新或是副作用带来的全局重新渲染。

shouldComponentUpdate ,PureComponent 和 React.memo ,immetable.js 助力性能调优

在这里我们拿immetable.js为例,讲最传统的限制更新方法,第六部分将要将一些避免重新渲染的细节。

  1. PureComponent 和 React.memo

React.PureComponent与 React.Component 用法差不多 ,但React.PureComponent 通过props和state的浅对比来实现 shouldComponentUpate()。如果对象包含复杂的数据结构(比如对象和数组),他会浅比较,如果深层次的改变,是无法作出判断的,React.PureComponent 认为没有变化,而没有渲染试图。

  1. shouldComponentUpdate

使用 shouldComponentUpdate()以让React知道当state或props的改变是否影响组件的重新render,默认返回ture,返回false时不会重新渲染更新,而且该方法并不会在初始化渲染或当使用 forceUpdate() 时被调用,通常一个shouldComponentUpdate 应用是这么写的。

//控制状态 仅当`state` 中 `data1` 发生改变的时候,重新更新组件。
shouldComponentUpdate(nextProps, nextState) {
  /* 当 state 中 data1 发生改变的时候,重新更新组件 */  
  return nextState.data1 !== this.state.data1
}

//控制props属性 仅当`props` 中 `data2` 发生改变的时候,重新更新组件。
shouldComponentUpdate(nextProps, nextState) {
  /* 当 props 中 data2发生改变的时候,重新更新组件 */  
  return nextProps.data2 !== this.props.data2
}
复制代码
  1. immetable.js

immetable.js 是Facebook 开发的一个js库,可以提高对象的比较性能,像之前所说的pureComponent 只能对对象进行浅比较,,对于对象的数据类型,却束手无策,所以我们可以用 immetable.js 配合 shouldComponentUpdate 或者 react.memo来使用。immutable 中

合理处理细节问题

  • ①绑定事件尽量不要使用箭头函数
//有状态组件
class index extends React.Component{
    handerClick=()=>{
        console.log(666)
    }
    handerClick1=()=>{
        console.log(777)
    }
    render(){
        return <div>
            <ChildComponent handerClick={ this.handerClick }  />
            <div onClick={ this.handerClick1 }  >hello,world</div>
        </div>
    }
}

//无状态组件
function index(){
   
    const handerClick1 = useMemo(()=>()=>{
       console.log(777)
    },[])  /* [] 存在当前 handerClick1 的依赖项*/
    const handerClick = useCallback(()=>{ console.log(666) },[])  /* [] 存在当前 handerClick 的依赖项*/
    return <div>
        <ChildComponent handerClick={ handerClick }  />
        <div onClick={ handerClick1 }  >hello,world</div>
    </div>
}
复制代码
  • ②循环正确使用key

无论是react 和 vue,正确使用key,目的就是在一次循环中,找到与新节点对应的老节点,复用节点,节省开销。

  • ③无状态组件hooks-useMemo 避免重复声明。

  • ④懒加载 Suspense 和 lazy

防止重复渲染

  • ① 学会使用的批量更新state
  • ② 合并state
  • ③ useMemo React.memo隔离单元
  • ④ ‘取缔’state,学会使用缓存。
  • ⑤ useCallback回调

使用状态管理

对于不变的数据,多个页面或组件需要的数据,为了避免重复请求,我们可以将数据放在状态管理里面。

海量数据优化

  1. 时间分片

时间分片的概念,就是一次性渲染大量数据,初始化的时候会出现卡顿等现象。我们必须要明白的一个道理,js执行永远要比dom渲染快的多。  ,所以对于大量的数据,一次性渲染,容易造成卡顿,卡死的情况。

setTimeout 可以用 window.requestAnimationFrame() 代替,会有更好的渲染效果。我们demo使用列表做的,实际对于列表来说,最佳方案是虚拟列表,而时间分片,更适合热力图,地图点位比较多的情况

class Index extends React.Component<any,any>{
    state={
       list: []
    }
    handerClick=()=>{
       this.sliceTime(new Array(40000).fill(0), 0)
    }
    sliceTime=(list,times)=>{
        if(times === 400) return 
        setTimeout(() => {
            const newList = list.slice( times , (times + 1) * 100 ) /* 每次截取 100 个 */
            this.setState({
                list: this.state.list.concat(newList)
            })
            this.sliceTime( list ,times + 1 )
        }, 0)
    }
    render(){
        const { list } = this.state
        return <div>
            <button onClick={ this.handerClick } >点击</button>
            {
                list.map((item,index)=><li className="list"  key={index} >
                    { item  + '' + index } Item
                </li>)
            }
        </div>
    }
}
复制代码
  1. 虚拟列表

虚拟列表 是解决长列表渲染的最佳方案。

为了防止大量dom存在影响性能,我们只对,渲染区和缓冲区的数据做渲染,,虚拟列表区 没有真实的dom存在。 缓冲区的作用就是防止快速下滑或者上滑过程中,会有空白的现象。

虚拟列表是按需显示的一种技术,可以根据用户的滚动,不必渲染所有列表项,而只是渲染可视区域内的一部分列表元素的技术。正常的虚拟列表分为 渲染区,缓冲区 ,虚拟列表区。

首屏加载:

-   首屏优化一般涉及到几个指标FP(First Paint 首次绘制)、FCP(First Contentful Paint 首次有内容的渲染)、FMP(First Meaningful Paint 首次有意义的绘制)、TTI(Time To interactive 可交互时间);要有一个良好的体验是尽可能的把FCP提前,需要做一些工程化的处理,去优化资源的加载
-   方式及分包策略,资源的减少是最有效的加快首屏打开的方式;
-   对于CSR的应用,FCP的过程一般是首先加载js与css资源,js在本地执行完成,然后加载数据回来,做内容初始化渲染,这中间就有几次的网络反复请求的过程;所以CSR可以考虑使用骨架屏及预渲染(部分结构预渲染)、suspence与lazy做懒加载动态组件的方式
-   当然还有另外一种方式就是SSR(服务端渲染)的方式,SSR对于首屏的优化有一定的优势,但是这种瓶颈一般在Node服务端的处理,建议使用stream流的方式来处理,对于体验与node端的内存管理等,都有优势;
-   不管对于CSR(客户端渲染)或者SSR(服务端渲染),都建议配合使用Service worker,来控制资源的调配及骨架屏秒开的体验
-   react项目上线之后,首先需要保障的是可用性,所以可以通过React.Profiler分析组件的渲染次数及耗时的一些任务,但是Profile记录的是commit阶段的数据,所以对于react的调和阶段就需要结合performance API一起分析;
-   由于React是父级props改变之后,所有与props不相关子组件在没有添加条件控制的情况之下,也会触发render渲染,这是没有必要的,可以结合React的PureComponent以及React.memo等做浅比较处理,这中间有涉及到不可变数据的处理,当然也可以结合使用ShouldComponentUpdate做深比较处理;
-   所有的运行状态优化,都是减少不必要的render,React.useMemo与React.useCallback也是可以做很多优化的地方;
-   在很多应用中,都会涉及到使用redux以及使用context,这两个都可能造成许多不必要的render,所以在使用的时候,也需要谨慎的处理一些数据;
-   最后就是保证整个应用的可用性,为组件创建错误边界,可以使用componentDidCatch来处理;
- 使用script 的 async 和 defer 属性,异步加载避免阻塞
复制代码

react 最新版本解决了什么问题 加了哪些东西

React 16.x的三大新特性 Time Slicing, Suspense,hooks

    1. Time Slicing(解决CPU速度问题)使得在执行任务的期间可以随时暂停,跑去干别的事情,这个特性使得react能在性能极其差的机器跑时,仍然保持有良好的性能
    1. Suspense (解决网络IO问题)和lazy配合,实现异步加载组件。 能暂停当前组件的渲染, 当完成某件事以后再继续渲染,解决从react出生到现在都存在的「异步副作用」的问题,而且解决得非
  • 的优雅,使用的是「异步但是同步的写法」,我个人认为,这是最好的解决异步问题的方式
    1. 此外,还提供了一个内置函数 componentDidCatch,当有错误发生时, 我们可以友好地展示 fallback 组件;可以捕捉到它的子元素(包括嵌套子元素)抛出的异常;可以复用错误组件。

如果这篇文章帮到了你,记得点赞?收藏加关注哦?,希望点赞多多多多…

文中如有错误,欢迎在评论区指正


往期文章

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享