Vue补充总结(一)

2020-07-20  本文已影响0人  强某某

在哪个组件发布其实就是再哪个组件监听,只是书写的时候书写到了父组件

//子组件
<h1 @click="$emit('foo',msg)">{{ msg }}</h1>
//父组件
<HelloWorld msg="hello" @foo='onFoo' />
 onFoo(msg){
    console.log(msg);
}

观察者模式

class Bus{
    constructor(){
        this.callbacks={}
    }
    $on(name,fn){
        this.callbacks[name]=this.callbacks[name]||[];
        this.callbacks[name].push(fn);
    }
    $emit(name,args){
        if (this.callbacks[name]) {
            this.callbacks[name].forEach(cb=>cb(args));
        }
    }
}
Vue.prototype.$bus=new Bus();
//如果自己实现,则可以new Bus();
Vue.prototype.$bus=new Vue();//main.js中
this.$bus.on('foo',msg);
this.$bus.$emit('foo');

实践中可以使用Vue替换Bus,因为Vue实现了相关功能

祖先和后代之间传递,由于嵌套层数过多,props不切实际,provide inject使用场景就在此处;provide inject主要为高阶插件/组件库使用,一般不推荐直接使用在代码里面;provide inject不是响应式的,但是如果传递的是引用类型则修改内部是完全可以的,但是比如说只是传入的字符串这时候修改会报错的官方建议尽量传入不修改的值

//APP.vue
<template>
  <div id="app">
    <img src="./assets/logo.png">
    <HelloWorld msg="hello" @foo='onFoo' />
    <HelloWorld msg="world" @foo='onFoo' />
  </div>
</template>

<script>
import HelloWorld from "./components/HelloWorld";
export default {
  name: 'App',
  // provide:{
  //   haha:'haha'
  // },
  //provide可以传递this,但是不能上面直接写,指向会有问题,如下写法
  provide(){
    return{
      hehe:this,
      tua:this.tua
    }
  },
  data(){
    return{
      tua:'tua'
    }
  },
  components:{
    HelloWorld
  },
  methods:{
    onFoo(msg){
      console.log(msg);
    }
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
//HelloWorld.vue
<template>
  <div class="hello" @click="$parent.$emit('haha',msg)">
    <h1 @click="$emit('foo',msg)">{{ msg }}</h1>
    <h2>{{tua}}</h2>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props:{
    msg:String
  },
  inject:['tua'],
  created(){
    //父组件监听,父组件发送,此时是两个HelloWorld组件
    this.$parent.$on('haha',(info)=>{
      console.log(info);
    })
  }
}
</script>
<style scoped></style>

模拟实现element-ui的form组件并实现校验等功能


1.jpg

注意:自定义组件要实现v-model必须实现:value @input,因为v-model其实就是这两者的语法糖

//index.vue
<template>
  <div class="hello">
    <KForm :model="model" :rules="rules" ref="loginForm">
      <KFormItem label="用户名" prop="username">
        <KInput v-model="model.username"></KInput>
      </KFormItem>
      <!-- prop之所以传递,是因为在校验或者其他场景下需要通过this去获取实例,但是此时KFormItem有两个需要通过key区分 -->
      <KFormItem label="密码" prop="password">
        <KInput v-model="model.password" type="password"></KInput>
      </KFormItem>
    </KForm>
    <KFormItem>
      <button @click="onLogin">登录</button>
    </KFormItem>

    {{model}}
  </div>
</template>

<script>
import KInput from "./KInput";
import KFormItem from "./KFormItem";
import KForm from "./KForm";
export default {
  name: "HelloWorld",
  data() {
    return {
      model: {
        username: "tom",
        password: ""
      },
      rules: {
        username: [{ required: true, message: "用户名必填" }],
        password: [{ required: true, message: "密码必填" }]
      }
    };
  },
  components: {
    KInput,
    KFormItem,
    KForm
  },
  methods:{
    onLogin(){
      this.$refs.loginForm.validate((isValid)=>{
        if (isValid) {
          
        }else{
          console.log('有错');
        }
      })
    }
  }
};
</script>
<style scoped></style>
//KForm.vue
<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  props: {
    model: {
      type: Object,
      required: true
    },
    rules: {
      type: Object
    }
  },
  provide() {
    return {
      form: this //传递form实例给后代,比如formitem用来校验
    }
  },
  methods: {
    validate(cb) {
      //之所以要filter是因为内部有些节点不需要校验。例如登录,必须有prop属性才需要校验
      //注意此处不够健壮
      //map结果是若干promise结果
      const tasks = this.$children
        .filter(item => item.prop)
        .map(item => item.validate());
      //如果没有任何一个有异常,则会走到then,否则不会进入
      Promise.all(tasks)
        .then(() => {
          cb(true);
        })
        .catch(() => cb(false));
    }
  }
};
</script>

<style>
</style>
//KFormItem.vue
<template>
  <div>
      <label v-if="label">
          {{label}}
      </label>
      <slot></slot>
      <!-- 校验信息 -->
      <p v-if="errorMessage">{{errorMessage}}</p>
  </div>
</template>

<script>
import Schema from "async-validator";
export default {
    data(){
        return{
            errorMessage:''
        }
    },
    props:{
        label:{
            type:String,
            default:''
        },
        prop:String
    },
    //有些版本这么写就行,但是有些早期vue版本需要下面的写法
    // inject:["form"],
    inject:{
        form:{default:{}}
    },
    mounted(){
        this.$on('validate',()=>{
            this.validate()
        })
    },
    methods:{
        validate(){
            //执行组件校验
            //1.获取校验规则
           const rules=  this.form.rules[this.prop];
            //2.获取数据
            const value=  this.form.model[this.prop];
            //3. 执行校验-此时通过校验库去校验-async-validator   element就是用的这个库
            const desc={
                [this.prop]:rules
            }
            const schema=new Schema(desc);
            schema.validate({
                [this.prop]:value
            },errors=>{
                if (errors) {
                    this.errorMessage=errors[0].message;
                }else{
                    this.errorMessage='';
                }
            })
        }
    }
}
</script>

<style>
</style>
//KIput.vue
<template>
  <div>
      <!-- $attrs是存储的props之外的东西,v-bind就是把index.vue在Kinput其他属性展开,自然就会把type等传递过来 -->
      <input  :value="value" @input="onInput" v-bind="$attrs">
  </div>
</template>

<script>
export default {
    props:{
        value:{
            type:String,
            default:''
        }
    },
    inheritAttrs:false,//避免顶层容器继承属性,例如当外面type有类型password的时候,此div会继承这些类似的属性,false则不会继承
    methods:{
        onInput(e){
           //通知父组件数值变化,但是实际上此时是该组件监听该组件通知,之所以这样还麻烦的这么写,是因为符合vue的单向数据流,此时就是从父级到子 
            this.$emit('input',e.target.value)
            //通知FormItem校验
            this.$parent.$emit('validate');
        }
    }
}
</script>
<style></style>

单向数据流的优势:把副作用分离出来,好写测试,假如子组件可以修改父组件的数据,那么会导致其他依赖父组件的子组件都会受到影响

自定义组件的v-model

以下来自于官方文档
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。model选项可以用来避免这样的冲突:

Vue.component('base-checkbox', {
    model: {
      prop: 'checked',
      event: 'change'
    },
    props: {
      checked: Boolean
    },
    template: `
      <input
        type="checkbox"
        v-bind:checked="checked"
        v-on:change="$emit('change', $event.target.checked)"
      >
    `
  })

现在在这个组件上使用 v-model 的时候:

<base-checkbox v-model="lovingVue"></base-checkbox>

这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。

说明:意思在于,v-model不光可以用于input事件,可以通过自定义绑定任何事件

补充:@click.native的使用场景?记不住百度下

自定义提示组件

//create.js

import Vue from "vue";
//创建指定的组件实例,并挂载于body上面
export default function create(Component, props) {
    //1. 创建组件实例
    const vm = new Vue({
        render(h) {
            //h  createlement的简称
            return h(Component, { props })
        }
    }).$mount();
    const comp = vm.$children[0];
    //追加dom到body,注意comp是组件实例,不是html字符串不可以直接放入dom
    document.body.appendChild(vm.$el);
    //清理
    comp.remove = () => {
        document.body.removeChild(vm.$el);
        vm.$destroy();
    }
    return comp;
}
<template>
  <div v-if="isShow">
    <h3>{{title}}</h3>
    <p>{{message}}</p>
  </div>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: ""
    },
    message: {
      type: String,
      default: ""
    },
    duration: {
      type: Number,
      default: 0
    }
  },
  data() {
    return {
      isShow: false
    };
  },
  methods: {
    show() {
      this.isShow = true;
      setTimeout(() => {
        this.hide();
      }, this.duration);
    },
    hide() {
      this.isShow = false;
      this.remove();
    }
  }
};
</script>

<style>
</style>

简化版vue-router

let Vue;
class VueRouter {
    constructor(options) {
        this.$options = options;
        //创建一个路由path和route映射
        this.routeMap = {};
        //记录当前路径current需要响应式,利用vue响应式原理可以做到这点
        this.app = new Vue({
            data: {
                current: '/'
            }
        })
    }
    init() {
        //绑定浏览器的事件
        this.bindEvent();
        //解析路由配置
        this.createRouteMap(this.$options);
        //创建router-link和router-view
        this.initComponent();
    }
    bindEvent() {
        //如果不绑定this,则onHashChange内部this就是windows了,绑定了就是VueRouter实例
        window.addEventListener('hashchange', this.onHashChange.bind(this));
        window.addEventListener('load', this.onHashChange.bind(this));
    }
    onHashChange() {
        this.app.current = window.location.hash.slice(1) || '/';
    }
    createRouteMap(options) {
        options.routes.forEach(item => {
            //['/home']:{path:'/home',component:Home}
            this.routeMap[item.path] = item;
        })
    }
    initComponent() {
        //注册两个到全局,都可以使用
        Vue.component('router-link', {
            props: {
                to: String
            },
//此时没有箭头,则this指向就是router-link自己,所以this.$slots.default就是router-link包裹的东西
            render(h) {
                //目标是:<a :href="to">xxxx</a>
                // return <a href={this.to}>{this.$slots.default}</a>
                return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default) //this.$slots.default其实就是默认插槽,此时是xxxx
            }
        })
        //hash->current->render
        Vue.component('router-view', {
            //箭头函数保留this指向,当前this就是vuerouter的实例
            render:(h)=> {
                const Comp=this.routeMap[this.app.current].component;
                // console.log(1111,h(Comp));//VNode....
                return h(Comp);
            }
        })
    }
}
//把VueRouter变为插件-组件实现install就变为插件了
VueRouter.install = function (_Vue) {
    //Vue.use的时候其实就是传递过来vue的构造函数
    Vue = _Vue;
    //混入任务
    Vue.mixin({
        beforeCreate() {
            //这里的代码将来会在外面初始化的时候被调用,这样就实现了vue的扩展
            //this就是vue的实例,所有vue实例都会执行,但是这时候只希望根组件执行一次
            if (this.$options.router) {
                /**
                 * new Vue({
                      el: '#app',
                      router,
                      components: { App },
                      template: '<App/>'
                    })
                    其实就是这个router
                 */
                Vue.prototype.$router = this.$options.router;
//此时是全局混入,再最外面Vue实例化时候beforeCreate时候调用,全局混入在所有beforeCreate最先执行
                this.$options.router.init();
            }
        }
    })
}
//但是注意:不是所有的插件都需要混入实现,此时是因为特殊的引用关系和调用时机
export default VueRouter;
//Krouter.js
import HelloWorld from "@/components/HelloWorld";
import VueRouter from "./VueRouter";
import Vue from 'vue'
Vue.use(VueRouter);
export default new VueRouter({
    routes:[
        {path:'/',component:HelloWorld}
    ]
})
//main.js
import router from "./utils/krouter";

说明:这一行导入,在krouter中Vue.use(VueRouter)先执行,new VueRouter后执行,但是实际上,VueRouter.install内部的混入是在beforeCreate才执行的,所以顺序上new VueRouter先于混入内部的this.$options.router.init();

递归组件

递归组件其实就是内部还有自己,有两个条件,必须有name,同时必须有结束条件

<template>
  <div>
    <h3>{{data.title}}</h3>
    <!-- 必须有结束条件 -->
    <Node v-for="d in data.children" :key="d.id" :data="d"></Node>
  </div>
</template> <script>
export default {
  name: "Node", // name对递归组件是必要的
  props: {
    data: {
      type: Object,
      require: true
    }
  }
};
// 使⽤
// <Node :data="{id:'1',title:'递归组件',children:[{...}]}"></Node>
</script>

路由守卫

全局守卫,router.js

// 路由配置
{
    path: "/about",
    name: "about",
    // 需要认证,注意此时这个不是真的完成逻辑了,只能看作一个标记位
    meta: { auth: true }, 
    component: () => import(/* webpackChunkName: "about" */
        "./views/About.vue")
}
// 守卫
router.beforeEach((to, from, next) => {
    // 要访问/about且未登录需要去登录
    if (to.meta.auth && !window.isLogin) {
        if (window.confirm("请登录")) {
            window.isLogin = true;
            next(); // 登录成功,继续 
        } else {
            next('/');// 放弃登录,回⾸⻚
        }
    } else {
        next(); // 不需登录,继续 
    }
});

路由独享守卫

可以在某条路由内部写这个钩子

beforeEnter(to, from, next) {
    // 路由内部知道⾃⼰需要认证
    if (!window.isLogin) {
        // ...
    } else {
        next();
    }
}

组件内的守卫

export default {
 beforeRouteEnter(to, from, next) {},
 beforeRouteUpdate(to, from, next) {},
 beforeRouteLeave(to, from, next) {}
};

动态添加路由

利⽤$router.addRoutes()可以实现动态路由添加,常⽤于⽤户权限控制

// router.js
// 返回数据可能是这样的
//[{
// path: "/",
// name: "home",
// component: "Home", //Home
//}]
// 异步获取路由
api.getRoutes().then(routes => {
    const routeConfig = routes.map(route => mapComponent(route));
    router.addRoutes(routeConfig);
})
// 映射关系
const compMap = {
//也可以直接 'Home':Home
//但是下面是懒加载形式,更加优雅而且避免不需要的打包
    'Home': () => import("./view/Home.vue")
}
// 递归替换
function mapComponent(route) {
    route.component = compMap[route.component];
    if (route.children) {
        route.children = route.children.map(child => mapComponent(child))
    }
    return route
}

面包屑实现

利⽤$route.matched可得到路由匹配数组,按顺序解析可得路由层次关系

// Breadcrumb.vue
watch: {
    $route() {
        // [{name:'home',path:'/'},{name:'list',path:'/list'}]
        console.log(this.$route.matched);
        // ['home','list']
        this.crumbData = this.$route.matched.map(m => m.name)
    }
}
//如果无法触发:加上immediate:true   //一开始执行一次

彻底明白VUE修饰符sync

//父组件
<template>
    <div>
        <input type="button"
               value="我是父组件中的按钮"
               @click="show">
        <child @upIsShow="changeIsShow" v-show="isShow"/>
    </div>
</template>
<script>
    import child from "@/components/child"
    export default {
        data() {
            return {
                isShow:false
            }
        },
        components:{
            child
        },
        methods:{
            show(){
                this.isShow=true;
            },
            changeIsShow(bol){
                this.isShow=bol;
            }
        }
    }
</script>
//子组件
<template>
    <div>
         我是一个子组件,我在红色的海洋里!
        <input type="button" value="点我隐身" @click="upIsShow">
    </div>
</template>
<script>
    export default {
        methods:{
            upIsShow(){
                this.$emit("upIsShow",false);
            }
        }
    }
</script>

如果我要将父组件中的事@upIsShow修改为@update:isShow不违法吧:

<child @update:isShow="changeIsShow" v-show="isShow"/>

子组件的emit自然也要做对应调整:

upIsShow(){
    this.$emit("update:isShow",false);
}

那么如果现在我将父组件的changeIsShow直接写成匿名函数,也能运行吧:

<child @update:isShow="function(bol){isShow=bol}" v-show="isShow"/>

现在我将那匿名函数改成箭头函数,不过分吧:

<child @update:isShow="bol=>isShow=bol" v-show="isShow"/>

最后我将上面那行代码做最后一次修改:

<child :isShow.sync="isShow" v-show="isShow"/>

至此终于涉及到了sync了。以上代码:isShow.sync="isShow"其实是 @update:isShow="bol=>isShow=bol"语法糖。是其一种简写形式。

完整代码

//父组件
<template>
    <div>
        <input type="button"
               value="我是父组件中的按钮"
               @click="show">
        <child :isShow.sync="isShow" v-show="isShow"/>
    </div>
</template>
<script>
    import child from "@/components/child"
    export default {
        data() {
            return {
                isShow:false
            }
        },
        components:{
            child
        },
        methods:{
            show(){
                this.isShow=true;
            },
            changeIsShow(bol){
                this.isShow=bol;
            }
        }
    }
</script>
//子组件
<template>
    <div>
         我是一个子组件,我在红色的海洋里!
        <input type="button" value="点我隐身" @click="upIsShow">
    </div>
</template>
<script>
    export default {
        methods:{
            upIsShow(){
                this.$emit("update:isShow",false);
            }
        }
    }
</script>

如果没有sync则父组件就需要在html上面写函数或者在父组件method里面写函数,但是针对上面这种简单通信的逻辑,其实sync可以省去父组件部分逻辑

插件

// 插件定义
MyPlugin.install = function (Vue, options) {
    // 1. 添加全局⽅法或属性
    Vue.myGlobalMethod = function () {
        // 逻辑...
    }
    // 2. 添加全局资源
    Vue.directive('my-directive', {
        bind(el, binding, vnode, oldVnode) {
            // 逻辑...
        }
    ...
    })
    // 3. 注⼊组件选项
    Vue.mixin({
        created: function () {
            // 逻辑...
        }
        ...
        })
    // 4. 添加实例⽅法
    Vue.prototype.$myMethod = function (methodOptions) {
        // 逻辑...
    }
}
// 插件使⽤
Vue.use(MyPlugin)

混入

// 定义⼀个混⼊对象
var myMixin = {

    created: function () {
        this.hello()
    },
    methods: {
        hello: function () {
            console.log('hello from mixin!')
        }
    }
}
// 定义⼀个使⽤混⼊对象的组件
var Component = Vue.extend({
    mixins: [myMixin]
})

render函数详解

⼀些场景中需要 JavaScript的完全编程的能⼒,这时可以⽤渲染函数,它⽐模板更接近编译器

render(h) {
    return h(tag, { ...}, [children])
}

h其实就是createElement函数

{
    // 与 `v-bind:class` 的 API 相同,
    // 接受⼀个字符串、对象或字符串和对象组成的数组
    'class': {
        foo: true,
            bar: false
    },
    // 与 `v-bind:style` 的 API 相同,
    // 接受⼀个字符串、对象,或对象组成的数组
    style: {
        color: 'red',
            fontSize: '14px'
    },
    // 普通的 HTML 特性
    attrs: {
        id: 'foo'
    },
    // 组件 prop
    props: {
        myProp: 'bar'
    },
    // DOM 属性
    domProps: {
        innerHTML: 'baz'
    },
    // 事件监听器在 `on` 属性内,
    // 但不再⽀持如 `v-on:keyup.enter` 这样的修饰器。
    // 需要在处理函数中⼿动检查 keyCode。
    on: {
        click: this.clickHandler
    },
}

函数式组件

组件若没有管理任何状态,也没有监听任何传递给它的状态,也没有⽣命周期⽅法,只是⼀个接受⼀些prop 的,可标记为函数式组件,此时它没有上下⽂

Vue.component('my-component', {
//标记为函数式组件
  functional: true,
  // Props 是可选的
  props: {
    // ...
  },
  // 为了弥补缺少的实例
  // 提供第二个参数作为上下文
  render: function (createElement, context) {
    // ...
  }
})

Vuex数据管理

TIM截图20200722140212.jpg

核心概念

状态和状态变更

state保存数据状态,mutations⽤于修改状态,store.js

export default new Vuex.Store({
    state: { count: 0 },
    mutations: {
        increment(state, n = 1) {
            state.count += n;
        }
    }
})

使⽤状态,vuex/index.vue

<template>
    <div>
        <div>冲啊,⼿榴弹扔了{{ $store.state.count }}个</div>
        <button @click="add">扔⼀个</button>
 </div>
</template > <script>
    export default {
        methods: {
        add() {
        this.$store.commit("increment");
 }
 }
};
</script>

派⽣状态 - getters

从state派⽣出新状态,类似计算属性

export default new Vuex.Store({
    getters: {
        score(state) {
            return `共扔出:${state.count}`
        }
    }
})

登录状态⽂字,App.vue

<span>{{$store.getters.score}}</span>

mutation喝action区别

mutations 类似于事件,用于提交 Vuex 中的状态 state
action 和 mutations 也很类似,主要的区别在于mutations 只能是同步操作,,action `==可以包含==`异步操作,而且可以通过 action 来提交 mutations
mutations 有一个固有参数 state,接收的是 Vuex 中的 state 对象

action 也有一个固有参数 context,但是 context 是 state 的父级,包含 state、getters等等

Vuex 的仓库是 store.js,将 axios 引入,并在 action 添加新的方法

分发调用action:

this.$store.dispatch('action中的函数名',发送到action中的数据)

在组件中提交 Mutation:
this.$store.commit(“mutation函数名”,发送到mutation中的数据)

在action中提交mutation :
actions: {
    increment (context) {    //官方给出的指定对象, 此处context可以理解为store对象
      context.commit('increment');
    }
  }
  

总之:
mutation使用是: this.$store.commit("add");
action使用是: this.$store.dispatch("addSync");

动作 - actions

复杂业务逻辑,类似于controller

export default new Vuex.Store({
 actions: {
 incrementAsync({ commit }) {
 setTimeout(() => {
 commit("increment", 2);
 }, 1000);
 }
 }
})

使⽤actions:

<template>
    <div id="app">
        <div>冲啊,⼿榴弹扔了{{ $store.state.count }}个</div>
        <button @click="addAsync">蓄⼒扔俩</button>
 </div>
</template > <script>
    export default {
        methods: {
        addAsync() {
        this.$store.dispatch("incrementAsync");
 }
};
</script>

模块化

按模块化的⽅式编写代码,store.js

const count = {
    //必须开启名命空间
    namespaced: true,
    // ...
};
export default new Vuex.Store({
    modules: { a: count }
});

使⽤变化,components/vuex/module.vue

<template>
    <div id="app">
        <div>冲啊,⼿榴弹扔了{{ $store.state.a.count }}个</div>
        //注意此处是getters
        <p>{{ $store.getters['a/score'] }}</p>
        <button @click="add">扔⼀个</button>
    <button @click="addAsync">蓄⼒扔俩</button>
 </div >
</template > <script>
    export default {
        methods: {
        add() {
        this.$store.commit("a/increment");
 },
 addAsync() {
        this.$store.dispatch("a/incrementAsync");
 }
 }
};
</script>

补充:十分重要

 Vue.component("comp", {
  /**
   * 浏览情况下才能使用字符串模板,因为浏览器直接使用vue是通过script引入的带有运行时编译器的js版本,所以才能解析字符串模板
   * webpack情况下,是不能使用template字符串这种形式的
   * 
   * 但是不光这种方式(第二种)可以用,因为webpack配置的有babel-loader所以jsx也可以使用
   */
  // template:'<div id="box" class="foo><sapn>aaa</span></div>',
   render(h) {
     return h("div", {
       class: { foo: true}, attrs: { id: "box" } }, [
         h("span", "aaa")
       ]);
   }
 });

自定义实现简化版Vuex

//维护状态  state


//修改状态 commit


// 业务逻辑的控制 dispatch

//状态派发  getter

//实现state的响应式


//实现插件

//实现混入

let Vue;
function install(_Vue, storeName = '$store') {
    Vue = _Vue;
    //混入:把store选项指定到Vue原型上
    Vue.mixin({
        beforeCreate() {
            if (this.$options.store) {
                Vue.prototype[storeName] = this.$options.store;
            }
        }
    })

}


class Store {
    //options其实就是{state:{count:0}}类似这种
    constructor(options = {}) {
        //利用vue的数据响应式
        this.state = new Vue({
            data: options.state
        })
        //初始化mutations
        this.mutations = options.mutations || {};
        this.actions = options.actions || {};
        options.getters && this.hadleGetters(options.getters);
    }


    //触发mutations,需要实现commit

    //箭头函数,this指向store实例
    commit = (type, arg) => {
        const fn = this.mutations[type];//获取状态变更函数
        fn(this.state, arg);
    }

    dispatch(type, arg) {
        const fn = this.actions[type];
        return fn({ commit: this.commit, state: this.state }, arg);
    }
    hadleGetters(getter) {
        this.getters = {}

        //定义只读的属性
        Object.keys(getter).forEach(key => {
            Object.defineProperty(this.getters, key, {
                get: () => {
                    return getter[key](this.state);
                }
            })
        })
    }
}

//Vuex
export default { Store, install }

使用

import Vue from "vue";
import KVuex from "./Kvuex";
Vue.use(KVuex);

export default new KVuex.Store({
    state:{
        count:0
    },
    mutations:{
        add(state,num=1){
            state.count+=num;
        }
    },
    actions:{
        addSyc({commit}){
            return new Promise(resolve=>{
                setTimeout(()=>{
                    commit("add");
                    resolve({ok:1})
                },1000)
            })
        }
    },
    getters:{
        score(state){
            return "score: "+state.count;
        }
    }
})
//main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './Kindex'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  store,
  components: { App },
  template: '<App/>'
}) 

说明:虽然一般官方建议都是通过mutation去修改state,但是实际上直接操作state也可以修改,但是不建议,因为这样破化了单向数据流,不方便测试同时可以分离副作用

上一篇下一篇

猜你喜欢

热点阅读