一、Vue介绍
1. 安装
1. 安装node.js
2. 终端执行命令
2.1 npm install -g cnpm --registry=https://registry.npm.taobao.or
2.2 cnpm install -g @vue/cli
3. 运行vue项目 npm run serve
2. vue项目结构
-
-
①:assets:存放项目中需要用到的资源文件,css、js、images 等。
②:componets: 存放 vue 开发中一些公共组件:例如项目初始的 header.vue、footer.vue 就是公共组件。
③:router:vue 路由的配置文件。
④:views:存放页面文件
⑤:app.vue:根组件
⑥:main.js:项目的入口文件,定义了 vue 实例,并引入根组件 app.vue,将其挂载到 index.html 中 id 为‘app’的节点上。
二、Vue双向绑定
1. 声明式渲染
-
每一个 Vue 文件由三部分组成,
template
、script
、style
,它们分别对应HTML
、JavaScript
、CSS
。 -
在
template
里面只允许有一个块状元素,通常情况下在template
里面写的都是div
。template
标签内只能有一个块标签,其他所有的标签都在这个块标签内 -
<template> <h2>{{title}}</h2> <ul class="list"> <li>{{todoList[0]}}</li> </ul> </template> <script> // export default是固定格式,不需要纠结 export default { // 模块的名字 name: "app", // 页面中数据存放的地方 data() { return { title: "优课达--学的比别人好一点", todoList: [ "完成HTML标签学习", "完成CSS文字样式学习", "完成CSS盒模型学习", "完成Flex布局学习", "完成JavaScript入门学习" ] }; } }; </script> <style lang="scss" scope> </style> //使用sass语法,需要在style标签上添加lang="scss" //scope 你可以理解为锁,将 css 代码锁在本文件内,只在本文件内有用
2. 处理用户输入
-
双向绑定v-model:
若
<input type="text" v-model="message" />
,则改变message
变量的值,input
标签内的值会变化,改变input
标签内的值,message
变量的值也会改变。
3. 处理用户事件
-
事件绑定:v-on:
<button v-on:click="add">按钮</button>
or<button @click="add">按钮</button>
-
methods(方法):
<script> export default{ name:"app", methods:{ alertFn:function(){ alert("Hello World"); } } } </script>
-
事件修饰符:
-
阻止冒泡事件
<div @click.stop="fn2"></div>
-
捕获事件
<div class="div2" @click.capture="fn2"></div>
-
阻止默认事件
<div class="div2" @click.prevent="fn2"></div>
-
4. 监听数据变化
-
侦听器watch:
<script> export default { name: "app", watch: { firstName: { handler: function (newName, oldName) { // 第一个参数为新值,第二个参数为旧值,不能调换顺序,newName等于handler,对newName进行操作handler也会变化 this.fullName = newName + " " + this.lastName; }, immediate: true } } }; </script>
-
侦听器侦听的是
data
里面的变量,当变量发生变化的时候,侦听器开始运行 -
侦听器的名称一定要跟被侦听的变量名一致
-
当我们侦听的值没有发生改变的时候,是不会触发侦听器的,并且,页面第一次渲染的时候也不会触发侦听器。当
immediate
属性的值为true
时,不论数据是否变化,页面刷新以后,handler
方法就会运行。
三、Vue Template
1. HTML 属性渲染语法
-
属性动态绑定v-bind:
-
v-bind 的简写模式,即冒号 :
-
如:
<img src="#" :alt="变量" />
-
2. 模板中使用表达式
- 差值表达式中不仅可以写一个变量,还可以进行简单的计算,可以使用三元表达式进行判断,在模版中还可以使用 js 内置方法。不要在模版中滥用表达式,尤其是 js 的内置方法。否则会使 html 代码变得臃肿。
3. 条件渲染语句
-
v-if:当条件满足的时候,会显示标签内的内容。
<p v-if="isShow()">{{ message }}</p>
-
v-else:不符合if的时候,就显示else中的内容。
<p v-if="isShow()">{{ message }}</p> <p v-else>{{ defaultMessage }}</p>
-
v-else-if:当条件不止一个的时候,就可以用到v-else-if。
<p v-if="questions[0].type==='PICK'">{{ questions[0].content }}</p> <p v-else-if="questions[1].type==='MULT'">{{ questions[1].content }}</p> <p v-else>题目还没有加载进来...</p> //当前两个都不满足条件时第三个才起作用
-
v-show:v-show的用法和v-if是一样的,即当条件满足,就会显示标签中的内容
-
区别:
-
v-show指令只是将标签的display属性设置为none
-
v-if指令,如果不满足条件,则此标签在dom中根本不存在。
-
-
用法:当标签出现以后就不会再次消失,或者消失/出现的频率不高,就用v-if。 当标签会被频繁的切换,从消失到显示,那么就用v-show。
-
不用太纠结这个,因为开发中大多数情况下都会用v-if。只不过有些人比较严谨,会考虑切换开销和渲染开销。
-
4. 列表渲染语句
-
v-for:
-
循环渲染数字
<li v-for="item in 5" :key="item">{{ item }}</li>
-
循环渲染数组
<li v-for="(item,index) in nameList" :key="items">{{ item }}</li>
-
循环渲染对象
<li v-for="(value,key,index) in book" :key="key"></li>
-
遍历数组中的对象
<li v-for="(item,index) in books" :key="item"> {{ index+1 }}----{{ item.title }}----{{ item.author }}----{{ item.publishedTime }} </li>
-
key属性可以用来提升v-for渲染的效率,是每个元素的唯一标识,一一对应,不可更改
-
四、Vue 动态 Class、Style
1. 计算属性
-
<script> export default { name: "app", data:function(){ return { message:"优课达--学的比别人好一点~" } } // 计算属性 computed:{ reverseMessage:function(){ return this.message.split('').reverse().join('') } } }; </script>
-
计算属性的两个性质:依赖和缓存。
-
**依赖:**案例中
message
就是计算属性的依赖 -
**缓存:**上一次计算得到的值
-
当
message
发生改变(即依赖变化),reverseMessage
计算属性会重新计算,然后返回计算结果;message
不发生改变(即依赖不变化),reverseMessage
计算属性会返回缓存的值,而不会重新计算。
-
-
计算属性避免了不必要的代码执行,性能更优
-
计算属性一定要有返回值,但方法不一定要有返回值
2. 动态class
-
动态样式绑定
-
<div class="base" v-bind:class="{ active: isActive,base:true}"></div>
-
<div v-bind:class="['red-style', 'font-style',{'redbg':isChoosed}]"></div>
-
<div v-bind:class="['red-style', 'font-style',isChoosed ? 'redbg' : 'bluebg']" ></div>
-
active
是类名,对应CSS
样式中的类名;如果是带连字符的类名base-active
,就需要用单引号引起来 -
isActive
是boolean
类型的值,决定是否应用该类名。可以是变量、方法、表达式
-
3. 动态style
-
<div :style="{background:'red','font-weight':700,'font-size':'20px'}"></div>
-
<div :style="flexStyle"></div> data:function(){ return { flexStyle: { display: 'flex', flexDirection: 'column', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'no-wrap', }, } }
-
data() { return { fontStyle: { color: 'red', fontSize: '33px' }, boxStyle:{width: '200px', height: '200px', border: `1px solid black`} }; }, <div :style="[fontStyle,boxStyle]">这里是文字</div>
-
<div :style="[fontStyle, isActive ? boxStyle : colorBox]">这里是文字</div>
五、Vue组件
1. 自定义组件
-
通常一个应用会以一棵嵌套的组件树的形式来组织:
-
组件的局部注册
-
重复使用的组件间的 data 是相互独立的。
2. 组件单向数据流
-
当父组件给子组件的 prop 传递一个值的时候,这个值就变成了子组件实例的一个属性。
//在父组件中 <template> <div id="app"> <!-- 注意!title1 和 title2 是父组件的 data 中定义的数据,title 则是子组件中接收数据时的变量名 --> <HelloVue :title="title1"></HelloVue> <HelloVue :title="title2"></HelloVue> </div> </template> //在子组件中 <script> export default { name: 'HelloVue', // 第一步:在 prop 属性中接收 title props: ['title'] }; </script> //带类型声明 props: { title: String, // 多类型 likes: [String, Number], // 带有默认值 isPublished: { type: Boolean, default: true }, // 必填 commentIds: { type: Array, required: true }, author: Object, callback: Function, contactsPromise: Promise }
-
单向数据流指的是父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。子组件不能直接修改父组件通过 prop 传递过来的数据
-
若需要对传到子组件中的数据进行处理,有两种方法:
-
计算属性
props: ['initialTitle'], computed: { normalizedTitle: function () { // 对传入的 initialTitle 进行去空格、字母小写化处理 return this.initialTitle.trim().toLowerCase() } }
-
prop 传入的数据作为本地数据使用
props: ['initialTitle'], data: function () { return { // 要读取 prop 里的 initialTitle,要在前面加 “this.” // 将传入的 initialTitle 作为本地属性 title 的初始值 title: this.initialTitle } }
-
3. 自定义组件绑定原生事件
-
事件修饰符
-
当给父组件中的子组件标签绑定事件时,只有子组件的事件会触发,现在要让父组件的子组件标签的事件也能被执行,我们可以添加
.native
修饰符:@click.native="print(article)"
-
.stop 阻止冒泡 .prevent 阻止标签默认行为 .capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 .self 只当在 event.target 是当前元素自身时触发处理函数 .once 事件将只会触发一次 .passive 告诉浏览器你不想阻止事件的默认行为
-
-
按键修饰符
-
回车键监听:
<button @keyup.enter="print(article)">按回车键执行 print</button>
因为回车键(enter 键)的 ASCII 码是 13,所以也可以这样写:
<button @keyup.13="print(article)">按回车键执行 print</button>
-
4. 自定义事件
-
-
给子组件 Article.vue 绑定自定义事件:
在 App.vue 中用
v-on:upVote="handleLikes"
给 Article.vue 绑定自定义事件 -
在 Article.vue 中调用自定义事件 “upVote” :
<button @click="$emit('upVote')">点赞</button>
-
如果在点赞的同时还有其他要执行的代码可以这样写:
methods: { childEvent: function() { // 调用自定义事件 upVote,这里的第二个参数最后会传到父组件中的 handleLikes 方法里 this.$emit('upVote', this.article); // do other things } }
<button @click="childEvent">点赞</button>
-
-
自定义事件的参数:
- prop 可以完成父组件到子组件的数据传递,自定义事件则可以帮我们完成子组件到父组件的数据传递(通过方法参数)。
-
自定义事件中的双向绑定
-
<MyCount class="count" :count.sync="count"></MyCount>
-
<div class="my-count"> <button @click="$emit('update:count', count+1)">加一</button> {{ count }} </div> //子组件 MyCount.vue 中用 update:count 的模式触发事件,把 count+1 赋值给 count:
-
虽然 count 是定义在 App.vue 里的,但是通过双向绑定,我们在子组件中改变 count 值,App.vue 里的 count 值也会有相同的变化。
-
5. 组件函数调用
-
调用子组件中的方法
-
给要访问的子组件添加
ref
属性<template> <Modal ref="modal"></Modal> </template>
-
通过
this.$refs.modal
来访问自定义组件 Modal.vue。 -
// 调用子组件中的 show 方法 this.$refs.modal.show();
-
-
ref 访问子元素
-
<template> <div id="app"> <input ref="input" type="text" /> <button @click="focusInput">点击使输入框获取焦点</button> </div> </template> <script> export default { name: 'app', methods: { focusInput() { // this.$refs.input 访问输入框元素,并调用 focus() 方法使其获取焦点 this.$refs.input.focus(); } } } </script>
-
6. 组件 slot 入门
-
在子组件 Modal.vue 中用
slot
标签预留一个位置,slot
标签中的内容是后备内容,也可以为空: -
后备内容:当父组件不在插槽里添加内容时,插槽显示的内容。
-
在父组件中使用子组件,并给插槽加入个性化内容:
<Modal :visible.sync="visible">个性化内容</Modal>
7. 组件 slot 进阶
-
<slot>
元素有一个特殊的属性:name
。这个属性可以用来定义额外的插槽:<div class="modal" v-if="visible"> <div class="modal-content"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> </div>
-
没有写 name 属性,但其实它会自动带隐含的名字“default”,也就是我们所说的“匿名插槽”,而带有 name 属性的插槽,我们称为“具名插槽”。
-
在向具名插槽提供内容的时候,我们可以在一个
<template>
元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:<Modal :visible.sync="visible"> <template v-slot:header> <h1>Modal title</h1> </template> <template> <div>main content</div> <div>main content</div> </template> <template v-slot:footer> <p>Modal footer</p> </template> </Modal>
六、Vue-router
1. 安装、配置 Router
-
配置路由:在router/index.js中进行如下配置
// 0. 导入 Vue 和 VueRouter,要调用 Vue.use(VueRouter) import Vue from 'vue' import VueRouter from 'vue-router' Vue.use(VueRouter) // 1. 定义 (路由) 组件。 // 可以从其他文件 import 进来 import Foo from "./views/Foo.vue"; import Bar from "./views/Bar.vue"; // 2. 定义路由 // 每个路由应该映射一个组件。 其中"component" 可以是 // 通过 Vue.extend() 创建的组件构造器, // 或者,只是一个组件配置对象。 const routes = [ { path: '/foo', component: Foo }, { path: '/bar', component: Bar } ] // 3. 创建 router 实例,然后传 `routes` 配置 export default new VueRouter({ mode: 'history', routes // (缩写) 相当于 routes: routes }) // 4. 在 main.js 中创建和挂载根实例
-
路由的使用:
-
在 App.vue 中我们使用
<router-link>
组件来导航:<template> <div id="app"> <h1>Hello App!</h1> <p> <!-- 使用 router-link 组件来导航. --> <!-- 通过传入 `to` 属性指定链接. --> <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 --> <router-link to="/foo">Go to Foo</router-link> <router-link to="/bar">Go to Bar</router-link> </p> <!-- 路由出口 --> <!-- 路由匹配到的组件将渲染在这里 --> <!-- App.vue 中的 <router-view></router-view> 是路由的最高级出口 --> <router-view></router-view> </div> </template>
-
2. 添加路由
-
const routes = [ { path: '/foo', name: 'fooName', component: () => import('./views/Foo.vue') } ];
-
通过命名跳转:
<!-- to 的值是一个对象而不是字符串,所以要在 to 前加 : --> <router-link :to="{name: 'fooName'}">Go to Foo</router-link>
3. 路由布局管理
-
定义嵌套路由:
const routes = [ { path: '/album', component: Album, // children 属性可以用来配置下一级路由(子路由) children: [ { path: '', component: Empty }, { path: 'list', component: List }, { path: 'add', component: Add } ] } ];
-
App.vue 中的
<router-view>
对应routes
里的第一层路由,Album.vue 组件中的<router-view>
对应routes
里的第二层路由。
4. 动态路由
-
动态路由即符合某种模式的多个路径映射到同一个组件
-
import User from "./views/User.vue"; const routes = [ // id 就是路径参数 { path: '/user/:id', component: User } ]
-
id 为路径参数,一个“路径参数”前需要使用冒号
:
标记。当 url 匹配到路由中的一个路径时,参数值会被设置到this.$route.params
里,可以在组件内读取到。比如/user/456
匹配的就是/user/:id
,那么这个用户的 id 就是 456,this.$route.params.id
的值就是 456。 -
捕获 404 页面:
import NotFound from "./views/NotFound.vue"; const routes = [ { // 会匹配所有路径 path: '*', component: NotFound } ]
-
当使用通配符路由时,请确保含有通配符的路由应该放在最后。因为路由的匹配通常是根据注册的顺序匹配的,如果
path: '*'
路由放在最前面,那么所有的页面都会因为先匹配到通配符路由而由 NotFound 组件渲染。 -
当使用一个通配符时,
$route.params
内会自动添加一个名为pathMatch
的参数。它包含了 URL 通过通配符被匹配的部分
5. 页面跳转
-
$router.push 方法的参数可以是一个字符串路径:
$router.push('user') $router.push('/user') /// 意味着匹配根路由,所以 '/user' 这样的写法不管原路径 localhost:8080/?? 中的 ?? 是什么,跳转后 url 都会变为 localhost:8080/user。
-
router.push 方法的参数可以是一个描述地址的对象:
// 对象 // 这种写法和字符串类型的参数一样 $router.push({ path: 'home' }) // 命名的路由 $router.push({ name: 'user', params: { userId: '123' }}) // 带查询参数,变成 /register?plan=private $router.push({ path: 'register', query: { plan: 'private' }})
-
参数传递的对应规则:
- name 对应 params,路径形式:user/123;
- path 对应 query,路径形式:user?id=123;
-
获取参数:
-
可以通过 $route 访问当前路由
-
// $route { // 路由名称 name: "user", meta: {}, // 路由path path: "/user/123", // 网页位置指定标识符 hash: "#abc", // window.location.search query: {name: "userName"}, // 路径参数 user/:userId params: {userId: "123"}, fullPath: "/user/123?name=userName#abc" }
-
页面跳转后获取参数可以很方便的通过
$route.query
、$route.params
、$route.hash
获取。
-
6. 重定向和别名
-
别名:
-
当访问
/a
时,渲染的是 A;当访问/b
时,就像访问/a
一样,渲染的也是 A。const routes: [ // 定义 alias 属性 { path: '/a', alias: '/b', component: A } ];
-
-
重定向:
-
若路由
/a
重定向到/b
,即访问/a
时, url 会自动跳到/b
,然后匹配路由/b
const routes: [ // 定义 redirect 属性,将 /a 重定向到 /b { path: '/a', redirect: '/b' } ]
-
-
监听路由:
-
监听路由
$route
的变化watch: { $route(to,from){ console.log(to, from); } } // 或者 watch: { $route: { handler: function(to,from) { console.log(to,from); }, // 深度观察监听 deep: true } }, //to 是变化后的路由,from 是变化前的路由。一般我们用第一种写法就可以了。
-
mounted:
-
mounted() { this.updateTab() }, methods: { updateTab() { this.tabList.map(menu => { menu.active = menu.type === this.$route.query.courseType || 'JAVA' }) } }
-
mounted是vue中的一个钩子函数,一般在初始化页面完成后,再对dom节点进行相关操作。通常是为 metheds 函数提前定义( 类似提前声明变量 进入页面内容全部渲染完成后自动引用函数)
-
-
created:
vue.js
中created
方法是一个生命周期钩子函数,一个vue
实例被生成后会调用这个函数。一般可以在created
函数中调用ajax
获取页面初始化所需的数据。
-
7. 网络请求 async 与 await
-
“异步” async:
async
是“异步”的简写,用于申明一个异步function
,而这个async
函数返回的是一个Promise
对象。
-
“等待异步” await:
-
await
用于等待一个异步方法执行的完成,它会阻塞后面的代码,等着 Promise 对象 resolve ,然后得到 resolve 的值,作为 await 表达式的运算结果:async function getAsyncFn() { const result = await asyncFn(); console.log(result); } getAsyncFn();
-
要注意,
await
只能出现在async
函数中,如果用在普通函数里,就会报错。
-
-
多个请求并发执行 Promise.all:
-
async function asyncFn1() { return "优课达"; } async function asyncFn2() { return "学的比别人好一点"; } async function getAsyncFn() { const result = await Promise.all([asyncFn1(), asyncFn2()]); console.log(result); } getAsyncFn();
-
-
运用 async 和 await 请求数据:
-
<script> export default { data: function() { return { courseList: [] }; }, async mounted() { // 在生命周期 mounted 中调用获取课程信息的方法 await this.queryAllCourse(); }, methods: { // 在 methods 对象中定义一个 async 异步函数 async queryAllCourse() { // 在 fetch 中传入接口地址 const res = await fetch('https://www.fastmock.site/mock/2c5613db3f13a5c02f552c9bb7e6620b/f5/api/queryallcourse'); // 将文本体解析为 JSON 格式的promise对象 const myJson = await res.json(); // 获取返回数据中的 data 赋值给 courseList this.courseList = myJson.data; } } } </script>
-