跳至主要內容

Vue——王红元

LincZero大约 15 分钟

Vue——王红元

本课程又小码哥教育王红元提供

目录

Vuejs知识量化

一. 邂逅Vuejs

二. Vue基础语法

三. 组件化开发

四. Vue Cli详解

五. Vue-Router

六. Vuex详解(状态管理)

七. 网络封装

八. 项目实战

九. 项目部署

十. 原理相关

杂记

ES定义变量

ES6一般不用var,用let(变量)和const(常量)

MVVM

View <> ViewModel(Vue) <> Model

Vue的生命周期

debug/release

Vue源码分析

src > core(核心) > index.js(入口)

../instance > index.js > init.js

Vue实例下定义一些生命周期函数(钩子函数)来查看周期

img

img

img

代码规范

缩进 > 前端开发缩进2个空格更流行

CLI > .editconfig 2个空格

代码编辑器模板

数据结构

数组、栈、堆、链表、树结构

搜资料

img

其他问题

手机页面显示问题

img

URL

协议://服务器[:端口]/路径?查询(query)
scheme://host:port/path?query#fragment

基础语法

Vue——Html部分

<div id="app">
	<!-- 4种数据绑定----------------------------------------->
    <h1>{{ message }}</h1>								// 数据绑定  {{}}、v-html、v-text
    <input type="text" v-model="message"/>				// 双向绑定  v-model
    <button v-on:click="handleIncrement"></button>		// 函数绑定  v-on:方法、@方法
    <h1 v-bind:class="{done: item.done}"></h1>			// 属性绑定  v-bind:属性、:属性
    
    <!-- 2种流程语法------------------------------------------->
    <li v-if="show">mess</li>							// 显示隐藏  v-if、v-else-if、v-else、v-show
    <li v-for="item in items">{{item.mess}}</li>		// 循环指令  v-for; item in shuzu、item in obj取v
</div>

补充

数据绑定的补充,插值语法
    <h1>{{ message }}</h1>                   // Mustache语法,可运算
    <h1 v-html=''></h1>                      // 编译内容
    <h1 v-text=''></h1>                      // 不编译内容
    <h1 v-cloak></h1>                        // 解析再显  v-cloak 斗篷,解决js卡顿时用户能看到{{}}的问题
    <h1 v-once>{{ message }}</h1>            // 禁用改变  v-once 只改变一次(不让数据驱动视图)
    <h1 v-pre>{{ message }}</h1>             // 禁用编译  v-pre 文本显示

双向绑定的补充
    多种表单的的绑定
        绑定文本框                           // 返回内容
        绑定单选框                           // (多个绑定同一个变量)返回选中值
        绑定复选框单选                       // 返回布尔
        绑定复选框多选                       // (多个绑定同一个变量)返回数组
        绑定Select单选                      // 返回Value
    修饰符
        v-model.lazy                       // 懒绑定,让input事件更新数据 转换为 回车/失焦时更新数据
        v-model.number                     // 把内容自动转换为数字
        v-model.trim                       // 删除两侧空格

属性绑定的补充,对象语法和数组语法
    对象语 法,后接对象                      :class="{active: true, line: false}"  :style="{font-size: '50px'}"
    数组语法,后接数组                      :class=[actice, 'line']               :style="[{}, {}]"
    也可以接返回对象/数组的函数              :class="getClasses()"

方法绑定的补充,'()'的省略与修饰符
    括号省略
        方法无参时                          // 可省略'()'
        方法有参时                          // 不省略'()'时传递undefine,省略'()'时传递浏览器生成的event时间对象
        event值                            // 也可用'($event)'手动传递,这个值挺有用的:如:event.target.value
    修饰符
        .stop                              // 阻止事件冒泡(嵌套事件监听),Ex:@click.stop=''
        .prevent                           // 阻止默认行为(后接函数以外),Ex:@submit.prevent,@click.stop.prevent=''
        .(键值/别名)                        // 监听指定键盘键帽(键别名/键值)Ex:@keyup.enter='onEnter',@keydown.13='onEnter'
        .native                            // (在组件内才会用到)
        .once                              // 只触发一次回调,Ex:@click.once='doThis'

if补充
    v-if/v-else-if/v-else(整体删减))、v-show(display隐藏)
    Virtual Dom对Dom的复用问题

for补充    
    v-for; (item, index) in shuzu、(value, key, index) in obj
    Virtual Dom对Dom的复用问题。这里建议加key:item
        能更好复用。如数组内插入元素后,原来的dom不会修改

Vue——Script部分

<script src="./node_modules/vue/dist/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',                                  // 1. 挂载点
        data: {                                      // 2. 对象内容 —— 数据(或方法),组件当中则必须是方法
            message: 'Hello Vue.js'
            handleIncrement: function(){}
        },
        computed: {                                  // 3. 对象内容 —— 计算属性
            fullName: function () {}                        // 【与methods区别】这里用变量属性名调用方法,不加'()'
                                                            // 【与methods区别】重复调用不会多次计算,节约性能
        },
        methods: {                                   // 4. 对象内容 —— 方法
            handleIncrement2: function(){}                  // 【与data区别】data函数的this是Window,这里的this是Vue对象
            handleIncrement2(){}                            // ES6简写
        }
        生命周期函数: {}                              // 5. Vue生命周期函数,钩子函数,可查看Vue的生命周期/在指定流程中引用。详细看流程图
        filters: {                                   // 6. 过滤器方法
            show(price){return 1}                           // 使用时:xxx | show
        }
        components: {                                // 7. 局部组件定义(模块化)
            标签名: 模块名
        }
        props: {                                     // 8. 组件属性定义
            属性名: {
                type: 类型,
                default: 默认值,
                required: true
            }
        }
    })
</script>

补充

计算属性
    计算属性并非函数,实质是个对象,有setter和getter属性
    computed: {
        fullName: {
            set: function (newValue) {       // 计算属性一般没有set方法,只读属性。当给fullName赋值时启用get方法
                const names = newValue.split(' ')
                this.firstName = names[0]
                this.lastName = names[1]
            },
            get: function () {
                return this.firstName + ' ' +this.lastName
            }
        }
    }

其他补充

创建Vue时,Option能放什么

el、data、computed、methods、生命周期函数

2. 插值语法

mustache语法、v-html、v-text;v-once、v-pre、v-cloak:斗篷

3. v-bind

v-bind:src、:herf、对象语法、数组语法、函数语法

4. 计算属性


组件化

组件化使用

组件使用三个步骤:创建组件构造器、注册组件、使用组件

组件的注册分为:全局组件、局部组件

创建组件构造器【Vue.extend】

const cpnC1 = Vue.extend({
    template:`
        <div>
            <h2>组件标题<h2>
        </div>
    `
})

注册组件【Vue,component】,并定义组件标签的名称

全局组件注册(可以在多个Vue实例下使用)

Vue.component('cpn1', cpnC1)  // 全局组件

局部组件注册(只能在一个Vue实例下使用)

常用,且一般只有一个Vue实例

//const app = new Vue({
//    el:'',
    components: {
        cpn1: cpnC1
    }
//})

使用组件

<cpn1></cpn1>

父组件和子组件

嵌套

const cpnC2 = Vue.extend({
    template:`
        <h1>组件标题<h1>
        <cpn1></cpn1>        // 内部可用子组件
    `,
    components: {            // 在父组件cpnC2里注册子组件cpnC1
        cpn1: cpnC1
    }
})

Vue实例也是组件 & el和template共存

Vue实例也可叫 root 组件

事实上 Vue.extend( {}) 和 new Vue({}),即组件构造器和Vue实例之间很像

区别在于前者用"template"进行注册组件,而后者用"el"进行挂载,且后者也可以写"template"的

当后el和template同时存在,会自动把template内容自动替换到el中

好处是不再需要修改index.html的代码

组件化使用的语法糖

全局注册组件

Vue.component('cpn1', {
    template:`
        <div>
            <h2>组件标题<h2>
        </div>
    `
})

局部注册组件

components: {
    cpn1: {
        template:`
            <div>
                <h2>组件标题<h2>
            </div>
        `
    }
}

简单来说就是不用你写Vue.extend()构造器了,把组件构造器名直接替换成内容

组件模板抽离&组件数据分离

方法1

<script type="text/x-template" id="cpn">  // 注意这里的type,id给的js
    <div>这是一个模板</div>
</script>
// ......
template: '#cpn'  // 这里就很类似于el:'#app'的写法了

方法2 √

<template id="cpn">
    <div>这是一个模板</div>
</template>
// ......
template: '#cpn'  // 这里就很类似于el:'#app'的写法了

数据抽离

{{title}}
// ......
template: '#cpn',
data() {                 // 这里的data变成了方法,且需要返回值。匿名函数封包
    reutrn {             // 为什么要这么设计:避免这些组件共享data!!!
        title: 'abc'     // 每次回会返回一个新的对象
    }
methods: {}
}
// ——————————————————————
// 如果想相互影响:
const obj = {count:0}
    data() {return: obj}

父子组件的通信 (Parent-Child)

父组件传递数据给子组件:props(properties),自定义属性

注意:

v-bind中不支持驼峰命名!props绑定的属性名若是驼峰命名

如cInfo,那么在绑定时应该写为:c-info!

脚手架可以写驼峰

<div id="app">
    <cpn :cmessage="message"></cpn>  // 信息传递
</div>
<template id="cpn">
    <div>
        {{message}}
    </div>
</template>
// ......
const cpn = {
    template: '#cpn'
    props: ['cmessage'],  // 定义组件属性-方式1
    props: {              // 定义组件属性-方式2,可指定类型
        cmessage: String,
    }
    props: {              // 定义组件属性-方式3,可指定默认值
        cmessage: {
            type: String,
            default: 'aaa'      // 注意!当类型是对象或数组时,默认值必须是函数 
            required: true
        }
    }
    props: {              // 定义组件属性-方式4,自定义验证函数
        validator: function (value) {
            return ['a','b','c'].indexOf(value) !== -1
        }
    }
    // 属性:Number/Boolean/Array/Object/Data/Function/Symbol
    data() {
        return {
        }
    },
    methods: {}
}
const app = new({
    el: '#app',
    data: {
        message: 'hi',
    }
    componts: {
        cpn
    }
})

子组件传递数据给子组件:$emit Event,自定义事件

注意:

v-on中不支持驼峰命名!$emit发送的时间名若是驼峰命名

如cClick,那么在绑定时应该写为:c-click!

脚手架可以写驼峰

<div id="app">
    <cpn v-on:itemclick="cpnClick"></cpn>  // 父组件监听自定义子组件的事件
</div>
<template id="cpn">
    <div>
        <button @click="btnClick(1)">{{message}}</button> 
    </div>
</template>
// ......
const cpn = {
    template: '#cpn',
    data() {
        return {
            message: 'Hi'
        }
    },
    methods: {
        btnClick(item) {                   // 目的:吧item传给父组件
            this.$emit('itemclick', item)  // 向父组件发送事件,第二参取代event参
        }
    }
}
const app = new({
    el: '#app',
    data: {
        message: 'hi',
    },
    componts: {
        cpn
    },
    methods: {
        cpnClick(item) {
            console.log('cpnClick', item)  // 父组件接收到子组件传来的信息
        }
    }
})

互通信案例

一般方法:

不直接v-model绑定props里的值! 单向绑定data并$emit自定义事件

data () {
    return {
        数据属性: this.props属性
    }
}

Watch方法 & v-model:

组件实例属性

<template>
    <input type="text" v-model="dnum">
</template>
// ......
<script>
    export default {
        template: {/**/},
        props:{
            num:Number                // 自定义组件属性num,从父组件拿数据
        },
        data() {
            return {
                dnum: this.num        // 把组件属性的值给组件数据dnum
            }
        },
        watch: {                      // 监听组件数据的改变
            dnum(newValue) {
                this.$emit('num_change', newValue); 
            }                         // 自定义组件事件,传递给父组件
        }
    }
</script>

父组件访问子组件:$children,$refs(reference引用)

$children,返回一个数组。还可以拿到所有的组件

$refs,返回一个对象。更常用,定位更准确

<div id="app">
    <cpn ref="aaa"></cpn>                            // ref标签
    <button @click="btnClick">父组件按钮</button>
</div>
<template id="cpn">
</template>
<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el:"app",
        data: {},
        method: {
            btnClick() {
                this.$children[0].showMessage()     // 父组件调用第一个子组件的方法
                console.log(this.$children[0].name) // 父组件调用第一个子组件的数据
                console.log(this.$refs.aaa.name)    // 父组件调用ref标签为aaa的组件
            }
        },
        components: {
            cpn: {
                template: '#cpn',
                data (){
                    return {
                        name: "子组件内的数据"
                    }
                },
                methods: {
                    showMessage(){
                        console.log('子组件内的方法')
                    }
                },
            }
        }
    })
</script>

子组件访问父组件:$parent,$root

**【不建议使用】**子组件访问父组件的话复用性很差!

<div id="app">
    <cpn></cpn>
</div>
<template id="cpn">
    <button @click="btnClick">子组件按钮</button>
</template>
<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el:"app",
        data: {},
        components: {
            cpn: {
                template: '#cpn',
                methods: {
                    btnClick() {
                        console.log(this.$parent);// 访问父组件
                        console.log(this.$root);  // 访问根组件
                    }
                },
            }
        }
    })
</script>

插槽slot

slot目的:让组件具有更强的扩展性

简单来说就是给组件一个预留的空间

插槽

注意1:若插槽内有多个内容,则内容全部替换掉原插槽

注意2:若组件有多个插槽,则替换内容替换全部插槽

<div id="app">
    <cpn>
        <button>插入到插槽里的按钮</button>
    </cpn>
</div>
<template id="cpn">
    <h2>子组件相同的部分</h2>
    <slot>  // 插槽
        <button>无内容时插槽里的按钮</button>
    </solt>
</template>
<script src="../js/vue .js"></script>
<script>
    const app = new Vue({
        el:"app", 
        data: {},
        components: {
            cpn: {
                template: '#cpn',
            }
        }
    })
</script>

具名插槽

<div id="app">
    <cpn>
        <span slot="center">新中间</span>
    </cpn>
</div>
<template id="cpn">
    <slot name='left'>左侧</solt>
    <slot name='center'>中间</solt>
    <slot name='right'>右侧</solt>
</template>
<script src="../js/vue .js"></script>
<script>
    const app = new Vue({
        el:"app", 
        data: {},
        components: {
            cpn: {
                template: '#cpn',
            }
        }
    })
</script>

编译作用域与作用域插槽(伪 · 父用子)

编译作用域:使用的变量会在你所在的组件里查找

作用域插槽:父组件替换插槽标签,但内容由子组件提供吗

<div id="app">
    <cpn>
        <span v-for="i in items">{{i}}</span>  // 拿不到子组件的
        <span v-for="i in items">              // 方案
            <template slot-scope="slot">       // 新版本可把换为div等标签
                <span v-for="i in slot.data1">{{i}}</span>
            </template>
        </span>
    </cpn>
</div>
<template id="cpn">
    <slot :data1="items">                     // 作用域插槽传递
        <li v-for="i in items">{{i}}</li>
    </solt>
</template>
<script src="../js/vue .js"></script>
<script>
    const app = new Vue({
        el:"app", 
        data: {},
        components: {
            cpn: {
                template: '#cpn',
                data() {
                    return {
                        item: ['1','2','3']
                    }
                }
            }
        }
    })
</script>

Demo

<div id="app">
    <cpn></cpn>
</div>
<template id="cpn">
</template>
<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el:"app",
        data: {},
        components: {
            cpn: {
                template: '#cpn',
                methods: {},
                props: {}
            }
        }
    })
</script>

模块化开发(webpack,一堆配置)

Webpack(依赖于node环境)

  • 描述

    • 是一个现代的JavaScript应用的【静态模块打包】工具
    • 静态模块打包工具:frunt/gulp/webpack,前两者对模块化支持不强
    • 可以用CommonJs写,然后打包成ES5给浏览器执行
  • 打包

    • scss/less->css, jsx/.vue->js
    • ES6->ES5, TypeSctipt->JavaScript
    • 图片压缩
  • 流程

    • 开发 --> webpack模块化打包成项目 --> 放到服务器 --> 客户端
  • 安装(node -v 大于8.9)

    • npm install -g webpack@3.6.0
    • 【注意】vue-cli2 依赖 webpack3.6.0

其他打包工具

  • grunt, gulp
    • 核心是Task,被称为前端自动化任务管理工具
    • 更加强调前端流程自动化
  • Webpack
    • 更强大,支持模块化管理
    • 更强调模块化开发管理

项目目录

src   // source code file
    main.js
    mathUtils.js
    index.html  // 最终放置......
dist  // distribution 发布
package.json
webpack.config.js
index.html  // 开发时放置......

终端打包

终端打包

webpack配置实现智能打包

webpack.config.js文件

const path = require('path')
module.exports = {
    entry: './src/main.js',     // 入口
    output: {                   // 出口
        path: path.resolve(__dirname, 'dist'),  // 要绝对路径。为便于移植,建议动态获取路径
        filename: 'bundle.js'
    },
}

Webpack配置loader

loader(CSS)

  • 作用
    • 给webpack扩展,使其能转换文件
  • 安装
    • 有很多不同版本的loader,建议直接去官网查
  • 使用
npm install --save-dev css-loader // 解析css文件后,用import加载并返回css代码
npm install --save-dev style-loader // 将模块的导出作为样式添加到DOM里

// ————————入口文件main.js
require('./css/normal.css')  // 依赖css文件

// ————————webpack.config.js
module.exports = {
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader'] // 这里有两个loader,且从右向左读
            }
        ]
    }
}
  • less样式格式的同理
    • 上官网查、需要:
    • use: ['style-loader', 'css-loader', 'less-loader'] // 从右往左用

loader(图片)

图片文件的处理

需要url-loader、file-loader

  • 当加载图片小于limit时:会把图片格式编译为base64字符串
    • base64编码的容量稍大了,但减少了请求次数
  • 当加载图片大于limit时:需要file-loader加载,file-loader不需要配置
    • 这种方式会把图片打包进dist里,并生成了一个32位Hash名
// ————————normal.css
body {
    background: url("../img/test.jpg")
}

// ————————webpack.config.js
rules: [
    {/**/},
    {/**/},
    {
        test: /\.(png|jpg|gif)$/,
        use: {
            loader: 'url-loader',
            options: {
                limit: 8192 // 文件大小分割线
            }
        }
    }
]

图片路径问题

开发阶段时,默认情况引用的是外部的.jpg而非/dist下的,要配置publicPath

发布阶段时,由于index.html在dist中,则不需要该配置

module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js',
        publicPath: 'dist/'  // 开发阶段解决路径问题,在url前加上这个
    },
    module: {
        rules: [
        ]
    }
}

图片名称问题

有时图片名有要求时,自动生成的32位图片Hash能防止重复,但可能很乱

rules: [
    {/**/},
    {/**/},
    {
        test: /\.(png|jpg|gif)$/,
        use: {
            loader: 'url-loader',
            options: {
                limit: 8192,
                name: 'img/[name].[hash:8].[ext]' // 自定义转换的图片名
            }
        }
    }
]

loader(ES5-ES6)

babel-loader

// ———————— 终端
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
或
npm install babel-loader babel-ceore babel-preset-env webpack

// ————————  webpack.config.js
rules:[
    {...},
    {...},
    {...},
    {
        test: /\.js$/,
        exclude: /(node_modules|bower_components)/, // 排除文件夹
        use: {
            loader: 'babel-loader',
            options: {
                presets: ['@babel/preset-env']
            }
        }
    }
]

loader(.Vue)

vue-loader、vue-template-compiler

// ———————— 终端
npm install vue-loader vue-template-compiler --save-dev

// ————————  webpack.config.js
rules:[
    {...},
    {...},
    {...},
    {...},
    {
        test: /\.vue$/,
        use: {
            use: ['vue-loader']
        }
    }
]

报错问题

报错:使用vue-loader的v14版本以后,需要一个插件

临时解决方案:换低版本

webpack配置Vue

三种Vue安装方式

  1. 下载到本地
  2. CDN引入
  3. npm安装(Vue就是模块)
    1. npm install vue --save // 运行时依赖

报错 runtime-only

原因:Vue的两个不同版本
    runtime-only     // 该版本不可以有任何的template(包括Vue实例)
    runtime-compiler // 可以有template

解决方案:配置,指定runtime-compiler
    module.exports = {
        entry: './src/main.js',
        output: {...},
        module: {rules: [...]},
        resolve: {                              // 一般用来解决路径问题
            extensions: ['.js', '.css', '.vue'],// (可选)这个配置可以引用时省略后缀名
            alias: {                            // 别名
                'vue$': 'vue/dist/vue.esm.js'   // 包括compiler
            }
        }
    }

Vue最终方案

目录结构

  • dist
  • node_modules
  • src
    • css
    • img
    • js
    • vue
      • app.js
      • App.vue
    • main.js
  • index.html
  • package.json
  • package-lock.json
  • webpack.config.js

代码

// ———————— ./index.html(不再需要修改)
<div id="app"></div>

// ———————— ./src/vue/App.vue(Vue组件文件)
<template>
    <div>
        <h2>{{message}}</h2>
        <button @click="btnClick">按钮</button>
    </div>
</template>
<script>
    export default {
        name: "App",
        data() {
            return {
                message: 'HelloWorld',
            }
        },
        methods: {
            btnClick() {}
        }
    }
</script>
<style scoped>
    .title {
        color: green;
    }
</style>

// ———————— ./src/vue/app.js(负责组件里面的所有东西)
export default {
}

// ———————— ./src/main.js
import Vue from 'vue'
import App from './vue/App.vue'

new Vue({
    el: '#app',
    template: '<App/>',
    componts: {
        App
    }
})

Plugin——webpack插件

框架和插件

插件plugin > 对现有的架构进行扩展

loader和plugin区别

loader,主要用于转换某些类型的模块,是转换器

plugin,对webpack本身的扩展,是一个扩展器

其他插件:webpack.BannerPlugin

plugins: [
    new webpack.BannerPlugin('最终版权归xxx所有') // 横幅插件,webpath自带
],

其他插件:html-webpack-plugin

更具html模板自动生成dist下的html代码,而且会帮引用bundle.js

// npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins: [ // 注意:这里的webpack版本要3.2.0不然报错
    new HtmlWebpackPlugin({
        template: 'index.html'
    })
],

其他插件:uglifyjs-webpack-plugin

丑化压缩插件,把空格和换行删掉

// npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')

plugins: [
    new UglifyjsWebpackPlugin()
],

其他插件:webpack-dev-server

提供一个可选的本地开发服务器,且可以实现浏览器自动刷新显示修改后的结果!而不用重新打包!

// npm install webpack-dev-server@2.9.3 --save-dev

plugins: [...],
devServer: {
    contentBase: './dist',
    inline: true
}

// ./node_modules/.bin/webpack-dev-server 或:
"scripts": {
    "dev": "webpack-dev-server --open" // --open可选,能自动打开浏览器
},

webpack配置文件的分离

配置分离

build文件夹,存放配置
    base.config.js,复制原来的webpack.config.js,但只放公共的东西
    prod.config.js,放产品的东西,如uglifyjs-webpack-plugin的配置
    dev.config.js,刚开发时的东西,如webpack-dev-serverd的配置

配置文件合并

// npm install webpack-merge --save-dev

// ———————— 如 prod.config.js,同理修改dev.config.js
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
const webpackMerge = require('webpack-merge')
const baseConfig = require('./base.config')
module.exports = webpackMerge(baseConfig, {
    plugins: [
        new uglifyjsWebpackPlugin()
    ]
})

// ———————— package.json // 改
"scripts": {
    "build": "webpack --config ./build/prod.config.js" 
    "dev": "webpack-dev-server --open --config ./build/dev.config.js"
}

// ———————— base.config.js // 改
path: path.resolve(__dirname, '../dist')