Vue基礎(二)

Vue 學習

Vue擴展

Vue實例參數補充

const vm = new Vue({
	el:'#app',
    data:{
        // 實例數據
    },
    computed:{
    	// 計算屬性
	},
    methods:{
        // 實例的方法
    },
    watch:{
        // 監聽器
    },
    // ...一堆生命週期鉤子函數
    router: xxx // 通過new VueRouter() 創建的實例與Vue實例進行綁定
    render: (createElement) => {
    	// 渲染函數,自帶一個參數,這個參數是一個函數
    	// 返回的結果會直接替換整個 #app 的區域(類似於v-text)
    	// 而一般的組件是隻替換組件使用處的代碼 相當於 插值表達式(具體可以往後看)
    	return createElement(組件對象) // 返回值是html代碼
	}
})

修飾符補充

.native修飾符

一般註冊的事件 @click @keyup 都是vue的合成事件,不是原生事件,如果想直接越過vue,不進行合成事件的處理,直接原生事件,那麼就在註冊事件的時候,加上 .native修飾符就可以了 @click.native=""

在使用elementUI中的el-input 組件的時候,因爲這個組件沒有提供回車的事件,因此我們想要使用自己定義的回車事件就需要越過Vue的事件處理,執行原生事件就需要用到.native修飾符了

組件

組件是可複用的 Vue 實例,且帶有一個名字。因爲組件是可複用的 Vue 實例,所以它們與 new Vue 接收相同的選項,例如 data、computed、watch、methods 以及生命週期鉤子等。僅有的例外是像 el 這樣根實例特有的選項。並且在組件中,data 必須是一個函數

一個頁面可以是有多個組件組成

組件的優勢:

1. 便於複用
2. 便於維護
3. 便於分工

組件化和模塊化的區別

區別:
組件是具體的:按照一些小功能的通用性和可複用性來抽象組件
組件化更多關注的 UI 部分,頁面的每個部件,比如頭部,內容區,彈出框甚至確認按鈕都可以成爲一個組件,每個組件有獨立的 HTML、css、js 代碼。

模塊是抽象的:按照項目業務劃分的大模塊
模塊化側重的功能的封裝,主要是針對 Javascript 代碼,隔離、組織複製的 javascript 代碼,將它封裝成一個個具有特定功能的的模塊。

模塊可以通過傳遞參數的不同修改這個功能的的相關配置,每個模塊都是一個單獨的作用域,根據需要調用。一個模塊的實現可以依賴其它模塊。

組件註冊

  • 當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,比如:my-header
  • 當使用 PascalCase (首字母大寫命名) 定義一個組件時,在引用這個自定義元素時使用 kebab-case 方式
  • template 在如下這樣使用的時候必須有一個 root Element(根元素)進行包裹,不然會報錯
  1. 全局註冊組件

註冊

// Vue.component('組件名', { 組件對象 })
// 方式一
Vue.component('myheader', {
  template: '<h1>hello world!</h1>'
})
// 方式二
Vue.component('my-header', {
  template: '<h1>hello world!</h1>'
})
// 方式三
Vue.component('myHeader', {
  template: '<h1>hello world!</h1>'
})

使用

<!-- 對應上面的方式一 -->
<div id="app">
  <myheader></myheader>
</div>
<!-- 對應方式二和三 -->
<div id="app">
  <my-header></my-header>
</div>
  1. 局部註冊組件
const vm = new Vue({
  el: '#app',
  components: {
    // 也可以寫成 myheader 或者 myHeader 方式與上面一樣
    'my-header': {
      template: '<h1>hello world</h1>'
    }
  }
})

組件對象參數

data 必須是一個函數 vue 組件中的 data 必須是個函數的目的:就是爲了保證組件實例之間不會相互影響!

  • 如果 data 是個對象,就是引用類型的,任意一個地方發生改變,其他人都會受到影響
  • 用了函數之後,每次創建 vue 組件實例,都會調用函數,重新創建一個新的對象,那麼每個實例都有自己的 data 對象,就不會互相影響了

組件嵌套

父子組件——在使用組件的時候,如果一個組件的模板中,用到了另一個組件標籤,則這兩個組件就形成了父子關係

Vue.component('father', {
  // 在父組件的模板中引用了子組件,可以使用雙標籤<son></son> 也可以使用單標籤 <son />
  template: '<h1> {{msg}} <son></son></h1>',
  data() {
    return {
      msg: 'hello father'
    }
  }
})

Vue.component('son', {
  template: '<h1>{{msg}}</h1>',
  data() {
    return {
      msg: 'hello son'
    }
  }
})
// 這種並不是父子組件,這樣定義只是在組件中又定義了一個組件,而這個son組件的使用範圍只是在father組件內部,無法在外部使用,是一個局部組件
Vue.component('father', {
  template: '<h1>hello father</h1>',
  components: {
    son: {
      template: '<h1>hello son</h1>'
    }
  }
})

以上這些只是組件的最最最最……基礎的使用,這樣的使用很費勁,但是這部分是基礎,必須會了才能更好的學習之後的

組件通訊 ★

組件之間是相互獨立的,數據是無法直接互相訪問的!!在實際開發當中,我們經常會遇到,在一個組件中,要使用另外一個組件中的數據的情況。這個時候就需要使用組件通訊技術!!即在組件之間傳遞數據

  1. 父組件 → 子組件(屬性傳值)

    Prop 是你可以在組件上註冊的一些自定義特性。當一個值傳遞給一個 prop 特性的時候,它就變成了那個組件實例的一個屬性。爲了給博文組件傳遞一個標題,我們可以用一個 props 選項將其包含在該組件可接受的 prop 列表中。一個組件默認可以擁有任意數量的 prop,任何值都可以傳遞給任何 prop

// 父組件傳值給子組件是在編譯的時候自動的
Vue.component('father', {
 // step 1: 給子組件添加通過 v-bind 綁定的屬性(prop),並且把father的數據綁定給屬性(prop)
  template: '<h1> {{msg}} <son :msgFromFather="msg"></son></h1>',
  data() {
    return {
      msg: '這是father的msg'
    }
  }
})

Vue.component('son', {
 // step 2: 在子組件中增加屬性 props 這是一個數組,用來接收step1中綁定的屬性(prop)
  props: ['msgFromFather'],
 // step 3: 在子組件模板中可以直接props中的屬性名
  template: '<h1>{{msgFromFather}}</h1>'
})
  1. 子組件 → 父組件(事件傳值)

    單向數據流
    所有的 prop 都使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致你的應用的數據流向難以理解。

    額外的,每次父級組件發生更新時,子組件中所有的 prop 都將會刷新爲最新的值。這意味着你不應該在一個子組件內部改變 prop。如果你這樣做了,Vue 會在瀏覽器的控制檯中發出警告。

Vue.component('father', {
  // 通過事件綁定的方式,用 @ 給子組件添加事件(事件名自定義),並且與step1的方法綁定,傳遞給子組件
  template: '<div>{{sonData}} <son @getData="getSonData"></son></div>',
  data() {
    return {
      // 用於存放子組件的數據
      sonData: ''
    }
  },
  methods: {
    // step 1: 聲明一個函數,並且有一個形參用於接收子組件的數據
    getSonData(value) {
      console.log(value)
      this.sonData = value
    }
  }
})
Vue.component('son', {
  template: `<div>
            <button @click="clickHandler">sendToFather</button>
          </div>`,
  data() {
    return {
      msg: '這是son的msg'
    }
  },
  methods: {
    // 子組件傳值給父組件是需要自己觸發的
    clickHandler() {
      // step 3: 通過this.$emit('事件名',子組件數據),調用父組件傳遞的函數把子組件的數據傳遞給父組件
      this.$emit('getData', this.msg)
    }
  }
})
  1. 父子組件雙向綁定

    儘管因爲存在單向數據流,導致我們不能直接在子組件中去修改父組件傳遞過來的數據,但是我們可以通過雙向綁定的方式,在實現子組件中修改父組件中傳遞的數據

    方法一:組件間的數據傳遞

這個方法的實現其實就是父傳子,子傳父的結合,父組件把數據傳遞給子組件(屬性傳值),然後子組件通過事件傳值又把修改的數據傳遞給父組件,這樣實現了雙向綁定

Vue.component('father', {
    template: `<div>父組件:<input type="text" v-model="msg"><son :val="msg" @xxx="msg = $event"></son></div>`,
    data() {
        return {
            msg: 'felix'
        }
    },
    methods: {
        getval(val) {
            this.msg = val
        }
    }
})

Vue.component('son', {
    props: ['val'],
    template: `<div>子組件:<input type="text" :value=sonMsg @input="changeval"></div>`,
    data() {
        return {
         sonMsg: this.val
        }
    },
    methods: {
        changeval(e) {
            this.$emit('xxx', e.target.value)
        }
    },
    watch: {
        val() {
            this.sonMsg = this.val
        }
    }
})

const vm = new Vue({
    el: '#app'
})

方法二:.sync修飾符

使用.sync修飾符其實就是方法一的簡寫

// 方法一中的
<son :val="msg" @xxx="msg = $event"></son>

// 修改成如下這樣
<son :val.sync="msg"></son>

// 方法一種的
this.$emit('xxx', e.target.value)

// 修改成如下這樣,這裏的必須要這麼寫update:xxx,冒號後面的是與上面的綁定,但是update是必不可少的,我們在網上看到的一些雙向綁定使用方法一的時候也是用update這樣的方式寫的,但是實際的邏輯就是通過上面說的來實現的,沒必要使用update,但是這裏必須使用update,調用Vue內部的update事件
this.$emit('update:val', e.target.value)

方法三:v-model綁定

Vue.component('father', {
    // step 1: 通過v-model給子組件綁定數據,其實v-model相當於是給value值綁定數據的
    template: `<div><son v-model="msg"></son>父組件:<input type="text" v-model="msg"> 
</div>`,
    // 上面的son中的v-model其實是這個的簡寫 <son :value="msg" @input="msg = $event"></son>
    data() {
        return {
            msg: 'felix'
        }
    }
})

Vue.component('son', {
    // step 3:給子組件添加input事件
    template: `<div>子組件: <input type="text" v-model="sonMsg" @input="set"></div>`,
    // step 2:接收父組件中綁定的數據,因爲v-model相當於是給value綁定數據,因此這裏用value接收
    props: ['value'],
    data() {
        return {
            sonMsg: this.value
        }
    },
    methods: {
        set(e) {
            // step 4:通過文本框的input事件去觸發父組件中用v-model綁定的input事件,並把數據傳遞給父組件,實現雙向數據綁定
            this.$emit('input', e.target.value)
        }
    },
    watch: {
        value() {
         	// 添加監視,對傳遞過來的value值,這樣只要父組件中的數據修改了,子組件中的數據sonMsg也可立即修改並響應式的在界面上顯示   
            this.sonMsg = this.value
        }
    }
})

const vm = new Vue({
    el: '#app'
})
  1. 非父子組件傳值

同級組件之間的數據傳遞

// bear2 發送數據給 bear1 兩者不是父子組件,是兄弟組件

// step 1 : 定義global-event-bus (全局事件總線)  一個空的Vue實例
const bus = new Vue()

Vue.component('bear1', {
  template: `<div>{{msg}}</div>`,
  data() {
    return {
      msg: ''
    }
  },
  methods: {
    // step 2 : 定義一個函數用於接收另一個組件傳遞過來的數據通過形參mes
    getBear2Msg(mes) {
      this.msg = '收到熊二來信:' + mes
    }
  },
  created() {
    // step 3 : 在組件剛創建之後就把聲明的函數通過$on 註冊到總線(bus)上,方便另一個組件可以通過總線觸發
    bus.$on('getMsg', this.getBear2Msg)
  }
})

Vue.component('bear2', {
  template: `<div>
<button @click="sendMsg">告訴熊大</button>
</div>`,
  data() {
    return {
      msg: '熊大,光頭強又來砍樹啦!'
    }
  },
  methods: {
    sendMsg() {
      // step 4 : 通過$emit 觸發接收組件(即bear1組件)的函數,並且把數據傳遞過去,這樣上面的getBear2Msg的mes參數就有了值
      bus.$emit('getMsg', this.msg)
    }
  }
})

const vm = new Vue({
  el: '#app'
})

還有一種方法就是使用 Vuex 來解決

組件補充(瞭解)

下面兩個屬性在實際開發中用的不多,作爲了解知道就行

  1. $refs 屬性

    • 這個屬性可以用來在組件中訪問子組件或者子元素
    • 如果要在當前組件中訪問子組件或子元素,那麼就需要給子組件或者子元素標籤添加 ref 屬性,隨便給個名字 ref=名字
    • 在當前組件中就可以通過 this.$refs.名字訪問到子組件或子元素了
      • 如果是子組件,則獲取到的是 vue 實例
      • 如果是子元素,則獲取到的是 dom 對象
    Vue.component('father', {
      template: `<div><son ref="child"></son><button @click="handler">按鈕</button></div>`,
      methods: {
        handler() {
          console.log(this.$refs.child.msg)
        }
      }
    })
    
    Vue.component('son', {
      template: `<div>這是子組件數據:  {{msg}}</div>`,
      data() {
        return {
          msg: '這是son的msg'
        }
      }
    })
    
    const vm = new Vue({
      el: '#app'
    })
    
  2. $parent 屬性

    獲取當前組件的父組件或者父元素

    Vue.component('father', {
      template: `<div>{{msg}}<son ref="child"></son></div>`,
      data() {
        return {
          msg: '這是father的msg'
        }
      }
    })
    
    Vue.component('son', {
      template: `<div><button @click="handler">按鈕</button></div>`,
      methods: {
        handler() {
          console.log(this.$parent.msg)
        }
      }
    })
    
    const vm = new Vue({
      el: '#app'
    })
    

插槽

插槽,當組件中需要一些內容,這些內容是通過在組件標籤內書寫,傳遞進去的,那麼我們需要使用插槽進行接收

匿名插槽

定義組件

Vue.component("mycomp", {
    template:"<div><slot></slot></div>",
    data(){
        return {
            btnText: "按鈕文字"
        }
    }
});

new Vue({
    el: "#app"
});

頁面代碼

<div id="app">
    <mycomp>
        <div>這是放在組件最前面的內容</div>
        <div>這是放在組件最後面的內容</div>
    </mycomp>
</div>

最後就會把<mycomp>標籤裏的兩個div替換template模板中的slot標籤

具名插槽

具名插槽: 插槽可以起名字, 將內容和插槽進行對應

我要實現讓一個div放前面,一個放後面,這時候就需要讓給插槽命名,這樣纔可以讓對應的東西放到對應的插槽中去

Vue.component("mycomp", {
    template:
    "<div>1: <slot name='first'></slot> 2:<slot name='second'></slot></div>",
    data(){
        return {
            btnText: "按鈕文字"
        }
    }
});
<mycomp>
    <div slot="front">這是放在組件最前面的內容</div>
    <div slot="back">這是放在組件最後面的內容</div>
</mycomp> 

當有很多內容需要放到某個具名插槽中去,如果每個都給添加一個slot屬性來指定名字,這樣是很麻煩的

<mycomp>
    <div slot="front">這個要放到前面</div>
    <div slot="front">這個也要放到前面</div>
    <div slot="back">這個放在後面</div>
    <div slot="back">這個也要放在後面</div>
</mycomp>

因此可以如下修改

<mycomp>
    <!-- 如果有多個內容要放到同一個插槽裏,那麼就可以使用template標籤把他們包起來 -->
    <!-- template標籤不會生成額外的標籤,不會影響標籤結構 ,用div包裹的話,會增加一個div標籤,結構就發生了改變-->
    <template slot="front">
        <div>這個要放到前面</div>
        <div>這個也要放到前面</div>
    </template>

    <template slot="back">
        <div>這個放在後面</div>
        <div>這個也要放在後面</div>
    </template>
</mycomp> 

作用域插槽

作用域插槽: 當我們在給組件中插槽填寫內容的時候,如果想要用到組件中的數據,直接使用拿不到數據 這個時候就可以使用作用域插槽,把組件中的數據給傳遞出來,就可以直接使用了

Vue.component("mycomp", {
    // 通過v-bind(或 : )來綁定組件中的數據,綁定的名字自定義
    template:
    "<div>1: <slot name='first' :btnText='btnText'></slot> 2:<slot name='second' :btnText='btnText'></slot></div>",
    data(){
        return {
            btnText: "按鈕文字"
        }
    }
});
<!-- v-slot:插槽的名字="組件中傳遞出來的數據對象" -->
<mycomp>
    <!-- 因爲傳遞的值是一個對象,這裏可以通過{上面定義的名字}來進行解構,然後就可以用插值表達式-->
    <template v-slot:second="{btnText}">
        <button>{{btnText}}</button>
    </template>
</mycomp>

插槽這東西我們在正常的開發中基本上很難用到,因爲我們一般可以直接用人家封裝的組件,不用自己去寫組件提供給別人用,如果需要提供給別人使用的,那麼就需要用到插槽方便用戶在組件中輸入值來進行傳遞,但是當我們用到別人的組件的時候就會用到插槽,比如ElementUI的el-table組件中,我們要獲取其中某一行的數據,這時候就需要通過v-slot=“scope”來接收。

單頁面應用

單頁面應用就是根據hash值來改變頁面內容的

單頁面應用(single-page application 簡寫SPA)是指一個項目只有一個頁面,頁面的切換效果,是通過組件的切換來完成的。
單頁面應用因爲只有一個頁面,所以頁面不能發生跳轉,但是,我們又需要根據url地址來展示不同的組件
這個時候,只能用哈希值來表示究竟要展示哪個組件了

模擬實現一個單頁面應用

<div id="app">
  <!-- 這就相當於一個佔位符,告訴Vue我要在這裏放一個組件,具體放哪個組件通過is屬性來控制 -->
  <component :is="currentPage"></component>
</div>
Vue.component('login', {
  template: `<div><h1>這是登錄頁</h1><input type="text"><input type="text"><button>登錄</button></div>`
})

Vue.component('register', {
  template: `<div><h1>這是註冊頁</h1><input type="text"><input type="text"><input type="text"><button>註冊</button></div>`
})

Vue.component('list', {
  template: `<div><h1>這是列表頁</h1><input type="text"><input type="text"><button>登錄</button></div>`
})

const vm = new Vue({
  el: '#app',
  data: {
    currentPage: 'login'
  },
  created() {
    this.goPage()

    // 通過這個屬性來監聽地址欄中的hash值是否改變了,hash值就是咱們之前用到的" #/xxx "的
    window.onhashchange = () => {
      this.goPage()
    }
  },
  methods: {
    goPage() {
      switch (location.hash) {
        case '#/login':
          this.currentPage = 'login'
          break
        case '#/register':
          this.currentPage = 'register'
          break
        case '#/list':
          this.currentPage = 'list'
          break
      }
    }
  }
})

vue-router

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,讓構建單頁面應用變得易如反掌

用 vue-router 實現上面的單頁面案例

<div id="app">
  <!-- vue-router中提供了一個聲明式導航 -->
  <ul>
    <!-- 使用 router-link 組件來導航. -->
    <!-- 通過傳入 `to` 屬性指定鏈接. -->
    <!-- <router-link> 默認會被渲染成一個 `<a>` 標籤 可以通過tag 屬性來修改渲染的標籤 -->
    <li>
      <router-link to="/login" tag="button" active-class="active">登錄頁</router-link>
    </li>
    <li><router-link to="/register">註冊頁</router-link></li>
    <li><router-link to="/list">列表頁</router-link></li>
  </ul>
  <!-- 路由出口 -->
  <!-- 路由匹配到的組件將渲染在這裏 -->
  <router-view></router-view>
</div>
// 1. 定義 (路由) 組件,這個就是之前創建組件時候的組件參數對象
const login = {
    template: `<div><h3>這是登錄頁</h3><input type="text"><input type="text"><button>登錄</button></div>`
}

const register = {
    template: `<div><h3>這是註冊頁</h3><input type="text"><input type="text"><input type="text"><button>註冊</button></div>`
};

const list = {
    template: `<div><h3>這是列表頁</h3><input type="text"><input type="text"><button>登錄</button></div>`
};
// 2. 定義路由 每個路由應該映射一個組件。 其中"component" 可以是 通過 Vue.extend() 創建的組件構造器, 或者,只是一個組件配置對象
const router = new VueRouter({
    // routes 裏面存放的就是路由規則(hash值和組件的對應關係)
    routes: [
        // 每一條路由規則就是一個對象
        {path: "/",component: login},
        {path: "/login",component: login},
        {path: "/register",component: register},
        {path: "/list",component: list}
    ]
});
const vm = new Vue({
    el: "#app",
    // 3. 將Vue根組件實例和路由對象關聯起來
    // 完整寫法是 router : router ES6可以簡寫成下面的
    router
});

​ 編程式導航

const vm = new Vue({
    el: "#app",
    router,
    methods: {
        clickHandler(){
            // 在js代碼中如果想要跳轉頁面,有個東西
            // router.push("/register");
            this.$router.push("/register")
            // this.$router.push({path: "/register"})
            // this.$router.push({name: "rg"})
        }
    }
});

<router-link>

​ 常用屬性

​ 1. to

表示目標路由的鏈接。當被點擊後,內部會立刻把 to 的值傳到 router.push(),所以這個值可以是一個字符串或者是描述目標位置的對象。

<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染結果 -->
<a href="home">Home</a>

<!-- 使用 v-bind 的 JS 表達式 -->
<router-link v-bind:to="'home'">Home</router-link>

<!-- 不寫 v-bind 也可以,就像綁定別的屬性一樣 -->
<router-link :to="'home'">Home</router-link>

<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>

<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

<!-- 帶查詢參數,下面的結果爲 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>

​ 2. active-class

設置 鏈接激活時使用的 CSS 類名。默認值可以通過路由的構造選項 linkActiveClass 來全局配置

路由參數

  1. 通過 ? 設置的參數

我們常見的設置參數是 #/detail?id=1&name=zs 這種形式

// 頁面代碼,設置連接
<router-link to="/detail?id=1">詳情頁</router-link>

const detail = {
    // 可以通過$route.query獲取對應的值,這裏是可以省略this的
    template: '<h1>詳情界面 ----- {{$route.query.id}}</h1>',
    created() {
        console.log(this.$route)
    }
}
const home = {
    template: '<h1> home </h1>'
}
const router = new VueRouter({
    routes: [
        { path: '/', component: home },
        { path: '/detail', component: detail }
    ]
})

const vm = new Vue({
    el: '#app',
    router
})
  1. 通過動態路由設置參數

還有一種形式是 #/detail/1/zs,直接寫傳遞的值

const detail = {
    // 這種形式的就通過params來獲取參數,我們可以直接帶地址欄輸入 #/detail/1
    template: '<h1>詳情界面 ----- {{$route.params.id}}</h1>',
    created() {
        console.log(this.$route)
    }
}
const home = {
    template: '<h1> home </h1>'
}
const router = new VueRouter({
    routes: [
        { path: '/', component: home },
        // 我們需要修改路由的匹配規則,這個意思是在/後面用id來佔位,表示這個位置放一個id值
        { path: '/detail/:id', component: detail }
    ]
})

const vm = new Vue({
    el: '#app',
    router
})

導航守衛

vue-router 提供的導航守衛主要用來通過跳轉或取消的方式守衛導航。

舉個例子:我們進行用戶沒有登錄,那麼我們就需要這個守衛幫我們進行攔截,然後重定向到登錄界面,登錄後如果用戶的token或者cookie什麼的保存的信息是合法的,就允許跳轉,這就是守衛的一個作用之一。

全局前置守衛

你可以使用 router.beforeEach 註冊一個全局前置守衛:

const router = new VueRouter({ ... })

router.beforeEach((to, from, next) => {
  // ...
})

當一個導航觸發時,全局前置守衛按照創建順序調用。守衛是異步解析執行,此時導航在所有守衛 resolve 完之前一直處於 等待中

每個守衛方法接收三個參數:

  • to: Route: 即將要進入的目標路由對象
  • from: Route: 當前導航正要離開的路由
  • next: Function: 一定要調用該方法來 resolve 這個鉤子。執行效果依賴 next 方法的調用參數。
    • next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
    • next(false): 中斷當前的導航。如果瀏覽器的 URL 改變了 (可能是用戶手動或者瀏覽器後退按鈕),那麼 URL 地址會重置到 from 路由對應的地址。
    • next(’/’) 或者 next({ path: ‘/’ }): 跳轉到一個不同的地址。當前的導航被中斷,然後進行一個新的導航。你可以向 next 傳遞任意位置對象,且允許設置諸如 replace: truename: 'home' 之類的選項以及任何用在 router-linktoprop或router.push`中的選項。
    • next(error): (2.4.0+) 如果傳入 next 的參數是一個 Error 實例,則導航會被終止且該錯誤會被傳遞給 router.onError()註冊過的回調。

確保要調用 next 方法,否則鉤子就不會被 resolved。

next方法簡單的說就是通過next()來讓守衛進行放行

守衛還有些別的,但是基本上類似,參數也類似,當用到的時候去查看就行

其他守衛查詢

路由嵌套

有時我們會遇到這樣一種情形,比如我們的界面如下圖

在這裏插入圖片描述

整個頁面我們用一個router-view進行了展示,但是當我們點擊側邊菜單的時候一些東西是展示在Main中的,這時候如果我們還是通過配置如下的路由,那麼整個界面都是會重新渲染的

{
    path:'/xxx',
    component: xxxx
}

這顯然不是我們想要的結果,這我們要怎麼實現呢?

這時候就用到了子路由(嵌套路由),假設當前這個頁面的路由是 /index

首先我們需要在Main中再放一個<router-view>,作爲嵌套路由的出口,在這塊區域中進行渲染

{
    path:"/index",
    component:index,
    children:[
        {
            path:'xxx',  // 還有一種形式是 path:'/xxx'
            component:xxx
        }
    ]
}

以 / 開頭的嵌套路徑會被當作根路徑。 這讓你充分的使用嵌套組件而無須設置嵌套的路徑。

帶 / 和不帶 /

// 路由配置中設置了 帶 /,則跳轉的連接這樣設置
// 顯示 http://localhost:8080/#/xxx
<router-link to="/xxx" />
// 路由配置中設置了不帶 / ,則如下設置
// 顯示 http://localhost:8080/#/index/xxx
<router-link to="/index/xxx" />

至於你到底要配置帶不帶 / 純看你自己

路由懶加載

當打包構建應用時,JavaScript 包會變得非常大,影響頁面加載。如果我們能把不同路由對應的組件分割成不同的代碼塊,然後當路由被訪問的時候才加載對應組件,這樣就更加高效了

const Foo = () => import('./Foo.vue')

在路由配置中什麼都不需要改變,只需要像往常一樣使用 Foo

const router = new VueRouter({
  routes: [
    { path: '/foo', component: Foo }
  ]
})

把組件按組分塊

有時候我們想把某個路由下的所有組件都打包在同個異步塊 (chunk) 中。只需要使用 命名 chunk,一個特殊的註釋語法來提供 chunk name (需要 Webpack > 2.4)。

const Foo = () => import(/* webpackChunkName: "group-foo" */ './Foo.vue')
const Bar = () => import(/* webpackChunkName: "group-foo" */ './Bar.vue')
const Baz = () => import(/* webpackChunkName: "group-foo" */ './Baz.vue')

Webpack 會將任何一個異步模塊與相同的塊名稱組合到相同的異步塊中。

Vue CLI

這個腳手架工具主要是用於開發單頁面應用的,因此我們可以看到在生成的項目中只有一個HTML頁面,在public文件夾中

單文件組件

在很多 Vue 項目中,我們使用 Vue.component 來定義全局組件,緊接着用 new Vue({ el: '#container '}) 在每個頁面內指定一個容器元素
這種方式在很多中小規模的項目中運作的很好,在這些項目裏 JavaScript 只被用來加強特定的視圖。但當在更復雜的項目中,或者你的前端完全由 JavaScript 驅動的時候,

下面這些缺點將變得非常明顯:
全局定義 (Global definitions) 強制要求每個 component 中的命名不得重複
字符串模板 (String templates) 缺乏語法高亮,在 HTML 有多行的時候,需要用到醜陋的 \
不支持 CSS (No CSS support) 意味着當 HTML 和 JavaScript 組件化時,CSS 明顯被遺漏
沒有構建步驟 (No build step) 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預處理器,如 Pug (formerly Jade) 和 Babel

文件擴展名爲 .vue 的 single-file components(單文件組件) 爲以上所有問題提供瞭解決方法,並且還可以使用 webpack 或 Browserify 等構建工具

單文件組件默認有下面三部分,分別是HTML、CSS 、JS

<template>
  <!-- 通過lang來設置模板語言,默認是HTML 也可以設置爲pug jade ejs等 -->
  <div>
    <!-- 組件HTML代碼 -->
  </div>
</template>

<script>
// 可以通過增加lang屬性來設置使用什麼語言,默認是js 也可以設置爲 ts(typescript)
export default {
  // 寫之前組件中的東西,比如data(){return {}} 、 props:[] ……
};
</script>

<style scoped>
/* 
  寫css樣式,可以通過lang屬性來設置,是css、less、sass 等,
  默認情況下樣式是全局作用的,加了scoped表示只在這個組件中起作用
*/
</style>

Vue-CLI使用

通常我們把它叫做Vue腳手架

在使用Vue開發的時候,我們會用到好多工具,webpack,babel,eslint…

在使用這些工具的時候,我們需要自己手動配置好多內容,這些內容配置太過繁瑣。

vue官方就出了一個腳手架工具,這個工具裏面會把所有我們需要的配置,全部幫我們自動處理完畢。

Vue-CLI的使用

  1. 安裝
npm i -g @vue/cli
  1. 創建項目
# 終端創建項目
vue create 項目名稱
# 圖形化界面創建
vue ui
  1. 生成項目目錄結構說明
1. 最外層的,基本上全都是配置文件,不需要過多的關注
2. node_modules 所有的npm下載的包
3. public 公共的靜態資源
4. src目錄(工作中需要關注的重點!)
   1. main.js 這是整個項目的入口文件,項目就是從他開始執行的,render函數之前有介紹
   2. router.js 這是用來配置路由規則文件
   3. App.vue 這是整個項目的根組件
   4. assets目錄,靜態資源,所有的組件中用到的靜態資源,全部放到這個目錄中 (圖片 css 字體文件)
   5. components目錄,頁面內使用的組件,就都放到這個文件夾中
   6. views目錄,所有的頁面組件都放到這個文件夾裏   (頁面組件就是指會有路由規則進行對應的組件)
  1. 運行項目
# server是在package.json文件中生成項目的時候scripts配置的,根據實際配置的來修改
# "serve": "vue-cli-service serve", package.json中是這樣的

npm run server
  1. 打包項目
# bulid 同樣是在package.json 中配置的
# "build": "vue-cli-service build",

npm run bulid

​ 通過打包後會在根目錄下生成一個dist文件夾,這個文件就是經過打包壓縮後的,一般都是直接把這個文件夾放到服務器上

配置反向代理

devServer.proxy

如果你的前端應用和後端 API 服務器沒有運行在同一個主機上,你需要在開發環境下將 API 請求代理到 API 服務器。這個問題可以通過 vue.config.js 中的 devServer.proxy 選項來配置。

注意:devServe這個名字不要自己改動,之前自己改動了導致配置的不起作用

devServer.proxy 可以是一個指向開發環境 API 服務器的字符串:

module.exports = {
  devServer: {
    proxy: 'http://localhost:4000'
  }
}

這會告訴開發服務器將任何未知請求 (沒有匹配到靜態文件的請求) 代理到http://localhost:4000。

如果你想要更多的代理控制行爲,也可以使用一個 path: options 成對的對象

module.exports = {
  devServer: {
    proxy: {
      '/api': {
        target: '<url>',
        ws: true,
        changeOrigin: true,
         // '/api/home' 路徑重寫爲:'/home'
         pathRewrite: { "^/api": "/" }
      },
      '/foo': {
        target: '<other_url>'
      }
    }
  }
}

這裏的vue.config.js這個文件不需要自己去引入哪裏,在運行項目的時候腳手架會自己去讀取這個配置文件,並編譯裏面的代碼,這樣我們就實現了反向代理

至於怎麼使用?我們在axios中寫路徑的時候可以省略上面的target部分,直接以你寫的/api 開始 接後面的路徑就行。

// 假設url http://localhost:8080/home?id=1&name=zs
// 並且上面的targe配置了 http://localhost:8080/
// 那麼我們就可以直接 以 /api/home?id=1&name=zs,這樣的方式去寫

axios補充

在vue開發中存在的問題

  1. 每個需要用到axios的組件,都需要單獨引入axios

  2. axios的基地址每次都要寫

  3. headers每次都要寫

解決辦法

  1. 我們可以把axios加到Vue構造函數的原型中 這樣每個組件中都可以通過 this.$http
// 把axios加到Vue的原型上
Vue.prototype.$http = axios;
  1. axios.defaults.baseURL = “”
// 通過defaults給axios設置一個默認的baseURL,可以在所有請求中都能用到這個地址
axios.defaults.baseURL = "http://localhost:8888/api/private/v1/";

// 其實請求頭中的一些數據也可以在這裏設置
axios.defaults.headers.common['Authorization'] = localStorage.getItem("token");
  1. 通過axios請求攔截器來實現(請求攔截器和響應攔截器)
axios.interceptoers.request.use(function(config){
  // config 就是攔截到的請求相關的所有的信息
  // 這個信息是可以進行修改的
  config.headers.Authorization = localStorage.getItem("token")
  // return config不能動,這個函數中必須有這個內容
  return config;  
})

舉個形象的例子

這個攔截器就好比是海關,你如果要出去就要經過海關的檢查,這裏就相當於攔截器進行檢查,而最後的return 是必不可少的,意思就相當於給予放行。

  1. 響應攔截器
axios.interceptors.response.use(function (response) {
    // Do something with response data
    // respone就是響應對象
    return response;
});

在服務端返回響應的時候,我們也會對token進行驗證,驗證token是否有效,然後決定頁面的跳轉

Vuex

Vuex 是一個專爲 Vue.js 應用程序開發的狀態管理模式,所謂的狀態管理可以理解爲對數據的管理。

之前我們接觸到的數據傳遞分爲父傳子(屬性傳值)、子傳父(事件傳值)、兄弟組件傳值,這幾種傳值方式如果嵌套結構過於複雜的時候,會導致數據的傳遞變得很複雜,因此Vuex在這裏就體現出了重要的作用, 一個項目只需要一個Vuex來管理我們的數據,集中管理項目中的狀態的,便於組件之間的數據共享。

Vuex是Vue的一個插件,因此要在Vue之後使用,或者Vue.use(vuex)

Vuex使用

在Vuex的實例對象中可以包括以下四個部分

// 通過Vuex.Store構造函數可以創建一個store對象
// store對象中的數據是響應式的
const store = new Vuex.Store({
    // 存放狀態(數據)
    state: {
       
    },
    // mutations裏面放的就是用來修改數據的方法,一般都是寫同步操作的
    mutations: {
        
    },
    // 一些異步修改數據的方法,最終調用的還是mutation的方法
    actions:{
        
    },
    // 與vue實例中的計算屬性類似
    getters:{
        
    }
});

與vue實例綁定

// 當把store實例和根組件關聯之後
// store就可以在任意組件中通過 this.$store 就可以訪問到這個store實例了
const vm = new Vue({
    el:"#app",
    store
})

在Vue中使用

<!-- 可以通過$store 來獲取Vuex的對象-->
<div id="app">
	{{$store.state.msg}}    
</div>

mutation

更改 Vuex 的 store 中的狀態(數據)的唯一方法是提交 mutation

const store = new Vuex.store({
    state:{
        msg: "hello world"
    },
    mutations:{
        updateMsg(state, value){
          // 這個函數就是用來修改state中的數據的
          // 可以接收兩個參數
          // state 指的就是vuex的存儲數據的對象
          // value 當用戶通過 store.commit 方法來觸發這個函數的時候
          // commit方法的第二個參數就會被賦值給value
          state.msg = value
        }
    }
})

 // vuex不允許直接通過給store.state中的數據賦值 來修改數據
store.state.msg = "123"

// 在vuex中如果需要修改數據
// 那麼我們就需要在vuex中提供修改數據的方法
// 這些修改數據的方法,全都放在一個叫mutations的對象中

// mutations中的方法,如何觸發?
// 只允許傳遞一個參數,如果需要傳遞多個數據,可以傳一個對象
store.commit("updateMsg", 123)

getter

Vuex 允許我們在 store 中定義“getter”(可以認爲是 store 的計算屬性)。就像計算屬性一樣,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變纔會被重新計算。

const store = new Vuex.Store({
    state: {
        arr: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    },
    // getters就像是vue中的計算屬性
    // 如果要獲取一個數據,而這個數據是通過對state中的數據進行計算之後的結果
    // 我們就可以通過getters來實現了
    getters: {
        evenNum(state){
            return state.arr.filter(v => v % 2 == 0);
        },

        // 在vue中計算屬性是無法傳參的
        // 但是在vuex中,getters是可以傳遞參數的
        //  $store.getters.zhengchuArr(3)
        zhengchuArr(state){
            return function(value){
                return state.arr.filter(v => v % value == 0);
            }
        },
        // zhengchuArr: state => value => state.arr.filter(v => v % value == 0)
    }
})
<div id="app">
    <ul>
        <!-- 無參數 -->
        <li v-for="item in $store.getters.evenNum">{{item}}</li>
        <!-- 需要傳參 -->
        <li v-for="item in $store.getters.zhengchuArr(4)">{{item}}</li>
    </ul>
</div>

action

Action 類似於 mutation,不同在於:

  • Action 提交的是 mutation,而不是直接變更狀態。
  • Action 可以包含任意異步操作。

Action 函數接受一個與 store 實例具有相同方法和屬性的 context 對象,因此你可以調用 context.commit 提交一個 mutation,或者通過 context.statecontext.getters 來獲取 state 和 getters

const store = new Vuex.Store({
    state: {
        msg: "hello world"
    },
    // mutations裏面放的就是用來修改數據的方法
    mutations: {
        updateMsg(state, value){
            state.msg = value;
        }
    },
    actions: {
        updateMsg(context, value){
            setTimeout(() => {
                // 在修改數據的時候,其實還是提交一個mutation完成數據修改
                context.commit("updateMsg", value);
            }, 1000)
        }
    }
});

// store.commit("updateMsg", 123)
store.dispatch("updateMsg", 456);

輔助函數

我們在使用的時候常常會遇到如下的使用

// 在組件的methods中是如下使用的
toggleTodo(id) {
    this.$store.commit("toggleTodo", id);
}

// 在vuex的store中的mutation的方法
toggleTodo(state, id) {
    state.todoList.forEach(v => {
        if (v.id === id) {
            v.isCompleted = !v.isCompleted;
        }
    });
}

可以發現基本上每次我們在組件中創建的方法中只有一條調用mutation中的方法的代碼,因此可以用到Vuex提供的輔助函數mapMutation

我們怎麼在組件中引入呢??

  1. mapMutation
// 導入mapMutation方法
import { mapMutations } from 'vuex'

export default {
    // ...
    methods: {
        ...mapMutations([
            'increment', // 將 `this.increment()` 映射爲 `this.$store.commit('increment')`

            // `mapMutations` 也支持載荷:
            'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.commit('incrementBy', amount)`
        ]),
        ...mapMutations({
            add: 'increment' // 將 `this.add()` 映射爲 `this.$store.commit('increment')`
        })
    }
}

這樣做了之後,我們就可以直接在組件中使用這些方法了

  1. mapGetters

getter中的一些計算屬性也可以這麼映射出來,這樣我們就不用寫很長的一串 $store.getters.xxx,而是可以直接使用xxx

import { mapGetters } from 'vuex'

export default {
    // ...
    computed: {
        // 使用對象展開運算符將 getter 混入 computed 對象中
        ...mapGetters([
            'doneTodosCount',
            'anotherGetter',
            // ...
        ])
    }
}
  1. mapAction
import { mapActions } from 'vuex'

export default {
    // ...
    methods: {
        ...mapActions([
            'increment', // 將 `this.increment()` 映射爲 `this.$store.dispatch('increment')`

            // `mapActions` 也支持載荷:
            'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
        ]),
        ...mapActions({
            add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
        })
    }
}
  1. mapState
// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'

export default {
    // ...
    computed: mapState({
        // 箭頭函數可使代碼更簡練
        count: state => state.count,

        // 傳字符串參數 'count' 等同於 `state => state.count`
        countAlias: 'count',

        // 爲了能夠使用 `this` 獲取局部狀態,必須使用常規函數
        countPlusLocalState (state) {
            return state.count + this.localCount
        }
    })
}

注意mutation和action是映射到methods中的,而state和getters是映射到computed計算屬性中去的

module

由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。

爲了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態

')`

        // `mapActions` 也支持載荷:
        'incrementBy' // 將 `this.incrementBy(amount)` 映射爲 `this.$store.dispatch('incrementBy', amount)`
    ]),
    ...mapActions({
        add: 'increment' // 將 `this.add()` 映射爲 `this.$store.dispatch('increment')`
    })
}

}


4. mapState

```js
// 在單獨構建的版本中輔助函數爲 Vuex.mapState
import { mapState } from 'vuex'

export default {
    // ...
    computed: mapState({
        // 箭頭函數可使代碼更簡練
        count: state => state.count,

        // 傳字符串參數 'count' 等同於 `state => state.count`
        countAlias: 'count',

        // 爲了能夠使用 `this` 獲取局部狀態,必須使用常規函數
        countPlusLocalState (state) {
            return state.count + this.localCount
        }
    })
}

注意mutation和action是映射到methods中的,而state和getters是映射到computed計算屬性中去的

module

由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的對象。當應用變得非常複雜時,store 對象就有可能變得相當臃腫。

爲了解決以上問題,Vuex 允許我們將 store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的狀態
store.state.b // -> moduleB 的狀態
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章