vue课上知识总结

vue课上知识总结

显示文本框的渲染

原先的dom节点渲染

通过 js 主要做如下事情

  1. 通过 js 惊醒业务逻辑的处理,如对变量进行运算、获取服务器端数据、应用 js 中的函数对数据进行处理
  2. 通过dom 改变页面:比如 创建元素、添加元素、删除元素、设置和湖区元素的属性、设置和获取元素的style 样式、设置和获取元素的 class 样式、为dom注册事件
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<input type="text" v-model="msg" id="msg" />
		<br/>
		<span></span>
		<script>
			// let input = document.querySelector('#msg')
			// /*我们可以使用声明对象方法,来让他继续表示是当前事件的出发对象*/
			// let that = input
			// input.addEventListener('keyup',()=>{
			// 	/**
			// 	 * 箭头函数中的this 不再是当前时间的出发对象,而是当前箭头函数所在环境的this
			// 	 * */
			// 	console.log(that.value)
			// })
			
			/**
			 * 但是为了代码简洁避免不必要的麻烦我们还是用function函数方法来写
			 * */
			let input = document.querySelector('#msg'); //获取dom元素
			let span = document.querySelector('span'); //获取dom元素
			input.addEventListener('keyup',function(){ //为dom元素注册事件监听
				span.innerHTML = this.value //将文本框中用户输入的数据赋值给span
			})
			 
		</script>
	</body>
</html>

使用 vue 方法渲染

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<!-- 为文本框绑定了事件
			事件名称:keyup
			事件处理函数:changeMsg -->
			<!-- <input type="text" v-model="txt" id="msg" v-on:keyup="changeMsg"/> -->
			<input type="text" v-model="msg" id="msg"/>
			<br/>
			<span>{{msg}}</span>
		</div>
		<!-- 引入 vue -->
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			// var app = new Vue({
			// 	el: "#app",
			// 	data: {
			// 		txt:'',
			// 		msg:'',
			// 	},
			// 	methods:{
			// 		changeMsg: function () {
			// 			this.msg = this.txt
			// 		},
			// 	},
			// })
			
			var app = new Vue({
				el: "#app",
				data: {
					msg:'',
				},
			})
		</script>
	</body>
</html>

两者对比的区别

最明显的区别:

vue 中不用操作 dom 元素

这可以让来发这将精力集中在数据处理及业务逻辑开发中

书写形式上
js
 //1.获取dom元素
let input = document.querySelector('#msg');
let span = document.querySelector('span'); 
//2.为dom元素注册事件监听
input.addEventListener('keyup',function(){ 
    //3.将文本框中用户输入的数据赋值给span
    span.innerHTML = this.value 
})
vue.js
<!-- 1.创建dom结构 -->
<div id="app">
    <!-- 6.通过插值表达式使用data中的数据 -->
    <p>{{msg}}</p>
	<p>{{age}}</p>
	<p>{{books}}</p>
</div>
<!-- <div>{{msg}}</div> -->
<!-- 2.引入vue -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    // 3. 创建 vue 实例
    let app = new Vue({
        // 4.当前 vue 实例的挂载点是 id 为 app 的dom,其实设置的就是vue实例中变量的可用范围
        el:"#app", 
        //5.data中是模型数据,这些数据依赖与当前的实例,可以在控制台中通过app.msg方式访问data中的msg数据
		data:{
            msg:'hello',
			age:20,
			books:['平凡世界','三国演义']
		},
	})
</script>

模板使用js表达式

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<p>{{age +1}}</p>
			<p>{{message.split('').reverse().join()}}</p>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					age:10,
					message:'12345',
				},
			})
		</script>
	</body>
</html>

列表渲染

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

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<!-- <p>{{blogs}}</p> -->
			<ul>
				<!-- <li>{{blogs[0]}}</li>
				<li>{{blogs[1]}}</li>
				<li>{{blogs[2]}}</li> -->
				
				<!-- blog自定义的,blogs调用来的 -->
				<li v-for="blog in blogs">{{blog}}</li>
			</ul>
			<ul>
				<li v-for="item in books">{{item}}</li>
				<li v-for="item in books">{{item.title}}---- {{item.author}}</li>
			</ul>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
				  blogs: ['今天下雪了', '明天还下雪', '后天就30°了', '外面好冷啊'],
				  books: [
				    { id: 1, title: '庆余年', author: '忘了' },
				    { id: 2, title: '鬼吹灯', author: '天下霸唱' },
				    { id: 3, title: '盗墓笔记', author: '南派三叔' },
				  ],
				},
			})
		</script>
	</body>
</html>

模板渲染

实际开发中,数据肯定是从服务器端获取

  • created 函数会再 vue 实例实例化时执行
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<ul>
				<li v-for="item in books">{{item.title}}</li>
			</ul>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					// books是实例化变量,无法访问外边的东西
					books: [],
				},
				//  created 钩子可以用来在一个实例被创建之后执行代码:
				created: function() {
					fetch('data.json')
						.then((res) => {
							return res.json()
						})
						.then((res) => {
							// console.log(res)
							this.books = res
						})
				},
			})
		</script>
	</body>
</html>

data.json文件:

[
  { "id": 1, "title": "庆余年", "author": "忘了" },
  { "id": 2, "title": "鬼吹灯", "author": "忘了" },
  { "id": 3, "title": "盗墓笔记", "author": "忘了" }
]
页面渲染

可以在一个页面中创建多个挂载点,并创建多个 vue 实例,

将不同的 vue 实例与不同的挂载点相关联

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<p>{{name}}</p>
		</div>
		<div id="vm">
			<p>{{name}}</p>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			// 可以在一个页面中创建多个挂载点,并创建多个 vue 实例,
			// 将不同的 vue 实例与不同的挂载点相关联
			let app = new Vue({
				el: '#app',
				data: {
					name:'aa',
				},
			})
			
			let vm = new Vue({
				el: '#vm',
				data: {
					name:'zwj',
				},
			})
		</script>
	</body>
</html>
维护状态

当 Vue 正在更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。

这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出

为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key attribute:

image.png

建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

因为它是 Vue 识别节点的一个通用机制,key 并不仅与 v-for 特别关联。后面我们将在指南中看到,它还具有其它用途。

不要使用对象或数组之类的非基本类型值作为 v-forkey。请用字符串或数值类型的值。

条件渲染

可以单独使用 v-if,或者 v-if 和 v-else,或者 v-if 和 v-else-if 搭配使用

v-if在单独使用的时候,与 v-show 的作用是一样的,都是根据变量的值决定是显示还是隐藏当前元素

<!doctype html>
<html>

	<head>
		<meta charset="utf-8">
		<title></title>
		<meta charset="UTF-8"/>
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		
	</head>

	<body>
		<div id="app">
			<h1 v-if="isMary">已婚</h1>
			<h1 v-else>未婚</h1>
			<!-- v-if在单独使用的时候,与v-show的作用是一样的,
			都是根据变量的值决定是显示还是隐藏当前元素 -->
			<div v-if="type">v-if</div>
			<div v-show="type">v-show</div>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el:'#app',
				data:{
					isMary:false,
					type:true,
				},
			})
		</script>
	</body>

</html>

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

数据绑定

所谓的绑定,就是将 vue 实例中的 data 属性中的变量显示到挂载点(dom结构)中

内容绑定

将 data 中的数据显示成内容(开始标签与结束标签之间)

使用 {{}}

<div id="app">
    <p>{{msg}}</p>
</div>

上面只能显示纯文本,如果要显示 html 内容,需要使用 v-html 指令

<div id="app">
    <p v-html="content"></p>
</div>

属性绑定

将 data 中的数据作为某个元素属性的值

使用 v-bind:属性名称 指令,属性可以是内置,也可以是自定义的

<!-- 如果属性名称前面没有加上 v-bind:,则属性的值只能是一个静态的值;
如果属性名称前面加上 v-bind:,则属性的值可以是data 中的一个变量 -->
<h1 v-bind:id="id">{{msg}}</h1>

表单控件的值

可以使用 v-model 指令在表单 <input><textarea><select> 元素上创建 双向数据绑定 ,他会根据控件类型自动选取正确的方法来更新元素

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text和textarea元素使用value property和input事件;
  • checkbox和radio 使用checked property和change 事件;
  • select字段将value 作为 prop 并将change 作为事件。
<input><textarea>
<input type="text" v-model="msg" />
<textarea v-model="msg" cols="30" rows="10"></textarea>
复选框:
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<div id="app">
            <!-- 使用 id的主要目的是为了,当我们鼠标点击文本时,也能起到勾选作用  -->
			<label for="swip">游泳</label>
			<input type="checkbox" id="swim" v-model="isSwim" />
			<label for="read">阅读</label>
			<input type="checkbox" id="swim" v-model="isRead" />
			<label for="play">游戏</label>
			<input type="checkbox" id="swim" v-model="isPlay" />
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el:"#app",
				data:{
                    // 变量值决定复选框的状态
					isSwim: true,
					isRead: true,
					isPlay:false,
				},
			})
		</script>
	</body>
</html>

单选框
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<label for="man"></label>
			<input type="radio" id="man" value="man" v-model="gender" />
			<label for="women"></label>
			<input type="radio" id="women" value="women" v-model="gender" />
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					gender:'',
				},
			})
		</script>
	</body>
</html>

  • 设置多个单选框的 v-model 为同样的值,就可以省略 name 属性

  • 选择某个单选框后,此单选框的 vlue 属性值会赋值给 gender 属性

  • 设置 gender 属性的值为某个单选框的 value 值,此单选框就会默认选中

选择器
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<select v-model="city">
                <!-- disabled 设置成不能被选中状态 -->
				<option disabled value="">请选择</option>
				<option value="bj">北京</option>
				<option value="sh">上海</option>
			</select>
			<p>{{city}}</p>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					city:'',
				},
			})
		</script>
	</body>
</html>

双向绑定
什么是数据双向绑定?

vue是一个mvvm框架,即数据双向绑定,即当数据发生变化的时候,视图也就发生变化,当视图发生变化的时候,数据也会跟着同步变化。这也算是vue的精髓之处了。值得注意的是, 我们所说的数据双向绑定,一定是对于UI控件来说的,非UI控件不会涉及到数据双向绑定。 单向数据绑定是使用状态管理工具(如redux)的前提。如果我们使用vuex,那么数据流也是单项的,这时就会和双向数据绑定有冲突,我们可以这么解决

为什么要实现数据的双向绑定?

在vue中,如果使用vuex,实际上数据还是单向的,之所以说是数据双向绑定,这是用的UI控件来说,对于我们处理表单,vue的双向数据绑定用起来就特别舒服了。

即两者并不互斥, 在全局性数据流使用单项,方便跟踪; 局部性数据流使用双向,简单易操作。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title></title>
	</head>
	<body>
		<div id="app">
			<!-- <span>{{msg}}</span> -->
			<input type="text" v-model="msg" />
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el:"#app",
				data:{
					msg:'hello',
				},
			})
		</script>
	</body>
</html>

任务单.gif

单项数据绑定
  1. 所有数据只有一份

  2. 一旦数据变化,就去更新页面(只有data–>DOM,没有DOM–>data)

  3. 若用户在页面上做了更新,就手动收集(双向绑定是自动收集),合并到原有的数据中。

<!-- 单向数据绑定 -->
<div id="app">
    <span>{{msg}}</span>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
    let app = new Vue({
        el:"#app",
		data:{
            msg:'hello',
        },
	})
</script>
  • 所有的 prop 都使得其父子 prop 之间形成了一个 单向下行绑定

    • 父级 prop 的更新会向下流动到子组件中,但是反过来则不行。
    • 这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。
  • 额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。

    • 这意味着你应该在一个子组件内部改变 prop。
    • 如果你这样做了,Vue 会在浏览器的控制台中发出警告。

这里有两种常见的试图变更一个 prop 的情形:

  1. 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。 在这种情况下,最好定义一个本地的 data property 并将这个 prop 用作其初始值:

    props: ['initialCounter'],
    data: function () {
      return {
        counter: this.initialCounter
      }
    }
    
  2. 这个 prop 以一种原始的值传入且需要进行转换。 在这种情况下,最好使用这个 prop 的值来定义一个计算属性:

    props: ['size'],
    computed: {
      normalizedSize: function () {
        return this.size.trim().toLowerCase()
      }
    }
    

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

代码段

vs code 添加代码段

如何操作用户自定义代码片段(快捷键)?
第一步:文件==>首选项==>用户代码片段

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C2yvIOaQ-1615345381455)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/994d0388-1ff0-4e65-9d76-e156a794a6f2.png)]

第二步:新建全局代码片段文件或者选择代码片段文件 html.json

第一种

image.png

第二种

image.png

第三步:添加如下代码段,用于快速生成 vue 页面的基本结构
"Print to console": {
        "scope": "",
        "prefix": "vue",
        "body": [
            "<!DOCTYPE html>"+
      "<html lang=\"en\">"+
       "<head>"+
          "<meta charset=\"UTF-8\" />"+
          "<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />"+
          "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />"+
          "<title>Document</title>"+
          "</head>"+
          "<body>"+
          "<div id=\"app\"></div>"+
          "<script src=\"https://cdn.jsdelivr.net/npm/vue/dist/vue.js\"></script>"+
          "<script>"+
          "let app = new Vue({"+
            "el: '#app',"+
            "data: {},"+
            "})"+
            "</script>"+
            "</body>"+
            "</html>"
        ],
        "description": "Log output to console"
    }
第四步:生成

vue+回车键/Tab键

任务单.gif

常用代码块列表
通用js代码块
  • iff :简单if
  • forr :for循环结构体
  • fori :for循环结构体并包含i
  • funn:函数
  • funa:匿名函数
  • clog:打印日志
  • clogvar:打印变量命名和值
dom代码块
  • dg :document.getElementById
  • dl :$("")
vue代码块

敲v,即可拉出各种vue代码块

代码块配置格式说明

vscode使用json定义代码块的格式,兼容HBuilderX的代码块格式,也就是你可以把vscode里已经配置的自定义代码块方便的挪到HBuilderX中使用。

每个配置项的说明如下:

key

“key” :代码块显示名称,显示在代码助手列表中的名字。key是不能重复的。

prefix

“prefix” :代码块的触发字符,就是敲什么字母可以激活这个代码块。

body

“body” :代码块的内容。内容中有如下特殊格式

$1 表示代码块输入后光标的所在位置。如需要多光标,就在多个地方配置$1;如该位置有预置数据且需要选中,则写法是

${1:selectedtext};这里还支持下拉候选菜单,多选项即下拉候选列表使用${1:foo1/foo2/foo3}

$2 表示代码块输入后再次按tab后光标的切换位置tabstops(代码块展开后按tab可以跳到下一个tabstop,在HBuilderX中看到类似绿色光标的不闪的竖线,就可以按tab或回车跳转光标过去)

$0代表代码块输入后最终光标的所在位置(也可以按回车直接跳过去)。

双引号使用\"转义

换行使用多个数组表示,每个行一个数组,用双引号包围,并用逗号分隔

缩进需要用\t表示,不能直接输入缩进或空格!

triggerAssist

“triggerAssist” :为true表示该代码块输入到文档后立即在第一个tabstop上触发代码提示,拉出代码助手,默认为false

project

project: 将代码块控制在指定项目类型下生效。可取值有:uni-appWebAppWap2App

比如:"project": "uni-app",代表这个代码块仅在uni-app项目下生效

如果不设置,则该代码块在所有项目类型下均生效。

Web指普通项目,App指5+App项目。

如需设置多种项目类型,用逗号分隔。比如:"project": "uni-app,App"

样式处理

操作元素的 class 列表和内联样式是数据绑定的一个常见需求。因为它们都是 attribute,所以我们可以用 v-bind 处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将 v-bind 用于 classstyle 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组

vue 操作 classs

  • class 与 v-bind:class 可以共存,但是其实最终操作的都是元素的 class 属性

  • v-bind:class 的值可以是字符串、对象或者数组

  • v-bind:class 的值如果为对象,如{active:isActive,color:isColor},则属性名称是 style 中定义的样式类名称,属性值是 data 中定义的变量名称

  • 对象表示法

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

    • <p v-bind:class="{active:isActive}">对象表示法</p> 
      
    • 上面的语法表示active这个class存在与否将取决于数据property isActive 的truthiness。

  • 多个对象共存

    • 你可以在对象中传入更多字段来动态切换多个class。此外,v-bind:class指令也可以与普通的class attribute共存。当有如下模板:
    • 将 class 属性称为静态 class,将 v-bind:class 称作动态属性
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
		<style>
			.c1{
				width: 300px;
				height: 200px;
				background-color: antiquewhite;
			}
			.c2{
				width:300px;
				height: 200px;
				background-color: aquamarine;
			}
			.base{
				width: 400px;
				height: 200px;
				border: 1px solid black;
			}
			
			.active{
				width: 400px;
				height: 200px;
				background-color: aqua;
			}
			.color{
				color: red;
			}
		</style>
	</head>
	<body>
		<!-- class 与v-bind:class可以共存,但是其实最终操作的都是元素的class属性
		     v-bind:class的值可以是字符串、对象或者数组
			 v-bind:class的值如果为对象,如{active:isActive,color:iscolor},
			 则属性名称是 style 中定义的样式类名称,属性值是 data 中定义的变量名称
		 -->
		
		
		<!-- 操作元素的class列表和内联样式是数据绑定的一个常见需求。
		因为它们都是attribute,所以我们可以用v-bind处理它们:
		只需要通过表达式计算出字符串结果即可。
		不过,字符串拼接麻烦且易错。
		因此,在将 v-bind 用于class和style时,
		vue.js做了专门的增强。
		表达式结果的类型除了字符串之外,还可以是对象或数组。-->
		
		<div id="app">
			<!-- <p class="c1"></p> -->
			<!-- 不加v-bind:那么pClass是个字符串,设置v-bind:那么pClass会变成属性 -->
			<p v-bind:class="pClass"></p>
			
			
			<!-- 对象表示法 -->
			<!-- 我们可以传给v-bind :class—个对象,以动态地切换class: -->
			<!-- <p v-bind:class="{active:isActive}">对象表示法</p> -->
			<!-- 上面的语法表示active这个class存在与否将取决于数据property isActive 的truthiness。 -->
			
			
			<!-- 多个对象共存 -->
			<!-- 你可以在对象中传入更多字段来动态切换多个class。此外,v-bind:class指令也可以与普通的class attribute共存。当有如下模板: -->
			<!-- 将 class 属性称为静态 class,将 v-bind:class 称作动态属性 -->
			<p class="base" v-bind:class="{active:isActive,color:isColor}">对象表示法</p>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
			<script>
				let app = new Vue({
					el:'#app',
					data:{
						id:11,
						pClass:'c2',
						isActive:true,
						isColor:true,
					},
				})
			</script>
	</body>
</html>

vue 操作 style

  • 绑定内联样式
    • v-bind:style的对象语法十分直观–看着非常像CSS,但其实是一个JavaScript 对象。
    • CSSproperty名可以用驼峰式(camelCase)或短横线分隔(kebab-case,记得用引号括起来)来命名:
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
		<style>
			
		</style>
	</head>
	<body>
		<!-- 绑定内联样式 -->
		<!-- v-bind:style的对象语法十分直观--看着非常像CSS,
		但其实是一个JavaScript 对象。
		CSSproperty名可以用驼峰式(camelCase)或
		短横线分隔(kebab-case,记得用引号括起来)来命名: -->
		
		<div id="app">
			<p :style="{width: w,height: h,backgroundColor:a}"></p>
			<p :style="obj"></p>
		</div>
		
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					w:'300px',
					h:'200px',
					a: 'lightblue',
					obj:{
						width:'400px',
						height:'100px',
						backgroundColor: 'blue'
					}
				},
			})
		</script>
	</body>
</html>

事件处理

js 中可以通过 addEventListener 的方式为元素注册各种事件类型

vue 中通过 v-on 指令为元素注册事件监听,在 methods 对象中定义方法

  • 事件当中,this 指向的是当前 vue 实例
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
	</head>
	<body>
		<div id="app">
			<button v-on:click="greet">按钮</button>
		</div>
		
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					name:'onlifes'
				},
                 // 在 methods 对象中定义方法
				methods:{
					greet:function (){
						// 事件当中,this 指向的是当前 vue 实例
						// console.log('greet')
						console.log(this.name);
						this.name='zwj'
					}
				}
			})
		</script>
	</body>
</html>

案例:切换元素背景颜色

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
		<title></title>
		<style>
			.base{
				width: 300px;
				height: 300px;
				border: 1px solid #000000;
				background-color: aliceblue;
			}
			
			.active{
				background-color: blue;
			}
		</style>
	</head>
	<body>
		<div id="app">
			<div v-on:click="changeBg" class="base" :class="{active:isActive}"></div>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					isActive:false,
				},
				methods:{
					changeBg:function(){
						this.isActive = !this.isActive
					}
				}
			})
		</script>
	</body>
</html>

任务单.gif

案例:切换壁纸

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<style>
			*{
				margin: 0;
				padding: 0;
			}
			html,
			body,
			#app{
				height: 100%;
			}
			nav{
				height: 150px;
				background-color: rgba(0, 0, 0, .2);
				text-align: center;
			}
			img{
				height: 120px;
				margin: 15px auto;
				
			}
		</style>
	</head>
	<body>
		<div id="app" :style="{backgroundImage: BgImg}">
			<nav>
				<img src="images/1.jpg" v-on:click="changeBg('images/1.jpg')" alt="">
				<img src="images/2.jpg" v-on:click="changeBg('images/2.jpg')" alt="">
				<img src="images/3.jpg" v-on:click="changeBg('images/3.jpg')" alt="">
			</nav>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					BgImg: 'url("images/1.jpg")'
				},
				methods:{
					changeBg:function(url){
						console.log(url);
						// 切换 BgImg 的值为当前点击的图片的路径
						this.BgImg = `url(${url})`
						
					}
				}
			})
		</script>
	</body>
</html>

任务单.gif

计算属性

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如:

<div id="app">
	{{number.split('').reverse().join('')}}
</div>

在这个地方,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。

所以,对于任何复杂逻辑,你都应当使用计算属性

  • 计算属性内部一定要使用 return 返回一个值
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />

		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<!-- {{number.split('').reverse().join('')}} -->
			<!-- {{reversednumbers}} -->
			<table>
				<thead>
					<tr>
						<th>编号</th>
						<th>标题</th>
						<th>发表时间</th>
					</tr>
				</thead>
				<tbody>
					<tr v-for="item in blogListFormat">
						<td>{{item.id}}</td>
						<td>{{item.title}}</td>
						<td>{{item.create_time}}</td>
					</tr>
				</tbody>
			</table>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
		<script>
			/*
			data 中和 compoted 中都叫做属性,也就是说 numbers 和 reversednumbers 都叫做属性
			1、data 中的属性的值是一个数字、字符串、对象、数组等静态值
			2、compited 中的属性叫做计算属性,其值是一个匿名函数
			3、匿名函数一定要返回一个值,作为这个属性的值
			.*/

			let app = new Vue({
				el: '#app',
				data: {
					numbers: '12345',
					blogList: [{
							id: 1,
							title: '今天真的好冷',
							create_time: 1614665768000
						},
						{
							id: 2,
							title: '明天气温就回升了',
							create_time: 1614579284000
						},
						{
							id: 3,
							title: '春暖花开',
							create_time: 1583043284000
						}
					]
				},
				// 用户自定义的方法
				methods: {
					// formatTime: function (a) {
					//   return a
					// }
				},
				// 计算属性
				computed: {
					reversednumbers: function() {
						// 计算属性内部一定要使用 return 返回一个值
						return this.numbers.split('').reverse().join('')
					},
					blogListFormat: function() {
						this.blogList.forEach(item => {
							item.create_time = moment((item.create_time)).format('YYYY-MM-DD HH:mm:ss')
						})
						return this.blogList
					}
				}
			})
		</script>
	</body>
</html>

data 中和 compoted 中都叫做属性,也就是说 numbers 和 reversednumbers 都叫做属性

  1. data 中的属性的值是一个数字、字符串、对象、数组等静态值
  2. compited 中的属性叫做计算属性,其值是一个匿名函数
  3. 匿名函数一定要返回一个值,作为这个属性的值

格式化日期

进入moment文件,进行格式化时间
第一种:引入在线的moment文件
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
第二种:引入保存到本地的moment文件
<script src="../moment.min.js"></script>
第三种:引入 npm 安装的moment文件

如果是以 npm 方式安装的 moment 不能使用 <scrpit src="">的方式引入,应使用 import 的方式引入,并且要设置 script 的类型为module

import moment from '../node_modules/moment/dist/moment.js'

image.png

格式化时间的方法
使用计算属性格式化日期

image.png

使用方法格式化日期
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<table>
				<thead>
					<tr>
						<th>编号</th>
						<th>标题</th>
						<th>发表时间</th>
					</tr>
				</thead>
				<tbody>
					<tr v-for="item in blogList">
						<td>{{item.id}}</td>
						<td>{{item.title}}</td>
						<td>{{formatTime(item.create_time)}}</td>
					</tr>
				</tbody>
			</table>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
		<script>
			// 我们为什么需要缓存?
			// 假设我们有一个性能开销比较大的计算属性A,
			// 它需要遍历一个巨大的数组并做大呈的计算。
			// 然后我们可能有其他的计算属性依赖于A。
			// 如果没有缓存,我们将不可避免的多次执行A的 getter!
			// 如果你不希望有缓存,请用方法来替代
			
			// 一句话总结:希望缓存的话用计算,不希望缓存用方法
			let app = new Vue({
				el: '#app',
				data: {
					numbers: '12345',
					blogList: [{
							id: 1,
							title: '今天真的好冷',
							create_time: 1614665768000
						},
						{
							id: 2,
							title: '明天气温就回升了',
							create_time: 1614579284000
						},
						{
							id: 3,
							title: '春暖花开',
							create_time: 1583043284000
						}
					]
				},
				// 用户自定义的方法
				methods: {
					formatTime: function (a) {
					  // return a
					  // console.log(a)
					  return moment(a).format('YYYY-MM-DD HH:mm:ss')
					}
				},
			})
		</script>
	</body>
</html>

注意:控制台中修改 create_time 的值测试计算属性,注意不能加上引号

我们为什么需要缓存?

假设我们有一个性能开销比较大的计算属性A,它需要遍历一个巨大的数组并做大呈的计算。然后我们可能有其他的计算属性依赖于A。如果没有缓存,我们将不可避免的多次执行A的 getter!如果你不希望有缓存,请用方法来替代
一句话总结:希望缓存的话用计算,不希望缓存用方法

侦听属性

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。

侦听器

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

vue 中的计算属性是不支持异步操作的,只能计算一些同步的值,但是可以使用 vue-async-computed 插件实现这一点

那为什么不直接使用侦听器呢?

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<input type="text" v-model="age" />
			<br/>
			<span>{{message}}</span>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			/**
			 * 用户在文本框中输入年龄
			 * 1.如果年龄 >=12,span中就显示“12岁已经到了可以入刑的年龄”
			 * 2. 如果年龄 <12,span中显示“还不到入刑年龄”
			 * */
			
			// 侦听器侦听的是data里面的属性
			let app = new Vue({
				el: '#app',
				data: {
					age:20,
					message:''
				},
				// 如果你想自定义方法,放到methods中
				methods:{},
				// 如果你想加计算属性,放到computed中
				computed:{},
				// 如果自己定义侦听器,放到watch中
				watch:{
					age:function(newValue,oldValue){
						// 当 age 属性发生变化时,就会执行这个函数
						// console.log('excute');
						// console.log(newValue,oldValue)
						if(newValue >= 12){
							this.message='大于12岁已经到了入刑年龄'
						}else{
							this.message='还不到入刑年龄'
						}
						
					}
				}
			})
		</script>
	</body>
</html>
计算属性实现侦听器功能
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<input type="text" v-model="age" />
			<br/>
			<span>{{ageInfo}}</span>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			/**
			 * 用户在文本框中输入年龄
			 * 1.如果年龄 >=12,span中就显示“12岁已经到了可以入刑的年龄”
			 * 2. 如果年龄 <12,span中显示“还不到入刑年龄”
			 * */
			
			// 侦听器侦听的是data里面的属性
			let app = new Vue({
				el: '#app',
				data: {
					age:20,
					message:''
				},
				// 如果你想自定义方法,放到methods中
				methods:{},
				// 如果你想加计算属性,放到computed中
				computed:{
					ageInfo: function(){
						if(this.age >=12){
							return '大于12岁已经到了入刑年龄'
						}else{
							return '还不到入刑年龄'
						}
					}
				},
			})
		</script>
	</body>
</html>
搜索建议案例
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>
	<body>
		<div id="app">
			<label for="question">请输入你的问题</label>
			<input type="text" v-model="question">
			<ul>
			  <li v-for="item in answer">
			    {{item}}
			  </li>
			</ul>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					question:'',
					answer:'',
				},
				methods:{
					getAnswer:function(){
						// console.log(this.question);
						axios.get('http://localhost:3000/answer.php?q=' + this.question)
						 .then(function(response){
							 console.log(response);
						 })
					}
				},
				watch:{
					question:function(newValue){
						this.getAnswer()
					}
				}
			})
		</script>
	</body>
</html>

php代码

<?php
// 获取传递过来的数据
$question=$_GET['q'];
$answer=[];
switch($question){
  case '李':
    $answer=['李白','李一桐','李商隐'];
  break;
  case '李一':
    $answer=['李一山','李一桐','李一波'];
  break;
  case '李一桐':
    $answer=['李一桐深夜未归','李一桐深夜买醉','李一桐到底是谁'];
  break;
}
echo json_encode($answer);

表格的CURD

使用 BS添加样式版表格

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Vue对列表进行增删改查</title>
		<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.min.css" rel="stylesheet">
		<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
		<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.min.js"></script>

	</head>
	<body>
		<div id="app">
			<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal" data-whatever="@mdo">添加</button>
			<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel">
				<div class="modal-dialog" role="document">
					<div class="modal-content">
						<div class="modal-header">
							<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
						</div>
						<div class="modal-body">
							<form>
								<div class="form-group">
									<label for="name" class="control-label">姓名</label>
									<input type="text" class="form-control" id="name" v-model="name">
								</div>
								<div class="form-group">
									<label for="message-text" class="control-label">年龄</label>
									<input type="text" class="form-control" id="message-text" v-model="age">
								</div>
								<div class="form-group">
									<label for="chengji" class="control-label">成绩</label>
									<input type="text" class="form-control" id="chengji" v-model="cheng">
								</div>
							</form>
						</div>
						<div class="modal-footer">
							<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
							<button type="button" class="btn btn-primary" v-on:click="add">添加</button>
							<button type="button" class="btn btn-primary" v-on:click="upde">更新</button>
						</div>
					</div>
				</div>
			</div>
			<table class="table">
				<thead>
					<tr>
						<th>编号</th>
						<th>姓名</th>
						<th>年龄</th>
						<th>成绩</th>
						<th>操作</th>
					</tr>
				</thead>
				<tbody v-for="(item,index) in blogList">
					<tr>
						<td>{{index+1}}</td>
						<td>{{item.name}}</td>
						<td>{{item.age}}</td>
						<td>{{item.cheng}}</td>
						<td>
							<button class="btn btn-success" v-on:click="update(index)" data-toggle="modal" data-target="#exampleModal"
							 data-whatever="@mdo">编辑</button>
							<button class="btn btn-warning" v-on:click="del(index)">删除</button>
						</td>
					</tr>
				</tbody>
			</table>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					blogList: [{
							id: 1,
							name: 'Jin',
							age: '14',
							cheng: '564'
						},
						{
							id: 2,
							name: 'Jan',
							age: '20',
							cheng: '464'
						},
						{
							id: 3,
							name: 'Jone',
							age: '18',
							cheng: '380'
						}
					],
					ss:'',
					name: '',
					age: '',
					cheng: '',
				},
				methods: {
					add: function() {
						let obj = {
							name: this.name,
							age: this.age,
							cheng: this.cheng,
						}
						this.name = ''
						this.age = ''
						this.cheng = ''
						this.blogList.push(obj)
					},
					update: function(id) {
						this.ss = id
						this.name = this.blogList[id].name
						this.age = this.blogList[id].age
						this.cheng = this.blogList[id].cheng
					},
					upde : function(){
						this.blogList[this.ss].name = this.name
						this.blogList[this.ss].age = this.age
						this.blogList[this.ss].cheng = this.cheng 
						// console.log(this.blogList[this.ss])
						// console.log(this.name)
						this.name = ''
						this.age = ''
						this.cheng = ''
					},
					// 删除
					del: function(id) {
						this.blogList.splice(id, 1),
						console.log(this.blogList);
					},
				}
			})
		</script>
	</body>
</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGhALS9c-1615345381465)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/958a95ff-bd21-4c87-a140-7b0bf638bf77.gif)]

简陋版表格

<!DOCTYPE html>
<html lang="en">

	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<style>
			html,
			body,
			#app {
				height: 100%;
			}

			.popular {
				position: absolute;
				left: 50%;
				top: 50%;
				transform: translate(-50%, -50%);
				width: 500px;
				height: 200px;
				background-color: #ccc;
			}
			[v-cloak]{
				display:none;
			}
		</style>
	</head>

	<body>
		<div id="app" v-on:click="close">
			<!-- 阻止单击事件继续传播,防止事件冒泡 -->
			<button v-on:click.stop="add">Add</button>
			<table v-cloak>
				<thead>
					<tr>
						<th>编号</th>
						<th>标题</th>
						<th>创建时间</th>
						<th>操作</th>
					</tr>
				</thead>
				<tbody>
					<tr v-for="item in blogList">
						<td>{{item.id}}</td>
						<td>{{item.title}}</td>
						<td>{{formatTime(item.create_time)}}</td>
						<td>
							<button @click="edite(item.id)">编辑</button>
							<button @click="handleDel(item.id)">删除</button>
						</td>
					</tr>
				</tbody>
			</table>
			<!-- 博客新增弹出层 -->
			<!-- <div class="popular" :style="{display:add_isActive?'block':'none'}"> -->
			<div class="popular" v-show="add_isActive" v-cloak>
				<label for="标题">博客标题:</label>
				<input type="text" id="title" v-model="title" ref="inputs" />
				<button v-on:click="handleAdd">Submit</button>
			</div>

			<!-- 博客编辑弹出层 -->
			<!-- <div class="popular" :style="{display:add_isActive?'block':'none'}"> -->
			<div class="popular" v-show="edite_isActive" v-cloak>
				<label for="标题">博客标题:</label>
				<input type="text" id="title" v-model="title" ref="inputs" />
				<button @click="handleEdite">Submit</button>
			</div>

		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					blogList: [{
							id: 1,
							title: '今天真的好冷',
							create_time: 1614665768000
						},
						{
							id: 2,
							title: '明天气温就回升了',
							create_time: 1614579284000
						},
						{
							id: 3,
							title: '春暖花开',
							create_time: 1583043284000
						}
					],
					add_isActive: false, //控制博客新增层是否显示:false:隐藏,true:显示
					edite_isActive: false, //控制博客编辑层是否显示:false

					title: "", //用户输入的信息
					maxId: 3, //当前博客的最大的id
					editeId: -1, // 待编辑的博客id
				},
				methods: {
					formatTime: function(arg) {
						return moment(arg).format('YYYY-MM-DD HH:mm:ss')
					},
					// 显示新增博客层
					add: function() {
						this.add_isActive = true
						// 使用Vue.nextTick实现进入页面文本框自动获取焦点
						//因为 mounted 阶段 dom 并未渲染完毕,所以需要$nextTick
						this.$nextTick(() => {
							this.$refs.inputs.focus() //通过 $refs 获取dom 并绑定 focus 方法
						})
					},
					// 关闭博客层编辑层
					close: function(event) {
						if (event.target.id == 'app') {
							this.add_isActive = false;
							this.edite_isActive = false;
						}
					},
					// 新增数据
					handleAdd: function() {
						this.maxId++
						let obj = {
							id: this.maxId,
							title: this.title,
							create_time: Date.now()
						}
						this.blogList.push(obj)
						this.add_isActive = false
						this.title = ''
					},
					// 显示编辑数据
					edite: function(id) {
						// 编辑的id 等于 获取的 id
						this.editeId = id
						this.edite_isActive = true
						// 使用Vue.nextTick实现进入页面文本框自动获取焦点
						//因为 mounted 阶段 dom 并未渲染完毕,所以需要$nextTick
						this.$nextTick(() => {
							this.$refs.inputs.focus() //通过 $refs 获取dom 并绑定 focus 方法
						})
						// 获取到待编辑博客的id
						let obj = this.blogList.find(item => {
							return item.id == id
						})
						this.title = obj.title
					},
					// 编辑数据
					handleEdite: function() {
						this.blogList.forEach(item => {
							if (item.id == this.editeId) {
								item.title = this.title
							}
						})
						this.edite_isActive = false
						this.title = ''
					},
					// 删除数据
					handleDel:function(id){
						function f1(arg){
							// item.id == id等于比较
							return arg.id == id
						}
						//findIndex()方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置
						let index = this.blogList.findIndex(f1)
						// console.log(index);
						this.blogList.splice(index,1)
						
						
					}
				}
			})
		</script>
	</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QvwKIZ04-1615345381466)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/2edb542a-51f5-4cca-836e-693515b84094.gif)]

组件版

<!DOCTYPE html>
<html lang="en">

	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<!-- 引入样式 -->
		<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">

		<style>
			html,
			body,
			#app {
				height: 100%;
			}

			.popular {
				position: absolute;
				left: 50%;
				top: 50%;
				transform: translate(-50%, -50%);
				width: 500px;
				height: 200px;
				background-color: #ccc;
			}

			[v-cloak] {
				display: none;
			}
		</style>
	</head>

	<body>
		<div id="app">
			<el-button type="primary" v-on:click.stop="add">Add</el-button>
			<el-table :data="blogList" style="width: 100%">
				<el-table-column prop="id" label="编号" width="180">
				</el-table-column>
				<el-table-column prop="title" label="标题" width="180">
				</el-table-column>
				<el-table-column prop="create_time" label="发表时间">
				</el-table-column>
				<el-table-column label="操作">
					<template slot-scope="scope">
						<el-button size="mini" @click="edite(scope.row.id)">编辑</el-button>
						<el-button size="mini" type="danger" @click="handleDel(scope.row.id)">删除</el-button>
					</template>
				</el-table-column>
			</el-table>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<!-- 引入组件库 -->
		<script src="https://unpkg.com/element-ui/lib/index.js"></script>
		<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
		<script>
			let app = new Vue({
				el: '#app',
				data: {
					blogList: [{
							id: 1,
							title: '今天真的好冷',
							create_time: 1614665768000
						},
						{
							id: 2,
							title: '明天气温就回升了',
							create_time: 1614579284000
						},
						{
							id: 3,
							title: '春暖花开',
							create_time: 1583043284000
						}
					],
					title: "", //用户输入的信息
					maxId: 3, //当前博客的最大的id
					editeId: -1, // 待编辑的博客id
				},
				methods: {
					formatTime: function(arg) {
						return moment(arg).format('YYYY-MM-DD HH:mm:ss')
					},
					// 显示新增博客层
					add: function() {
						this.$prompt('请输入标题', '提示', {
							confirmButtonText: '确定',
							cancelButtonText: '取消',
							inputPattern: /\w+/,
							inputErrorMessage: '标题不能为空'
						}).then(({
							value
						}) => {
							this.maxId++
							let obj = {
								id: this.maxId,
								title: value,
								create_time: Date.now()
							}
							this.blogList.push(obj)

							this.$message({
								type: 'success',
								message: '新增博客成功'
							});
						}).catch(() => {
							this.$message({
								type: 'info',
								message: '取消输入'
							});
						});
					},
					// 显示编辑数据
					edite: function(id) {
						this.editeId = id
						// 获取到待编辑博客的id
						let obj = this.blogList.find(item => {
							return item.id == id
						})
						console.log(obj)
						this.$prompt('请输入标题', '提示', {
							confirmButtonText: '确定',
							inputPattern: /\w+/,
							inputErrorMessage: '标题不能为空'
						}).then(({
							value
						}) => {
							this.blogList.forEach(item => {
								if (item.id == this.editeId) {
									item.title = value
								}
							})
							this.$message({
								type: 'success',
								message: '修改成功'
							});
						}).catch(() => {
							this.$message({
								type: 'info',
								message: '取消输入'
							});
						});
						document.querySelector('.el-input__inner').value = obj.title
					},
					// 删除数据
					handleDel: function(id) {
						function f1(arg) {
							// item.id == id等于比较
							return arg.id == id
						}
						//findIndex()方法返回传入一个测试条件(函数)符合条件的数组第一个元素位置
						let index = this.blogList.findIndex(f1)
						// console.log(index);
						this.blogList.splice(index, 1)


					}
				}
			})
		</script>
	</body>

</html>

任务单.gif

文本框自动获取焦点

使用Vue.nextTick实现进入页面文本框自动获取焦点
  • 场景:页面加载时需要让文本框获取焦点
  • 用法:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
<input type="text" id="title" v-model="title" ref="inputs" />
mounted(){ //因为 mounted 阶段 dom 并未渲染完毕,所以需要$nextTick
  this.$nextTick(() => {
    this.$refs.inputs.focus() //通过 $refs 获取dom 并绑定 focus 方法
  })
}

vue页面加载闪烁问题的解决方法

方法一:v-cloak

v-cloak指令和css规则如[v-cloak]{display:none}一起用时,这个指令可以隐藏未编译的Mustache标签直到实例准备完毕。
v-cloak 指令可以像css选择器一样绑定一套css样式然后这套css会一直生效到实例编译结束。

[v-cloak]{ //在css里添加	
    display:none;
}

<div> 不会显示,直到编译结束。v-cloak并不需要添加到每个渲染数据的标签上,只要在el挂载的标签上添加就可以

<div class="app" v-cloak>
    {{ message }}
</div>

但是有的时候会不起作用,可能的原因如下:

  1. v-cloak的display属性被层级更高的给覆盖掉了,所以要提高层级

    [v-cloak] {
        display: none !important;
    } 
    
  2. 样式放在了@import引入的css文件中

  3. v-cloak的这个样式放在@import 引入的css文件中不起作用,可以放在link引入的css文件里或者内联样式中

方法二: v-text指令

vue中我们会将数据包在两个大括号中,然后放到HTML里,但是在vue内部,所有的双括号 ( {{}} ) 会被编译成textNode的一个v-text指令。

而使用v-text的好处就是永远更好的性能,更重要的是可以避免FOUC (Flash of Uncompiled Content) ,也就是上面与遇到的问题。

<span v-text="message"></span>
<!-- same as -->
<span>{{message}}</span>

组件

什么是组件

组件是可复用的 Vue 实例,且带有一个名字。组件的出现是为了拆分 Vue 实例的代码量的,能够让我们以不同的组件,来划分不同的功能模块,将来我们需要什么样的功能,就可以去调用对应对的组件即可。

总结:随着SPA(单页面应用)的流行,组件化开始变得越来越重要

确切的说,只要有UI层的展示,就必定有可以组件化的地方。简单来说,组件就是将一段UI样式和其对应的功能作为独立的整体去看待,无论这个整体放在哪里去使用,它都具有一样的功能和样式,从而实现复用,这种整体化的细想就是组件化。不难看出,组件化设计就是为了增加复用性,灵活性,提高系统设计,从而提高开发效率

组件注册分为全局注册和局部注册

推荐使用局部注册,因为说到底组件就是一段结构+样式+脚本的代码,如果是全局组件,无论当前组件中是否使用此组件,最终都会被下载,如果局部组件,不使用就不会下载

组件是可复用的 Vue 实例,且带有一个名字:

在这个例子中是 <button-counter>。我们可以在一个通过 new Vue 创建的 Vue 根实例中,把这个组件作为自定义元素来使用:

<div id="components-demo">
  <button-counter></button-counter>
</div>
new Vue({ el: '#components-demo' })

因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

注册组件和使用

创建组件

创建组件=》注册组件=》使用组件

 Vue.component('list-item', {
      template: `<li>first</li>`
    })
  1. 参数1:组件名称
  2. 参数2:组件配置项
  3. template:组件Dom 结构
组件的注册:
  1. 为了能在模板中使用,这些组件必须先注册以便Vue能够识别。
  2. 这里有两种组件的注册类型:全局注册和局部注册。
  3. 至此,我们的组件都只是通过Vue.component全局注册的:
    • 全局注册的组件可以用在其被注册之后的任何(通过new vue )新创建的Vue根实例,也包括其组件树中的所有子组件的模板中。
    • 到目前为止,关于组件注册你需要了解的就这些了,如果你阅读完本页内容并掌握了它的内容,我们会推荐你再回来把组件注册读完。
定义组件的方式
  1. 利用 Vue.component来创建全局组件
  2. 利用定义第一个对象,来接受 组件的信息 定义一个局部组件
使用组件

在模板中直接使用即可

<div id="app">
    <list-item></list-item>
</div>
组件是vue实例
  1. 组件也是一个 vue 实例,这个 vue 实例与根实例的区别在于不能使用el、其他的如 data、computed等都可以使用
  2. 组件中的 data 的值应该是一个函数
  3. Vue.component 将一个组件进行了全局注册,所有的 vue 实例 都可以使用这个组件
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
</head>

<body>
    <div id="app">
		<yhb-button></yhb-button>
	</div>
	<div id="vm">
		<yhb-button></yhb-button>
	</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
		/**
		 * 1.组件也是一个 vue 实例,这个 vue 实例与根实例的区别在于不能使用el、其他的如 data、computed等都可以使用
		 * 2.组件中的 data 的值应该是一个函数
		 * 3.Vue.component 将一个组件进行了全局注册,所有的 vue 实例 都可以使用这个组件
		 * 
		 * 
		 * 
		 * 组件的注册:
		 * 1.为了能在模板中使用,这些组件必须先注册以便Vue能够识别。
		 * 2.这里有两种组件的注册类型:全局注册和局部注册。
		 * 2.至此,我们的组件都只是通过Vue.component全局注册的:
		 * 3.全局注册的组件可以用在其被注册之后的任何(通过new vue )新创建的Vue根实例,也包括其组件树中的所有子组件的模板中。
		 * 4.到目前为止,关于组件注册你需要了解的就这些了,如果你阅读完本页内容并掌握了它的内容,我们会推荐你再回来把组件注册读完。
		 * 
		 * 
		 * 

		 * */
		let btn = Vue.component('yhb-button',{
			template:`<button>自定义按钮</button>`,
			data:function(){
				return {
					msg:'hello'
				}
			},
			computed:{
				ReversMsg:function(){
					return this.msg.split('').reverse().join('')
				}
			}
		})
		
		// 创建了 vue 根实例 app
        let app = new Vue({
            el: '#app',
            data: {},
        })
		
		/**
		 * 再创建一个  vue 根实例
		 * */
		 
		 let vm = new Vue({
		     el: '#vm',
		     data: {},
		 })
    </script>
</body>

</html>

image.png

组件中的data

data 必须是一个函数

data 值是个函数,使用函数创建了一个局部作用域,从而隔离多个组件间的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Ia1hNwA-1615345381472)(http://qiniu.yaoyao.info/image-20201231163341360.png)]

当我们定义这个 <button-counter> 组件时,你可能会发现它的 data 并不是像这样直接提供一个对象:

data: {
  count: 0
}

取而代之的是,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:

data: function () {
  return {
    count: 0
  }
}

如果 Vue 没有这条规则,点击一个按钮就可能会像如下代码一样影响到其它所有实例

image.png

props总结

prop的命名

首先明白两点

  • js 中的变量不支持 - 连接符(post-title),但是可以使用驼峰写法(postTitle)或则使用 _ 连接符(post_title)
  • html 中不区分大小写,无论 html 中的属性是大写还是小写还是大小写混合,最终渲染时,都会转换成小写
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
</head>

<body>
	<h1 ID="1111">asdadasda</h1>
    <div id="app">
		<!-- html 中使用 props 中的属性 postTitle 时,应该换成等价的 post-title -->
		<list-item v-for="item in posts" :post-id="item.id" :post-title="item.title"></list-item>
	</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
		Vue.component('list-item',{
			props:['postId','postTitle']// 这里是 js 语法,所以可以使用 postTitle 作为属性名称,不能使用 post-title
			template:`<li><a href="#"></a></li>`
		})
        let app = new Vue({
            el: '#app',
            data: {
				posts:[
					{id:1,title:'今天很冷'},
					{id:2,title:'明天会更冷'},
				]
			},
        })
    </script>
</body>

</html>
解决方案有两种
  • props 中的属性和在html中使用此属性时都是用 _ 连接符
  • props 中定义属性时使用驼峰写法,html 中使用属性时,使用 - 连接符,这两个属性是等价的

img

prop的类型
props: {
	post_id: Number,
    postTitle: String
},

如果传入的类型不一致,虽然也可以显示,但是会在控制台输出警告信息

所以 prop 的类型检查主要是为了当你的组件给别人使用时,别人可以根据类型传入数据,如果自己使用,是否设置就无所谓了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qfIZdUqn-1615345381477)(http://qiniu.yaoyao.info/20210305084926.png)]

prop的值不能直接修改

对于 props 中定义的 prop 来说,应该只是接受来自父组件的值,而不能直接在子组件中修改,但是可以使用计算属性达到我们想要的值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sRupqwlj-1615345381479)(http://qiniu.yaoyao.info/20210305091632.png)]

案例:博文项组件
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
	<style>
		*{
			margin: 0;
			padding: 0;
		}
		ul{
			list-style-type: none;
			width: 300px;
		}
		li{
			width: 100%;
			display: flex;
			margin: 10px 0;
			padding-left: 20px;
		}
		.postInfo{
			margin-left: 30px;
		}
		.postInfo div{
			color: ccc;
			font-size: 12px;
			display: flex;
			justify-content: space-between;
		}
	</style>
</head>

<body>
    <div id="app">
		<ul>
			<post-item v-for="item in posts" :title="item.title" :id="item.id" :author="item.author"></post-item>
		</ul>
	</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
		Vue.component('post-item',{
           // 通过 props 接受所需要的父组件的值 
			props:['title','id','author'],
			template:`<li>
				<span>{{id}}</span>
				<div class="postInfo">
					<h5>{{title}}</h5>
					<div>
						<span>{{author}}</span><span>3244</span>
					</div>
				</div>
			</li>`
		})
        let app = new Vue({
            el: '#app',
            data: {
				posts:[
					{id:1,title:'今天太热了',author:'菜鸟码农'},
					{id:2,title:'不能开空调了',author:'大农哥'},
					{id:3,title:'冬天已经过去了,春姑娘马上来了',author:'拉面哥'}
				]
			},
        })
    </script>
</body>

</html>

遍历数据

当传递的数据是数组时,不会向上面根据索引获取对象,而是通过 v-for 指令遍历数据,生成组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p1FOA0wd-1615345381480)(http://qiniu.yaoyao.info/image-20201231172201371.png)]

遍历 list 组件
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
</head>

<body>
    <div id="app">
		<ul>
			<yhb-list name1="刘备"></yhb-list>
			<yhb-list name1="关羽"></yhb-list>
			<yhb-list name1="张飞"></yhb-list>
			<yhb-list :name1="name"></yhb-list>
		</ul>
		<ul>
			<yhb-list v-for="item in posts" :name1="item.title"></yhb-list>
		</ul>
	</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
		// 每一个组件都是一个 vue 实例
		Vue.component('yhb-list',{
			// props:['name1']
			props:{
				name1:String
			},
			template:`<li><a href="#">{{name1}}</a></li>`
			
		})
        let app = new Vue({
            el: '#app',
            data: {
				name:'赵云',
				posts:[
					{id:1,title:'今天气温将上升到18°'},
					{id:2,title:'冬天已经过去,春姑娘马上要来了'}
				]
			},
        })
    </script>
</body>

</html>

组件间的传值

传值方式
传递静态数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8sgRZtC-1615345381481)(http://qiniu.yaoyao.info/image-20201231170257989.png)]

传递动态数据

父组件中传递的数据来自 data 或者计算属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6LcdZZ3F-1615345381482)(http://qiniu.yaoyao.info/image-20201231170922579.png)]

v-bind 指令的作用是:告诉vue 值是一个js表达式,而不是一个普通字符串,既然是js表达式,就会进行js解析,所以才可以传递动态数据

传值方式 有 :父组件向子组件传值,子组件向父组件传值,非关系组件传值。

引用官网的一句话:父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息,如下图所示:

20180110222712301 1.png

传递静态或动态 Prop

像这样,你已经知道了可以像这样给 prop 传入一个静态的值:

<blog-post title="My journey with Vue"></blog-post>

你也知道 prop 可以通过 v-bind 动态赋值,例如:

<!-- 动态赋予一个变量的值 -->
<blog-post v-bind:title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post
  v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

在上述两个示例中,我们传入的值都是字符串类型的,但实际上 *任何 *类型的值都可以传给一个 prop。

传值方向
父组件向子组件传值:
  • 父组件向子组件传递方法

    1. 父组件向子组件传递方法使用事件绑定机制,当自定义一个事件属性后,子组件就可以调用传递的这个方法
    2. 使用$emit(‘父组件中的方法’)触发父组件传过来的方法
    3. $emit(‘父组件中的方法’,)中的第二个参数往后都为需要传递的参数
    4. 子组件可以通过$emit传递自身的数据,父组件也可以将接收的子组件的数据存到自身的data属性中
  • <div id='app'>
         <!-- 父组件可以在引用子组件的同时,通过属性绑定的形式把父组件的数据传递给子组件 -->
         <com1 :parentmsg="msg"></com1>
     </div>
     <script>
         let app = new Vue({
           el: '#app',
           data: {
             msg: '这是父组件的数据',
           },
           //创建一个子组件
           components: {
             com1: {
               template: '<h1>这是子组件--{{parentmsg}}</h1>',
               data() {
                 return {
                 }
               },
               // 把父组件传递过来的数据,先在props数组中定义一下,这样才能使用这个数据
               props: ['parentmsg']
             }
           }
         });
     </script>
    
  • 父组件向子组件传值(data与props的区别)

    1. 子组件默认无法访问到父组件中data上的数据和methods中的方法

    2. 父组件可以在引用子组件的同时通过属性绑定(v-bind)的形式把需要传递给子组件的数据传给子组件

    3. 父组件传过来的属性需要先在props数组中定义一下(与属性的名字相同),这样才能使用这个数据

    4. 组件中的所有props中的数据都是通过父组件传递给子组件的

    5. props中的数据都是只读的无法重新赋值

    6. 子组件中的data数据并不是父组件传递过来的,是子组件私有的,例如:子组件通过ajax请求回来的数据,可以放到data身上

    7. 子组件接收的父组件的值分为引用类型和普通类型两种:

      • 普通类型:字符串(String)、数字(Number)、布尔值(Boolean)、空(Null)
      • 引用类型:数组(Array)、对象(Object)
    8. 基于 vue 的 单向数据流 ,即组件之间的数据是单向流通的,子组件是不允许直接对父组件传来的值进行修改的,所以应该避免这种直接修改父组件传过来的值的操作,否则控制台会报错

      • 如果传过来的值是简单数据类型,是可以在子组件中修改,也不会影响其他兄弟组件内同样调用了来自该父组件的值。

      • 具体操作是可以先 把传过来的值重新赋值给data中的一个变量,然后再更改那个变量

      // 子组件
      export default {
          props: ['myName'],
          data() {
              return {
                  name : this.myName    // 把传过来的值赋值给新的变量
              }
          },
          watch: {
              myName(newVal) {
                  this.name = newVal //对父组件传过来的值进行监听,如果改变也对子组件内部的值进行改变
              }
          },
          methods: {
              changeName() {  
                  this.name = 'Lily'  // 这里修改的只是自己内部的值,就不会报错了
              },
          }
      }
      
    • 注: 如果不使用 watch 来监听父组件传递的 myName 值,子组件中的 name 值是不会随着父组件的 myName 值进行改变,因为 data 中 name: this.myName 仅仅只是定义了一个初始值。
    • 如果引用类型的值,当在子组件中修改后,父组件的也会修改,因其数据是公用的,其他同样引用了该值的子组件也会跟着被修改。可以理解成父组件传递给子组件的值,就相当于复制了一个副本,这个副本的指针还是指向父组件中的那个,即共享同一个引用。所以除非有特殊需要,否则不要轻易修改。

父传子的实现方式就是通过props属性,子组件通过props属性接收从父组件传过来的值,而父组件传值的时候使用 v-bind 将子组件中预留的变量名绑定为data里面的数据即可

image.png

子组件向父组件传值
1.子组件绑定一个事件,通过 this.$emit() 来触发
  1. 父组件的方法被传递到子组件内部,子组件在内部调用父组件传递过来的方法,同时把要发送给父组件的数据,当作参数传递过去;
  2. 子组件内部通过 this.$emit(‘方法名’, 要传给父元素的值) 方式,来调用父组件中的方法,同时把数据传递给父组件使用
  3. 通过在子组件注册这个事件 然后父组件在方法里面来接受注册的这个事件等于的方法 。父组件方法接受的参数就是子组件传过来的参数

子组件中需要以某种方式例如点击事件的方法来触发一个自定义事件;子组件给父组件传参用this.$emit(‘事件名’,携带的内容),父组件在相应的位置监听事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j5AQUm0l-1615345381483)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/c9a30803-49a6-485a-a772-f89d13fa2b48.png)]

2.通过 callback 函数

先在父组件中定义一个callback函数,并把 callback 函数传过去

// 父组件
<child :callback="callback"></child>

methods: {
    callback: function(name) {
        this.name = name
    }
}

在子组件中接收,并执行 callback 函数

// 子组件
<button @click="callback('Jack')">改变父组件的name</button>

props: {
    callback: Function,
}
3.通过 $parent / $children 或 $refs 访问组件实例

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。

// 子组件
export default {
  data () {
    return {
      title: '子组件'
    }
  },
  methods: {
    sayHello () {
        console.log('Hello');
    }
  }
}
// 父组件
<template>
  <child ref="childRef" />
</template>

<script>
  export default {
    created () {
      // 通过 $ref 来访问子组件
      console.log(this.$refs.childRef.title);  // 子组件
      this.$refs.childRef.sayHello(); // Hello
      
      // 通过 $children 来调用子组件的方法
      this.$children.sayHello(); // Hello 
    }
  }
</script>

注:这种方式的组件通信不能跨级。

非父子组件进行传值
  1. 创建一个空的 Vue 实例作为事件总线bus
  2. 使用 bus.$emit(‘名称’,数据) 进行传递数据
  3. 使用 bus.$on(名称’,function(val){val即为接收到的数据}) 接收数据

非父子组件之间传值,需要定义个公共的公共实例文件bus.js,作为中间仓库来传值,不然路由组件之间达不到传值的效果。

公共bus.js

//bus.js
import Vue from 'vue'
export default new Vue()

组件A:

<template>
  <div>
    A组件:
    <span>{{elementValue}}</span>
    <input type="button" value="点击触发" @click="elementByValue">
  </div>
</template>
<script>
  // 引入公共的bug,来做为中间传达的工具
  import Bus from './bus.js'
  export default {
    data () {
      return {
        elementValue: 4
      }
    },
    methods: {
      elementByValue: function () {
        Bus.$emit('val', this.elementValue)
      }
    }
  }
</script>

组件B:

<template>
  <div>
    B组件:
    <input type="button" value="点击触发" @click="getData">
    <span>{{name}}</span>
  </div>
</template>
<script>
  import Bus from './bus.js'
  export default {
    data () {
      return {
        name: 0
      }
    },
    mounted: function () {
      var vm = this
      // 用$on事件来接收参数
      Bus.$on('val', (data) => {
        console.log(data)
        vm.name = data
      })
    },
    methods: {
      getData: function () {
        this.name++
      }
    }
  }
</script>
兄弟组件之间传值
1.还是通过 $emit 和 props 结合的方式

在父组件中给要传值的两个兄弟组件都绑定要传的变量,并定义事件

// 父组件
<child-a :myName="name" />
<child-b :myName="name" @changeName="editName" />  
    
export default {
    data() {
        return {
            name: 'John'
        }
    },
    components: {
        'child-a': ChildA,
        'child-b': ChildB,
    },
    methods: {
        editName(name) {
            this.name = name
        },
    }
}

在子组件B中接收变量和绑定触发事件

// child-b 组件
<p>姓名:{{ myName }}</p>
<button @click="changeName">修改姓名</button>
    
<script>
export default {
    props: ["myName"],
    methods: {
        changeName() {
            this.$emit('changeName', 'Lily')   // 触发事件并传值
        }
    }
}
</script>
// child-a 组件
<p>姓名:{{ newName }}</p>
    
<script>
export default {
    props: ["myName"],
    computed: {
        newName() {
            if(this.myName) { // 判断是否有值传过来
                return this.myName
            }
            return 'John' //没有传值的默认值
        }
    }
}
</script>

即:当子组件B 通过 $emit() 触发了父组件的事件函数 editName,改变了父组件的变量name 值,父组件又可以把改变了的值通过 props 传递给子组件A,从而实现兄弟组件间数据传递。

2.通过一个空 vue 实例

创建一个 EventBus.js 文件,并暴露一个 vue 实例

import Vue from 'Vue'
export default new Vue()

在要传值的文件里导入这个空 vue 实例,绑定事件并通过 $emit 触发事件函数

(也可以在 main.js 中全局引入该 js 文件,我一般在需要使用到的组件中引入)

<template>
    <div>
        <p>姓名: {{ name }}</p>
        <button @click="changeName">修改姓名</button>
    </div>
</template>

<script>
import { EventBus } from "../EventBus.js"

export default {
 data() {
     return {
         name: 'John',
     }
  },
  methods: {
      changeName() {
          this.name = 'Lily'
          EventBus.$emit("editName", this.name) // 触发全局事件,并且把改变后的值传入事件函数
      }
    }
}
</script>

在接收传值的组件中也导入 vue 实例,通过 $on 监听回调,回调函数接收所有触发事件时传入的参数

import { EventBus } from "../EventBus.js"

export default {
    data() {
        return {
            name: ''
        }
    },
    created() {
         EventBus.$on('editName', (name) => {
             this.name = name
         })
    }
}

这种通过创建一个空的 vue 实例的方式,相当于创建了一个事件中心或者说是中转站,用来传递和接收事件。这种方式同样适用于任何组件间的通信,包括父子、兄弟、跨级,对于通信需求简单的项目比较方便,但对于更复杂的情况,或者项目比较大时,可以使用 vue 提供的更复杂的状态管理模式 Vuex 来进行处理。

3.使用 vuex

Vuex 原理简介

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x1IqfRW8-1615345381484)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/ba1a23f2-2f6b-41e0-aa3a-06f30fcb7a0d.png)]

Vuex 实现了一个单向数据流,在全局拥有一个 state 存放数据,当组件要更改 state 中的数据时,必须通过 mutations 进行,mutations 同时提供了订阅者模式供外部插件调用获取 state 数据的更新。而当所有异步操作(常用于调用后端接口异步获取数据)或批量的同步操作需要走 actions,但 actions 也是无法直接修改 state 的,还是需要通过触发 mutations 中的方法,然后 mutations 来修改 state 的数据。数据变更后相应推送给组件,组件重新渲染到视图上。

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法是在 vuex 里数据改变的时候把数据拷贝一份保存到 localStorage 里面,刷新之后如果 localStorage 里有保存的数据,取出来再替换 store 里的 state。

Vuex 是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间数据的共享

  • 能够在 Vuex 中集中管理共享的数据,易于开发和后期维护

  • 能够高效的实现组件之间的数据共享,提高开发效率

  • 存储在 Vuex 中的数据都是响应式的,能够实时保持数据与页面的同步

一般情况下,只有组件之间共享的数据,才有必要存储到 Vuex 中;对于组件的私有数据,依旧存储在组件自身的 data 中即可。

  • 多个视图依赖于同一个状态:例如多组件之间数据共享,在不同页面都可以拿到用户信息
  • 来自不同视图的行为需要改变同一个状态:比如用户会员信息,在不同页面可以更改

Vuex的基本使用

  1. 安装 Vuex 依赖包:

    npm install vuex --save //一定要加 –save,因为这个包在生产环境中也要使用的。
    
  2. 导入 Vuex 包:

import Vue from 'vue'
import Vuex from 'vuex'
// 挂载Vuex
Vue.use(Vuex)
  1. 创建 store 对象
const store = new Vuex.Store({
  // state 中存放的就是全局共享的数据
  state: {
    count: 0
  }
})
export default store;
  1. 将 store 对象挂载到 vue 实例中
new Vue({
    el: '#app',
    render: h => h(app),
    router,
    // 将创建的共享数据对象,挂载到 Vue 实例中
    // 所有的组件,就可以从 store 中获取全局的数据了
    store
})

具体关于 Vuex 的操做 https://www.cnblogs.com/dhui/p/12938083.html

多层父子组件传值:

有时需要实现通信的两个组件不是直接的父子组件,而是祖父和孙子,或者是跨越了更多层级的父子组件,这种时候就不可能由子组件一级一级的向上传递参数,特别是在组件层级比较深,嵌套比较多的情况下,需要传递的事件和属性较多,会导致代码很混乱。

这时就需要用到 vue 提供的更高阶的方法:provide/inject。

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效查 看 官 网

provide/inject:简单来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量,不管组件层级有多深,在父组件生效的生命周期内,这个变量就一直有效。

父组件:

export default {
  provide: { // 它的作用就是将 name 这个变量提供给它的所有子组件。
    name: 'Jack'
  }
}

子组件:

export default {
  inject: ['name'], // 注入了从父组件中提供的name变量
  mounted () {
    console.log(this.name);  // Jack
  }
}

注:provide 和 inject 绑定并不是可响应的。即父组件的name变化后,子组件不会跟着变。

如果想要实现 provide 和 inject 数据响应,有两种方法:

  • provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在后代组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如 props,methods
// 父组件 
<div>
      <button @click="changeName">修改姓名</button>
      <child-b />
</div>
<script>
    ......
    data() {
        return {
            name: "Jack"
        };
    },
    provide() {
        return {
            parentObj: this //提供祖先组件的实例
        };
    },
    methods: {
        changeName() {
            this.name = 'Lily'
        }
    }
</script>

后代组件中取值:

<template>
  <div class="border2">
    <P>姓名:{{parentObj.name}}</P>
  </div>
</template>
<script>
  export default {
    inject: {
      parentObj: {
        default: () => ({})
      }
    } // 或者inject: ['parentObj']
  };
</script>

注:这种方式在函数式组件中用的比较多。函数式组件,即无状态(没有响应式数据),无实例化(没有 this 上下文),内部也没有任何生命周期处理方法,所以渲染性能高,比较适合依赖外部数据传递而变化的组件。

  • 使用 Vue.observable 优化响应式 provide,这个我用的不熟就不说了,可以 → 官方文档
总结
  • 父子通信: 父向子传递数据是通过 props
  • 子向父是通过 $emit;通过 $parent / c h i l d r e n 通 信 ; children 通信; childrenref 也可以访问组件实例;provide / inject ;$attrs / $listeners
  • 兄弟通信: EventBus;Vuex;
  • 跨级通信: EventBus;Vuex;provide / inject ;$attrs / $listeners

组件中的事件

分析下面代码,为什么没有达到期待的效果?

组件事件
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="/vue.js"></script>
  <style>
    .active {
      color: red;
    }
  </style>
</head>

<body>
  <div id="app">
    <list-item v-for="(item,index) in posts" :key="index" :post="item"></list-item>

  </div>
  <script src="/node_modules/moment/moment.js"></script>
  <script src="/node_modules/axios/dist/axios.js"></script>
  <script>
    Vue.component('list-item', {
      data: function () {
        return {
          currentId: -1
        }
      },
      props: {
        post: Object
      },
      template: `<li @click="changeActive(post.id)" :class="{active:post.id==currentId}">{{currentId}}{{post.title}}</li>`,
      methods: {
        changeActive: function (id) {
          console.log(id);
          this.currentId = id
        }
      }

    })
    var vm = new Vue({
      el: '#app',
      data: {
        posts: [
          { id: 1, title: '元旦快乐' },
          { id: 2, title: '春节快乐' }
        ]
      }
    })
  </script>
</body>

</html>

每个组件实例中都维护着一个 currentId,所以会导致两个 li 标签都激活

如果两个组件维护一个共同的 currentId,就会产生互斥效果,也就是同一时刻只能有一个 Li 标签被激活

组建的互斥效果
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    .active {
      color: red;
    }
  </style>
</head>

<body>
  <div id="app">
    <ul>
      <list-item @change-active="handleActive" v-for="(item,index) in posts" :post="item" :current_id="currentId">
      </list-item>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>

    Vue.component('list-item', {
      /**
       * props 中的属性,如果是1个单词的话,可以直接小写,如果是两个单词,不要使用下面的驼峰写法(currentId)
       * 而应该使用 _ 连接起来
       */
      props: ['post', 'current_id'],
      template: `<li @click="changeActive(post.id)" :class="{active:current_id==post.id}">{{current_id}}-{{post.title}}</li>`,
      methods: {
        changeActive: function (id) {
          this.$emit('change-active', id)
        }
      }
    })
    let app = new Vue({
      el: '#app',
      data: {
        currentId: -1,
        posts: [
          { id: 1, title: '元旦快乐' },
          { id: 2, title: '春节快乐' }
        ]
      },
      methods: {
        handleActive: function (id) {
          this.currentId = id
        }
      }
    })
  </script>
</body>

</html>

任务单.gif

  • 父组件向子组件传值通过在组件中定义一个 props 属性
  • 子组件向父组件传值,通过 $emit 触发父组件中的自定义事件,同时将值作为参数传递

为组件绑定事件

组件虽小,五脏俱全

组件内部也可以有 methods等

一般情况下,组件的某个事件触发后,会通过$emit 方法向上广播,并可以传递参数过去,父组件在根组件上定义此自定义事件(不是js的原生组件),并定义事件处理函数

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
</head>

<body>
	<!-- <h1 ID="1111">asdadasda</h1> -->
    <div id="app">
		<!-- html 中使用 props 中的属性 postTitle 时,应该换成等价的 post-title -->
		<list-item v-for="item in posts" :key="item.id" :post_id="item.id" :post-title="item.title"
		  :create-time="item.create_time" @del-post="handleDel">
		  <!-- del-post 自定义事件 -->
		</list-item>
	</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
	<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
    <script>
		Vue.component('list-item', {
		  // props: ['post_id', 'postTitle'], // 这里是 js 语法,所以可以使用 postTitle 作为属性名称,不能使用 post-title
		  props: {
		    post_id: Number,
		    postTitle: String,
		    createTime: Number
		  },
		  data: function () {
		    return {
		
		    }
		  },
		  template: `<li><a href="#">{{post_id}}--{{postTitle}}--{{formatTime}}</a><button @click="del(post_id)">删除</button></li>`,
		  computed: {
		    formatTime: function () {
		      return moment(this.createTime).format('YYYY-MM-DD HH:mm:ss')
		    }
		  },
		  methods: {
		    del: function (id) {
		      // 向父组件发射一个事件,好让父组件能够监听到当前的事件
		      // 或者说是通过 emit 方法触发负组件中的 del-post 事件,进而执行绑定的事件处理函数
		      this.$emit('del-post', id)
		    }
		  }
		})
        let app = new Vue({
          el: '#app',
          data: {
            posts: [
              { id: 1, title: '今天很冷', create_time: Date.now() },
              { id: 2, title: '明天会更冷', create_time: Date.now() }
            ]
          },
          methods: {
            handleDel: function (id) {
              // 根据id找到符合要删除的元素在数组中的索引
              /**
               * findIndex 是从某个数组中找到某个符合条件的元素的索引
               * 
               * 此方法的参数是一个函数,在函数中编写匹配条件
               */
              // let index = this.posts.findIndex(item => {
              //   return item.id == id
              // })
              // this.posts.splice(index, 1)
        
              // 使用 forEach
              let index = this.posts.forEach((item, index) => {
                if (item.id == id) {
                  this.posts.splice(index, 1)
                }
              })
        
            }
          }
        })
    </script>
</body>

</html>

总结

  • 根组件上定义的是一个自定义事件,不是js原生事件,自定义事件由子组件通过 this.$emit 触发
  • 自定义事件名称,建议是上面的写法
根据id找到符合要删除的元素在数组中的索引
  • findIndex 是从某个数组中找到某个符合条件的元素的索引
  • 此方法的参数是一个函数,在函数中编写匹配条件
let index = this.posts.findIndex(item => {
    return item.id == id
})
this.posts.splice(index, 1)
使用 forEach
  • 在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的this值:
    • 如果是该函数是一个构造函数,this指针指向一个新的对象
    • 在严格模式下的函数调用下,this指向undefined。
    • 如果是该函数是一个对象的方法,则它的this指针指向这个对象。
    • 等等
    • This被证明是令人天烦的面向对象风格的编程。
    • 箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承
let index = this.posts.forEach((item, index) => {
                if (item.id == id) {
                  this.posts.splice(index, 1)
                }
              })

为根组件注册事件

https://cn.vuejs.org/v2/guide/components-custom-events.html#%E5%B0%86%E5%8E%9F%E7%94%9F%E4%BA%8B%E4%BB%B6%E7%BB%91%E5%AE%9A%E5%88%B0%E7%BB%84%E4%BB%B6

全局注册

到目前为止,我们只用过 Vue.component 来创建组件:

Vue.component('my-component-name', {
  // ... 选项 ...
})

这些组件是全局注册的。也就是说它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。比如:

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

new Vue({ el: '#app' })
<div id="app">
  <component-a></component-a>
  <component-b></component-b>
  <component-c></component-c>
</div>

在所有子组件中也是如此,也就是说这三个组件在各自内部也都可以相互使用。

局部注册

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
var ComponentC = { /* ... */ }

然后在 components 选项中定义你想要使用的组件:

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

或者也可以在某个组件的 components 属性中引入

对于 components 对象中的每个 property 来说,其 property 名就是自定义元素的名字,其 property 值就是这个组件的选项对象。

注意局部注册的组件在其子组件中不可用。例如,如果你希望 ComponentAComponentB 中可用,则你需要这样写:

var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

或者如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

注意在 ES2015+ 中,在对象中放一个类似 ComponentA 的变量名其实是 ComponentA: ComponentA 的缩写,即这个变量名同时是:

  • 用在模板中的自定义元素的名称
  • 包含了这个组件选项的变量名
案例

这里只是创建一个局部组件,并不能直接在 vue 实例中使用

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>

<body>
  <div id="app">
    <list-item></list-item>
  </div>
  <div id="vm">
    <aa></aa>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    /**
     * 这里只是创建一个局部组件,并不能直接在 vue 实例中使用
     */
    let listItem = {
      template: `<li><a href="#">home</a></li>`,
      data: function () {
        return {}
      },
      methods: {},
      computed: {},
      watch: {},
      props: {}
    }
    let app = new Vue({
      el: '#app',
      data: {},
      components: {
        'list-item': listItem
      }
    })

    let vm = new Vue({
      el: '#vm',
      components: {
        'aa': listItem
      }
    })
  </script>
</body>

</html>

组件嵌套

全局组件的嵌套

全局组件的嵌套很简单,在根组件中直接使用即可

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    nav {
      width: 500px;
      margin: 10px auto;
    }

    nav ul {
      list-style: none;
      overflow: hidden;
    }
  </style>
</head>

<body>

  <div id="app">
    <el-list :data="posts"></el-list>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    // 创建列表组件
    Vue.component('el-list', {
      props: ['data'],
      template: `<nav>
                  <ul>
                    <el-list-item v-for="item in data" :key="item.id" :title="item.title"></el-list-item>  
                  </ul>
                </nav>`
    })
    // 创建列表项组件
    Vue.component('el-list-item', {
      props: ['title'],
      template: `<li><a href="#">{{title}}</a></li>`
    })
    let app = new Vue({
      el: '#app',
      data: {
        posts: [
          { id: 1, title: '今天好热' },
          { id: 2, title: '明天更热' },
          { id: 3, title: '因为有空调' },
        ]
      },
    })
  </script>
</body>

</html>
局部组件的嵌套

如果是局部组件,原则就是哪个组件需要就在哪个组件中注册

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    nav {
      width: 500px;
      margin: 10px auto;
    }

    nav ul {
      list-style: none;
      overflow: hidden;
    }
  </style>
</head>

<body>

  <div id="app">
    <el-list :data="posts"></el-list>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    // 创建列表项组件
    let esListItem = {
      props: ['title'],
      template: `<li><a href="#">{{title}}</a></li>`
    }
    // 创建列表组件
    let elList = {
      props: ['data'],
      template: `<nav>
                  <ul>
                    <el-list-item v-for="item in data" :key="item.id" :title="item.title"></el-list-item>  
                  </ul>
                </nav>`,
      components: {
        'el-list-item': esListItem
      }
    }

    let app = new Vue({
      el: '#app',
      data: {
        posts: [
          { id: 1, title: '今天好热' },
          { id: 2, title: '明天更热' },
          { id: 3, title: '因为有空调' },
        ]
      },
      components: {
        'el-list': elList,
      }
    })
  </script>
</body>

</html>

注意:局部组件嵌套,子组件要在父组件上面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IRgvUQEt-1615345381485)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/8a5ea277-6775-4ffa-bd83-1180aecee9a5.png)]

案例:使用局部变量实现效果
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    .yhb-list {


      width: 1000px;
      padding: 10px;

    }

    .yhb-list ul {
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      list-style: none;
      overflow: hidden;
      width: 100%;
    }

    .list-item {
      width: 220px;
      height: 285px;
      padding: 10px;
    }

    a {
      text-decoration: none;
    }

    .list-item img {
      width: 100%;
    }

    .list-item p {
      margin-top: 5px;
    }
  </style>
</head>

<body>

  <div id="app">
    <yhb-list :data="videoList"></yhb-list>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

  <script>
    let listItem = {
      props: ['video'],
      template: `<li class="list-item">
                    <a href="#">
                      <img :src="video.imgUrl" alt="">
                      <p class="title">{{video.title}}</p>
                      <p v-if="video.create_time">{{video.create_time}}</p>
                      <p v-if="video.click">{{video.click}}</p>
                      <p>{{video.author}}</p>
                    </a>
                  </li>`
    }
    let yhbList = {
      props: ['data'],
      template: `<div class="yhb-list">
                    <ul>
                      <list-item v-for="item in data" :video="item"></list-item>
                    </ul>
                  </div>`,
      components: {
        'list-item': listItem
      }
    }


    let app = new Vue({
      el: '#app',
      data: {
        videoList: [
          {
            id: 1,
            imgUrl: './images/1614932159808.jpg',
            title: '女神来了特辑展示...',
            create_time: '2021-03-08 19:00"00',
            click: null,
            author: 'CSDN资讯'
          },
          {
            id: 2,
            imgUrl: './images/1614932159808.jpg',
            title: '数据处理:深度解析并...',
            create_time: '2021-03-09 20:00"00',
            click: null,
            author: 'CSDN资讯'
          },
          {
            id: 2,
            imgUrl: './images/1614932159808.jpg',
            title: '黑客第一步,从不用鼠标...',
            create_time: null,
            click: 106,
            author: 'CSDN资讯'
          },
          {
            id: 2,
            imgUrl: './images/1614932159808.jpg',
            title: '少女时代-All Night...',
            create_time: null,
            click: 73,
            author: '红原图科技'
          },
          {
            id: 1,
            imgUrl: './images/1614932159808.jpg',
            title: '女神来了特辑展示...',
            create_time: '2021-03-08 19:00"00',
            click: null,
            author: 'CSDN资讯'
          },
          {
            id: 2,
            imgUrl: './images/1614932159808.jpg',
            title: '数据处理:深度解析并...',
            create_time: '2021-03-09 20:00"00',
            click: null,
            author: 'CSDN资讯'
          },
          {
            id: 2,
            imgUrl: './images/1614932159808.jpg',
            title: '黑客第一步,从不用鼠标...',
            create_time: null,
            click: 106,
            author: 'CSDN资讯'
          },
          {
            id: 2,
            imgUrl: './images/1614932159808.jpg',
            title: '少女时代-All Night...',
            create_time: null,
            click: 73,
            author: '红原图科技'
          }
        ]
      },
      components: {
        'yhb-list': yhbList
      }
    })
  </script>
</body>

</html>

image.png

插槽

在 2.6.0 中,我们为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令)。它取代了 slotslot-scope 这两个目前已被废弃但未被移除且仍在文档中的 attribute。新语法的由来可查阅这份 RFC

什么是插槽?

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

插槽内容

一句话:插槽内可以是任意内容。

如下代码:

  1. 在子组件中放一个占位符:
let listItem1 = {
    template:`
				<div>
					<p></p>
					<p><slot></slot></p>
				</div>
			`
}
  1. 在父组件中给这个占位符填充内容:

    <list-item1>aaaaaa</list-item1>
    

这是比较简单的插槽案例

卡槽还可以直接调用从父组件中调用来的东西

子组件:

let listItem = {
	 template:`
				<li><slot>列表项</slot></li>
			`
}

父组件:

let app = new Vue({
            el: '#app',
            data: {
				title:'abc'
			},
			components: {
			  'list-item': listItem,
			}
        })

html代码:

<div id="app">
		<list-item>{{title}}</list-item>
</div>

image.png

完整案例:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
</head>

<body>
    <div id="app">
		<list-item>{{title}}</list-item>
		<list-item1>aaaaaa</list-item1>
	</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
		let listItem = {
			template:`
				<li><slot>列表项</slot></li>
			`
		}
		let listItem1 = {
			template:`
				<div>
					<p></p>
					<p><slot></slot></p>
				</div>
			`
		}
        let app = new Vue({
            el: '#app',
            data: {
				title:'abc'
			},
			components: {
			  'list-item': listItem,
			  'list-item1': listItem1
			}
        })
    </script>
</body>

</html>

image.png

插槽就是Vue实现的一套内容分发的API,将<slot></slot>元素作为承载分发内容的出口。

这句话的意思就是,没有插槽的情况下在组件标签内些一些内容是不起任何作用的,当我在组件中声明了slot元素后,在组件元素内写的内容

具名插槽

有时我们需要多个插槽。例如对于一个带有如下模板的 home 组件:

html结构:

<div class="container">
  <header>
    <!-- 我们希望把页头放这里 -->
  </header>
  <main>
    <!-- 我们希望把主要内容放这里 -->
  </main>
  <footer>
    <!-- 我们希望把页脚放这里 -->
  </footer>
</div>

home组件:

let home = {
    template: `
      	<header>
    	<!-- 我们希望把页头放这里 -->
  		</header>
  		<main>
    	<!-- 我们希望把主要内容放这里 -->
  		</main>
  		<footer>
    	<!-- 我们希望把页脚放这里 -->
  		</footer>
      `
    }
    let app = new Vue({
      el: '#app',
      data: {},
      components: {
        'home': home
      }
})

对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

一个不带 name<slot> 出口会带有隐含的名字“default”。

在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<home>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>
  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
 </home>

现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot<template> 中的内容都会被视为默认插槽的内容。

然而,如果你希望更明确一些,仍然可以在一个 <template> 中包裹默认插槽的内容:

<home>
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

  <template v-slot:default>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </template>

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
<home>

任何一种写法都会渲染出:

<div class="container">
  <header>
    <h1>Here might be a page title</h1>
  </header>
  <main>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
  </main>
  <footer>
    <p>Here's some contact info</p>
  </footer>
</div>

注意 v-slot 只能添加在 <template> (只有一种例外情况),这一点和已经废弃的 slot attribute 不同。

综合案例:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>

<body>
  <div id="app">
    <home>
      <template v-slot:header>aaaa</template>
      <template v-slot:main>bbbb</template>
      <template v-slot:footer>cccc</template>
    </home>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <script>
    let home = {
      template: `
      <div class="container">
        <header>
         <slot name="header"></slot>
        </header>
        <main>
          <!-- 我们希望把主要内容放这里 -->
          <slot name="main"></slot>
        </main>
        <footer>
          <!-- 我们希望把页脚放这里 -->
          <slot name="footer"></slot>
        </footer>
      </div>
      `
    }
    let app = new Vue({
      el: '#app',
      data: {},
      components: {
        'home': home
      }
    })
  </script>
</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OY3MI3Jb-1615345381488)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/6bd54f14-dedf-4b1f-9b89-1badb27ea415.png)]

将上面使用局部变量实现效果的案例改造成占位符方式
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    .yhb-list {


      width: 1000px;
      padding: 10px;

    }

    .yhb-list ul {
      display: flex;
      flex-direction: row;
      flex-wrap: wrap;
      list-style: none;
      overflow: hidden;
      width: 100%;
    }

    .list-item {
      width: 220px;
      height: 285px;
      padding: 10px;
    }

    a {
      text-decoration: none;
    }

    .list-item img {
      width: 100%;
    }

    .list-item p {
      margin-top: 5px;
    }
  </style>
</head>

<body>

  <div id="app">
    <yhb-list :data="videoList"></yhb-list>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

  <script>
    let listItem = {
      props: ['video'],
      template: `<li class="list-item">
                    <a href="#">
                      <img :src="video.imgUrl" alt="">
                      <p class="title"><slot name="title"></slot></p>
                      <p v-if="video.create_time"><slot name="create_time"></slot></p>
                      <p v-if="video.click"><slot name="click"></slot></p>
                      <p><slot name="author"></slot></p>
                    </a>
                  </li>`
    }
    let yhbList = {
      props: ['data'],
      template: `<div class="yhb-list">
                    <ul>
                      <list-item v-for="item in data" :video="item">
                        <template v-slot:title>{{item.title}}</template> 
                        <template v-slot:create_time>{{item.create_time}}</template> 
                        <template v-slot:click>{{item.click}}</template> 
                        <template v-slot:author>{{item.author}}</template> 
                      </list-item>
                    </ul>
                  </div>`,
      components: {
        'list-item': listItem
      }
    }


    let app = new Vue({
      el: '#app',
      data: {
      	videoList: [{
      		id: 1,
      		imgUrl: 'image/1614932159808.png',
      		title: '女神来了特辑....',
      		create_time: '2021-03-08 19:00"00',
      		click:null,
      		author: 'CSDN资讯'
      	},
      	{
      		id: 2,
      		imgUrl: 'image/1614932159808.png',
      		title: '数据处理:深度解析并...',
      		create_time: '2021-03-09 20:00"00',
      		click:null,
      		author: 'CSDN资讯'
      	},
      	{
      		id: 3,
      		imgUrl: 'image/1614932159808.png',
      		title: '黑客第一步,从不用鼠标....',
      		create_time: null,
      		click:106,
      		author: 'CSDN资讯'
      	},
      	{
      		id: 4,
      		imgUrl: 'image/1614932159808.png',
      		title: '小时代-All Night....',
      		create_time: null,
      		click:73,
      		author: '红原图科技'
      	},
      	{
      		id: 5,
      		imgUrl: 'image/1614932159808.png',
      		title: '女神来了特辑....',
      		create_time: '2021-03-08 19:00"00',
      		click:null,
      		author: 'CSDN资讯'
      	},
      	{
      		id: 6,
      		imgUrl: 'image/1614932159808.png',
      		title: '数据处理:深度解析并...',
      		create_time: '2021-03-09 20:00"00',
      		click:null,
      		author: 'CSDN资讯'
      	},
      	{
      		id: 7,
      		imgUrl: 'image/1614932159808.png',
      		title: '黑客第一步,从不用鼠标....',
      		create_time: null,
      		click:106,
      		author: 'CSDN资讯'
      	},
      	{
      		id: 8,
      		imgUrl: 'image/1614932159808.png',
      		title: '小时代-All Night....',
      		create_time: null,
      		click:73,
      		author: '红原图科技'
      	},
      	]
      },
      components: {
        'yhb-list': yhbList
      }
    })
  </script>
</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bvcvvEod-1615345381489)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/6c310c12-3bf8-4162-b2ec-1d6c82d2965c.png)]

路由

官方路由

对于大多数单页面应用,都推荐使用官方支持的 vue-router 库。更多细节可以移步 vue-router 文档

后端路由与前端路由

后端路由:URL地址与服务器资源的匹配关系

前端路由:根据不同的用户事件,显示不同的页面内容,主要用于实现 SPA

自定义路由实现

为了更好的理解路由的作用,我们自己定义一个路由

自己定义路由两种方法,Hash 和 HTML5 History API

Hash 方法定义路由

这里是一个简单的路由

  • 监听 window 的onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称

模板代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P6SCOqmI-1615345381490)(http://qiniu.yaoyao.info/image-20210102181640894.png)]

创建局部组件并在 vue 实例中使用

img

在 onhashchange 事件中显示不同的组件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6RbXIdx-1615345381492)(http://qiniu.yaoyao.info/image-20210102181900799.png)]

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
	<style>
	  a {
	    margin: 15px;
	  }
	</style>
</head>

<body>
    <div id="app">
		<nav>
            <!-- 使用锚点的方式刷新页面 -->
			<a href="#/follow">关注</a>
			<a href="#/recommend">推荐</a>
			<a href="#/hotList">热榜</a>
			<a href="#/smallVideo">小视频</a>
		</nav>
		<component :is="component_name"></component>
	</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
		// 创建关注组件
		let follow = {
			template:`<h3>今日关注</h3>`
		}
		// 创建推荐组件
		let recommend = {
			template:`<h3>今日推荐</h3>`
		}
		// 创建热榜组件
		let hotList = {
			template:`<h3>今日热榜</h3>`
		}
		// 创建小视频组件
		let smallVideo = {
			template:`<h3>今日小视频</h3>`
		}
        let app = new Vue({
            el: '#app',
            data: {
				component_name: ''
			},
			components:{
				'follow':follow,
				'recommend':recommend,
				'hotList' : hotList,
				'smallVideo' : smallVideo
			}
        })
		window.onhashchange=function(){
			let hash=window.location.hash
			switch(hash){
				case '#/follow':
				 app.component_name='follow'
				 break
				 case '#/recommend':
				  app.component_name='recommend'
				  break
				  case '#/hotList':
				   app.component_name='hotList'
				   break
				   case '#/smallVideo':
				    app.component_name='smallVideo'
				    break
			}
		}
    </script>
</body>

</html>
url中#(hash)的含义

hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)

1、#号的含义

#代表网页中的一个位置。其右面的字符,就是该位置上的标识符。

http://www.example.com/index.html#print

这个URL代表的是这个www.example.com域名的这个index.html页面下的print位置。浏览器读取这个URL后,会自动的将print所在的位置滚动到可视区域内。

为网页位置指定标识符,有两个方法:

使用锚点,如<a name="print"></a>,这种只能使用a标签
使用id属性,如<div id="print" >,这种适用各种标签

2、HTTP请求不包括’#’

‘#’是用来指导浏览器动作的,对服务器端完全无用。所以,HTTP请求中不包括#

比如,访问下面的网址,

http://www.example.com/index.html#print

浏览器实际发出的请求是这样的:

GET /index.html HTTP/1.1
Host: www.example.com

可以看到,只是请求index.html,根本没有"#print"的部分。

3、#后的字符

在第一个 #后面出现的任何字符,都会被浏览器解读为位置标识符。这意味着,这些字符都不会被发送到服务器端。比如:

http://www.example.com/?color=#fff

浏览器实际发出的请求是:

GET /?color= HTTP/1.1

Host: www.example.com

可以看到,“#fff”被省略了。只有将#转码为%23,浏览器才会将其作为实义字符处理。也就是说上面的网址应该写成:

http://example.com/?color=%23fff
4、改变#不触发网页重载

单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页。
比如,从

http://www.example.com/index.html#location1

改成

http://www.example.com/index.html#location2

浏览器不会重新向服务器请求index.html。

5、改变#会改变浏览器的访问历史

每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用"后退"按钮,就可以回到上一个位置。

这对于ajax应用程序特别有用,可以用不同的#值,表示不同的访问状态,然后向用户给出可以访问某个状态的链接。

值得注意的是,上述规则对IE 6和IE 7不成立,它们不会因为#的改变而增加历史记录。

6、window.location.hash读取#值

window.location.hash这个属性可读可写。读取时,可以用来判断网页状态是否改变;写入时,则会在不重载网页的前提下,创造一条访问历史记录。

7、onhashchange事件

这是一个HTML 5新增的事件,当#值发生变化时,就会触发这个事件。IE8+、Firefox 3.6+、Chrome 5+、Safari 4.0+支持该事件。

它的使用方法有三种:

window.onhashchange = func;
//
<body onhashchange="func();">
//
window.addEventListener("hashchange", func, false);

对于不支持onhashchange的浏览器,可以用setInterval监控location.hash的变化。

8、Google抓取#的机制

默认情况下,Google的网络蜘蛛忽视URL的#部分。

但是,Google还规定,如果你希望Ajax生成的内容被浏览引擎读取,那么URL中可以使用"#!",Google会自动将其后面的内容转成查询字符串_escaped_fragment_的值。

比如,Google发现新版twitter的URL如下:

http://twitter.com/#!/username

就会自动抓取另一个URL:

http://twitter.com/?_escaped_fragment_=/username

通过这种机制,Google就可以索引动态的Ajax内容。

摘自:https://segmentfault.com/a/1190000013006640

vue Router

Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:

  • 嵌套的路由/视图表
  • 模块化的、基于组件的路由配置
  • 路由参数、查询、通配符
  • 基于 Vue.js 过渡系统的视图过渡效果
  • 细粒度的导航控制
  • 带有自动激活的 CSS class 的链接
  • HTML5 历史模式或 hash 模式,在 IE9 中自动降级
  • 自定义的滚动条行为

用 Vue.js + Vue Router 创建单页应用,是非常简单的。使用 Vue.js ,我们已经可以通过组合组件来组成应用程序,当你要把 Vue Router 添加进来,我们需要做的是,将组件 (components) 映射到路由 (routes),然后告诉 Vue Router 在哪里渲染它们

使用 vue Router 修改上面的案例

安装并引入路由文件
安装
npm install vue
引入
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
添加路由链接

可以使用超链接,或者使用 router-link 组件

<router-link to="/follow">关注</router-link>
<router-link to="/recommend">推荐</router-link>
<router-link to="/small_video">关小视频注</router-link>
<router-link to="/hot_list">热榜</router-link>
添加路由填充位
<router-view></router-view>

相当于1个占位符,组件内容会替换这个填充位

创建组件
 const follow = {
     template: `<h3>关注内容</h3>`,
 };
 const recommend = {
     template: `<h3>推荐信息</h3>`,
 };
 const SmallVideo = {
     template: `<h3>小视频</h3>`,
 };
 const HotList = {
     template: `<h3>热榜</h3>`,
 };
完整版案例
<!DOCTYPE html>
<html lang="en">

	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
	</head>

	<body>
		<div id="app">
			<!-- 使用 router-link 组件来导航. -->
			<!-- 通过传入 `to` 属性指定链接. -->
			<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
			<router-link to="/follow">关注</router-link>
			<router-link to="/recommend">推荐</router-link>
			<router-link to="/small">小视频</router-link>
			<router-link to="/hot">热点</router-link>
			<!-- 路由出口 -->
			<!-- 路由匹配到的组件将渲染在这里 -->
			<router-view></router-view>
		</div>
		<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
		<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
		<script>
			// 0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用 Vue.use(VueRouter)
			// 1. 定义 (路由) 组件。
			// 创建关注组件
			const follow = {
			  template: `<h3>关注内容</h3>`,
			};
			// 创建推荐组件
			const recommend = {
			  template: `<h3>推荐信息</h3>`,
			};
			// 创建小视频组件
			const SmallVideo = {
			  template: `<h3>小视频</h3>`,
			};
			// 创建热榜组件
			const HotList = {
			  template: `<h3>热榜</h3>`,
			};
			// 2. 定义路由
			// 每个路由应该映射一个组件。 其中"component" 可以是
			// 通过 Vue.extend() 创建的组件构造器,
			// 或者,只是一个组件配置对象。
			const routes = [
			  { path: '/follow', component: follow },
			  { path: '/recommend', component: recommend },
			  { path: '/small', component: SmallVideo },
			  { path: '/hot', component: HotList }
			]
			// 3. 创建 router 实例,然后传 `routes` 配置
			const router = new VueRouter({
				 routes // (缩写) 相当于 routes: routes
			})
			// 4. 创建和挂载根实例。
			// 记得要通过 router 配置参数注入路由,
			// 从而让整个应用都有路由功能
			let app = new Vue({
			  router
			}).$mount('#app') // 将当前 vue 实例挂载到app dom 上
		</script>
	</body>

</html>

任务单.gif

一个小知识点

在此注意如果你想把image.png

这两个常量名换成别的常量名,必须要注意一点,你不能直接改了两个常量名就行,必须要把下面这里给改掉[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sp8Bk9GO-1615345381495)(http://changetm.oss-cn-beijing.aliyuncs.com/Temp/8d9bf74c-6bf1-4c6e-a2d1-bce6ceeb78ec.png)]

因为routes和router是模板中所带的

如果写成这种模式就会报错

image.png

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值