VUE核心概念
vuex其实就是一个重新封装的new Vue 对象,他的动态响应数据就是data属性,而commit这些方法,都只是回调
vuex的构成
- state:提供一个响应式数据;
- Getter:借助Vue的计算属性computed来实现缓存;
- Mutation:更改state方法;
- Action:触发mutation 方法;
- Module:Vue.set 动态添加state 到响应式数据中;
Vuex的映射:
state(数据)、getters(计算属性)需要映射在computed实例中
mutations(同步操作放),actions(异步操作方法)等需要放在methods实例中
vuex运行机制
new vue =>init=>$mount=>render function =>vnode
init 对数据进行响应式化
render function 会被转化成 VNode 节点
obj: 目标对象
prop: 需要操作的目标对象的属性名
descriptor: 描述符
Object.defineProperty(obj,prop,descrptor)
Object.defineProperty 用来把对象变成可观察的
function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
enumerable:true, //属性可枚举
configurable:true, //属性可被修改或删除
get:function reactiveGetter(){
return val;
},
set:function reactiveSetter(newVal){
if(newVal===val) return ;
cb(newVal)
}
})
}
在上面再封装一层 observer 。这个函数传入一个
value(需要「响应式」化的对象),通过遍历所有属性的方式对该对象的每一个属性都通过 defineReactive 处理。 (注:实际上
observer 会进行递归调用,为了便于理解去掉了递归的过程)
function observer(obj){
if(!obj||(typeof obj!=='object')){
return
}
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}
class Vue{
constructor(options){
this._data=options.data;
observer(this._data)
}
}
订阅者dep (Dependency) 主要作用是存放watcher观察者对象
class Dep{
constructor(){
//用来存放watcher对象的数组
this.subs=[]
}
//在subs中添加一个watch对象
addSub(sub){
this.subs.push(sub)
}
//通知所有watcher对象视图更新
notify(){
this.subs.forEach(sub=>{
sub.update()
})
}
}
观察者watcher
class watcher{
constructor(){
/*在new一个watch对象时将该对象赋值给Dep.target,在get中用到*/
Dep.target=this;
}
update(){
console.log('视图更新啦)
}
}
修改一下 defineReactive 以及 Vue 的构造函数,来完成依赖收集。
function defineReactive(obj,key,val){
//一个Dep类对象
const dep=new Dep()
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get:function reactiveGetter(){
dep.addSub(Dep.target)
return val;
},
set:function reactiveSetter(newVal){
if(newVal===val) return;
dep.notify()
}
})
}
class Vue{
constructor(options){
this._data=options.data;
observer(this._data)
/*新建一个watch观察者对象,这时候Dep.target会指向这个watcher对象*/
new watcher()
//模拟render过程,触发test属性的get函数
console.log('render~',this._data.test)
}
}
数据状态更新时的差异diff及patch机制
const nodeOps={
setTextContent(text){
if(platform==='weex'){
node.parentNode.setAttr('value',text)
}else if(platform==='web'){
node.setTextContent=text
}
},
parentNode(){
},
removeChild(){
},
nextSibling(){
},
insertBefore(){
}
}
patch的diff算法,是通过同层的树节点进行比较,而非对树进行逐层遍历搜索,时间复杂度O(n)
patch的简单过程代码
function patch(oldVnode,vnode,parentElm){
if(!oldVnode){
addVnodes(parentElm,null,vnode,0,vnode.length-1)
}else if(!vnode){
removeVnodes(parentElm,oldVnode,0,oldVnode.length-1)
}else{
if(sameVnode(oldVnode,vnode)){
patchVnode(oldVNode,vnode)
}else{
removeVnodes(parentElm,oldVNode,0,oldVNode.length-1)
addVnodes(parentElm,null,vnode,0,vnode.length-1)
}
}
}
sameVnode的判断
function sameVnode(){
return (
a.key===b.key&&
a.tag===b.tag&&
a.isComment===b.isComment&&
(!!a.data)===(!!b.data) &&
sameInputType(a,b)
)
}
function sameInputType(a,b){
if(a.tag!=='input') return true
let i
const typeA=(i=a.data)&&(i=i.attrs)&&i.type
const typeB=(i=b.data)&&(i=i.attrs)&&i.type
return typeA===typeB
}
######对比相同的节点有哪些变化
function patchVnode(oldVnode,vnode){
if(oldVNode===vnode){
return
}
if(vnode.isStatic&&oldVnode.isStatic&&vnode.key===oldnode.key){
vnode.elm=oldVnode.elm;
vnode.componentInstance=oldVnode.componentInstance
return
}
const elm=vnode.elm=oldVNode.elm
const oldCh=oldVnode.children;
const ch=vnode.children
if(vnode.text){
nodeOps.setTextContent(elm,vnode.text)
}else
if(oldCh&&ch&&(oldCh!==ch)){
updateChildren(elm,oldCh,ch)
}else if(ch){
if(oldVnode.text) nodeOps.setTextContent(elm,'')
addVnodes(elm,null,ch,0,ch.length-1)
}else if(oldCh){
removeVnodes(elm,oldCh,0,oldCh.length-1)
}else if(oldVNode.text){
nodeOps.setTextContent(elm,'')
}
}
//updateChildren
function updateChildren(parentElm,oldCh,newCh){
let oldStartIdx=0;
let newStartIdx=0;
let oldEndIdx=oldCh.length-1
let oldStartVnode=oldCh[0]
let oldEndVnode=oldCh[oldEndIdx]
let newEndIdx=newCh.length-1
let newStartVnode=newCh[0]
let newEndVnode=newCh[newEndIdx]
let oldKeyToIdx,idxInOld,elmToMove,refElm;
while(oldStartIdx<=oldEndIdx&&newStartIdx<=newEndIdx){
if(!oldStartVnode){
oldStartVnode=oldCh[++oldStartIdx]
}else if(!oldEndVnode){
oldEndVnode=oldCh[--oldEndIdx]
}else if ( sameVnode(oldStartVnode,newStartVnode) ){
patchVnode(oldStartVnode,newStartVnode)
oldStartVnode=oldCh[++oldStartIdx]
newStartVnode=newCh[++newStartIdx]
}else if(sameVnode(oldEndVnode,newEndVnode)){
patchVnode(oldEndVnode,newEndVnode)
oldEndVnode=oldCh[--oldEndIdx]
newEndVnode=newCh[--newEndIdx]
}else if(sameVnode(oldStartVnode,newEndVnode)){
patchVnode(oldStartVnode,newEndVnode)
nodeOps.insertBefore(parentElm,oldStartVnode.elm,nodeOps.nextSibling(oldEndVnode.oldStartVnode=oldCh[++oldStartIdx]))
newEndVnode=newCh[--newEndIdx]
}else if(sameVnode(oldEndVnode,newStartVnode)){
patchVnode(oldEndVnode,newStartVnode)
nodeOps.insertBefore(parentElm,oldEndVnode.elm,oldStartVnode.elm)
oldEndVnode=oldCh[--oldEndIdx]
newStartVnode=newCh[++newStartIdx]
}else{
let elmToMove=oldCh[idxInOld]
if(!oldKeyToIdx) oldKeyToIdx=createKeyToOldIdx(oldCh,oldStartIdx,oldEndIdx)
idxInOld=newStartVnode.key?oldKeyToIdx[newStartVnode.key]:null
if(!idexInOld){
createElm(newStartVnode,parentElm);
newStartVnode=newCh[++newStartIdx]
}else{
elmToMove=oldCh[idxInOld]
if(sameVnode(elmToMove,newStartVnode)){
patchVnode(elmToMove,newStartVnode)
oldCh[idxInOld]=undefined
nodeOps.insertBefore(parentElm,newStartVnode.elm,oldStartVnode.elm)
newStartVnode=newCh[++newStartIdx]
}else{
createElm(newStartVnode,parentElm)
newStartVnode=newCh[++newStartIdx]
}
}
}
}
if(oldStartIdx>oldEndIdx){
refElm=(newCh[newEndIdx+1])?newCh[newEndIdx+1].elm:null;
addVnodes(parentElm,refElm,newCh,newStartIdx,newEndIdx)
}else if(newStartIdx>newEndIdx){
removeVnodes(parentElm,oldCh,oldStartIdx,oldEndIdx)
}
}
axios封装
配置axios
首先,创建一个Service.js,这里面存放的时axios的配置以及拦截器等,最后导出一个axios对象。我平常elementUI用的比较多,这里你也可以使用自己的UI库。
import axios from 'axios'
import { Message, Loading } from 'element-ui'
const ConfigBaseURL = 'https://localhost:3000/' //默认路径,这里也可以使用env来判断环境
let loadingInstance = null //这里是loading
//使用create方法创建axios实例
export const Service = axios.create({
timeout: 7000, // 请求超时时间
baseURL: ConfigBaseURL,
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
// 添加请求拦截器
Service.interceptors.request.use(config => {
loadingInstance = Loading.service({
lock: true,
text: 'loading...'
})
return config
})
// 添加响应拦截器
Service.interceptors.response.use(response => {
loadingInstance.close()
// console.log(response)
return response.data
}, error => {
console.log('TCL: error', error)
const msg = error.Message !== undefined ? error.Message : ''
Message({
message: '网络错误' + msg,
type: 'error',
duration: 3 * 1000
})
loadingInstance.close()
return Promise.reject(error)
})
封装请求
这里我再创建一个request.js,这里面放的是具体请求。
export function getConfigsByProductId(productId) {
return Service({
url: '/manager/getConfigsByProductId',
params: { productId: productId }
})
}
export function getAllAndroidPlugins() {
return Service({
url: '/manager/getAndroidPlugin ',
method: 'get'
})
}
export function addNewAndroidPlugin(data) {
return Service({
url: '/manager/addAndroidPlguin',
data: JSON.stringify(data)
})
}
在vue组件中使用
import {getAllAndroidPlugins,getConfigsByProductId,addNewAndroidPlugin} from '@/api/request.js'
getAllAndroidPlugins()
.then(res=>{
})
全局使用 在main.js中
import {Service} from '@/api/Service.js'
Vue.prototype.$axios=Service