DVA整合reducer,initialState、action、saga
app.model({
namespace: 'products',
state: {
list: [],
loading: false,
},
在dom ready之后运行
subscriptions: [
function(dispatch) {
dispatch({type: 'products/query'});
},
],
effects: {
['products/query']: function*() {
yield call(delay(800));
yield put({
type: 'products/query/success',
payload: ['ant-tool', 'roof'],
});
},
},
reducers: {
['products/query'](state) {
return { ...state, loading: true, };
},
['products/query/success'](state, { payload }) {
return { ...state, loading: false, list: payload };
},
},
});
介绍下这些 model 的 key :(假设你已经熟悉了 redux, redux-saga 这一套应用架构)
- namespace - 对应 reducer 在 combine 到 rootReducer 时的 key 值
- state - 对应 reducer 的 initialState
- subscription - elm@0.17 的新概念,在 dom ready 后执行,这里不展开解释,详见:A Farewell to FRP
- effects - 对应 saga,并简化了使用
- reducers
虚拟DOM
使用普通js来描述DOM结构
在react、vue等技术出现之前,我们要改变页面内容只能通过遍历查询dom树的方式找到要修改的dom然后修改样式或结构来达到更新dom的目的,这种方式非常消耗资源(操作dom会引起浏览器的回流和重绘)。但是如果建立一个与dom树相应的虚拟dom对象(js对象),对象嵌套的方式表示dom树及其层级结构,这样对dom的更改就变成了对js对象的操作,性能开销也变小。
Vue和React的区别
-
vue提供一些便捷的指令和属性 -
React使用JSX模板,允许HTML和js混写,Vue基于html的模板语法 -
在响应式方面,Vue的数据改动时,界面会自动更新,React需要调动setState方法。 ? 把两者称为Push-based和Pull-based。所谓Push-based就是说,改动数据之后,数据本身会把这个改动推送出去,告知渲染系统自动进行渲染。在React里面,它是一个Pull的形式,用户要给系统一个明确的信号说明现在需要重新渲染了,这个系统才会重新渲染
React
React响应式原理
- 开发者只需要关注数据,当状态改变时,React框架会自动根据新的状态重新渲染构建UI。
- React框架在接受到状态改变时,会根据当前渲染树,结合最新的状态改变通过Diff算法计算出树中改变的部分,只更新变化的部分,从而避免整棵树重构,提高性能。状态改变后React不会立即去计算并渲染DOM树变化的部分,react会在DOM的基础上建立一个抽象层,即虚拟DOM树,对数据和状态的任何变化都会自动且高效的同步到虚拟DOM,最后在批量同步到真实DOM上,而不是每次改变都去操作DOM。
React中的setState
? React中的state不能直接修改,需要调用setState()的方法来修改。
? setState执行的时候可以简单认为属于原生js执行的空间,那么属于同步,而被react处理过的空间属于异步空间,但是如果多次使用setState修改state,react为了提高性能会按批次更新state然后render即异步操作,所以setState()后立即取state中的值并不是更新之后的状态,如果想要取得更新之后的state可以在setState()的第二个参数callback中取得新值。setState()有两个参数,第一个参数可以是Object或者函数,当参数是Object时,对应的key,value就是更新后的值,如果参数是函数,setState()会将上一次更新的结果作为参数传入这个函数中。
React组件通信的方式
- 父组件向子组件传递props
- 父组件将一个回调函数传入子组件,子组件调用该函数可以向父组件通信
- 使用context可以避免组件嵌套传参,provider生产者产生数据,consumer消费者在各个组件中传递参数
- 利用组件共同的父组件通信
- redux,dva,mobx 在view中触发action,改变state,进而改变其他组件的view
- 引用ref可以获得子组件的实例
- Render Props 渲染的细节由父组件控制
Redux和Vuex的区别
- Vuex改进了Redux中的action和reducer函数,用mutations变化函数取代reducer,无需switch,只需要在对应的mutation函数改变state值就可以。
- Vuex由于Vue的自动重新渲染特性,不需要重新订阅渲染函数render,只要生成新的state就可以。
- Vuex数据流的顺序是View调用store.commit提交对应的请求到Store中对应的mutation函数 ,让store改变state(vue检测到数据变化自动渲染)
redux-saga中遇到的坑
? saga是一个系列的工作文件,拦截action执行,执行异步操作,put出一个新的action改变reducer
- saga不会强迫我们捕获异常
- generator调试环境糟糕,babel的source-map会错位
- action定义谨慎,避免action在saga和reducer之间重复触发
Hooks和redux
Hooks
? Hooks是在react16.8版本中出现的一个新功能,是一种函数,它可以实现组件的逻辑复用,可以让我们在函数组件中使用类组件中的状态和生命周期等功能,hook都是以use开头
- useState 创建状态 返回数组,第一个参数是状态,第二个是改变状态的函数
- useEffect 副作用函数(数据获取,页面渲染结束后执行的操作)第一个参数是执行的操作,第二个参数是依赖列表,如果没有第二个参数,则会在第一个渲染结束和每次更新渲染页面的时候都会执行该函数
- useRef 创建ref ,此索引在整个生命周期都不会变,可以获取组件或者元素的实例,可以做输入框聚焦和动画
- useMemo 可以做函数组件优化,只有当依赖改变时才会触发,和usCallback作用类似
- useCallback 类似useMemo
- useContext 获取上下文注入值 可以结构出provider和consumer两个方法 provider生产者传递数据,consumer消费者可以获取数据
- useLayoutEffect 和useEffect类似,在DOM更新之后执行,执行顺序早于useEfffect,DOM更新完以后才会渲染。
redux
redux是一个可预测的状态容器,实质是单向数据流的思想。
redux三大特性:单一数据流、状态state是只读的、状态修改事由纯函数完成
redux由action、reducer、store组成
- action是view发出的通知state发生变化的动作
- reducer 接收action和当前的state作为参数,是改变state的具体计算过程
- store 是保存数据的地方。 redux提供createStore这个函数来生成store
- store.getState 获取当前时刻的state
- store.dispatch view派发 action的方法
- store.subscribe 设置监听函数 可以监听state,当state改变时可以做相应的操作。
Fiber
? Fiber是React16中新的协调引擎,主要目的是使虚拟DOM进行增量式渲染,可以类比generator函数。fiber可以认为是最小的工作单元,提升对于动画,布局,手势等场景的适用性,核心功能:增量渲染,将渲染工作分解为多个区块并将其散落到每一帧中。
- 暂停工作,并回来
- 为不同类型任务分配等级
- 重用以前完成的工作
- 不需要的时候终止任务
为什么不建议在componentWillMount做Ajax操作?
在React16之后将会废弃这个钩子,虽然可以使用,但是会有警告,而且会增加渲染的时间出现白屏现象。在React16之后采用了Fiber架构,只有componentDidMount钩子是确定执行一次的,componentWillMount钩子有可能会执行多次。在render阶段,可能会被暂停,中止或重启。
React的diff算法规则是什么
diff算法即差异查找算法,对于HTML DOM结构就是tree的差异查找算法,只有在更新阶段才会有diff算法的应用,react为了降低时间复杂度是按照层比较新旧两个虚拟dom树。
-
tree diff 新旧两颗dom树,逐层对比的过程就是tree diff,当整颗dom树逐层对比完成后,那必然会找到需要更新的元素 -
component diff 在进行tree diff 的时候每一层都有自己的组件,组件级别的对比就是component diff ,如果对比前后组件的类型不同,则需要移除旧组件创建新的组件渲染到页面。react只会匹配类型相同的组件,如果A组件被B替换,react会直接删除A然后创建B组件;如果A组件转移到同层B组件上,那么A会 被销毁,在B组件下重新生成,以A为根节点的树整个都被重建,虽然会浪费性能,但实际上很少跨层移动dom节点,一般都是同层横向移动。 -
element diff 在进行组件对比时,如果两个组件类型相同,需要进行元素级别的对比,叫做element diff 对于列表渲染,react在创建时需要为每一项输入一个独一无二的key,方便进行diff运算,在比较新旧子元素的时候是通过key值来精确判断两个节点是否是同一个,如果没有key见谁就更新谁,消耗性能
styled-component的使用
首先引入styled import styled from ‘styled-compont’
在文件中通过 const name=styled.div`
样式
`
styled.dom标签名
例:
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
render(
<Title>
Hello styled-component
</Title>
);
React状态管理工具都有哪些,Redux actionCreator都有什么?
mobx、react等
Redux actionCreator用来创建action,通常只用来返回action对象
Vue和React中Diff算法的区别
都是忽略跨级比较,只做同级比较。vue diff时调动patch函数,参数是vnode和oldVnode,分别代表新旧节点。
- vue对比节点,当节点元素相同但是classname不同时就会被认为是不同类型的元素,会删除重建,react认为是同类型节点只是修改属性。
- vue的列表对比,采用的是两端到中间的对比方式,而react采用的是从左到右依次对比的方式。当一个集合只是把最后一个节点移动到第一个,react会把前面的节点依次移动,vue只会把最后一个节点移到第一个。总体上说vue更加高效。
为什么有时连续setState只有一次生效
如果连续setState同一个值,批量更新策略会对其进行覆盖,取最后一次执行,如果同时setState多个不同的值,在更新时会对其进行合并批量更新。如果想对上次setState后的结果进行下一次setState,可以让setState接收一个函数,这个函数用上一个state作为第一个参数,这次被更新的props作为第二个参数
React JSX组件渲染过程
- 使用JSX编写的组件后所有JSX代码通过babel转化为React.createELement执行
- createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,对传入的子节点进行处理,最终构成一个ReactElement对象,即所谓的虚拟DOM
- ReactDOM.render将生成好的虚拟DOM渲染到指定的容器上,采用了批处理、事务等机制并对特定浏览器进行性能优化最终转换为真实DOM
Redux中间件原理?异步中间件thunk和saga如何使用
redux添加中间件 applyMiddleware
redux中间件的执行时机在action发出和reducer执行之间,中间件的实质就是对redux中store.dispatch函数的再封装。(要保持reducer的纯净)
thunk
import {createStore, applyMiddleware} from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
export default createStore(
rootReducer,
applyMiddleware(thunk)
)
redux-thunk是把异步操作放在action里面,而saga是把异步逻辑拆分出来放在一个异步文件里面。
thunk检查action的类型,如果是函数就执行这个action,并把dispatch、getState, extraArgument 作为参数传递进去,否则就调用 next 让下一个中间件继续处理 action。
saga
redux-saga比redux-thunk更加强大,不止可以充当中间件的作用,可以实现多种代码效果
import $http from '@/utils/http'
import { put,takeEvery,call } from 'redux-saga/effects'
import {LOADLIST,GETLIST,LOADSHOES,GETSHOES,LOADCLOTHES,GETCLOTHES} from './actionType'
function* foo(){
let recommend=yield call($http.get,{
url:'recommend.json'
})
let shoes=yield call($http.get,{
url:'shoes.json'
})
let clothes=yield call($http.get,{
url:'clothes.json'
})
yield put({
type:LOADLIST,
list:recommend.data.data.hotList
})
yield put({
type:LOADSHOES,
shoes:shoes.data.data.list
})
yield put({
type:LOADCLOTHES,
clothes:clothes.data.data.list
})
}
function* saga(){
yield takeEvery(GETLIST,foo)
yield takeEvery(GETSHOES,foo)
}
export default saga
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import { helloSaga } from './sagas'
import reducer from 'xxxx'
const sagaMiddleware = createSagaMiddleware()
const store = createStore(
reducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(helloSaga)
异步调用
- sagas被实现为Genetator函数,他会yield对象到 redux-saga middleware
- yield的对象都是一类指令,可被middleware解释执行
- 当middleware取得一个yield后的Promise,middleware会暂停saga,直到该promise完成
- 一旦promise被reslove,middleware会恢复saga执行,直到遇到下一个yield
takeEvery可以执行多个saga
put 使用功能和dispatch一样,一般只在saga函数内部使用。
call和fork
这两个函数主要都是用来发起异步操作的(如发送请求),不同的是 call 发起的是阻塞操作,即必须等待该异步操作完全完成才能进行后续的操作,而 fork 是非阻塞的,因此可以使用 fork 来实现多个并发的请求操作(fork相当于生成了一个task——一个在后台运行的进程)
call(fn, …args):第一个参数是一个generator函数(也可以是一个返回Promise或任意其它值的普通函数),后面的参数都是依次传入这个函数的形参数值,返回值是promise的resolve结果
call默认是接收到promise成功标志时解除阻塞,但是如果是普通函数的话,可能达不到阻塞的效果。
React加载组件的方式
子组件、children、render函数
为什么使用自定义hooks
hooks都是use开头的函数,可以把公用的逻辑封装起来复用,缩减嵌套,实现代码拆分
设置父组件传值的类型
可以使用props-types这个插件
传值校验
子组件名.propTypes= {
avname= PropTypes.number.isRequired
content:PropTypes.string,
index:PRopTypes.number
}
设置默认值
子组件名.defaultProps = {
avname:'松岛枫’
}
React顶层DOM对象
- React.Children 组件中的节点
- React.cloneElement type(ReactElement),[props(object)],[children(ReactElement)] 克隆并返回一个新的ReactElement(内部子元素也会跟着克隆),新返回的元素会保留有旧元素的props,ref,key,也会集成新的props(只要在第二个参数中有定义)
- React.createElement…
根据原来的组件生成新的组件,添加属性
- HOC高阶组件
- 顶层API React.cloneEmelent
Mobx和redux的区别
Mobx
创建store,对store中的数据进行监听
constructor(title) {
makeObservable(this, {
finished: observable,
title: observable,
changeFinished: action
})
this.title = title
}
mobx是面向对象编程的状态管理库,在action中定义改变状态的函数,在store中集中管理state和action。mobx状态可变,可以直接修改。mobx没有一个全局状态树,状态分散在各个独立的store中。
mobx流程:view中触发action,在action中修改state,通过computed拿到state的计算值,自动触发对应的reactions,这里包含autorun,渲染视图等。
原理:使用Object.defineProperty拦截数据的访问,一旦值发生变化,将会调用react的render方法重新渲染视图或者触发autorun。
autorun
当autorun函数中依赖的可观察属性发生改变时,就会自动触发autorun函数的执行,可以执行一些副作用操作。它不同于computed,不会产生一个新值,可以用来打印日志等。
observable
observable装饰器可以装饰一个属性,当这个属性发生改变时就会触发相应的动作,这个属性的类型可以包括string、boolean、array、object等
oberver
当可观察属性改变时,调用render方法重新渲染视图
makeObservable
手动为store中的属性添加类型,有两个参数,第一个是当前环境的this,第二个参数是对象,在这个对象中为属性添加响应式或者指定action和computed,
key为属性,value是要指定的类型。
makeObservable(this, {
finished: observable,
title: observable,
changeFinished: action
})
makeAutoObservable
自动为store中的属性指定observable和action等
一个参数就是当前环境的this
constructor(todos) {
makeAutoObservable(this)
this.todos = todos
}
Redux和mobx的区别
- redux将数据保存在单一的store中,mobx的数据分散在多个store中
- redux使用 plainobject保存数据,需要手动出炉变化后的操作,mobxobservable保存数据,并且数据变化后自动处理响应操作
- redux状态不可变,不能直接修改状态,应该返回一个新的状态,使用纯函数;mobx状态可变,也可以直接修改
- mobx面向对象的编程,redux面向函数式编程。
为什么redux的reducer为什么要设计成不能直接修改原状态,而返回修新状态对象
- 源码上来说redux会对reducer返回的状态进行引用地址的比较,不同才更新,直接修改源码不会更新
- 设计角度,要知道redux返回的状态是否有改变,必须对状态对象进行深度比较,但是这样比较消耗性能,所以仅进行状态对象引用地址的比较,有开发者决定是否更新,返回新状态才更新。
React优化
- 使用纯组件
- 使用React.memo进行组件记忆(高阶组件)对于相同的输入不重复执行
- 在类组件中可以使用shouldComponentUpdate生命周期函数,决定是否重新渲染组件,使用immutable对象
- 不使用内联函数和内联样式属性,内联函数会在每次重新渲染时创建一个新的函数
- 列表函数添加key值
- 函数组件中可以使用useCallback和useMemo进行组件优化,依赖不改变不重复执行
React的事件代理
React 基于 Virtual DOM 实现了一个 SyntheticEvent (合成事件)层,我们所定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,它完全符合 W3C 标准,不会存在任何 IE 标准的兼容性问题。并且与原生的浏览器事件一样拥有同样的接口,同样支持事件的冒泡机制,我们可以使用 stopPropagation() 和 preventDefault() 来中断它。
react-router里的标签和标签有什么区别
link的跳转行为只会触发相匹配的页面内容更新,而不会刷新整个页面,他做了三件事情:
- 有onclick就执行onclick
- click的时候阻止a标签默认事件
- 根据href(就是to),用history跳转,只是连接改变,但是没有刷新页面。
a标签就是普通的超链接,用于从当前页面挑战到href指向的另一个页面
a标签默认事件禁止后做什么猜实现跳转
DOM操作获取所有的a标签,循环列表为每一个a添加click事件
let domArr=document.getElementsByTagName(‘a’)
[…domArr].forEach(item=>{
item.addEventListener(‘click’,function(){
location.href=this.href;
})
})
使用import时,webpack对node_modules里的依赖会做什么
- import只是一个引用,在没有用到的时候不会执行
- 需要执行的时候去模块里面取值,webpack根据引入方式去判断模块的类型,进行相关转译,babel会默认把ES6的模块转译为CommonJS规范,然后把node_modues里面的依赖打包成自执行函数,模块会传到数组里面,函数运作之后将模块通过module.exports导出
Vue
Object.defineProperty、Proxy
? vue2使用Object.defineProperty里面的setter和getter方法的观察者模式来实现响应式,但是Object.Property存在以下几种缺陷
-
无法检测到对象属性的新增或删除 -
无法检测数组的变化,只能通过重新构造数组的部分方法来实现响应式(push、pop、shift、splice、sort、reverse) vue3使用Proxy来代替Object.defineProperty实现了数据响应,Proxy的优势如下: -
Proxy可以直接监听对象而不是属性,可以直接监听数组的变化 -
Proxy有多大13种拦截方法,不限于apply、ownKyes、deleteProperty、has等,这些是Object.defineProperty不具有的。 -
Proxy返回的是一个新对象,我们可以只操作新对象达到目的,而Object.defineProperty只能遍历对象的属性直接修改属性 但是Proxy存在浏览器兼容的问题并且无法使用垫片(polyfill)弥补
Vue响应式数据原理
? vue的响应式主要利用了Object.defineProperty中的getter和setter方法的观察者模式来实现,在组件初始化时会给每一个data属性注册getter和setter,然后new一个自己的watcher对象,这时watcher会立即调用组件的render函数去生成虚拟DOM。调用render时就会用到data属性值,此时会触发getter函数,将当前的watcher函数注册进sub。当data属性值改变时会遍历sub里面的所有watcher对象,通知他们去重新渲染组件。
computed和watch区别
computed
- 支持缓存,只有当依赖数据发生改变才会重新计算
- 不支持异步,有异步操作时操作无效,无法监听数据变化
- 如果一个属性是由其他属性计算而来的,这属性依赖其他属性,是一个一对多或者一对一,一般用computed
- 如果computed属性值是函数,默认会走get方法,函数返回值就是属性的属性值;在computed中属性都有一个get和set方法,当数据变化时调用set方法。
watch
-
不支持缓存,数据变化直接触发相应的操作 -
支持异步函数 -
监听的函数接收两个参数,第一个是最新的值,第二个是输入之前的值 -
监听数据必须是data中声明过或者是父组件传递过来的props中的数据,watch有两个参数 ? immediate: ? 这样使用watch时有一个特点,就是当值第一次绑定的时候,不会执行监听函数,只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数,则就需要用到immediate属性。 比如当父组件向子组件动态传值时,子组件props首次获取到父组件传来的默认值时,也需要执行函数,此时就需要将immediate设为true new Vue({
el: '#root',
data: {
cityName: ''
},
watch: {
cityName: {
handler(newName, oldName) {
immediate: true
}
}
deep ? 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听。 ? 数组(一维、多维)的变化不需要通过深度监听,对象数组中对象的属性变化则需要deep深度监听。 new Vue({
el: '#root',
data: {
cityName: {id: 1, name: 'shanghai'}
},
watch: {
cityName: {
handler(newName, oldName) {
deep: true,
immediate: true
}
}
设置deep: true 则可以监听到cityName.name的变化,此时会给cityName的所有属性都加上这个监听器,当对象属性较多时,每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值,则可以做以下优化:使用字符串的形式监听对象属性: watch: {
'cityName.name': {
handler(newName, oldName) {
},
deep: true,
immediate: true
}
watch工作原理 watch在一开始初始化时,会读取一遍监听的数据的值,这个数据会被收集到watch的watcher,然后给watch设置的handler会被放进watcher的更新函数中,当数据改变时,通知watch的watcher进行更新,设置的handler就会被调用进行更新操作。
Vue组件间通信
-
父组件向子组件传值通过props -
子组件向父组件传值 通过事件的形式 $emit ? 子组件 ? changeTitle() {
this.$emit("titleChanged","子向父组件传值");
}
父组件 <app-header v-on:titleChanged="updateTitle" ></app-header>
methods:{
updateTitle(e){
this.title = e;
}
},
-
中央总线(bus总线)$emit / $on 通过一个空的vue实例作为中央事件总线,用它触发事件和监听事件,可以实现任何组件间通信。 ? <div id="itany">
<my-a></my-a>
<my-b></my-b>
<my-c></my-c>
</div>
<template id="a">
<div>
<h3>A组件:{{name}}</h3>
<button @click="send">将数据发送给C组件</button>
</div>
</template>
<template id="b">
<div>
<h3>B组件:{{age}}</h3>
<button @click="send">将数组发送给C组件</button>
</div>
</template>
<template id="c">
<div>
<h3>C组件:{{name}},{{age}}</h3>
</div>
</template>
<script>
var Event = new Vue();
var A = {
template: '#a',
data() {
return {
name: 'tom'
}
},
methods: {
send() {
Event.$emit('data-a', this.name);
}
}
}
var B = {
template: '#b',
data() {
return {
age: 20
}
},
methods: {
send() {
Event.$emit('data-b', this.age);
}
}
}
var C = {
template: '#c',
data() {
return {
name: '',
age: ""
}
},
mounted() {
Event.$on('data-a',name => {
this.name = name;
})
Event.$on('data-b',age => {
this.age = age;
})
}
}
var vm = new Vue({
el: '#itany',
components: {
'my-a': A,
'my-b': B,
'my-c': C
}
});
</script>
? -
vuex -
$attr $listeners 组件间嵌套传递参数 -
provide inject -
$parent $children 访问父/子实例 -
ref 获取组件实例
Vue-router原理
通过改变url在不重新请求页面的情况下,更新视图。有两种方式实现
-
Hash 通过hashChange监听url的变化,每次改变hash都会在浏览器访问历史中增加一个记录 -
History H5新增的方法 pushState与replaceState 可以将url替换并且不刷新页面 ? pushState将新路由添加到浏览器访问历史的栈顶 ? replaceState 将新路由添加到浏览器访问历史的栈顶,而是替换掉当前的路由
Vue组件懒加载
-
异步组件实现路由懒加载
component: resolve=>(require(["@/components/HelloWorld"],resolve))
-
es提出的import(推荐)
const HelloWorld = ()=>import("@/components/HelloWorld")
Vue的动态组件和异步组件
动态组件: Vue提供了一个特殊元素component用来挂在不同的组件,通过改变属性is的值来选择要挂载的组件。
<component :is="currentView"></component>
异步组件:在组件被需要时才开始加载渲染,并把结果缓存起来用于后再再次渲染
Vue.component(
'async-webpack-example',
() => import('./my-async-component')
)
Vue-router懒加载
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [{ path: '/foo', component: Foo }]
})
Vue运行机制
-
初始化 调用原型的_init()进行初始化,初始化生命周期,props,data,methods,computed,watch等,利用Object.definePropty()对data中的属性设置setter和getter函数,实现响应式和依赖收集 -
挂载组件 初始化之后调用$mount挂载组件 如果是运行时编译即不存在render函数但存在template需要进行编译步骤 -
编译 parse(解析)、optimize(标记静态节点做优化)、generate(转换成字符串) parse:利用正则将模板转换为抽象语法树(AST) optimize:标记静态节点,以后update的时候,diff算法可以跳过静态节点 generate:将静态语法树(AST)转换成字符串,供render去渲染DOM 经过这个步骤组件中就会存在render函数 -
响应式:在进行render渲染时会对data进行数据读取,会触发getter函数,把data中的属性进行依赖收集,将这些属性放到观察者(watcher)的观察队列中,当进行属性修改时就会触发setter函数,setter告诉观察者(watcher)数据变化,重新渲染视图,观察者调用update更新视图 -
虚拟DOM:render function转换为虚拟DOM,虚拟dom就是一个js对象。render function被转换为VNODe节点。 -
更新视图: 在updata时,执行patch将oldNODE传进去,通过diff算法跟VNODE进行比较算出差异然后更新视图。
template解析过程
- template模板经过parse()生成AST抽象语法树(使用正则解析截取),optimize对静态节点(和数据没有关系,不需要每次更新)优化(diff跳过静态节点,优化patch性能),generate生成render字符串,调用render函数生成DOM
- 调用new Watcher函数监听数据变化,调用render函数生成vnode
- 数据更新vnode会与oldnode做diff比较,然后更新dom
nextTick
Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。
Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。$nextTick 是在下次 DOM 更新循环结束之后执行延迟回调,在修改数据之后使用 $nextTick,则可以在回调中获取更新后的 DOM,
Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载已完成。
Vue.nextTick(callback) 使用原理:
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。 当你设置 vm.someData = ‘new value’,DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
@2.5后版本 $nextTick was using setIwmmediate > MessageChannel > setTimeout
@2.5前版本 $nextTick was using Promise > MutationObserver > setTimeout
https://www.cnblogs.com/purple-windbells/p/11731919.html
Vue优化
- 路由懒加载
- 开启服务器Gzip压缩技术,webpack 提供的
compression-webpack-plugin 进行压缩 - 使用cdn加速
- v-if和v-show、computed和watch
- 列表渲染添加key值
- 使用keep-aive进行组件缓存
- 第三方库按需引入 插件babel-plugin-import
- 服务端渲染 ssr
路由守卫
v-model原理
本质上是v-bind和v-on的结合体,绑定一个value通过v-on触发来更新数据。
主要依赖与Object.defineProperty()这个函数监听get和set事件
实现过程
首先对数据进行劫持监听,设置一个监听器Observer,监听所有的属性。如果属性发生变化就告诉订阅者Watcher是否需要更新。还要有一个消息订阅器Dep收集这些订阅者,在监听器Observer和订阅者Watcher之间统一管理。还需要指令解析器Compile对每个节点元素进行解析,将相关指令对应初始化成一个订阅者Watcher,并替换模板数据或者绑定的函数,Watcher接收到属性变化就执行相关函数更新视图。
1.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
2.实现一个订阅者Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
3.实现一个解析器Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。
流程图如下:
function objServer(obj){
let keys = Object.keys(obj);
keys.forEach((item)=>{
definedActive(obj,item,obj[item])
})
returnobj;
}
function definedActive(obj,item,val){
Object.defineProperty(obj,item,{
get(){
console.log(`${item}获取了`)
},
set(newVlaue){
val = newVlaue;
console.log(`${item}修改了`)
}
})
}
let obj = objServer({
a:1,
b:2
})
obj.a
obj.b
obj.a = 2;
obj.b = 3;
Vue3 proxy的原理
主要通过proxy对对象进行绑定监听处理,通过es6的 new map(自带set和get)对对象的属性进行处理,将要执行的函数匹配到存到对应的prop上,通过每次的访问触发get方法,进行存方法 的操作,通过修改可以触发set方法,接着执行回调监听的函数,达到修改视图和数据。
手写emit on发布订阅
let obj = {};
const $on = (name,fn)=>{
if(!obj[name]){
obj[name] = [];
}
obj[name].push(fn);
}
const $emit = (name,val)=>{
if(obj[name]){
obj[name].map((fn)=>{
fn(val);
});
}
}
const $off = (name,fn)=>{
if(obj[name]){
if(fn){
let index = obj[name].indexOf(fn);
if(index > -1){
obj[name].splice(index,1);
}
}else{
obj[name].length = 0;
}
}
}
export default {
$on,
$emit,
$off
}
服务器端渲染 SSR
半静态 后端解析字符串
nuxt 约定式路由
_name.vue动态路由
动态路由 越具体越先匹配
asyncData 异步加载数据返回的值会跟data合并
|