# 概述
Vue 属于单页面应用 (SPA),指令后边都可以写 js 内容
单页面只需加载一次就把整个项目加载完了
# 如何开始一个 Vue 项目
做 Vue 开发的模式都是属于 node 项目开发模式。由于 Vue 框架本身非常复杂,需要各种工具包,需要搭建一个 Vue 的开发环境。对于新手来说,手动搭建一个 Vue 的开发环境非常难,但是官方提供了一个非常不错的脚手架 (命令行工具 CLI)
- 安装脚手架工具 CLI,在命令行执行
npm i -g @vue/cli全局脚手架工具 (在桌面就可以) vue --version 查看版本号 - 利用脚手架工具搭建 vue 项目
- 直接使用命令搭建 执行
vue create xxx(名字)创建项目,选择默认预设即可 - 使用图形化界面搭建 (只有 脚手架工具 3.X 以上才可以使用),在对应位置打开命令行工具,执行
vue ui命令,会在浏览器中弹出图形化界面,可以创建项目了 - 运行已经创建好的项目,其实就是执行项目下的
package.json文件内的,script 字段下的serve命令,这个命令执行完毕之后,默认的就会将 vue 的项目启动在本机 (localhost) 服务器下的 8080 端口,有冲突的话顺延。
- 直接使用命令搭建 执行
# vue 项目默认的目录结构
- node_modules (环境以及项目所需要的包),以后通过 npm 下载的所有的包也会放在这里
- README.md 项目介绍文件
- package.json (node 项目的标志性文件)
- package-lock.json 项目安装的所有包的记录
- babel.config.js (babel 的配置文件) babel: 转义 js 的
- .gitignore 如果该项目是 git 仓库,那么 .gitignore 内声明的文件以及文件夹就不会上传到 git。该文件就是 git 仓库忽略上传的文件
- public 文件夹 里面放的是单页面应用的 html 模板
- src 文件夹 (vue 项目源代码)
- assets 文件夹 (静态资源文件夹)
- components 文件夹 (存组件的文件夹)
- App.vue
- main.js (项目的入口文件)(引入全局 css 在这里) 例如:
import "./assets/global.css";这里的.css 不可以省略
# 组件
App.vue 组件是项目最大的组件,因为只有 App 组件会被渲染到页面的 #app 中
vue 组件的构成
- template 标签
- script 标签 (可以省略)
- style 标签 (可以省略)
template 标签基本和以前的 html 非常像。一个 template 标签内只能有一个子级
style 标签给组件写样式
script 标签,一般必须默认导出一个对象,对象下有一个 name 属性属性值和组件名一样
# 组件的嵌套
-
App.vue 可以看成是 body
-
父组件导入需要展示的子组件
-
在父组件中 script 标签内的导出对象下,使用 components 属性,进行子组件注册
-
简答操作 (案例简单排版,可不看)
-
App.vue 文件
<template> <div id="app"> <!-- vue 的 template 内开始标签和结束标签之间如果没有内容,请将标签写成单闭合 --> <header /> </div> </template> <script> import Header from "./components/Header"; export default { name: "App", components: { // 名:组件的值 (名其实就是一个自定义标签) // 上边Header导入的是值(import),这里值和名一样,上边的标签(<Header />)是名 Header, }, }; </script> -
Header.vue 文件
<template> <header> <h2>我是头部</h2> </header> </template> <script> export default { name: "Header", //写成大写的 }; </script>
-
# 模板语法
其实就是 如何在 template 中使用 js
- 在开始和结束标签之间使用,需要使用双花括号语法
{{ }} - 当作属性的属性值使用,需要使用 vue 的指令
v-bind(可以简写为:(冒号))
# prop
将整个对象下的 所有属性一次性全部传递 v-bind="todo" (todo 是 prop 数据) 接收的时候 props:['id','text'] , 这些都是 todo 中的属性
父组件提供子组件需要展示的内容
用来实现简单的组件复用,子组件在父组件出现的时候,需要展示的内容可以让父组件提供
实现 prop 的
-
1. 在父组件内传递相应的数据给子组件,直接在组件的标签上当作属性传递数据,属性名可以自定义,属性值是传递的真正数据
-
案例一:父组件文件
<template> <div class="right"> <div class="top"> <Box imgTitle="pic1" imgSrc="https://www.samsungmobilepress.com/asset/asset/8578e85c-4f8b-4652-8565-580aab3f8fc3/306x240.jpg" /> </div> </div> </template> -
案例二:父组件文件
<template> <div class="left"> <!-- Content 时候可能展示p,有时候是h,有时候是a --> <!-- 将需要展示的不同 html 结构写在组件的开始标签和结束标签之间 --> <content> <p>展示的内容</p> </content> <content> <h2>展示的内容</h2> </content> <!-- 上面这种传递 prop 的方式称为 slot(插槽) --> </div> </template> <script> import Content from "./Content"; export default { name: "Left", components: { Content, }, }; </script>
-
-
2. 子组件获取父组件传递的数据展示,需要在子组件内使用 props 属性接收。
-
案例一:使用数组方式接收
子组件文件
<template> <div class="box"> <!-- <img v-bind:src="imgSrc" alt /> --> <img :src="imgSrc" alt /> <p>{{imgTitle}}</p> </div> </template> <script> export default { name: "Box", // 这个数据可以认为是一个该组件内的全局变量随意使用。 props: ["imgTitle", "imgSrc"], // prop 数据的使用 // 1. 在 template 中直接用,需要遵循vue的模板语法 // 2. 在 script 中需要使用 this.xxx }; </script> -
案例二:使用插槽
<template> <div class="content"> <!-- 父组件传递的 slot prop直接使用 slot 标签接收 --> <slot /> </div> </template> <script> export default { name: "Content", }; </script>
-
-
3. 轮播图小案例
-
父文件
<template> <div id="app"> <div v-if="arr.length" class="news_list"> <div v-for="news in arr" :key="news.id"> <img :src="news.imgSrc" alt /> <div class="info"> <span>{{news.date}}</span> <h3>{{news.title}}</h3> <p> <!-- 关键字 --> <!-- <span v-for="(word,index) in news.keyWords" :key="index">#{{word}}</span> --> <keyword v-for="(word,index) in news.keyWords" :key="index" :word="word" /> </p> </div> </div> </div> </div> </template> <script> import keyword from "./components/Keyword"; export default { name: "App", components: { keyword, }, data() { return { arr: [ { id: 1, title: "Samsung Internet 14.0 Beta is Now Available", imgSrc: "https://www.samsungmobilepress.com/asset/asset/cd35c15e-bf6d-4683-a774-986b517d4837/306x240.jpg", date: "March 09, 2021", keyWords: [ "Samsung Internet", "Samsung Internet 14.0", "Update", "Privacy", "Multi-tasking", ], }, ], }; }, }; </script> -
子文件
<template> <span class="key_word">#{{word}}</span> </template> <script> export default { name: "Keyword", props: ["word"], }; </script>
-
# data 组件的数据
<template>
<header>
<h2>我是头部</h2>
<span>{{count}}</span>
</header>
</template>
<script>
// 数据改变引起视图改变
// 1. 创建 data
// 2. 将 data 展示到 视图上
// 3. 修改 data
export default {
name: "Header",
// data 必须是一个函数,并且返回一个对象,对象下的属性就是 data 数据
// data 的使用和 prop 基本一样
data() {
return {
count: 1000,
};
},
};
</script>
# this 在 data prop methods …… 的使用
this 就是 vue 实例(组件本身)
- template 直接使用,只需要遵循模板语法即可 (属性中 使用
:绑定,标签间用{{ }}) - script 标签中 使用
this.xxxxxx 表示对应的名字
# 事件处理
在 vue 里面的 methods 定义方法的时候,如果方法内需要使用 this 那么必须将该方法定义成普通函数
-
定义事件
-
绑定事件
<template> <header> <span>{{count}}</span> <button v-on:click="add()">+</button> <button @click="remove()">-</button> </header> </template> <script> export default { name: "Header", data() { return { count: 1000, }; }, methods: { add() { this.count = this.count + 1; }, remove() { // this.add(); //这里调用加法 if (this.count > 1) { this.count = this.count - 1; } }, }, }; </script> -
事件传参
<template> <div> <h2>轮播图</h2> <div class="banner"> <ul class="pagination"> <!-- num 是从 1 开始的,所以这里要减1 --> <!-- 事件函数内获取事件对象 --> <!-- 如果绑定函数的时候没有传递额外的参数,那么事件的第一个参数默认就会是事件对象直接使用 event参数即可 --> <!-- 如果绑定函数的时候传递了参数,那么事件内想要使用事件对象的话,绑定的时候使用 $event 当作实参传递使用--> <li v-for="num in imgArr.length" :key="num.id" @click="changeActiveIndex(num - 1,$event)" :class="{ active: activeIndex === num - 1 }" ></li> </ul> </div> </div> </template> <script> export default { name: "SwiperDemo", data() { return { imgArr: [ { id: 1, src: "https://imgcps.jd.com/ling4/295Yi4/5ruhNTHlh481MA/p-5bd8/5001404a/cr/s/q.jpg", }, { id: 2, src: "https://imgcps.jd.com/ling4/491816LSn/6YMei1oDE/p-5bf7/7ba1c/cr/s/q.jpg", }, ], activeIndex: 0, }; }, methods: { changeActiveIndex(index, e) { console.log(e); this.activeIndex = index; }, }, }; </script>
# class 与 style 的绑定
# 样式的控制
-
class 的绑定
-
直接使用 js 语法实现 比如三目运算符 v-bind
<template> <header> <!-- 样式 结构 --> <button @click="changeShow">切换出现和显示</button> <!-- 将 show 和 box 的 class 挂钩 --> <div :class="show ? 'box':'box hide'"></div> <!-- 对象语法 --> <!-- 普通的 js 语法实现 --> <div :class="{box:true,hide:!show}"></div> <!-- 数组语法实现 --> <div :class="['box',show?'':'hide']"></div> </header> </template> <script> export default { name: "Header", data() { return { show: true, }; }, methods: { changeShow() { this.show = !this.show; }, }, }; </script> -
数组语法嵌套对象语法
-
-
style 的绑定
-
直接使用 js 语法实现 比如三目运算符 v-bind
<template> <header> <button @click="changeShow">切换出现和显示</button> <!-- vue 中的事件绑定可以直接写在标签上,不一定需要写在 methods 内定义 --> <!-- 直接写的话一般只写 修改 data 的操作 --> <button @click="active=!active">切换背景色</button> <!-- 普通的 js 语法实现 --> <div class="'box'" :style="`background-color:${active?'red':'#00b3d4'}`" ></div> <!-- 对象语法 --> <div class="box" :style="{backgroundColor:active?'red':'#00b3d4'}" ></div> </header> </template> <script> export default { name: "Header", data() { return { show: true, active: false, }; }, methods: { changeShow() { this.show = !this.show; }, }, }; </script> -
数组语法就是可以使用多个对象语法 [对象语法 1,对象语法 2]
-
-
指令 v-show 实现:只是可以实现出现或消失 none 和 block 的切换
<template> <header> <div class="box" v-show="show"></div> </header> </template> <script> export default { name: "Header", data() { return { show: true, }; }, methods: { changeShow() { this.show = !this.show; }, }, }; </script>
# 结构的添加和删除
-
使用指令 v-if v-else v-else-if (v-if 和 v-else 必须是同级,且中间不能有东西)
<template> <header> <div class="box" v-if="show"></div> <p v-else>box 消失了 我出现了</p> <!-- <p v-else-if="true">box 消失了 我出现了</p> --> <!-- 如果 v-if 成立,v-else-if和v-else就不看了 --> </header> </template> <script> export default { name: "Header", data() { return { show: true, }; }, methods: { changeShow() { this.show = !this.show; }, }, }; </script>
# 条件渲染
- v-show(
标签还在) 需要比较频繁的操作使用 v-show (不管初始条件是什么,元素都会被渲染) - v-if(
标签没了) 如果运行时条件很少改变,使用 v-if (如果初始条件为假则不渲染,直到条件第一次为真时,会开始渲染)
# 列表渲染
-
v-for
<template> <div class="right"> <div class="keywords"> <!-- 要根据数据渲染多个div --> <!-- 使用 v-for 的时候,必须使用 :key属性,而且 key 属性的属性值必须是在兄弟之间唯一的--> <div class="keyword" v-for="(word,index) in keyWords" :key="index"> <span>{{`0${index+1}`}}</span> <h4>{{word}}</h4> </div> </div> <!-- 特殊的 v-for --> <div v-for="num in 4" :key="num">{{num}}</div> <!-- 1 2 3 4 --> <div v-for="(str,index) in 'hello'" :key="index">{{str}}</div> <!-- h e l l o --> </div> </template> <script> export default { name: "Right", data: () => ({ keyWords: ["iphone 7", "iphone 8", "iphone 9", "iphone 10"], }), }; </script> -
在 template 标签上使用 v-for
<template> <div class="right"> <!-- 在 template 标签上使用 v-for,可以渲染包含多个元素的内容 --> <!-- key 是不可以加在 template 标签上的,要加在里面元素内,而且最外层的每一个都要添加,还要唯一 --> <!-- 这里的template其实就是个空标签,啥也不是(空的) --> <ul> <template v-for="item in list"> <li :key="item.id">{{item.id}}</li> <li :key="item.text">{{item.text}}</li> </template> </ul> </div> </template> <script> export default { name: "Right", data: () => ({ list: [ { id: 1, text: "zxcv" }, { id: 2, text: "qwer" }, { id: 3, text: "asdf" }, ], }), }; </script> -
v-if 往往和 v-for 一起使用
<template> <div class="right"> <div v-if="keyWords.length" class="keywords"> <div class="keyword" v-for="(word,index) in keyWords" :key="index"> <span>{{`0${index+1}`}}</span> <h4>{{word}}</h4> </div> </div> <div v-else>暂时没有数据</div> </div> </template> <script> export default { name: "Right", data: () => ({ keyWords: ["iphone 7", "iphone 8", "iphone 9", "iphone 10"], }), }; </script>
# vue 组件的生命周期钩子 (函数)
从组件开始渲染 --> 销毁。不同的生命周期阶段对应着组件内不同的生命周期函数,在对应阶段所对应的函数会自动触发。所有的生命周期都是同步函数 (同步阻塞,异步非阻塞) 只有当前边的函数执行完之后,才能执行后边的函数
-
初始渲染阶段
beforeCreate()创建前,检查一遍有哪些 data,prop, 事件……(飞机起飞之前的检查)created()创建后,说明 data,prop, 事件…… 都已处理完毕,可以往页面上渲染了 在这里可以修改 data了,异步请求修改数据可以在此生命周期执行 (检查完毕可以起飞了)beforeMount()挂载 (渲染) 之前,组件的render(渲染) 函数首次执行mounted()组件渲染完毕,组件就出现在了页面中,可以获取真实的 dom 节点,也可以进行异步请求,但一般在 created () 中请求
-
数据更新渲染阶段(data,prop,slot…… 发生改变)
beforeUpdate()更新之前 (数据更新时),数据更新时,没更新完毕updated()数据更新完毕 (页面更新也完毕了),可以获取更新之后的 data以及真实 dom 节点(由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子)
-
销毁
beforeDestroy()组件销毁前,实例仍然可以使用一般用于清除定时器,清除 window 的滚动条事件destroyed()组件销毁后 所有的指令,事件,监听…… 都没了 (跟组件相关的没了,跟组件没关系的还在)
-
自动轮播图案例
<template> <div> <h2>轮播图</h2> <div class="banner" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave"> <div class="img-wrap"> <img :class="{ active: activeIndex === index }" v-for="(img, index) in imgArr" :src="img.src" :key="img.id" alt /> </div> <div class="left-arrow" @click="leftArrow"><</div> <div class="right-arrow" @click="rightArrow">></div> <ul class="pagination"> <li @click="changeActiveIndex($event,num - 1)" :class="{ active: activeIndex === num - 1 }" v-for="num in imgArr.length" :key="num.id" ></li> </ul> </div> </div> </template> <script> export default { name: "SwiperDemo", data() { return { imgArr: [ { id: 1, src: "https://imgcps.jd.com/ling4/2922989/5o5Yi4/5ruh1A/p-51d8/5001404a/cr/s/q.jpg", }, { id: 2, src: "https://imgcps.jd.com/ling4/4918116/576O51Sn/6YOo51DE/p-5bd1f7/7c1c/cr/s/q.jpg", }, ], activeIndex: 0, }; }, methods: { changeActiveIndex(e, index) { this.activeIndex = index; }, leftArrow() { const { activeIndex, imgArr } = this; if (activeIndex > 0) { this.activeIndex--; } else { this.activeIndex = imgArr.length - 1; } }, rightArrow() { const { activeIndex, imgArr } = this; if (activeIndex === imgArr.length - 1) { this.activeIndex = 0; } else { this.activeIndex++; } }, onMouseEnter() { clearInterval(this.run); }, onMouseLeave() { this.run = setInterval(() => { this.rightArrow(); // 重点 }, 1000); }, }, created() { // 在组件的 script 标签内,嵌套的函数内想要使用 this(组件本身) 需要将函数定义为箭头函数 this.run = setInterval(() => { console.log("执行"); this.rightArrow(); }, 1000); // 这里建议使用this为组件添加方法 // 想要定义一个组件内的全局变量,又不想弄成 data,可以直接使用 this.xxx 定义 }, beforeDestroy() { // 组件销毁之前调用。在这一步,组件仍然完全可用 // 清除定时器,清除 window 的滚动条事件 clearInterval(this.run); }, }; </script>
# 组件之间的交互
-
父子组件之间
-
父组件想要修改子组件的 data,将子组件的 data 定义到父组件中,然后将其当作 prop 传递给子组件,如果子组件想要修改的话,可以在父组件定义修改的方法,将该方法传递给子组件执行,
切莫直接修改 prop-
父文件
<template> <div id="app"> <button @click="open">open a dialog</button> <!-- 父组件传递给子组件的prop如果是一个函数的话 --> <!-- 可以将这种传递方式换成自定义事件的传递 --> <dialog :visibility="visibility" :close="close" /> </div> </template> <script> import Dialog from "./components/DialogDemo/Dialog"; export default { name: "App", components: { Dialog, }, data() { return { visibility: false, }; }, methods: { open() { this.visibility = true; }, close() { this.visibility = false; }, }, }; </script> -
子文件
<template> <div v-show="visibility" class="dialog-wrap"> <!-- 遮罩 --> <div class="mask"></div> <!-- 弹窗 --> <div class="dialog"> <button @click="close">取消</button> </div> </div> </template> <script> export default { name: "Dialog", // prop 的验证,类型检查 // 接收 prop 的时候需要使用对象形式 props: { visibility: { type: Boolean, // 值的类型,非必须的 default: false, // 默认值是对象类的时候必须使用函数返回该对象,非必须的 // required: true // 必须传递(加上就是必须传visibility),非必须的 // validator 自定义验证函数 非必须 /* validator: (value) => { // return true; //通过 // value 指的就是传递过来的值,满足条件就是通过,不满足条件就是出错 return [1, 2].includes(value); //必须是1和2中的一个 }, */ }, close: Function, // 类型校验简单版 }, }; </script>
-
-
-
案例
将兄弟组件的状态共享到父组件中,当作 props 传递
-
TodoWrap.vue 文件 (父组件)
<template> <div class="todo-wrap"> <h1>todos</h1> <TodoForm /> <TodoList :todos="todos" :delTodo="delTodo" /> <!-- 第二个delTodo是和method中的delTodo对, :delTodo和TodoList文件中的delTodo对, TodoList中的@delTodo和TodoItem文件中的对 --> </div> </template> <script> import TodoForm from "./TodoForm"; import TodoList from "./TodoList"; export default { name: "TodoWrap", components: { TodoForm, TodoList }, data() { return { todos: [ { id: "7t7t7d", text: "666", isCompleted: true }, { id: "f898dsf", text: "给力", isCompleted: false }, ], }; }, methods: { delTodo(id) { const delIndex = this.todos.findIndex((todo) => todo.id === id); if (delIndex > -1) { this.todos.splice(delIndex, 1); } }, }, }; </script> -
TodoForm.vue 文件和 TodoList.vue 文件是兄弟文件 TodoForm 要修改 TodoList
<template> <div class="todo-form"> <span>图标</span> <input type="text" /> </div> </template> <script> export default { name: "TodoForm", }; </script> -
TodoItem.vue 文件
<template> <div class="todo-item"> <div class="checkbox"> <input type="checkbox" :checked="todo.isCompleted" :id="todo.id" /> <label :for="todo.id"></label> </div> <span class="todo-text">{{todo.text}}</span> <span class="del" @click="$emit('delTodo',todo.id)">X</span> </div> </template> <script> export default { name: "TodoItem", props: ["todo"], }; </script>
-
# 自定义事件
自定义事件只能往下传递一次,不能传递两次 , 解决方案是 上边的案例 (TodoWrap,TodoList,TodoItem)
父组件用 prop 传递给子组件,子组件用 props 接收到之后,使用自定义事件(@)传递给孙子组件
父组件有函数想传给子组件使用
-
父文件
<template> <div id="app"> <button @click="open">open a dialog</button> <!-- 父组件传递给子组件的prop如果是一个函数的话,我们可以将这种传递方式换成自定义事件的传递 --> <!-- 用法:传递的时候 @事件名='对应的事件函数' @close="close" 接收的时候 不使用 prop 接收,直接使用$emit('事件名',事件参数) template中直接使用 script中需要使用 this.$emit('事件名',事件参数) $emit('close')是直接执行传递过来的函数 --> <dialog :visibility="visibility" @close="close" /> </div> </template> <script> import Dialog from "./components/DialogDemo/Dialog"; export default { name: "App", components: { Dialog, }, data() { return { visibility: false, }; }, methods: { open() { this.visibility = true; }, close() { this.visibility = false; }, }, }; </script> -
子文件
<template> <div v-show="visibility" class="dialog-wrap"> <div class="mask"></div> <!-- 弹窗 --> <div class="dialog"> <!-- <button @click="$emit('close')">取消</button> --> <!-- 这两句效果一样,props中不需要接收close函数 --> <button @click="cancle">取消</button> </div> </div> </template> <script> export default { name: "Dialog", props: { visibility: { type: Boolean, default: false, }, // close: Function, }, methods: { cancle() { this.$emit("close"); }, }, }; </script>