vuex原理解析
vuex
的设计理念是:集中状态管理,可预测状态变化
插件
在使用vuex
时,首先需要Vue.use(Vuex)
,也就是安装vuex
插件。
-
怎么实现一个插件?
很简单,对外暴露一个
install()
方法。 -
这个插件的主要功能是什么?
挂载
$store
,因为Vue.use()
先执行,此时还没有创建new Vue()
实例,所以使用mixin
混入延迟挂载。
function install(_Vue) {
//Vue.use() 可以接收到Vue实例
Vue = _Vue
Vue.mixin({
beforeCreate() {
// 因为使用混入,钩子在每个组件创建实例时都会调用
// 根实例才有该选项
if (this.$options.store) {
//挂载vue原型方便其他组件调用$store
Vue.prototype.$store = this.$options.store
}
}
})
}
复制代码
state 响应式状态管理
- 实现数据响应式的几种方式?
Object.defineProperty()
Vue.util.defineReactive()
Vue.observable()
new Vue({data(){}})
这里用的是第四种
new Vue({
data: {
//$$state为什么用$$不代理,外部不能访问
$$state:options.state
}
})
复制代码
state
封装
//只读state,可以获取数据
get state() {
return this._vm._data.$$state
}
set state(v) {
console.error('please use replaceState to reset state');
}
复制代码
实现commit()
dispatch()
方法
传入方法名和参数,去mutations
和actions
匹配
// 保存mutaions和actions选项
this._mutations = options.mutations;
this._actions = options.actions;
commit(type, payload) {
const entry = this._mutations[type]
if (!entry) {
console.error('unkown mutation type');
}
//传入state
entry(this.state, payload)
}
dispatch(type, payload) {
const entry = this._actions[type]
if (!entry) {
console.error('unkown action type');
}
//{commit}上下文对象就是当前实例this
entry(this, payload)
}
复制代码
绑定this
指向
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
复制代码
实现 getters
Vuex
的getters
则是借助vue
的计算属性computed
实现数据实时监听
this._wrappedGetters = options.getters
//外界通过$store.getters.xxx访问getters
this.getters = {};
//定义computed数据
const computed = {};
//获取当前this
const store =this;
Object.keys(this._wrappedGetters).forEach(key => {
//this指向发生改变,用上方定义好的this
const fn = store._wrappedGetters[key]
//computed是无参数函数,而getters是有参数函数,所以高阶函数包一下
computed[key] = function(){
return fn(store.state)
}
})
//指定getters为只读属性
Object.defineProperty(store.getters,key,{
get: () => store._vm[key]
})
this._vm = new Vue({
data: {
$$state: options.state // $$不代理,外部不能直接访问
},
computed
})
复制代码
vue-router原理
单页面程序中,url
发生变化,不刷新显示对应视图内容
实现插件,挂载$router
// 参数1是Vue.use调用时传入的
VueRouter.install = function(_Vue) {
Vue = _Vue;
// 1.挂载$router属性
//Vue.use()优先执行,此时new Vue()还没有执行,所以要延迟执行$router挂载
// 全局混入目的:延迟下面逻辑到router创建完毕并且附加到选项上时才执行
Vue.mixin({
//new Vue()第一个执行的钩子函数
beforeCreate() {
// 因为使用混入,钩子在每个组件创建实例时都会调用
// 根实例才有该选项
if (this.$options.router) {
//挂载vue原型方便其他组件调用$router
Vue.prototype.$router = this.$options.router;
}
},
});
复制代码
url 变化页面不刷新
hash
#/user/1
hash
模式会携带’#
‘,不会刷新浏览器,
通过监听hashchange
,监听url
变化
this.current = window.localtion.hash.slice(1) || "/";
// 监听hash变化
window.addEventListener("hashchange", () => {
console.log(this.current);
this.current = window.location.hash.slice(1);
});
复制代码
history
/user/1
history
正常的url
形式
通过监听popstate
,监听url
变化
数据响应式,url变化内容重新渲染
Vue.util.defineReactive
定义响应式数据
//定义响应式matched数组,存放当前路由匹配的嵌套组件
Vue.util.defineReactive(this,'matched',[]);
//遍历路由表routes
this.match();
match(routes){
//路由表
let routes = routes || this.$options.routes;
//递归遍历路由表
for(route of routes){
if(route.path === '/' && this.current === '/'){
//添加路由信息
this.matched.push(route);
return;
}
// /about/info
if(route.path !== '/' && this.current.indexOf(route.path) != -1){
//包含/about
this.matched.push(route);
//还有嵌套子路由,递归调用match
if(route.children.length > 0){
this.match(route.children)
}
return;
}
}
}
}
复制代码
实现两个全局组件 router-link 和 router-view
实现router-link
实现一个a
标签 <a href="https://juejin.cn/post/xxx">user</a>
Vue.component("router-link", {
props: {
to: {
type: String,
required: true,
},
},
render(h) {
// <a href="https://juejin.cn/post/to">xxx</a>
// return <a href={'#'+this.to}>{this.$slots.default}</a>
return h(
"a",
{
attrs: {
href: "#" + this.to,
},
},
this.$slots.default
);
},
});
复制代码
实现router-view
判断router-view
深度,实现路由嵌套;通过matched
数组渲染嵌套组件
Vue.component("router-view", {
render(h) {
//标记当前router-view深度
this.$vnode.data.routerView = true;
//深度标识
let depth = 0;
//向上查找routerView
let parent = this.$parent;
while(parent){
const vnodeData = parent.$vnode && parent.$vnode.data;
if(vnodeData.routerView){
//说明parent是个router-view
depth++;
}
parent = parent.$parent;
}
// 获取当前路由对应的组件
let component = null;
//matched是响应式的所以动态渲染路由
const route = this.$router.matched[depth];
if (route) {
component = route.component
}
console.log(this.$router.matched, component);
return h(component);
},
});
};
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END