[学习vue]全家桶的原理和实现
在正式进入之前先抛出几个问题:
1 vue-router能不能放在react中使用?
2 use vueRouter的时候发生了什么?
3 为什么要把router作为一个选项放在new vue 中?
4 为什么router-link router-view 不需要注册,就可以直接使用?
答案就在文章中~~
vue-router
Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。主要解决的问题就单页面应用的导航问题。
router的任务分析
- 解析routers选项
- 监控url变化
html5 history api /login
hash xx.html#login
//vue-router.js
//声明插件:vue插件需求实现一个install静态方法
let Vue; //保存vue构造函数引用 目的是不用吧vue打包进去
class KVueRouter{
}
//参数是vue构造函数
KVueRouter.install = function(_vue){
Vue = _Vue;
//实现一个混入
Vue.mixin({
beforeCreate(){
//获取KVueRouter实例并挂载到Vue.prototype
if(this.$options.router){
// 在跟组件beforeCreate时执行一次且只会执行一次
Vue.prototype.$router = this.$options.router;
}
}
})
}
这时候有一个疑问,为什么要写把router挂载的配置写在混入里,而不是直接写在install方法呢?
这是因为和实例的关系很大,我们先执行了Vue.use的方法,其实是执行了插件的install的方法,但是这时候,实例还不存在呢,那怎么办呢,我们只好退而求其次,把代码延后执行,延后到当beforeCreate的时候,才执行。
接下来我们需要注册两个全局组件 router-view 和 router-link
...
Vue.component('router-link',{})
Vue.component('router-view',{})
接下来来完成核心任务
class KVueRouter{
//解析routes
//监听事件
//声明组件
constructor(options){
this.$options = options;
this.routeMap = {}; // {'/index': {component: Index,...}}
//当前url需要响应式的
this.app = new Vue({
data : { current : '/'}
})
}
//初始化
init(){
//监听事件
this.bindEvents();
//解析routes
this.createRouteMap();
//声明组件
this.initComponent();
}
bindEvents(){
window.addEventListener('hashchange', this.onHashchange.bind(this))
}
onHashchange(){
this.app.current = window.location.hash.slice(1) || '/'
}
createRouteMap(){
//遍历用户配置路由数组
this.$options.routes.forEach(route => {
this.routeMap[route.path] = route;
})
}
initComponent(){
//转换目标: <a href = '/'>xx</a>
// <router-link to = '/'>
Vue.component('router-link', {
props: {
to: String
},
render(h){
// h(tag, data,children)
return h( 'a' , {
attrs:{href : '#' + this.to}
}, [ this.$slots.default ]) // 这里还可以放其他的具名插槽 甚至作用域插槽
// 也可以使用jsx
}
})
}
}
最后执行一下install方法,把KVueRouter导出一下
export default KVueRouter
这时候可以引入写好的文件,来调试一下了
//router.js
import Vue from 'vue'
// import Router from 'vue-router'
import Router from './kvue-router'
import Home from './views/Home.vue'
// 1.应用插件:做了什么?
Vue.use(Router) // use执行了插件install()
// 2.创建Router实例
export default new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: Home
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
}
]
})
在main.js中挂载router
//main.js
import router from './router'
new Vue({
router, // 配置router实例
render: h => h(App),
}).$mount("#app");
//app.vue
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view></router-view>
这时候我们可以实现路由跳转了,但是并不能真正的加载对应的组件,就差渲染内容组件了
router-view 这个组件负责实现渲染组件工作,我们拿出我们要渲染的component
// 获取path对应的Component将它渲染出来
Vue.component("router-view", {
render: (h) => {
const Component = this.routeMap[this.app.current].component;
return h(Component)
}
})
另外vue也像我们暴露了一个可以定义一个响应式数据的方法
const initial = window.location.hash.slice(1) || ‘/’
Vue.util.defineReactive(this,’current’,initial)
接下来,我们来总结一下
关于实现路由,我们要做的第一件事是实现一个插件install(),这个install方法,会把vue的构造函数传进来,拿到vue的构造函数之后,我们就可以做很多事情,接下来我们另一个要实现的kvuerouter里,监听事件,事件发生变化以后,我们要做的事情把this.app的current的改成新的hash,但是为什么修改完新的hash之后,会在router-view把对应的组件渲染出来呢,原因是只要render函数里面用到了某个响应式的数据,这个数据发生变化了,我们的组件就会重新执行render,这就是典型的依赖收集,意思就是说render函数里只要用的data里的东西,就会产生依赖,编辑器在执行render函数的时候,会先执行依赖收集的过程,先把依赖全部找到,vue里的current只要改变,和它相关的组件都会发生改变,也就会导致router-view的component的重新执行,界面就渲染了。
该示例没有解决嵌套路由的问题,我们可以参考一下官方的文档。
vuex数据管理
vuex是一个专门为vue.js应用开发的状态管理模式,集中式存储管理应用所有组件的状态。它是一个单项数据流的设计思想,它为了让数据可控,数据可追踪,设计出这样一个单项数据流, 我们在实践的时候,也要避免同时被父子组件操作的情况,维持这样一个单向的关系,怎么去维系呢,我们把一些通用的全局的数据,把他抽象到一个store里去保管,只能用不能改,如果想改数据,只能commit一个mutaions,或者dispath一个actions,让actions去commit一个mutaions,而且在vuex 里面必须实现一个数据的响应式,实现的方式也是利用了vuex的构造初始化的时候做了响应式。
vuex
核心概念
state状态,数据
- mutations更改状态的函数
- actions异步操作
- store包含以上概念的容器
状态和状态的变更
state保存数据状态,mutations用于修改状态,store.js
export default new Vuex.Store({
state: {count : 0},
mutations:{
increment(state){
state.count += 1;
}
}
})
vuex的任务分析
- 实现插件: $store挂载
- 实现store: 解析vuex配置,持有state,实现dispatch,commit,getters
- 借助vue实现数据响应式
//kvuex.js
let Vue;
class Store {
// 持有state,并使其响应化
// 实现commit和dispatch两个方法
constructor(options) {
// 数据响应式
// this.state是Vue实例,访问this.state.count
this.state = new Vue({ data: options.state });
this.mutations = options.mutations;
this.actions = options.actions;
this.getters = options.getters;
// bind this
this.commit = this.commit.bind(this);
this.dispatch = this.dispatch.bind(this);
this.getters = this.getters.bind(this);
}
// 实现commit:可以修改state中的数据
commit(type, arg) {
this.mutations[type](this.state, arg);
}
dispatch(type, arg) {
return this.actions[type](this, arg);
}
getters(getters){
//遍历getters选项,为this.getters定义property
//属性名就是选项中的key ,只需定义get函数保证只读性
Object.keys(getters).forEach(key =>{
Object.defineProperty(this.getters, key, {
get : () =>{
return getters[key](this.state)
}
})
})
}
// 声明插件install
// _Vue是形参:Vue构造函数,use会把它传进来
function install(_Vue) {
Vue = _Vue;
Vue.mixin({
beforeCreate() {
// this指的是组件实例
if (this.$options.store) {
Vue.prototype.$store = this.$options.store;
}
},
});
}
// 导出Vuex
export default { Store, install };
vuex和vuerouter的实现思想大致相同,以上在不考虑代码健壮性的前提下,来实现核心思想的。