一、相同点
1.都有组件化开发和Virtual DOM
2.都支持props进行父子组件间数据通信
3.都支持数据驱动视图,不直接操作真实的DOM,更新状态数据界面就自动更新
4.都支持服务器端渲染
5.都有支持native的方案,react的react native vue的weex
二、不同点
1.数据绑定:vue实现了数据的双向绑定,react数据流动是单项的。
2.组件写法不一样,react推荐的是JSX,也就是把HTML和css全部都写进JavaScript, 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模式
6.HoC和mixins
Vue组合不同功能的方式是通过mixin
React组合不同功能的方式是通过HoC(高阶组件)
7.组件通信间的区别
Vue中有三种方式可以实现组件通信:
父组件通过props向子组件传递数据或者回调,父给子:子接收props
子组件通过事件向父组件发送消息;子给父$emit,父接收@方法名
新增的provide/inject来实现父组件向子组件注入数据,可以跨越多个层级。
eventbus https://juejin.cn/post/6974184935804534815
React中也有对应的三种方式:
父组件通过props可以向子组件传递数据或者回调;
可以通过 context 进行跨层级的通信,
8.Vuex和Redux区别
从表面上来说,store注入和使用方式有一些区别。在Vuex中,$store被直接注入到了组件实例中,因此
可以比较灵活的使用:使用dispatch、commit提交更新,通过mapState或者直接通过this.$store来
读取数据。在Redux中,我们每一个组件都需要显示的用connect把需要的props和dispatch连接起来。
另外,Vuex更加灵活一些,组件中既可以dispatch action,也可以commit updates,而Redux中只
能进行dispatch,不能直接调用reducer进行修改。
从实现原理上来说,最大的区别是两点:Redux使用的是不可变数据,而Vuex的数据是可变的,因此,
Redux每次都是用新state替换旧state,而Vuex是直接修改。Redux在检测数据变化的时候,是通过
diff的方式比较差异的,而Vuex其实和Vue的原理一样,是通过getter/setter来比较的,这两点的区
别,也是因为React和Vue的设计理念不同。React更偏向于构建稳定大型的应用,非常的科班化。相比之
下,Vue更偏向于简单迅速的解决问题,更灵活,不那么严格遵循条条框框。因此也会给人一种大型项目用
React,小型项目用Vue的感觉。
9.监听数据变化的实现原理不同
Vue通过 getter/setter以及一些函数的劫持,能精确知道数据变化。
React默认是通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染。
为什么React不精确监听数据变化呢?这是因为Vue和React设计理念上的区别,Vue使用的是可变数据,
而React更强调数据的不可变,两者没有好坏之分,Vue更加简单,而React构建大型应用的时候更加鲁棒。
三、vue 生命周期
1>beforeCreate( 创建前 )
在实例初始化之后,el 和 data 并未初始化,因此无法访问methods, data, computed等上的方法和数据。
2>created ( 创建后 )
实例已经创建完成之后被调用,在这一步,实例已完成以下配置:数据观测、属性和方法的运算,
watch/event事件回调,完成了data 数据的初始化,el没有。 然而,挂在阶段还没有开始, $el属性
目前不可见,这是一个常用的生命周期,因为你可以调用methods中的方法,改变data中的数据,并且修
改可以通过vue的响应式绑定体现在页面上,,获取computed中的计算属性等等,通常我们可以在这里对
实例进行预处理,也有一些童鞋喜欢在这里发ajax请求,值得注意的是,这个周期中是没有什么方法来对
实例化过程进行拦截的,因此假如有某些数据必须获取才允许进入页面的话,并不适合在这个方法发请求,
建议在组件路由钩子beforeRouteEnter中完成
3>beforeMount
挂在开始之前被调用,相关的render函数首次被调用(虚拟DOM),实例已完成以下的配置: 编译模板,
把data里面的数据和模板生成html,完成了el和data 初始化,注意此时还没有挂在html到页面上。
4>mounted
挂在完成,也就是模板中的HTML渲染到HTML页面中,此时一般可以做一些ajax操作,mounted只会执行一次。
5>beforeUpdate
在数据更新之前被调用,发生在虚拟DOM重新渲染和打补丁之前,可以在该钩子中进一步地更改状态,不会
触发附加地重渲染过程
6>updated(更新后)
在由于数据更改导致地虚拟DOM重新渲染和打补丁只会调用,调用时,组件DOM已经更新,所以可以执行依
赖于DOM的操作,然后在大多是情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环,该
钩子在服务器端渲染期间不被调用
7>beforeDestroy(销毁前)
一般在这一步做一些重置的操作,比如清除掉组件中的定时器 和 监听的dom事件
8>destroyed(销毁后)
所以的事件监听器会被移出,所有的子实例也会被销毁,该钩子在服务器端
渲染期间不被调用
四、react生命周期
1>初始化阶段:
getDefaultProps: 获取实例的默认属性
getInitialState:获取每个实例的初始化状态
componentWillMount:组件即将被装载、渲染到页面上(在整个生命周期中只会触发一次)
render:组件在这里生成虚拟的 DOM 节点
componentDidMount:组件真正在被装载之后(在整个生命周期中只会触发一次)
2>运行中状态:
componentWillReceiveProps:组件将要接收到属性的时候调用
shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回 false,接收数据后不更
新,阻止 render 调用,后面的函数不会被继续执行了)
componentWillUpdate:组件即将更新不能修改属性和状态
render:组件重新描绘
componentDidUpdate:组件已经更新
3>销毁阶段:
componentWillUnmount:组件即将销毁
新增俩个状态
getDerivedStateFromProps(nextProps, prevState)
用于替换
componentWillReceiveProps
getSnapshotBeforeUpdate(prevProps, prevState)
用于替换
componentWillUpdate
5.为啥react 使用了 hook?hook 优于class的方面?
1.在组件之间复用状态逻辑很难
React 没有提供将可复用性行为“附加”到组件的途径,render props 和 高阶组件来解决此类问题,但是重新组织你的组件结构,这可能会很麻烦,使你的代码难以理解【嵌套地狱】,所以说React 需要为共享状态逻辑提供更好的原生途径,所以 就用了hook。
2.复杂组件变得难以理解
组件起初很简单,但是逐渐会被状态逻辑和副作用充斥。每个生命周期常常包含一些不相关的逻辑。例如,组件常常在 componentDidMount 和 componentDidUpdate 中获取数据。但是,同一个 componentDidMount 中可能也包含很多其它的逻辑,如设置事件监听,而之后需在 componentWillUnmount 中清除。相互关联且需要对照修改的代码被进行了拆分,而完全不相关的代码却在同一个方法中组合在一起。如此很容易产生 bug。
Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据) ,而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测。
3.难以理解的class
class 是学习 React 的一大屏障。你必须去理解 JavaScript 中 this 的工作方式,这与其他语言存在巨大差异。还不能忘记绑定事件处理器。
为了解决这些问题,Hook 使你在非 class 的情况下可以使用更多的 React 特性
6.HOC 与 mixin 的区别?
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
hoc:高阶组件是参数为组件,返回值为新组件的函数
HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用
mixin是:混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项
HOC可用于许多任务,例如:
-
代码重用,逻辑和引导抽象
-
渲染劫持
-
状态抽象和控制
-
Props 控制
-
data合并策略:数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先;
-
钩子函数合并策略:同名钩子函数将合并为一个数组,因此都将被调用。混入对象的钩子将在组件自身钩子之前调用;
-
值为对象的选项合并策略: 例如
methods、components和directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
总结: vue 的混入就是给组件添加功能,冲突就用合并不一样的合并策略解决;但是组件还是原来的组件,不能对template模板复用;
react 的 HOC, 其实就是通过函数,返回一个基本一样的组件,可以对组件的所有功能扩展,灵活性比较高;
react为什么放弃mixin,选用了hoc?
-
1.隐含了一些依赖,比如我在组件中写了某个 state 并且在 mixin 中使用了,就这存在了一个依赖关系。万一下次别人要移除它,就得去 mixin 中查找依赖
-
2.多个 mixin 中可能存在相同命名的函数,同时代码组件中也不能出现相同命名的函数,否则就是重写了,其实我一直觉得命名真的是一件麻烦事。。
-
3.雪球效应,虽然我一个组件还是使用着同一个 mixin,但是一个 mixin 会被多个组件使用,可能会存在需求使得 mixin 修改原本的函数或者新增更多的函数,这样可能就会产生一个维护成本
7.redux的工作原理:
redux 是将整个应用的state存储在一个公共的store文件当中,组件可以通过分发(dispatch)一个动作或者是行为(action)给这个公用的store,而不是直接去通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。这里我个人对的理解是,在我们的组件内部有个类似于监听器的东西,一旦监听到store中的值发生了改变就会刷新我们的页面。
redux 三大原则
- 唯一数据源
整个应用的数据存储在一个统一的状态树中,也就是我们前面所说的公共的store 文件。在组件都会从这个store中获取数据。
- 保持只读状态
state是只读的,唯一改变state的方法就是触发action,action是一个用于描述以发生时间的普通对象。
- 数据改变只能通过纯函数来执行
使用纯函数来执行修改,为了描述action如何改变state的,你需要编写reducers。
4、redux原理详细解析
下面详细介绍一下redux的工作原理
4.1 Store
redux中的数据是存储在一个状态树store中,所以说:
- store就是保存数据的地方,你可以把它看成一个数据,而且整个应用能有一个store。
- redux提供了createStore这个函数,用来生成store。
import {createStore} from 'redux'
const store=createStore(fn);
复制代码
4.2 State
state就是store中存储的数据,store里面可以拥有多个state,Redux规定一个state对应一个View,只要state相同,view就是一样的,反过来也是一样的,可以通过store.getState( )获取。
import {createStore} from 'redux'
const store=createStore(fn);
const state=store.getState()
复制代码
4.3 Action
state的改变会导致View的变化,上面我们说过redux中不能直接通过this.setState()修改state,为了使state发生改变,在redux中提供了一个对象Action,我们可以理解为一个行为或者是动作,也就是说这个action可以改变state,而且也是改变state的唯一方法。
const action={
type:'ADD_TODO', //action名称,必须存在
payload:'redux'
}
复制代码
4.4 store.dispatch( )
store.dispatch( )是view发出Action的唯一办法,这里解释一下,在view中,用户触发一个行为或者简单理解点击某一个按钮,这时候如果需要修改state值,就需要触发action,而store.dispatch接收一个Action作为参数,将它发送给store通知store来改变state。
const action = {
type:'ADD_TODO',
payload:'redux'
};
store.dispatch(action);
复制代码
4.5 Reducer
Store收到Action以后,必须给出一个新的state,这样view才会发生变化。这种state的计算过程就叫做Reducer。 Reducer是一个纯函数,他接收Action和当前state作为参数,返回一个新的state。
const reducer =(state,action)=>{
switch(action.type){
case ADD_TODO:
return newstate;
default return state
}
}
5.5 redux实现原理
const createStore = (reducer, initialState) => {
let state = initialState;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
return action
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
}
};
// 在创建仓库的时候,内部会先派发一个初始动作,目的是初始化state
dispatch({type:'@@REDUX_INIT'})
return { getState, dispatch, subscribe };
};
11.React Fiber
React Fiber可以理解为:
React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态
React内部实现的一套状态更新机制。支持任务不同优先级,可中断与恢复,并且恢复后可以复用之前的中间状态
在React15及以前,Reconciler采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。
为了解决这个问题,React16将递归的无法中断的更新重构为异步的可中断更新,由于曾经用于递归的虚拟DOM数据结构已经无法满足需要。于是,全新的Fiber架构应运而生
Fiber包含三层含义:
- 作为架构来说,之前
React15的Reconciler采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler。React16的Reconciler基于Fiber节点实现,被称为Fiber Reconciler。 - 作为静态的数据结构来说,每个
Fiber节点对应一个React element,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息。 - 作为动态的工作单元来说,每个
Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...)
这种将长任务分拆到每一帧中,像蚂蚁搬家一样一次执行一小段任务的操作,被称为时间切片(time slice)
cpu的瓶颈
解决CPU瓶颈的关键是实现时间切片,而时间切片的关键是:将同步的更新变为可中断的异步更新。
IO的瓶颈
网络延迟是前端开发者无法解决的。如何在网络延迟客观存在的情况下,减少用户对网络延迟的感知?
React给出的答案是将人机交互研究的结果整合到真实的 UI 中
react15架构中递归更新的缺点
由于递归执行,所以更新一旦开始,中途就无法中断。当层级很深时,递归更新时间超过了16ms,用户交互就会卡顿。
在上一节中,我们已经提出了解决办法——用可中断的异步更新代替同步的更新。
React16架构
React16架构可以分为三层:
- Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
- Reconciler(协调器)—— 负责找出变化的组件
- Renderer(渲染器)—— 负责将变化的组件渲染到页面上
可以看到,相较于React15,React16中新增了Scheduler(调度器) ,让我们来了解下他。
Scheduler(调度器)
既然我们以浏览器是否有剩余时间作为任务中断的标准,那么我们需要一种机制,当浏览器有剩余时间时通知我们。
其实部分浏览器已经实现了这个API,这就是requestIdleCallback (opens new window)。但是由于以下因素,React放弃使用:
- 浏览器兼容性
- 触发频率不稳定,受很多因素影响。比如当我们的浏览器切换tab后,之前tab注册的
requestIdleCallback触发的频率会变得很低
基于以上原因,React实现了功能更完备的requestIdleCallbackpolyfill,这就是Scheduler。除了在空闲时触发回调的功能外,Scheduler还提供了多种调度优先级供任务设置。
Reconciler(协调器)
我们知道,在React15中Reconciler是递归处理虚拟DOM的。让我们看看React16的Reconciler (opens new window)。
我们可以看见,更新工作从递归变成了可以中断的循环过程。每次循环都会调用shouldYield判断当前是否有剩余时间。
/** @noinline */
function workLoopConcurrent() {
// Perform work until Scheduler asks us to yield
while (workInProgress !== null && !shouldYield()) {
workInProgress = performUnitOfWork(workInProgress);
}
}
那么React16是如何解决中断更新时DOM渲染不完全的问题呢?
在React16中,Reconciler与Renderer不再是交替工作。当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟DOM打上代表增/删/更新的标记,类似这样:
export const Placement = /* */ 0b0000000000010;
export const Update = /* */ 0b0000000000100;
export const PlacementAndUpdate = /* */ 0b0000000000110;
export const Deletion = /* */ 0b0000000001000;
整个Scheduler与Reconciler的工作都在内存中进行。只有当所有组件都完成Reconciler的工作,才会统一交给Renderer。
Renderer(渲染器)
Renderer根据Reconciler为虚拟DOM打的标记,同步执行对应的DOM操作。
优先级调度
case ImmediatePriority:
case UserBlockingPriority:
case NormalPriority:
case LowPriority:
case IdlePriority:
不同优先级意味着什么?不同优先级意味着不同时长的任务过期时间
var timeout;
switch (priorityLevel) {
case ImmediatePriority:
timeout = IMMEDIATE_PRIORITY_TIMEOUT;
break;
case UserBlockingPriority:
timeout = USER_BLOCKING_PRIORITY_TIMEOUT;
break;
case IdlePriority:
timeout = IDLE_PRIORITY_TIMEOUT;
break;
case LowPriority:
timeout = LOW_PRIORITY_TIMEOUT;
break;
case NormalPriority:
default:
timeout = NORMAL_PRIORITY_TIMEOUT;
break;
}
var expirationTime = startTime + timeout;
其中:
// Times out immediately
var IMMEDIATE_PRIORITY_TIMEOUT = -1;
// Eventually times out
var USER_BLOCKING_PRIORITY_TIMEOUT = 250;
var NORMAL_PRIORITY_TIMEOUT = 5000;
var LOW_PRIORITY_TIMEOUT = 10000;
// Never times out
var IDLE_PRIORITY_TIMEOUT = maxSigned31BitInt;
可以看到,如果一个任务的优先级是ImmediatePriority,对应IMMEDIATE_PRIORITY_TIMEOUT为-1,那么
var expirationTime = startTime - 1;
则该任务的过期时间比当前时间还短,表示他已经过期了,需要立即被执行。