Vue補(bǔ)充總結(jié)(一)

  • created :父組件早于子組件
  • mounted :子組件早于父組件

在哪個(gè)組件發(fā)布其實(shí)就是再哪個(gè)組件監(jiān)聽抡驼,只是書寫的時(shí)候書寫到了父組件

//子組件
<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();
//如果自己實(shí)現(xiàn),則可以new Bus();
Vue.prototype.$bus=new Vue();//main.js中
this.$bus.on('foo',msg);
this.$bus.$emit('foo');

實(shí)踐中可以使用Vue替換Bus,因?yàn)閂ue實(shí)現(xiàn)了相關(guān)功能

祖先和后代之間傳遞褪尝,由于嵌套層數(shù)過多,props不切實(shí)際,provide inject使用場景就在此處房蝉;provide inject主要為高階插件/組件庫使用颊郎,一般不推薦直接使用在代碼里面;provide inject不是響應(yīng)式的他炊,但是如果傳遞的是引用類型則修改內(nèi)部是完全可以的争剿,但是比如說只是傳入的字符串這時(shí)候修改會報(bào)錯(cuò)的官方建議盡量傳入不修改的值

//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(){
    //父組件監(jiān)聽哩掺,父組件發(fā)送,此時(shí)是兩個(gè)HelloWorld組件
    this.$parent.$on('haha',(info)=>{
      console.log(info);
    })
  }
}
</script>
<style scoped></style>

模擬實(shí)現(xiàn)element-ui的form組件并實(shí)現(xiàn)校驗(yàn)等功能


1.jpg

注意:自定義組件要實(shí)現(xiàn)v-model必須實(shí)現(xiàn):value @input涩笤,因?yàn)関-model其實(shí)就是這兩者的語法糖

//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之所以傳遞嚼吞,是因?yàn)樵谛r?yàn)或者其他場景下需要通過this去獲取實(shí)例,但是此時(shí)KFormItem有兩個(gè)需要通過key區(qū)分 -->
      <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('有錯(cuò)');
        }
      })
    }
  }
};
</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實(shí)例給后代辆它,比如formitem用來校驗(yàn)
    }
  },
  methods: {
    validate(cb) {
      //之所以要filter是因?yàn)閮?nèi)部有些節(jié)點(diǎn)不需要校驗(yàn)誊薄。例如登錄,必須有prop屬性才需要校驗(yàn)
      //注意此處不夠健壯
      //map結(jié)果是若干promise結(jié)果
      const tasks = this.$children
        .filter(item => item.prop)
        .map(item => item.validate());
      //如果沒有任何一個(gè)有異常锰茉,則會走到then,否則不會進(jìn)入
      Promise.all(tasks)
        .then(() => {
          cb(true);
        })
        .catch(() => cb(false));
    }
  }
};
</script>

<style>
</style>
//KFormItem.vue
<template>
  <div>
      <label v-if="label">
          {{label}}
      </label>
      <slot></slot>
      <!-- 校驗(yàn)信息 -->
      <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(){
            //執(zhí)行組件校驗(yàn)
            //1.獲取校驗(yàn)規(guī)則
           const rules=  this.form.rules[this.prop];
            //2.獲取數(shù)據(jù)
            const value=  this.form.model[this.prop];
            //3. 執(zhí)行校驗(yàn)-此時(shí)通過校驗(yàn)庫去校驗(yàn)-async-validator   element就是用的這個(gè)庫
            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,//避免頂層容器繼承屬性片吊,例如當(dāng)外面type有類型password的時(shí)候,此div會繼承這些類似的屬性协屡,false則不會繼承
    methods:{
        onInput(e){
           //通知父組件數(shù)值變化,但是實(shí)際上此時(shí)是該組件監(jiān)聽該組件通知俏脊,之所以這樣還麻煩的這么寫,是因?yàn)榉蟰ue的單向數(shù)據(jù)流肤晓,此時(shí)就是從父級到子 
            this.$emit('input',e.target.value)
            //通知FormItem校驗(yàn)
            this.$parent.$emit('validate');
        }
    }
}
</script>
<style></style>

單向數(shù)據(jù)流的優(yōu)勢:把副作用分離出來爷贫,好寫測試,假如子組件可以修改父組件的數(shù)據(jù)补憾,那么會導(dǎo)致其他依賴父組件的子組件都會受到影響

自定義組件的v-model

以下來自于官方文檔
一個(gè)組件上的 v-model 默認(rèn)會利用名為 value 的 prop 和名為 input 的事件漫萄,但是像單選框、復(fù)選框等類型的輸入控件可能會將 value attribute 用于不同的目的盈匾。model選項(xiàng)可以用來避免這樣的沖突:

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)"
      >
    `
  })

現(xiàn)在在這個(gè)組件上使用 v-model 的時(shí)候:

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

這里的 lovingVue 的值將會傳入這個(gè)名為 checked 的 prop腾务。同時(shí)當(dāng) <base-checkbox> 觸發(fā)一個(gè) change 事件并附帶一個(gè)新的值的時(shí)候,這個(gè) lovingVue 的 property 將會被更新削饵。

說明:意思在于岩瘦,v-model不光可以用于input事件,可以通過自定義綁定任何事件

補(bǔ)充:@click.native的使用場景窿撬?記不住百度下

自定義提示組件

//create.js

import Vue from "vue";
//創(chuàng)建指定的組件實(shí)例启昧,并掛載于body上面
export default function create(Component, props) {
    //1. 創(chuàng)建組件實(shí)例
    const vm = new Vue({
        render(h) {
            //h  createlement的簡稱
            return h(Component, { props })
        }
    }).$mount();
    const comp = vm.$children[0];
    //追加dom到body,注意comp是組件實(shí)例,不是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;
        //創(chuàng)建一個(gè)路由path和route映射
        this.routeMap = {};
        //記錄當(dāng)前路徑current需要響應(yīng)式劈伴,利用vue響應(yīng)式原理可以做到這點(diǎn)
        this.app = new Vue({
            data: {
                current: '/'
            }
        })
    }
    init() {
        //綁定瀏覽器的事件
        this.bindEvent();
        //解析路由配置
        this.createRouteMap(this.$options);
        //創(chuàng)建router-link和router-view
        this.initComponent();
    }
    bindEvent() {
        //如果不綁定this箫津,則onHashChange內(nèi)部this就是windows了,綁定了就是VueRouter實(shí)例
        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() {
        //注冊兩個(gè)到全局,都可以使用
        Vue.component('router-link', {
            props: {
                to: String
            },
//此時(shí)沒有箭頭苏遥,則this指向就是router-link自己饼拍,所以this.$slots.default就是router-link包裹的東西
            render(h) {
                //目標(biāo)是:<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其實(shí)就是默認(rèn)插槽,此時(shí)是xxxx
            }
        })
        //hash->current->render
        Vue.component('router-view', {
            //箭頭函數(shù)保留this指向田炭,當(dāng)前this就是vuerouter的實(shí)例
            render:(h)=> {
                const Comp=this.routeMap[this.app.current].component;
                // console.log(1111,h(Comp));//VNode....
                return h(Comp);
            }
        })
    }
}
//把VueRouter變?yōu)椴寮?組件實(shí)現(xiàn)install就變?yōu)椴寮?VueRouter.install = function (_Vue) {
    //Vue.use的時(shí)候其實(shí)就是傳遞過來vue的構(gòu)造函數(shù)
    Vue = _Vue;
    //混入任務(wù)
    Vue.mixin({
        beforeCreate() {
            //這里的代碼將來會在外面初始化的時(shí)候被調(diào)用,這樣就實(shí)現(xiàn)了vue的擴(kuò)展
            //this就是vue的實(shí)例,所有vue實(shí)例都會執(zhí)行师抄,但是這時(shí)候只希望根組件執(zhí)行一次
            if (this.$options.router) {
                /**
                 * new Vue({
                      el: '#app',
                      router,
                      components: { App },
                      template: '<App/>'
                    })
                    其實(shí)就是這個(gè)router
                 */
                Vue.prototype.$router = this.$options.router;
//此時(shí)是全局混入,再最外面Vue實(shí)例化時(shí)候beforeCreate時(shí)候調(diào)用教硫,全局混入在所有beforeCreate最先執(zhí)行
                this.$options.router.init();
            }
        }
    })
}
//但是注意:不是所有的插件都需要混入實(shí)現(xiàn)叨吮,此時(shí)是因?yàn)樘厥獾囊藐P(guān)系和調(diào)用時(shí)機(jī)
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";

說明:這一行導(dǎo)入,在krouter中Vue.use(VueRouter)先執(zhí)行瞬矩,new VueRouter后執(zhí)行茶鉴,但是實(shí)際上,VueRouter.install內(nèi)部的混入是在beforeCreate才執(zhí)行的景用,所以順序上new VueRouter先于混入內(nèi)部的this.$options.router.init();

遞歸組件

遞歸組件其實(shí)就是內(nèi)部還有自己涵叮,有兩個(gè)條件,必須有name伞插,同時(shí)必須有結(jié)束條件

<template>
  <div>
    <h3>{{data.title}}</h3>
    <!-- 必須有結(jié)束條件 -->
    <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>

路由守衛(wèi)

全局守衛(wèi)割粮,router.js

// 路由配置
{
    path: "/about",
    name: "about",
    // 需要認(rèn)證,注意此時(shí)這個(gè)不是真的完成邏輯了,只能看作一個(gè)標(biāo)記位
    meta: { auth: true }, 
    component: () => import(/* webpackChunkName: "about" */
        "./views/About.vue")
}
// 守衛(wèi)
router.beforeEach((to, from, next) => {
    // 要訪問/about且未登錄需要去登錄
    if (to.meta.auth && !window.isLogin) {
        if (window.confirm("請登錄")) {
            window.isLogin = true;
            next(); // 登錄成功媚污,繼續(xù) 
        } else {
            next('/');// 放棄登錄舀瓢,回??
        }
    } else {
        next(); // 不需登錄,繼續(xù) 
    }
});

路由獨(dú)享守衛(wèi)

可以在某條路由內(nèi)部寫這個(gè)鉤子

beforeEnter(to, from, next) {
    // 路由內(nèi)部知道??需要認(rèn)證
    if (!window.isLogin) {
        // ...
    } else {
        next();
    }
}

組件內(nèi)的守衛(wèi)

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

動態(tài)添加路由

利?$router.addRoutes()可以實(shí)現(xiàn)動態(tài)路由添加耗美,常?于?戶權(quán)限控制

// router.js
// 返回?cái)?shù)據(jù)可能是這樣的
//[{
// path: "/",
// name: "home",
// component: "Home", //Home
//}]
// 異步獲取路由
api.getRoutes().then(routes => {
    const routeConfig = routes.map(route => mapComponent(route));
    router.addRoutes(routeConfig);
})
// 映射關(guān)系
const compMap = {
//也可以直接 'Home':Home
//但是下面是懶加載形式京髓,更加優(yōu)雅而且避免不需要的打包
    '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
}

面包屑實(shí)現(xiàn)

利?$route.matched可得到路由匹配數(shù)組,按順序解析可得路由層次關(guān)系

// 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)
    }
}
//如果無法觸發(fā):加上immediate:true   //一開始執(zhí)行一次

徹底明白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>
         我是一個(gè)子組件商架,我在紅色的海洋里堰怨!
        <input type="button" value="點(diǎn)我隱身" @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自然也要做對應(yīng)調(diào)整:

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

那么如果現(xiàn)在我將父組件的changeIsShow直接寫成匿名函數(shù),也能運(yùn)行吧:

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

現(xiàn)在我將那匿名函數(shù)改成箭頭函數(shù)甸私,不過分吧:

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

最后我將上面那行代碼做最后一次修改:

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

至此終于涉及到了sync了诚些。以上代碼:isShow.sync="isShow"其實(shí)是 @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>
         我是一個(gè)子組件,我在紅色的海洋里砸烦!
        <input type="button" value="點(diǎn)我隱身" @click="upIsShow">
    </div>
</template>
<script>
    export default {
        methods:{
            upIsShow(){
                this.$emit("update:isShow",false);
            }
        }
    }
</script>

如果沒有sync則父組件就需要在html上面寫函數(shù)或者在父組件method里面寫函數(shù)弃鸦,但是針對上面這種簡單通信的邏輯,其實(shí)sync可以省去父組件部分邏輯

插件

// 插件定義
MyPlugin.install = function (Vue, options) {
    // 1. 添加全局?法或?qū)傩?    Vue.myGlobalMethod = function () {
        // 邏輯...
    }
    // 2. 添加全局資源
    Vue.directive('my-directive', {
        bind(el, binding, vnode, oldVnode) {
            // 邏輯...
        }
    ...
    })
    // 3. 注?組件選項(xiàng)
    Vue.mixin({
        created: function () {
            // 邏輯...
        }
        ...
        })
    // 4. 添加實(shí)例?法
    Vue.prototype.$myMethod = function (methodOptions) {
        // 邏輯...
    }
}
// 插件使?
Vue.use(MyPlugin)

混入

// 定義?個(gè)混?對象
var myMixin = {

    created: function () {
        this.hello()
    },
    methods: {
        hello: function () {
            console.log('hello from mixin!')
        }
    }
}
// 定義?個(gè)使?混?對象的組件
var Component = Vue.extend({
    mixins: [myMixin]
})

render函數(shù)詳解

?些場景中需要 JavaScript的完全編程的能?幢痘,這時(shí)可以?渲染函數(shù)唬格,它?模板更接近編譯器

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

h其實(shí)就是createElement函數(shù)

{
    // 與 `v-bind:class` 的 API 相同,
    // 接受?個(gè)字符串、對象或字符串和對象組成的數(shù)組
    'class': {
        foo: true,
            bar: false
    },
    // 與 `v-bind:style` 的 API 相同购岗,
    // 接受?個(gè)字符串汰聋、對象,或?qū)ο蠼M成的數(shù)組
    style: {
        color: 'red',
            fontSize: '14px'
    },
    // 普通的 HTML 特性
    attrs: {
        id: 'foo'
    },
    // 組件 prop
    props: {
        myProp: 'bar'
    },
    // DOM 屬性
    domProps: {
        innerHTML: 'baz'
    },
    // 事件監(jiān)聽器在 `on` 屬性內(nèi)喊积,
    // 但不再?持如 `v-on:keyup.enter` 這樣的修飾器烹困。
    // 需要在處理函數(shù)中?動檢查 keyCode。
    on: {
        click: this.clickHandler
    },
}

函數(shù)式組件

組件若沒有管理任何狀態(tài)乾吻,也沒有監(jiān)聽任何傳遞給它的狀態(tài)髓梅,也沒有?命周期?法,只是?個(gè)接受?些prop 的绎签,可標(biāo)記為函數(shù)式組件枯饿,此時(shí)它沒有上下?

  • props :提供所有 prop 的對象
  • children : VNode ?節(jié)點(diǎn)的數(shù)組
  • slots : ?個(gè)函數(shù),返回了包含所有插槽的對象
  • scopedSlots : (2.6.0+) ?個(gè)暴露傳?的作?域插槽的對象诡必。也以函數(shù)形式暴露普通插槽奢方。
  • data :傳遞給組件的整個(gè)數(shù)據(jù)對象,作為 createElement 的第?個(gè)參數(shù)傳?組件
  • parent :對?組件的引?
  • listeners : (2.3.0+) ?個(gè)包含了所有?組件為當(dāng)前組件注冊的事件監(jiān)聽器的對象擒权。這是
  • data.on 的?個(gè)別名袱巨。
  • injections : (2.3.0+) 如果使?了 inject 選項(xiàng),則該對象包含了應(yīng)當(dāng)被注?的屬性碳抄。
Vue.component('my-component', {
//標(biāo)記為函數(shù)式組件
  functional: true,
  // Props 是可選的
  props: {
    // ...
  },
  // 為了彌補(bǔ)缺少的實(shí)例
  // 提供第二個(gè)參數(shù)作為上下文
  render: function (createElement, context) {
    // ...
  }
})

Vuex數(shù)據(jù)管理

  • Vuex 是?個(gè)專為 Vue.js 應(yīng)?開發(fā)的狀態(tài)管理模式愉老,集中式存儲管理應(yīng)?所有組件的狀態(tài)。
  • Vuex遵循“單向數(shù)據(jù)流”理念剖效,易于問題追蹤以及提?代碼可維護(hù)性嫉入。
  • Vue中多個(gè)視圖依賴于同?狀態(tài)時(shí),視圖間傳參和狀態(tài)同步?較困難璧尸,Vuex能夠很好解決該問題咒林。
TIM截圖20200722140212.jpg

核心概念

  • state 狀態(tài)、數(shù)據(jù)
  • mutations 更改狀態(tài)的函數(shù)
  • actions 異步操作(只是可以存在異步操作爷光,例如網(wǎng)絡(luò)請求垫竞,但是一般不會把網(wǎng)絡(luò)放這里)
  • store 包含以上概念的容器

狀態(tài)和狀態(tài)變更

state保存數(shù)據(jù)狀態(tài),mutations?于修改狀態(tài)蛀序,store.js

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

使?狀態(tài)欢瞪,vuex/index.vue

<template>
    <div>
        <div>沖啊,?榴彈扔了{(lán){ $store.state.count }}個(gè)</div>
        <button @click="add">扔?個(gè)</button>
 </div>
</template > <script>
    export default {
        methods: {
        add() {
        this.$store.commit("increment");
 }
 }
};
</script>

派?狀態(tài) - getters

從state派?出新狀態(tài)徐裸,類似計(jì)算屬性

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

登錄狀態(tài)?字遣鼓,App.vue

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

mutation喝action區(qū)別

mutations 類似于事件,用于提交 Vuex 中的狀態(tài) state
action 和 mutations 也很類似重贺,主要的區(qū)別在于mutations 只能是同步操作骑祟,回懦,action `==可以包含==`異步操作,而且可以通過 action 來提交 mutations
mutations 有一個(gè)固有參數(shù) state次企,接收的是 Vuex 中的 state 對象

action 也有一個(gè)固有參數(shù) context怯晕,但是 context 是 state 的父級,包含 state缸棵、getters等等

Vuex 的倉庫是 store.js贫贝,將 axios 引入,并在 action 添加新的方法

分發(fā)調(diào)用action:

this.$store.dispatch('action中的函數(shù)名'蛉谜,發(fā)送到action中的數(shù)據(jù))

在組件中提交 Mutation:
this.$store.commit(“mutation函數(shù)名”稚晚,發(fā)送到mutation中的數(shù)據(jù))

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

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

動作 - actions

復(fù)雜業(yè)務(wù)邏輯,類似于controller

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

使?actions:

<template>
    <div id="app">
        <div>沖啊型诚,?榴彈扔了{(lán){ $store.state.count }}個(gè)</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>沖啊狰贯,?榴彈扔了{(lán){ $store.state.a.count }}個(gè)</div>
        //注意此處是getters
        <p>{{ $store.getters['a/score'] }}</p>
        <button @click="add">扔?個(gè)</button>
    <button @click="addAsync">蓄?扔倆</button>
 </div >
</template > <script>
    export default {
        methods: {
        add() {
        this.$store.commit("a/increment");
 },
 addAsync() {
        this.$store.dispatch("a/incrementAsync");
 }
 }
};
</script>

補(bǔ)充:十分重要

 Vue.component("comp", {
  /**
   * 瀏覽情況下才能使用字符串模板也搓,因?yàn)闉g覽器直接使用vue是通過script引入的帶有運(yùn)行時(shí)編譯器的js版本,所以才能解析字符串模板
   * webpack情況下涵紊,是不能使用template字符串這種形式的
   * 
   * 但是不光這種方式(第二種)可以用傍妒,因?yàn)閣ebpack配置的有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")
       ]);
   }
 });

自定義實(shí)現(xiàn)簡化版Vuex

//維護(hù)狀態(tài)  state


//修改狀態(tài) commit


// 業(yè)務(wù)邏輯的控制 dispatch

//狀態(tài)派發(fā)  getter

//實(shí)現(xiàn)state的響應(yīng)式


//實(shí)現(xiàn)插件

//實(shí)現(xiàn)混入

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

}


class Store {
    //options其實(shí)就是{state:{count:0}}類似這種
    constructor(options = {}) {
        //利用vue的數(shù)據(jù)響應(yīng)式
        this.state = new Vue({
            data: options.state
        })
        //初始化mutations
        this.mutations = options.mutations || {};
        this.actions = options.actions || {};
        options.getters && this.hadleGetters(options.getters);
    }


    //觸發(fā)mutations,需要實(shí)現(xiàn)commit

    //箭頭函數(shù)摸柄,this指向store實(shí)例
    commit = (type, arg) => {
        const fn = this.mutations[type];//獲取狀態(tài)變更函數(shù)
        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颤练,但是實(shí)際上直接操作state也可以修改,但是不建議驱负,因?yàn)檫@樣破化了單向數(shù)據(jù)流嗦玖,不方便測試同時(shí)可以分離副作用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市跃脊,隨后出現(xiàn)的幾起案子宇挫,更是在濱河造成了極大的恐慌,老刑警劉巖酪术,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件器瘪,死亡現(xiàn)場離奇詭異,居然都是意外死亡绘雁,警方通過查閱死者的電腦和手機(jī)橡疼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咧七,“玉大人衰齐,你說我怎么就攤上這事任斋〖套瑁” “怎么了耻涛?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長瘟檩。 經(jīng)常有香客問我抹缕,道長,這世上最難降的妖魔是什么墨辛? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任卓研,我火速辦了婚禮,結(jié)果婚禮上睹簇,老公的妹妹穿的比我還像新娘奏赘。我一直安慰自己,他們只是感情好太惠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布磨淌。 她就那樣靜靜地躺著,像睡著了一般凿渊。 火紅的嫁衣襯著肌膚如雪梁只。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天埃脏,我揣著相機(jī)與錄音搪锣,去河邊找鬼。 笑死彩掐,一個(gè)胖子當(dāng)著我的面吹牛构舟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播堵幽,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼旁壮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了谐檀?” 一聲冷哼從身側(cè)響起抡谐,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桐猬,沒想到半個(gè)月后麦撵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溃肪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年免胃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惫撰。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡羔沙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出厨钻,到底是詐尸還是另有隱情扼雏,我是刑警寧澤坚嗜,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站诗充,受9級特大地震影響苍蔬,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蝴蜓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一碟绑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茎匠,春花似錦格仲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至造烁,卻和暖如春否过,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惭蟋。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工苗桂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人告组。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓煤伟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親木缝。 傳聞我的和親對象是個(gè)殘疾皇子便锨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354