伞仙博客 伞仙博客
首页
  • 前端文章

    • HTML-CSS
    • JavaScript
    • Vue
    • Node
  • Python数据分析
  • Git
  • 博客搭建
  • 其他
设计
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
GitHub

伞仙

我是伞仙
首页
  • 前端文章

    • HTML-CSS
    • JavaScript
    • Vue
    • Node
  • Python数据分析
  • Git
  • 博客搭建
  • 其他
设计
  • 友情链接
关于
  • 分类
  • 标签
  • 归档
GitHub
  • HTML-CSS文章

  • JavaScript文章

  • Vue文章

    • Vue学习记录
      • getter & setter 拦截器
      • v-on
      • v-show | v-if
      • v-for
      • v-model | v-bind
      • v-text | v-html
      • v-bind:class
      • v-bind:style
      • 事件修饰符
      • computed 计算属性
      • watch属性 监听器
      • 组件基础
      • 组件注意点
      • 组件的使用
      • prop传值问题
      • 局部注册
      • props
      • 自定义事件
      • 双向绑定
      • .sync
      • 依赖注入
      • 生命周期钩子(函数)
      • 常用API
      • instanceof
      • 过渡&动画
      • Tree 递归组件
      • $set
      • 自定义指令
      • v-on 实现原理
      • v-model 实现原理
      • 混入
      • 过滤器
      • 插槽
      • 动态组件
      • 回顾 npm install 命令
      • yarn
      • webpack 的使用
      • loader
      • postcss
      • 图片处理
      • babel
      • webpack 打包 Vue
      • webpack-dev-server
      • html-webpack-plugin
      • 安装 3.x 版本
      • 安装 2.x 版本
      • 检查是否安装完成
      • 创建项目 3.x
      • 创建的项目目录
      • vue插件
      • 安装
      • 标签
      • 动态路由匹配
      • 捕获所有路由或 404 Not found 路由
      • 嵌套路由
      • 编程式的导航
      • 给路由添加名称和原数据
      • 懒加载组件
      • $route 属性与方法
      • 导航守卫
    • Vue创建路由的封装
    • elementUI的table背景透明
  • Node文章

  • 前端
  • Vue文章
伞仙
2019-08-01

VUE学习记录

提示

去年学习vue的记录,合并在一起留个纪念,没用阅读价值

# 1. 入门篇

vue是什么: 前去了解

一套用于构建用户界面的渐进式框架

Vue分为几部分?

  • Vue 核心
  • Vue Router路由跳转
  • Vuex 全局数据状态管理

vue对比其他框架

  • react : jsx、纯函数式
  • angular : typescript、全面

什么是mvvm?

  • model 数据
  • view 视图模板
  • viewModel 连接数据和视图模板的桥梁

# getter & setter 拦截器

const app = {
    // 拦截访问title成员
    get title(){
        return _title;
    }
    // 拦截修改title成员赋值
    set title(value){
        _title = value;
    }
}
1
2
3
4
5
6
7
8
9
10

# v-on

<!-- 完整语法 -->
<a v-on:click="doSomething">...</a>

<!-- 缩写 -->
<a @click="doSomething">...</a>

<!-- 
内置的变量 $event 来传递事件对象
v-on:click="click(a, $event)" 
-->
1
2
3
4
5
6
7
8
9
10

# v-show | v-if

v-if 和 v-show 的区别 一个是使用display 一个是直接从dom移除元素

v-show 在展示大量数据时,不显示也会耗性能

<div id="app">
    <!--可以使用简单的条件表达式和boolean值-->
    <div v-show="isShow">我是内容</div>
    <button @click="isShow=!isShow">显示||隐藏</button>
</div>

<script>
    const vm = new Vue({
        el: '#app',
        data: {
            isShow: false
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

v-if | v-else-if | v-else

<div v-if="type === 'A'">
  A
</div>
<div v-else-if="type === 'B'">
  B
</div>
<div v-else-if="type === 'C'">
  C
</div>
<div v-else>
  Not A/B/C
</div>
1
2
3
4
5
6
7
8
9
10
11
12

v-if vs v-show v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。

v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。

相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。

一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

# v-for

<!--
循环对象
下标是对象的key

循环数组
下标是数组的index

循环次数
循环的对象就是数字
-->
<span v-for="(item, index) in student"> {{index}} {{item}}</span>
1
2
3
4
5
6
7
8
9
10
11

# v-model | v-bind

<!-- 
v-model 一般用于表单元素
双向绑定
我提供数据给他
他也可以提供数据给我
input output  = io

双向绑定 =  input output
单向绑定 = output 
-->
<textarea v-model="text"></textarea>

<!-- v-bind指令用于给html标签设置属性。 -->

class 属性绑定
<div v-bind:class="{active: isActive}">文字</div>

style 属性绑定
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">菜鸟教程</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# v-text | v-html

v-text 讲标签字符串按字符串显示
v-html 将标签字符串按标签去显示

<div v-text="text"></div>
<div v-html="html"></div>
<!-- 你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。 -->

1
2
3
4
5
6
7

表单修饰符

  • .lazy
    在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述输入法组合文字时)。你可以添加 lazy 修饰符,从而转变为使用 change 事件进行同步
  • .number
    如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符
  • .trim
    如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符

# 2. bind|事件修饰符|计算属性|监听器

# v-bind:class

1.可以绑定 数组 如果数组里是字符串 他会根据(,)来进行分割 可以支持数组加对象的组合 也可以再加上字符串组合 (使用对象的时候key作为class名称 value作为决定因素) 2.可以绑定 对象 // key: value (boolean) value的布尔用于决定这个样式名称(key)要不要添加到标签上 3.可以绑定 字符串

# html:

<style>
.active {
    background: red;
}
</style>

<div id="app">
    <input type="text" v-on:focus="onFoucs" v-on:blur="onBlur" v-bind:index="index">
    <a 
        href="#" 
        v-bind:class="[
            'abc',
            {
                active: actived,
                item: true
            },
            {
                aa: true,
                cc: true
            }
        ]">
        a标签
    </a>

</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# JavaScript:

const app = new Vue({
    el: "#app",
    data: {
        index: "xxx",
        actived: false,
        className: {
            key: false,
            name: true,
            "active-a": true
        }
    },
    methods: {
        onFoucs() {
            this.actived = true;
        },
        onBlur() {
            this.actived = false;
        },
        getCss() {
            return ["xx", "yy"]
        }
    },
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# v-bind:style

style用于弥补class的不足的

v-bind:style="string"
v-bind:style="array"
v-bind:style="{
    key css样式名称
    value css样式值 (值可以是变量)
    key: value
}"
1
2
3
4
5
6
7

# 事件修饰符

.capture 事件在捕获阶段就运行
.stop 阻止事件冒泡
.prevent 阻止事件的默认行为 例如右键触发的菜单屏蔽 例如  文本域回车换行
.self 事件元素必须是当前元素本身 跟事件冒泡没有关系
.once 事件只执行一次 就解绑
.passive 移动端事件优化的修饰符

上面这些修饰符以及后面要讲的修饰符 都可以用在同一个事件上可以使用多个
v-on:click.once.stop.prevent.self 像这样用点连接就可以

键盘的键盘码修饰符 可以使用键盘码 也可以使用按键别名(名称)
keydown.13 === keydown.enter

keydown.ctrl.shift.v (ctrl 和shift 都是系统修饰按键) 必须同时按下才可以
keydown.a.v.c 只要是a、v、c任意一个按键按下即可触发 (因为他们不是系统修饰按键)

v-on:click.ctrl="click('parent')" 按住ctrl+click 触发 (多按一个按键 系统是检测不了的 也会触发事件)
v-on:click.ctrl.exact 只能是按照ctrl + click 触发事件 只要是多按了任意以一个键 都不会触发事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# computed 计算属性

<body>
    <div id="app">
        <input v-model.number="num" />
        <div>
            {{getCount()}}
        </div>
        
        <div>
            {{getCount()}}
        </div>

        <div>
            {{getCount()}}
        </div>
    </div>


    <script>
        const app = new Vue({
            el: "#app",
            data: {
                 // 名称也是不能和methods computed里重复的
                num: 1
            },
            methods: {
                // 名称也是不能和data computed里重复的
                getCount() {
                    console.log("运行了多少次")
                    // 有两次调用就会运行两次 不能缓存结果 每次都是重新计算
                    // 每一次引用都会重新运行一次
                    return this.num + 1;
                }
            },
            computed: {
                // 计算属性里面的方法 可以直接当做变量来使用
                // 因为他是一个getter setter的语法糖
                // 计算属性里面的方法不能和 methods data里面的名称重复
                countNumber() {
                    console.log("运行了多少次111")
                    // 如果当前data选项里有数据发生改变 computed就会重新计算一次
                    // 只要变量不发生变化就不重新计算
                    // 不管引用多少 都不会触发新的计算
                    // 只要模板上没有引用 就算变量发生了改变 也不会计算 (惰性加载机制)
                    return this.num + 1;
                }
            },
        })
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# watch属性 监听器

<body>
    <div id="app">

        <input v-model="search" />

        {{obj.name}}

        <button v-on:click="change">修改</button>
    </div>


    <script>
        const app = new Vue({
            el: "#app",
            data: {
                search: "",
                obj: {
                    name: "张三"
                }
            },
            watch: {
                // 用于监听data里的变量是否发生了改变
                // 要想监听data选项里的哪一个成员发生改变 只需要编写一个(成员)同名函数即可
                search(newValue, oldValue) {
                    console.log("更新后的值:" ,newValue,"更新前的值:", oldValue)
                },
                // 对象是不监听成员的 只监听对象本身是否发生了改变(是否变成了一个新对象)
                // obj() {
                //     console.log("obj 已经发生了变化")
                // }

                obj: {
                    // 函数名称只能叫handler 监测处理器
                    handler() {
                        // console.log("默认情况下和直接obj() 方法一样不监听成员")
                        console.log("监测对象 以及对象的成员发生改变 必须要deep 为true 的情况下")
                    },
                    // 递归检测 成员是否发生了改变
                    // 如果要监听对象的成员是否改变 使用这个属性
                    deep: true
                }
            },
            methods: {
                change() {
                    // 对象如果被改成新的对象 watch能监听到
                    this.obj = {name: "李四"}


                    // 监听器默认是不监听对象的成员是否发生改变的
                    // this.obj.name ="李四"
                }
            },
        })
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 3. 组件基础 | props传参

# 组件基础

// 组件是一个对象 他和vue实例的结构类似 需要通过注册来使用

全局组件
Vue.component(name = string, options = {});
name    组件的名称 也就是组件的挂载元素
options 就是这个组件的配置信息
1
2
3
4
Vue.component("i-button", {
    // 在组件里 也有一个data的选项 只不过是一个函数返回一个对象
    data() {
        return {
            value: "这是一个按钮",
            title: "组件"
        }
    },
    // 组件的模板 只能在template里编写
    // 在模板里也可以使用变量 只能使用当前data选项里的变量
    template: `
    <div class="i-button" @click="click">
        {{value}} {{title}}
    </div>            
    `,
    // 组件也可以有自己的处理函数
    methods: {
        click() {
            alert("i-button click")
        }
    },
    watch: {
        
    },
    // 组件也可以有自己的计算函数
    computed: {
        
    },
})
Vue.component("i-test", {
    template: `
    <div>
        test组件
        <i-button></i-button>
    </div>
    `,
})

const app = new Vue({
    el: "#app",
    data() {
        return {
            title: "实例"
        }
    },
    methods: {

    },
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

# 组件注意点

全局组件 只需要用Vue.component 注册就可以

在实例没有模板(template)的情况下 在html里编写模板 必须使用烤串(命名)而且必须是双标签 (要不然会被html解析成小写的vue就不认识这个组件)

在实例有模板(template)的情况下 可以使用小驼峰或者大驼峰 或者 烤串 不受任何限制

# 组件命名方式

1. user-name 烤串命名法  适合写到实例的挂载元素(模板)里 由于html在加载解析的时候会把大写的html转换成小写的
2. userName  小驼峰命名法
3. UserName  大驼峰命名法 在使用时 可以使用烤串来编写标签 也可以使用小驼峰来编写标签
1
2
3
// 大驼峰的情况下 可以使用任意一种命名来使用 (在html里编写只能用烤串命名法)
Vue.component("UserName", {
    template: "<b>UserName</b>"
})


// 小驼峰的情况下 可以使用 小驼峰(不能在html里使用小驼峰 只能在template写)命名来使用组件 也可以使用 烤串来使用 (大驼峰使用不行)
Vue.component("userName", {
    template: "<b>UserName</b>"
})


// 烤串命名的情况下 只能使用烤串命名来使用组件
Vue.component("user-name", {
    template: "<b>xxx</b>"
}) 


// 在编写注册组件时 优先使用大驼峰来命名 (所有的方式都可以使用  除了在html里必须用烤串名来使用以外)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 组件的使用

# html:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="./js/Button.js"></script>
<script src="./js/InputGroup.js"></script>

<div id="app">
    <!-- 在html 里面使用组件时 如果接受参数的prop名称是 驼峰命名的 属性也需要烤串命名来使用 -->
</div>
1
2
3
4
5
6
7
8

# javascript:

const app = new Vue({
    el: "#app",
    template: `
    <div class="app">
        <Button color="default" v-on:click="click">111</Button>
        <Button color="primary">提交</Button>
        <Button color="success">发送</Button>


        <InputGroup
            before-text="账号"
            type="text"
            placeholder="请输入账号"
        />

        <InputGroup
            before-text="密码"
            type="password"
            placeholder="请输入密码"
        />
    </div>
    `,
    methods: {
        click() {
            // 默认组件是没有事件的 如果需要事件 必须我们给组件添加自定义事件
            alert("xxx");
        }
    },
})

// <slot/> 插槽 用于分发组件里嵌套的子元素

// function Button(color) {
//     return "<button class="+"btn " + "btn-" + color +"></button>"
// }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# Botton组件:

Vue.component("Button", {
    // 使用数组的方式来接收参数
    // 要接收参数就在数组里添加 组件上的属性名称即可
    props: ["color"],
    data() {
        return {
            title: "xx"
        }
    },
    template: `
    <button 
        v-bind:class="['btn', 'btn-' + color]" 
        type="submit"
    >
        <slot/>
    </button>
    `
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# InputGroup组件:

Vue.component("InputGroup", {
    props: ["before-Text", "after", "placeholder", "type"],
    template: `
    <div class="input-group">
        <span v-if="beforeText" class="input-group-addon" id="basic-addon1">{{beforeText}}</span>
        <input :type="type" class="form-control" :placeholder="placeholder" aria-describedby="basic-addon1">
        <span v-if="after" class="input-group-addon" id="basic-addon1">{{after}}</span>
    </div>
    `
})
1
2
3
4
5
6
7
8
9
10

# prop传值问题

* 1. 使用 烤串传值
* 
* prop 名称可以写成烤串 取值可以使用小驼峰
* prop 名称可以写成小驼峰  取值可以使用小驼峰
* prop 名称可以写成大驼峰 取值可以使用大驼峰
* 
* 
* 2. 使用 小驼峰传值
* 
* prop 名称不可以写成大驼峰
* prop 名称可以写成烤串 取值可以使用小驼峰
* prop 名称可以写成小驼峰  取值可以使用小驼峰
* 
* 
* 3. 使用 大驼峰传值
* prop 名称不可以写成小驼峰
* prop 名称不可以写成烤串
* prop 名称可以写成大驼峰 取值可以使用大驼峰

// 1. 在使用组件时 最好的传值方式是烤串
// 2. 在使用组件时 其次的方式是小驼峰
// 3. 在使用组件时 最差的方式是大驼峰


// 组件是先写好 再使用
// 使用组件者 最好是使用 烤串来传参 因为这样 不管组件的编写者使用的是任意方式来接收参数都可以接收到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 4.局部组件 | prop | 自定义事件 | 双向绑定

# 局部注册

所有组件都全局注册 就会造成命名不够使用

局部组件 只在使用的地方注册 局部组件就是一个组件的选项(未生成组件实例的组件对象)

// 局部组件 Card
const Card = {
     template: `
     <div class="card">
        <slot />
     </div>
     `,
    methods: {
         
     },
 }

//在谁的 模板(template里要使用局部组件) 需要在自己的 components 里注册
//实例也一样
const app = new Vue({
    el: "#app",
    template: `
    <div>
        <Card>xxx</Card>
    </div>
    `,
    // 注册局部组件
    components: {
        // key 是要注册组件的名称
        // value 组件的选项

        //Card: Card,
        //newName: Card,
        Card // es6中key与value相同时可缩写成一个
    }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# props

props: {
   // key 为要接收的prop名称
   // value 是js的类型
   // 如果不确定到底是什么类型 可以使用数组来约束
   a: [String, Number],

   // 如果不想验证这个参数的类型 但是又想把它当做prop来使用 可以使用null 跳过验证
   g: null,

   // 可以把prop 的类型改成对象
   color: {
       // type key作为类型验证
       type: String,
       // default 选项就可以作为类型验证时的默认值
       default: "default",
       // required 如果为true color这个属性就必须传递 否则报错 
       // required: true,
   },

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。

数据要拷贝一份,父级给的数据只能参考

props: {
        value: Object,
    },
    data() {
        return {
            iObject: JSON.parse(JSON.stringify(this.value))
        }
    },
1
2
3
4
5
6
7
8

子组件改变通知父级,父组件可以选择要不要改变

// 父组件
template: `<Test @model="change" />`,
methods:{
    change(value) {
        console.log(value)
        // 可以选择要不要改变
        // this.test = value
    }
}

// 子组件
// 通知父组件
this.$emit("model",this.iObject)
1
2
3
4
5
6
7
8
9
10
11
12
13

# prop验证

// 自定义验证
size: {
    validator(value) {
        // console.error("类型错误")
        // return false;
        // 如果验证通过返回true
        // 如果验证不通过返回false
        switch(value) {
            case "lg":
                return true;
            case "sm":
                return true;
            case "xs":
                return true;
            default:
                console.error("prop h must be 'lg' or 'sm' or 'xs'")
                return false;
        }
    }
},
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 禁用特性继承 inheritAttrs: false

组件 会默认把组件上所使用的 自定义的或原生的 属性过渡到 组件的模板跟元素上 如果你不想这样 使用 inheritAttrs: false 组件的根元素上不会继承 组件上所使用的属性 这个操作不影响组件捕获prop

# prop注意点

在编写组件时 一旦你把一个html属性 设置为prop name这个属性将不会被显性的显示出来 (被当做 prop 参数来使用) 当你不加时 它只是一个普通的html属性 从组件上被过渡到 组件的根元素上

class 属性 style 属性

上面这两个属性呢 不能当做prop来使用 它会直接显示到组件的根元素上

组件上的这两个属性会直接过渡到 组件模板中的根元素上

如果组件的根元素上有 class样式 组件上的class会叠加到 组件根元素上 如果组件的根元素上有 style 组件上的style会叠加到 组件根元素上

布尔类型的prop(属性) 如果不传递 有默认值 默认为 false 如果传递 不带值 只写prop 不写 value也不写等号 默认为true <Input bool /> // 如果bool的类型为Boolean 默认为true <Input bool />// 如果没有使用户bool 默认为false

# 自定义事件

如果在组件上添加 @绑定事件 这里会把@绑定事件的处理函数添加到组件内部的$listeners

在组件内部可以使用$emit() 才运行 $listeners的对应处理事件

# 自定义事件的流程

1.在组件上监听一个任意事件 2.由组件内部的原生元素 或者 组件触发的事件来触发 组件上监听的事件

# 原生事件

@click.native="fn" 原生事件 不管组件上有没有自定义事件 都可以触发原生元素事件 这个事件是从组件的根元素上捕获而来的

# 双向绑定

示例:

// 父组件
<div id="app">
    <Input v-model="value" />
</div>

// 子组件
const Input = {
    template:`
        <div class="input">
            <input type="text" v-model="iValue">
        </div>
    `,
    props:{
        value:String
    },
    data(){
        return {
            iValue:this.value
        }
    },
    watch: {
        iValue(newValue,oldValue){
            // 使用v-model  this.$listeners 中会有input
            this.$emit('input',newValue);
        }
    },
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

v-model 其实就是 v-bind:value="test" 和 @input="value => test = value" 的一个组合语法糖

# 5.sync | 生命周期钩子 | API

# .sync

:propName.sync="before"

sync 修饰符 用来绑定prop 除了可以给该prop参数以外 它还可以给他监听一个事件 事件名称为 (update:propName) 如果要和它同步组件内数据改变后 需要调用该事件并且传递参数

.sync 修饰符的 v-bind 不能和表达式一起使用

示例:

<Input :value.sync="value" />

this.$emit('update:value',newValue);
1
2
3

# 依赖注入

react比较需要,vue少见

// 提供者 提供服务
provide() {
    return {
        title: "xxx",
        run() {
            alert(1);
        }
    }
}
// 注入父组件里 provide (提供的服务)
inject: ["title", "run"],
1
2
3
4
5
6
7
8
9
10
11

# 生命周期钩子(函数)

# beforeCreate

// console.log(this.iValue);
// 组件创建之前 无法获取到data选项数据
// 要等到创建阶段才会初始化 才开始注入的数据 以及 创建响应式的拦截器
console.log("开始创建一个新的组件")
// 初始化数据

// 可以关闭掉组件上监听的事件
// this.$off("input")
1
2
3
4
5
6
7
8

# created

// 可以获取到响应式数据 
console.log(this.iValue);
// 在这里模板还没有开始渲染 生成 无法获取到真实dom元素
console.log(this.$el); // undefined 无法获取
console.log("新的组件创建好了")
// 可以用来ajax 请求数据
1
2
3
4
5
6

# beforeMount

// 这里的模板还没有被插入到dom中去 只是刚创建而已
console.log(this.$el); // undefined 无法获取
// 用于获取在dom创建之前的一些数据 获取dom元素的位置信息
console.log("新的组件要开始插入到dom啦")
1
2
3
4

# Mounted

// 已经挂载好了
console.log(this.$el); // 可以获取已经创建好的dom 元素了
console.log("新的组件已经插入到dom啦")
1
2
3

# beforeUpdate

// 必须是跟模板进行绑定的数据发生改变时 才会触发
// 当视图发生改变时 调用该回调
console.log("当前有数据正在发生改变")
1
2
3

# Updated

console.log("数据已经更新完成了")
1

# beforeDestroy

console.log("组件销毁前")
1

# Destroyed

console.log("组件销毁后")
1

# 常用API

// $el 获取组件的真实dom元素
this.$el

// $attrs 获取除了prop 以外的属性
this.$attrs

// $children 数组 获取当前组件里面使用的其他组件
this.$children

// $listeners 获取当前组件上所监听的自定义事件
this.$listeners

// $options 获取组件的选项 获取传递的参数
this.$options

// $parent 用来获取当前组件所使用的位置 在哪个组件里使用的
this.$parent //返回一个组件或者实例对象

// 可以修改父(实例)组件里的成员
// this.$parent.value = true

// $root 获取到实例的对象
this.$root


// $slots 获取组件开始标签和 结束标签里的内容
this.$slots

// $on(eventName, evenHandler) 组件内部监听事件
// 1. eventName 事件名称
// 2. evenHandler 事件处理函数
this.$on

this.$on("xx", () => {
    console.log("运行事件")
})
console.log(this.$listeners)
this.$emit("xx")

// 一次性事件
this.$once

// 触发的方式还是使用$emit来触发
// $emit 触发事件
this.$emit


// $off(eventName, evenHandler) 关闭事件
this.$off

var index = 0;
setInterval(() => {
    this.$emit("xx")
    index++;
    if(index > 5) {
        // 关闭事件
        this.$off("xx")
    }
}, 1000);

// $refs 获取被ref 标记的 元素 或 组件
// 如果是元素元素 会获取到原生元素对象
// 如果是组件获取到的是组件对象
// 如果被标记的元素使用v-for 获取到的是一个数组(标签或元素)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65

# instanceof

instanceof运算符用于测试构造函数的prototype属性是否出现在对象的原型链中的任何位置 语法 object instanceof constructor object: 要检测的对象 constructor: 某个构造函数

// 判断日期格式
formatTime(data) {
    if (typeof data === "string") {
        return data;
    } else if (data instanceof Date) {
        const yyyy = data.getFullYear();
        const M = data.getMonth() + 1;
        const d = data.getDate();
        return yyyy + "/" + M + "/" + d
    }
}

let data = new Date(newValue);
if (data.toDateString() === "Invalid Date") {
    this.beforeError = "日期格式有误"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 6.过渡&动画 | 递归组件 | $set

8-27 过渡&动画 递归组件 $set

# 过渡&动画

vue动画.jpg 过渡的类名 在进入/离开的过渡中,会有 6 个 class 切换。

  1. v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
  2. v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
  3. v-enter-to: 2.1.8版及以上 定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
  4. v-leave: 定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
  5. v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
  6. v-leave-to: 2.1.8版及以上 定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  • 条件渲染 (使用 v-if)
  • 条件展示 (使用 v-show)
  • 动态组件
  • 组件根节点

这里是一个典型的例子:

<!-- html -->
<div id="demo">
    <button @click="show = !show">
        Toggle
    </button>
    <transition name="fade">
        <p v-if="show">hello</p>
    </transition>
</div>
1
2
3
4
5
6
7
8
9
// js
new Vue({
    el: '#demo',
    data: {
        show: true
    }
})
1
2
3
4
5
6
7
/* css */
.fade-enter-active, .fade-leave-active{
    transition: opacity .5s;
}
.fade-enter, .fade-leave-to{
    opacity: 0;
}
1
2
3
4
5
6
7

### 样式写法

/*
 * 进入
 */

/* 当过渡进入时使用 初始化 */
.fade-1 {
    opacity: 0;
}
/* 动画要过渡到什么状态 */
.fade-1-to {
    opacity: 1;
}
/* 过渡所需要的过渡时间等等 */
.fade-1-active {
    transition: opacity 2s linear;
}


/* 当过渡进入时使用 初始化 */
.fadeLeft-enter {
    opacity: 0;
    transform: translateX(-100%);
}
/* 动画要过渡到什么状态 */
.fadeLeft-enter-to {
    opacity: 1;
    transform: translateX(0);
}
/* 过渡所需要的过渡时间等等 */
.fadeLeft-enter-active {
    transition: all 2s linear;
}


/*
 * 离开
 */

/* 当过渡离开时时使用 初始化 */
.fade-leave {
    opacity: 1;
}
/* 动画要过渡到什么状态 */
.fade-leave-to {
    opacity: 0;
}
.fade-leave-active {
    transition: all 1s linear;
}


/* 当过渡离开时时使用 初始化 */
.fadeLeft-leave {
    opacity: 1;
    transform: translateX(0);
}
/* 动画要过渡到什么状态 */
.fadeLeft-leave-to {
    opacity: 0;
    transform: translateX(-100%);
}
.fadeLeft-leave-active {
    transition: all 1s linear;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<transition name="fade">...</transition>

<!-- 
    transition
    会检测组件或元素的显示状态
    如果是隐藏 会加上leave 的样式
        name + "-leave-active"
        name + "-leave-to"
        name + "-leave"

    如果是显示 会加上enter 的样式
        name + "-enter-active"
        name + "-enter-to"
        name + "-enter"
-->

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 动画的回调

@before-enter   动画进入之前的回调
@enter          动画进入时的回调

如果进入完成 after-enter 会执行
如果进入未完成取消的话 enter-cancelled 会执行
@after-enter    动画进入后的回调
@enter-cancelled进入动画已取消

@before-leave   动画离开之前的回调
@leave          动画离开时的回调
@after-leave    动画离开时结束后的回调
@leave-canc elled动画离开取消时的回调
1
2
3
4
5
6
7
8
9
10
11
12

# transition-group 过渡列表

<div id="app">
    <!-- 
        transition-group 用来过渡列表
        必须指定一个tag 指定过渡的容器
        class属性可以直接使用在tag上
        必须指定一个key属性 属性值必须是唯一的 不能为数字

        剩下的属性 和transition 组件是一样的
    -->
    <transition-group 
        tag="div" 
        class="banner"
        :enter-active-class="enter"
        leave-active-class="time slideOutRight"
    >
        <div class="item" 
            v-for="(item, index) in arr"
            :key="item"
            v-show="index===activeIndex"
            :style="{
                backgroundColor: item
            }"
        >
            {{item}}
        </div>
    </transition-group>

    <button @click="activeIndex++">下一张</button>
</div>

<script>
    const app = new Vue({
        el: "#app",
        data() {
            return {
                arr: ["red", "blue", "yellow"],
                activeIndex: 0,
                enter: "time slideInLeft",
            }
        },
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 使用animate.css动画

<!-- 1.引入 -->
<link rel="stylesheet" href="./animate.css">

<!-- 2.加动画时间 -->
<style>
.rotateIn {
    animation: rotateIn linear 1s;
}
.rotateOut {
    animation: rotateOut linear 1s;
}
</style>

<!-- 3.使用 -->
<transition
    enter-active-class="rotateIn"
    leave-active-class="rotateOut"
>
    内容
</transition>

<!-- 
    在class加入 animated
    enter-active-class="rotateIn animated"
    leave-active-class="rotateOut animated"
    在transition元素上使用 :duration 设置动画时长
    :duration = "500" //进入和离开都是500毫秒
    :duration = "{enter: 200, leave: 400}" //进入200ms,离开400ms
 -->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

# Tree 递归组件

稍有不慎,递归组件就可能导致无限循环:

name: 'stack-overflow',
template: '<div><stack-overflow></stack-overflow></div>'
1
2

类似上述的组件将会导致“max stack size exceeded”错误,所以请确保递归调用是条件性的 (例如使用一个最终会得到 false 的 v-if)。

# 组件之间的循环引用

假设你需要构建一个文件目录树,像访达或资源管理器那样的。你可能有一个 <tree-folder> 组件,模板是这样的:

<p>
  <span>{{ folder.name }}</span>
  <tree-folder-contents :children="folder.children"/>
</p>
1
2
3
4

还有一个 <tree-folder-contents> 组件,模板是这样的:

<ul>
  <li v-for="child in children">
    <tree-folder v-if="child.children" :folder="child"/>
    <span v-else>{{ child.name }}</span>
  </li>
</ul>
1
2
3
4
5
6

# $set

// $set(target, key, value)
// 1. target 要添加的目标对象
// 2. key 对象的成员
// 3. value 对象的value

this.$set(item, "children", [])
1
2
3
4
5
6

# 7.自定义指令

# 自定义指令

全局: Vue.directive(name,option) 局部:

const sync = {
    bind(){
        ...
    },
    update(){
        ...
    }
}
directives: {
    sync,
    name: option,
}
1
2
3
4
5
6
7
8
9
10
11
12

binding 指令上所绑定的数据

  1. name 指令名称 不带v-
  2. rawName 指令的真实名称 带 v-
  3. value 指令所绑定的值
  4. expression 表达式 指令上的绑定的原始表达式
  5. arg 指令所带的参数 指令冒号后面跟的字符 v-test:aaa aaa 就是参数 切只能有一个
  6. modifiers 指令所使用的修饰符
/**
*  Vue.directive(name: string, option: object)
* 1. name  指令名称
* 2. option 指令的选项
*/

Vue.directive("focus", {
    // 指令和(标签或组件)第一次绑定时触发
    bind(el, binding, vnode, oldVNode) {
        console.log(binding)
        
        /** 
            * el 指令所绑定的元素对象(如果绑定指令的是一个组件 获取到的是组件的根元素)
            * binding 指令上所绑定的数据
            * 1. name 指令名称 不带v-
            * 2. rawName 指令的真实名称 带 v-
            * 3. value 指令所绑定的值
            * 4. expression 表达式 指令上的绑定的原始表达式
            * 5. arg 指令所带的参数 指令冒号后面跟的字符 v-test:aaa aaa 就是参数 切只能有一个
            * 6. modifiers 指令所使用的修饰符
            */
    },
    // 当绑定这个指令的(标签或组件)被添加至dom时触发
    inserted(el, binding, vnode, oldVNode) {
        console.log("绑定指令的元素已经添加到dom了")
        el.focus();
    },
    // 当绑定这个指令的(标签或组件)当数据更新时触发
    update(el, binding, vnode, oldVNode) {
        console.log("绑定指令的数据发生改变了")
    },
    // 当绑定这个指令的(标签或组件)组件发生改变时触发
    componentUpdated(el, binding, vnode, oldVNode) {
        // 使用组件的上下文里的变量发生了改变
        console.log("绑定指令的组件发生改变了")
    },
    // 取消绑定时触发
    unbind(el, binding, vnode, oldVNode) {
        // 组件或元素销毁的时候触发
        console.log("指令和组件以及解除绑定了")
    }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# v-on 实现原理

Vue.directive("listener", {
    bind(el, binding) {
        // binding.arg 指令所带的参数 指令冒号后面跟的字符
        el.addEventListener(binding.arg, (e) => {
            
            // 修饰符实现原理
            // binding.modifiers 指令所使用的修饰符
            if (binding.modifiers.stop) {
                // console.log("已阻止事件冒泡")
                // 阻止事件冒泡
                e.stopPropagation()
            }

            // binding.value 指令所绑定的值,这里是个函数
            binding.value(e)
        })
    }
});

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# v-model 实现原理

sync: {
    bind(el, binding, vnode) {
        // 判断el是否为表单元素
        if ((el instanceof HTMLInputElement) || (el instanceof HTMLTextAreaElement)) {
            // vue对各个类型的表单元素有不同处理,用el.type来区分
            console.log(el.type)
            
            // 在绑定阶段把默认值绑定给元素
            el.value = binding.value

            // 如果绑定的值(表达式)不是虚拟dom上下文的成员,报错
            if (!vnode.context[binding.expression]) {
                throw new Error(`Failed to generate render function: ReferenceError: Invalid left-hand side in assignment in`)
            }

            // 监听事件 修改虚拟dom上下文中(指令所在的模板中的实例 或者组件里)的数据
            el.addEventListener("input", () => {
                console.log(binding.expression)
                // 修改虚拟dom上下文中的数据
                if (vnode.context[binding.expression]) {
                    vnode.context[binding.expression] = event.target.value
                }
            });
        }
    },
    update(el, binding, vnode, oldVNode) {
        if ((el instanceof HTMLInputElement) || (el instanceof HTMLTextAreaElement)) {
            el.value = binding.value
        }

        // if (vnode.context[binding.expression]) {
        //     vnode.context[binding.expression] = binding.value
        // }

        console.log(binding)
        
        // 虚拟dom 的上下文 指令所使用的模板所在的组件 或实例
        console.log(vnode.context)
    },
    unbind() {
        // 清除掉 事件监听
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

# 8.混入 | 过滤器 | 插槽 | 动态组件

8-29 混入 过滤器 插槽 动态组件

# 混入

/**
* 全局混入
* Vue.mixin(option)
* 1. option 和组件的选项一样
* 
* 全局组件
* Vue.component(name, option)
*/

// 局部组件 就是一个组件选项 (带template)
// 局部混入 也是一个组件选项 (不带template)



// 会在每一个组件里 混入这里的data的成员
// 无论你是全局组件还是局部组件 里面都会包含 name这个成员
// 解决维护问题
// 组件内部的成员 可以覆盖混入的成员(声明周期除外)
// 如果组件内部有生命周期 混入也有相同的生命周期 (混入的生命周期先执行 组件内部的后执行)
Vue.mixin({
    data() {
        return {
            name: "张三",
            prefix: "i-data-"
        }
    },
    beforeMount() {
        // 用它来监控每一个组件的 运行状态等
        console.log("混入的生命周期",this);
    },
})

// vue在实例化组件时
// 1. 创建基础组件时 就把混入的成员先加到基础里面去了
// 2. 把组件中的成员加到组件里


// 局部混入
const mixin = {
    data() {
        return {
            key: "123"
        }
    },
}

const mixin1 = {
    data() {
        return {
            date: new Date().toLocaleString()
        }
    },
}


// 有很多个或者全局的所有组件 都具有相同的属性 可以使用全局混入来管理
// 如果只有几个组件 具有相同的属性 那么 就用局部混入


const Test = {
    mixins: [mixin, mixin1],
    template: `
    <div :class="prefix + 'test'">test {{key}}  {{date}} </div>
    `,
    beforeMount() {
        console.log("组件内部的生命周期", this);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

# 过滤器

/**
* 全局过滤器 管道
* 
* 任意组件或者实例中都能使用
* 
* Vue.filter(name, handler)
* 1. name 过滤器名称
* 2. handler 过滤器的处理函数
*/

Vue.filter("currency", (value) => {
    return currencys[window.navigator.language] + value
})

template:`<div> {{yen | currency | filter... }} </div>`

//  局部过滤器 只在特定的控制区域使用
filters: {
    // 对象key是过滤器名称
    // 对象的value是过滤器
    lowercase(value) {
        return value.toLowerCase().replace(/[\d]/g, "");
    },
    uppercase(value) {
        return value.toUpperCase();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 插槽

<h1 slot="title">标题</h1>

  • 如果在标签上 加上slot属性 这里的内容会被默认分配到 插槽里 $slots
  • 如果在组件的开始标签和结束标签中间嵌套的内容 没有加slot属性 就会被分配到默认插槽里
  • <template> 模板容器 可以在上面加入slot 属性 不会被显示出来
  • 作用域插槽 由组件内部把组件内部的变量 传递给使用组件时的插槽

# 虚拟DOM

  • VNode 虚拟dom 只是一个对象结构 用于做数据变更对比的 (增量更新)
  • 修改 创建原生的dom对象 (原生dom 和 浏览器的渲染引擎绑定在一块的)
  • 虚拟dom 只是模拟的dom结构 在修改数据的时候 虚拟dom 会去比较 哪个一个跟原生dom相关的数据
  • 发生了改变 当修改被检查完毕之后呢 一起修改 (避免频繁的修改dom)
// <slot /> 默认插槽 vue自带的组件 直接取组件的$slots.default 来显示

// <slot name="" /> 具名插槽 (具有名称的插槽)

// 作用域插槽 在开发组件时 给组件的具名插槽上 绑定prop 使用组件和插槽时 可以获取到这个绑定的变量

// <slot name="title" :a="key" /> prop就是传递过去的 对象中的key 绑定的值呢 key的value
1
2
3
4
5
6
7

作用域插槽示例:

<slot name="title" :a="key" :b="key2" :c="key3" />

<template slot="title" slot-scope="xx">
    <!-- 这里的xx.c可以取到组件的prop参数c -->
    <span>标题1 {{xx.c}} {{xx.a}}</span>
</template>
1
2
3
4
5
6

# 动态组件

vue自带的组件 component 动态组件 <component :is="字符串 组件名称" /> 动态切换组件示例:

<script>
const Home = {
    template:`
    <div>Home组件</div>
    `
}

const Mine = {
    template:`
    <div>Mine组件</div>
    `
}

const News = {
    template:`
    <div>News组件</div>
    `
}

const app = new Vue({
    el: "#app",
    data() {
        return {
            list: ["Home", "Mine", "News"],
            active: "Mine"
        }
    },
    template: `
    <div>
        
        <button :class="{active:active === item}" v-for="item in list" @click="active=item">{{item}}</button>
        <component :is="active"  />
        
    </div>
    `,
    components: {
        Home,
        News,
        Mine
    }
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

# 9. webpack | Babel | npm install回顾

8-31 webpack

# 回顾 npm install 命令

npm install moduleName # 安装模块到项目目录下
 
npm install -g moduleName # -g 的意思是将模块安装到全局,具体安装到磁盘哪个位置,要看 npm config prefix 的位置。
 
npm i moduleName -S #简写 -S 即 -save
npm install -save moduleName # -save 的意思是将模块安装到项目目录下,并在package文件的dependencies节点写入依赖。
 
npm i moduleName -D #简写 -D 即 -save-dev
npm install -save-dev moduleName # -save-dev 的意思是将模块安装到项目目录下,并在package文件的devDependencies节点写入依赖。
1
2
3
4
5
6
7
8
9
  • devDependencies 里面的插件只用于开发环境,不用于生产环境 比如项目中使用的 gulp ,压缩css、js的模块。这些模块在我们的项目部署后是不需要的,所以我们可以使用 -save-dev 的形式安装。
  • dependencies 是需要发布到生产环境的 像 express 这些模块是项目运行必备的,应该安装在 dependencies 节点下,所以我们应该使用 -save 的形式安装。

# yarn

yarn 管理器 和npm 一样 要使用yarn

npm i -g yarn
1

# webpack 的使用

npm i webpack webpack-cli

yarn add webpack webpack-cli
1
2
3
  1. 全局安装
npm i -g webpack webpack-cli
1

# 使用webpack的好处

  1. 模块化文件 (省去闭包) 模块化更好 管理
  2. 压缩代码 (体积更小 不容易读懂)
  3. 工程化 (自动处理css 自动处理less sass scss stylus postcss)

# 1.使用webpack

要使用webpack 必须在项目的根目录下创建一个叫 webpack.config.js的配置文件 webpack是用nodejs 编写的 所以 这个配置文件 用node方式导出配置对象

const path = require("path");

module.exports = {
    // 入口文件 要从哪开始编译js文件
    entry: "", // 文件的地址

    // 编译好的js文件应该输出到哪
    output: {
        // 要输出的目录路径 必须是一个绝对路径 (目录如果不存在 webpack会自动创建)
        path: path.resolve("dist"),
        // 打包后的文件名称
        filename: "bundle.js",
    },

    // 模块处理工具 用于处理不同的文件 webpack允许你使用第三方工具来进行处理不同的文件
    module: {
        // 模块处理规则
        // 定义什么文件 由什么工具来处理
        rules: [
            // 一个对象一个规则
            {
                //包含一个正则表达式 匹配文件名称的
                test: /\.css$/, 
                use: [
                    // 都需要使用npm下载
                    "style-loader", // 用于给css文件包装一个style标签
                    "css-loader",   // 用于解析css文件的
                    "postcss-loader", // postcss 处理器
                ],
            }            
        ]
    },

    // 打包时的一些插件
    plugins: [],

    // 开发模式 development 里面没有压缩代码 并且包含了注释
    // 生产模式 production 里面压缩了代码 不包含注释
    mode: "development",
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# loader

以下内容引用 dayindayout的简书文章

webpack可以把以指定入口的一系列相互依赖的模块打包成一个文件,这里的模块指的不只是js,也可以是css;

样式引入两种方法(这两种方法都需要配置响应的loader):

(1)、在引入css时,在最后生成的js文件中进行处理,动态创建style标签,塞到head标签里;

(2)、打包时把css文件拆出来,css相关模块最终打包到一个指定的css文件中,我们手动用link标签去引入这个css文件就可以了;

style-loader css-loader

1、安装 style-loader 和 css-loader

npm i --save-dev style-loader css-loader

yarn add --save-dev style-loader css-loader
1
2
3

2、main.js中引用用到的样式;

require('./main.css'); 
1

3、在webpack.config.js里配置loader

 loaders: [{  
    test: /\.css$/,  
    loader:'style-loader!css-loader'  
},{  
    test: /\.less$/,  
    loader:'style-loader!css-loader'  
}]
1
2
3
4
5
6
7

注:loaders是一个数组,其中的元素是我们使用的所有loader,每个loader对应一个object,test是加载器要匹配的文件后缀正则,!”用来分隔不同的加载器。上述loader配置表示,webpack在打包过程中,遇到后缀为css的文件,就会使用style-loader和css-loader去加载这个文件。上面的loader配置是webpack1的写法,对应的webpack2写法如下(建议用webpack2)

 loaders: [{  
    test: /\.css$/,  
    use:['style-loader','css-loader']
},{  
    test: /\.less$/,  
    use:['style-loader','css-loader']
}]
1
2
3
4
5
6
7

注: 1.遇到后缀为.css的文件,webpack先用css-loader加载器去解析这个文件,遇到“@import”等语句就将相应样式文件引入(所以如果没有css-loader,就没法解析这类语句),最后计算完的css,将会使用style-loader生成一个内容为最终解析完的css代码的style标签,放到head标签里。

2.loader是有顺序的,webpack肯定是先将所有css模块依赖解析完得到计算结果再创建style标签。因此应该把style-loader放在css-loader的前面(webpack loader的执行顺序是从右到左)。

第二种样式样式引入的方法: extract-text-webpack-plugin该插件的主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象;首先我先来介绍下这个插件的安装方法:

npm install extract-text-webpack-plugin --save-dev
1

2、webpack.config.js中写入

const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
    entry: './src/main.js',
    output: {
        path: path.resolve(__dirname, './dist'),
        publicPath: '/dist/',
        filename: 'build.js'
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: "css-loader"
            })
        }]
    },
    plugins: [newExtractTextPlugin("styles.css")]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

3、在html中引入样式

<link rel="stylesheet" href="./dist/style.css" type="text/css" />
1

# postcss

用于给css自动添加前缀 和处理一些背景图片合并 等等功能 需要先创建一个 postcss.config.js 的配置文件

要使用它 需要安装

npm i autoprefixer postcss postcss-loader
# 或者
yarn add autoprefixer postcss postcss-loader -D
1
2
3
module.exports = {
    // postcss的插件
    // autoprefixer 自动添加浏览器的私有前缀
    plugins: [
        require('autoprefixer')
    ]
}
1
2
3
4
5
6
7

应该添加哪些浏览器前缀,根据市场占有率来控制 package.json配置

"browserslist": [
"last 2 version",
"> 1%"
],

1
2
3
4
5

# 图片处理

  1. 复制图片到另一个目录 file-loader
  2. 把图片解析成base64的字符串 url-loader
npm i url-loader file-loader -D

yarn add url-loader file-loader -D
1
2
3
{
    test: /\.(png|jpg|jpeg|gif)$/,
    // use: "url-loader"
    use: "file-loader",
},
1
2
3
4
5

# babel

{
    test: /\.js$/,
    // 排除哪些目录的js文件不编译
    exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            // babel的预设 使用预设的模式来解析js
            presets: ['@babel/preset-env']
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# babel 配置文件

可以写入到package.json 的babel选项中 也可以添加到根目录的.babelrc文件中

"presets": [
    "@babel/preset-env"
  ],
1
2
3
{
    test: /\.js$/,
    // 排除哪些目录的js文件不编译
    exclude: /node_modules/,
    use: "babel-loader",
}
1
2
3
4
5
6

# webpack 打包 Vue

{
    test: /\.vue$/,
    // 利用vue-loader 把vue文件编译成一个js对象
    // 把css 使用style-loader和css-loader来处理
    // 把js 使用vue-loader 中 babel-loader
    // 把template 使用vue的组件中的render选项来进行处理
    use: "vue-loader",
}
1
2
3
4
5
6
7
8
// 不支持模板编译模式的vue,用于单文件Vue组件
import Vue from "vue";

// 支持template 模板编译
import Vue from "vue/dist/vue.esm";
1
2
3
4
5

# 单文件Vue组件

就是这个文件是一个单独的组件

<template>
<!-- html-loader来处理 保存到组件的模板选项里去 -->
    <div>
        <h1>{{title}}</h1>
    </div>
</template>

<script>
    // 这个文件只是被vue处理成组件对象
    // template 被vue添加到了 render函数里运行
    export default {
        data() {
            return {
                title: "这是一个标题"
            }
        },
        methods: {

        }
    }
</script>

<style>
    /* 这里的东西由css-loader 直接处理了 */
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# 10.vue-cli | webpack-dev-server | html-webpack-plugin

9-2

webpack-dev-server html-webpack-plugin vue-cli VUE插件

# webpack-dev-server

使用express创建的一个开发服务器

会把编译的内容放入内存,不会生成文件,所以在开发时图片使用 url-loader来编译

启动后修改文件会自动刷新浏览器

安装

npm install webpack-dev-server
#or
yarn add webpack-dev-server
1
2
3

修改启动命令

"dev": "webpack-dev-server"
1

修改webpack.config.js配置文件

devServer: {
    proxy: { // 代理url到后端开发服务器
      '/api': 'http://localhost:3000'
    },
    contentBase: path.join(__dirname, 'public'), // boolean | string | array, 静态文件位置
    compress: true, // 是否启用Gzip压缩
    port: 3000 // 端口号


    historyApiFallback: true, // true for index.html upon 404, object for multiple paths
    hot: true, // hot module replacement. Depends on HotModuleReplacementPlugin
    https: false, // true for self-signed, object for cert authority
    noInfo: true, // only errors & warns on hot reload
    // ...
  },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# html-webpack-plugin

用于把DevServer 在内存中生成的文件 添加到指定的html页面中

yarn add html-webpack-plugin
#or
npm install --save-dev html-webpack-plugin
1
2
3

webpack.config.js文件配置

plugins:{
    // 把你准备好的html模板 通过webpack-dev-server 把编译好的 js文件插入到你的模板中去
    new HtmlWebpackPlugin({
        title: "网页标题",
        filename: "index.html",
        template: path.resolve("public", "index.htl")
    })
}
1
2
3
4
5
6
7
8

# vue-cli

用webpack搭建好的一个vue开发环境 (集成环境)

# 安装 3.x 版本

npm install -g @vue/cli
# OR
yarn global add @vue/cli
1
2
3

# 安装 2.x 版本

npm install -g vue-cli
# 创建项目
vue init webpack <project-name>
1
2
3

# 检查是否安装完成

vue --version
> 3.11.0
1
2

# 创建项目 3.x

vue create <project-name>

# 按上下键选择预设方案
? Please pick a preset:
# 选默认
> default (babel, eslint) 
  Manually select features


# 项目创建好后
# 先进入到创建好的项目目录
$ cd my-first-vue-project 
# 启动项目
$ yarn serve 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 创建的项目目录

src 开发目录 assets 资源文件目录 (图片 css文件之类的) components 组件目录 views 页面文件 App.vue 项目的启动组件 main.js 项目的入口文件(webpack打包的入口文件) 页面主要逻辑 router.js 定义页面路由跳转规则 stores.js vuex状态管理

## 入口文件

// 入口js文件
import Vue from 'vue';
import App from './App.vue';
import test from "./plugins/test.plugin";
// 先导入iview
// import iView from 'iview';
// 再导入iview需要的css文件
// import 'iview/dist/styles/iview.css';
// import Button from "./components/Button";

// Vue的use方法用于使用插件
// Vue.use(iView);

// Vue.config.productionTip = false

// 如果要声明全局组件 必须在new vue实例之前 注册好组件
// Vue.component("Button", Button);


// Vue.use(plugin?: Function | Object, options?: object)


// 自己实现的插件注册方法
// Vue.iUse = function(plugin, options = {}) {
//     if (typeof plugin === "function") {
//         plugin(Vue, options);
//     } else if(typeof plugin === "object" && typeof plugin.install === "function") {
//         plugin.install(Vue, options);
//     }
// }

// Vue.iUse(test, {
//     name: "!11"
// })

Vue.use(test)

new Vue({
    el: "#app",
    render: h => h(App),
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# vue插件

Vue的use方法用于使用插件
Vue.use(iView);

Vue.use(plugin?: Function | Object, options?: object)


自己实现的插件注册方法
Vue.iUse = function(plugin, options = {}) {
    if (typeof plugin === "function") {
        plugin(Vue, options);
    } else if(typeof plugin === "object" && typeof plugin.install === "function") {
        plugin.install(Vue, options);
    }
}

Vue.iUse(test, {
    name: "!11"
})

Vue.use(test)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 11.vueRouter

2019/9/5 vue-router

# vue-router

mvc 后端

Model View Controller

通过服务器生成html页面 浏览器显示出来

vue-router 根据输入的地址 切换不同的界面(界面渲染的过程 在浏览器完成)

vue全家桶的成员 路由模块 vue-router 是vue的一个插件 使用vue-router 需要先注册插件

location 输入的地址,vue路由的实现 history 历史,前一页后一页的实现 vue对它们做了一个拦截器

# 安装

1.直接使用vue创建自定义项目,选中router,会自动配置

2.手动配置

npm install vue-router
# or
yarn add vue-router
1
2
3

使用

// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'

// 1. 注册vue-router
Vue.use(VueRouter)

// 2. 创建好一个路由 等待被使用(放进vue实例中使用)
export default new VueRouter({
    // 路由模式
    // 1. hash 兼容老浏览器的模式
    // 2. history 使用浏览器历史记录来管理路由
    mode: "history",
    // 路由列表 数组
    routes: [
        // 每一个对象是一个路由的配置
        {
            path: "/",
            alias: "/index.html",
            component: () => import("./pages/Home.vue")
        },
        {
            path: "/admin",
            component: ()=> import("./pages/Admin.vue")
            // 子路由
            children: [
                 {
                    path: "",
                    alias: "index.html",
                    component: () => import("./pages/admin/Hone.vue"),
                }
            ]
        }
    ]
})

// main.js
// 3.导入生成好的路由对象
import router from './router'

new Vue({
  // 4.路由注册到实例中
  router,
  render: h => h(App),
}).$mount('#app')

// APP.vue
// 5.在实例的模板中添加路由容器
<template>
  <div id="app">
    <router-view/>
  </div>
</template>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

# 标签

<router-link to="/foo"> 使用 router-link 组件来导航. 通过传入 to 属性指定链接. <router-link>默认会被渲染成一个 <a> 标签

<router-view /> 路由出口 路由匹配到的组件将渲染在这里 <router-view /> vue-router的全局属性 用于显示路由内容的容器 要注意,当 <router-link> 对应的路由匹配成功,将自动设置 class 属性值 .router-link-active。

# 动态路由匹配

我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 User 组件,对于所有 ID 各不相同的用户,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果:

const User = {
  template : '<div>User</div>'
}

const router = new VueRouter({
  routes: [
    // 动态路径参数 以冒号开头
    {
      path: '/user/:id',
      component: User
    }
  ]
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14

现在呢,像 /user/foo 和 /user/bar 都将映射到相同的路由。

一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 User 的模板,输出当前用户的 ID:

const User = {
  template: '<div>User {{ $route.params.id }}</div>'
}
1
2
3

你可以在一个路由中设置多段“路径参数”,对应的值都会设置到 $route.params中。例如:

// 模式: /user/:username/post/:post_id
// 匹配路径: /user/evan/post/123

// $route.params: 
{ username: 'evan', post_id: '123' }
1
2
3
4
5

# 捕获所有路由或 404 Not found 路由

常规参数只会匹配被 / 分隔的 URL 片段中的字符。如果想匹配任意路径,我们可以使用通配符 (*):


{
  // 会匹配所有路径
  path: '*',
  // 重定向
  redirect: "/404.html"
}
{
  // 会匹配以 `/user-` 开头的任意路径
  path: '/user-*'
}

1
2
3
4
5
6
7
8
9
10
11
12

当使用通配符路由时,请确保路由的顺序是正确的,也就是说含有通配符的路由应该放在最后。路由 { path: '*' } 通常用于客户端 404 错误。如果你使用了History 模式,请确保正确配置你的服务器。

当使用一个通配符时,$route.params 内会自动添加一个名为 pathMatch 参数。它包含了 URL 通过通配符被匹配的部分:


// 给出一个路由 { path: '/user-*' }
this.$router.push('/user-admin')
this.$route.params.pathMatch // 'admin'
// 给出一个路由 { path: '*' }
this.$router.push('/non-existing')
this.$route.params.pathMatch // '/non-existing'

1
2
3
4
5
6
7
8

# 嵌套路由

实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件,例如:

/user/foo/profile                     /user/foo/posts
+------------------+                  +-----------------+
| User             |                  | User            |
| +--------------+ |                  | +-------------+ |
| | Profile      | |  +------------>  | | Posts       | |
| |              | |                  | |             | |
| +--------------+ |                  | +-------------+ |
+------------------+                  +-----------------+
1
2
3
4
5
6
7
8
template: `
  <div class="admin">
    <h2>Admin</h2>
    <router-view></router-view>
  </div>
`

// 要在嵌套的出口中渲染组件,需要在 VueRouter 的参数中使用 children 配置:
{
  path: "/admin",
  component: ()=> import("./pages/Admin.vue")
  // 子路由
  children: [
        {
          path: "",
          alias: "index.html",
          component: () => import("./pages/admin/Hone.vue"),
      }
  ]
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

要注意,以 / 开头的嵌套路径会被当作根路径。 这让你充分的使用嵌套组件而无须设置嵌套的路径。

children 配置就是像 routes 配置一样的路由配置数组

# 编程式的导航

除了使用 <router-link> 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现。

# router.push(location, onComplete?, onAbort?)

注意:在 Vue 实例内部,你可以通过 $router 访问路由实例。因此你可以调用 this.$router.push。

想要导航到不同的 URL,则使用 router.push 方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,则回到之前的 URL。

当你点击 <router-link> 时,这个方法会在内部调用,所以说,点击 <router-link :to="..."> 等同于调用 router.push(...)。

该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:


// 字符串
router.push('home')

// 对象
router.push({ path: 'home' })

// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})

// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})

1
2
3
4
5
6
7
8
9
10
11
12
13

# 给路由添加名称和原数据

{
  path: "/admin/:userId",
  component: Admin,
  // 给当前路由添加个名称
  // 命名路由:router.push({ name: 'Admin', params: { userId: '123' }})
  name: "Admin",
  // 给当前路由添加一些元数据
  meta: {
    a: true,
    b: 2333,
    // 比如说该页面需要验证,就可以写这么一个元数据,当它为true时,需要通过导航守卫验证
    needAuth: true,
    authLogin: true
  }
}

// 导航守卫中通过 `to.meta.authLogin` 来获取该页面是否需要登录认证
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 懒加载组件

component: () => import("./pages/Home.vue");
1

# $route 属性与方法

$route 记录当前路由信息

/**
  * $route 从当前路由中取出 对应的参数
  * 1. fullPath 当前路由的完全路径
  * 2. hash 路径中#号后面的数据
  * 3. meta 路由的元数据
  * 4. name 当前路由的名称 在配置路由的时候添加name选项就会有此参数
  * 5. params 匹配的动态路由参数
  * 6. query 查询字符串参数
  */
1
2
3
4
5
6
7
8
9

后退 this.$router.back();

前进 this.$router.forward();

跳转 this.$router.go(1); this.$router.push()

替换 this.$router.replace()

重定向:redirect

覆盖页面


// this.$router.replace(`/news/${item.id}`)

// 把当前记录覆盖成新页面的记录
// 可以有效的禁止用户返回到 我们不想让他返回的页面
this.$router.replace({
    // 路由的名称
    name: "NewsDetailPage",
    // 路由的路径参数
    params: {
        id: item.id
    },
    query: {
        createTime: item.createTime
    }
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 导航守卫


// 前置守卫
// 进入路由之前的守卫
router.beforeEach((to, from, next) => {
    /**
     * 1. to 要进入到的路由 就是组件中的$route对象
     * 2. from 你刚从哪个路由来
     * 3. next 函数 放行
     */

    // 业务逻辑 
    
    if (to.meta.authLogin) {
        // 需要验证的地方我再去做验证
        // 如果本地存储里有登录状态 放行

        if (localStorage.getItem("logined")) {
            next()
        } else {
            next({
                path: "/admin/login.html"
            })
        }
    } else {
        next();
    }
});



// 后置守卫
// 进入路由后的守卫
router.afterEach((to, form, next) => {

});


/**
 * 验证登录的流程
 * 
 * 1. 检查哪些路由是否需要验证登录
 * 2. 通过本地存储里保存的登录状态来判断是否已登录
 * 3. 如果登录 放行
 * 4. 如果未登录 跳转至登录页面
 * 
 * 
 * 登录页面的逻辑
 * 1. 登录 保存登录状态至 本地存储
 * 2. 跳转到管理页面
 * 
 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
在 GitHub 上编辑此页
#vue
上次更新: 2020/09/19, 14:09:00

← 前端RSA加密与解密 Vue创建路由的封装 →

最近更新
01
快速注册github账号
01-26
02
解决github图片不显示问题
01-26
03
Git如何删除所有提交历史
01-25
更多文章>
Theme by Vdoing | Copyright © 2020-2021 伞仙 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式