vue

Vue入门

目录

basic

1.概述

  • Vue (读音/vju/, 类似于view)是一套用于构建用户界面的渐进式框架,发布于2014年2月。
  • 与其它大型框架不同的是,Vue被设计为可以自底向上逐层应用。
  • Vue的核心库只关注视图层,不仅易于上手,还便于与第三方库(如: vue-router: 跳转,vue-resource: 通信,vuex:管理)或既有项目整合。
  • 官网:https://cn.vuejs.org/
  • Soc原则:关注点分离原则
  • Vue 的核心库只关注视图层,方便与第三方库或既有项目整合。
  • HTML + CSS + JS : 视图 : 给用户看,刷新后台给的数据
  • 网络通信 : axios
  • 页面跳转 : vue-router
  • 状态管理:vuex
  • Vue-UI : ICE , Element UI

声明式渲染:不需要改变Dom,Vue可以通过绑定Dom修改数据,而不会修改Dom

2.第一个vue

2.1丶什么是MVVM

MVVM(Model-View-ViewModel)是一种软件设计模式, 源自于经典的MVC(Model-View-Controller)模式 , MVVM的核心是ViewModel层 ,作用如下

  • 该层向上与视图层进行双向数据绑定

  • 向下与Model层通过接口请求进行数据交互

    MVVM已经相当成熟了,主要运用但不仅仅在网络应用程序开发中。当下流行的MVVM框架有Vue.js

2.2丶为什么使用MVVM

MVVM模式和MVC模式一样,主要目的是分离视图(View)和模型(Model),有几大好处

  1. 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。

  2. 可复用:你可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。

  3. 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewMode),设计人员可以专注于页面设计。

  4. 可测试:界面素来是比较难以测试的,而现在测试可以针对ViewModel来写。

    1646354774242

2.3丶 MVVM模式的实现者

  • Model:模型层, 在这里表示JavaScript对象
  • View:视图层, 在这里表示DOM(HTML操作的元素)
  • ViewModel:连接视图和数据的中间件, Vue.js就是MVVM中的View Model层的实现者

在MVVM架构中, 是不允许数据和视图直接通信的, 只能通过ViewModel来通信, 而View Model就是定义了一个Observer观察者

  • ViewModel能够观察到数据的变化, 并对视图对应的内容进行更新
  • ViewModel能够监听到视图的变化, 并能够通知数据发生改变

至此, 我们就明白了, Vue.js就是一个MV VM的实现者, 他的核心就是实现了DOM监听与数据绑定

2.4.丶第一个vue

  1. 先安装插件vue.js
  2. 新建html文件,导入Vue.js
  3. 创建vue实例
  4. 将数据绑定导页面元素
  5. 有了viewmodel的区别:
    • 当我们需要修改前端的数据,使用js就会修改整个dom
    • 而我们使用viewmodel后,可以动态修改数据,而不需要修改dom
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<!--view层,模板-->
//3.绑定
<div id="a">
{{A}}
</div>
</body>
//2.引入
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
   //3,创建实例
    var vm=new Vue({
        el:"#a",
        //model:数据
        data:{
            A:"helloVue"
        }
    })
    
</script>
</html>

3.基础语法

3.1丶v-bind: 绑定元素特性

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<!--view层,模板-->
    //绑定 v-bind:title
<div id="a" v-bind:title="A">
    你好
</div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
    var vm=new Vue({
        el:"#a",
        //model:数据
        data:{
            A:"nihao"
        }
    })

</script>
</html>

3.2丶v-if, v-else-if,v-else

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

</head>
<body>
<!--view层,模板-->
<div id="a">
  <h1  v-if="A==='nihao'">你好</h1>
  <h1  v-else-if="A==='nihao2'">你好2</h1>
  <h1  v-else>你好3</h1>
</div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
  var vm=new Vue({
    el:"#a",
    //model:数据
    data:{
      A:"nihao"
    }
  })

</script>
</html>

3.3丶for

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

</head>
<body>
<!--view层,模板-->
<div id="a">
    //H是数组,hh是元素别名
  <li v-for="H in hh">
    {{H}}
  </li>
</div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
  var vm=new Vue({
    el:"#a",
    //model:数据
    data:{
      hh:[
        {A:"你好",B:"nihao"},1,true
      ]
    }
  })

</script>
</html>

3.4丶v-on监听事件

事件有Vue的事件、和前端页面本身的一些事件!我们这里的click是vue的事件, 可以绑定到Vue中的methods中的方法事件!

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

</head>
<body>
<!--view层,模板-->
<div id="a">
  <button v-on:click="A">点击</button>
  {{msg}}
</div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
  var vm=new Vue({
    el:"#a",
    //model:数据
    data:{
      msg: "nihao"
    },
    methods:{
      A:function (){
        this.msg="你好啊"
      }
    }
  })

</script>
</html>

4.双向绑定

4.1丶概念

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

    Vue.js是一个MV VM框架, 即数据双向绑定, 即当数据发生变化的时候, 视图也就发生变化, 当视图发生变化的时候,数据也会跟着同步变化。这也算是Vue.js的精髓之处了

    值得注意的是,我们所说的数据双向绑定,一定是对于UI控件来说的非UI控件不会涉及到数据双向绑定

  2. 为什么要实现双向绑定

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

4.2丶input输入框

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<!--view层,模板-->
<div id="a">
    <input type="text" v-model="msg">
    {{msg}}
</div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
    var vm=new Vue({
        el:"#a",
        data:{
            msg:"123"
        }
    })

</script>
</html>

4.2丶单选框

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<!--view层,模板-->
<div id="a">
    性别:
    <input type="radio" name="sex" value="男" v-model="msg"><input type="radio" name="sex" value="女" v-model="msg"><br>
    {{msg}}
</div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
    var vm=new Vue({
        el:"#a",
        data:{
            msg:""
        }
    })

</script>
</html>

4.下拉列表

<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml" xmlns:v-on="http://www.w3.org/1999/xhtml">
<head>
  <meta charset="UTF-8">
  <title>Title</title>

</head>
<body>
<!--view层,模板-->
<div id="a">
  性别:
 <select v-model="msg1">
   <option></option>
   <option></option>
 </select>
  <br>
  性别选中:{{msg1}}
</div>
</body>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script type="text/javascript">
  var vm=new Vue({
    el:"#a",
    data:{
      msg1:"男"
    }
  })

</script>
</html>

5.watch监听和computed计算属性

watch的使用
new Vue({
	el: "#Root",
	data: {
		id: 0
},
	methods: {
		add() {
			this.id += 1;
		}
},
	watch: {
		immediate:true, //初始化时让handler调用一下
		id(a,b){
			alert(a)
			alert(b)
}
}
简写:
watch:{
	id(a,b){
		a:旧,b;新
	} 如果id(a){
		此时的a为新
}
}
computed的使用
computed的使用:定义返回的值不能是data里定义过的
计算属性:
1.定义:要用的属性不存在,要通过已有属性计算得来。
2.原理:底层借助了Objcet.defineproperty方法提供的getter和setter。
3.get函数什么时候执行?
(1).初次读取时会执行一次。
(2).当依赖的数据发生改变时会被再次调用。
4.优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。
5.备注:
1.计算属性最终会出现在vm上,直接读取使用即可。
2.如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变。

<body> 
    <div id="Root"> 
        <p>{{id}}</p> 
		{{id2}} 
		<button @click="add">计算属性</button> 
	</div> 
</body> 
<script> 
            var vm = new Vue({ 
                el: "#Root", 
                data: { id: 123 },
                methods: { add() { 
                    // 此时我们是可以拿到id2的,在vm里,不要觉得data里没有就是没有这个数据 this.id2 += 1; } }, 
                computed: { 
                    // 简写 // id2(){ // return this.id+=10; // } 
                    // 完整版 id2: { get() { return this.id; }, 
                    // set在id2被修改时执行 set(value) { alert(value) this.id += 10 } } } }) 
</script>

6.vue组件

非单文件组件

步骤:

  1. 首先注册组件
  2. 创建组件模板
  3. 定义一个属性来传递参数
  4. 在view层,调用组件,v-bind绑定属性传递参数
  5. 作用:提高复用
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>基本使用</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<!-- 
        Vue中使用组件的三大步骤:
            一、定义组件(创建组件)
            二、注册组件
            三、使用组件(写组件标签)

        一、如何定义一个组件?
            使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
            区别如下:
                1.el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
                2.data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
            备注:使用template可以配置组件结构。

        二、如何注册组件?
            1.局部注册:靠new Vue的时候传入components选项
            2.全局注册:靠Vue.component('组件名',组件)

        三、编写组件标签:
            <school></school>
		-->
		<!-- 准备好一个容器-->
		<div id="root">
			<hello></hello>
			<hr>
			<h1>{{msg}}</h1>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<school></school>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<student></student>
		</div>

		<div id="root2">
			<hello></hello>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false

		//第一步:创建school组件
		const school = Vue.extend({
			template:`
				<div class="demo">
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>
					<button @click="showName">点我提示学校名</button>	
				</div>
			`,
			// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
			data(){
				return {
					schoolName:'尚硅谷',
					address:'北京昌平'
				}
			},
			methods: {
				showName(){
					alert(this.schoolName)
				}
			},
		})

		//第一步:创建student组件
		const student = Vue.extend({
			template:`
				<div>
					<h2>学生姓名:{{studentName}}</h2>
					<h2>学生年龄:{{age}}</h2>
				</div>
			`,
			data(){
				return {
					studentName:'张三',
					age:18
				}
			}
		})
		
		//第一步:创建hello组件
		const hello = Vue.extend({
			template:`
				<div>	
					<h2>你好啊!{{name}}</h2>
				</div>
			`,
			data(){
				return {
					name:'Tom'
				}
			}
		})
		
		//第二步:全局注册组件
		Vue.component('hello',hello)

		//创建vm
		new Vue({
			el:'#root',
			data:{
				msg:'你好啊!'
			},
			//第二步:注册组件(局部注册)
			components:{
				school,
				student
			}
		})

		new Vue({
			el:'#root2',
		})
	</script>
</html>

单文件组件

一个组件必须要有的三部分:

结构:html

交互:js,vue

样式:css

注意:浏览器在不会识别vue文件的,需要脚手架

  • 次组件

    <template>
      <div class="yangshi">
        <h1 v-text="a"></h1>
        <button @click="dianji"></button>
      </div>
    </template>
    
    <script>
     #一定要暴露出去,不然外面不能引入  
    export default {
      name: "SChool",
      data () {
        return {
          a: "大班"
        }
      },
      methods: {
        dianji () {
          alert("你好")
        }
      }
    }
    </script>
    
    <style scoped>
    .yangshi {
      background-color: aqua;
    }
    </style>
    
  • 主组件APP

    <template>
      <div>
        <School></School>
      </div>
    </template>
    
    <script>
    引入
    import School from "../html./vUE/单组件.vue"
    export default {
      name: "App",
      components: {
        School
      }
    }
    </script>
    
    <style>
    </style>
    
  • main.js里的new Vue()

    import App from "App"
    
    new Vue({
      el: "#Root",
      components: {
        App
      }
    })
    
  • index.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>测试</title>


</head>

<body>
  <div id="Root">
    <App></App>
  </div>
</body>
<script src="../js/vue.js"></script>
<script src="./main.js"></script>


</html>

Vue2模块化

1.Vue自带的脚手架

(1).文件结构分析

1652687771447

  • node_moudles:是放一些我们需要的文件

    • 里面vue文件里:有完整版和runtime版本的vue
    • 注意:
      • vue=核心功能+模板解析器
      • runtime版本的vue,没有模板解析器,不能使用template,绑定模板
      • 当你引用的是runtime版本的vue文件时,就要用render
  • public:

    • index.html就是放主文件的容器
    • favico.ico:网页图标
  • src:来源

    • assets:资源

    • components:我们自己定义的组件

    • App.vue:主组件

    • main.js:入口

  • babel.config.js:es6转换为es5

  • jsconfig.json:可以自己做一些配置

  • pakeage-lock: 文件是锁定安装时的包的版本号,以保证其他人在npm install时大家的依赖能保证一致。第一次npm install时自动生成。

  • package: 文件是对项目或者模块包的描述,也是配置项,安装node_modules的凭据。

  • readme:可以笔记

  • vue.config.js:修改默认配置

    • 例如main.js和index.html的位置

2.属性ref(标识属性)

干什么用的?

类似于获取dom,js获取dom是拿不到组件标签的,但使用这个我们可以拿到组件标签的vc对象

怎么用?

注意:组件的对象和Vue对象是不一样的

<template>
  <div>
    <h1 ref="hi">{{name}}</h1>
   <School id="school" ref="school"></School>
   <button @click="dianji">点击</button>
  </div>
</template>

<script>

import School from "./components/单组件.vue"
export default {
  // name: "App",
  data () {
    return {
      name:"liuchao"
    }
  },
  components: {
   School
  },
  methods: {
    dianji(){
      // 1.拿到html标签
      // console.log(this.$refs)
      // 此时dom是拿不到我们的组件标签的
      console.log(document.getElementById(school))
      //  通过cv对象拿到
       console.log(this.$refs.school)
    }
  }
  
}
</script>

1652691611578

3.props属性

官网: Prop — Vue.js (vuejs.org)

干啥用的?

父组件给子组件传数据

传的数据类型有?
  • String
  • Number
  • Boolean
  • Array
  • Object(甚至可以传一个函数,函数也是对象,在下面7的案例里应用到)
注意点:
  • 注意当age前面v-bind绑定时,此时就是一个动态属性了
  • 1.如果我们:age="11",那么子组件拿到的数据是字符串里的表达式'11',在这里也就是11
  • 2.如果你写成:age='age',拿的是下面data里的数据,这里传到子组件里的就是一个字符串11
  • 3.可以传所有的类型
  • 4.props传下来的数据不能改(只读),不然会报错,如果你传了一个对象下来,修改了对象里的属性值,此时是不会报错的,因为地址没改,但是不建议
具体的使用:
  • 父组件传值

  • 子组件接收

    • 接收方式三种

      • 直接传
      • 限制属性
      • 类型限制+默认值的指定+必要性的限制
父组件传值
<template>
  <div>
    <!-- 注意当age前面v-bind绑定时,此时就是一个动态属性了
    (注意点)1.如果我们:age="11",那么子组件拿到的数据是字符串里的表达式'11',在这里也就是11
            2.如果你写成:age='age',拿的是下面data里的数据,这里传到子组件里的就是一个字符串11
           3.可以传所有的类型 -->
    <Student :name='name'
             :sex="sex"
             :age="11"
             :shuzu='["father", "mother", "borther", "sister"]'
             :duixiang='{a:"小花",b:"小红",c:"小兰"}' />
    <button @click="dijian2">点击2</button>
  </div>
</template>

<script>
import Student from './components/student.vue'

export default {
  name: 'App',
  data () {
    return {
      name: "lc",
      sex: "男",
      age: "20"
    }
  },
  components: { Student },
  methods: {
    dijian2 () {
      console.log(this)
    }
  }
}
</script>
子组件
<template>
  <div>
    <h1>{{msg}}</h1>
    <h2>学生姓名:{{name}}</h2>
    <h2>学生性别:{{sex}}</h2>
    <h2>学生年龄:{{myage+1}}</h2>
    <ul>
      <li v-for="(s,index) in shuzu"
          :key="index">{{s}}</li>
      <li v-for="(d,index) in duixiang"
          :key="index">{{d}}</li>
    </ul>
    <!-- 当我们想操作父组件传进来的数据时 -->
    <button @click="updateAge">点击1</button>
  </div>
</template>

<script>
export default {
  name: 'Student',
  data () {
    return {
      msg: "我的信息",
      myage: this.age
    }
  },
  // 第一种:简单声明接收
  // props: ['name', 'sex', 'age', 'shuzu', 'duixiang'],

  // 第二种:限制类型
  // props: {
  //   name: String,
  //   sex: String,
  //   age: Number,
  //   shuzu: Array,
  //   duixiang: Object
  // },

  // 第三种:接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
  props: {
    name: {
      type: String,
      require: false,    //设置为false就是可以不传
      default: "lc"     //默认值,一般不跟require一起用
    },
    sex: String,
    age: Number,
    shuzu: Array,
    duixiang: Object
  },
  methods: {
    updateAge () {
      // 1.直接操作数据会有效果,但是控制台会有报错
      // this.age+=1;
      // 2.所以我们创建一个myage,转换一下
      this.myage += 1
    }
  }
}
</script>

4.mixin混合

官网: 混入 — Vue.js (vuejs.org)

干啥用的?

提高代码复用,就是提取配置属性的

注意点
  • 当提取的和组件data里的数据重复时,优先组件里的
  • 挂载是两边都会执行
  • 有全局使用和局部使用
使用步骤
  • 提取mixin混合(定义)
  • 使用mixins: [hunhe],必须用数组配置(局部)
  • 直接在主入口,main.js中使用Vue.mixin()调用(主动)
    • 全局使用时,所有的vm和vc上都有这个混合属性
局部使用
teacher.vue
<template>
  <div>
    <p v-text="text"></p>
    <button @click="dianji">
      老师点
    </button>
  </div>
</template>

<script>
import hunhe from '../mixin'
export default {
  name: "Teacher",
  data () {
    return {
      text: "1",
    }
  },
  mixins: [hunhe],
  mounted () {
    console.log("挂载谁先1")
  }
}
</script>
student.vue
<template>
  <div>
    <p v-text="text"></p>
    <button @click="dianji">
      学生点
    </button>
  </div>
</template>

<script>

import hunhe from '../mixin'
export default {
  name: 'Student',
  data () {
    return {
      text: "1"
    }
  },
  mixins: [hunhe],
  mounted () {
    console.log("挂载谁先1")
  }
}
</script>
mixin.js(提取)
  • 要export导出
export default {
  methods: {
    dianji () {
      this.text = "你好"
    }
  },
  // 当这里的data和组件里的重复时,优先组件里的
  data () {
    return {
      text:"2"
    }
  },
  // 挂载是两边都会执行
  mounted () {
    console.log("挂载谁先2")
  }
}
全局使用
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 全局mixin
import hunhe from './mixin'
Vue.mixin(hunhe)
//创建vm
new Vue({
  el: '#app',
  render: h => h(App)
})

5.插件

官网: 插件 — Vue.js (vuejs.org)

干啥用?

提高复用, 通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  1. 添加全局方法或者 property。如:vue-custom-element
  2. 添加全局资源:指令/过滤器/过渡等。如 vue-touch
  3. 通过全局混入来添加一些组件选项。如 vue-router
  4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

怎么用?

创建插件

plug.js(名字随意)

export default {
//必须有install方法,参数为Vue的构造函数
 install (Vue,a,b) {
    alter(a+b)
    Vue.mixin({
      data () {
        return {
          x:100
        }
      }
    })
  }
}
使用插件

main.js主入口里

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
// 引入创建的插件
import plug from './plug'
//创建vm
Vue.use(plug,1,2)
new Vue({
  el: '#app',
  render: h => h(App)
})

注意点

  • 必须有install方法,参数为Vue的构造函数

  • 使用use后,Vue运行就自己调用

  • install()函数有多个对象,第二个之后都是自己定义的需要的参数

  • 一般使用在全局配置上

6.scope样式

干啥用?

解决不同组件汇总到App后样式名字相同的冲突

怎么用?

丢到style标签里就行

<style scoped>
.text {
  background-color: red;
}

是怎么实现的

使用scoped后会使用随机数字标记

1652793574487

7.ToDoList案例

结构

App

  • header
  • list
    • item
  • footer

代码

APP.vue
<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :Addlist="Addlist" />
        <MyList :list="list"
                :qufan="qufan"
                :shanchu="shanchu" />
        <MyFooter :List="list"
                  :AllPick="AllPick"
                  :clean="clean" />
      </div>
    </div>
  </div>
</template>

<script>

import MyHeader from './components/MyHeader.vue'
import MyFooter from './components/MyFooter.vue'
import MyList from './components/MyList.vue'

export default {
  name: 'App',
  components: {
    MyHeader,
    MyFooter,
    MyList,
  },
  data () {
    return {
      list: [
        { id: "01", title: "run", ok: false },
        { id: "02", title: "breakfast", ok: false },
        { id: "03", title: "water", ok: false }
      ],
      complete: 0
    }
  },
  methods: {
    Addlist (item) {
      this.list.unshift(item)
    },
    qufan (id) {
      this.list.forEach((element) => {
        if (element.id == id) {
          element.ok = !element.ok
        }
      });
    },
    shanchu (id) {
      this.list = this.list.filter(L => {
        return L.id != id
      })
    },
    AllPick (ok) {
      this.list.forEach((element) => {
        element.ok = ok
      });
    },
    clean () {
      this.list = this.list.filter(L => {
        return !L.ok
      })
    }
  }
}
</script>
<style>
body {
  background: #fff;
}
.btn {
  display: inline-block;
  padding: 4px 12px;
  margin-bottom: 0;
  font-size: 14px;
  line-height: 20px;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
    0 1px 2px rgba(0, 0, 0, 0.05);
  border-radius: 4px;
}
.btn-danger {
  color: #fff;
  background-color: #da4f49;
  border: 1px solid #bd362f;
}
.btn-danger:hover {
  color: #fff;
  background-color: #bd362f;
}
.btn:focus {
  outline: none;
}
.todo-container {
  width: 600px;
  margin: 0 auto;
}
.todo-container .todo-wrap {
  padding: 10px;
  border: 1px solid #ddd;
  border-radius: 5px;
}
</style>
Header.vue
<template>
  <div>
    <div class="todo-header">
      <input type="text"
             placeholder="请输入你的任务名称,按回车键确认"
             v-model="title"
             @keyup.enter="add" />
    </div>
  </div>
</template>

<script>
export default {
  name: "Header",
  props: ["Addlist"],
  data () {
    return {
      title: ""
    }
  },
  methods: {
    add () {
      if (this.title.trim() == '') {
        return alert("输入不能为空")
      } else {
        const item = { id: Math.random(), title: this.title, ok: false }
        this.Addlist(item)
        this.title = ''
      }
    }
  }
}
</script>

<style>
.todo-header input {
  width: 560px;
  height: 28px;
  font-size: 14px;
  border: 1px solid #ccc;
  border-radius: 4px;
  padding: 4px 7px;
}
</style>
List.vue
<template>
  <ul class="todo-main">
    <MyItem v-for="l in list "
            :key='l.id'
            :list="l"
            :qufan="qufan"
            :shanchu="shanchu" />
  </ul>
</template>

<script>
import MyItem from './MyItem.vue';

export default {
  name: "list",
  components: { MyItem },
  props: ["list", "qufan", "shanchu"]
}
</script>

<style>
.todo-main {
  margin-left: 0px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding: 0px;
}

.todo-empty {
  height: 40px;
  line-height: 40px;
  border: 1px solid #ddd;
  border-radius: 2px;
  padding-left: 5px;
  margin-top: 10px;
}
</style>
Item.vue
<template>
  <li>
    <label>
      <input type="checkbox"
             :checked="list.ok"
             @click="gouxuan(list.id)" />
      <span>{{list.title}}</span>
    </label>
    <button class="btn btn-danger"
            @click="remove(list.id)">删除</button>
  </li>
</template>

<script>
export default {
  name: "item",
  props: ["list", "qufan", "shanchu"],
  data () {
    return {
      show: true
    }
  },
  methods: {
    remove (id) {
      // 通知APp删除
      this.shanchu(id)
    },
    gouxuan (id) {
      // 通知App取反
      this.qufan(id)
    }
  }
}
</script>

<style scoped>
li {
  list-style: none;
  height: 36px;
  line-height: 36px;
  padding: 0 5px;
  border-bottom: 1px solid #ddd;
}

li label {
  float: left;
  cursor: pointer;
}

li label li input {
  vertical-align: middle;
  margin-right: 6px;
  position: relative;
  top: -1px;
}

li button {
  float: right;
  display: none;
  margin-top: 3px;
}

li:before {
  content: initial;
}

li:last-child {
  border-bottom: none;
}

li:hover {
  background-color: #ddd;
}

li:hover button {
  display: block;
}
</style>>

Footer.vue
<template>
  <!-- 拿到一个数,在js里如果一个整数大于0就会返回一个处理成一个true,小于等于0为false -->
  <div class="todo-footer"
       v-if="List.length">
    <label>
      <!-- 这里有一个优化,使用v-model ,   :checked="isAll"初始化  @click="quanxuan" 修改-->
      <!-- <input type="checkbox" :checked="isAll"  @click="quanxuan" /> -->
      <input type="checkbox"
             v-model="isAll" />
    </label>
    <span>
      <span>已完成{{gouxuan}}</span> / 全部{{List.length}}
    </span>
    <button class="btn btn-danger" @click="cleanAll">清除已完成任务</button>
  </div>
</template>

<script>

export default {
  name: "Header",
  props: ["List", "AllPick","clean"],
  computed: {
    // 勾选后,修改下面的数据
    gouxuan () {
      let i = 0;
      this.List.forEach(element => {
        if (element.ok) {
          i++
        }
      }
      )
      return i
    },
    // 当上面全部选中,下面全选
    isAll: {
      get () {
        return this.gouxuan == this.List.length
      },
      // set在isAll被修改时执行
      set (value) {
        this.AllPick(value)
      }
    }
  },
  methods: {
    cleanAll(){
        this.clean()
    }
  }
}
</script>

<style>
.todo-footer {
  height: 40px;
  line-height: 40px;
  padding-left: 6px;
  margin-top: 5px;
}

.todo-footer label {
  display: inline-block;
  margin-right: 20px;
  cursor: pointer;
}

.todo-footer label input {
  position: relative;
  top: -1px;
  vertical-align: middle;
  margin-right: 5px;
}

.todo-footer button {
  float: right;
  margin-top: 5px;
}
</style>

遇到的问题和学到的东西

  • 怎么完整使用组件来写代码

    • 拆分静态组件,按功能拆分
    • 实现动态组件,当数据多个组件使用时,需要状态提升,这个案例里我就都在APP.vue写的数据
  • 兄弟组件间的通信如何通信

    • 让两个组件的共同上级组件
  • 子组件间怎么访问父组件的数据

    • 可以传函数,父组件创建函数并使用父组件的数据实现一个功能,传给子组件,子组件调用函数
  • 过滤器 return 后是过滤的条件,会返回一个新的你要过滤的对象,需要重新接收

  • 当你调用方法时,都会有一个参数 e 可以调用到 当前的dom

  • 当你要对一个输入标签要做初始化展示,又需要后续修改,试试v-model

注意点

  • props传的数据,不要修改,只使用

8.浏览器本地存储

localStorage(本地,只有清除缓存才能删除数据)和sessionStorage(会话,关闭浏览器就没了)

都有4个函数

localStorage.setItem("mei", "123") 添加(只能添加两个字符串)

localStorage.remove("mei") 删除一个

localStorage.getItem("mei") 获取这个

localStorage.clear() 删除所有

sessionStorage.一样的函数

优化Todolist案例,将数据放在本地

data () {
    return {
      // list是拿本地储存的,当取到的数据为null时,就会使用[]
      list: JSON.parse(localStorage.getItem("list")) || [],
      complete: 0
    }
  },

//监听list
 watch: {
    // 这里要用深度监视,不然,检测不到对象里的数据的变化
    list: {
      deep: true,
      handler (value) {
        localStorage.setItem("list", JSON.stringify(value))
      }
    }
  },

9.自定义组件事件

干啥用的?

  • 给组件做事件,自己定义一些处理,自定义事件其实,基本还是需要用到原本的js事件做一些自己想要的处理,就是事件包装
  • 可以做到子传父的效果
  • 我们上面用到的父给子传函数,达到子给传父数据,都可以用自定义事件代替

怎么用?

两种方式

  • 第一种 v-on 其实就是在这个zhi组件实例里挂上一个事件,也就是要拿到这个组件实例,所以就有了第二种情况
  • 第二种 用ref 的优势 灵活可以在mounth里做处理

步骤

  • 子组件的实例对象使用$emit挂载上事件

    • 组件实现的事件,就是子组件里定义的事件
    • 比如你子组件里定义了点击,移开,回车,那么这三个事件父组件都能触发
  • 父组件使用事件,触发一个方法,方法的参数就是子组件传回来的数据

子组件
<template>
  <div>
    姓名:{{name}} <br>
    大小:{{size}}
    <button @mouseenter="dianji">自定义事件</button>
  </div>
</template>

<script>
export default {
  name: "zhi",
  data () {
    return {
      name: "lc",
      size: 28
    }
  },
  methods: {
    dianji(){
        //$emit()触发绑定事件
      this.$emit("shu",{name:this.name,size:this.size})
    }
  }
 
}
</script>

父组件
<template>
  <div id="root">
    <!-- 第一种  v-on 其实就是在这个zhi组件实例里挂上一个事件,也就是要拿到这个组件实例,所以就有了第二种情况-->
    <!-- <zhi @shu="shuchu"/> -->

    <!-- 第二种 用ref 的优势 灵活可以在mounth里做处理 -->
    <zhi ref="zidingyi" />
    <button @click="see">看看</button>
  </div>
</template>

<script>
import zhi from './components/自定义组件事件Test.vue'
export default {
  name: 'App',
  components: {
    zhi
  },
  methods: {
    shuchu (shuju) {
      console.log(shuju)
    },
    see () {
      console.log(this)
    }
  },
  // 第二种
  mounted () {
    // 第一个参数 自定义事件名 对应组件那边绑定的事件名,  第二个 调用的函数 
    //$on绑定事件
    this.$refs.zidingyi.$on("shu", this.shuchu)
  }
}
</script>

如何解绑事件

如何实现,使用vc实例的$off()方法

 jiebang () {
      // 解绑一个
      this.$off("shu")
      // 解绑多个
      // this.$off(["shu","second"])
    }

10.全局事件总线(通信)

干啥用?

还是用来通信,像我们前面的案例,实现兄弟通信是把数据存到了更高一级的组件里,然后分别传下来,麻烦

适用于任何组件通信

怎么用?

首先这个玩意,不是官方的api,而是一种写法,利用了所有组件都能拿到原型对象

  • 创建事件总线,并绑定一个属性

  • $on绑定一个事件,接收数据并发送一个消息

  • $emit调用这个事件,并传入数据

  • $off销毁这个事件

main.js
new Vue({
  el: '#app',
    //为什么用beforeCreate钩子函数,因为必须在Vue实例创建之前执行,不然$bus没有创建成功,而组件里又调用了它,就会报错
  beforeCreate () {
      //创建全局事件总线,当然不仅仅可以用vm对象也可以用vc对象
    Vue.prototype.$bus = this
  },
  render: h => h(App)
})
student.vue
<template>
  <div>
    <button @click="sendName">发送给学校</button>
  </div>
</template>

<script>
export default {
  name: "Student",
  data () {
    return {
      name: "刘超"
    }
  },
  methods: {
    sendName () {
        //发送数据
      this.$bus.$emit("send", this.name)
    }
  }
}
</script>

<style>
</style>
School.vue
<template>
  <div>{{name}}</div>
</template>

<script>
export default {
  name: "School",
  data () {
    return {
      name: ""
    }
  },
  mounted () {
      //接收数据
    this.$bus.$on("send", (data) => {
      this.name = data
    })
  },
    //销毁
  beforeDestroy () {
    console.log("销毁事件send")
    this.off("send")
  }
}
</script>

注意

  • $on绑定事件的地方,是接收数据的一方,$emit是你想发送数据的一方
  • 由于都是在一个属性上创建事件,所以可能会事件名冲突,所以需要用完就销毁事件

11.消息订阅与发布(通信)

特点:有很多第三方库可以实现,不是vue里的,很多框架都可以使用

下载一个库,用法和全局事件大同小异,百度即可

12动画&过渡

官网: 进入/离开 & 列表过渡 — Vue.js (vuejs.org)

干什么用

在插入,更新和移除Dom元素的时候,给元素添加样式类名

动画效果

  • 使用transition标签包裹住需要动画的标签
  • 定义动画
  • 使用v-enter-active和.v-leave-active进来和出去调用动画
<template>
  <div>

    <button @click="IsShow=!IsShow">切换</button>
    <transition appear>
      <p v-show="IsShow">你好</p>
    </transition>
  </div>
</template>

<script>
export default {
  name: 'App',
  data () {
    return {
      IsShow: true
    }
  }

}
</script>
<style scoped>
p {
  height: 50px;
  width: 400px;
  background-color: aqua;
}
    //进来和出去的的过程执行的
.v-enter-active {
  animation: zz 0.1s;
}
.v-leave-active {
  animation: zz 0.1s linear reverse;
}
/* 定义一个动画关键帧 */
@keyframes zz {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}
</style>>

过度效果

  • 使用transition标签

  • 使用.v-enter,.v-enter-to,.v-leave,.v-leave-to

  • 定义开始到结束的时间v-enter-active,.v-leave-active

    /* 定义进入时开始的位置和出去结束的位置 */
    .v-enter,
    .v-leave-to {
      transform: translateX(-100%);
    }
    /* 定义出去时结束的位置和出去时开始的位置 */
    .v-enter-to,
    .v-leave {
      transform: translateX(0);
    }
    /* 还需要定义这个过渡的时间等 */
    .v-enter-active,
    .v-leave-active {
      transitio
    

多个元素过渡(列表过渡)

  • 使用标签transition-group标签

  • 必须要有key

       <transition-group appear>
          <p v-show="!IsShow" :key="1">你好</p>
          <p v-show="IsShow"  :key="2">刘超</p>
        </transition-group>
      </div>
    </template>
    

第三方库

这里以animate.css(官网: 动画.css |CSS 动画的跨浏览器库。 (animate.style) )为例子

步骤
  • 修改name:为第三方库提供的name

  • 使用属性 enter-active-class和leave-active-class进入和出去,调用某一个动画

        <transition-group name="animate__animated animate__bounce"
                          enter-active-class="animate__shakeY"
                          leave-active-class="animate__bounceOut"
                          appear>
          <p v-show="!IsShow"
             :key="1">你好</p>
          <p v-show="IsShow"
             :key="2">刘超</p>
        </transition-group>
    

注意点

  • transition这个标签不会再浏览器元素中显示,当你实现动画时,会出现下面一瞬间添加属性的效果

1653802930618

  • appear是出场动画,为true实现
  • 当有多个不同的标签要实现过渡动画,就需要定义name进行区分

13.插槽

官网: 插槽 — Vue.js (vuejs.org)

干啥用?

  • 解决了有相同布局,但是展示标签内容不同的组件,提高复用
  • 也可以说是父组件在子组件指定位置插入某个html结构,也是组件间的通信方式

怎么用?

  • 在定义的子组件里想展示不一样内容的位置(挖坑) 写入标签

  • 父组件运用子组件时,使用双标签,在双标签里,填入想展示的内容,填坑

分类

默认插槽

子组件One.vue
<template>
  <div class="cathgory">
    <p>{{title}}</p>
      //挖坑
    <slot>我是默认值,如果组件没有定义,就显示这句话</slot>
  </div>
</template>

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

<style>
.cathgory {
  height: 300px;
  width: 200px;
  background-color: aqua;
  text-align: center;
}
</style>
父组件APP.vue
<template>
  <div class="whole">
    <One title="music">
        //填坑
      <ul>
        <li v-for="(i,index) in 5"
            :key="index">{{i}}</li>
      </ul>
    </One>
    <One title="games">
      <img src="./assets/logo.png"></img>
    </One>
    <One title="films">
      <video src="https://www.bilibili.com/video/BV1Zy4y1K7SH?p=102"></video>
    </One>
  </div>
</template>

<script>
import One from './components/One.vue';

export default {
  name: 'App',
  components: {
    One,
  }
}
</script>
<style scoped>
.whole {
  display: flex;
  justify-content: space-around;
}
</style>
具名插槽
挖坑的地方起名
  <div class="cathgory">
    <p>{{title}}</p>
    <slot name="first">我是默认值1,如果组件没有定义,就显示这句话</slot>
    <slot name="second">我是默认值2,如果组件没有定义,就显示这句话</slot>
  </div>
填坑的地方使用名
  • 当多个标签要用到一个插槽时,都补上一个很麻烦,怎么解决
    • 用div? 会多了一个div,不好看,那用什么
    • 用template,template是不会显示在元素上的
 <One title="music">
      <ul slot="first">
        <li v-for="(i,index) in 5"
            :key="index">{{i}}</li>
      </ul>
      <p slot="second">插槽2</p>
    </One>
    <One title="games">
      <img slot="first"
           src="./assets/logo.png"></img>
    </One>
    <One title="films">
      <template slot="first"> //v-slot:first
        <video src="https://www.bilibili.com/video/BV1Zy4y1K7SH?p=102"></video>
        <p>欢迎收看电影</p>
      </template>
    </One>
作用域插槽
为什么需要?

能够做到将挖坑的子组件的数据传给填坑的父组件中的插槽处

怎么用?
挖坑处传数据

One.vue

//可以传多组数据
<slot name="first"
          :musics="music">我是默认值1,如果组件没有定义,就显示这句话</slot>

 data () {
    return {
      music: [
        "稻香", "龙卷风", "青花瓷", '晴天'
      ]
    }

APP.vue

  • 一定要使用template接收,收到的会是一个对象(为啥是对象,因为可能拿到多组数据)
  • 其实这些可以用子传父完成,官方给我们一种精简版本
   <One title="music">
      <template slot="first"
                scope="myMusic">
        <ul>
          <li v-for="(i,index) in myMusic.musics"
              :key="index">{{i}}</li>
        </ul>
        <p>插槽2</p>
      </template>
    </One>
填坑处拿数据

14.vuex

官网链接: 安装 | Vuex (vuejs.org)

干啥用?

先看官网:
  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 (状态就是数据)
  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。
总结:

也就是解决多个组件需要相同的数据,适用于多组件数据的共享通信

结构分析

1653200592674

看图可知结论
  • vuex:是由Actions,Mutations,State三部分(都是对象)组成
  • 我们的组件是调用一个方法dispatch(派遣)给Action,参数有需要执行的方法和State里的数据
  • Action又通过commit(提交)方法给Mtations,commit的参数也是执行的方法和State的数据
  • Mutate方法不需要我们调用,会自动修改State的数据,如何会Render渲染我们的组件
思考:

上面的结论点有个冲突,感觉Actions是多余的,因为只是把数据传进去又传出来,那么Actions有什么用呢

  • Actions上面有个Backend Api(后端接口),也就是说,当需要从另外一台服务器拿到数据,就需要Action执行,

  • 那么如果数据可以直接拿到,Actions是不是就多余了,所以我们的组件是可以直接调用commit的,跳过Actions方法

注意点:
  • vuex三部分由store对象管理
  • dispatch和commit都是store的方法
  • Mutations才是正在做数据修改的地方

怎么用?

环境的搭建
  • 安装vuex,记住vue2对应vuex3,vue3对应vuex4
  • vue.use(vuex),全局使用插件
    • 为什么要这样,因为我们想所有的组件上都有store对象,我们必须要用到store对象的方法
  • 创建store商店
main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'

// 引入vuex
// 使用插件,得放到store里

// 引入我们创建的store,后面不写文件默认index
import store from './store'
//关闭Vue的生产提示
Vue.config.productionTip = false
var vm=new Vue({
  el: '#app',
  store,
  render: h => h(App)
})
console.log(vm)
store/index.js
import Vuex from 'vuex'
import Vue from 'vue'
// 这里得使用插件,必须放在这里,因为脚手架的约定
Vue.use(Vuex)
// 响应组件的动作(可以做异步)
const Actions = {}
// 操作数据
const Mumations = {}
// 储存数据
const State = {}
var store=new Vuex.Store({
  Actions,
  Mumations,
  State
})

export default store
遇到的问题

在创建store对象前,必须要先使用vuex插件,而你引入store就会执行完,如果你想把引用vuex和执行vuex插件放在前面,但是由于脚手架,默认把所有import优先放在前面,所以你还是不能解决

那该怎么解决?

把引用vuex和执行vuex插件在store里执行就行

基础的使用

index.js

import Vuex from 'vuex'
import Vue from 'vue'
// 这里得使用插件,必须放在这里,因为脚手架的约定
Vue.use(Vuex)
// 响应组件的动作(可以做异步),和一些业务处理
const actions = {
  ji (context, value) {
    if (context.state.sum % 2 != 0) {
      context.commit('JI', value)
    }
  }

}
// 操作数据
const mutations = {
  add (state, value) {
    state.sum += value
  },
  jian (state, value) {
    state.sum -= value
  },
  JI (state, value) {
    state.sum += value
  }
}
// 储存数据
const state = {
  sum: 0
}

// 类似于计算属性,可以对数据进行处理
const getters = {
  sumLast (state) {
    return state.sum * 10;
  }
}
var store = new Vuex.Store({
  actions,
  mutations,
  state,
  getters
})

export default store

count.vue

<template>
  <div>
    <p>当前总和{{this.$store.state.sum}}</p>
    <p>当前总和*10{{this.$store.getters.sumLast}}</p>
    <select v-model.number="n">
      <option v-for="x in xuan"
              :key="x">{{x}}</option>
    </select>

    <button @click="add">+</button>
    <button @click="jian">-</button>
    <button @click="ji">奇数才能+</button>
  </div>
</template>

<script>
export default {
  name: "count",
  data () {
    return {
      n: 1,
      zong: 0,
      xuan: [1, 2, 3]
    }
  },
  methods: {
    add () {
      this.$store.commit("add", this.n)
      console.log(this.$store)
    },
    jian () {
      this.$store.commit("jian", this.n)
    },
    ji(){
      this.$store.dispatch("ji",this.n)
    }
  }
}
</script>

注意:

  • state储存数据,actions做一些异步或者业务处理,mutations做数据处理

  • action对象中,方法的参数,分别是context(mini版store),和value(我们自己传入的数据)

    • 为什么说是mini版store,里面的方法是store的一部分,包括我们常用的commit和state以及dispatch
  • mutations,方法的参数,分别是state(储存数据的对象)和value(我们自己传入的数据)

  • getters对象类似于计算属性(计算属性只能在单组件里使用),通过$store.getters获取值,getter对象里方法的参数是state,跟计算属性一样方法名就是返回的值名,需要返回

  • 开发者工具是记录mutations的,也就是如果你在actions做完了数据处理,开发者工具检测不到

思考:

这个vuex的三个组成部分,有点像MVC架构,在count.vue(Controller层)里调用,actions(Service层)里调用,mutations(Dao层)里创建这个真正操作数据的方法

扩展mapState和mapGetters
为啥要用?

我们在插值语法里一直写$store.state.某值,然后我们想到去计算属性里自己定义,但是我们发现还是有大量的重复,所以vuex给我们提供了对象mapState和mapGetters

<p>当前总和{{sum}}</p>

computed: {
    sum () {
      return this.$store.state.sum
    },
	sumLast(){
	  return this.$store.getters.sumLast
}
  },
使用
  • 引入

    import { mapState } from 'vuex'
    import { mapGetters } from 'vuex'
    
  • 参数 为一个对象,且键值对都是字符串,第一个是方法计算方法sum,第二个是state的数据sum(这里的...是es6的能把对象的key:value帮我们展开)

 computed: {
    ...mapState({ 'sum': 'sum' }),
    ...mapGetters({ "sumLast": "sumLast" })
     //如果key,vue重名可以写成mapGetters({["sumLast"]})
  },
扩展mapActions和mapMutations
作用:跟上面扩展一样
  methods: {
    add () {
      this.$store.commit("add", this.n)
    },
    jian () {
      this.$store.commit("jian", this.n)
    },
    ji () {
      this.$store.dispatch("ji", this.n)
    }
  }
使用

引用

import { mapActions, mapMutations } from 'vuex'

使用

参数:方法名,actions下的方法和mutations下面的方法

methods: {
     //默认会帮我们创建方法ji(),add(),jian()
    ...mapActions({ 'ji': 'ji' }),
    ...mapMutations({ 'add': 'ADD', 'jian': 'JIAN' })
  }

注意:

  • 我们发现,这里的话我们需要的参数没有传过去,肯定有问题,可以通过下面这种方式传数据
 <button @click="add(n)">+</button>
    <button @click="jian(n)">-</button>
    <button @click="ji(n)">奇数才能+</button>
  • 上面的4个扩展,当两个参数名相同时,简写用数组接收,比如mapGetters({["sumLast"]})

模块化的使用

为什么需要?

你想啊,我们所有的组件共用一个store里的东西,当组件使用的东西多了,那不是很难管理

怎么使用?

在store文件夹中再分一层为moudle层,放各个组件的需要操作数据,最后引入到index里

list2.vue

<template>
  <div>
    前:
    <ul>
      <li v-for="(L,index) in list2"
          :key="index">{{L}}</li>
    </ul>
    <input type="text"
           v-model="shu">
    <button @click="add(shu)">添加</button>
    <button @click="addxian(shu)">限制添加</button>
    后
    <ul>
      <li v-for="(L,index) in addlist"
          :key="index">{{L}}</li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex'
import { mapMutations } from 'vuex'
export default {
  name: "list1",
  data () {
    return {
      value: 1,
      shu: ""
    }
  },
  computed: {
     //简写 
    ...mapState("a", { "list2": "list" }),
    addNumber () {
      console.log(this.$store.getters["a/addNumber"])
      return this.$store.getters["a/addNumber"]
    },
    sum () {
      return this.$store.state.a.sum
    },
    addlist () {
      return this.$store.getters['a/addlist']
    }
  },
  methods: {
    ...mapMutations("a", { "add": "ADD" }),
    addxian () {
      this.$store.dispatch("a/addxian", this.shu)
    }
  }
}
</script>

<style>
</style>

One.js

<template>
  <div>
    前:
    <ul>
      <li v-for="(L,index) in list2"
          :key="index">{{L}}</li>
    </ul>
    <input type="text"
           v-model="shu">
    <button @click="add(shu)">添加</button>
    <button @click="addxian(shu)">限制添加</button><ul>
      <li v-for="(L,index) in addlist"
          :key="index">{{L}}</li>
    </ul>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
  name: "list1",
  data () {
    return {
      value: 1,
      shu: ""
    }
  },
  computed: {
    // ...mapState("a", { "list2": "list" }),
    list2 () {
      return this.$store.state.a.list
    },
    addlist () {
      return this.$store.getters["a/addlist"]
    },
    // ...mapGetters("a", { "addlist": "addlist" }),
  },
  methods: {
    // ...mapMutations("a", { "add": "ADD" }),
    add () {
      return this.$store.commit("a/ADD", this.shu)
    },
    
    // ...mapActions("a", { "addxian": "addxian" }),
    addxian(){
       return this.$store.dispatch("a/addxian", this.shu)
    }
  }
}
</script>

<style>
</style>

Two.js

const Two = {
  state: {
    sum: 10
  },
  mutations: {
    ADD (state, value) {
      state.sum += value
    }
  }
}

export default Two

index.js

在store里定义对象modules声明

import Vuex from 'vuex'
import Vue from 'vue'
import One from './Moudle/One'
import Two from './Moudle/Two'
Vue.use(Vuex)


var store = new Vuex.Store({
  modules: {
    a: One,
    b: Two
  }
})

export default store

此时我们输出这个时候的store只有我们定义的a,b对象

1653311383255

像上面说的,只有state里只有a和b了,那么数据的调用就会发生改变

数据调用方式
第一种:
  • 拿state数据时:调用state里的对象a再去调用里面的数据

  • 调用方法(actions和mutations)时(不同组件可能有相同方法):需要 moudle里定义的名字/方法

  • 拿getters里的数据要用getters["moudle/getter下的属性"],自己打印store看

 methods: {
    add () {
      return this.$store.commit( 'a/ADD', this.shu)
    },
    addxian () {
      this.$store.dispatch("a/addxian", this.shu)
    }
  },
 computed: {
    list2 () {
      return this.$store.state.a.list
    },
    addlist(){
      return this.$store.getters['a/addlist']
    },
  },
第二种:

使用前面的扩展来实现简写

  computed: {
      //...展开对象的键值对填充到当前对象
      //'a':表示store里的a对象
      //{key:value}:都是字符串,是a对象下的,在计算属性下,key是最终的计算值,value是state对象下的值
   ...mapState("a", { "list2": "list" }),
    ...mapGetters("a", { "addlist": "addlist" }),
  },
  methods: {
    ...mapMutations("a", { "add": "ADD" }),
    ...mapActions("a", { "addxian": "addxian" }),
  }
}

注意:

const One = {
  namespaced: true,
}

如果在One.js下的对象里没有加入namespaced: true就会出现下面的报错

1653311770631

15.通信总结

父传子:用pros

子传父:props传函数,自定义事件

兄弟组件:全局事件总线(上面都能实现)

vuex:都能实现,但是麻烦,一般用在大型项目上

16.路由

官网: Home | Vue Router (vuejs.org)

什么是路由?

  • 生活中的路由器:一个网络接口对应一台设备(key:value)

  • 程序中的路由:

    • 前端路由:
      • 组件间的跳转
    • 后端路由:
      • 通过一个请求会找到一个function,并返回数据(比如java的Controller)

为什么需要路由?

  • 多页面网站:

    • 一个网站跳到另一个网站,需要刷新页面,耍起来很不舒服
  • 单页面网站(SPA):

    • 不刷新网页的情况下更新数据(ajax),舒服

路由的基础使用?

环境步骤:
  • 下载vue-router
  • use使用插件(跟vuex一样,不使用这个插件,vm实例对象里没有这个router对象)
  • 通过router创建路由
  • 使用路由
使用步骤:
  • 创建路由(此时就算没有下面这个步骤,你也可以通过浏览器链接跳转)

    export default new Router({
      routes: [
        { path: "/shopcast", component: Shop },
        { path: "/", component: Lie }
      ]
    })
    
  • 使用路由

    • to就是路由里配置的path
    • link默认为a标签,style样式里想写就必须写a标签的
    • router-view就是对应组件呈现的样子,你想呈现在哪,就放哪
     <router-view></router-view>
     <router-link to="/">列表</router-link>
     <router-link to="/shopcast">购物车</router-link>
    
  • 将router挂到vm上

    import Router from './router/index'
    Vue.use(vueRouter)
    Vue.config.productionTip = false
    
    var vm = new Vue({
      el: '#app',
      router: Router,
      render: h => h(App)
    })
    
项目目录:
.
|-- App.vue
|-- components
|   |-- lie.vue
|   `-- shop.vue
|-- main.js
`-- router
    `-- index.js
lie.vue
<template>
  <p>我是列表菜单</p>
</template>

<script>
export default {
  name: "Lie"
}
</script>

<style>
p {
 font-size: 30px;
}
</style>
shop.vue
<template>
  <p>我是购物车</p>
</template>

<script>
export default {
  name: "Shop"
}
</script>
index.js
import Router from 'vue-router'
import Shop from '../components/shop.vue'
import Lie from '../components/lie.vue'

export default new Router({
  routes: [
    { path: "/shopcast", component: Shop },
    { path: "/", component: Lie }
  ]
})
main.js
import Vue from 'vue'
import App from './App.vue'
import vueRouter from 'vue-router'
import Router from './router/index'
Vue.use(vueRouter)
Vue.config.productionTip = false

var vm = new Vue({
  el: '#app',
  router: Router,
  render: h => h(App)
})
App.vue
<template>
  <div>
    <router-view></router-view>
    <router-link to="/">列表</router-link>
    <router-link to="/shopcast">购物车</router-link>
  </div>
</template>

<script>

export default {
  name: 'App',
}
</script>

<style scoped>
a {
  width: 500px;
  margin-right: 20px;
  border: 1px solid;
  text-decoration: none;
  display: block;
  height: 40px;
  float: left;
  font-size: 20px;
  height: 40px;
  text-align: center;
  margin-top: 500px;
}
div{
  margin-left: 160px;
}

</style>

注意点

路由组件和一般组件

一般组件:我们会去直接使用 比如:

路由组件:需要我们link 和view标签跳转和呈现

在切换路由组件的过程中,其他路由组件的状态

结论:销毁

验证:

lie.vue

export default {
  name: "Lie",
  mounted () {
    console.log("列表组件被挂载了")
  },
  beforeDestroy () {
    console.log("列表组件被销毁了")
  }
}

shop.vue

export default {
  name: "Shop",
  mounted () {
    console.log("商品组件被挂载了")
  },
  beforeDestroy () {
    console.log("商品组件被销毁了")
  }
}

点击切换就会发现

1653376397016

$route及 router

结论:

$route:每一个路由组件都是不同的

$router:所有组件共有一个

当你在路由里给组件注册了路由路径时,输出这个组件实例对象,就可以发现

1653376581311

嵌套路由

使用

一个路由里套另一个就行children

路由规则
export default new Router({
  routes: [
    { path: "/shopcast", component: Shop },
    {
      path: "/lie1", component: Lie, children: [
        { path: "lie2", component: Lie2 }
      ]
    }
  ]
})
组件使用
<template>
  <div>
    <p>我是列表菜单</p>
    <router-link to="/lie1/lie2">lie2啊</router-link>
    <router-view></router-view>
  </div>
</template>

我的想法

原本我以为需要在父路由组件实例里挂载上当前的路由,再一想好像确实没有必要,因为当定义一个路由配置的时候,path绑定不仅仅绑定了一个component,还有children,所以挂载到vm实例时,一个组件和他所有的子路由组件都挂载上去了

注意点:
  • children里的path路径不能带 /
  • 子路由组件使用时,to=父路径/子路径
  • 父路由会在子路由后销毁

query参数

干啥用?

当一个组件需要复用的时候,只是数据不同就需要传递数据

怎么用?
  • 在link上传递数据

  • 在对应的路由组件接收数据,每一个路由组件实例都会有各自的$route,里面有数据

项目结构

1653381726396

lie2.vue
<template>
  <div>
    <ul>
      <li v-for="i in mes"
          :key="i.y">
          <!-- 字符串写法 -->
        <!-- <router-link :to="`/lie1/lie2/lie3?id=${i.y}&messega=${i.context}`">{{i.x}}</router-link> -->
        <!-- 对象式写法 -->
        <router-link :to="{path:'/lie1/lie2/lie3',query:{id:i.y,messega:i.context}}">{{i.x}}</router-link>
      </li>
    </ul>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: "Lie2",
  data () {
    return {
      mes: [
        { x: "消息1", y: "1", context: "你好1" },
        { x: "消息2", y: "2", context: "你好2" },
        { x: "消息3", y: "3", context: "你好3" },
      ]
    }
  },
  mounted () {
    console.log("lie2挂载上了")
    console.log(this)
  }
}
</script>

show.vue
<template>
  <div>
    <p>id:{{$route.query.id}}</p>
    <p>messega:{{$route.query.messega}}</p>
  </div>
</template>

<script>
export default {
  name: "Show",
  mounted () {
    console.log(this.$route)
  }
}
</script>

<style>
</style>
index.js
import Router from 'vue-router'
import Shop from '../pages/shop.vue'
import Lie from '../pages/lie.vue'
import Lie2 from '../pages/lie2.vue'
import Show from '../pages/show.vue'

export default new Router({
  routes: [
    { path: "/shopcast", component: Shop },
    {
      path: "/lie1", component: Lie, children: [
        {
          path: "lie2", component: Lie2, children: [
            { path: "lie3", component: Show }
          ]
        }
      ]
    }
  ]
})

命名路由

干啥用?

解决路由路径过长的问题

怎么用?

定义路由配置的时候,加个name

export default new Router({
  routes: [
    { name: "shop", path: "/shopcast", component: Shop },
    {
      name: "lie1",
      path: "/lie1", component: Lie, children: [
        {
          name: "lie2",
          path: "lie2", component: Lie2, children: [
            { name: "lie3", path: "lie3", component: Show }
          ]
        }
      ]
    }
  ]
})

使用时,必须使用对象类型,调用name

<router-link :to="{name:'lie3',query:{id:i.y,messega:i.context}}">{{i.x}}</router-link>

params参数

用法:

  • 组件上只传值,不传值的名
  • 在配置文件那里定义值名
<!-- params参数 -->
        <!-- 字符串写法 -->
        <!-- <router-link :to="`/lie1/lie2/lie3/${i.y}/${i.context}`">{{i.x}}</router-link> -->
        <!-- 对象写法,并且params只能使用name,不能使用path -->
        <router-link :to="{name:'lie3',params:{id:i.y,messega:i.context}}">{{i.x}}</router-link>
 { name: "lie3", path: "lie3/:id/:messega", component: Show }

路由的props配置

为什么使用?
  • 在vuex里模板里大量使用$store.state.数据后来我们使用mapState等对象处理简化了它

  • 那么在vue-router里该怎么简化呢?

    • props属性配置
怎么用?
  • 在路由配置里配置对象属性props
  • 在对应的组件里接收参数
import Router from 'vue-router'
import Shop from '../pages/shop.vue'
import Lie from '../pages/lie.vue'
import Lie2 from '../pages/lie2.vue'
import Show from '../pages/show.vue'

export default new Router({
  routes: [
    { name: "shop", path: "/shopcast", component: Shop },
    {
      name: "lie1",
      path: "/lie1", component: Lie, children: [
        {
          name: "lie2",
          path: "lie2", component: Lie2, children: [
            {
              name: "lie3", path: "lie3/:id/:messega", component: Show,
              // 第一种对象形式,只能赋固定值
              // props:{id:"1",messega:"你好"} 

              //第二种(只适用于params),通过布尔值,如果为真就把所有params的值,传给props
              // props: true

              // 第三种,函数形式,我们无非就是想拿到$route,函数默认参数就是route
              props ($route) {
                return {
                  id: $route.query.id,
                  messega: $route.query.messega
                }
              }
            }
          ]
        }
      ]
    }
  ]
})
<template>
  <div>
    <p>id:{{id}}</p>
    <p>messega:{{messega}}</p>
  </div>
</template>

<script>
export default {
  name: "Show",
  mounted () {
    console.log(this.$route)
  },
      //像组件传值一样接收
  props: ["id","messega"]
}
</script>

<style>
</style>

push和replace模式

应用场景:

  • push:router-link默认模式,就像栈一样,有一个从上往下压,当前有一个指针,可以返回到之前的历史位置

  • replace:需要在router-link添加replace,每跳转到一个组件,之前的组件记录就会被删掉,无法跳回

 <router-link replace to="/lie1">列表</router-link>
    <router-link replace to="/shopcast">购物车</router-link>

编程式路由导航

干啥用?

不借助进行路由跳转,并且更加灵活

怎么用?
注意点

记得我们上面说过$route(每一个路由的规则)和$router(路由器)

route:是路由的规则,可以有多个

router:是路由器,是VueRouter的实例对象,全局唯一一个

使用
  • 路由规则不变

  • 编程路由

    • 有两种定义方式,上面说的push和replace
    • 对象里的内容跟link 绑定的:to对象内容格式一样
 <button @click="p(i)">push显示</button>
 <button @click="r(i)">replace显示</button>


methods: {
    r (i) {
      this.$router.replace({
        path: '/lie1/lie2/lie3',
        query: { id: i.y, messega: i.context }
      })
    },
    p (i) {
      this.$router.push({
        name: 'lie3',
        query: { id: i.y, messega: i.context }
      })
    }
  }
拓展

前进和后退,同样在$router下的原型对象下

methods: {
    forward () {
      this.$router.forward();
    },
    back () {
      this.$router.back();
    },
    go () {
      this.$router.to(2);
    }
  }

路由组件缓存

干啥用?

我们前面(注意点)说过,一个组件跳到另外的组件时,这个组件会被销毁

解决,当我们跳到另一个路由组件仍然需要保存当前组件上的数据

怎么用?
  • 使用标签包裹展示组件,此时包裹的所有组件都被缓存
  • 使用属性(include,exclude,值为组件名),限制是哪一个组件缓存
<keep-alive include="Lie2">
      <router-view></router-view>
    </keep-alive>

特别的组件生命周期

干啥用:组件独有的钩子函数,用于捕获路由组件的激活状态

activated和deactivated

当展示的是当前组件时,调用activated

当不是时调用deactivated

全局路由守卫

干什么用?

做权限判断,每次切换路由,都会经过路由守卫

怎么用?
前置守卫(切换前调用)

每次切换的时候就会执行,有三个参数

to:

from:

next():不执行这个方法,组件就不会切换过去

const router = new Router({
  routes: [                                              //meta里存储数据
    { name: "shop", path: "/shopcast", component: Shop, meta: { isOk: true, n: "商店" }
    //省略
    }
  ]
})


// 前置守卫
router.beforeEach((to, from, next) => {
  // to:去哪,能拿到组件name,path,meta,query,params等 
  // from:来自哪,同上
  if (to.meta.isOk) {
    if (localStorage.getItem("guard") == 'lie1') {
      next()   //放行
    }
  } else {
    alert("权限不足")
  }
  // console.log(to, from)
})
meta元数据

干啥用的?

router配置里有一个专门供我们存储数据的位置

后置守卫(切换后调用)

每次执行完前置的next方法后才会调用这个

这里有个应用案例:每次切换路由更改对应网页title

// 后置守卫,前置守卫next()后,切换路由后执行
router.afterEach((to, from) => {
    //只有两个参数,路由都切换了当然不需要next()
  console.log(to, from)
  document.title = to.meta.n
})

独享和组件路由守卫

独享守卫:很明显,
  • 是某一个路由里单独使用的,
  • 只有前置beforeEnter,参数与全局的一致
  • 无后置
routes: [
    {
      name: "shop", path: "/shopcast", component: Shop, meta: { isOk: true, n: "商店" }, beforeEnter (to,from,next) {
          console.log(to,from,next)
      }
    }
    ]

组件路由守卫

  • 放在组件里
  • 参数一样3个
  • 这里的before和after和全局路由守卫前后置的区别
    • 全局守卫前置:那你从一个路由切换到另外一个执行,此时不执行next()方法,将无法执行后置,并不会路由跳转
    • 全局后置:就是跳转成功后执行
    • 组件路由beforeRouteEnter().进入到当前组件(必须通过路由规则进入)的路由执行
    • 组件路由beforeRouteLeave(),离开当前组件(必须通过路由规则离开)到另外一个组件执行
    • 区别:
      • 除了上面使用的区别
      • 组件路由跳转成功,执行一个方法
      • 而全局跳转成功则是执行两个
export default {
  name: "Lie",
  mounted () {
    // console.log(this)
    // console.log("列表组件被挂载了")
  },
  beforeDestroy () {
    // console.log("列表组件被销毁了")
  },
  methods: {
    lie4 () {
      this.$router.push({
        name: "lie4",
      })
    }
  },
  // 当进入当前路由组件时,执行
  beforeRouteEnter (to, from, next) {
    console.log("进入")
    console.log(to, from, next)
    next()
  },
  // 当退出当前路由组件,执行
  beforeRouteLeave (to, from, next) {
    console.log("出去")
    console.log(to, from, next)
    // next()
  }
}

hash和history模式及打包

hash
  • 带#,不美观
  • 默认就是这个工作模式
  • 兼容性好
  • 的作用,#后的都只是hash值,并不是地址,也就是说,你在给服务端发请求的时候,#后的不会带过去

history
  • 美观
  • 兼容性没有hash好
  • 在应用部署时上线时,需要后端人员的配合,解决刷新404问题
打包

我们把我们的文件给别人,不可能直接把vue文件给别人

  • 所以就需要打包 npm run build
  • 生成dist文件夹,下面就是html,css,js文件了

17.element ui

移动端常用ui

Vant

Cube UI

Mint UI

pc端常用ui

Element UI

IView UI

基础使用
  • 下载

  • 引入,并使用全局插件

  • 官网每个组件下面都会有对应属性看着修改就行了

    main.js

//引入全部的组件
import Element from 'element-ui'
// 引入全部的样式
import 'element-ui/lib/theme-chalk/index.css';
//注册全局组件
Vue.use(Element)
按需使用

如果直接下载全部引用的话,会导致消耗很大的资源

使用
  • 官网下载里的按需引入,下载

  • 根据官网配置babel文件

  • 用哪个,引入哪个

  • 全局组件注册

    import { Button, Link, InputNumber, Rate } from 'element-ui';
    //此时不需要引入css,引入组件就会帮我们引入css了
    // 注册全局组件 第一个参数就是组件的名字,你甚至都可以改成自己的
    Vue.component(InputNumber.name, InputNumber)
    Vue.component("el-link", Link)
    Vue.component("LC-rate", Rate)
    Vue.component(Button.name, Button);
    
报错

1653630355864

解决:下载没找到的东西就行

1653630489418

修改配置文件

官网的: ["es2015", { "modules": false }],
修改成:["@babel/preset-env", { "modules": false }]

18.配置代理

19.axios

Vue3

posted @   又菜又ai  阅读(106)  评论(0)    收藏  举报
点击右上角即可分享
微信分享提示