Vue2.0+3.0笔记

生命周期 

非单文件组件:全局事件时

 

 脚手架文件结构  

 ├── node_modules
   ├── public
   │   ├── favicon.ico: 页签图标
   │   └── index.html: 主页面
   ├── src
   │   ├── assets: 存放静态资源
   │   │   └── logo.png
   │   │── component: 存放组件
   │   │   └── HelloWorld.vue
   │   │── App.vue: 汇总所有组件
   │   │── main.js: 入口文件
   ├── .gitignore: git版本管制忽略的配置
   ├── babel.config.js: babel的配置文件
   ├── package.json: 应用包配置文件
   ├── README.md: 应用描述文件
   ├── package-lock.json:包版本控制文件

关于不同版本的Vue

1. vue.js与vue.runtime.xxx.js的区别:
    1. vue.js是完整版的Vue,包含:核心功能 + 模板解析器。
    2. vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
2. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。

vue.config.js配置文件

1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

ref属性

1. 被用来给元素或子组件注册引用信息(id的替代者)
2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
3. 使用方式:
    1. 打标识:```<h1 ref="xxx">.....</h1>``` 或 ```<School ref="xxx"></School>```
    2. 获取:```this.$refs.xxx```

props配置项

1. 功能:让组件接收外部传过来的数据
2. 传递数据:```<Demo name="xxx"/>```
3. 接收数据:
    1. 第一种方式(只接收):```props:['name'] ```
    2. 第二种方式(限制类型):```props:{name:String}```
    3. 第三种方式(限制类型、限制必要性、指定默认值):       
 ```js
        props:{
         name:{
         type:String, //类型
         required:true, //必要性
         default:'老王' //默认值
         }
        }
        ```
    > 备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

mixin(混入)

1. 功能:可以把多个组件共用的配置提取成一个混入对象
2. 使用方式:
    第一步定义混合:   
 ```
    {
        data(){....},
        methods:{....}
        ....
    }
    ```
    第二步使用混入:
    ​ 全局混入:```Vue.mixin(xxx)```
    ​ 局部混入:```mixins:['xxx']  ```

插件

1. 功能:用于增强Vue
npm i vue-resource
mian.ts中引入和使用
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
Vue.config.productionTip = false
// 引入插件plugins.js
import plugins from './plugins'
// 关闭Vue的生产提示
Vue.config.productionTip = false

// 引用插件
Vue.use(plugins)
//应用(使用)插件 install(Vue,x,y,z)
Vue.use(plugins,1,2,3)
// 创建vm
new Vue({
  render: h => h(App)
}).$mount('#app')
2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
3. 定义插件:  plugins.js
```js
    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
   
        // 2. 添加全局指令
        Vue.directive(....)
   
        // 3. 配置全局混入(合)
        Vue.mixin(....)
   
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    ```
export default {
  install(Vue,x,y,z){
    console.log(x,y,z)
    //全局过滤器
    Vue.filter('mySlice',function(value){
      return value.slice(0,4)
    })

    //定义全局指令
    Vue.directive('fbind',{
      //指令与元素成功绑定时(一上来)
      bind(element,binding){
        element.value = binding.value
      },
      //指令所在元素被插入页面时
      inserted(element,binding){
        element.focus()
      },
      //指令所在的模板被重新解析时
      update(element,binding){
        element.value = binding.value
      }
    })
    //定义混入
    Vue.mixin({
      data() {
        return {
          x:100,
          y:200
        }
      },
    })

    //给Vue原型上添加一个方法(vm和vc就都能用了)
    Vue.prototype.hello = ()=>{alert('你好啊')}
  }
}
4. 使用插件:```Vue.use()```,mian.ts中
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件vue-resource
import vueResource from 'vue-resource'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 使用插件
Vue.use(vueResource)

//创建vm
new Vue({
  render: h => h(App),
  beforeCreate() {//开启总线
    Vue.prototype.$bus = this
  },
}).$mount('#app')

scoped样式

1. 作用:让样式在局部生效,防止冲突。
2. 写法:```<style scoped>```

总结TodoList案例

1. 组件化编码流程:
    ​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
    ​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
    ​       1).一个组件在用:放在组件自身即可。
    ​       2). 一些组件在用:放在他们共同的父组件上(<span style="color:red">状态提升</span>)。
    ​ (3).实现交互:从绑定事件开始。
2. props适用于:
    ​ (1).父组件 ==> 子组件 通信
    ​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)
3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

webStorage

1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3. 相关API:  
  1. ```xxxxxStorage.setItem('key', 'value');```
                  该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. ```xxxxxStorage.getItem('person');```

        ​      该方法接受一个键名作为参数,返回键名对应的值。

    3. ```xxxxxStorage.removeItem('key');```

        ​      该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. ``` xxxxxStorage.clear()```

        ​      该方法会清空存储中的所有数据。
4. 备注:
    1. SessionStorage存储的内容会随着浏览器窗口关闭而消失。
    2. LocalStorage存储的内容,需要手动清除才会消失。
    3. ```xxxxxStorage.getItem(xxx)```如果xxx对应的value获取不到,那么getItem的返回值是null。
    4. ```JSON.parse(null)```的结果依然是null。

组件的自定义事件

1. 一种组件间通信的方式,适用于:子组件 ===> 父组件
2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
3. 绑定自定义事件:
    1. 第一种方式,在父组件中:```<Demo @atguigu="test"/>```  或 ```<Demo v-on:atguigu="test"/>```
    2. 第二种方式,在父组件中:
        ```js
        <Demo ref="demo"/>
        ......
        mounted(){
           this.$refs.xxx.$on('atguigu',this.test)
        }
        ```
    3. 若想让自定义事件只能触发一次,可以使用```once```修饰符,或```$once```方法。
4. 触发自定义事件:```this.$emit('atguigu',数据)```    
5. 解绑自定义事件```this.$off('atguigu')```
6. 组件上也可以绑定原生DOM事件,需要使用```native```修饰符。
7. 注意:通过```this.$refs.xxx.$on('atguigu',回调)```绑定自定义事件时,回调要么配置在methods中要么用箭头函数,否则this指向会出问题!

全局事件总线(GlobalEventBus):任意组件事件之间的传递,如爷孙组件

1. 一种组件间通信的方式,适用于任意组件间通信this.$bus.
 
 
2. 安装全局事件总线:
   ```js
   new Vue({
      ......
      beforeCreate() {
         Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
      },
       ......
   })
   ```
3. 使用事件总线:
   1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。   
      ```js
      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo) // $bus总线.$on绑定事件(‘事件名’,触发调用函数)
     } ```
 2. $emit提供数据:```this.$bus.$emit('xxxx',数据)```
item.vue中需触发事件
methods: {
    //勾选or取消勾选
    handleCheck(id){
      // 通知App组件将对应的todo对象的done值取反
      // 数据在App中,前往App
      // this.checkTodo(id)
      // 总线: 触发事件
      this.$bus.$emit('checkTodo',id)
    },
    // 删除
    handleDelete(id){
      if (confirm('确定删除吗?')){
        // this.deleteTodo(id)
        // 总线: 触发事件
        this.$bus.$emit('deleteTodo',id)
      }
    }
  }
4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件销毁组件事件绑定注:必须带事件名参数,不然销毁所有组件this.$bus.$off('事件名')
app.vue
// 总线开启:接收item.vue组件传参,使用钩子$on和$off完整写法
  mounted(){
    // $bus总线.$on绑定事件(‘事件名’,触发调用函数)
    this.$bus.$on('checkTodo',this.checkTodo)
    this.$bus.$on('deleteTodo',this.deleteTodo)
  },
  // 总线.$off解绑时间
  beforeDestroy(){
    this.$bus.$off('checkTodo')
    this.$bus.$off('deleteTodo')
  }
总线:场景更适应爷孙深层次组件事件之间的传递。

消息订阅与发布(pubsub)

1.   一种组件间通信的方式,适用于任意组件间通信(需要数据的地方是订阅消息,提供数据的地方是发布消息)
2. 使用步骤:
   1. 安装pubsub:```npm i pubsub-js```
   2. 引入: ```import pubsub from 'pubsub-js'```,一般组件挂载完后进行订阅,mounted(){}
   3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
      ```js
      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      ```
   4. 提供数据:```pubsub.publish('xxx',数据)```
   5. 最好在beforeDestroy钩子中,用```PubSub.unsubscribe(pid)```去取消订阅
注:相对于总线和消息订阅实现功能,Vue推荐使用总线   

nextTick :实际开发中使用较多

1. 语法:```this.$nextTick(回调函数)```
<template>
  <li>
    <label>
      <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
      <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props -->
      <!-- <input type="checkbox" v-model="todo.done"/> -->
      <span v-show="!todo.isEdit">{{todo.title}}</span>
      <!--@blur='handleBlur('数据,新数据')’-->
      <input
        type="text"
        v-show="todo.isEdit"
        :value="todo.title"
        @blur="handleBlur(todo,$event)"
        ref="inputTitle"  //使用nextTick
      >
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
    <button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button>
  </li>
</template>

<script>
// 引入消息订阅pubsub:发布
import pubsub from 'pubsub-js'

export default {
  name:'MyItem',
  //声明接收todo
  props:['todo'],
  methods: {
    //勾选or取消勾选
    handleCheck(id){
      //通知App组件将对应的todo对象的done值取反
      // this.checkTodo(id)
      this.$bus.$emit('checkTodo',id)
    },
    //删除
    handleDelete(id){
      if(confirm('确定删除吗?')){
        //通知App组件将对应的todo对象删除
        // this.deleteTodo(id)
        // this.$bus.$emit('deleteTodo',id)
        // publish发布消息
        pubsub.publish('deleteTodo',id)
      }
    },
    // 编辑状态
    handleEdit(todo){
      // 使用hasOwenProperty('isEdit')判断自身是否有isEdit属性
      if (todo.hasOwnProperty('isEdit')){
        todo.isEdit = true
      }else {
        // console.log('@')
        this.$set(todo,'isEdit',true)
      }
      // nextTick:下一次 DOM 更新结束后执行
      // 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
      this.$nextTick(function (){
        this.$refs.inputTitle.focus()
      })
    },
    // 失去焦点回调(真正执行修改逻辑):编辑状态结束
    // 接收@blur='handleBlur('数据,新数据')’
    handleBlur(todo,e){
      todo.isEdit = false
      if(!e.target.value.trim()) return alert('输入不能为空!')
      // 总线:提交新数据,$emit('事件名','数据_id','修改的新数据')
      this.$bus.$emit('upDataTodo',todo.id,e.target.value)
    }
  },
}
</script>
2. 作用:在下一次 DOM 更新结束后执行其指定的回调。 nextTick所指定的回调会在下一次 DOM 更新结束后执行
3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
定时器虽然也能达到效果,但推荐使用nextTick

Vue封装的过度与动画

1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
2. 图示:<img src="https://img04.sogoucdn.com/app/a/100520146/5990c1dff7dc7a8fb3b34b4462bd0105" style="width:60%" />
 
3. 写法:
   1. 准备好样式:
      - 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      - 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点

   2. 使用```<transition>```包裹要过度的元素,并配置name属性:
      ```vue
      <transition name="hello">
         <h1 v-show="isShow">你好啊!</h1>
      </transition>
      ```

Test.vue完整代码:

<!--使用过渡动画-->
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!--过渡标签:transition -->
    <!--过渡标签自定义name='hello':style中 v-变更hello-enter-active-->
    <!--:appear='true'缺省写法 ,直接 appear默认-->
    <!--<transition name="hello" :appear="true">-->
    <transition name="hello" appear>
      <h1 v-show="isShow" >你好啊</h1>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'Test',
  data () {
    return {
      isShow:true
    }
  },
}
</script>

<style scoped>
  h1{
    background-color: orange;
  }
  /*使用过渡标签后自动动画,Vue特定:v-enter-active*/
  /*vue-enter进入-active激活*/
  /*.v-enter-active {
    !*css3动画来:匀速*!
    animation: atguigu 1s linear;
  }*/
  /*<!--过渡标签自定义name='hello':style中 v-变更hello-enter-active-->*/
  .hello-enter-active {
    animation: atguigu 1s linear;
  }
  /*使用过渡标签后自动动画,Vue特定:v-leave-active*/
  /*vue-leave离开-active激活*/
  /*<!--过渡标签自定义name='hello':style中 v-变更hello-leave-active-->*/
  /*.v-leave-active {*/
  .hello-leave-active {
    /*css3动画离开*/
    /*播放动画:atguigu 1秒 reverse翻转*/
    animation: atguigu 1s reverse;
  }

  /*定义动画关键帧*/
  @keyframes atguigu {
    from{
      transform: translateX(-100%);
    }
    to{
      transform: translateY(0px);
    }
  }
</style>
   3. 备注:若有多个元素需要过度,则需要使用:```<transition-group>```,且每个元素都要指定```key```值。

Test2.vue完整代码:

<!--不使用过渡动画-->
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!--过渡标签:transition -->
    <!--过渡标签自定义name='hello':style中 v-变更hello-enter-active-->
    <!--:appear='true'缺省写法 ,直接 appear默认-->
    <!--<transition name="hello" :appear="true">-->
    <transition name="hello" appear>
      <h1 v-show="isShow" >你好啊</h1>
    </transition>
    <!--有多个元素需要过度,则需要使用:```<transition-group>```,且每个元素都要指定```key```值-->
    <!--使用场景:如效果取反时-->
    <transition-group name="hello" appear>
      <h1 v-show="!isShow" key="1">你好啊!</h1>
      <h1 v-show="isShow" key="2">尚硅谷</h1>
    </transition-group>
  </div>
</template>

<script>
export default {
  name: 'Test',
  data () {
    return {
      isShow:true
    }
  },
}
</script>

<style scoped>
/*破坏原有h1效果:transition */
h1{
  background-color: orange;
  /*transition: 0.5s linear;*/
}
/*进入的起点,离开的终点*/
.hello-enter,.hello-leave-to {
  transform: translateX(-100%);
}
/*避免破坏h1原有代码:*/
.hello-enter-active,.hello-leave-active {
  transition: 0.5s linear;
}
/*进入的终点、离开的起点*/
.hello-enter-to,.hello-leave {
  transform: translateX(0);
}

</style>

使用animate.css第三方库方法

安装:npm install -y animate.css --save

Test3.vue完整代码

<!--使用animate.css第三方库-->
<template>
  <div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <!--过渡标签:transition -->
    <!--过渡标签自定义name='hello':style中 v-变更hello-enter-active-->
    <!--:appear='true'缺省写法 ,直接 appear默认-->
    <!--<transition name="hello" :appear="true">-->
    <!--<transition name="hello" appear>
      <h1 v-show="isShow" >你好啊</h1>
    </transition>-->

    <!--有多个元素需要过度,则需要使用:```<transition-group>```,且每个元素都要指定```key```值-->
    <!--使用场景:如效果取反时-->
    <!--使用第三方animate.css时,按官网配置name='animate__animated animate__bounce'
    enter-active-class="效果名animate__swing"
    leave-active-class="效果名animate__backOutUp" 三个-->
    <transition-group
      name="animate__animated animate__bounce"
      enter-active-class="animate__swing"
      leave-active-class="animate__backOutUp"
      appear>
      <h1 v-show="!isShow" key="1">你好啊!</h1>
      <h1 v-show="isShow" key="2">尚硅谷</h1>
    </transition-group>
  </div>
</template>

<script>
// 引入第三方animate.css库
import 'animate.css'

export default {
  name: 'Test',
  data () {
    return {
      isShow:true
    }
  },

}
</script>

<style scoped>
/*破坏原有h1效果:transition */
h1{
  background-color: orange;
  /*transition: 0.5s linear;*/
}

/*使用第三库时,以下省略不写*/
/*
!*进入的起点,离开的终点*!
.hello-enter,.hello-leave-to {
  transform: translateX(-100%);
}
!*避免破坏h1原有代码:*!
.hello-enter-active,.hello-leave-active {
  transition: 0.5s linear;
}

!*进入的终点、离开的起点*!
.hello-enter-to,.hello-leave {
  transform: translateX(0);
}
*/

</style>

 

vue脚手架配置代理

Vue中推荐使用ajax库:   Axios库

npm i axios

方法一

​  在vue.config.js中添加如下配置:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false, // 关闭语法检查

  // 开启代理服务器:端口5000
  // 弊端1:不能配置多个代理,只能唯一
  // 弊端2:请求本地无时,才走代理服务器
  devServer:{
    proxy:"http://localhost:5000"
  }
})
说明:
1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

方法二

​  编写vue.config.js配置具体代理规则:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,
  lintOnSave: false, // 关闭语法检查

  // 方式一
  // 开启代理服务器:端口5000
  // 弊端1:不能配置多个代理,只能唯一
  // 弊端2:请求本地无时,才走代理服务器
  /*devServer:{
    proxy:"http://localhost:5000"
  },*/
  // 方式二: 官网devServer.proxy中
  // 更多的代理控制行为
  devServer: {
    proxy: {
      // 代理1:正常写法
      // 注:使用一个 path: options 成对的对象 重点
      // ‘/api’前缀:可自定义
      '/atguigu': {//匹配所有以’/atguigu‘开头的请求路径
        target: 'http://localhost:5000', // 代理目标的基础路径
        pathRewrite: {'^/atguigu':''},// 重写路径:{key'匹配所有^/atguigu : value为'空'}
        ws: true, // 用于支持websocket
        // 用于控制请求头中的host值
        changeOrigin: true // 来自于路径:ture为5000请求服务器端口一致,false默认为8080真实端口
      },
      '/demo': {
        target: 'http://localhost:5001',
        // 重写路径:{key'匹配所有^/atguigu : value为'空'}
        pathRewrite: {'^/demo':''},
        ws: true, // 用于支持websocket
        // 用于控制请求头中的host值
        changeOrigin: true // 来自于路径:false为5000请求服务器端口一致,true为8080真实端口
      },
      // 代理2:精简写法
      '/foo': {
        target: '<other_url>'
      }
    }
  }
})
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080,
   changeOrigin默认值为true
 
对应的App.vue完整代码
<template>
  <div>
    <button @click="getStudents">获取学生信息</button>
    <button @click="getCars">获取汽车信息</button>
  </div>
</template>

<script>
// 引入axios
import axios from 'axios'

export default {
  name:'App',
  methods: {
    getStudents(){
      // 请求get().then()返回值:前提配置好vue.config.js中代理服务器和端口
      // vue.config.js中配置了/atguigu前缀,端口后放前缀
      // 问题:代理转发路径时带有/atguigu报错,需重写代理路径pathRewrite
      axios.get('/atguigu/http://localhost:8080/atguigu/students').then(
        // 成功和失败:获取的是对象data,不是data()方法
        Response => {
          console.log('请求成功了!',Response.data)
        },
        error => {
          console.log('请求失败了',error.message)
        }
      )
    },
    getCars(){
      axios.get('http://localhost:8080/demo/cars').then(
          response => {
            console.log('请求成功了',response.data)
          },
          error => {
            console.log('请求失败了',error.message)
          }
      )
    }
  }
}
</script>
<style>
</style>
说明:
1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
2. 缺点:配置略微繁琐,请求资源时必须加前缀。
 

github_Search案例

1、拆解index.html为Search.vue和List.vue组件

2、style中引入index.css

3、引入第三方库:bootstrap.css

  1、src/assets静态资源创建Css文件夹资源:问题报错 如字体等

// 引入css问题:第三方资源字体或其他无资源时报错
// 问题解决:在公共资源public里建立静态资源Css
import './assets/css/bootstrap.css'

  2、将第三方库移动至public文件夹下,并在index.html中添加 <link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">

 

<!DOCTYPE html>
<html lang="">
  <head>
      <meta charset="utf-8">
      <!--  针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面  -->
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <!--  开启移动端的理想视口-->
      <meta name="viewport" content="width=device-width,initial-scale=1.0">
      <!--  配置页签图标-->
      <link rel="icon" href="<%= BASE_URL %>favicon.ico">
      <!--  注:引入第三方样式 -->
      <link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
      <!--  配置网页标题-->
      <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <!--当浏览器不支持js时noscript中的元素就会被渲染-->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <!--容器-->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

 完善案例完整代码:

mian.ts

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
  render: h => h(App),
  beforeCreate() {//开启总线
    Vue.prototype.$bus = this
  },
}).$mount('#app')

App.vue

<template>
  <div class="container">
    <Search/>
    <List/>
  </div>
</template>

<script>
import Search from '@/components/Search'
import List from '@/components/List'
// 引入css问题:第三方资源字体或其他无资源时报错
// 问题解决:在公共资源public里建立静态资源Css
// import './assets/css/bootstrap.css'

export default {
  name:'App',
  components:{Search,List},
}
</script>
<style>

</style>

components中Search.vue

<template>
  <section class="jumbotron">
    <h3 class="jumbotron-heading">Search Github Users</h3>
    <div>
      <!--获取用户关键词:v-model='keyword'-->
      <input type="text" placeholder="enter the name you search" v-model="keyWord"/>&nbsp;
      <button @click="searchUsers">Search</button>
    </div>
  </section>
</template>

<script>
// 引入axios
import axios from 'axios'

export default {
  name: 'Search',
  data(){
    return{
      keyWord: '',
    }
  },
  methods: {
    searchUsers(){
      //总线:提供数据,请求前更新List的数据
      this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})

      // 注意:需要模板解析要使用 ~ `` 符号包裹,q=${this.keyWord}
      axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
          response => {
            console.log('请求成功了')
            // 请求成功后更新List的数据
            this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
          },
          error => {
            // 请求后更新List的数据
            this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
          }
      )
    }
  }

}
</script>

<style scoped>

</style>

components中List.vue

<template>
  <div class="row">
    <!-- 展示用户列表 -->
    <!--使用获取的用户数据绑定:user.login\user.html_url\user.avatar_url属性-->
    <div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
      <a :href="user.html_url" target="_blank">
        <img :src="user.avatar_url" style='width: 100px'/>
      </a>
      <p class="card-text">{{user.login}}</p>
    </div>
    <!-- 展示欢迎词 -->
    <h1 v-show="info.isFirst">欢迎使用!</h1>
    <!-- 展示加载中 -->
    <h1 v-show="info.isLoading">加载中....</h1>
    <!-- 展示错误信息 -->
    <h1 v-show="info.errMsg">{{info.errMsg}}</h1>
  </div>
</template>

<script>
export default {
  name: 'List',
  data(){
    return {
      info: {
        isFirst: true,
        isLoading: false,
        errMsg:'',
        users: []
      },
    }
  },
  // 总线:接收获取数据,使用mounted钩子
  mounted () {
    this.$bus.$on('updateListData',(dataObj)=>{
      // info替换成dataObj:达到响应式效果
      // 合并属性:{...this.info,...dataObj}以...dataObj属性为主
      this.info = {...this.info,...dataObj}
    })
  },

}
</script>

<style scoped>
.album {
  min-height: 50rem; /* Can be removed; just added for demo purposes */
  padding-top: 3rem;
  padding-bottom: 3rem;
  background-color: #f7f7f7;
}

.card {
  float: left;
  width: 33.333%;
  padding: .75rem;
  margin-bottom: 2rem;
  border: 1px solid #efefef;
  text-align: center;
}

.card > img {
  margin-bottom: .75rem;
  border-radius: 100px;
}

.card-text {
  font-size: 85%;
}
</style> 

Vue项目中使用2个Ajax库插件和axios的区别

mian.ts中使用插件

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件vue-resource
import vueResource from 'vue-resource'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 使用插件
Vue.use(vueResource)

//创建vm
new Vue({
  render: h => h(App),
  beforeCreate() {//开启总线
    Vue.prototype.$bus = this
  },
}).$mount('#app')

Search.vue中:this.http.get替换axios.get

 

 

  

 替换后,2者功能完全一致。但是推荐使用axios库方法,插件Vue.use(vueResource)不推荐

插槽

1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于父组件 ===> 子组件
2. 分类:默认插槽、具名插槽、作用域插槽
3. 使用方式:
   1. 默认插槽:
      app.vue父组件中:              
<Category>
      <div>html结构1</div>
</Category>
<template>
  <div class="container">
    <!--绑定事件:listData统一事件名,便于接收-->
    <!--出现列表不同样式时:不需统一事件名-->
    <!--<Category title="美食" :listData="foods">-->
    <Category title="美食">
      <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
    </Category>
    <Category title="游戏">
      <ul>
        <li v-for="(g,index) in games" :key="index">{{ g }}</li>
      </ul>
    </Category>
    <Category title="电影" :listData="films">
      <!--控制播放controls:无法播放问题-->
      <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
    </Category>
  </div>
</template>

<script>
import Category from '@/components/Category'
export default {
  name:'App',
  components:{Category},
  data(){
    return{
      foods:['火锅','烧烤','小龙虾','牛排'],
      games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
      films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
    }
  }
}
</script>

<style lang="css">
  .container{
    display: flex;
    /*相邻距离:调整主轴*/
    justify-content: space-around;
  }
  video{
    width: 100%;
  }

</style>
      Category.vue子组件中:
 
<template>
   <div>
      <!-- 定义插槽 -->
      <slot>插槽默认内容...</slot>
      </div>
</template>            
<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
    <!--<ul>
      <li v-for="(item,index) in listData" :key="index">{{ item }}</li>
    </ul>-->
  </div>
</template>

<script>
export default {
  name: 'Category',
  props: ['listData',"title"]

}
</script>

<style scoped>
  .category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
  }
  h3{
    text-align: center;
    background-color: orange;
  }
  img{
    width: 100%;
  }
</style>
   2. 具名插槽:
App.vue中:父组件
      ```vue
      父组件中:
              <Category>
                  <template slot="center">
                    <div>html结构1</div>
                  </template>
     
                  <template v-slot:footer>
                     <div>html结构2</div>
                  </template>
              </Category>

完整代码

<template>
  <div class="container">   
    <Category title="美食">
      <!--slot具名插槽-->
      <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
      <a slot="footer" href="http://www.atguigu.com" >更多美食</a>
    </Category>
    <Category title="游戏">
      <ul slot="center">
        <li v-for="(g,index) in games" :key="index">{{ g }}</li>
      </ul>
      <div class="foot" slot="footer">
        <a href="http://www.atguigu.com">单机游戏</a>
        <a href="http://www.atguigu.com">网络游戏</a>
      </div>
    </Category>
    <Category title="电影" :listData="films">
      <!--控制播放controls:无法播放问题-->
      <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
      <!--此时template标签中的内容显示在页面上,但是看dom结构没有template标签-->
      <!--template下Vue2.6插槽:v-slot才有效新写法-->
      <template v-slot:footer>
      <!--<template slot="footer">-->
        <div  class="foot">
          <a href="http://www.atguigu.com">经典</a>
          <a href="http://www.atguigu.com">热门</a>
          <a href="http://www.atguigu.com">推荐</a>
        </div>
        <h4>欢迎前来观影</h4>
      </template>
    </Category>
  </div>
</template>

<script>
import Category from '@/components/Category'

export default {
  name:'App',
  components:{Category},
  data(){
    return{
      foods:['火锅','烧烤','小龙虾','牛排'],
      games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
      films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
    }
  }
}
</script>

<style lang="css">
/*可以合并简写,功能相同*/
  .container,.foot{
    /*弹性布局*/
    display: flex;
    /*相邻距离:调整主轴*/
    justify-content: space-around;
  }
  /*可以合并简写*/
  /*.foot{
    display: flex;
    !*相邻距离:调整主轴*!
    justify-content: space-around;
  }*/
  video{
    width: 100%;
  }
  h4{
    text-align: center;
  }

</style>
      子组件中:
<template>
      <div>
          <!-- 定义插槽 -->
          <slot name="center">插槽默认内容...</slot>
          <slot name="footer">插槽默认内容...</slot>
       </div>
</template>

Category.vue完整代码

<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
    <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>
  </div>
</template>

<script>
export default {
  name: 'Category',
  props: ['listData',"title"]

}
</script>

<style scoped>
  .category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
  }
  h3{
    text-align: center;
    background-color: orange;
  }
  img{
    width: 100%;
  }
</style>

   3. 作用域插槽必须使用 <template scope='自定义域名’> 标签
      1. 理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
      2. 具体编码:     
 ```vue
         父组件中:
                 <Category>
                     <template scope="scopeData">
                         <!-- 生成的是ul列表 -->
                         <ul>
                             <li v-for="g in scopeData.games" :key="g">{{g}}</li>
                         </ul>
                     </template>
                 </Category>
         
                 <Category>
                     <template slot-scope="scopeData">
                         <!-- 生成的是h4标题 -->
                         <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
                     </template>
                 </Category>
         子组件中:
                 <template>
                     <div>
                         <slot :games="games"></slot>
                     </div>
                 </template>
                 
                 <script>
                     export default {
                         name:'Category',
                         props:['title'],
                         //数据在子组件自身
                         data() {
                             return {
                                 games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                             }
                         },
                     }
                 </script>
app.vue完整代码:
<!--作用域插槽:结构是使用者来决定,数据在组件中-->
<template>
  <div class="container">
    <Category title="游戏">
      <!--作用域插槽:必须使用<template scope="自定义域名"标签接收data对象games-->
      <template scope="atguigu">
        <!--无序列表ul-->
        <ul>
          <li v-for="(g,index) in atguigu.games" :key="index">{{ g }}</li>
        </ul>
      </template>
    </Category>

    <Category title="游戏">
      <!--ES6结构赋值:scope="{games}"优化简写-->
      <template scope="{games}">
        <!--有序列表ol-->
        <ol>
          <!--ES6结构赋值:scope="{games}"优化简写-->
          <!--<li v-for="(g,index) in atguigu.games" :key="index">{{ g }}</li>-->
          <li v-for="(g,index) in games" :key="index">{{ g }}</li>
        </ol>
      </template>
    </Category>

    <Category title="游戏">
      <!--新的写法-->
      <template v-slot="{games}">
      <!--<template scope="{games}">-->
        <h4 v-for="(g,index) in games" :key="index">{{ g }}</h4>
      </template>
    </Category>
  </div>
</template>

<script>
import Category from '@/components/Category'

export default {
  name:'App',
  components:{Category},

}
</script>

<style lang="css">
/*可以合并简写,功能相同*/
  .container,.foot{
    /*弹性布局*/
    display: flex;
    /*相邻距离:调整主轴*/
    justify-content: space-around;
  }
  /*可以合并简写*/
  /*.foot{
    display: flex;
    !*相邻距离:调整主轴*!
    justify-content: space-around;
  }*/
  video{
    width: 100%;
  }
  h4{
    text-align: center;
  }
</style>

Category.vue

<template>
  <div class="category">
    <h3>{{ title }}分类</h3>
    <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
    <!--作用域插槽:绑定事件data数据对象games-->
    <!--可以传多个对象:msg-->
    <slot :games="games" msg="hello">我是默认的一些内容</slot>

  </div>
</template>

<script>
export default {
  name: 'Category',
  props: ['listData',"title"],
  data(){
    return{
      games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
    }
  }
}
</script>

<style scoped>
  .category{
    background-color: skyblue;
    width: 200px;
    height: 300px;
  }
  h3{
    text-align: center;
    background-color: orange;
  }
  img{
    width: 100%;
  }
</style>   ```

Vuex (重点掌握

1.概念

​     在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

2.何时使用?

​     多个组件需要共享数据时,Vue.use(vuex)
Vue总线时:

 

 Vuex时:

 

 

 3.搭建vuex环境

 

 

 

 

 

 

1. 创建文件:```src/store/index.js```
   ```js
   //引入Vue核心库
   import Vue from 'vue'
   //引入Vuex
   import Vuex from 'vuex'
   //应用Vuex插件
   Vue.use(Vuex)
   
   //准备actions对象——响应组件中用户的动作
   const actions = {}
   //准备mutations对象——修改state中的数据
   const mutations = {}
   //准备state对象——保存具体的数据
   const state = {}
   
   //创建并暴露store
   export default new Vuex.Store({
      actions,
      mutations,
      state
   })

完整代码:

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions对象——用于响应组件中的动作
const actions = {}
//准备mutations对象——用于操作数据(state)状态
const mutations = {}
//准备stats对象——用于存储数据
const stats = {}

//创建Store({配置对象})
/*const store = new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  stats,

})
暴露导出接口
export default store*/
// 进行简写优化
// 创建并暴露store
export default new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  stats,
})
 
2. 在```main.js```中创建vm时传入```store```配置项
   ```js
   ......
   //引入store
   import store from './store'
   ......
   
   //创建vm
   new Vue({
      el:'#app',
      render: h => h(App),
      store
   })
   ```
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import vueResource from 'vue-resource'
//引入store
import store from './store'

//关闭Vue的生产提示
Vue.config.productionTip = false
//使用插件
Vue.use(vueResource)

//创建vm
new Vue({
  store,// 同名触发简写store:store,
  render: h => h(App),
  beforeCreate() {
    Vue.prototype.$bus = this
  }
}).$mount("#app")

4.基本使用

1. 初始化数据、配置```actions```、配置```mutations```,操作文件```store.js```或Store/index.js  
 ```js
   //引入Vue核心库
   import Vue from 'vue'
   //引入Vuex
   import Vuex from 'vuex'
   //引用Vuex
   Vue.use(Vuex)
   
   const actions = {
       //响应组件中加的动作
      jia(context,value){
         // console.log('actions中的jia被调用了',miniStore,value)
         context.commit('JIA',value)
      },
   }
   
   const mutations = {
       //执行加
      JIA(state,value){
         // console.log('mutations中的JIA被调用了',state,value)
         state.sum += value
      }
   }
   
   //初始化数据
   const state = {
      sum:0
   }
   
   //创建并暴露store
   export default new Vuex.Store({
      actions,
      mutations,
      state,
   })
   ```
 
2. 组件中读取vuex中的数据:```$store.state.sum```,模板中不用加this,
3. 组件中修改vuex中的数据:```$store.dispatch('action中的方法名',数据)``` 或 ```$store.commit('mutations中的方法名',数据)```
   >  备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写```dispatch```,直接编写```commit```

5.getters的使用

1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工
2. 在```store.js```中追加```getters```配置   
   const getters = {
      bigSum(state){
         return state.sum * 10
      }
   }
   
   //创建并暴露store
   export default new Vuex.Store({
      ......
      getters
   })
   ```
3. 组件中读取数据:```$store.getters.bigSum```

6.四个map方法的使用

1. <strong>mapState方法:</strong>用于帮助我们映射```state```中的数据为计算属性  
 ```js
   computed: {
       //借助mapState生成计算属性:sum、school、subject(对象写法)
        ...mapState({sum:'sum',school:'school',subject:'subject'}),
           
       //借助mapState生成计算属性:sum、school、subject(数组写法)
       ...mapState(['sum','school','subject']),
   },
   ```
2. <strong>mapGetters方法:</strong>用于帮助我们映射```getters```中的数据为计算属性
 ```js
   computed: {
       //借助mapGetters生成计算属性:bigSum(对象写法)
       ...mapGetters({bigSum:'bigSum'}),
   
       //借助mapGetters生成计算属性:bigSum(数组写法)
       ...mapGetters(['bigSum'])
   },
   ```

完整代码:index.js

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions对象——用于响应组件中的动作
const actions = {
  // jia:function (){可简写
  /*jia(context,value){
    // console.log('actions中的jia被调用了!',context,value)
    context.commit('JIA',value)
  },
  jian(context,value){
    context.commit('JIAN',value)
  },*/ // 逻辑简写:功能commit直接mutations

  jiaOdd(context,value){
    console.log('actions中的jiaOdd被调用了!')
    // 使用上下文获取this.$store.state.sum判断
    // if (this.$store.state.sum % 2){
    if (context.state.sum % 2){
      context.commit('JIA',value)
    }
  },
  jiaWait(context,value){
    setTimeout(()=>{
      context.commit('JIA',value)
    },500)
  }
}
//准备mutations对象——用于操作数据(state)状态
const mutations = {// 检测数据变化
  JIA(state,value){
    // console.log('mutations中的JIA被调用了!',state,value)
    state.sum += value
  },
  JIAN (context, value) {
    state.sum -= value
  }
}
//准备stats对象——用于存储数据
const state = {
  sum: 0, //当前的和,初始数据
  School: '尚硅谷',
  subject: '前端',
}
// 准备getters:用于将state中的数据进行加工
const getters = {
  bigSum(state){
    return state.sum*10
  }
}

//创建Store({配置对象})
/*const store = new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  stats,

})
暴露导出接口
export default store*/
// 进行简写优化
// 创建并暴露store
export default new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  state,
  getters,
})

Count.vue完整代码:

<template>
  <div>
    <!--优化:可用计算属性来省略$store.state.-->
    <!--<h1>当前求和为:{{ $store.state.sum }}</h1>-->
    <h1>当前求和为:{{ sum }}</h1>
    <h3>当前求和为10倍:{{ bigSum }}</h3>
    <h3>在{{ School }},做{{ subject }}</h3>
    <!--select单选或多选菜单-->
    <!--v-model.number前置转换数值型-->
    <select v-model.number="n" name="" id="">
      <!--v-model.number前置转换数值型::value无需绑定强制数值型-->
      <!--<option :value="1">1</option>-->
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
    <button @click="incrementOdd">当前求和为奇数再加</button>
    <button @click="incrementWait">等一等再加</button>
  </div>
</template>

<script>
// 引入 {映射状态} from vuex 生成State代码
import {mapState} from 'vuex'
import {mapGetters} from 'vuex'

export default {
  name: 'Count',
  data(){
    return{
      n: 1, //用户选择的数字
    }
  },
  computed: {
    // 靠程序员自己亲自去写计算属性
    /*sum(){
      return this.$store.state.sum
    },
    School(){
      return this.$store.state.School
    },
    subject(){
      return this.$store.state.subject
    },*/
    // 借助mapState生成计算属性,从state中读取数据。(对象写法):效果同上
    // ...mapState({})-ES6风格:逐一展开mapState中的key:value展示出来,等同
    ...mapState({sum:'sum',School:'School',subject:'subject'}),

    // 再优化:借助mapState生成计算属性,从state中读取数据。(数组写法):效果同上
    // ...mapState([value1,value2,value3]),只要kv对中的value,而且kv名要一致才可简写
    ...mapState(['sum','School','subject']),

    /* ******************************** */

    /*bigSum(){
      return this.$store.getters.bigSum
    },*/
    // 借助mapGetters生成计算属性,从getters中读取数据。(对象写法):效果同上
    // ...mapGetters({})-ES6风格:逐一展开mapGetters中的key:value展示出来,等同
    ...mapGetters({bigSum:'bigSum'}),
    
    //再优化:kv名要一致,(数组写法):效果同上
    ...mapGetters(['bigSum']),
  },
  methods: {
    increment(){
      // this.sum += this.n
      // 使用vuex的$store.dispatch(key,value)
      // this.$store.dispatch('jia',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIA',this.n)
    },
    decrement(){
      // this.sum -= this.n
      // this.$store.dispatch('jian',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIAN',this.n)
    },
    incrementOdd(){
      /*if (this.sum % 2) {
        this.sum += this.n
      }*/
      /*if (this.$store.state.sum % 2){
        this.$store.dispatch('jia',this.n)
      }*/
      this.$store.dispatch('jiaOdd',this.n)
    },
    incrementWait(){
      /*setTimeout(()=>{
        // this.sum += this.n
        this.$store.dispatch('jia',this.n)
      },500)*/
      this.$store.dispatch('jiaWait',this.n)
    },
  },
  mounted () {
    // console.log('Count',this.$store) // 组件挂载直接输出this.$store
    // 使用映射mapstate({key:value}):({sum():this.$store.state.sum})
    const x = mapState({sum:'sum',School:'School',subject:'subject'})
    console.log(x)
  }
}
</script>
<style scoped>
  button{
    margin-left: 5px;
  }
</style>
3. <strong>mapActions方法:</strong>用于帮助我们生成与```actions```对话的方法,即:包含```$store.dispatch(xxx)```的函数
Count.vue
 methods:{
       //靠mapActions生成:incrementOdd、incrementWait(对象形式)
       ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
   
       //靠mapActions生成:incrementOdd、incrementWait(数组形式)
       ...mapActions(['jiaOdd','jiaWait'])
   }
/*incrementOdd(){
      /!*if (this.sum % 2) {
        this.sum += this.n
      }*!/
      /!*if (this.$store.state.sum % 2){
        this.$store.dispatch('jia',this.n)
      }*!/
      this.$store.dispatch('jiaOdd',this.n)
    },
    incrementWait(){
      /!*setTimeout(()=>{
        // this.sum += this.n
        this.$store.dispatch('jia',this.n)
      },500)*!/
      this.$store.dispatch('jiaWait',this.n)
    },*/
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    ...mapActions({ incrementOdd:'jiaOdd',incrementWait:'jiaWait' }),
    // 数组写法:
    ...mapActions(['jiaOdd','jiaWait']),  
   ```
4. <strong>mapMutations方法:</strong>用于帮助我们生成与```mutations```对话的方法,即:包含```$store.commit(xxx)```的函数
Count.vue中   
methods:{
       //靠mapActions生成:increment、decrement(对象形式)
       ...mapMutations({increment:'JIA',decrement:'JIAN'}),
       
       //靠mapMutations生成:JIA、JIAN(对象形式)
       ...mapMutations(['JIA','JIAN']),
   }
   ```
  methods: {
    /*increment(){
      // this.sum += this.n
      // 使用vuex的$store.dispatch(key,value)
      // this.$store.dispatch('jia',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIA',this.n)
    },
    decrement(){
      // this.sum -= this.n
      // this.$store.dispatch('jian',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIAN',this.n)
    },*/
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),

    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
    // ...mapMutations(['JIA','JIAN']),
> 备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

完整代码

main.ts

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import vueResource from 'vue-resource'

//引入store
import store from './store'

//关闭Vue的生产提示
Vue.config.productionTip = false

//使用插件
Vue.use(vueResource)

//创建vm
new Vue({
  store, // 同名触发简写store:store,
  render: h => h(App),
  beforeCreate() {
    Vue.prototype.$bus = this
  }
}).$mount("#app")

App.vue

<template>
  <div>
    <Count/>
  </div>
</template>

<script>
import Count from '@/components/Count'

export default {
  name:'App',
  components:{Count},
  mounted () {
    console.log('App',this)
  }
}
</script>

<style lang="css">

</style>

Store/index.js

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions对象——用于响应组件中的动作
const actions = {
  // jia:function (){可简写
  /*jia(context,value){
    // console.log('actions中的jia被调用了!',context,value)
    context.commit('JIA',value)
  },
  jian(context,value){
    context.commit('JIAN',value)
  },*/ // 逻辑简写:功能commit直接mutations

  jiaOdd(context,value){
    console.log('actions中的jiaOdd被调用了!')
    // 使用上下文获取this.$store.state.sum判断
    // if (this.$store.state.sum % 2){
    if (context.state.sum % 2){
      context.commit('JIA',value)
    }
  },
  jiaWait(context,value){
    setTimeout(()=>{
      context.commit('JIA',value)
    },500)
  }
}
//准备mutations对象——用于操作数据(state)状态
const mutations = {// 检测数据变化
  JIA(state,value){
    // console.log('mutations中的JIA被调用了!',state,value)
    state.sum += value
  },
  JIAN (context, value) {
    state.sum -= value
  }
}
//准备stats对象——用于存储数据
const state = {
  sum: 0, //当前的和,初始数据
  School: '尚硅谷',
  subject: '前端',
}
// 准备getters:用于将state中的数据进行加工
const getters = {
  bigSum(state){
    return state.sum*10
  }
}

//创建Store({配置对象})
/*const store = new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  stats,
})
暴露导出接口
export default store*/
// 进行简写优化
// 创建并暴露store
export default new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  state,
  getters,
})

components/Count.vue

<template>
  <div>
    <!--优化:可用计算属性来省略$store.state.-->
    <!--<h1>当前求和为:{{ $store.state.sum }}</h1>-->
    <h1>当前求和为:{{ sum }}</h1>
    <h3>当前求和为10倍:{{ bigSum }}</h3>
    <h3>在{{ School }},做{{ subject }}</h3>
    <!--select单选或多选菜单-->
    <!--v-model.number前置转换数值型-->
    <select v-model.number="n" name="" id="">
      <!--v-model.number前置转换数值型::value无需绑定强制数值型-->
      <!--<option :value="1">1</option>-->
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
// 引入 {映射状态} from vuex 生成State代码
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
// import {mapGetters} from 'vuex'
// import { mapMutations } from 'vuex'

export default {
  name: 'Count',
  data(){
    return{
      n: 1, //用户选择的数字
    }
  },
  computed: {
    // 借助mapState生成计算属性,从state中读取数据。(对象写法):效果同上
    // ...mapState({})-ES6风格:逐一展开mapState中的key:value展示出来,等同
    ...mapState({sum:'sum',School:'School',subject:'subject'}),

    // 再优化:借助mapState生成计算属性,从state中读取数据。(数组写法):效果同上
    // ...mapState([value1,value2,value3]),只要kv对中的value,而且kv名要一致才可简写
    ...mapState(['sum','School','subject']),

    /* ******************************** */

    // 借助mapGetters生成计算属性,从getters中读取数据。(对象写法):效果同上
    // ...mapGetters({})-ES6风格:逐一展开mapGetters中的key:value展示出来,等同
    ...mapGetters({bigSum:'bigSum'}),

    //再优化:kv名要一致,(数组写法):效果同上
    ...mapGetters(['bigSum']),
  },
  methods: {
    /*increment(){
      // this.sum += this.n
      // 使用vuex的$store.dispatch(key,value)
      // this.$store.dispatch('jia',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIA',this.n)
    },
    decrement(){
      // this.sum -= this.n
      // this.$store.dispatch('jian',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIAN',this.n)
    },*/
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),

    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
    // ...mapMutations(['JIA','JIAN']),

    /* **************************** */

    /*incrementOdd(){
      /!*if (this.sum % 2) {
        this.sum += this.n
      }*!/
      /!*if (this.$store.state.sum % 2){
        this.$store.dispatch('jia',this.n)
      }*!/
      this.$store.dispatch('jiaOdd',this.n)
    },
    incrementWait(){
      /!*setTimeout(()=>{
        // this.sum += this.n
        this.$store.dispatch('jia',this.n)
      },500)*!/
      this.$store.dispatch('jiaWait',this.n)
    },*/
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    ...mapActions({ incrementOdd:'jiaOdd',incrementWait:'jiaWait' }),
    // 数组写法:
    ...mapActions(['jiaOdd','jiaWait']),

  },
  mounted () {
    // console.log('Count',this.$store) // 组件挂载直接输出this.$store
    // 使用映射mapState({key:value}):({sum():this.$store.state.sum})
    const x = mapState({sum:'sum',School:'School',subject:'subject'})
    console.log(x)
  }
}
</script>

<style scoped>
  button{
    margin-left: 5px;
  }
</style>

 

多组件共享数据

App.vue中新建Person组件

 

 

 Person.vue组件代码:

<template>
  <div>
    <h1>人员列表</h1>
    <h3 style="color:red">Count组件的求和为:{{ sum }}</h3>
    <input type="text" placeholder="请输入名字" v-model="name">
    <button @click="add">添加</button>
    <ul>
      <!--<li v-for="p in $store.state.personalbar" key="p.id">{{ p.name }}</li>-->
      <!--使用computed计算属性:优化$store.state.-->
      <li v-for="p in personList" :key="p.id">{{ p.name }}</li>
    </ul>
  </div>
</template>

<script>
import {nanoid} from 'nanoid'

export default {
  name: 'Person',
  data() {
    return {
      name:''
    }
  },
  computed:{
    personList(){
      return this.$store.state.personList
    },
    sum(){
      return this.$store.state.sum
    },
    // 简写可避免问题:
    // ...mapState(['personList']),
  },
  methods:{
    add(){
      // 将输入的名字包装成对象
      const personObj = {id:nanoid(),name:this.name}
      // console.log(personObj)
      this.$store.commit('ADD_PERSON',personObj)
      // 保持输入框为空
      this.name= ''
    }
  },
}
</script>

<style scoped>

</style>

Count.vue组件代码:

<template>
  <div>
    <!--优化:可用计算属性来省略$store.state.-->
    <!--<h1>当前求和为:{{ $store.state.sum }}</h1>-->
    <h1>当前求和为:{{ sum }}</h1>
    <h3>当前求和为10倍:{{ bigSum }}</h3>
    <h3>在{{ School }},做{{ subject }}</h3>
    <h3 style="color:red">Person组件的总人数是:{{ personList.length }}</h3>
    <!--select单选或多选菜单-->
    <!--v-model.number前置转换数值型-->
    <select v-model.number="n" name="" id="">
      <!--v-model.number前置转换数值型::value无需绑定强制数值型-->
      <!--<option :value="1">1</option>-->
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
// 引入 {映射状态} from vuex 生成State代码
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
// import {mapGetters} from 'vuex'
// import { mapMutations } from 'vuex'

export default {
  name: 'Count',
  data(){
    return{
      n: 1, //用户选择的数字
    }
  },
  computed: {

    // 借助mapState生成计算属性,从state中读取数据。(对象写法):效果同上
    // ...mapState({})-ES6风格:逐一展开mapState中的key:value展示出来,等同
    ...mapState({sum:'sum',School:'School',subject:'subject'}),

    // 再优化:借助mapState生成计算属性,从state中读取数据。(数组写法):效果同上
    // ...mapState([value1,value2,value3]),只要kv对中的value,而且kv名要一致才可简写
    ...mapState(['sum','School','subject',"personList"]),

    /* ******************************** */

    // 借助mapGetters生成计算属性,从getters中读取数据。(对象写法):效果同上
    // ...mapGetters({})-ES6风格:逐一展开mapGetters中的key:value展示出来,等同
    ...mapGetters({bigSum:'bigSum'}),

    //再优化:kv名要一致,(数组写法):效果同上
    ...mapGetters(['bigSum']),
  },
  methods: {
    /*increment(){
      // this.sum += this.n
      // 使用vuex的$store.dispatch(key,value)
      // this.$store.dispatch('jia',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIA',this.n)
    },
    decrement(){
      // this.sum -= this.n
      // this.$store.dispatch('jian',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIAN',this.n)
    },*/
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),

    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
    // ...mapMutations(['JIA','JIAN']),

    /* **************************** */

    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    ...mapActions({ incrementOdd:'jiaOdd',incrementWait:'jiaWait' }),
    // 数组写法:
    ...mapActions(['jiaOdd','jiaWait']),

  },
  mounted () {
    // console.log('Count',this.$store) // 组件挂载直接输出this.$store
    // 使用映射mapState({key:value}):({sum():this.$store.state.sum})
    const x = mapState({sum:'sum',School:'School',subject:'subject'})
    console.log(x)
  }
}
</script>

<style scoped>
  button{
    margin-left: 5px;
  }
</style>

index.js或Vuex.js核心store代码:

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions对象——用于响应组件中的动作
const actions = {
  // jia:function (){可简写
  /*jia(context,value){
    // console.log('actions中的jia被调用了!',context,value)
    context.commit('JIA',value)
  },
  jian(context,value){
    context.commit('JIAN',value)
  },*/ // 逻辑简写:功能commit直接mutations

  jiaOdd(context,value){
    console.log('actions中的jiaOdd被调用了!')
    // 使用上下文获取this.$store.state.sum判断
    // if (this.$store.state.sum % 2){
    if (context.state.sum % 2){
      context.commit('JIA',value)
    }
  },
  jiaWait(context,value){
    setTimeout(()=>{
      context.commit('JIA',value)
    },500)
  }
}
//准备mutations对象——用于操作数据(state)状态
const mutations = {// 检测数据变化
  JIA(state,value){
    // console.log('mutations中的JIA被调用了!',state,value)
    state.sum += value
  },
  JIAN (context, value) {
    state.sum -= value
  },
  // 添加人员:无需判断限制,可直接Mutations
  ADD_PERSON (state,value) {
    console.log('mutations中的ADD_PERSON被调用了!')
    // unshift(value):从数组最前面添加元素
    state.personList.unshift(value)
  },
}
//准备stats对象——用于存储数据
const state = {
  sum: 0, //当前的和,初始数据
  School: '尚硅谷',
  subject: '前端',
  personList: [
    {id:'001',name:'模拟'}
  ],
}

// 准备getters:用于将state中的数据进行加工
const getters = {
  bigSum(state){
    return state.sum*10
  }
}

//创建Store({配置对象})
/*const store = new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  stats,

})
暴露导出接口
export default store*/
// 进行简写优化
// 创建并暴露store
export default new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  state,
  getters,
})

7.模块化+命名空间:优化Vuex中store

 

 

 

 

 

 组件中

  

1. 目的:让代码更好维护,让多种数据分类更加明确。
2. 修改```store.js```或者index.js
 ```javascript
   const countAbout = {
     namespaced:true,//开启命名空间
     state:{x:1},
     mutations: { ... },
     actions: { ... },
     getters: {
       bigSum(state){
          return state.sum * 10
       }
     }
   }
   
   const personAbout = {
     namespaced:true,//开启命名空间
     state:{ ... },
     mutations: { ... },
     actions: { ... }
   }
   
   const store = new Vuex.Store({
     modules: {
       countAbout,
       personAbout
     }
   })
   ```
3. 开启命名空间后,组件中读取state数据:
   ```js
   //方式一:自己直接读取
   this.$store.state.personAbout.list
   //方式二:借助mapState读取:
   ...mapState('countAbout',['sum','school','subject']),
   ```
4. 开启命名空间后,组件中读取getters数据:
   ```js
   //方式一:自己直接读取
   this.$store.getters['personAbout/firstPersonName']
   //方式二:借助mapGetters读取:
   ...mapGetters('countAbout',['bigSum'])
   ```
5. 开启命名空间后,组件中调用dispatch
   ```js
   //方式一:自己直接dispatch
   this.$store.dispatch('personAbout/addPersonWang',person)
   //方式二:借助mapActions:
   ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
   ```
6. 开启命名空间后,组件中调用commit
   ```js
   //方式一:自己直接commit
   this.$store.commit('personAbout/ADD_PERSON',person)
   //方式二:借助mapMutations:
   ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
   ```

 

main.ts完整代码

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import vueResource from 'vue-resource'

//引入store
import store from './store'

//关闭Vue的生产提示
Vue.config.productionTip = false

//使用插件
Vue.use(vueResource)

//创建vm
new Vue({
  store, // 同名触发简写store:store,
  render: h => h(App),
  beforeCreate() {
    Vue.prototype.$bus = this
  }
}).$mount("#app")

App.vue完整代码

<template>
  <div>
    <Count/>
    <hr>
    <Person/>
  </div>
</template>

<script>
import Count from '@/components/Count'
import Person from '@/components/Person'

export default {
  name:'App',
  components:{Count,Person},
  mounted () {
    console.log('App',this)
  }
}
</script>

<style lang="css">

</style>

Count.vue完整代码:使用vue模块化:import {mapState,mapMutations,mapActions,mapGetters} from 'vuex'

<template>
  <div>
    <!--优化:可用计算属性来省略$store.state.-->
    <!--<h1>当前求和为:{{ $store.state.sum }}</h1>-->
    <h1>当前求和为:{{ sum }}</h1>
    <h3>当前求和为10倍:{{ bigSum }}</h3>
    <h3>在{{ School }},做{{ subject }}</h3>
    <h3 style="color:red">Person组件的总人数是:{{ personList.length }}</h3>
    <!--select单选或多选菜单-->
    <!--v-model.number前置转换数值型-->
    <select v-model.number="n" name="" id="">
      <!--v-model.number前置转换数值型::value无需绑定强制数值型-->
      <!--<option :value="1">1</option>-->
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
// 引入 {映射状态} from vuex 生成State代码
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
// import {mapGetters} from 'vuex'
// import { mapMutations } from 'vuex'

export default {
  name: 'Count',
  data(){
    return{
      n: 1, //用户选择的数字
    }
  },
  computed: {
    /*// 借助mapState生成计算属性,从state中读取数据。(对象写法):效果同上
    // ...mapState({})-ES6风格:逐一展开mapState中的key:value展示出来,等同
    ...mapState({sum:'sum',School:'School',subject:'subject'}),

    // 再优化:借助mapState生成计算属性,从state中读取数据。(数组写法):效果同上
    // ...mapState([value1,value2,value3]),只要kv对中的value,而且kv名要一致才可简写
    ...mapState(['sum','School','subject',"personList"]),*/

    //使用模块化Vuex: Store中的 a,b 模块
    // 模块化Vuex:命名空间必须开启,否则无法使用
    ...mapState('countAbout',['sum','School','subject']),
    ...mapState('personAbout',['personList']),

    /* ******************************** */

    // 借助mapGetters生成计算属性,从getters中读取数据。(对象写法):效果同上
    // ...mapGetters({})-ES6风格:逐一展开mapGetters中的key:value展示出来,等同
    // ...mapGetters({bigSum:'bigSum'}),
    // 模块化Vuex:
    ...mapGetters('countAbout',['bigSum']),

    /*//再优化:kv名要一致,(数组写法):效果同上
    ...mapGetters(['bigSum']),*/

  },
  methods: {
    /*increment(){
      // this.sum += this.n
      // 使用vuex的$store.dispatch(key,value)
      // this.$store.dispatch('jia',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIA',this.n)
    },
    decrement(){
      // this.sum -= this.n
      // this.$store.dispatch('jian',this.n) // 逻辑简写:功能commit直接mutations
      this.$store.commit('JIAN',this.n)
    },*/
    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    // ...mapMutations({increment:'JIA',decrement:'JIAN'}),
    // 使用模块化Vuex
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
    // ...mapMutations(['JIA','JIAN']),

    /* **************************** */

    // 借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法):效果同上
    // ...mapMutations({})-ES6风格:逐一展开mapMutations中的map:key展示出来,等同
    // ...mapActions({ incrementOdd:'jiaOdd',incrementWait:'jiaWait' }),
    // 使用Vuex模块化:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

    // 数组写法:
    // ...mapActions(['jiaOdd','jiaWait']),

  },
  mounted () {
    // console.log('Count',this.$store) // 组件挂载直接输出this.$store
    // 使用映射mapState({key:value}):({sum():this.$store.state.sum})
    // const x = mapState({sum:'sum',School:'School',subject:'subject'})
    console.log(this.$store)
  }
}
</script>

<style scoped>
  button{
    margin-left: 5px;
  }
</style>

Person.vue完整代码:使用Vuex模块化:未使用。。。mapState,mapActions,mapMutions,mapGetters

<template>
  <div>
    <h1>人员列表</h1>
    <h3 style="color:red">Count组件的求和为:{{ sum }}</h3>
    <h3>列表中第一个人的名字是:{{firstPersonName}}</h3>
    <input type="text" placeholder="请输入名字" v-model="name">
    <button @click="add">添加</button>
    <button @click="addWang">添加一个姓王的人</button>
    <button @click="addPersonServer">添加一个人,名字随机</button>

    <ul>
      <!--<li v-for="p in $store.state.personalbar" key="p.id">{{ p.name }}</li>-->
      <!--使用computed计算属性:优化$store.state.-->
      <li v-for="p in personList" :key="p.id">{{ p.name }}</li>

    </ul>
  </div>
</template>

<script>
import {nanoid} from 'nanoid'
export default {
  name: 'Person',
  data() {
    return {
      name:''
    }
  },
  computed:{
    personList(){
      return this.$store.state.personAbout.personList
    },
    sum(){
      // return this.$store.state.sum
      // 使用Vuex模块化
      return this.$store.state.countAbout.sum
    },
    // 简写可避免问题:
    // ...mapState(['personList']),

    firstPersonName(){
      return this.$store.getters['personAbout/firstPersonName']
    }
  },
  methods:{
    add(){
      // 将输入的名字包装成对象
      const personObj = {id:nanoid(),name:this.name}
      // console.log(personObj)
      // this.$store.commit('ADD_PERSON',personObj)
      // 模块化vuex: '分类名/ADD_PERSON‘ 提交写法
      this.$store.commit('personAbout/ADD_PERSON',personObj)
      // 保持输入框为空
      this.name= ''
    },
    addWang(){
      const personObj={id:nanoid(),name:this.name}
      this.$store.dispatch('personAbout/addPersonWang',personObj)
      this.name=''
    },
    addPersonServer(){
      this.$store.dispatch('personAbout/addPersonServer')
    }
  },
}
</script>

<style scoped>

</style>

Store中index.js完整代码

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'

// 引入Vuex模块
import countOptions from './count'
import personOptions from './person'

//应用Vuex插件
Vue.use(Vuex)

// 进行Vuex模块化:优化
// 求和功能相关的配置
// 模块优化:将代码迁移至count.js中
/*const countOptions = {
  namespaced: true, // 命名空间开启:可简写
  actions:{
    jiaOdd(context,value){
      console.log('actions中的jiaOdd被调用了!')
      // 使用上下文获取this.$store.state.sum判断
      // if (this.$store.state.sum % 2){
      if (context.state.sum % 2){
        context.commit('JIA',value)
      }
    },
    jiaWait(context,value){
      setTimeout(()=>{
        context.commit('JIA',value)
      },500)
    }
  },
  mutations:{
    JIA(state,value){
      // console.log('mutations中的JIA被调用了!',state,value)
      state.sum += value
    },
    JIAN (state, value) {
      state.sum -= value
    },
  },
  state:{
    sum: 0, //当前的和,初始数据
    School: '尚硅谷',
    subject: '前端',
  },
  getters:{
    bigSum(state){
      return state.sum*10
    }
  },
}*/

// 人员管理功能相关的配置
// 模块优化:将代码迁移至person.js中
/*const personOptions = {
  namespaced:true, // 命名空间开启:可简写
  actions:{},
  mutations:{
    // 添加人员:无需判断限制,可直接Mutations
    ADD_PERSON (state,value) {
      console.log('mutations中的ADD_PERSON被调用了!')
      // unshift(value):从数组最前面添加元素
      state.personList.unshift(value)
    },
  },
  state:{
    personList: [
      {id:'001',name:'模拟'}
    ],
  },
  getters:{
    bigSum(state){
      return state.sum*10
    }
  },
}*/

// 使用Vuex模块化:优化
//准备actions对象——用于响应组件中的动作
/*const actions = {
  // jia:function (){可简写
  /!*jia(context,value){
    // console.log('actions中的jia被调用了!',context,value)
    context.commit('JIA',value)
  },
  jian(context,value){
    context.commit('JIAN',value)
  },*!/ // 逻辑简写:功能commit直接mutations

  jiaOdd(context,value){
    console.log('actions中的jiaOdd被调用了!')
    // 使用上下文获取this.$store.state.sum判断
    // if (this.$store.state.sum % 2){
    if (context.state.sum % 2){
      context.commit('JIA',value)
    }
  },
  jiaWait(context,value){
    setTimeout(()=>{
      context.commit('JIA',value)
    },500)
  }
}
//准备mutations对象——用于操作数据(state)状态
const mutations = {// 检测数据变化
  JIA(state,value){
    // console.log('mutations中的JIA被调用了!',state,value)
    state.sum += value
  },
  JIAN (context, value) {
    state.sum -= value
  },
  // 添加人员:无需判断限制,可直接Mutations
  ADD_PERSON (state,value) {
    console.log('mutations中的ADD_PERSON被调用了!')
    // unshift(value):从数组最前面添加元素
    state.personList.unshift(value)
  },
}
//准备stats对象——用于存储数据
const state = {
  sum: 0, //当前的和,初始数据
  School: '尚硅谷',
  subject: '前端',
  personList: [
    {id:'001',name:'模拟'}
  ],
}

// 准备getters:用于将state中的数据进行加工
const getters = {
  bigSum(state){
    return state.sum*10
  }
}*/

//创建Store({配置对象})
/*const store = new Vuex.Store({
  actions:actions,// 同名时触发简写,如下
  mutations,
  stats,

})
暴露导出接口
export default store*/
// 进行简写优化
// 创建并暴露store
export default new Vuex.Store({
  /*actions:actions,// 同名时触发简写,如下
  mutations,
  state,
  getters,*/
  // Vuex模块化:命名空间必须开启
  modules:{
    countAbout:countOptions,
    personAbout:personOptions,
  }
})

Count.js完整代码

// 进行Vuex模块化:优化
// 求和功能相关的配置
// 优化:将代码index.js迁移至count.js中
export default {
  namespaced: true, // 命名空间开启:可简写
  actions:{
    jiaOdd(context,value){
      console.log('actions中的jiaOdd被调用了!')
      // 使用上下文获取this.$store.state.sum判断
      // if (this.$store.state.sum % 2){
      if (context.state.sum % 2){
        context.commit('JIA',value)
      }
    },
    jiaWait(context,value){
      setTimeout(()=>{
        context.commit('JIA',value)
      },500)
    }
  },
  mutations:{
    JIA(state,value){
      // console.log('mutations中的JIA被调用了!',state,value)
      state.sum += value
    },
    JIAN (state, value) {
      state.sum -= value
    },
  },
  state:{
    sum: 0, //当前的和,初始数据
    School: '尚硅谷',
    subject: '前端',
  },
  getters:{
    bigSum(state){
      return state.sum*10
    }
  },
}

Person.js完整代码

// 人员管理功能相关的配置
// 模块优化:将代码迁移至person.js中
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
  namespaced:true, // 命名空间开启:可简写
  actions:{
    addPersonWang(context,value){
      //判断名字value值中含’王‘
      if (value.name.indexOf('王')=== 0){
        context.commit('ADD_PERSON',value)
      }else{
        alert('添加的人必须姓王')
      }
    },
    // 从服务要一个名字:需要引入axios
    addPersonServer (context) {
      // 发起get请求:成功和失败的回调
      axios.get('https://api。uixsj.cn/hitokoto/get?type=social').then(
        Response => {
          context.commit('ADD_PERSON',{id:nanoid(),name:Response.data})
        },
        error => {
          alert(error.message)
        }
      )
    }
  },
  mutations:{
    // 添加人员:无需判断限制,可直接Mutations
    ADD_PERSON (state,value) {
      console.log('mutations中的ADD_PERSON被调用了!')
      // unshift(value):从数组最前面添加元素
      state.personList.unshift(value)
    },
  },
  state:{
    personList: [
      {id:'001',name:'模拟'}
    ],
  },
  getters:{
    bigSum(state){
      return state.sum*10
    }
  },
}

路由:Vue中重点(重点的重点)

 

vue-router的理解:vue 的一个插件库,专门用来实现 SPA 应用
对 SPA 应用的理解:
  1. 单页 Web 应用(single page web application,SPA)。
  2. 整个应用只有一个完整的页面。
  3. 点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
  4. 数据需要通过 ajax 请求获取。

 

1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。
2. 前端路由:key是路径,value是组件。
 

 key 为路径, value 可能是 function 或 component。前端路由component和后端路由function

路由分类
  1. 后端路由:
    1) 理解:value 是 function, 用于处理客户端提交的请求。
    2) 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据。
  2. 前端路由:
    1) 理解:value 是 component,用于展示页面内容。
    2) 工作过程:当浏览器的路径改变时, 对应的组件就会显示。

1.基本使用

1. 安装vue-router,命令:
npm i vue-router
2. 应用插件:
Vue.use(VueRouter)
3. 编写router配置项:
   ```js
   //引入VueRouter
   import VueRouter from 'vue-router'
   //引入Luyou 组件
   import About from '../components/About'
   import Home from '../components/Home'
   
   //创建router实例对象,去管理一组一组的路由规则
   const router = new VueRouter({
      routes:[
         {
            path:'/about',
            component:About
         },
         {
            path:'/home',
            component:Home
         }
      ]
   })
   
   //暴露router
   export default router
   ```

router/index.ts完整代码

import Vue from 'vue'   //注:这句必须要有,虽然在main.js里面已经引入过Vue,但是这里不要这句的话,就直接报错了Vue is not defined

// 该文件专门用于创建整个应用的路由器
import VueRouter, { RouteConfig } from 'vue-router'

// 引入组件
import About from '../components/About.vue'
import Home from '@/components/Home.vue'

Vue.use(VueRouter)

// 创建并暴露一个路由器
export default new VueRouter({
  routes:[//注:此处的方法名,记住这里是routes,不是routers,没有r,要是写成routers,控制台不会报错,就是渲染不出组件来,牢记啊!不然会让人崩溃的
    {
      path:'/about',
      component: About,//注:此处容易跟着代码提示一不小心写成components,要注意,控制台报错TypeError: Cannot read property '$createElement' of undefined
    }, // 一组路由KV对
    {
      path: '/Home',
      component: Home,
    }, // 一组路由KV对
  ], // 数组管理一堆route路由
})

/*默认路由数据
import HomeView from '../views/HomeView.vue'

Vue.use(VueRouter)

const routes: Array<RouteConfig> = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/!* webpackChunkName: "about" *!/ '../views/AboutView.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
*/
4. 实现切换(active-class可配置高亮样式)
   App.vue
   <router-link active-class="active" to="/about">About</router-link>
   ```
5. 指定展示位置
   App.vue
   <router-view></router-view>
   ```

2.几个注意点

1. 路由组件通常存放在```pages```文件夹一般组件通常存放在```components```文件夹,一般组件就是自己写组件标签 <Banner/>
 
2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
About.vue和Home.vue中
 
3. 每个组件都有自己的```$route```属性,里面存储着自己的路由信息。
4. 整个应用只有一个router,可以通过组件的```$router```属性获取到。

3.嵌套多级路由(多级路由)

解析静态模板:home-message.html和home.news

将静态模板拆分为Home.vue中的子组件message.vue和news.vue组件

   

1. 配置路由规则,使用children配置项:

 index.js中
   routes:[
      {
         path:'/about',
         component:About,
      },
      {
         path:'/home',
         component:Home,
         children:[ //通过children配置子级路由
            {
               path:'news', //此处一定不要写:/news
               component:News
            },
            {
               path:'message',//此处一定不要写:/message
               component:Message
            }
         ]
      }
   ]
   ```

2、路由器中router/index.ts,将拆分的message.vue和news.vue子组件写入一级路由Home

import Vue from 'vue'   //注:这句必须要有,虽然在main.js里面已经引入过Vue,但是这里不要这句的话,就直接报错了Vue is not defined

// 该文件专门用于创建整个应用的路由器
import VueRouter, { RouteConfig } from 'vue-router'

// 引入组件
import About from '@/pages/About.vue'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import Message from '../pages/Message.vue'

Vue.use(VueRouter)

// 创建并暴露一个路由器
export default new VueRouter({
  routes:[//注:此处的方法名,记住这里是routes,不是routers,没有r,要是写成routers,控制台不会报错,就是渲染不出组件来,牢记啊!不然会让人崩溃的
    {// 一级路由
      path:'/about',
      component: About,//注:此处容易跟着代码提示一不小心写成components,要注意,控制台报错TypeError: Cannot read property '$createElement' of undefined
    }, // 一组路由KV对
    {// 一级路由
      path: '/Home',
      component: Home,
      // 子路由使用数组:一级路由下二级路由
      children: [ // 子路由:可能N多个,所以使用数组
        {
          path: 'news', // 子路由:默认有‘/',遍历children时自动增加’/'
          component: News,
        },
        {
          path: 'message',
          component: Message,
        }
      ],
    }, // 一组路由KV对
  ], // 数组管理一堆route路由
})

/*源router内容
import HomeView from '../views/HomeView.vue'

Vue.use(VueRouter)

const routes: Array<RouteConfig> = [
  {
    path: '/',
    name: 'home',
    component: HomeView
  },
  {
    path: '/about',
    name: 'about',
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/!* webpackChunkName: "about" *!/ '../views/AboutView.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router
*/ 
2. 跳转(要写完整路径):
  Home.vue中
   <router-link to="/home/news">News</router-link>
   ```

Home.vue完整代码:

<template>
  <div>
    <h2>Home组件内容</h2>
    <div>
      <ul class="nav nav-tabs">
        <li>
          <!--<a class="list-group-item active" href="./home-news.html">News</a>-->
          <!--将a标签转变成router路由标签:router-link,to='完整跳转路径‘-->
          <!--注:子路由to='完整路径名’-->
          <router-link class="list-group-item" active-class="active"  to="/home/news">News</router-link>
        </li>
        <li>
          <!--<a class="list-group-item " href="./home-message.html">Message</a>-->
          <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
        </li>
      </ul>
      <!--编写路由匹配规则:router/index.vue-->
      <router-view></router-view>
    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  /*beforeDestroy () {// 周期钩子:销毁
    console.log('Home组件即将被销毁了')
  },
  mounted () {// 挂载完毕
    console.log("Home挂载完毕",this)
  },*/

}
</script>

<style scoped>

</style>

4.路由的query参数

1. 传递参数
Message.vue
   <!-- 跳转并携带query参数,to的字符串写法 -->
   <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
               
   <!-- 跳转并携带query参数,to的对象写法 -->
   <router-link
      :to="{
         path:'/home/message/detail',
         query:{
            id:666,
               title:'你好'
         }
      }"
   >跳转</router-link>
   ```

Message.vue完整代码:

<template>
  <div>
    <ul>
      <!--v-for 必须配 :key-->
      <li v-for="m in messageList" :key="m.id">
        <!--<a href="/message1">{{ m.title }}</a>&nbsp;&nbsp;-->
        <!--使用router路由:to='完整路由'-->
        <!--<router-link to="/home/message/detail">{{ m.title }}</router-link>&nbsp;&nbsp;-->
        <!--query传参:to='完整路由?id=666&title=你好啊!'-->
        <!--<router-link to="/home/message/detail?id=666&title=你好啊!">{{ m.title }}</router-link>&nbsp;&nbsp;-->
        <!--跳转路由并携带query参数,to的字符串写法,
        带有js参数${m.id}的:解析需要绑定:to="``",注意:``-->
        <!--<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!-- 跳转路由并携带query参数,to的对象写法(推荐) -->
        <router-link :to="{
          // 编写2个属性
          path: '/home/message/detail', // 路由路径
          query: { // query对象传参
            id: m.id,
            title: m.title,
          }
        }">
          {{ m.title }}
        </router-link>
      </li>
    </ul>
    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'Message',
  data(){
    return{
      messageList:[
        // 可以使用axios从服务器获取数据
        {id:'001',title:'消息001'},
        {id:'002',title:'消息002'},
        {id:'003',title:'消息003'},
      ],
    }
  }
}
</script>

<style scoped>

</style>
2. 三级路由组件Detail.vue中接收参数:
Detail.vue
   $route.query.id
   $route.query.title
   ```

完整代码:

<!--编写路由信息:router/index.ts-->
<!--编写路由信息时:二级路由Message下:三级路由Detail-->
<template>
  <ul>
    <li>消息编号:{{ $route.query.id }}</li>
    <li>消息标题:{{ $route.query.title }}</li>
  </ul>
</template>

<script>

export default {
  name: 'Detail',
  mounted(){
    // console.log(this.$route) // 获取当前路由信息
  }
}
</script>

<style scoped>

</style>

5.命名路由

1. 作用:可以简化路由的跳转。
2. 如何使用
   1. 给路由命名:

router/index.ts完整代码

import Vue from 'vue'   //注:这句必须要有,虽然在main.js里面已经引入过Vue,但是这里不要这句的话,就直接报错了Vue is not defined

// 该文件专门用于创建整个应用的路由器
import VueRouter, { RouteConfig } from 'vue-router'

// 引入组件
import About from '@/pages/About.vue'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import Message from '../pages/Message.vue'
import Detail from '@/pages/Detail.vue'

Vue.use(VueRouter)

// 创建并暴露一个路由器
export default new VueRouter({
  routes:[//注:此处的方法名,记住这里是routes,不是routers,没有r,要是写成routers,控制台不会报错,就是渲染不出组件来,牢记啊!不然会让人崩溃的
    {// 一级路由
      name: 'guanyu', // 命名路由:简化跳转编码
      path:'/about',
      component: About,//注:此处容易跟着代码提示一不小心写成components,要注意,控制台报错TypeError: Cannot read property '$createElement' of undefined
    }, // 一组路由KV对
    {// 一级路由
      path: '/Home',
      component: Home,
      // 子组件二级路由数组:一级路由下二级路由
      children: [ // 子路由:可能N多个,所以使用数组
        {
          path: 'news', // 子路由:默认有‘/',遍历children时自动增加’/'
          component: News,
        },
        {
          path: 'message',
          component: Message,
          children:[//三级路由:message子组件的子组件Detail
            {
              name:'xiangqing', // 命名路由:简化跳转编码
              path: 'detail',
              component: Detail,
            },
          ]
        }
      ],
    }, // 一组路由KV对
  ], // 数组管理一堆route路由
})

   2. 简化跳转:message.vue

  完整代码:

<template>
  <div>
    <ul>
      <!--v-for 必须配 :key-->
      <li v-for="m in messageList" :key="m.id">
        <!--<a href="/message1">{{ m.title }}</a>&nbsp;&nbsp;-->
        <!--使用router路由:to='完整路由'-->
        <!--<router-link to="/home/message/detail">{{ m.title }}</router-link>&nbsp;&nbsp;-->
        <!--query传参:to='完整路由?id=666&title=你好啊!'-->
        <!--<router-link to="/home/message/detail?id=666&title=你好啊!">{{ m.title }}</router-link>&nbsp;&nbsp;-->
        <!--跳转路由并携带query参数,to的字符串写法,
        带有js参数${m.id}的:解析需要绑定:to="``",注意:``-->
        <!--<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!-- 跳转路由并携带query参数,to的对象写法(推荐) -->
        <router-link :to="{
          // 编写2个属性
          // path: '/home/message/detail', // 路由路径
          name: 'xiangqing', // // 命名路由index.ts中:简化path:跳转编码效果同上
          query: { // query对象传参
            id: m.id,
            title: m.title,
          }
        }">
          {{ m.title }}
        </router-link>
      </li>
    </ul>
    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'Message',
  data(){
    return{
      messageList:[
        // 可以使用axios从服务器获取数据
        {id:'001',title:'消息001'},
        {id:'002',title:'消息002'},
        {id:'003',title:'消息003'},
      ],
    }
  }
}
</script>

<style scoped>

</style>

6.路由的params参数

1. 配置路由,声明接收params参数
router/index.ts中
 
import Vue from 'vue'   //注:这句必须要有,虽然在main.js里面已经引入过Vue,但是这里不要这句的话,就直接报错了Vue is not defined

// 该文件专门用于创建整个应用的路由器
import VueRouter, { RouteConfig } from 'vue-router'

// 引入组件
import About from '@/pages/About.vue'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import Message from '../pages/Message.vue'
import Detail from '@/pages/Detail.vue'

Vue.use(VueRouter)

// 创建并暴露一个路由器
export default new VueRouter({
  routes:[//注:此处的方法名,记住这里是routes,不是routers,没有r,要是写成routers,控制台不会报错,就是渲染不出组件来,牢记啊!不然会让人崩溃的
    {// 一级路由
      name: 'guanyu', // 命名路由:简化跳转编码
      path:'/about',
      component: About,//注:此处容易跟着代码提示一不小心写成components,要注意,控制台报错TypeError: Cannot read property '$createElement' of undefined
    }, // 一组路由KV对
    {// 一级路由
      path: '/Home',
      component: Home,
      // 子组件二级路由数组:一级路由下二级路由
      children: [ // 子路由:可能N多个,所以使用数组
        {
          path: 'news', // 子路由:默认有‘/',遍历children时自动增加’/'
          component: News,
        },
        {
          path: 'message',
          component: Message,
          children:[//三级路由:message子组件的子组件Detail
            {
              name:'xiangqing', // 命名路由:简化跳转编码
              // path: 'detail', // query传参
              path: 'detail/:id/:title', // params传参:id/:title占位符
              component: Detail,
            },
          ]
        }
      ],
    }, // 一组路由KV对
  ], // 数组管理一堆route路由
})
2. 传递参数
message.vue中

完整代码

<template>
  <div>
    <ul>
      <!--v-for 必须配 :key-->
      <li v-for="m in messageList" :key="m.id">
        <!--<a href="/message1">{{ m.title }}</a>&nbsp;&nbsp;-->
        <!--使用router路由:to='完整路由'-->
        <!--<router-link to="/home/message/detail">{{ m.title }}</router-link>&nbsp;&nbsp;-->
        <!--query传参:to='完整路由?id=666&title=你好啊!'-->
        <!--<router-link to="/home/message/detail?id=666&title=你好啊!">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!--跳转路由并携带query参数,to的字符串写法,
        带有js参数${m.id}的:解析需要绑定:to="``",注意:``-->
        <!--<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!-- 跳转路由并携带params参数,to的字符串写法 -->
        <!--<router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!-- 跳转路由并携带query参数或者params参数,to的对象写法(推荐) -->
        <router-link :to="{
          // 编写2个属性
          // path: '/home/message/detail', // 路由路径
          name: 'xiangqing', // 命名路由index.ts中:简化跳转编码效果同上
          // query: { // query对象传参
          params: { // params对象传参,注:必须使用name:命名路由不能使用path:
            id: m.id,
            title: m.title,
          }
        }">
          {{ m.title }}
        </router-link>
      </li>
    </ul>
    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'Message',
  data(){
    return{
      messageList:[
        // 可以使用axios从服务器获取数据
        {id:'001',title:'消息001'},
        {id:'002',title:'消息002'},
        {id:'003',title:'消息003'},
      ],
    }
  }
}
</script>

<style scoped>

</style>
   > 特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置

3. 接收参数:
Detail.vue中
   $route.params.id
   $route.params.title
   ```
<!--编写路由信息:router/index.ts-->
<!--编写路由信息时:二级路由Message下:三级路由Detail-->
<template>
  <ul>
    <!--query方式-->
    <!--<li>消息编号:{{ $route.query.id }}</li>-->
    <!--params方式-->
    <li>消息编号:{{ $route.params.id }}</li>
    <!--<li>消息标题:{{ $route.query.title }}</li>-->
    <li>消息标题:{{ $route.params.title }}</li>
  </ul>
</template>

<script>

export default {
  name: 'Detail',
  mounted(){
    // console.log(this.$route) // 获取当前路由信息
  }
}
</script>

<style scoped>

</style>

7.路由的props配置

​  作用:让路由组件更方便的收到参数

 

 index.vue中

 {
          path: 'message',// 子路由:可能N多个,所以使用数组
          component: Message,
          children:[//三级路由:message子组件的子组件Detail
            {
              name:'xiangqing', // 命名路由:简化跳转编码
              // path: 'detail', // query传参
              path: 'detail/:id/:title', // params传参:id/:title占位符
              component: Detail,

              //props的第一种写法,值为对象,对象中的所有key-value都会以props的形式传给当前Detail组件
              // props: {a:1,b:'hello'}, //数据写死,不常用

              //props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
              // props:true, //但是不接收query传参

              //props的第三种写法: 值为函数
              props($route){
                return{id:$route.params.id,title:$route.params.title} //靠返回值,注意传参是query还是params,Message.vue属性中
              }
            },
          ]
}

Message.vue中

 

 Detail.vue中

 

8.```<router-link>```的replace属性

1. 作用:控制路由跳转时操作浏览器历史记录的模式
2. 浏览器的历史记录有两种写入方式:分别为```push```和```replace```,```push```是追加历史记录,```replace```是替换当前记录。路由跳转时候默认为```push```

 

 

3. 如何开启```replace```模式:```<router-link replace .......>News</router-link>```

 9.编程式路由导航

1. 作用:不借助```<router-link> ```实现路由跳转,让路由跳转更加灵活
2. Message.vue具体编码:

组件路由:Message.vue 完整代码:

<template>
  <div>
    <ul>
      <!--v-for 必须配 :key-->
      <li v-for="m in messageList" :key="m.id">
        <!--<a href="/message1">{{ m.title }}</a>&nbsp;&nbsp;-->
        <!--使用router路由:to='完整路由'-->
        <!--<router-link to="/home/message/detail">{{ m.title }}</router-link>&nbsp;&nbsp;-->
        <!--query传参:to='完整路由?id=666&title=你好啊!'-->
        <!--<router-link to="/home/message/detail?id=666&title=你好啊!">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!--跳转路由并携带query参数,to的字符串写法,
        带有js参数${m.id}的:解析需要绑定:to="``",注意:``-->
        <!--<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!-- 跳转路由并携带params参数,to的字符串写法 -->
        <!--<router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{ m.title }}</router-link>&nbsp;&nbsp;-->

        <!-- 跳转路由并携带query参数或者params参数,to的对象写法(推荐) -->
        <router-link :to="{
          // 编写2个属性
          // path: '/home/message/detail', // 路由路径
          name: 'xiangqing', // 命名路由index.ts中:简化跳转编码效果同上
          // query: { // query对象传参,使用props第三种写法
          params: { // params对象传参,注:必须使用name:命名路由不能使用path:
            id: m.id,
            title: m.title,
          }
        }">
          {{ m.title }}
        </router-link>
        <!--编程式路由导航-->
        <!--pushShow(m):将v-for中的m当参数传入-->
        <button @click="pushShow(m)">push查看</button>
        <button @click="replaceShow(m)">replace查看</button>
      </li>
    </ul>
    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'Message',
  data(){
    return{
      messageList:[
        // 可以使用axios从服务器获取数据
        {id:'001',title:'消息001'},
        {id:'002',title:'消息002'},
        {id:'003',title:'消息003'},
      ],
    }
  },
  methods:{
    pushShow(m){// pushShow(m):将v-for中的m当参数传入
      // console.log('输出了路由器',this.$router)
      // 编程式路由导航
      this.$router.push({// 将router-link to中内容写入
        // 编写2个属性
        // path: '/home/message/detail', // 路由路径
        name: 'xiangqing', // 命名路由index.ts中:简化跳转编码效果同上
        // query: { // query对象传参,使用props第三种写法
        params: { // params对象传参,注:必须使用name:命名路由不能使用path:
          id: m.id,
          title: m.title,
        }
      })
    },
    replaceShow(m){
      // console.log('输出了路由器',this.$router)
      // 编程式路由导航
      this.$router.replace({// 将router-link to中内容写入
        name: 'xiangqing',
        params: { // params对象传参,注:必须使用name:命名路由不能使用path:
          id: m.id,
          title: m.title,
        }
      })
    }
  }
}
</script>

<style scoped>

</style>

一般路由:Banner.vue

<template>
  <div class="col-xs-offset-2 col-xs-8">
    <div class="page-header">
      <h2>Vue Router Demo</h2>
      <button @click="back">后退</button>
      <button @click="forward">前进</button>
      <button @click="test">测试一下go</button>
    </div>
  </div>
</template>

<script>

export default {
  name: 'Banner',
  methods:{
    back(){
      this.$router.back()
    },
    forward(){
      this.$router.forward()
    },
    test(){// 控制浏览器前进后退步数,3或者-3
      this.$router.go(2)
      // this.$router.go(-3)
    }
  }
}
</script>

<style scoped>

</style>

 

10.缓存路由组件

1. 作用:让不展示的News.vue路由组件保持挂载,不被销毁。数据进行缓存

2.Home.vue路由组件具体编码:注:news.vue属于Home.vue组件内容

 

Home.vue完整代码:   

<template>
  <div>
    <h2>Home组件内容</h2>
    <div>
      <ul class="nav nav-tabs">
        <li>
          <!--<a class="list-group-item active" href="./home-news.html">News</a>-->
          <!--将a标签转变成router路由标签:router-link,to='完整跳转路径‘-->
          <!--注:子路由to='完整路径名’-->
          <router-link class="list-group-item" active-class="active"  to="/home/news">News</router-link>
        </li>
        <li>
          <!--<a class="list-group-item " href="./home-message.html">Message</a>-->
          <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
        </li>
      </ul>
      <!--缓存路由组件:保持活跃标签:include包含组件=‘组件名’-->
      <keep-alive include="News">
        <!--编写路由匹配规则:router/index.vue-->
        <router-view></router-view>
      </keep-alive>

    </div>
  </div>
</template>

<script>
export default {
  name: 'Home',
  /*beforeDestroy () {// 周期钩子:销毁
    console.log('Home组件即将被销毁了')
  },
  mounted () {// 挂载完毕
    console.log("Home挂载完毕",this)
  },*/

}
</script>

<style scoped>

</style>

11.两个新的生命周期钩子

1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
2. 具体名字:
   1. ```activated```路由组件被激活时触发。
   2. ```deactivated```路由组件失活时触发。
News.vue完整代码
<template>
  <ul>
    <li :style="{opacity}">欢迎学习Vue</li>
    <li>news001 <input type="text"></li>
    <li>news002 <input type="text"></li>
    <li>news003 <input type="text"></li>
  </ul>
</template>

<script>
export default {
  name: 'News',
  data(){
    return {
      opacity: 1
    }
  },
  /*beforeDestroy () {// 生命钩子周期:配合组件缓存
    console.log('News组件即将被销毁了!')
    clearInterval(this.timer) // 销毁定时器
  },
  mounted () { // 定时器
    this.timer =setInterval(()=>{
      console.log('@')
      this.opacity -= 0.01
      if(this.opacity <= 0) this.opacity =1
    },16)
  } // 与beforDestroy钩子成对出现*/
  activated () { //生命钩子 激活:组件中使用更灵活
    this.timer =setInterval(()=>{
      console.log('@')
      this.opacity -= 0.01
      if(this.opacity <= 0) this.opacity =1
    },16)
  },
  deactivated () { //生命钩子 失活 与activated成对出现
    clearInterval(this.timer) // 销毁定时器
  }

}
</script>

<style scoped>

</style>

3、回顾还有个生命周期钩子:nextTick

 // nextTick:下一次 DOM 更新结束后执行
      // 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
      this.$nextTick(function (){
        this.$refs.inputTitle.focus()
      })

12.路由守卫(重点):开发常用

1. 作用:对路由进行权限控制
2. 分类:全局守卫、独享守卫、组件内守卫
3. 全局守卫:

router/index.ts完整代码

import Vue from 'vue'   //注:这句必须要有,虽然在main.js里面已经引入过Vue,但是这里不要这句的话,就直接报错了Vue is not defined

// 该文件专门用于创建整个应用的路由器
import VueRouter, { RouteConfig } from 'vue-router'

// 引入组件
import About from '@/pages/About.vue'
import Home from '@/pages/Home.vue'
import News from '@/pages/News.vue'
import Message from '../pages/Message.vue'
import Detail from '@/pages/Detail.vue'

Vue.use(VueRouter)

// 创建并暴露一个路由器
// export default new VueRouter({
// 使用路由守卫时:先创建
const router = new VueRouter({
  routes:[//注:此处的方法名,记住这里是routes,不是routers,没有r,要是写成routers,控制台不会报错,就是渲染不出组件来,牢记啊!不然会让人崩溃的
    {// 一级路由
      name: 'guanyu', // 命名路由:简化跳转编码
      path:'/about',
      component: About,//注:此处容易跟着代码提示一不小心写成components,要注意,控制台报错TypeError: Cannot read property '$createElement' of undefined
      meta:{// 路由元信息:程序员自定义
        // isAuth:true, // 是否条件访问授权:是
        title:'关于',
      },
    }, // 一组路由KV对
    {// 一级路由
      name: 'zhuye',
      path: '/Home',
      component: Home,
      meta:{// 路由元信息:程序员自定义
        // isAuth:true, // 是否条件访问授权:是
        title:'主页',
      },
      // 子组件二级路由数组:一级路由下二级路由
      children: [ // 子路由:可能N多个,所以使用数组
        {
          name:'xinwen',
          path: 'news', // 子路由:默认有‘/',遍历children时自动增加’/'
          component: News,
          meta:{// 路由元信息:程序员自定义
            isAuth:true, // 是否授权:是
            title:'新闻',
          },
        },
        {
          name:'xiaoxi',
          path: 'message',
          component: Message,
          meta:{// 路由元信息:程序员自定义
            isAuth:false, // 是否授权:是
            title: '消息',
          },
          // 可能N多个,所以使用数组
          children:[//三级路由:message子组件的子组件Detail
            {
              name:'xiangqing', // 命名路由:简化跳转编码
              // path: 'detail', // query传参
              path: 'detail/:id/:title', // params传参:id/:title占位符
              component: Detail,
              meta: {
                title: '详情',
              },

              //props的第一种写法,值为对象,对象中的所有key-value都会以props的形式传给当前Detail组件
              // props: {a:1,b:'hello'}, //数据写死,不常用

              //props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
              // props:true, //但是不接收query传参

              //props的第三种写法: 值为函数
              props($route){
                return{id:$route.params.id,title:$route.params.title} //靠返回值,注意传参是query还是params
              }
            },
          ]
        }
      ],
    }, // 一组路由KV对
  ], // 数组管理一堆route路由
})

// 使用路由器守卫时;beforeEach,before什么之前Each每一次(个)路由之前
// 全局 前置 路由守卫————初始化的时候被调用、每次路由切换之前被调用
// 参数:to去哪,form来自,next放行
router.beforeEach((to, from, next) =>{
  console.log('前置路由守卫',to,from)

  // document.title = to.meta.title || '通达系统' //此处有bug,受限访问的title不受限,解决放在放行之前

  // if (to.path === '/home/news' || to.path === '/home/message'){
  // if (to.name === 'xinwen' || to.name === 'atguigu'){
  // 路由元信息:鉴权
  if (to.meta.isAuth){// 判断是否需要鉴权
    /*
    表单提交验证
    localStorage.getItem(key):获取指定key本地存储的值;   //  获取指定key 本地存储数据的值。
    localStorage.setItem(key,value):将value存储到key字段; // 获取指定value 存储到key 字段
    localStorage.removeItem(key):删除指定key本地存储的值;  // 删除指定key 本地存储的值*/
    if (localStorage.getItem('school')==='atguigu'){
      /*//此处有bug,受限访问的title不受限,解决放在放行之前,但是写2遍繁琐,直接后置路由守卫优化
      document.title = to.meta.title || '通达系统' */
      next()
    }else {
      alert('学校名不对,无权查看!')
    }
  }else {
    /*//此处有bug,受限访问的title不受限,解决放在放行之前,但是写2遍繁琐,直接后置路由守卫优化
    document.title = to.meta.title || '通达系统' */
    next()
  }
})

// 全局 后置 路由守卫————初始化的时候被调用、每次路由切换之后被调用执行
router.afterEach((to, from)=>{
  console.log('后置路由守卫',to,from)
  document.title = to.meta.title || '通达系统' // 优化
})

// 暴露路由器
export default router

 

4. 独享守卫:

    {// 一级路由
      name: 'zhuye',
      path: '/Home',
      component: Home,
      meta:{// 路由元信息:程序员自定义
        // isAuth:true, // 是否条件访问授权:是
        title:'主页',
      },
      // 子组件二级路由数组:一级路由下二级路由
      children: [ // 子路由:可能N多个,所以使用数组
        {
          name:'xinwen',
          path: 'news', // 子路由:默认有‘/',遍历children时自动增加’/'
          component: News,
          meta:{// 路由元信息:程序员自定义
            isAuth:true, // 是否授权:是
            title:'新闻',
          },
          // 独享(局部)路由守卫:进入之前enter
          // 重点:独享(局部)路由守卫没有后置路由守卫!但是可以匹配全局后置路由守卫
          beforeEnter:(to, from, next)=>{
            // 逻辑与全局 前置 路由守卫一致
            console.log('独享局部路由守卫',to,from)

            // document.title = to.meta.title || '通达系统' //此处有bug,受限访问的title不受限,解决放在放行之前

            // if (to.path === '/home/news' || to.path === '/home/message'){
            // if (to.name === 'xinwen' || to.name === 'atguigu'){
            // 路由元信息:鉴权
            if (to.meta.isAuth){// 判断是否需要鉴权
              /*
              表单提交验证
              localStorage.getItem(key):获取指定key本地存储的值;   //  获取指定key 本地存储数据的值。
              localStorage.setItem(key,value):将value存储到key字段; // 获取指定value 存储到key 字段
              localStorage.removeItem(key):删除指定key本地存储的值;  // 删除指定key 本地存储的值*/
              if (localStorage.getItem('school')==='atguigu'){
                //此处有bug,受限访问的title不受限,解决放在放行之前,但是写2遍繁琐,直接后置路由守卫优化
                document.title = to.meta.title || '通达系统'
                  next()
              }else {
                alert('学校名不对,无权查看!')
              }
            }else {
              //此处有bug,受限访问的title不受限,解决放在放行之前,但是写2遍繁琐,直接后置路由守卫优化
              document.title = to.meta.title || '通达系统'
                next()
            }
          }
        },

5. 组件内守卫:

 

  

13.路由器的两种工作模式

1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
3. hash模式:router/index.ts

 mode: 'hash', 

   1. 地址中永远带着#号,不美观 。
   2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
   3. 兼容性较好。
4. history模式:router/index.ts
 
   1. 地址干净,美观 。
   2. 兼容性和hash模式相比略差。
   3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。
5.打包
  1、服务器运行 : npm run serve  启动
  2、打包生成: npm run build    项目打包

   3、npm run build    项目打包后出现dist文件夹

    4、部署:将dist内容上传服务器进行部署

 前端以结束

实操案列

控制台:编写Server端

#mkdir dome
#npm init
#npm i express
 
1、编写Server.js微信服务器
// 引入express
const express = require('express')
const history = require('connect-history-api-fallback');

const app = express()
app.use(history())
app.use(express.static(__dirname+'/static'))

// 路由
app.get('/person',(req,res)=>{
    res.send({ // 给客户端返回对象:名字和年龄
        name:'tom',
        age:18
    })
})

// 创建app实例对象
// listen端口监听(端口号,(错误)进行回调)=》{//函数体}
app.listen(5005,(err)=>{
    if(!err) console.log('服务器启动成功了!')
})

服务器运行命令: node server

 

2、创建Static静态资源文件夹: 将dist打包内的文件放入

 

 

 刷新报错:

解决方法: 可将前端mode:'history'变更‘hash’从新打包发布

npm run build

解决mode:'history'打包后出现404问题

1、后端工程师:进行路由匹配,方式一

2、方式二:安装服务器中间件connect-history-api-fallback

 

 引入中间件:

 

 

 3、方式三:使用nginx

 

  

第 7 章:Vue UI 组件库

7.1 移动端常用 UI 组件库

1. Vant https://youzan.github.io/vant
2. Cube UI https://didi.github.io/cube-ui
3. Mint UI http://mint-ui.github.io
4. Nutui https://nutui.jd.com/ 京东

7.2 PC 端常用 UI 组件库

1. Element UI https://element.eleme.cn    饿了么UI组件库
2. IView UI https://www.iviewui.com

7.3 安装element-ui

注意:安装时 -S :为生产依赖,-D:为开发依赖

1、安装 https://element.eleme.cn

npm i element-ui -S

 2、快速上手

 在 main.js 中写入以下内容:

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
// import vueResource from 'vue-resource'
//引入store模块化Vuex
// import store from './store'

// 引入路由
import VueRouter from 'vue-router'
// 引入路由器
import router from './router'  //import后面的router只能写成router,且首字母大写都不行,不然在下面new Vue里面注入的时候控制台会报错Cannot read property 'matched'

//完整引入
//引入ElementUI插件组件库
import ElementUI from 'element-ui';
//引入ElementUI全部样式
import 'element-ui/lib/theme-chalk/index.css';

//按需引入
// import { Button,Row,DatePicker } from 'element-ui';

//关闭Vue的生产提示
Vue.config.productionTip = false


// 注册使用插件
// Vue.use(vueResource)
// 注册应用路由
Vue.use(VueRouter)
// 注册应用插件组件库
Vue.use(ElementUI)

//创建vm
new Vue({
  // store, // 模块化配置项:同名触发简写store:store,
  render: h => h(App),
  router:router, // 固定写法:@/router,记得在这里注入引入的router
  /*beforeCreate() {
    Vue.prototype.$bus = this // 开启全局总线
  }*/
}).$mount("#app") 

3、组件

复制代码,

使用type、plain、round和circle属性来定义 Button 的样式。
<el-row><el-button>默认按钮</el-button><el-button type="primary">主要按钮</el-button><el-button type="success">成功按钮</el-button><el-button type="info">信息按钮</el-button><el-button type="warning">警告按钮</el-button><el-button type="danger">危险按钮</el-button>
 <el-button type="primary" icon="el-icon-edit"></el-button> //type、icon查询相关Attrbutes属性表</el-row>

查询样式最后面的Attributes属性表

参数说明类型可选值默认值
size 尺寸 string medium / small / mini
type 类型 string primary / success / warning / danger / info / text
plain 是否朴素按钮 boolean false
round 是否圆角按钮 boolean false
circle 是否圆形按钮 boolean false
loading 是否加载中状态 boolean false
disabled 是否禁用状态 boolean false
icon 图标类名 string
autofocus 是否默认聚焦 boolean false
native-type 原生 type 属性 string button / submit / reset button

 4、快速上手中:按需引入

借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。

首先,安装 babel-plugin-component:  -D:为开发依赖

npm install babel-plugin-component -D

然后,将 .babelrc 修改为:注意Vue-cli脚手架项目文件为babel.config.js

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

避免破坏源文件,只修改添加

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    ["es2015", { "modules": false }]
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:注意 不用加el- 组件名第一字母大写,

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
// import vueResource from 'vue-resource'
//引入store模块化Vuex
// import store from './store'

// 引入路由
import VueRouter from 'vue-router'
// 引入路由器
import router from './router'  //import后面的router只能写成router,且首字母大写都不行,不然在下面new Vue里面注入的时候控制台会报错Cannot read property 'matched'

//完整引入
//引入ElementUI组件库
// import ElementUI from 'element-ui';
//引入ElementUI全部样式
// import 'element-ui/lib/theme-chalk/index.css';

//按需引入
// import { Button,Row,DatePicker } from 'element-ui';
import {Button,Row,DatePicker} from 'element-ui'

//关闭Vue的生产提示
Vue.config.productionTip = false


// 注册使用插件
// Vue.use(vueResource)
// 注册应用路由
Vue.use(VueRouter)

// 注册完整应用组件库
// Vue.use(ElementUI)
// 组件id自定义
Vue.component('Button-name',Button);
Vue.component('julan-row',Row);
Vue.component('tongda-datepicker',DatePicker);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Row)
 * Vue.use(Button,Row,DatePicker)
 */


//创建vm
new Vue({
  // store, // 模块化配置项:同名触发简写store:store,
  render: h => h(App),
  router:router, // 固定写法:@/router,记得在这里注入引入的router
  /*beforeCreate() {
    Vue.prototype.$bus = this // 开启全局总线
  }*/
}).$mount("#app")

按需引入的问题

 not found XXX: 解决 npm i XXX

 

 解决:修改babel.config.js

  修改后

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset',
    ["@babel/preset-env", { "modules": false }]
  ],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}

 

 

Vue3快速上手

 

  


1.Vue3简介


- 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
- 耗时2年多、[2600+次提交](https://github.com/vuejs/vue-next/graphs/commit-activity)、[30+个RFC](https://github.com/vuejs/rfcs/tree/master/active-rfcs)、
[600+次PR](https://github.com/vuejs/vue-next/pulls?q=is%3Apr+is%3Amerged+-author%3Aapp%2Fdependabot-preview+)、[99位贡献者](https://github.com/vuejs/vue-next/graphs/contributors)
- github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0

## 2.Vue3带来了什么

### 1.性能的提升

- 打包大小减少41%

- 初次渲染快55%, 更新渲染快133%

- 内存减少54%

  ......

### 2.源码的升级

- 使用Proxy代替defineProperty实现响应式

- 重写虚拟DOM的实现和Tree-Shaking

  ......

### 3.拥抱TypeScript

- Vue3可以更好的支持TypeScript

### 4.新的特性

1. Composition API(组合API)

   - setup配置
   - ref与reactive
   - watch与watchEffect
   - provide与inject
   - ......
2. 新的内置组件
   - Fragment
   - Teleport
   - Suspense
3. 其他改变

   - 新的生命周期钩子
   - data 选项应始终被声明为一个函数
   - 移除keyCode支持作为 v-on 的修饰符
   - ......

# 一、创建Vue3.0工程

## 1.使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

```bash
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
```

## 2.使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

- 什么是vite?—— 新一代前端构建工具。
- 优势如下:
  - 开发环境中,无需打包操作,可快速的冷启动。
  - 轻量快速的热重载(HMR)。
  - 真正的按需编译,不再等待整个应用编译完成。
- 传统构建 与 vite构建对比图

<img src="https://cn.vitejs.dev/assets/bundler.37740380.png" style="width:500px;height:280px;float:left" />
<img src="https://cn.vitejs.dev/assets/esm.3070012d.png" style="width:480px;height:280px" />

```bash
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
```

# 二、常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

## 1.拉开序幕的setup

1. 理解:Vue3.0中一个新的配置项,值为一个函数。
2. setup是所有<strong style="color:#DD5145">Composition API(组合API)</strong><i style="color:gray;font-weight:bold">“ 表演的舞台 ”</i>
4. 组件中所用到的:数据、方法等等,均要配置在setup中。
5. setup函数的两种返回值:
   1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
   2. <span style="color:#aad">若返回一个渲染函数:则可以自定义渲染内容。(了解)</span>
6. 注意点:
   1. 尽量不要与Vue2.x配置混用
      - Vue2.x配置(data、methos、computed...)中<strong style="color:#DD5145">可以访问到</strong>setup中的属性、方法。
      - 但在setup中<strong style="color:#DD5145">不能访问到</strong>Vue2.x配置(data、methos、computed...)。
      - 如果有重名, setup优先。
   2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

##  2.ref函数

- 作用: 定义一个响应式的数据
- 语法: ```const xxx = ref(initValue)```
  - 创建一个包含响应式数据的<strong style="color:#DD5145">引用对象(reference对象,简称ref对象)</strong>
  - JS中操作数据: ```xxx.value```
  - 模板中读取数据: 不需要.value,直接:```<div>{{xxx}}</div>```
- 备注:
  - 接收的数据可以是:基本类型、也可以是对象类型。
  - 基本类型的数据:响应式依然是靠``Object.defineProperty()`````get``````set```完成的。
  - 对象类型的数据:内部 <i style="color:gray;font-weight:bold">“ 求助 ”</i> 了Vue3.0中的一个新函数—— ```reactive```函数。

## 3.reactive函数

- 作用: 定义一个<strong style="color:#DD5145">对象类型</strong>的响应式数据(基本类型不要用它,要用```ref```函数)
- 语法:```const 代理对象= reactive(源对象)```接收一个对象(或数组),返回一个<strong style="color:#DD5145">代理对象(Proxy的实例对象,简称proxy对象)</strong>
- reactive定义的响应式数据是“深层次的”。
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

## 4.Vue3.0中的响应式原理

### vue2.x的响应式

- 实现原理:
  - 对象类型:通过```Object.defineProperty()```对属性的读取、修改进行拦截(数据劫持)。
 
  - 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
 
    ```js
    Object.defineProperty(data, 'count', {
        get () {},
        set () {}
    })
    ```

- 存在问题:
  - 新增属性、删除属性, 界面不会更新。
  - 直接通过下标修改数组, 界面不会自动更新。

### Vue3.0的响应式

- 实现原理:
  - 通过Proxy(代理):  拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
  - 通过Reflect(反射):  对源对象的属性进行操作。
  - MDN文档中描述的Proxy与Reflect:
    - Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
   
    - Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
   
      ```js
      new Proxy(data, {
        // 拦截读取属性值
          get (target, prop) {
            return Reflect.get(target, prop)
          },
          // 拦截设置属性值或添加新属性
          set (target, prop, value) {
            return Reflect.set(target, prop, value)
          },
          // 拦截删除属性
          deleteProperty (target, prop) {
            return Reflect.deleteProperty(target, prop)
          }
      })
     
      proxy.name = 'tom'  
      ```

## 5.reactive对比ref

-  从定义数据角度对比:
   -  ref用来定义:<strong style="color:#DD5145">基本类型数据</strong>
   -  reactive用来定义:<strong style="color:#DD5145">对象(或数组)类型数据</strong>
   -  备注:ref也可以用来定义<strong style="color:#DD5145">对象(或数组)类型数据</strong>, 它内部会自动通过```reactive```转为<strong style="color:#DD5145">代理对象</strong>
-  从原理角度对比:
   -  ref通过``Object.defineProperty()`````get``````set```来实现响应式(数据劫持)。
   -  reactive通过使用<strong style="color:#DD5145">Proxy</strong>来实现响应式(数据劫持),
并通过<strong style="color:#DD5145">Reflect</strong>操作<strong style="color:orange">源对象</strong>内部的数据。
-  从使用角度对比:
   -  ref定义的数据:操作数据<strong style="color:#DD5145">需要</strong>```.value```,读取数据时模板中直接读取<strong style="color:#DD5145">不需要</strong>```.value```
   -  reactive定义的数据:操作数据与读取数据:<strong style="color:#DD5145">均不需要</strong>```.value```

## 6.setup的两个注意点

- setup执行的时机
  - 在beforeCreate之前执行一次,this是undefined。
 
- setup的参数
  - props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性。
  - context:上下文对象
    - attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 ```this.$attrs```
    - slots: 收到的插槽内容, 相当于 ```this.$slots```
    - emit: 分发自定义事件的函数, 相当于 ```this.$emit```


## 7.计算属性与监视

### 1.computed函数

- 与Vue2.x中computed配置功能一致

- 写法

  ```js
  import {computed} from 'vue'
 
  setup(){
      ...
    //计算属性——简写
      let fullName = computed(()=>{
          return person.firstName + '-' + person.lastName
      })
      //计算属性——完整
      let fullName = computed({
          get(){
              return person.firstName + '-' + person.lastName
          },
          set(value){
              const nameArr = value.split('-')
              person.firstName = nameArr[0]
              person.lastName = nameArr[1]
          }
      })
  }
  ```

### 2.watch函数

- 与Vue2.x中watch配置功能一致

- 两个小“坑”:

  - 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
  - 监视reactive定义的响应式数据中某个属性时:deep配置有效。
 
  ```js
  //情况一:监视ref定义的响应式数据
  watch(sum,(newValue,oldValue)=>{
    console.log('sum变化了',newValue,oldValue)
  },{immediate:true})
 
  //情况二:监视多个ref定义的响应式数据
  watch([sum,msg],(newValue,oldValue)=>{
    console.log('sum或msg变化了',newValue,oldValue)
  })
 
  /* 情况三:监视reactive定义的响应式数据
        若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
        若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
  */
  watch(person,(newValue,oldValue)=>{
    console.log('person变化了',newValue,oldValue)
  },{immediate:true,deep:false}) //此处的deep配置不再奏效
 
  //情况四:监视reactive定义的响应式数据中的某个属性
  watch(()=>person.job,(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
  },{immediate:true,deep:true})
 
  //情况五:监视reactive定义的响应式数据中的某些属性
  watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    console.log('person的job变化了',newValue,oldValue)
  },{immediate:true,deep:true})
 
  //特殊情况
  watch(()=>person.job,(newValue,oldValue)=>{
      console.log('person的job变化了',newValue,oldValue)
  },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
  ```

### 3.watchEffect函数

- watch的套路是:既要指明监视的属性,也要指明监视的回调。

- watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

- watchEffect有点像computed:

  - 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
  - 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

  ```js
  //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
  watchEffect(()=>{
      const x1 = sum.value
      const x2 = person.age
      console.log('watchEffect配置的回调执行了')
  })
  ```

## 8.生命周期

<div style="border:1px solid black;width:380px;float:left;margin-right:20px;"><strong>vue2.x的生命周期</strong>
<img src="https://cn.vuejs.org/images/lifecycle.png" alt="lifecycle_2" style="zoom:33%;width:1200px" />
</div><div style="border:1px solid black;width:510px;height:985px;float:left"><strong>vue3.0的生命周期</strong>
<img src="https://v3.cn.vuejs.org/images/lifecycle.svg" alt="lifecycle_2" style="zoom:33%;width:2500px" /></div>


1

- Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
  - ```beforeDestroy```改名为 ```beforeUnmount```
  - ```destroyed```改名为 ```unmounted```
- Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
  - `beforeCreate`===>`setup()`
  - `created`=======>`setup()`
  - `beforeMount` ===>`onBeforeMount`
  - `mounted`=======>`onMounted`
  - `beforeUpdate`===>`onBeforeUpdate`
  - `updated` =======>`onUpdated`
  - `beforeUnmount` ==>`onBeforeUnmount`
  - `unmounted` =====>`onUnmounted`

## 9.自定义hook函数

- 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

- 类似于vue2.x中的mixin。

- 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。



## 10.toRef

- 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。
- 语法:```const name = toRef(person,'name')```
- 应用:   要将响应式对象中的某个属性单独提供给外部使用时。


- 扩展:```toRefs``````toRef```功能一致,但可以批量创建多个 ref 对象,语法:```toRefs(person)```


# 三、其它 Composition API

## 1.shallowReactive 与 shallowRef

- shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
- shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。

- 什么时候使用?
  -  如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive。
  -  如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef。

## 2.readonly 与 shallowReadonly

- readonly: 让一个响应式数据变为只读的(深只读)。
- shallowReadonly:让一个响应式数据变为只读的(浅只读)。
- 应用场景: 不希望数据被修改时。

## 3.toRaw 与 markRaw

- toRaw:
  - 作用:将一个由```reactive```生成的<strong style="color:orange">响应式对象</strong>转为<strong style="color:orange">普通对象</strong>
  - 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
- markRaw:
  - 作用:标记一个对象,使其永远不会再成为响应式对象。
  - 应用场景:
    1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
    2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

## 4.customRef

- 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。

- 实现防抖效果:

  ```vue
  <template>
    <input type="text" v-model="keyword">
    <h3>{{keyword}}</h3>
  </template>
 
  <script>
    import {ref,customRef} from 'vue'
    export default {
      name:'Demo',
      setup(){
        // let keyword = ref('hello') //使用Vue准备好的内置ref
        //自定义一个myRef
        function myRef(value,delay){
          let timer
          //通过customRef去实现自定义
          return customRef((track,trigger)=>{
            return{
              get(){
                track() //告诉Vue这个value值是需要被“追踪”的
                return value
              },
              set(newValue){
                clearTimeout(timer)
                timer = setTimeout(()=>{
                  value = newValue
                  trigger() //告诉Vue去更新界面
                },delay)
              }
            }
          })
        }
        let keyword = myRef('hello',500) //使用程序员自定义的ref
        return {
          keyword
        }
      }
    }
  </script>
  ```

 

## 5.provide 与 inject

<img src="https://v3.cn.vuejs.org/images/components_provide.png" style="width:300px" />

- 作用:实现<strong style="color:#DD5145">祖与后代组件间</strong>通信

- 套路:父组件有一个 `provide` 选项来提供数据,后代组件有一个 `inject` 选项来开始使用这些数据

- 具体写法:

  1. 祖组件中:

     ```js
     setup(){
      ......
         let car = reactive({name:'奔驰',price:'40万'})
         provide('car',car)
         ......
     }
     ```

  2. 后代组件中:

     ```js
     setup(props,context){
      ......
         const car = inject('car')
         return {car}
      ......
     }
     ```

## 6.响应式数据的判断

- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由 `reactive` 创建的响应式代理
- isReadonly: 检查一个对象是否是由 `readonly` 创建的只读代理
- isProxy: 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理

# 四、Composition API 的优势

## 1.Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改 。

<div style="width:600px;height:370px;overflow:hidden;float:left">
    <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f84e4e2c02424d9a99862ade0a2e4114~tplv-k3u1fbpfcp-watermark.image" style="width:600px;float:left" />
</div>
<div style="width:300px;height:370px;overflow:hidden;float:left">
    <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e5ac7e20d1784887a826f6360768a368~tplv-k3u1fbpfcp-watermark.image" style="zoom:50%;width:560px;left" />
</div>















## 2.Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。

<div style="width:500px;height:340px;overflow:hidden;float:left">
    <img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc0be8211fc54b6c941c036791ba4efe~tplv-k3u1fbpfcp-watermark.image"style="height:360px"/>
</div>
<div style="width:430px;height:340px;overflow:hidden;float:left">
    <img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6cc55165c0e34069a75fe36f8712eb80~tplv-k3u1fbpfcp-watermark.image"style="height:360px"/>
</div>













# 五、新的组件

## 1.Fragment

- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用

## 2.Teleport

- 什么是Teleport?—— `Teleport` 是一种能够将我们的<strong style="color:#DD5145">组件html结构</strong>移动到指定位置的技术。

  ```vue
  <teleport to="移动位置">
    <div v-if="isShow" class="mask">
      <div class="dialog">
        <h3>我是一个弹窗</h3>
        <button @click="isShow = false">关闭弹窗</button>
      </div>
    </div>
  </teleport>
  ```

## 3.Suspense

- 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

- 使用步骤:

  - 异步引入组件

    ```js
    import {defineAsyncComponent} from 'vue'
    const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
    ```

  - 使用```Suspense```包裹组件,并配置好```default``````fallback```

    ```vue
    <template>
      <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
          <template v-slot:default>
            <Child/>
          </template>
          <template v-slot:fallback>
            <h3>加载中.....</h3>
          </template>
        </Suspense>
      </div>
    </template>
    ```

# 六、其他

## 1.全局API的转移

- Vue 2.x 有许多全局 API 和配置。
  - 例如:注册全局组件、注册全局指令等。

    ```js
    //注册全局组件
    Vue.component('MyButton', {
      data: () => ({
        count: 0
      }),
      template: '<button @click="count++">Clicked {{count}} times.</button>'
    })
   
    //注册全局指令
    Vue.directive('focus', {
      inserted: el => el.focus()
    }
    ```

- Vue3.0中对这些API做出了调整:

  - 将全局的API,即:```Vue.xxx```调整到应用实例(```app```)上

    | 2.x 全局 API(```Vue```) | 3.x 实例 API (`app`)                        |
    | ------------------------- | ------------------------------------------- |
    | Vue.config.xxxx           | app.config.xxxx                             |
    | Vue.config.productionTip  | <strong style="color:#DD5145">移除</strong> |
    | Vue.component             | app.component                               |
    | Vue.directive             | app.directive                               |
    | Vue.mixin                 | app.mixin                                   |
    | Vue.use                   | app.use                                     |
    | Vue.prototype             | app.config.globalProperties                 |
 

## 2.其他改变

- data选项应始终被声明为一个函数。

- 过度类名的更改:

  - Vue2.x写法

    ```css
    .v-enter,
    .v-leave-to {
      opacity: 0;
    }
    .v-leave,
    .v-enter-to {
      opacity: 1;
    }
    ```

  - Vue3.x写法

    ```css
    .v-enter-from,
    .v-leave-to {
      opacity: 0;
    }
   
    .v-leave-from,
    .v-enter-to {
      opacity: 1;
    }
    ```

- <strong style="color:#DD5145">移除</strong>keyCode作为 v-on 的修饰符,同时也不再支持```config.keyCodes```

- <strong style="color:#DD5145">移除</strong>```v-on.native```修饰符

  - 父组件中绑定事件

    ```vue
    <my-component
      v-on:close="handleComponentEvent"
      v-on:click="handleNativeClickEvent"
    />
    ```

  - 子组件中声明自定义事件

    ```vue
    <script>
      export default {
        emits: ['close']
      }
    </script>
    ```

- <strong style="color:#DD5145">移除</strong>过滤器(filter)

  > 过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

- ......

 

posted @ 2022-11-25 10:29  爵岚  阅读(142)  评论(0编辑  收藏  举报