Vue简介

1.基本使用

vue2 官方文档:https://v2.vuejs.org/v2/guide/
vue3 官方文档:https://vuejs.org/guide/introduction.html
最简单的安装方式是创建一个 .html 文件,然后通过如下方式引入 Vue 2.6.8

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

在idea 有更好的代码提示效果,建议安装 vue.js 插件。

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统,以操作数据的方式操作 DOM 节点,Vue 不建议使用者直接操作节点内容,目前大部分开发平台都倡导以修改数据的方式操作节点,操作节点被认为是一种落后的技术

<div id="app">
  {{ message }}
div>

代码渲染

var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

通过浏览器控制台输入 app.message=“测试” 可以修改vue的数据,会发现一旦数据变化了,vue 管理的页面也发生变化。
为了让用户和你的应用进行交互,我们可以用 v-on 指令添加一个事件监听器,通过它调用在 Vue 实例中定义的方法:

<div id="app-5">
  <p>{{ message }}p>
  <button v-on:click="reverseMessage">反转消息button>
div>

渲染代码

var app5 = new Vue({
  el: '#app-5',
  data: {
    message: 'Hello Vue.js!'
  },
  methods: {
    reverseMessage: function () {
      this.message = this.message.split('').reverse().join('')
    }
  }
})

2.生命周期钩子 Lifecycle

beforeCreate         组件创建之前
created            组件创建完毕
beforeMount          组件挂载之前
mounted           组件挂载完毕
beforeUpdate        组件更新之前
updated            组件更新完毕
beforeDestroy         组件销毁之前
destroyed           组件销毁完毕

案例:

let app = new Vue({
    el: "#app",
    data: {
        message: "大家好,我是vue"
    },
    beforeCreate() {
        console.info("beforeCreate:组件创建之前")
    },
    created() {
        console.info("created:组件创建完毕")
    },
    beforeMount() {
        console.info("beforeMount:组件挂载之前")
    },
    mounted() {
        console.info("mounted:组件挂载完毕")
    },
    beforeUpdate() {
        console.info("beforeUpdate:组件更新之前")
    },
    updated() {
        console.info(" updated:组件更新完毕")
    },
    beforeDestroy() {
        console.info("beforeDestroy:组件销毁之前")
    },
    destroyed() {
        console.info("destroyed:组件销毁完毕")
    },
    methods: {
        setMessage() {
            this.message = this.message.split('').reverse().join('')
        }
    }
})

3.模板语法 Template Syntax

1.插值
数据绑定最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值:

<span>Message: {{ msg }}span>

Mustache 标签将会被替代为对应数据对象上 msg 属性的值。无论何时,绑定的数据对象上 msg 属性发生了改变,插值处的内容都会更新。
通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。但请留心这会影响到该节点上的其它数据绑定:

<span v-once>这个将不会改变: {{ msg }}span>

2.原始 HTML
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:

<p>Using mustaches: {{ rawHtml }}p>
<p>Using v-html directive: <span v-html="rawHtml">span>p>

这个 span 的内容将会被替换成为属性值 rawHtml,直接作为 HTML——会忽略解析属性值中的数据绑定。

3.Attribute
Mustache 语法不能作用在 HTML attribute 上,遇到这种情况应该使用 v-bind 指令:

<div v-bind:id="dynamicId">div>

对于布尔 attribute (它们只要存在就意味着值为 true),v-bind 工作起来略有不同,在这个例子中:

<button v-bind:disabled="isButtonDisabled">Buttonbutton>

如果 isButtonDisabled 的值是 null、undefined 或 false,则 disabled attribute 甚至不会被包含在渲染出来的 button 元素中。

4.使用 JavaScript 表达式``
迄今为止,在我们的模板中,我们一直都只绑定简单的属性键值。但实际上,对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。

{{ number + 1 }}

{{ ok ? 'YES' : 'NO' }}

{{ message.split('').reverse().join('') }}

<div v-bind:id="'list-' + id">div>

4.常用指令

指令(Directives)是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)。指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。回顾前面看到的例子:

<p v-if="seen">现在你看到我了p>

这里,v-if 指令将根据表达式 seen 的值的真假来插入/移除
元素。
一些指令能够接收一个“参数”,在指令名称之后以冒号表示。例如,v-bind 指令可以用于响应式地更新 HTML attribute:

<a v-bind:href="url">...a>

在这里 href 是参数,告知 v-bind 指令将该元素的 href 与 attribute 与表达式 url 的值绑定。
另一个例子是 v-on 指令,它用于监听 DOM 事件:

<a v-on:click="doSomething">...a>

在这里参数是监听的事件名。我们也会更详细地讨论事件处理。
从 2.6.0 开始,可以用方括号括起来的 JavaScript 表达式作为一个指令的参数,但是变量名会被 vue 转为小写,所以建议使用下划线命名。

<a v-bind:[attribute_name]="url"> ... a>

这里的 attribute_name 会被作为一个 JavaScript 表达式进行动态求值,求得的值将会作为最终的参数来使用。例如,Vue 实例有一个 data 属性 attribute_name,其值为 “href”,那么这个绑定将等价于 v-bind:href。

同样地,你可以使用动态参数为一个动态的事件名绑定处理函数:

<a v-on:[event_name]="doSomething"> ... a>

在这个示例中,当 eventName 的值为 “focus” 时,v-on:[eventName] 将等价于 v-on:focus。
指令缩写
1.v-bind 缩写

<a v-bind:href="url">...a>
<a :href="url">...a>
<a :[key]="url"> ... a>

2.v-on 缩写

<a v-on:click="doSomething">...a>
<a @click="doSomething">...a>
<a @[event]="doSomething"> ... a>

它们看起来可能与普通的 HTML 略有不同,但 : 与 @ 对于 attribute 名来说都是合法字符,在所有支持 Vue 的浏览器都能被正确地解析。而且,它们不会出现在最终渲染的标记中。缩写语法是完全可选的。

5.数据侦听器 Watchers

当需要监听数据变化时就需要侦听器。Vue 通过 watch 选项提供了一套更通用的方法,来响应数据的变化。

<div id="watch-example">
  <p>
    Ask a yes/no question:
    <input v-model="question">
  p>
  <p>{{ answer }}p>
div>

渲染代码:

var watchExampleVM = new Vue({
  el: '#watch-example',
  data: {
    question: '',
    answer: 'I cannot give you an answer until you ask a question!'
  },
  watch: {
    // 如果 `question` 发生改变,这个函数就会运行
    question: function (newQuestion, oldQuestion) {
      this.answer = 'Waiting for you to stop typing...'
      this.debouncedGetAnswer()
    }
  }
})

侦听器的格式
1、 方法格式的侦听器
缺点1:无法在刚进入页面的时候,自动触发
缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器
2、 对象格式的侦听器
好处1:可以通过 immediate 选项,让侦听器自动触发(默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使用 immediate 选项。)
好处2:可以通过 deep 选项,让侦听器深度监听对象中每个属性的变化!!!

watch: {
    user: {
        handler(newval) {    	
        },
        immediate: true,
        deep: true
    }
}

注意:

1.handler 是固定写法,表示当 user 或 user 的属性变化时,自动调用 handler 处理函数。
2.immediate默认值为false,immediate 表示页面初次渲染好之后,就立即触发当前的 watch 侦听器。
3.当监听的变量是一个对象时,使用 deep: true 可以监听对象内部的属性变化。

watch: {
    'user.name': {
        handler(newval) {    	
        },
    }
}

如果只想监听对象中的某个属性,也可以具体指定属性里面的某个。

6.过滤器 Filters

过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式与 v-bind 属性绑定。

过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符|”进行调用,示例代码如下:

{{message|change}}
注意:过滤器函数形参中的第一个值,永远都是‘管道符’前面的那个值,剩余的参数用方法也可以接收,而且最终展示内容由过滤器的返回值决定。
<div id="app">
    <div>{{message|change('w','W')}}div>
div>
<script>
    new Vue({
        el: "#app",
        data: {message: "hello world!!"},
        filters: {
            change(message, c1, c2) {
                const first = message.charAt(0).toUpperCase();
                const other = message.slice(1).replace(c1, c2)
                return first + other;
            }
        }
    })
</script>

全局过滤器(在脚手架项目中使用)
在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。如果希望在多个 vue 组件之间共享过滤器,则可以按照如下的格式定义全局过滤器:

app.filter('capitalize', (str) => {
    // 强调:过滤器中,一定要有一个返回值
    return str.charAt(0).toUpperCase() + str.slice(1) + '~';
})

如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是”私有过滤器“。

7.Class、Style 绑定

我们可以传给 v-bind:class 一个对象,以动态地切换 class:

<div v-bind:class="{ active: isActive }">div>

上面的语法表示 active 这个 class 存在与否将取决于数据 property isActive 的 truthiness。 你可以在对象中传入更多字段来动态切换多个 class。此外,v-bind:class 指令也可以与普通的 class attribute 共存。当有如下模板:

<div class="static"
  v-bind:class="{ active: isActive, 'text-danger': hasError }">div>

和如下 data:

data: {
  isActive: true,
  hasError: false
}

结果渲染为:

<div class="static active">div>

当 isActive 或者 hasError 变化时,class 列表将相应地更新。例如,如果 hasError 的值为 true,class 列表将变为 “static active text-danger”。
绑定的数据对象不必内联定义在模板里:

<div v-bind:class="classObject">div>
data: {
  classObject: {
    active: true,
    'text-danger': false
  }
}

v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用引号括起来) 来命名:

<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }">div>

渲染模板数据:

data: {
  activeColor: 'red',
  fontSize: 30
}

直接绑定到一个样式对象通常更好,这会让模板更清晰:

<div v-bind:style="styleObject">div>

渲染模板数据:

data: {
  styleObject: {
    color: 'red',
    fontSize: '13px'
  }
}

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div v-bind:style="[baseStyles, overridingStyles]">div>

8.计算属性 Computed Properties

<div id="app">
    <p>{{userString}}p>
div>
<script>
    new Vue({
        el: "#app",
        data: {
            user: {name: "张三", id: "1"}
        },
        computed: {
            userString() {
                return this.user.id.padStart(5, '0') + '----->' + this.user.name
            }
        }
    })
script>

特点:定义的时候,要被定义为“方法”在使用计算属性的时候,当普通的属性使用即可

好处:实现了代码的复用只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值!

很多时候我们可以不使用计算属性而使用普通方法代替计算属性。

<div id="app">
    <p>{{userString}}p>
    <p>{{userInfo()}}p>
    <button @click="type=!type">{{type}}button>
div>
<script>
    new Vue({
        el: "#app",
        data: {
            user: {name: "张三", id: "1"},
            type: true
        },
        methods: {
            userInfo() {
                console.info("userInfo")
                return this.user.id.padStart(5, '0') + '----->' + this.user.name
            }
        },
        computed: {
            userString() {
                console.info("userString")
                return this.user.id.padStart(5, '0') + '----->' + this.user.name
            }
        }
    })
script>

9.条件渲染 Conditional Rendering

.v-if 用于条件限制某节点的渲染与否。

<h1 v-if="awesome">Vue is awesome!h1>

也可以用 v-else 添加一个“else 块”:

<h1 v-if="awesome">Vue is awesome!h1>
<h1 v-else>Oh no h1>

vue 2.1.0 新增 v-else-if,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:

<div v-if="type === 'A'"> Adiv>
<div v-else-if="type === 'B'">Bdiv>
<div v-else-if="type === 'C'">Cdiv>
<div v-else>Not A/B/Cdiv>

2.v-show 用于根据条件展示元素的指令。用法与 v-if 大致一样。

<h1 v-show="ok">Hello!h1>

不同的是带有 v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。

10.列表渲染 List Rendering

我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  li>
ul>

渲染代码:

var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

在 v-for 还支持一个可选的第二个参数,即当前项的索引。

<ul id="example-2">
  <li v-for="(item, index) in items">
    - {{ index }} - {{ item.message }}
  li>
ul>

你也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

在 v-for 里使用对象,也可以用 v-for 来遍历一个对象的属性。 ```html
  • {{ value }}
```

渲染代码:

new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})

你也可以提供第二个的参数为 property 名称 (也就是键名):

<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
div>

还可以用第三个参数作为索引:

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
div>

10.列表渲染 List Rendering

我们可以用 v-for 指令基于一个数组来渲染一个列表。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  li>
ul>

渲染代码:

var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

在 v-for 还支持一个可选的第二个参数,即当前项的索引。

<ul id="example-2">
  <li v-for="(item, index) in items">
    - {{ index }} - {{ item.message }}
  li>
ul>

你也可以用 of 替代 in 作为分隔符,因为它更接近 JavaScript 迭代器的语法:

在 v-for 里使用对象,也可以用 v-for 来遍历一个对象的属性。
<ul id="v-for-object">
  <li v-for="value in object">
    {{ value }}
  li>
ul>

渲染代码:

new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})

你也可以提供第二个的参数为 property 名称 (也就是键名):

<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
div>

还可以用第三个参数作为索引:

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
div>

11.表单输入绑定

你可以用 v-model 指令在表单 input、textarea 及 select 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
text 和 textarea 元素使用 value 属性和 input 事件
checkbox 和 radio 使用 checked 属性和 change 事件
select 字段将 value 作为 prop 并将 change 作为事件
1.单行文本

<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}p>

2.多行文本

<p>Message is: {{ message }}p>
<textarea v-model="message" placeholder="add multiple lines">textarea>p><p>3.复选框p><p>单个复选框,绑定到布尔值:p><input type="checkbox" v-model="checked">
<p>选中:{{ checked }}p>

多个复选框,绑定到同一个数组:

<div id='example-3'>
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
  <label for="jack">Jacklabel>
  <input type="checkbox" id="john" value="John" v-model="checkedNames">
  <label for="john">Johnlabel>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
  <label for="mike">Mikelabel>  
  <span>Checked names: {{ checkedNames }}span>
div>

4.单选按钮绑定到普通字符串

<div id="example-4">
  <input type="radio" id="one" value="One" v-model="picked">
  <label for="one">Onelabel>  
  <input type="radio" id="two" value="Two" v-model="picked">
  <label for="two">Twolabel>  
  <span>Picked: {{ picked }}span>
div>

5.选择框

单选时:

<div id="example-5">
  <select v-model="selected">
    <option disabled value="">请选择option>
    <option>Aoption>
    <option>Boption>
    <option>Coption>
  select>
  <span>Selected: {{ selected }}span>
div>

多选时 (绑定到一个数组):

<div id="example-6">
  <select v-model="selected" multiple>
    <option>Aoption>
    <option>Boption>
    <option>Coption>
  select>  
  <span>Selected: {{ selected }}span>
div>

用 v-for 渲染的动态选项:

<select v-model="selected">
  <option v-for="option in options" v-bind:value="option.value">
    {{ option.text }}
  option>
select>
<span>Selected: {{ selected }}

渲染数据:

new Vue({
  el: '...',
  data: {
    selected: 'A',
    options: [
      { text: 'One', value: 'A' },
      { text: 'Two', value: 'B' },
      { text: 'Three', value: 'C' }
    ]
  }
})

12.脚手架环境配置

1.安装 node.js 。完成后 cmd 打开命令提示符窗口,输入: npm -v 检测是否安装成功,如果输出版本号,则表示安装成功。
2.配置 npm
npm(node package manager):nodejs 的包管理器,用于 node 插件管理(包括安装、卸载、管理依赖等)因为 npm 安装插件是从国外服务器下载,受网络的影响比较大,可能会出现异常,所以我们乐于分享的淘宝团队干了这事。来自官网:这是一个完整 npmjs.org 镜像,你可以用此代替官方版本(只读),同步频率目前为 10 分钟一次以保证尽量与官方服务同步。

npm config set registry https://registry.npm.taobao.org

验证命令 npm config get registry 如果返回 https://registry.npm.taobao.org/,说明镜像配置成功。有时一些项目中相关依赖下载报错也可以安装 cnpm。使用时只需要将原来的 npm 换成 cnpm 命令即可。 安装 cnpm 命令如下:

npm install -g cnpm --registry=https://registry.npm.taobao.org

vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。

npm install -g @vue/cli

基于 vue-cli 快速生成工程化的 Vue 项目:

vue create 项目的名称
选择预设值,课件使用 vue3 ,回车后自动创建项目并导入 babel 与 eslint

? Please pick a preset: (Use arrow keys)
> Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
  Manually select features

也可以使用 idea 创建(前提条件安装 vue.js 插件)默认使用 vue3,如果想切换其他版本也可以取消勾选 Use the default project setup。然后控制台弹出上面的选项。
Vue简介_第1张图片
vue 项目的运行流程

在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。

其中:

① App.vue 用来编写待渲染的模板结构

② index.html 中需要预留一个 el 区域

③ main.js 把 App.vue 渲染到了 index.html 所预留的区域中

vue 项目中 src 目录的构成:
Vue简介_第2张图片
assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源

components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下

main.js 是项目的入口文件。整个项目的运行,要先执行 main.js

App.vue 是项目的根组件。

启动项目

npm run serve

13.组件

组件(Component)是 Vue.js 最强大的功能之一。 组件可以扩展 HTML 元素,封装可重用的代码。 组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:

vue 中规定组件的后缀名是 .vue。每个 vue 组件都由 3 部分构成,分别是:

template -> 组件的模板结构
script -> 组件的 JavaScript 行为
style -> 组件的样式
其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。

<template>
    <div class="app-container">
        <h1>App 根组件h1>
        <p>{{message}}p>
    div>
template>

<script>
    export default {
        name: "HelloWorld",
        data() {
            return {
                message: "hello"
            }
        }
    }
script>

<style scoped>
    .app-container {
        padding: 1px 20px 20px;
        background-color: #efefef;
    }
style>

注意:

1.template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素,template 中只能包含唯一的根节点。

2.vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。

3.vue 组件内的 style 节点负责编写样式美化当前组件的 UI 结构。使用 scoped 属性修饰后该样式只会影响当前组件。

在 components 目录下创建几个组件,组件在被封装好之后,彼此之间是相互独立的,不存在父子关系。在使用组件的时候,根据彼此的嵌套关系,形成了父子关系、兄弟关系。

使用组件的三个步骤

步骤1∶使用 import 语法导入需要的组件

步骤2:使用 components 节点注册组件

步骤3:以标签形式使用刚才注册的组件

<template>
  <HelloWorld/>
template>

<script>
import HelloWorld from './components/HelloWorld.vue'

export default {
  name: 'App',
  components: {
    HelloWorld
  }
}
script>

<style>

style>

通过 components 注册的是私有子组件。例如:在组件 A 的 components 节点下,注册了组件 F。则组件 F 只能用在组件 A 中;不能被用在组件 C 中。

在 vue 项目的 main.js 入口文件中,vue2 通过 Vue.component() 方法,可以注册全局组件。

// 导入全局组件

import Count from '@/components/Count.vue'

// 注册全局组件

Vue.component('MyCount',Count)

vue3 使用

import {createApp} from 'vue'
import App from './App.vue'
import HelloWorld from '@/components/HelloWorld.vue'

let vueApp = createApp(App);
vueApp.component("HelloWorld",HelloWorld)

vueApp.mount('#app')

默认情况下,写在.vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。

导致组件之间样式冲突的根本原因是:

① 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的

② 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素。

为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域,示例代码如下:

<template>
  <div class="left-container" v-data-001>
    <h3 v-data-001>Left组件h3>
    <hr />
  div>
template>
<style lang="less">
.left-container[v-data-001] {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}

/通过中括号“属性选择器”,防止组件的样式冲突问题,因为每一个组件分配的自定义属性是“唯一的”/

h3[v-data-001] {
  color: aqua;
}
style>

style 节点的 scoped 属性为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:

<template>
  <div class="left-container" >
    <h3>Left组件h3>
    <hr />
  div>
template>
<style scoped>
.left-container{
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}

/* style节点的scoped属性,用来自动为每个组件分配唯一的“自定义属性”,并自动为当前组件DOM标签和style样式应用这个自定义属性,防止组件的样式冲突问题

*/
h3 {
  color: aqua;
}
style>
/deep/ 样式穿透

如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样式对子组件生效,可以使用 /deep/ 深度选择器。

<style scoped>
h5 {
  color: pink; /* 不加上/deep/时,生成的选择器格式为h5[data-v-001] */
}
// 当使用第三方组件库的时候,如果有修改第三方组件默认样式的需求,需要用到/deep/
/deep/ h5 {
  color: pink; /* 加上/deep/时,生成的选择器格式为[data-v-001] h5*/
}
style>

14.动态组件与插槽

动态组件指的是动态切换组件的显示与隐藏。vue 提供了一个内置的 组件,专门用来实现动态组件的渲染。

<template>
    <button @click="componentName='HelloWorld'">HelloWorldbutton>
    <button @click="componentName='UserInfo'">UserInfobutton>
    <Component :is="componentName">Component>
template>

<script>
    import HelloWorld from './components/HelloWorld.vue'
    import UserInfo from './components/UserInfo.vue'

    export default {
        name: 'App',
        data() {
            return {
                componentName: "HelloWorld"
            }
        },
        components: {
            HelloWorld, UserInfo
        }
    }
script>

使用 keep-alive 保持状态

默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 组件保持动态组件的状态。

<keep-alive>
    <Component :is="componentName">Component>
keep-alive>

保持状态后组件的created 钩子函数就不会再执行了。所以为了解决这个问题keep-alive 提出两个对应的生命周期函数

当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。

当组件被激活时,会自动触发组件的 activated 生命周期函数。

<template>
    <div>
        userInfo
    div>
template>

<script>
    export default {
        name: "UserInfo",
        created() {
            console.info("UserInfo----created")
        },
        activated() {
            console.info("UserInfo----activated")
        }
    }
script>

插槽(Slot)就是子组件中的提供给父组件使用的一个占位符,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的标签。

父组件

<template>
    <div>
        userInfo
        <user-header>
            <p>父组件给子组件的内容p>
        user-header>
    div>
template>

<script>
    import UserHeader from "@/components/UserHeader";

    export default {
        name: "UserInfo",
        components: {UserHeader},
    }
script>

子组件:

<template>
    <div>
        <p>用户/用户头部p>
        <slot>slot>
    div>
template>

<script>
    export default {
        name: "UserHeader"
    }
script>

具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。在 slot 标签内部可以为插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何内容,则后备内容会生效。

<template>
    <div>
        <p>用户/用户头部p>
        <slot name="s1">后备内容slot>
        <slot name="s2">slot>
    div>
template>

父组件通过 v-slot:[name] 的方式指定到对应的插槽中。

<template>
    <div>
        userInfo
        <user-header>
            <template v-slot:s2><p>父组件给子组件的内容1p>template>
            <template v-slot:s1><p>父组件给子组件的内容2p>template>
        user-header>
    div>
template>
v-slot 可以简写为 #

<user-header>
    <template #s2><p>父组件给子组件的内容1p>template>
    <template #s1><p>父组件给子组件的内容2p>template>
user-header>

如果在封装组件时没有预留任何 插槽,则用户提供的任何自定义内容都会被丢弃。

作用域插槽

在封装组件的过程中,可以为预留的 slot 插槽绑定数据,这种带有 props 数据的 叫做“作用域插槽”。

<template>
    <div>
        <p>用户/用户头部p>
        <slot :user="user">slot>
    div>
template>

<script>
    export default {
        name: "UserHeader",
        data() {
            return {
                user: {id: 1, name: "张三", password: "123"}
            };
        }
    }
script>

父组件可以具名插槽的方式获取里面的数据。这也是插槽给父组件传值的重要方式。

<user-header>
    <template v-slot:default="scope"><p>内容{{scope}}p>template>
user-header>

输出:内容{ “user”: { “id”: 1, “name”: “张三”, “password”: “123” } }

配上结构赋值可以写成这样。

<user-header>
    <template v-slot:default="{user}"><p>内容{{user}}p>template>
user-header>

输出:内容{ “id”: 1, “name”: “张三”, “password”: “123” }

15.父子组件传值

prop 是子组件用来接受父组件传递过来的数据的一个自定义属性。 父组件的数据需要通过 props 把数据传给子组件,子组件需要显式地用 props 选项声明 “prop”

<user-edit :user="user">user-edit>
子组件

<template>
    <div>
        <p>用户信息{{user}}p>
    div>
template>

<script>
    export default {
        name: "UserEdit",
        props:["user"]
    }
script>

vue 规定组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错。要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!

props: ["user"],
data() {
    return {
        u: this.user
    }
}

在声明自定义属性 props 时,可以通过 default 来定义属性的默认值,可以通过 type 来定义属性的值类型。可以通过 required 选项,将属性设置为必填项,强制用户必须传递属性的值。default 与 required 逻辑上不能同时出现。

props: {
    user: {
        type: String,
        default: "张三",
        require: true
    }
}

注意:

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
另外,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

<template>
    <div>
        <p>用户信息{{u}}p>
        <button @click="u.name='哈哈哈'">updatebutton>
    div>
template>

<script>
    export default {
        name: "UserEdit",
        props: ["user"],
        data() {
            return {
                u: this.user
            }
        }
    }
script>

注意:上面的案例由于传递的数据是对象类型,修改 u 的 name 时,父组件的 user 的 name 也会修改,如果是基本数据类型那就不会影响父组件的数据。

子组件向父组件传值使用自定义事件。子组件通过调用 emit 方法给父组件传值。

<template>
    <div>
        <p>count的值:{{count}}p>
        <button @click="add">+1button>
    div>
template>

<script>
    export default {
        data() {
            return {
                count: 0
            }
        },
        methods: {
            add() {
                this.count++;
                this.$emit('numChange', this.count)
            }
        }
    }
script>

当 this.$emit(‘numChange’, this.count) 执行时,便会调用父组件绑定对应的自定义事件 numChange


父组件对应的方法 numberChange 便会执行,该方法的参数就是子组件传过来的数据。

父组件调用子组件方法

方法一:

3.1 在使用子组件时,给子组件加一个 ref 引用

3.2 父组件通过this.$refs即可找到该子组件,也可以操作子组件的方法

this.$refs.page1.子组件方法

方法二:

3.3 通过 $children,可以获取到所有子组件的集合

this.$children[0].某个方法

4、子组件调用父组件方法
4.1 通过 $parent 可以找到父组件,进而调用其方法

this.$parent.父组件方法

日常开发时,我们总会遇到需要父子组件双向绑定的问题,但是考虑到组件的可维护性,vue 中是不允许子组件改变父组件传的 props 值的。那么同时,vue 中也提供了一种解决方案 .sync 修饰符。在此之前,希望你已经知道了 vue 中是如何通过事件的方式实现子组件修改父组件的 data 的。子组件使用 $emit 向父组件发送事件:

this.$emit('update:title', newTitle)

父组件监听这个事件并更新一个本地的数据 title:

<text-document
  :title="title"
  @update:title="val => title = val"
>text-document>

为了方便这种写法,vue 提供了.sync 修饰符,说白了就是一种简写的方式,我们可以将其当作是一种语法糖,比如 v-on: click 可以简写为 @click。而上边父组件的这种写法,换成 sync 的方式就像下边这样:

<text-document
  v-model:title.sync="title"
>text-document>

有没有发现很清晰,而子组件中我们的写法不变,其实这两种写法是等价的,只是一个语法糖而已。

案例:

<template>
  <div>
    <child v-model:name.sync="name">child>
    <button @click="al">点击button>
    <button @click="change">改变button>
  div>
template>
 
<script>
import child from './Child'
export default {
  name: 'list',
  components: {
    child
  },
  data () {
    return {
      name: 'lyh'
    }
  },
  methods: {
    al() {
      alert(this.name)
    },
    change() {
      this.name = '123'
    }
  }
}
script>
child

<template>
  <div>
    <input :value="name" @input="abc" type="text">
  div>
template>
<script>
  export default {
    props: {
      name: {
        type: String,
        required: true
      }
    },
    methods: {
      abc(e) {
        console.log(e.target.value)
        this.$emit('update:name', e.target.value)
      }
    }
  }
script>

16.ref 引用与 nextTick

ref 用来辅助开发者在不依赖于 jQuery 的情况下,获取 DOM 元素或组件的引用。每个 vue 的组件实例上,都包含一个 $refs 对象,里面存储着对应的 DOM 元素或组件的引用。默认情况下,组件的 $refs 指向一个空对象。

ref 用来获取 DOM,操作 DOM

1、给 dom 节点记上 ref 属性,可以理解为给 dom 节点起了个名字。

2、加上 ref 之后,在 $refs 属性中多了这个元素的引用。

3、通过 vue 实例的 $refs 属性拿到这个 dom 元素。

<template>
    <div>
        <h3 ref="myh3">MyRef组件</h3>
        <button @click="getRef">获取 $refs引用</button>
    </div>
</template>
<script>
    export default {
        methods: {
            getRef() {
                this.$refs.myh3.style.color = 'red'
            }
        }
    }
</script>

获取组件,拿到组件中的变量和方法

1、给组件加上 ref 属性,可以理解为给组件起了个名字。

2、加上 ref 之后,在 $refs 属性中多了这个组件的引用。

3、通过 vue 实例的 $refs 属性拿到这个组件的引用,之后可以通过这个引用调用子组件的方法,或者获取子组件的数据。

this.$refs.son2.msg
this.$refs.son2.log1()
this.$nextTick(cb) 方法

组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。

<template>
    <div>
        <input v-if="showInput" ref="input" value="1">
        <button @click="getInputValue">获取输入框值button>
    div>
template>
<script>
    export default {
        data() {
            return {
                showInput: false
            }
        },
        methods: {
            getInputValue() {
                this.showInput = true
                //console.info(this.$refs.input.value)//报错
                this.$nextTick(() => {
                    console.info(this.$refs.input.value)
                })
            }
        }
    }
script>

17.自定义指令

vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。

vue 中的自定义指令分为两类,分别是:1.私有自定义指令。2.全局自定义指令

1.私有自定义指令

在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。mounted 方法第一个参数就是绑定的元素节点。

mounted : 在绑定元素的父组件被挂载后调用。

<template>
    <div>
        <p v-color>HelloWorldp>
    div>

template>

<script>
    export default {
        name: 'HelloWorld',
        directives: {
            color: {
                mounted(element) {
                    console.info(element)
                    element.style.color = "red"
                }
            }
        }
    }
script>

在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值:

<template>
    <div>
        <p v-color="'red'">HelloWorldp>
    div>
template>

<script>
    export default {
        name: 'HelloWorld',
        directives: {
            color: {
                mounted(el, binding) {
                    console.info(el)
                    el.style.color = binding.value
                }
            }
        }
    }
script> 

mounted 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 mounted 函数不会被触发。 updated 函数会在每次 DOM 更新时被调用。示例代码如下:

<template>
    <div>
        <p v-color="color">{{color}}p>
        <button @click="color='red'">redbutton>
        <button @click="color='blue'">bluebutton>
    div>
template>

<script>
    export default {
        name: 'HelloWorld',
        data(){
            return{
                color:"red"
            }
        },
        directives: {
            color: {
                mounted(el, binding) {
                    console.info(el)
                    el.style.color = binding.value
                },
                updated(el, binding){
                    el.style.color =  binding.value
                }
            }
        }
    }
script>

如果 mounted 和updated 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:

directives: {
    color(el, binding) {
        console.info(el)
        el.style.color = binding.value
    }
}

2.全局自定义指令

全局共享的自定义指令需要通过“app.directive()”进行声明

import { createApp } from 'vue'
const app = createApp({})
 
// 注册
app.directive('my-directive', {
  // 指令是具有一组生命周期的钩子:
  // 在绑定元素的 attribute 或事件监听器被应用之前调用
  created() {},
  // 在绑定元素的父组件挂载之前调用
  beforeMount() {},
  // 绑定元素的父组件被挂载时调用
  mounted() {},
  // 在包含组件的 VNode 更新之前调用
  beforeUpdate() {},
  // 在包含组件的 VNode 及其子组件的 VNode 更新之后调用
  updated() {},
  // 在绑定元素的父组件卸载之前调用
  beforeUnmount() {},
  // 卸载绑定元素的父组件时调用
  unmounted() {}
})
 
// 注册 (功能指令)
app.directive('my-directive', () => {
  // 这将被作为 `mounted` 和 `updated` 调用
})
 
// getter, 如果已注册,则返回指令定义
const myDirective = app.directive('my-directive')

如:

import {createApp} from 'vue'
import App from './App.vue'

createApp(App).directive('color', (el, binding) => {
    console.info(el)
    el.style.color = binding.value
}).mount('#app')

这样所有组件皆可以使用这个自定义指令

18.ES 模块化

在ES6没出来之前,模块加载方案主要使用CommonJS和AMD两种,前者用于服务器,后者用于浏览器。ES6在语言标准层面上实现了模块功能,而且使用起来相当简单。

模块功能主要由两个命令构成:export 和 import,export 命令用于规定模块的对外接口,import 用于引入其它模块提供的功能。 一般来说,一个模块对应的就是一个文件,该文件内部的变量外部无法获取,如果你希望外部能够读取到某个变量,就需要使用 export 关键字输出该变量。

// user.js
let name = "张三";
let age = 20;
 
const getSex = (s) => {
    return s === 1 ? "男" : "女";
}

// 通用写法,如果不想了解,就这样写就完事了

export {
    name,
    age,
    getSex
}

上面已经使用 export 命令定义了模块对外的接口后,其它的 JS 文件就可以通过 import 命令加载这个模块。

// main.js
import {name, age, getSex} from './user';
 
console.log(name); // 张三
console.log(age); // 20
console.log(getSex(1)); // 男

import 接受一对大括号,里面指定的是要从其它模块导入的变量名。大括号内的变量名,必须与导入模块对外接口的名称相同。

我们经常需要对加载模块进行重命名,如下写法:

import {name as otherName} from './user'; // 使用as进行重命名
console.log(otherName); // 张三

我们也经常使用到对模块的整体加载,如下写法:

// main.js
import * as user from './user'; // 使用 * 号指定一个对象,所有输出值都加载在这个对象上面
console.log(user.name); // 张三
console.log(user.age); // 20
console.log(user.getSex(1)); // 男

// 需要注意的是 user是静态分析的,不允许运行时改变
// 下面的写法是不允许的

user.height = 180;
user.setOld = function () {};

ES Module导出的是值的引用,导入导出值都指向同一个内存地址,所以导入值会跟随导出值变化。

19.setUp

首先,我们的组件不用写一堆东西了,只需要一个 setup 函数即可。这样做得好处就是,我们可以把很多零碎的东西拆成公共组件,然后交给其他组件去调用。我写 vue 有一个痛苦的点就是很多的东西我想抽离成组件,但是一拆,就得有 data (), methods 等等一堆。因此,有时候就偷懒,懒得拆了。现在好了,可以一个函数就是一个组件,多方便啊!

<template>
    <div class="home">
        这里是一个计数器 >>> <span class="red">{{count}}</span> 
        <button @click="countAdd">{{btnText}}</button>
    </div>
</template>

<script>
    // ref 是 vue 3.0 的一个重大变化,其作用为创建响应式的值
    import {ref} from 'vue'
    // 导出依然是个对象,不过对象中只有一个 setup 函数
    export default {
        setup() {
            // 定义一个不需要改变的数据
            const btnText = '点这个按钮上面的数字会变'
            // 定义一个 count 的响应式数据,并赋值为 0
            const count = ref(0)
            // 定义一个函数,修改 count 的值。
            const countAdd = () => {
                count.value++
            }
            // 导出一些内容给上面的模板区域使用
            return {
                btnText,
                count,
                countAdd
            }
        }
    }
</script>

其次,在 setup 函数中 return 出去的东西,可以在模板区域直接使用,也不必理会 this 这个神奇的东西。

然后就是 ref 这个函数,我们可以从 vue 中引入它,它传入一个值作为参数,返回一个基于该值的 响应式 Ref 对象,该对象中的值一旦被改变和访问,都会被跟踪到,通过修改 count.value 的值,可以触发模板的重新渲染,显示最新的值。

reactive 和 ref 的区别就是,reactive 是处理对象或者数组的。

<template>
    <dl>
        <dt>{{state.name}}</dt>
        <dd>性别:{{state.sex}}</dd>
        <dd>地址:{{state.address}}</dd>
    </dl>
    <button @click="addressChange">更新地址</button>
</template>
<script>
    // reactive 是 vue 3.0 的一个重大变化,其作用为创建响应式的对象或数组
    import {reactive} from 'vue'
    // 导出依然是个对象,不过对象中只有一个 setup 函数
    export default {
        setup() {
            // 定义一个 state 的响应式对象数据,并赋值
            const state = reactive({
                name: 'FungLeo',
                sex: 'boy',
                address: '上海'
            })
            console.log(state)
            // 定义一个函数,修改 state 的值。
            const addressChange = () => {
                state.address += '浦东'
            }
            // 导出一些内容给上面的模板区域使用
            return {
                state,
                addressChange
            }
        }
    }
</script>

生命周期函数

2.0 周期名称 3.0 周期名称 说明
beforeCreate setup 组件创建之前
created setup 组件创建完成
beforeMount onBeforeMount 组件挂载之前
mounted onMounted 组件挂载完成
beforeUpdate onBeforeUpdate 数据更新,虚拟 DOM 打补丁之前
updated onUpdated 数据更新,虚拟 DOM 渲染完成
beforeDestroy onBeforeUnmount 组件销毁之前
destroyed onUnmounted 组件销毁后
还可以像下面这种用法(不推荐)

<template>
    <div class="home">
        这里是一个计数器 >>> <span class="red">{{count}}</span> 
        <button @click="countAdd">点击加数字</button>
    </div>
</template>
<script>
    // 你需要使用到什么生命周期,就引出来什么生命周期
    import {
        onBeforeMount,
        onMounted,
        onBeforeUpdate,
        onUpdated,
        onBeforeUnmount,
        onUnmounted,
        ref
    } from 'vue'

    export default {
        // setup 函数,就相当于 vue 2.0 中的 created
        setup() {
            const count = ref(0)
            // 其他的生命周期都写在这里
            onBeforeMount(() => {
                count.value++
                console.log('onBeforeMount', count.value)
            })
            onMounted(() => {
                count.value++
                console.log('onMounted', count.value)
            })
            // 注意,onBeforeUpdate 和 onUpdated 里面不要修改值,会死循环的哦!
            onBeforeUpdate(() => {
                console.log('onBeforeUpdate', count.value)
            })
            onUpdated(() => {
                console.log('onUpdated', count.value)
            })
            onBeforeUnmount(() => {
                count.value++
                console.log('onBeforeUnmount', count.value)
            })
            onUnmounted(() => {
                count.value++
                console.log('onUnmounted', count.value)
            })
            // 定义一个函数,修改 count 的值。
            const countAdd = () => {
                count.value++
            }
            return {
                count,
                countAdd
            }
        }
    }
</script>

首先,在 vue 3.0 中,生命周期是从 vue 中导出的,我们需要用到哪些,就导出哪些。可能不少看官会认为多次一举,但实则不然。vue 提供这么多的生命周期,有几个是我们常用的?在大多数的组件中,我们用不到生命周期。即便是页面级别的应用,可能用到最多的是 onMounted 即可。当然,那些绑定时间的操作会用到解绑,因此会用到 onUnmounted。其它的生命周期,正常情况下是基本用不到的。所以,通过引入使用的这种设定,可以减少我们的最终编译的项目的体积。而且,这样的引入使用,更加的逻辑清晰。其次,除 setup 之外,其他的生命周期函数,都是在 setup 里面直接书写函数即可。

<template>
    <div class="home">
        这里是一个计数器 >>> <span class="red">{{count}}</span> 
        右边的数字是上面的数字的十倍 >>> <span class="red">{{bigCount}}</span> 
        右边的数字是上面的数字的一百倍 >>> <span class="red">{{computeCount['100x']}}</span> 
        右边的数字是上面的数字的一千倍 >>> <span class="red">{{computeCount['1000x']}}</span> 
        <button @click="countAdd">点这个按钮上面的数字会变</button>
    </div>
</template>

<script>
    // 需要使用计算属性,也需要从 vue 中导出引入
    import {ref, computed} from 'vue'
    // 导出依然是个对象,不过对象中只有一个 setup 函数
    export default {
        setup() {
            // 定义一个 count 的响应式数据,并赋值为 0
            const count = ref(0)
            // 定义一个函数,修改 count 的值。
            const countAdd = () => {
                count.value++
            }
            // 计算属性,使用计算函数并命名,然后在 return 中导出即可
            const bigCount = computed(() => {
                return count.value * 10
            })
            // 计算多个属性,可以通过返回一个对象的方式来实现
            const computeCount = computed(() => {
                return {
                    '100x': count.value * 100,
                    '1000x': count.value * 1000,
                }
            })
            // 导出一些内容给上面的模板区域使用
            return {
                count,
                countAdd,
                bigCount,
                computeCount
            }
        }
    }
</script>

计算属性和生命周期一样,都是从 vue 中导出引入的。我们把计算属性当成一个函数来使用,直接 return 计算结果即可。

20.路由(一)

路由的本质就是一种对应关系,根据不同的 URL 请求,返回对应不同的资源。设定访问路径,并将路径和组件映射起来(就是用于局部刷新页面,不需要请求服务器来切换页面)那么 url 地址和真实的资源之间就有一种对应的关系,就是路由。

使用 idea 创建 vue 项目。注意该项目版本为 vue3 项目。和前面目录结构有些许不同。
Vue简介_第3张图片
安装vue-router 全局配置 ,命令: npm i vue-router (默认安装的是 4 版本适配的是vue3,如果使用的是vue2的话,必须选定3版本 npm i vue-router@3。

在 src 文件夹下创建一个 router 文件夹然后在文件夹下创建 index.js,index.js中写路由的核心代码。

vue3 配置

// 引入一下用到的组件
import About from '../components/About'
import HelloWorld from '../components/HelloWorld'
import { createRouter,createWebHistory } from "vue-router";

const router = createRouter({
    history:createWebHistory(),
    routes:[
        {
            // path是路径
            path: "/about",
            //跳转的组件
            component: About
        },
        {
            // path是路径
            path: "/",
            //跳转的组件
            component: HelloWorld
        },
    ]
})
export default router;
在main.js文件中引入并使用 router 插件

import {createApp} from 'vue'
import App from './App.vue'

import router from './router'

createApp(App).use(router).mount('#app')

vue2 配置:

import VueRouter from 'vue-router'
import SystemIndex from "@/components/SystemIndex";
import UserLogin from "@/components/UserLogin";
import UserManger from "@/components/page/UserManger";
import DictManger from "@/components/page/DictManger";


const router = new VueRouter({
    routes: [{
        path: "/index", component: SystemIndex, children: [//子路由
            {path: "user", component: UserManger}, {path: "dict", component: DictManger}]
    }, {
        path: "/login", component: UserLogin
    }]
})
export default router;

main.js

import Vue from 'vue'
import App from './App.vue'
import router from "@/router";

import VueRouter from 'vue-router'
Vue.use(VueRouter);
Vue.config.productionTip = false

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

主页面 App.vue 加入 router-view 与 router-link

<template>
    <router-link to="/">首页</router-link>
    <router-link to="/about">关于</router-link>
    <router-view></router-view>
</template>

vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。

如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。

const router = new VueRouter({
  mode: 'history',
  routes: [...]
})

当你使用 history 模式时,URL 就像正常的 url,例如 http://yoursite.com/user/id,也好看!

不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好看了。

router-link组件支持用户在具有路由功能的应用中 (点击) 导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 a 标签,可以通过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,链接元素自动设置一个表示激活的 CSS 类名。

router-link 默认携带 class:router-link-active 和 router-link-exact-active,可以用来设置样式当然 class 过长可以通过全局配置来修改:linkActiveClass 和 linkExactActiveClass。

router-link 比起写死的 a href="…"会好一些,理由如下:

1.无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动。

2.在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面。

3.当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写 (基路径) 了。

命名视图

有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 sidebar (侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default。

<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>

一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):

routes: [
  {
    path: '/',
    components: {
    default: Foo,
      a: Bar,
      b: Baz
    }
  }
]

重定向和别名

重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b

const router = new VueRouter({
  routes: [
    { path: '/a', redirect: '/b' }
  ]
})

别名,/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。

const router = new VueRouter({
  routes: [
    { path: '/a', component: A, alias: '/b' }
  ]
})

21.路由(二)

vue3 多级路由

vue3 多级路由用到了children 属性,routes 中的属性成为一级路由,每级路由中 children 属性中的元素给出下级路由,一级路由路径要写斜杠/ 二级路由和多级路由中的路径不写斜杠/。跳转多级路由时,to=“路由路径” 要写全!从一级路由开始写!(如: to=“/home/news”)

可以给路由配置 name 属性,传递参数的时候会用到,添加 name 属性后跳转就不用写全路径,可以直接写路由的 name (:to=“{name:‘message’}”)

有 children 的路由项对应的组件需要添加对应的 router-view 用来展示子路由组件的内容。

routes: [
    {
        path: "/about",
        component: About
    },
    {
        path: "/",
        component: HelloWorld
    },
    {
        path: '/home',
        component: Home,
        children: [
            {
                name: 'news',
                path: 'news', 
                component: News
            },
            {
                name: 'message',
                path: 'message',
                component: Message,
            }
        ]
    }
]

路由传参

路由传参有两种传参方式,第一种 query 传参,第二种 params 传参。

1.query传参有两种写法一个是字符串写法直接写在路径中拼接 ?+参数,第二种时对象写法 ,把路径单独写,数据单独写

<router-link to="/home/message?id=1&title=你好">跳转</router-link>
<router-link :to="{name:'message',query:{id:1,title:'你好'}}">跳转</router-link>
<router-link :to="{path:'/home/message',query:{id:1,title:'你好'}}">跳转</router-link>

获取参数通过 this.$route.query 获取。

<template>
    <div>
        <h3>信息编号:{{ $route.query.id }}</h3>
        <h3>信息标题:{{ $route.query.title }}</h3>
    </div>
</template>

当有多个需要传递的参数时,总在模板中写 $route.query, 肯定是不好的,这时候就需要路由中接收并包装一下。还可以在路由设置中进行获取然后再传递到组件,这样就可以用到 props

{
    name: 'message',
    path: 'message',
    component: Message,
    props($route) {
        return {id: $route.query.id, title: $route.query.title}
    }
}

页面

<template>
    <div>
        <h3>信息编号:{{id}}</h3>
        <h3>信息标题:{{title}}</h3>
    </div>
</template>

<script>
    export default {
        name: "MessagePage",
        props: ["id", "title"],
    }
</script>

2.params传参

params传递参数也有两种方式

第一种直接路径传递参数:在路径中直接加引号内的东西都会当成字符处理,所以给 to 加上冒号让他解析成表达式,但是表达式没有以斜杠开头的,所以加上`模板引号 又变成了字符串,然后用 ${} 配合模板字符串。

<router-link :to="`/home/news/${n.id}/${n.name}`">
{{ n.name }}
</router-link>

第二种方式写成对象形式,这种方式必须写路由 name 不可以用 path 作为路径

<router-link :to="{name:'shownews',params: {id: n.id,name: n.name,}}">
    {{ n.name }}
</router-link>

但是,params 传递需要路由路径中配置占位符不然不知道哪个是路径哪个是数据

name: 'shownews',
// params 写法先在路径中占位 路径后面跟上占位符
path: 'shownews/:id/:name',
component: ShowNews,

获取参数时与 query 类似

<ul>
    <!-- query里面写数据 -->
    <li>编号{{ $route.params.id }}</li>
    <li>姓名{{ $route.params.name }}</li>
</ul>

这里也是如果有很多数据需要用时,可以借助路由包装一下,只需要 props 的值为 true

name: 'shownews',
path: 'shownews/:id/:name',
component: ShowNews,
props: true

获取

<template>
  <ul>
    <li>编号{{ id }}</li>
    <li>姓名{{ name }}</li>
  </ul>
</template>
 
<script>
export default {
  name: "ShowNews",
  props: ["id", "title"],
};
</script>

22.路由(三)

在 script 中控制页面跳转也可以用下面几个方法。

1.this.$router.push() 跳转到不同的 url,但这个方法会向 history 栈添加一个记录,点击后退会返回到上一个页面。和 router-link 作用相同。

2.this.$router.replace()同样是跳转到指定的 url,但是这个方法不会向 history 里面添加新的记录,点击浏览器返回,会跳转到上上一个页面。上一个记录是不存在的。相当于 router-link 里面加了个 :replace=“true”。

3.this.$router.go(n) 相对于当前页面向前或向后跳转多少个页面,类似 window.history.go(n)。n可为正数可为负数。正数返回上一个页面。

4.this.$router.forward() 前进

5.this.$router.back() 后退

push 与 replace 里面接收一个对象{ name or path,query or params}

路由缓存使用 keep-alive 标签,该标签支持两个属性: include(包含的组件缓存生效) 与 exclude(排除的组件不缓存,优先级大于include) 。include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。

<router-view v-slot="{ Component }">
    <keep-alive>
        <component :is="Component">
    </component></keep-alive>
</router-view>

使用路由缓存后,路由组件的 created 方法将不再执行,为了解决这个问题。vue-router 增加了两个钩子函数,用于捕获路由组件的激活状态。

1)activated 路由组件被激活时触发。2)deactivated 路由组件失活时触发。

<script>
    export default {
        name: "AboutView",
        created() {
            console.info("AboutView")
        },
        activated() {
            console.info("activated")
        },
        deactivated() {
            console.info("deactivated")
        }
    }
</script>

路由守卫

作用 : 对路由进行权限管理,必须符合条件才能访问。路由守卫有三种: 全局守卫、独享守卫、组件内守卫

第一种,全局守卫在所有的路由发生改变前都执行,使用路由守卫就不能直接暴露路由实例,需要接收一下,然后调用里面的 beforeEach((to,from,next)=>{}) 有三个参数 to:去哪个路径,from:从哪个路径里来,next:是个函数调用的时候 next() 放行。

router.beforeEach((to, from, next) => {
    console.log('beforeEach', to, from)
    if (to.path === "/about") {
        next({path: '/home'})
    } else {
        next()
    }
})

router.afterEach((to, from) => {
    console.log('afterEach', to, from)
    if (to.path === "/home") {
        document.title = "关于我们" //修改网页的title
    } else {
        document.title = '我的网页'
    }
})
export default router;

第二种,独享守卫

放在需要进行权限设置的路由里面,参数语法和全局一样,当访问这个路径前才执行 beforeEnter(to, from, next)

{
    path: "/",
    component: HelloWorld,
    beforeEnter(to, from, next) {
        console.log('beforeEnter', to, from)
        next()
    }
},

第三种,组件守卫放在组件里和 methods,components同级别 ,必须是通过路由规则进入该组件才可以调用 beforeRouteEnter(),beforeRouteLeave() ,beforeRouteUpdate() 参数和上面的相同。

23.使用 vuex

打开 vuex 官网地址:https://vuex.vuejs.org/
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

我们知道组件之间是独立的,组件之间想要实现通信,我目前知道的就只有 props 选项,但这也仅限于父组件和子组件之间的通信。如果兄弟组件之间想要实现通信呢?嗯…,方法应该有。抛开怎么实现的问题,试想一下,当做中大型项目时,面对一大堆组件之间的通信,还有一大堆的逻辑代码,会不会很抓狂??那为何不把组件之间共享的数据给“拎”出来,在一定的规则下管理这些数据呢? 这就是Vuex的基本思想了。

没有使用 vuex 前我们父子组件数据传递使用如下方式:

父组件

<template>
  <div>
    <h1>用户管理{{id}}</h1>
    <t-dialog1 :show="show" @update="closeDialog"></t-dialog1>
    <button class="showDialog" @click="show=true">显示1</button>   
  </div>
</template>

<script>
  import dialog1 from '@/components/Dialog1.vue'

  export default {
    name: "User",
    components: {
      "t-dialog1": dialog1
    },
    data() {
      return {
        show: true
      }
    }, methods: {
      closeDialog: function (data) {
        this.show = data
      }
    }
  }
</script>

<style scoped="">

</style>

子组件

<template>
  <div class="alert alert-success" v-show="show" @click="closDialog">弹窗内容</div>
</template>

<script>
  export default {
    name: "Dialog1",
    props:["show"],
    methods:{
      closDialog(){
        this.$emit("update",false)
      }
    }
  }
</script>

<style scoped="">
</style>

之所以这么麻烦,是因为父组件可以通过 props 给子组件传递参数,但子组件内却不能直接修改父组件传过来的参数。

这时候,使用 vuex 就可以比较方便的解决这种问题了:

安装 vuex,vue2 使用 vuex@3,vue3 项目使用 vuex@4

npm install vuex@3 --save

在根目录下创建 store 文件夹,在 store 文件夹下创建 index.js 文件,在 index.js 文件中书写代码,与 vue2 中使用的 vuex 相差不大

import { createStore } from 'vuex'

export default createStore({
    state: {
        show: true
    },
})

父组件

<template>
  <div>
    <h1>用户管理{{id}}</h1>
    <t-dialog2></t-dialog2>
    <button class="showDialog" @click="$store.state.show=true">显示2</button>
  </div>
</template>

<script>
  import dialog2 from '@/components/Dialog2.vue'
  export default {
    name: "User",
    components: {
      "t-dialog2": dialog2,
    },
    data() {
      return {        
      }
    }
  }
</script>

<style scoped="">
</style>

子组件:

<template>
  <div class="alert alert-success" v-show="$store.state.show" @click="$store.state.show=false">弹窗内容</div>
</template>

<script>
  export default {
    name: "Dialog2",
    methods:{
    }
  }
</script>

<style scoped="">
</style>

是不是方便了许多,这就是 vuex 最简单的应用,不要被网上其他教程吓到,vuex 原来可以这么简单 !

再然后,在实例化 Vue 对象时加入 store 对象:

import store from './store'
createApp(App).use(store).mount('#app')

完成到这一步 , 上述例子中的 $store.state.show 就可以使用了。

Store 是 Vuex 的一个核心方法,字面上理解为“仓库”的意思。Vuex Store 是响应式的,当 Vue 组件从 store 中读取状态(state 选项)时,若 store 中的状态发生更新时,他会及时的响应给其他的组件(类似双向数据绑定) 而且不能直接改变store的状态。

vuex 有 4 个核心选项:state mutations getters actions

24.Vuex 4个核心选项

Vuex 有4个核心选项:state getters mutations actions

1.state:用来存放组件之间共享的数据。他跟组件的 data 选项类似,只不过 data 选项是用来存放组件的私有数据。

let store = new Vuex.Store({
  state: {
    //存放组件之间共享的数据
    name: "张三"
  },
  mutations: {
    //显式的更改state里的数据
  },
  getters: {
    //过滤state数据
  },
  actions: {
    //
  }
});

一般的,组件获取 state 会里面的值通常在组件的计算属性(computed)获取 state 的数据(因为,计算属性会监控数据变化,一旦发生改变就会响应)

<template>
  <div>
    <h1>{{name}}</h1>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld',
    data() {
      return {}
    },
    computed: {
      name: function () {
        return this.$store.state.name
      }
    }
  }
</script>

2.getters:有时候,我们需要对 state 的数据进行筛选,过滤。这些操作都是在组件的计算属性进行的。如果多个组件需要用到筛选后的数据,那我们就必须到处重复写该计算属性函数;或者将其提取到一个公共的工具函数中,并将公共函数多处导入 ,两者都不太理想。如果把数据筛选完在传到计算属性里就不用那么麻烦了,getters 就是干这个的,你可以把 getters 看成是 store 的计算属性。getters 下的函数接收接收 state 作为第一个参数。

let store = new Vuex.Store({
  state: {
    name: "张三",
    age: 18
  },
  getters: {
    getAge: function (state) {
      return state.age + 1;
    }
  }
});

组件:

<template>
  <div>
    <h1>{{name}}</h1>
    <p>{{age}}</p>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld',
    data() {
      return {}
    },
    computed: {
      name: function () {
        return this.$store.state.name
      },
      age: function () {
        return this.$store.getters.getAge
      }
    }
  }
</script>

3.mutations:在 Vuex store 中,实际改变状态(state) 的唯一方式是通过提交(commit) 一个 mutation。mutations 下的函数接收 state 作为参数,接收一个叫做 payload(载荷)的对象作为第二个参数,这个对象可以是用来记录开发者使用该函数的一些信息,比如说提交了什么,提交的东西是用来干什么的,包含多个字段,也可以是一个简单的类型参数。还有一点需要注意:mutations 方法必须是同步方法!

let store = new Vuex.Store({
  state: {
    name: "张三",
    age: 18,
    num: 1
  },
  mutations: {
    change: function (state, a) {
      console.log(state.num += a);
    }
  },
  getters: {
    getAge: function (state) {
      return state.age;
    }
  }
});

组件:

<template>
  <div>
    <h1>{{name}}</h1>
    <p>{{age}}</p>
    <p @click="changeNum">{{num}}</p>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld',
    data() {
      return {}
    },
    computed: {
      name: function () {
        return this.$store.state.name
      },
      age: function () {
        return this.$store.getters.getAge
      },
      num: function () {
        return this.$store.state.num
      }
    }, methods: {
      changeNum: function () {
        this.$store.commit('change', 10)
      }
    }
  }
</script>

4.actions:由于 mutations 只能处理同步函数,实际项目开发中经常使用异步,于是 actions 出现了。Actions 提交的是 mutations,而不是直接变更状态。也就是说,actions 会通过 mutations,让 mutations 帮他提交数据的变更。Actions 可以包含任意异步操作。ajax、setTimeout、setInterval 不在话下。

let store = new Vuex.Store({
  state: {
    name: "张三",
    age: 18,
    num: 1
  },
  mutations: {
    change: function (state, a) {
      console.log(state.num += a);
    }
  },
  getters: {
    getAge: function (state) {
      return state.age;
    }
  },
  actions: {
    add: function (context, value) {
      setTimeout(function () {
        context.commit('change', value);
      }, 1000)
    }
  }
});

组件里面

<template>
  <div>
    <h1>{{name}}</h1>
    <p @click="changeNumAnsyc">{{age}}</p>
    <p @click="changeNum">{{num}}</p>
  </div>
</template>

<script>
  export default {
    name: 'HelloWorld',
    data() {
      return {}
    },
    computed: {
      name: function () {
        return this.$store.state.name
      },
      age: function () {
        return this.$store.getters.getAge
      },
      num: function () {
        return this.$store.state.num
      }
    }, methods: {
      changeNum: function () {
        this.$store.commit('change', 10)
      },
      changeNumAnsyc: function () {
        this.$store.dispatch('add', 5);
      }
    }
  }
</script>

context:在 actions 的方法中 context 是一个与 store 实例具有相同方法和属性的对象。可以通过 context.state 和 context.getters 来获取 state 和 getters。
dispatch:翻译为‘派发、派遣’的意思,在注解中触发事件时,dispatch 就会通知 actions 参数跟 commit 也是一样的。

25.vuex-persistedstate

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

但是 vuex 的数据页面刷新就会丢失,都是通过手动存入本地缓存中来解决数据刷新丢失的问题,稍显麻烦。有一个插件 vuex-persistedstate 可以解决这个问题。

vuex-persistedstate 使用浏览器的本地存储( local storage )对状态( state )进行持久化。这意味着刷新页面或关闭标签页都不会删除你的数据。

安装

npm i vuex-persistedstate

使用

import { createStore } from 'vuex'
import createPersistedState from "vuex-persistedstate";
import user from './modules/user.js'
import cart from './modules/cart.js'
import category from './modules/category.js'
export default createStore({
    modules: {
        user,
        cart,
        category
    },
    // 配置插件
    plugins: [
        // 默认储存在localstorage
        createPersistedState({
            // 本地储存名
            key: 'ceshivuex',
            // 指定模块
            paths: ['user', 'cart', 'category']
        })
    ],
})

注意:

1.默认是存储在localStorage中

2.key是存储数据的键名

3.paths是存储state中的那些数据,如果是模块下具体的数据需要加上模块名称,如user.token

4.修改state后触发才可以看到本地存储数据的的变化。

<template>
  <div class="app">
    {{ $store.state.user.profile.username }}
    <hr />
    <!-- 修改数据测试是否持久化 -->
    <button @click="$store.commit('user/setUser', { username: 'abcd' })">
      你来点我啊
    </button>
  </div>
</template> 

测试: user模块定义一个mutation在main.js去调用下,观察浏览器application的localStorage下数据。

26.Less基础

CSS 是一门非程序式语言,没有变量、函数、SCOPE( 作用域)等概念。CSS需要书写大量看似没有逻辑的代码,CSS冗余度是比较高的。不方便维护及扩展,不利于复用。

Less ( Leaner Style Sheets 的缩写)是一门CSS扩展语言,也成为CSS预处理器。做为CSS的一种形式的扩展,它并没有减少CSS的功能,而是在现有的CSS语法上,为CSS加入程序式语言的特性。它在CSS的语法基础之上,引入了变量,Mixin (混入),运算以及函数等功能,大大简化了CSS的编写,并且降低了CSS的维护成本,就像它的名称所说的那样,Less可以让我们用更少的代码做更多的事情。

官网地址:https://less.bootcss.com/

在 Node.js 环境中使用 Less :

npm install -g less
lessc styles.less styles.css

在浏览器环境中使用 Less :

<link rel="stylesheet/less" type="text/css" href="styles.less" />
<script src="https://cdn.jsdelivr.net/npm/less@4" ></script>

在 vue 项目中使用

npm install -D less less-loader

在.vue文件中,只需要在 style 节点加上 lang="less"属性,即可可直接使用 less

<style lang="less">
</style>

Less 变量(Variables)

变量是指没有固定的值,可以改变的。因为我们CSS中的一些颜色和数值等经常使用。变量命名规范有:必须有@为前缀、不能包含特殊字符、不能以数字开头和大小写敏感。

index.less
@width: 10px;
@height: @width + 10px;

#header {
  width: @width;
  height: @height;
}

编译为:

#header {
  width: 10px;
  height: 20px;
}

混合(Mixins)

混合(Mixin)是一种将一组属性从一个规则集包含(或混入)到另一个规则集的方法。假设我们定义了一个类(class)如下:

.bordered {
  border-top: dotted 1px black;
  border-bottom: solid 2px black;
}

如果我们希望在其它规则集中使用这些属性呢?没问题,我们只需像下面这样输入所需属性的类(class)名称即可,如下所示:

#menu a {
  color: #111;
  .bordered();
}

.post a {
  color: red;
  .bordered();
}

.bordered 类所包含的属性就将同时出现在 #menu a 和 .post a 中了。(注意,你也可以使用 #ids 作为 mixin 使用。)

嵌套(Nesting)

Less 提供了使用嵌套(nesting)代替层叠或与层叠结合使用的能力。假设我们有以下 CSS 代码:

#header {
  color: black;
}
#header .navigation {
  font-size: 12px;
}
#header .logo {
  width: 300px;
}

用 Less 语言我们可以这样书写代码:

#header {
  color: black;
  .navigation {
    font-size: 12px;
  }
  .logo {
    width: 300px;
  }
}

用 Less 书写的代码更加简洁,并且模仿了 HTML 的组织结构。

你还可以使用此方法将伪选择器(pseudo-selectors)与混合(mixins)一同使用。下面是一个经典的 clearfix 技巧,重写为一个混合(mixin) (& 表示当前选择器的父级):

.clearfix {
  display: block;
  zoom: 1;

  &:after {
    content: " ";
    display: block;
    font-size: 0;
    height: 0;
    clear: both;
    visibility: hidden;
  }
}

运算(Operations)

算术运算符 +、-、*、/ 可以对任何数字、颜色或变量进行运算。如果可能的话,算术运算符在加、减或比较之前会进行单位换算。计算的结果以最左侧操作数的单位类型为准。如果单位换算无效或失去意义,则忽略单位。无效的单位换算例如:px 到 cm 或 rad 到 % 的转换。

// 所有操作数被转换成相同的单位
@conversion-1: 5cm + 10mm; // 结果是 6cm
@conversion-2: 2 - 3cm - 5mm; // 结果是 -1.5cm

// conversion is impossible
@incompatible-units: 2 + 5px - 3cm; // 结果是 4px

// example with variables
@base: 5%;
@filler: @base * 2; // 结果是 10%
@other: @base + @filler; // 结果是 15%

乘法和除法不作转换。因为这两种运算在大多数情况下都没有意义,一个长度乘以一个长度就得到一个区域,而 CSS 是不支持指定区域的。

@base: 2cm * 3mm; // 结果是 6cm

Less 将按数字的原样进行操作,并将为计算结果指定明确的单位类型。

映射(Maps)

从 Less 3.5 版本开始,你还可以将混合(mixins)和规则集(rulesets)作为一组值的映射(map)使用。

#colors() {
  primary: blue;
  secondary: green;
}

.button {
  color: #colors[primary];
  border: 1px solid #colors[secondary];
}

输出符合预期:

.button {
  color: blue;
  border: 1px solid green;
}

导入(Importing)

“导入”的工作方式和你预期的一样。你可以导入一个 .less 文件,此文件中的所有变量就可以全部使用了。如果导入的文件是 .less 扩展名,则可以将扩展名省略掉:

@import "library"; // library.less
@import "typo.css";

27.axios(一)

浏览器页面在向服务器请求数据时有两种方式,最开始是表单提交,提交后返回的是整个页面的数据,页面都会强制刷新一下,这对于用户来讲并不是很友好。有时只是需要修改页面的部分数据,但是从服务器端发送的却是整个页面的数据,十分消耗网络资源。

另一种是 Ajax(Asynchronous JavaScript and XML):异步网络请求。Ajax能够让页面无刷新的请求数据。实现ajax的方式有多种,如 jQuery 封装的 ajax,原生的XMLHttpRequest,以及 axios。

Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js。Axios 使用简单,包尺寸小且提供了易于扩展的接口,本质上还是对原生XMLHttpRequest的封装,可用于浏览器和nodejs的HTTP客户端,只不过它是基于Promise的,符合最新的ES规范。

安装 Axios

npm install axios

在需要发请求的组件导入

import axios from "axios";

发起一个 GET 请求

// 向给定ID的用户发起请求
axios.get('/user?ID=12345')
  .then(function (response) {
    // 处理成功情况
    console.log(response);
  })
  .catch(function (error) {
    // 处理错误情况
    console.log(error);
  })
  .then(function () {
    // 总是会执行
  });

// 上述请求也可以按以下方式完成(可选)

axios.get('/user', {
    params: {
      ID: 12345
    }
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  })
  .then(function () {
    // 总是会执行
  });  

// 支持async/await用法
async function getUser() {
  try {
    const response = await axios.get('/user?ID=12345');
    console.log(response);
  } catch (error) {
    console.error(error);
  }
}

注意: 由于async/await 是ECMAScript 2017中的一部分,而且在IE和一些旧的浏览器中不支持,所以使用时务必要小心。

发起一个 POST 请求

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });

可以向 axios 传递相关配置来创建请求 axios(config)

axios({
  method: 'post',
  url: '/user/12345',
  data: {
    firstName: 'Fred',
    lastName: 'Flintstone'
  }
});

axios 为了方便起见,已经为所有支持的请求方法提供了别名。

axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])

注意:在使用别名方法时, url、method、data 这些属性都不必在配置中指定。

完整的请求配置

这些是创建请求时可以用的配置选项。只有 url 是必需的。如果没有指定 method,请求将默认使用 GET 方法。

{
  // `url` 是用于请求的服务器 URL
  url: '/user',

  // `method` 是创建请求时使用的方法
  method: 'get', // 默认值

  // `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
  // 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
  baseURL: 'https://some-domain.com/api/',

  // `transformRequest` 允许在向服务器发送前,修改请求数据
  // 它只能用于 'PUT', 'POST' 和 'PATCH' 这几个请求方法
  // 数组中最后一个函数必须返回一个字符串, 一个Buffer实例,ArrayBuffer,FormData,或 Stream
  // 你可以修改请求头。
  transformRequest: [function (data, headers) {
    // 对发送的 data 进行任意转换处理

    return data;
  }],

  // `transformResponse` 在传递给 then/catch 前,允许修改响应数据
  transformResponse: [function (data) {
    // 对接收的 data 进行任意转换处理

    return data;
  }],

  // 自定义请求头
  headers: {'X-Requested-With': 'XMLHttpRequest'},

  // `params` 是与请求一起发送的 URL 参数
  // 必须是一个简单对象或 URLSearchParams 对象
  params: {
    ID: 12345
  },

  // `paramsSerializer`是可选方法,主要用于序列化`params`
  // (e.g. https://www.npmjs.com/package/qs, http://api.jquery.com/jquery.param/)
  paramsSerializer: function (params) {
    return Qs.stringify(params, {arrayFormat: 'brackets'})
  },

  // `data` 是作为请求体被发送的数据
  // 仅适用 'PUT', 'POST', 'DELETE 和 'PATCH' 请求方法
  // 在没有设置 `transformRequest` 时,则必须是以下类型之一:
  // - string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams
  // - 浏览器专属: FormData, File, Blob
  // - Node 专属: Stream, Buffer
  data: {
    firstName: 'Fred'
  },
  
  // 发送请求体数据的可选语法
  // 请求方式 post
  // 只有 value 会被发送,key 则不会
  data: 'Country=Brasil&City=Belo Horizonte',

  // `timeout` 指定请求超时的毫秒数。
  // 如果请求时间超过 `timeout` 的值,则请求会被中断
  timeout: 1000, // 默认值是 `0` (永不超时)

  // `withCredentials` 表示跨域请求时是否需要使用凭证
  withCredentials: false, // default

  // `adapter` 允许自定义处理请求,这使测试更加容易。
  // 返回一个 promise 并提供一个有效的响应 (参见 lib/adapters/README.md)。
  adapter: function (config) {
    /* ... */
  },

  // `auth` HTTP Basic Auth
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  // `responseType` 表示浏览器将要响应的数据类型
  // 选项包括: 'arraybuffer', 'document', 'json', 'text', 'stream'
  // 浏览器专属:'blob'
  responseType: 'json', // 默认值

  // `responseEncoding` 表示用于解码响应的编码 (Node.js 专属)
  // 注意:忽略 `responseType` 的值为 'stream',或者是客户端请求
  // Note: Ignored for `responseType` of 'stream' or client-side requests
  responseEncoding: 'utf8', // 默认值

  // `xsrfCookieName` 是 xsrf token 的值,被用作 cookie 的名称
  xsrfCookieName: 'XSRF-TOKEN', // 默认值

  // `xsrfHeaderName` 是带有 xsrf token 值的http 请求头名称
  xsrfHeaderName: 'X-XSRF-TOKEN', // 默认值

  // `onUploadProgress` 允许为上传处理进度事件
  // 浏览器专属
  onUploadProgress: function (progressEvent) {
    // 处理原生进度事件
  },

  // `onDownloadProgress` 允许为下载处理进度事件
  // 浏览器专属
  onDownloadProgress: function (progressEvent) {
    // 处理原生进度事件
  },

  // `maxContentLength` 定义了node.js中允许的HTTP响应内容的最大字节数
  maxContentLength: 2000,

  // `maxBodyLength`(仅Node)定义允许的http请求内容的最大字节数
  maxBodyLength: 2000,

  // `validateStatus` 定义了对于给定的 HTTP状态码是 resolve 还是 reject promise。
  // 如果 `validateStatus` 返回 `true` (或者设置为 `null` 或 `undefined`),
  // 则promise 将会 resolved,否则是 rejected。
  validateStatus: function (status) {
    return status >= 200 && status < 300; // 默认值
  },

  // `maxRedirects` 定义了在node.js中要遵循的最大重定向数。
  // 如果设置为0,则不会进行重定向
  maxRedirects: 5, // 默认值

  // `socketPath` 定义了在node.js中使用的UNIX套接字。
  // e.g. '/var/run/docker.sock' 发送请求到 docker 守护进程。
  // 只能指定 `socketPath` 或 `proxy` 。
  // 若都指定,这使用 `socketPath` 。
  socketPath: null, // default

  // `httpAgent` and `httpsAgent` define a custom agent to be used when performing http
  // and https requests, respectively, in node.js. This allows options to be added like
  // `keepAlive` that are not enabled by default.
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // `proxy` 定义了代理服务器的主机名,端口和协议。
  // 您可以使用常规的`http_proxy` 和 `https_proxy` 环境变量。
  // 使用 `false` 可以禁用代理功能,同时环境变量也会被忽略。
  // `auth`表示应使用HTTP Basic auth连接到代理,并且提供凭据。
  // 这将设置一个 `Proxy-Authorization` 请求头,它会覆盖 `headers` 中已存在的自定义 `Proxy-Authorization` 请求头。
  // 如果代理服务器使用 HTTPS,则必须设置 protocol 为`https`
  proxy: {
    protocol: 'https',
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // see https://axios-http.com/zh/docs/cancellation
  cancelToken: new CancelToken(function (cancel) {
  }),

  // `decompress` indicates whether or not the response body should be decompressed 
  // automatically. If set to `true` will also remove the 'content-encoding' header 
  // from the responses objects of all decompressed responses
  // - Node only (XHR cannot turn off decompression)
  decompress: true // 默认值

}

响应结构

一个请求的响应包含以下信息。

{
  // `data` 由服务器提供的响应
  data: {},

  // `status` 来自服务器响应的 HTTP 状态码
  status: 200,

  // `statusText` 来自服务器响应的 HTTP 状态信息
  statusText: 'OK',

  // `headers` 是服务器响应头
  // 所有的 header 名称都是小写,而且可以使用方括号语法访问
  // 例如: `response.headers['content-type']`
  headers: {},

  // `config` 是 `axios` 请求的配置信息
  config: {},

  // `request` 是生成此响应的请求
  // 在node.js中它是最后一个ClientRequest实例 (in redirects),
  // 在浏览器中则是 XMLHttpRequest 实例
  request: {}
}

28.axios(二)

请求体编码

默认情况下,axios将 JavaScript 对象序列化为 JSON 。 要以 application/x-www-form-urlencoded (表单)格式发送数据,可以使用以下选项之一。

1.在浏览器中,可以使用URLSearchParams API,如下所示:

const params = new URLSearchParams();
params.append('param1', 'value1');
params.append('param2', 'value2');
axios.post('/foo', params);

请注意,不是所有的浏览器(参见 caniuse.com)都支持 URLSearchParams,使用前确保浏览器支持 URLSearchParams 对象。

2.使用qs 库编码数据:

import qs from 'qs';
const data = { 'bar': 123 };
const options = {
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  data: qs.stringify(data),
  url,
};
axios(options);

3.使用浏览器中的 FormData 对象。

const form = new FormData();
form.append('name', 'my value');
form.append('id', 1321);
axios.post('https://example.com', form)

默认配置

默认配置您可以指定默认配置,它将作用于每个请求。

axios.defaults.baseURL = 'https://api.example.com';

配置将会按优先级进行合并。它的顺序是:在lib/defaults.js中找到的库默认值,然后是实例的 defaults 属性,最后是请求的 config 参数。后面的优先级要高于前面的。

axios.defaults.baseURL = 'https://api.example.com';
const form = new FormData();
form.append('name', 'my value');
form.append('id', 1321);
axios.post('example.com', form, {baseURL: 'https://www.baidu.com'})

拦截器

在请求或响应被 then 或 catch 处理前拦截它们。拦截器分为请求拦截器和响应拦截器。

请求拦截器:用于拦截请求,自定义做一个逻辑后再把请求发送,可以用于配置公用的逻辑,就不用每个请求都配一遍。如在请求头中添加参数:

config.headers[‘userId’] = localStorage.getItem(‘userId’) // 在请求头中添加参数

响应拦截器:用于拦截响应,做一些处理后再出发响应回调。

// 添加请求拦截器
axios.interceptors.request.use(function (config) {
    // 在发送请求之前做些什么
    return config;
  }, function (error) {
    // 对请求错误做些什么
    return Promise.reject(error);
  });

// 添加响应拦截器
axios.interceptors.response.use(function (response) {
    // 2xx 范围内的状态码都会触发该函数。
    // 对响应数据做点什么
    return response;
  }, function (error) {
    // 超出 2xx 范围的状态码都会触发该函数。
    // 对响应错误做点什么
    return Promise.reject(error);
  });
如果你稍后需要移除拦截器,可以这样:

const myInterceptor = axios.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);

29.swiper

Swiper是纯 javascript 打造的滑动特效插件,面向手机、平板电脑等移动终端。在 wide 中 Swiper 只能用于无序列表和有序列表。

Swiper 的每个展示块(屏)为一个 slide,slide 中放置图片或文字等展示的内容,全部 slide 排成一行(或多行)包含在包装器 wrapper 中,而总容器 container 又包裹着wrapper 和箭头按钮控件 navigation 以及分页器控件 pagination。

当手指(或鼠标)触摸滑动 Swiper 时,Swiper 在浏览器每一帧通过计算滑动的距离差对 wrapper 进行位移(transform)从而产生拖动的效果。在手指(或鼠标)释放时,计算下一个 slide 的起始位置对 wrapper 设置位移动画(transition),从而产生切换动画效果。

<template>
    <div>
        <swiper>
            <swiper-slide>Slide 1</swiper-slide>
            <swiper-slide>Slide 2</swiper-slide>
            <swiper-slide>Slide 3</swiper-slide>
        </swiper>
    </div>
</template>

<script>
    import {Swiper, SwiperSlide} from 'swiper/vue';
    import 'swiper/css';

    export default {
        components: {
            Swiper,
            SwiperSlide,
        },
    }
</script>

将 slide 里面的内容换成图片或其他布局内容,并设置 swiper 的宽高。

<template>
    <div>
        <swiper class="swiper">
            <swiper-slide><img src="../assets/C001.png"></swiper-slide>
            <swiper-slide><img src="../assets/C002.png"></swiper-slide>
            <swiper-slide><img src="../assets/C003.png"></swiper-slide>
            <swiper-slide><img src="../assets/C004.png"></swiper-slide>
        </swiper>
    </div>
</template>

<script>
    import {Swiper, SwiperSlide} from 'swiper/vue';
    import 'swiper/css';

    export default {
        components: {
            Swiper,
            SwiperSlide,
        }
    }
</script>

<style scoped>
    .swiper {
        width: 300px;
        height: 200px;
        background-color: antiquewhite;
    }
</style>

Navigation - 导航模块、Pagination - 分布模块、Scrollbar - 滚动条模块 简单使用。默认情况下,Swiper Vue。js使用Swiper的核心版本(没有任何附加模块)。如果要使用导航、分页和其他模块,必须先安装它们。

<template>
    <div>
        <swiper class="swiper"
                :scrollbar="{ draggable: true }"
                :modules="modules"
                navigation="navigation"
                :pagination="{ clickable: true }">
            <swiper-slide><img src="../assets/C001.png"></swiper-slide>
            <swiper-slide><img src="../assets/C002.png"></swiper-slide>
            <swiper-slide><img src="../assets/C003.png"></swiper-slide>
            <swiper-slide><img src="../assets/C004.png"></swiper-slide>
        </swiper>
    </div>
</template>

<script>
    import {Swiper, SwiperSlide} from 'swiper/vue';
    import {Navigation, Pagination, Scrollbar} from 'swiper';
    import 'swiper/css';
    import 'swiper/css/navigation';
    import 'swiper/css/pagination';
    import 'swiper/css/scrollbar';

    export default {
        components: {
            Swiper,
            SwiperSlide,
        }, data() {
            return {
                modules: [Navigation, Pagination, Scrollbar],
            }
        }
    }
</script>

<style scoped>
    .swiper {
        width: 300px;
        height: 200px;
        background-color: antiquewhite;
    }
</style>

效果如下:

swiper 上常用事件 slideChange ,可以获取轮播滑动到何处。

<template>
    <div>
        <swiper class="swiper"
                :scrollbar="{ draggable: true }"
                :modules="modules"
                navigation="navigation"
                :pagination="{ clickable: true }"
                @slideChange="onSlideChange">
            <swiper-slide><img src="../assets/C001.png"></swiper-slide>
            <swiper-slide><img src="../assets/C002.png"></swiper-slide>
            <swiper-slide><img src="../assets/C003.png"></swiper-slide>
            <swiper-slide><img src="../assets/C004.png"></swiper-slide>
        </swiper>
    </div>
</template>

<script>
    import {Swiper, SwiperSlide} from 'swiper/vue';
    import {Navigation, Pagination, Scrollbar} from 'swiper';
    import 'swiper/css';
    import 'swiper/css/navigation';
    import 'swiper/css/pagination';
    import 'swiper/css/scrollbar';

    export default {
        components: {
            Swiper,
            SwiperSlide,
        }, data() {
            return {
                modules: [Navigation, Pagination, Scrollbar],
            }
        }, methods: {
            onSlideChange({activeIndex}) {
                console.log(activeIndex);
            }
        }
    }
</script>

<style scoped>
    .swiper {
        width: 300px;
        height: 200px;
        background-color: antiquewhite;
    }
</style>

30.整合 elementUI

Element,一套为开发者、设计师和产品经理准备的基于 Vue 的桌面端组件库。

安装 element ui

npm install element-plus --save 

在main.js中声明element-ui

import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
const app = createApp(App)
app.use(ElementPlus)

在helloworld.vue中加入

<el-button type="primary">element 按钮</el-button>

安装软件时可能出现安装版本不兼容问题。安装时使用

npm install --save <包名>@版本

如果不知道该包有哪些版本,可以使用

npm view <包名> versions --json

31.Vue底层实现原理

Vue是一个典型的MVVM框架,模型(Model)只是普通的JavaScript对象,修改它则视图(View)会自动更新。这种设计让状态过来变得非常简单而直观。那么Vue是如何把模型和视图建立起关联的呢?

实现原理概述
Vue简介_第4张图片

这是前言提到的文章里的代码,一段典型的体现了Vue特点的代码:

Vue实现这种数据双向绑定的效果,需要三大模块:

1.Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者

2.Compile:对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数

3.Watcher:作为连接Observer和Complie的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

Observer(Observer class that is attached to each observed object. Once attached, the observer converts the target object’s property keys into getter/setters that collect dependencies and dispatch updates.)

Observer的核心是通过Object.defineProperty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是 Watcher。

Watcher(A watcher parses an expression, collects dependencies, and fires callback when the expression value changes. This is used for both the $watch() api and directives.)

Watcher订阅者作为Observer和Complie之间通信的桥梁,主要做的事情是:

1.在自身实例化时往属性订阅者(dep)里面添加自己

2.自身必须有一个update()方法

3.待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调

Compile

Compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。

你可能感兴趣的:(vue.js,前端,javascript)