likes
comments
collection
share

Vue-CLI脚手架

作者站长头像
站长
· 阅读数 15

Vue--CLI是Vue官方提供的标准化开发工具,全名是command line interface,国内称作脚手架,它是基于webpack配置的。

脚手架结构分析

main.js文件,该文件是整个项目的入口文件。

// 引入Vue
import Vue from 'vue'
// 引入组件的管理者App父组件
import App from './App.vue'

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

// 创建Vue实例对象
new Vue({
  render: h => h(App),
}).$mount('#app')

webpack配置

Vue脚手架隐藏了所有webpack相关的配置,如果想查看具体配置,需要执行以下命令:

        vue inspect > output.js

vue.config.js 是一个可选的配置文件,开发者可以创建该文件用于自定义配置,比如配置语法检查关闭:

        module.exports = {
            // 关闭语法检查
            lintOnSave: false
        }

render函数

在脚手架生成的main.js文件中引用的是运行版本的vue文件即vue.runtime.esm.js,它没有模板解析器,所以无法使用template配置项编写模板,采用render()函数进行配置模板。

render()函数 它有一个参数createElement用于创建具体的模板内容,h1表示创建的元素,xxx表示元素的文本内容。

        render(createElement){
            return createElement('h1', 'xxx');
        }

具体使用过程中因为没有使用this,所以可以使用箭头函数简写,并且模板内容在App组件中已写好,所以直接传入组件参数App。

        import Vue from 'vue'
        import App from './App.vue'

        Vue.config.productionTip = false

        new Vue({
          render: h => h(App),
        }).$mount('#root')

组件之所以可以使用template模板,是因为有依赖(vue-template-compiler)支持.vue格式的文件的template模板解析。

mixins配置项

mixins 配置项 用于配置混入,混入是指多个组件共用的配置。

新建一个定义mixin.js文件,编写公用的配置并将其暴露。

        export const mixin = {
            methods: {
                showName(){
                    console.log(this.name);
                }
            }
        }

在组件中引入模块并使用mixins配置项注册模块,这叫局部混入。如果混入的数据与组件本身数据重复或者冲突,则优先以组件本身数据为准,如果是生命周期钩子,则触发全部。

        <template>
            <div>
                <h2>{{ name }}</h2>
                <h2>{{ age }}</h2>
                <button @click="showName">点我显示名称</button>
            </div>
        </template>

        <script>
            import  {mixin}  from '../mixin.js'
            export default {
                name: 'HighStudent',
                data(){
                    return{
                        name: '松风',
                        age: '18'
                    }
                },
                mixins: [mixin]
            }
        </script>

        <style>

        </style>

除了局部混入以外还可以采用全局混入,在main.js文件中引入混入并调用。

        import {mixin2} from '../mixin'

        Vue.mixin(mixin)

插件

Vue中的插件本质上是一个包含install(Vue, xxx)方法的对象,该方法的第一个参数是Vue,后面的参数是插件使用者传递的数据。

新建一个plugins.js文件用于定义插件,其中第一个参数代表Vue构造函数,还可以传入别的参数。所以可以实现很多功能,比如添加全局过滤器,全局指令,全局混入,添加实例方法等等。

        export default{
            install(Vue, x, y, z){
                console.log('@@@install调用了');
                Vue.mixin({
                    methods: {
                        showName(){
                            console.log(this.name);
                        }
                    },
                    mounted() {
                        console.log('混合')
                    },
                })
            }
        }

main.js文件中引入插件并使用。

        import plugins from './plugins';
        Vue.use(plugins, 1, 2, 3);

本地存储

浏览器通过Window.localStorage和Window.seessionStorage属性实现本地存储机制

相关API

localStorage.setItem('key', 'value') 接收一个键和值作为参数。

localStorage.getItem('key') 接收一个键名作为参数,返回键值。

localStorage.removeItem('key') 接收一个键名作为参数,删除该键名键值。

localStorage.clear() 清空所有数据

  • SeesionStorage存储的内容会随着浏览器窗口关闭而消失。
  • LocalStorage存储的内容,需要手动清除才会消失。
  • 如果value值不存在,会返回null。
  • JSON.parse(null)的结果依然null。
    <h2>localStorage</h2>
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>
    <button onclick="deleteData()">点我读取一个数据</button>
    <script>
        function saveData(){
            localStorage.setItem('msg', 'hello');
        }

        function readData(){
            console.log(localStorage.getItem('msg'));
        }
    </script>

父子组件访问

有时候我们需要父子组件互相直接访问,或者子组件访问跟组件。

父组件访问子组件

children属性 不常用。

ref标签属性 是Vue用于给元素或子组件注册引用信息(id的替代者)。

        <template>
            <div>
                <h1 v-text="msg" ref="title"></h1>
                <button @click="showDom">点我</button>
                <MySchool ref="school"></MySchool>
            </div>
        </template>

通过$refs属性获取对应的引用信息,应用在html标签上获取的就是真实DOM元素,应用在组件上获取的就是组件实例对象。

        export default {
            name: 'App',
            components: {MySchool, MyStudent},
            data(){
                return {
                    msg: '欢迎学习Vue'
                }
            },
            methods: {
                showDom(){
                    console.log(this.$refs.title); // <h1>欢迎学习Vue</h1>
                    console.log(this.$refs.school); // VueComponent {_uid: 8, _isVue: true,...}
                }
            },
        }

子组件访问父组件

$parent$root

父子组件通信

子组件不能够直接引用父组件或者Vue实例的数据,但是在开发中经常涉及到数据在上下级组件中传递的问题,父子组件通信的有两种,通过props向子组件传递数据,通过自定义事件向父组件发送数据。

父传子(props)

props配置项用于父子组件之间通信,子组件接收的数据是只读的并且和子组件本身的属性不能重名。 所以v-model绑定的数据不能是props传递的,因为props的数据不可修改。

但是如果传递的数据是对象类型的,修改对象内部的属性不会报错,但不推荐这样做。

父组件在模板标签中传递数据name和age,注意不要使用驼峰命名,多个单词采用短横-连接。

        <MyStudent :name="name" :age="age"></MyStudent>

然后子组件中使用props配置项接收数据name和age,接收的方式有两种。

  • 第一种仅接收数据
        props: ['name', 'age']
  • 第二种限制接收的数据类型
         props: {
             name: String,
             age: Number
         }

除了限制数据类型,还可以设置数据的必要性以及指定默认值,或者自定义验证函数。

  • type
  • required
  • default
        props: {
            name: {
                type: String, // name的类型是字符串
                required: true // name是必写的
            },
            age: {
                type: Number,
                default: 99 // age默认值是99
            },
            message: {
                type: Object,
                // 对象或数组默认值必须从一个工厂函数获取
                default(){
                    return {message: 'HelloWorld'}
                }
            },
            log: {
                // 自定义验证函数
                validator(value){
                    // 这里的value值就是log值
                    console.log(value);
                    // 传入的日志信息必须匹配下列字符串中的一个
                    return ['success', 'warning', 'danger'].indexOf(value) !== -1;
                }
           }
        }

props接收的数据优先级是高于子组件本身的属性的,所以如果需要修改接收的数据,可以在data中定义一个属性存储接收的数据。

        <script type='text/javascript' src='../js/vue.js'></script>
        <div id='root'>
            <cpn :num1="num1" :num2="num2"></cpn>
        </div>

        <template id="cpn">
            <div>
                <h2>{{number1}}</h2>
                <input type="text" v-model="number1">
                <h2>{{number2}}</h2>
                <input type="text" v-model="number2">
            </div>
        </template>

        <script>
            Vue.config.productionTip = false;

            const cpn = {
                template: '#cpn',
                props: {
                    num1: Number,
                    num2: Number
                },
                data(){
                    return {
                        // 定义两个属性用于存储props中的的属性
                        number1: this.num1,
                        number2: this.num2
                    }
                }
            }

            const vm = new Vue({
                el:'#root',
                data: {
                    num1: 1,
                    num2: 2
                },
                components: {
                    cpn
                }
            })
        </script>

子传父(自定义事件)

自定义事件是一种组件间通信的方式,适用于子组件传递数据给父组件,自定义事件流程主要是三步。

第一步在父组件中配置自定义事件逻辑。

      // 在父组件中配置自定义事件逻辑。
      methods: {
        getSchoolName(name){
          console.log('App组件收到了学校名', name);
        },
        getStudentName(name){
          console.log('App组件收到了学生名', name);
        }
      },

第二步在父组件中给子组件实例对象绑定自定义事件,有两种方式。

  • 在子组件实例对象中使用v-on或者@绑定自定义事件(注意区分自定义事件名函数名)。
        <template>
          <div id="app">
            <HighSchool v-on:getSchoolName="getSchoolName"></HighSchool>
            <hr>
            <HighStudent @getStudentName="getStudentName"></HighStudent>
          </div>
        </template>
  • 在子组件实例对象上标记ref属性,在父组件中对自定义事件进行挂载或者条件绑定,这个方式灵活性更强一些,可以延迟绑定或者满足某种条件时再绑定。
        <template>
          <div id="app">
            ......
            <HighSchool ref="school"></HighSchool>
          </div>
        </template>

        <script>
          ......
          mounted(){
            this.$refs.school.$on('getSchoolName', this.getSchoolName);
          }
        }
        </script>
  • 注意回调函数最好配置在methods中,如果直接传入函数则需要使用箭头函数,否则this指向会出现问题。若只想触发一次事件,可以使用once修饰符,或$once方法
        mounted() {
                this.$refs.MyStudent.$on('getStudentName', (name)=>{
                    console.log('App收到了学生姓名:', name)
                    this.studentName = name;
                });
                // this.$refs.student.$once('getStudentMsg', this.getStudentMsg);
        }

第三步子组件通过$emit()方法触发自定义事件。

        methods: {
            sendSchoolName(){
                this.$emit('getSchoolName', this.name)
            }
        }

this.$off(Event)方法 解绑所有自定义事件,Event参数表示需要解绑的事件名,多个事件需要以数组形式传入。

父组件中也可以给子组件绑定原生DOM事件,但是需要使用native修饰符,否则子组件会把事件当作自定义事件。

        <MyStudent ref="MyStudent" @click.native="show"></MyStudent>

全局事件总线(GlobalEventBus)

全局事件总线:一种组件通信方式,适用于任意组件间通信。它的原则是谁接收数据,谁绑定事件。谁传递数据,谁触发事件。

首先安装全局事件总线,$bus就是当前应用的vm。

        new Vue({
            ......
            beforeCreate() {
                // 安装全局事件总线
                Vue.prototype.$bus = this;
            },
            ......
        })

接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

        mounted() {
            this.$bus.$on('hello', (name) => {
                console.log('我是School组件,收到了数据',name);
            }

提供数据:B组件提供数据,则在B组件中调用自定义事件,本质上就是起到一个传参的作用。

        methods: {
            sendStudentName(){
                this.$bus.$emit('hello', this.name);
            }
        },

最好在beforeDestroy钩子中用$off解绑当前组件所用到的事件。

        beforeDestroy() {
            this.$bus.$off('hello');
        },

消息订阅与发布(pubsub)

一种组件间通信方式,适用于任意组件间通信。

安装对应的模块

        npm i pubsub-js

在需要发布和订阅消息的组件中引入pubsub

        import pubsub from 'pubsub-js'

接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身,每次订阅都会生成一个新的id。 回调中可以传递两个参数,第一个参数是事件名,第二个参数是数据。所以第一个参数可以用下划线_占位。

        mounted(){
                    this.pubId = pubsub.subscribe('hello', (msgName, data)=>{
                        console.log('有人发布了hello消息,hello消息的回调执行了!');
                        console.log(msgName, data);
                    })
                },

提供数据:B组件发布消息。

        methods: {
            sendStudentMsg(){
                pubsub.publish('hello', 666);
            }
        },

最好在beforeDestroy钩子中,用pubsub.unsubscribe(this.pubId)取消订阅。

插槽slot

组件的插槽是为了让我们封装的组件更加具有扩展性,就像我们的U盘硬盘一样。让使用者可以决定组件内部的一些内容到底展示什么。

默认插槽

在子组件中使用特殊的元素<slot>就可以为子组件开启一个默认插槽,该插槽插入什么内容取决于父组件如何使用,除此之外为了方便复用插槽还可以设置默认内容。

        <script type='text/javascript' src='../js/vue.js'></script>
        <div id='root'>
            <cpn></cpn>
            <cpn><span>HelloWorld</span></cpn>
        </div>
        <template id="cpn">
            <div>
                <h2>我是子组件</h2>
                // 插槽的默认内容是button按钮
                <slot><button>按钮</button></slot>
            </div>
        </template>

        <script>
            Vue.config.productionTip = false;
            const vm = new Vue({
                el:'#root',
                data: {

                },
                components: {
                    cpn: {
                        template: '#cpn'
                    }
                }
            })
        </script>

具名插槽

当子组件的功能复杂时,子组件的插槽可能不止一个,父组件插入内容时,需要区分不同的插槽。这时,我们需要给插槽起一个名字,称之为具名插槽。

        <script type='text/javascript' src='../js/vue.js'></script>
        <div id='root'>
            <cpn></cpn>
            // 让名为center的插槽内容替换如下
            <cpn><span slot="center">HelloWorld</span></cpn>
            <cpn></cpn>
        </div>
        <template id="cpn">
            <div>
                <h2>我是组件</h2>
                <slot name="left"><span>左边</span></slot>
                <slot name="center"><span>中间</span></slot>
                <slot name="right"><span>右边</span></slot>
            </div>
        </template>

        <script>
            Vue.config.productionTip = false;
            const vm = new Vue({
                el:'#root',
                data: {

                },
                components: {
                    cpn: {
                        template: '#cpn'
                    }
                }
            })
        </script>

作用域插槽

数据在组件自身,但是根据数据生成的结构需要由组件的使用者决定。(games数据在category组件中,但是使用数据所遍历出来的结构由vm组件决定)。

slot-scope可以接收到插槽传递的所有数据,接收的数据是键值对的对象形式, :

{ "games": [ "魔兽世界", "艾尔登法环", "只狼", "DOTA2" ], "message": "hello" }

所以可以使用解构赋值的方式直接获取。

        <script type='text/javascript' src='../js/vue.js'></script>
        <div id='root'>
            <!-- 生成的是默认列表 -->
            <category></category>
            <!-- 生成的是有序列表 -->
            <category>
                <template slot-scope='gamesdata'>
                    <ol>
                        <li v-for="item in gamesdata.games">{{item}}</li>
                    </ol>
                </template>
            </category>
            <!-- 使用解构赋值生成有序列表 -->
            <category>
                <template slot-scope='{games}'>
                    <ol>
                        <li v-for="item in games">{{item}}</li>
                    </ol>
                </template>
            </category>
        </div>
        
        <template id="category">
            <div>
                <h3>{{title}}分类</h3>
                <!-- 给slot插槽传递games数据 message数据-->
                <slot :games="games" message="hello">
                    <ul>
                        <li v-for="(item, index) in games" :key="index">{{item}}</li>
                    </ul>
                </slot>
            </div>
        </template>

        <script>
            Vue.config.productionTip = false;

            const category = {
                template: '#category',
                // 数据在子组件自身
                data(){
                    return {
                        title: '游戏',
                        games: ['魔兽世界', '艾尔登法环','只狼','DOTA2']
                    }
                },
            }

            const vm = new Vue({
                el:'#root',
                components: {
                    category
                }
            })
        </script>

nextTick

this.nextTick(callback)函数 在下一次DOM更新结束后执行其指定的回调,当改变数据后,要基于更新后的新DOM进行某些操作时,可以在nextTick所指定的回调函数中执行,比如input框的自动聚焦。

          ......
          // 处理待办事项的编辑
          handleEdit(id){
            //这里虽然完成了对虚拟DOM的操作,但是还没触发视图更新,如果直接进行文本框的聚焦,会导致聚焦失败。
            this.$bus.$emit('editTodo', id);
            // 所以通过nextTick()函数对更新后的DOM的进行聚焦操作。
            this.$nextTick(()=>{
              this.$refs.editInput.focus();
            })
          }
         ......

过渡和动画

在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。

vue中的动画本质上还是对css的操控,比如transition和animation属性的设置。

Vue-CLI脚手架

Vue中的ajax

解决跨域问题有两种配置代理服务器的方法,一种是简单配置:

  • 优点:配置简单,请求资源时直接发给8080即可。
  • 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  • 工作方式:若按照下述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)。
       devServer: {
         // 请求转发给谁
         proxy: 'http://localhost:6000'
      }

第二种是配置具体的代理规则:

  • 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  • 缺点:配置比较繁琐,请求资源时必须加前缀。
      devServer: {
        proxy: {
          '/api': { // 匹配所有以 'api'开头的请求路径
            target: 'http://localhost:6000', // 代理目标的基础路径
            pathRewrite: {'^/api': ''}, // 正则表达式将路径中的字符串api转换为空字符串
            ws: true, // 用于支持websocket
            changeOrigin: false, // 用于控制请求头中的host值
          },
          '/demo': { // 匹配所有以 'demo'开头的请求路径
            target: 'http://localhost:5000', // 代理目标的基础路径
            pathRewrite: {'^/demo': ''},
            // ws: true,
            changeOrigin: false,
          },
        }
      }
      /*
          changeOrigin为true时,服务器收到的请求头中的host为:localhost:5000
          changeOrigin为false时,服务器收到的请求头中的host为:localhost:8080
          changeOrigin默认值是true
          
      */

前台数据

      import axios from 'axios'
        export default {
          name: "App",
          methods: {
            getStudents(){
              axios.get('http://localhost:8080/api/students').then(
                response => {
                  console.log('请求成功了', response.data);
                },
                error => {
                  console.log('请求失败了', error.message);
                }
              )
            },
            getCars(){
              axios.get('http://localhost:8080/democars').then(
                response => {
                  console.log('请求成功了', response.data);
                },
                error => {
                  console.log('请求失败了', error.message);
                }
              )
            }
          },
        };

后台数据

    // 引入express模块
    const express = require('express');

    // 创建应用对象
    const app = express();

    app.use((request, response, next) => {
        console.log('有人发送请求给服务器了......');
        console.log('请求的资源是:', request.url);
        console.log('请求来自于', request.get('HOST'));
        next();
    })

    // 创建路由规则
    app.all('/cars', (request, response) => {
        // 设置响应头 设置允许跨域
        response.setHeader('Access-Control-Allow-Origin', '*');
        // 自定义响应头
        response.setHeader('Access-Control-Allow-Headers', '*');
        // 响应一个数据
        const data = {
            id: '001',
            name: '奔驰',
            price: 199
        }
        // 对对象进行字符串转换
        let str = JSON.stringify(data);
        // 设置响应体
        response.send(str);
    });

    // 监听端口启动服务
    app.listen(5000, () => {
        console.log("服务已启动,5000端口监听中......")
    })