【Vue全家桶】--- 第三章:Vue脚手架(Vue CLI)

🍅点击前往Vue开发知识总结总目录(建议收藏)

第三章:使用Vue脚手架(Vue CLI)

3.1、初始化脚手架

3.1.1、说明

  1. 脚手架全名:Command line interface
  2. Vue 脚手架是Vue 官方提供的标准化开发工具(开发平台)
  3. 文档: https://cli.vuejs.org/zh/。

3.1.2、具体步骤

前提:先安装Nodejs

第一步(仅第一次执行):全局安装@vue/cli。

npm install -g @vue/cli

第二步:切换到你要创建项目的目录,然后使用命令创建项目

vue create xxxx

第三步:启动项目(在当前目录下执行)

npm run serve

备注:

  • 如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org

  • Vue 脚手架隐藏了所有webpack 相关的配置,若想查看具体的webpakc 配置, 请执行:vue inspect > output.js

3.1.3、模板项目的结构及配置

结构

├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件

配置

1.关于不同版本的Vue:

1.vue.js与vue.runtime.xxx.js的区别:

​ (1)vue.js时完整版的Vue,包含:核心功能+模板解析器

​ (2)vue.runtime.xxx.js是运行版的Vue,只包含:核心功能:没有模板解析器

2.因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用

​ render函数接收到的createElement函数去指定具体内容

2.修改默认配置文件(vue.config.js)

  1. 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
  3. lintOnSave: false //关闭语法检查

3.暴露方式

第一种方式(分别):export const school = Vue.extend({})

第二种方式(同一):export {school}

第三种方式(默认==>推荐):export default school 简化 export default Vue.extend({})

第三种更精简方式

<script>
    export default {
        name: "xxx",
        data() {
            return {
                name: "小王",
                age: 15,
            };
        },
    };
</script>

3.1.4、第一个脚手架实例

main.js

/* 
该文件时整个项目的入口文件
*/
//引入Vue (残缺版vue)
import Vue from 'vue'
//完整版Vue
//import Vue from 'vue/dist/vue'
/* 
引入App组件,它是所有组件的父组件
*/
import App from './App.vue'
//关闭vue生产提示
Vue.config.productionTip = false


//创建Vue实例对象    vm
new Vue({
   el: '#app',
   //将App组件放入容器中
  /*  render(createElement) {

      return createElement('h1', '你好')
   } */
    render: h => h(App)
}) 

App.vue

<template>
<div>
    <SchoolName></SchoolName>
    <StudentName></StudentName>
    </div>
</template>

<script>
    //引入组件
    import SchoolName from './components/SchoolName.vue'
    import StudentName from './components/StudentName.vue'
    export default {
        name:'App',
        components:{
            SchoolName,
            StudentName
        }
    }
</script>

SchoolName.vue

  <!-- 组件的结构 -->
<template>
  <div class="demo">
    <h2>学校名称:{{ name }}</h2>
    <h2>学校地址:{{ address }}</h2>
  </div>
</template>

<script>
//组件交互相关的代码(数据、方法等等)
    export default{
      name:'SchoolName',
      data() {
        return {
          name: "平顶山学院",
          address: "平顶山",
        };
      },
    }

//export default school
</script>
<style>
/* 组件的样式 */
    .demo {
      background-color: orange;
    }
</style>

Student.vue

  <!-- 组件的结构 -->
<template>
  <div>
    <h2>学生名称:{{ name }}</h2>
    <h2>学生年龄:{{ age }}</h2>
  </div>
</template>

<script>
//组件交互相关的代码(数据、方法等等)
export default {
  name: "StudentName",
  data() {
    return {
      name: "小王",
      age: 15,
    };
  },
};
</script>

index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
		<!-- 针对IE浏览器的一个特殊配置,含义是让IE浏览器以最高的渲染级别渲染页面 -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
		<!-- 开启移动端的理想视口 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
		<!-- 配置页签图标 -->
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
		<!-- 引入第三方样式 -->
<!-- 	<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css"> -->
		<!-- 配置网页标题 -->
    <title>硅谷系统</title>
  </head>
  <body>
		<!-- 当浏览器不支持js时noscript中的元素就会被渲染 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
		<!-- 容器 -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

3.2、ref属性与props属性

3.2.1、ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
  4. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
  5. 获取:this.$refs.xxx

3.2.2、props属性

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']
    2. 第二种方式(限制类型):props:{name:String}
    3. 第三种方式(限制类型、限制必要性、指定默认值):
    props:{
    	name:{
    	type:String, //类型
    	required:true, //必要性
    	default:'老王' //默认值
    	}
    }
    

备注:

props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

data() {
    return {
        myName:this.name
    };
},

3.3、mixin(混入)

  1. 功能:可以把多个组件共用的配置提取成一个混入对象

  2. 使用方式:

    第一步定义混合:mixin.js**(main.js同级目录)**

    export const mixin = {//分别暴露
        data(){....},
        methods:{....}
        ....
    }
    

    第二步使用混入:import {mixin} from '../mixin'

    1. 全局混入:Vue.mixin(xxx)
    2. 局部混入:mixins:['xxx']

3.4、插件

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。

  3. 定义插件:plugins .js**(main.js同级目录)**

    export default {
        install(Vue){
            //全局过滤器
            Vue.filter('mySlice',function(value){
                return value.slice(0,4)
            })
    
            //定义全局指令
            Vue.directive('fbind',{
                //指令与元素成功绑定时(一上来)
                bind(element,binding){
                    element.value = binding.value
                },
                //指令所在元素被插入页面时
                inserted(element,binding){
                    element.focus()
                },
                //指令所在的模板被重新解析时
                update(element,binding){
                    element.value = binding.value
                }
            })
    
            //定义混入
            Vue.mixin({
                data() {
                    return {
                        x:100,
                        y:200
                    }
                },
            })
    
            //给Vue原型上添加一个方法(vm和vc就都能用了)
            Vue.prototype.hello = ()=>{alert('你好啊')}
        }
    }
    
  4. 引入并使用插件:

    //引入插件

    import plugins from './plugins'

    使用插件

    Vue.use()

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

3.5、scoped样式

  1. 作用:让样式在局部生效,防止冲突。
  2. 写法:<style scoped>
  3. 安装less

npm i less-loader

样式:

<style lang="less" scoped>
  .demo{
    background-color:skyblue;
    .qwe{
      font-size: 40px;
    }
  }
</style>

3.6、TodoList

  1. 组件化编码流程:

​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

​ 1).一个组件在用:放在组件自身即可。

​ 2). 一些组件在用:放在他们共同的父组件上(状态提升)。

​ (3).实现交互:从绑定事件开始。

  1. props适用于:

​ (1).父组件 ==> 子组件 通信

​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数

  1. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

  2. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。

案例:后面会持续改良,请向下继续阅读

子组件 ==> 父组件 通信方式:父先给子一个函数
请添加图片描述

main.js

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

App.js

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader :addTodo="addTodo" />
        <List
          :todos="todos"
          :checkTodo="checkTodo"
          :deleteTodo="deleteTodo"
        />
        <MyFooter
          :todos="todos"
          :checkAllTodo="checkAllTodo"
          :clearAllTodo="clearAllTodo"
        />
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import List from "./components/List.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
  name: "App",
  components: { MyHeader, MyFooter, List },
  data() {
			return {
				//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
				todos:[
					{id:'001',title:'抽烟',done:true},
					{id:'002',title:'喝酒',done:false},
					{id:'003',title:'开车',done:true}
				]
			}
		},
    methods:{
      //添加一个todo
      addTodo(todoObj){
         this.todos.unshift(todoObj)
      },
      //勾选or取消勾选一个todo
      checkTodo(id){
        this.todos.forEach((todo)=>{
            if(todo.id==id) todo.done = !todo.done
        })
      },
      //删除todo
      deleteTodo(id){
        //使用filter不修改原数组
        this.todos = this.todos.filter((todo)=>{
          return todo.id !== id
        })
      },
      //全选or全部选
      checkAllTodo(done){
        this.todos.forEach((todo)=>{
          todo.done = done
        })
      },
      //清除已经完成的todo
      clearAllTodo(){
        this.todos = this.todos.filter((todo)=>{
          return !todo.done
        })
      }
      
    }
};
</script>
<style>
/*base*/
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>

MyHeader.vue

<template>
  <div class="todo-header">
		<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
	</div>
</template>

<script>
import {nanoid} from 'nanoid'
export default {
    name:'MyHeader',
   data() {
        return {
            title:''
        } 
    }, 
    methods:{
        add(){
            if(!this.title.trim()) return alert('输入不能为空!')
            //将用户的输入包装成一个TODO对象
            const todoObj = {id:nanoid(),title:this.title,done:false}
            //通知App组件去添加一个todo对象
            this.addTodo(todoObj)
            //清空输入
            this.title = ''
        }
    },
    props:['addTodo']

}
</script>

<style scoped>
/*header*/
	.todo-header input {
		width: 560px;
		height: 28px;
		font-size: 14px;
		border: 1px solid #ccc;
		border-radius: 4px;
		padding: 4px 7px;
	}

	.todo-header input:focus {
		outline: none;
		border-color: rgba(82, 168, 236, 0.8);
		box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
	}
</style>

List.vue

<template>
  <ul class="todo-main">
		<Item 
			v-for="todoObj in todos"
			:key="todoObj.id" 
			:todo="todoObj"
			:checkTodo="checkTodo"
			:deleteTodo="deleteTodo"
			
		/>
	</ul>
</template>

<script>
import Item from './Item'
export default {
    name:'List',
    components:{Item},
    props:['todos','checkTodo','deleteTodo']

}
</script>

<style scoped>
	/*main*/
	.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="todo.done" @change="handleCheck(todo.id)"/>
	 <!-- 如下代码也能实现功能,但是不太推荐,因为有点违反规则,因为修改了props -->
	 <!-- <input type="checkbox" v-model="todo.done"/> -->
     <span>{{todo.title}}</span>
    </label>
    <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
  </li>
</template>

<script>
export default {
  name: "Item",
  //声明接收todo对象
  props:['todo','checkTodo','deleteTodo'],
  methods:{
	  //勾选
	  handleCheck(id){
		  //通知APP组件将对应的todo对象的done值取反
		  this.checkTodo(id)
		 
	  },
	  //删除
	  handleDelete(id){
         if(confirm('确定删除吗?')){
			 this.deleteTodo(id)
		 }
	  }
  }
};
</script>

<style scoped>
/*item*/
	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>

MyFooter.vue

<template>
  <div class="todo-footer" v-show="total">
    <label>
      <!-- 			<input type="checkbox" :checked="isAll" @change="checkAll"/>-->
    </label>
    <input type="checkbox" v-model="isAll" />
    <span>
      <span>已完成:{{ doneTotal }}</span> / 全部:{{ total }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "MyFooter",
  props: ["todos", "checkAllTodo", 'clearAllTodo'],
  computed: {
    total() {
      return this.todos.length;
    },
    doneTotal() {
      /* let i = 0
			this.todos.forEach(todo => {
				if(todo.done){
					i++
				}
			});
			return i */

      //reduce 条件统计 数组长度是几调用几次
      //pre上一次的值
      //current 当前值
      const i = this.todos.reduce((pre, todo) => {
        return pre + (todo.done ? 1 : 0);
      }, 0);
      return i;
    },
    isAll: {
      get() {
           return this.doneTotal === this.total && this.total > 0;
	  },
      set(value) {
        this.checkAllTodo(value);
      },
    },
  },
  methods: {
   /*  checkAll(e) {
      this.checkAllTodo(e.target.checked);
    }, */
	clearAll(){
		if(this.doneTotal>0){
			if(confirm('确定删除吗?'))
            this.clearAllTodo()
		}
         
		 else
		 alert('请选择需要清除的任务!')
	}
  },
};
</script>

<style scoped>
/*footer*/
.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>

3.7、webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

  3. 相关API:

  4. xxxxxStorage.setItem('key', 'value');
    该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

  5. xxxxxStorage.getItem('person');

    ​ 该方法接受一个键名作为参数,返回键名对应的值。

  6. xxxxxStorage.removeItem('key');

    ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

  7. xxxxxStorage.clear()

    ​ 该方法会清空存储中的所有数据。

  8. 备注:

  9. SessionStorage存储的内容会随着浏览器窗口关闭而消失。

  10. LocalStorage存储的内容,需要手动清除才会消失。

  11. xxxxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null。

  12. JSON.parse(null)的结果依然是null。

案例:以localStorage为例,sessionStorage类似

<!DOCTYPE html>
<html>
  <head>
    <meta charset='UTF-8'>
    <title>localStorage</title>
  </head>
  <body>
    <h2>localStorage</h2>
    <button onclick="saveData()">点我保存一个数据</button>
    <button onclick="readData()">点我读取一个数据</button>
    <button onclick="deleteData()">点我删除一个数据</button>
    <button onclick="deleteAllData()">点我清空全部数据</button>

    <script type="text/javascript">
        let person = {name:'张三',age:15 }
        function saveData(){
            //key 和 value 都必须是字符串,自动转换为字符串
            localStorage.setItem('msg','hello')
            localStorage.setItem('person',JSON.stringify(person))
        }
        function readData(){
            console.log(localStorage.getItem('msg'))
           const result = localStorage.getItem('person')
            console.log(JSON.parse(result))

        }
        function deleteData(){
           localStorage.removeItem('msg')
           localStorage.removeItem('person')
        }
        function deleteAllData(){
           localStorage.clear()   
        }
    </script>
  </body>
</html> 

3.8、组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

在3.6、TodoList章节中采用的方式是:通过父组件给子组件传递函数类型propos实现

  1. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  2. 绑定自定义事件:

  3. 第一种方式,在父组件中:<Student v-on:xiaowang="getStudentName" "/><Student @demo="m1"/>

父组件中

//两个写法xiaowang和m1
<Student v-on:xiaowang="getStudentName" @demo="m1"/>

    methods:{
        getStudentName(name,...params){
            console.log('App收到了学生名:',name,params)
            this.studentName = name
        }, 
            m1(){
                console.log('demo事件被触发了')
            }
    },

子组件中

//可以写一个事件触发
 <button @click="sendStudentName">把学生名字传给App</button>

 <button @click="sendStudentName">把学生名字传给App</button>
     <button @click="unbind">解绑xiaowang事件</button>
     <button @click="death">销毁当前Student组件的实例(vc)</button>
 methods:{
     sendStudentName(){
       //触发Student组件实例身上的xiaowang事件
      //this.$emit('xiaowang',this.name,222,3333)
      //this.$emit('demo')
      //this.$emit('click')
     },
     unbind(){
       this.$off('xiaowang')//解绑一个自定义事件
       //this.$off(['xiaowang','demo'])//解绑多个自定义事件
       //this.$off()//解绑所有的自定义事件
     },
     death(){
       this.$destroy()//销毁了当前的Student组件的实例,销毁后所有的Student实例的自定义事件全部不奏效
     }
   },

第二种方式:使用ref,在父组件中

<Student ref="student" @click.native="show"/><hr>

    methods:{
        getStudentName(name,...params){
            console.log('App收到了学生名:',name,params)
            this.studentName = name
        },
    },
        mounted(){
            this.$refs.student.$on('xiaowang',this.getStudentName)//绑定自定义事件
            /* this.$refs.student.$on('xiaowang',(name,...params)=>{
                 console.log('App收到了学生名:',name,params)
                 this.studentName = name
             }) */
            //this.$refs.student.$once('xiaowang',this.getStudentName)//绑定自定义事件:一次性
        }

  1. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。
  2. 触发自定义事件:this.$emit('atguigu',数据)
  3. 解绑自定义事件this.$off('atguigu')
  4. 组件上也可以绑定原生DOM事件,需要使用native修饰符。
  5. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

**案例:**还是3.6的TODOList案例,结合3.6、3.7、3.8整合改良

App.vue

<template>
  <div id="root">
    <div class="todo-container">
      <div class="todo-wrap">
        <MyHeader @addTodo="addTodo" />
        <List
          :todos="todos"
          :checkTodo="checkTodo"
          :deleteTodo="deleteTodo"
        />
        <MyFooter
          :todos="todos"
          @checkAllTodo="checkAllTodo"
          @clearAllTodo="clearAllTodo"
        />
      </div>
    </div>
  </div>
</template>

<script>
import MyHeader from "./components/MyHeader.vue";
import List from "./components/List.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
  name: "App",
  components: { MyHeader, MyFooter, List },
  data() {
			return {
				//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)
				/* todos:[
					{id:'001',title:'抽烟',done:true},
					{id:'002',title:'喝酒',done:false},
					{id:'003',title:'开车',done:true}
				] */
        todos:JSON.parse(localStorage.getItem('todos')) || []
			}
		},
    methods:{
      //添加一个todo
      addTodo(todoObj){
         this.todos.unshift(todoObj)
      },
      //勾选or取消勾选一个todo
      checkTodo(id){
        this.todos.forEach((todo)=>{
            if(todo.id==id) todo.done = !todo.done
        })
      },
      //删除todo
      deleteTodo(id){
        //使用filter不修改原数组
        this.todos = this.todos.filter((todo)=>{
          return todo.id !== id
        })
      },
      //全选or全部选
      checkAllTodo(done){
        this.todos.forEach((todo)=>{
          todo.done = done
        })
      },
      //清除已经完成的todo
      clearAllTodo(){
        this.todos = this.todos.filter((todo)=>{
          return !todo.done
        })
      }
      
    },
    watch:{
      todos:{
        deep:true,
        handler(value){
          localStorage.setItem('todos',JSON.stringify(value))
        }
      }
    }
};
</script>

MyHeader.vue

<template>
  <div class="todo-header">
		<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
	</div>
</template>

<script>
import {nanoid} from 'nanoid'
export default {
    name:'MyHeader',
   data() {
        return {
            title:''
        } 
    }, 
    methods:{
        add(){
            if(!this.title.trim()) return alert('输入不能为空!')
            //将用户的输入包装成一个TODO对象
            const todoObj = {id:nanoid(),title:this.title,done:false}
            //通知App组件去添加一个todo对象
            this.$emit('addTodo',todoObj)
            //清空输入
            this.title = ''
        }
    },

}
</script>

MyFooter.vue

<template>
  <div class="todo-footer" v-show="total">
    <label>
      <!-- 			<input type="checkbox" :checked="isAll" @change="checkAll"/>-->
    </label>
    <input type="checkbox" v-model="isAll" />
    <span>
      <span>已完成:{{ doneTotal }}</span> / 全部:{{ total }}
    </span>
    <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
  </div>
</template>

<script>
export default {
  name: "MyFooter",
  props: ["todos"],
  computed: {
    total() {
      return this.todos.length;
    },
    doneTotal() {
      /* let i = 0
			this.todos.forEach(todo => {
				if(todo.done){
					i++
				}
			});
			return i */

      //reduce 条件统计 数组长度是几调用几次
      //pre上一次的值
      //current 当前值
      const i = this.todos.reduce((pre, todo) => {
        return pre + (todo.done ? 1 : 0);
      }, 0);
      return i;
    },
    isAll: {
      get() {
           return this.doneTotal === this.total && this.total > 0;
	  },
      set(value) {
       this.$emit('checkAllTodo',value)
      },
    },
  },
  methods: {
   /*  checkAll(e) {
      this.checkAllTodo(e.target.checked);
    }, */
	clearAll(){
		if(this.doneTotal>0){
			if(confirm('确定删除吗?'))
            this.$emit('clearAllTodo')
		}    
		 else{
       alert('请选择需要清除的任务!')
     }
	}
  },
};
</script>

Item.vue、List.vue暂未做修改

3.9、全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信。
  2. 安装全局事件总线:main.js中
new Vue({
	el: '#app',
    render: h => h(App),
    //生命周期钩子,最先调用
	beforeCreate() {
        /此时的this就是vm
		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
	},
    ......
}) 
  1. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

       mounted(){
          this.$bus.$on('xxx',(data)=>{
           .....
          })
        }
      
    2. 提供数据this.$bus.$emit('xxxx',数据)

  2. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。(提供数据的组件中)

    beforeDestroy(){
         this.$bus.$off('xxx')//进行销毁
      },
    

3.10、消息订阅与发布(pubsub)

建议使用全局事件总线

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:安装pubsub:

    1. npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'接收发送都需要)

  3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

    回调方法必须有两个参数

    • 第一个参数:代表订阅名(例如下面的’hello’)

    • 第二个参数:回调数据

    methods(){
      demo(_,data){......}
    }
    ......
    mounted() {
        //此处三种写法
        //第一种(不建议)
        this.pubId =  pubsub.subscribe('hello',function(msgName,data){
         console.log(this)//不使用箭头函数,此时为undefined
        })
        //第二种
        this.pubId =  pubsub.subscribe('hello',(msgName,data)=>{
         // console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
         console.log(this)//不使用箭头函数,此时为undefined
         
        })
        //第三种(建议):配合上面的methods
        this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
    }
    
  4. 提供数据pubsub.publish('xxx',数据)

  5. 最好在接收数据beforeDestroy钩子中,用PubSub.unsubscribe(pid)去取消订阅。

3.11、nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

例:判断todo是否包含isEdit属性

<button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit" >编辑</button>

//编辑
handleEdit(todo) {
    //判断是否包含isEdit属性
    if (todo.hasOwnProperty('isEdit')) {
        //不包含则添加属性
        this.$set(todo, "isEdit", true);
    } else {
        todo.isEdit = true;
    }
    //下一轮
    this.$nextTick(function(){
        this.$refs.inputTitle.focus()
    })
}
//失去焦点回调(真正执行编辑逻辑)
handleBlur(todo,e) {
    todo.isEdit = false;
    if(!e.target.value.trim()) return alert('输入不能为空!!!')
    this.$bus.$emit('updateTodo',todo.id,e.target.value)
},

3.12、Vue封装的过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 图示:在这里插入图片描述

  3. 写法:

    第一步:准备好样式

    • 进入样式:
      1. v-enter:进入的起点
      2. v-enter-active:进入过程中
      3. v-enter-to:进入的终点
    • 离开样式:
      1. v-leave:离开的起点
      2. v-leave-active:离开过程中
      3. v-leave-to:离开的终点

​ 第二步:使用<transition>包裹要过度的元素,并配置name属性:

<transition name="hello">
	<h1 v-show="isShow">你好啊!</h1>
</transition>

​ 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

例:通过点击展示效果

单元素

<template>
<div>
    <button @click="isShow=!isShow">显示/隐藏</button>
    <transition name="hello" appear>
        <h1 v-show="isShow" class="go">你好啊!</h1>
    </transition>
    </div>
</template>

<script>
    export default {
        name: "Test",
        data() {
            return {
                isShow: true,
            };
        },
    };
</script>

<style  scoped>
    h1 {
        background-color: orange;
    }
    .hello-enter-active{
        animation: xiaowang 1s;
    }
    .hello-leave-active{
        animation: xiaowang 1s reverse;
    }
    @keyframes xiaowang {
        from{
            transform: translateX(-100px);
        }
        to{
            transform: translateX(0px);
        }
    }
</style>>

多元素

<template>
<div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group name="hello" appear>
        <h1 v-show="!isShow" class="go" :key=1>你好啊!</h1>
        <h1 v-show="isShow" class="go" :key=2>你好啊!</h1>
    </transition-group>
    </div>
</template>

<script>
    export default {
        name: "Test2",
        data() {
            return {
                isShow: true,
            };
        },
    };
</script>

<style  scoped>
    h1 {
        background-color: orange;
        transition: 0.5s linear;
    }
    /* 进入的起点 离开的终点*/
    .hello-enter ,.hello-leave-to {
        transform: translateX(-100%);
    }
    /* 进入的终点 离开的起点 */
    .hello-enter-to, .hello-leave{
        transform: translateX(0);
    }

</style>>

引入其他样式https://animate.style/
在这里插入图片描述

<template>
<div>
    <button @click="isShow = !isShow">显示/隐藏</button>
    <transition-group
                      appear
                      name="animate__animated animate__bounce" 
                      enter-active-class="animate__swing" 
                      leave-active-class="animate__backOutUp"
                      >
        <h1 v-show="!isShow" class="go" :key=1>你好啊!</h1>
        <h1 v-show="isShow" class="go" :key=2>你好啊!</h1>
    </transition-group>
    </div>
</template>

<script>
    import 'animate.css'
    export default {
        name: "Test2",
        data() {
            return {
                isShow: true,
            };
        },
    };
</script>

<style  scoped>
    h1 {
        background-color: orange;
        transition: 0.5s linear;
    }

</style>>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mylife0506

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值