Vue基础(二)

这篇博客详细介绍了Vue的基础知识,包括Vue实例参数补充、组件注册与通信、插槽的使用(匿名插槽、具名插槽、作用域插槽)以及单页面应用和vue-router的配置。此外,还讨论了Vue CLI的使用、axios的配置和Vuex的状态管理,强调了组件间通信的重要性,提供了多种组件通信的方法。
摘要由CSDN通过智能技术生成

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 的状态
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值