【前端】Vue体系(二)

Vue 2.0

三、使用Vue脚手架

1. 初始化脚手架

  • 初始化脚手架
    • Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
    • 最新的版本是 4.x。
    • 文档: https://cli.vuejs.org/zh/。
  • 具体步骤
    • 第一步(仅第一次执行):全局安装@vue/cli。
      • npm install -g @vue/cli
    • 第二步:切换到你要创建项目的目录,然后使用命令创建项目
      • vue create xxxx
    • 第三步:启动项目
      • npm run serve

2. 模板项目的结构

├── 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:包版本控制文件

## 脚手架文件结构
	├── 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:包版本控制文件

## 关于不同版本的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函数去指定具体内容。

## vue.config.js配置文件

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

3. ref 属性

// components/School.vue
<template>
	<div class="school">
		<h2>学校名称:{{name}}h2>
		<h2>学校地址:{{address}}h2>
	div>
template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京·昌平'
			}
		},
	}
script>

<style>
	.school{
		background-color: gray;
	}
style>
// App.vue
<template>
	<div>
		<h1 v-text="msg" ref="title">h1>
		<button ref="btn" @click="showDOM">点我输出上方的DOM元素button>
		<School ref="sch"/>
	div>
template>

<script>
	//引入School组件
	import School from './components/School'

	export default {
		name:'App',
		components:{School},
		data() {
			return {
				msg:'欢迎学习Vue!'
			}
		},
		methods: {
			showDOM(){
				console.log(this.$refs.title) //真实DOM元素
				console.log(this.$refs.btn) //真实DOM元素
				console.log(this.$refs.sch) //School组件的实例对象(vc)
			}
		},
	}
script> 
// main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})
## ref属性

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

4. props 配置

// App.vue
<template>
	<div>
		<Student name="李四" sex="" :age="18"/>
	div>
template>

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

	export default {
		name:'App',
		components:{Student}
	}
script>
// components/Student.vue
<template>
	<div>
		<h1>{{msg}}h1>
		<h2>学生姓名:{{name}}h2>
		<h2>学生性别:{{sex}}h2>
		<h2>学生年龄:{{myAge+1}}h2>
		<button @click="updateAge">尝试修改收到的年龄button>
	div>
template>

<script>
	export default {
		name:'Student',
		data() {
			console.log(this)
			return {
				msg:'我是一个尚硅谷的学生',
				myAge:this.age
			}
		},
		methods: {
			updateAge(){
				this.myAge++
			}
		},
		//简单声明接收
		// props:['name','age','sex'] 

		//接收的同时对数据进行类型限制
		/* props:{
			name:String,
			age:Number,
			sex:String
		} */

		//接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
		props:{
			name:{
				type:String, //name的类型是字符串
				required:true, //name是必要的
			},
			age:{
				type:Number,
				default:99 //默认值
			},
			sex:{
				type:String,
				required:true
			}
		}
	}
script>
## props配置项

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

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

3. 接收数据:

    1. 第一种方式(只接收):```props:['name'] ```

    2. 第二种方式(限制类型):```props:{name:String}```

    3. 第三种方式(限制类型、限制必要性、指定默认值):

        ```js
        props:{
        	name:{
        	type:String, //类型
        	required:true, //必要性
        	default:'老王' //默认值
        	}
        }
        ```

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

5. mixin 混入

// App.vue
<template>
	<div>
		<School/>
		<hr>
		<Student/>
	div>
template>

<script>
	import School from './components/School'
	import Student from './components/Student'

	export default {
		name:'App',
		components:{School,Student}
	}
script>
// components/School.vue
<template>
	<div>
		<h2 @click="showName">学校名称:{{name}}h2>
		<h2>学校地址:{{address}}h2>
	div>
template>

<script>
	//引入一个hunhe
	// import {hunhe,hunhe2} from '../mixin'

	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
				x:666
			}
		},
		// mixins:[hunhe,hunhe2],
	}
script>
// components/Student.vue
<template>
	<div>
		<h2 @click="showName">学生姓名:{{name}}h2>
		<h2>学生性别:{{sex}}h2>
	div>
template>

<script>
	// import {hunhe,hunhe2} from '../mixin'

	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男'
			}
		},
		// mixins:[hunhe,hunhe2]
	}
script>
// main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
import {hunhe,hunhe2} from './mixin'
//关闭Vue的生产提示
Vue.config.productionTip = false

Vue.mixin(hunhe)
Vue.mixin(hunhe2)


//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})
// mixin.js
export const hunhe = {
	methods: {
		showName(){
			alert(this.name)
		}
	},
	mounted() {
		console.log('你好啊!')
	},
}
export const hunhe2 = {
	data() {
		return {
			x:100,
			y:200
		}
	},
}
## mixin(混入)

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

2. 使用方式:

    第一步定义混合:

    ```
    {
        data(){....},
        methods:{....}
        ....
    }
    ```

    第二步使用混入:

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

6. 插件

// plugins.js
export default {
	install(Vue,x,y,z){
		console.log(x,y,z)
		//全局过滤器
		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('你好啊')}
	}
}
// main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false

//应用(使用)插件
Vue.use(plugins,1,2,3)
//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})
// App.vue
<template>
	<div>
		<School/>
		<hr>
		<Student/>
	div>
template>

<script>
	import School from './components/School'
	import Student from './components/Student'

	export default {
		name:'App',
		components:{School,Student}
	}
script>
// components/School.vue
<template>
	<div>
		<h2>学校名称:{{name | mySlice}}h2>
		<h2>学校地址:{{address}}h2>
		<button @click="test">点我测试一个hello方法button>
	div>
template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'小帽学堂',
				address:'深圳',
			}
		},
		methods: {
			test(){
				this.hello()
			}
		},
	}
script>
// components/Student.vue
<template>
	<div>
		<h2>学生姓名:{{name}}h2>
		<h2>学生性别:{{sex}}h2>
		<input type="text" v-fbind:value="name">
	div>
template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男'
			}
		},
	}
script>
## 插件

1. 功能:用于增强Vue

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

3. 定义插件:

    ```js
    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    ```

4. 使用插件:```Vue.use()```

7. scoped 样式

// App.vue
<template>
	<div>
		<h1 class="title">你好啊h1>
		<School/>
		<Student/>
	div>
template>

<script>
	import Student from './components/Student'
	import School from './components/School'

	export default {
		name:'App',
		components:{School,Student}
	}
script>

<style scoped>
	.title{
		color: red;
	}
style>
// main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})
// components/School.vue
<template>
	<div class="demo">
		<h2 class="title">学校名称:{{name}}h2>
		<h2>学校地址:{{address}}h2>
	div>
template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'小帽学堂',
				address:'深圳',
			}
		}
	}
script>

<style scoped>
	.demo{
		background-color: skyblue;
	}
style>
// components/Student.vue
<template>
	<div class="demo">
		<h2 class="title">学生姓名:{{name}}h2>
		<h2 class="alex">学生性别:{{sex}}h2>
	div>
template>

<script>
	export default {
		name:'Student',
		data() {
			return {
				name:'张三',
				sex:'男'
			}
		}
	}
script>

<style lang="less" scoped>
	.demo{
		background-color: pink;
		.alex{
			font-size: 40px;
		}
	}
style>
## scoped样式

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

8. TodoList 案例(静态)

// App.vue
<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<MyHeader/>
				<MyList/>
				<MyFooter/>
			div>
		div>
	div>
template>

<script>
	import MyHeader from './components/MyHeader'
	import MyList from './components/MyList'
	import MyFooter from './components/MyFooter.vue'

	export default {
		name:'App',
		components:{MyHeader,MyList,MyFooter}
	}
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>
// components/MyHeader.vue
<template>
	<div class="todo-header">
		<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
	div>
template>

<script>
	export default {
		name:'MyHeader',
	}
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>
// components/MyList.vue
<template>
	<ul class="todo-main">
		<MyItem/>
		<MyItem/>
		<MyItem/>
		<MyItem/>
	ul>
template>

<script>
	import MyItem from './MyItem'

	export default {
		name:'MyList',
		components:{MyItem}
	}
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>
// components/MyItem.vue
<template>
	<li>
		<label>
			<input type="checkbox">
			<span>xxxxxspan>
		label>
		<button class="btn btn-danger" @style="display:none">删除button>
	li>
template>

<script>
	export default {
		name:'MyItem'
	}
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>

9. TodoList 案例(初始化列表)

// components/MyList.vue
<template>
	<ul class="todo-main">
		<MyItem 
			v-for="todoObj in todos"
			:key="todoObj.id"
			:todo="todoObj" 
		/>
	ul>
template>

<script>
	import MyItem from './MyItem'

	export default {
		name:'MyList',
		components:{MyItem},
		data(){
			return {
				todos: [
					{id:'001', title:'抽烟',done:true},
					{id:'002', title:'喝酒',done:false},
					{id:'003', title:'开车',done:true}
				]
			}
		}
	}
script>

<style scoped>
	...
style>
// components/MyItem.vue
<template>
	<li>
		<label>
			<input type="checkbox" :checked="todo.done" />
			<span>{{todo.title}}span>
		label>
		<button class="btn btn-danger" style="display:none">删除button>
	li>
template>

<script>
	export default {
		name:'MyItem',
		//声明接收todo对象
		props:['todo']
	}
script>

<style scoped>
	...
style>

10. TodoList 案例(添加)

// components/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',
		props:['addTodo'],
		data() {
			return {
				//收集用户输入的title
				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 = ''
			}
		}
	}
script>

<style scoped>
	...
style>
// App.vue
<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<MyHeader :addTodo="addTodo" />
				<MyList :todos="todos"/>
				<MyFooter/>
			div>
		div>
	div>
template>

<script>
	import MyHeader from './components/MyHeader'
	import MyList from './components/MyList'
	import MyFooter from './components/MyFooter.vue'

	export default {
		name:'App',
		components:{MyHeader,MyList,MyFooter},
		data() {
			return {
				todos: [
					{id:'001', title:'抽烟',done:true},
					{id:'002', title:'喝酒',done:false},
					{id:'003', title:'开车',done:true}
				]
			}
		},
		methods: {
			addTodo(todoObj) {
				this.todos.unshift(todoObj)
			}
		}
	}
script>

<style>
	...
style>
// components/MyList.vue
<template>
	<ul class="todo-main">
		<MyItem 
			v-for="todoObj in todos"
			:key="todoObj.id"
			:todo="todoObj" 
		/>
	ul>
template>

<script>
	import MyItem from './MyItem'

	export default {
		name:'MyList',
		components:{MyItem},
		props:['todos']
	}
script>

<style scoped>
	...
style>

11. TodoList 案例(勾选)

// components/MyItem.vue
<template>
	<li>
		<label>
			<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
			<span>{{todo.title}}span>
		label>
		<button class="btn btn-danger" style="display:none">删除button>
	li>
template>

<script>
	export default {
		name:'MyItem',
		//声明接收todo对象
		props:['todo', 'checkTodo'],
		methods: {
			//勾选or取消勾选
			handleCheck(id){
				//通知App组件将对应的todo对象的done值取反
				this.checkTodo(id)
			}
		},
	}
script>

<style scoped>
	...
style>
// App.vue
<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<MyHeader :addTodo="addTodo" />
				<MyList :todos="todos" :checkTodo="checkTodo"/>
				<MyFooter/>
			div>
		div>
	div>
template>

<script>
	import MyHeader from './components/MyHeader'
	import MyList from './components/MyList'
	import MyFooter from './components/MyFooter.vue'

	export default {
		name:'App',
		components:{MyHeader,MyList,MyFooter},
		data() {
			return {
				todos: [
					{id:'001', title:'抽烟',done:true},
					{id:'002', title:'喝酒',done:false},
					{id:'003', title:'开车',done:true}
				]
			}
		},
		methods: {
			addTodo(todoObj) {
				this.todos.unshift(todoObj)
			},
			//勾选or取消勾选一个todo
			checkTodo(id){
				this.todos.forEach((todo)=>{
					if(todo.id === id) todo.done = !todo.done
				})
			}
		}
	}
script>

<style>
	...
style>
// components/MyList.vue
<template>
	<ul class="todo-main">
		<MyItem 
			v-for="todoObj in todos"
			:key="todoObj.id"
			:todo="todoObj"
			:checkTodo="checkTodo" 
		/>
	ul>
template>

<script>
	import MyItem from './MyItem'

	export default {
		name:'MyList',
		components:{MyItem},
		props:['todos', 'checkTodo']
	}
script>

<style scoped>
	...
style>

12. TodoList 案例(删除)

// components/MyItem.vue
<template>
	<li>
		<label>
			<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
			
			
			<span>{{todo.title}}span>
		label>
		<button class="btn btn-danger" @click="handleDelete(todo.id)">删除button>
	li>
template>

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

<style scoped>
	...
style>
// App.vue
<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<MyHeader :addTodo="addTodo" />
				<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
				<MyFooter/>
			div>
		div>
	div>
template>

<script>
	import MyHeader from './components/MyHeader'
	import MyList from './components/MyList'
	import MyFooter from './components/MyFooter.vue'

	export default {
		name:'App',
		components:{MyHeader,MyList,MyFooter},
		data() {
			return {
				todos: [
					{id:'001', title:'抽烟',done:true},
					{id:'002', title:'喝酒',done:false},
					{id:'003', title:'开车',done:true}
				]
			}
		},
		methods: {
			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){
				this.todos = this.todos.filter( todo => todo.id !== id )
			}
		}
	}
script>

<style>
	...
style>
// components/MyList.vue
<template>
	<ul class="todo-main">
		<MyItem 
			v-for="todoObj in todos"
			:key="todoObj.id" 
			:todo="todoObj" 
			:checkTodo="checkTodo"
			:deleteTodo="deleteTodo"
		/>
	ul>
template>

<script>
	import MyItem from './MyItem'

	export default {
		name:'MyList',
		components:{MyItem},
		//声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的
		props:['todos','checkTodo','deleteTodo']
	}
script>

<style scoped>
	...
style>

13. TodoList 案例(底部统计)

// components/MyFooter.vue
<template>
	<div class="todo-footer" v-show="total">
		<label>
			<input type="checkbox" v-model="isAll"/>
		label>
		<span>
			<span>已完成{{doneTotal}}span> / 全部{{todos.length}}
		span>
		<button class="btn btn-danger" @click="clearAll">清除已完成任务button>
	div>
template>

<script>
	export default {
		name:'MyFooter',
		props:['todos'],
		computed: {
			//已完成数
			doneTotal(){
				//此处使用reduce方法做条件统计
				/* const x = this.todos.reduce((pre,current)=>{
					console.log('@',pre,current)
					return pre + (current.done ? 1 : 0)
				},0) */
				//简写
				return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
			}
		}
	}
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>

14. TodoList 案例(底部交互)

// components/MyFooter.vue
<template>
	<div class="todo-footer" v-show="total">
		<label>
			
			<input type="checkbox" v-model="isAll"/>
		label>
		<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(){
				//此处使用reduce方法做条件统计
				/* const x = this.todos.reduce((pre,current)=>{
					console.log('@',pre,current)
					return pre + (current.done ? 1 : 0)
				},0) */
				//简写
				return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
			},
			//控制全选框
			isAll:{
				//全选框是否勾选
				get(){
					return this.doneTotal === this.total && this.total > 0
				},
				//isAll被修改时set被调用
				set(value){
					this.checkAllTodo(value)
				}
			}
		},
		methods: {
			/* checkAll(e){
				this.checkAllTodo(e.target.checked)
			} */
			//清空所有已完成
			clearAll(){
				this.clearAllTodo()
			}
		},
	}
script>

<style scoped>
	...
style>
// App.vue
<template>
	<div id="root">
		<div class="todo-container">
			<div class="todo-wrap">
				<MyHeader :addTodo="addTodo"/>
				<MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
				<MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
			div>
		div>
	div>
template>

<script>
	import MyHeader from './components/MyHeader'
	import MyList from './components/MyList'
	import MyFooter from './components/MyFooter.vue'

	export default {
		name:'App',
		components:{MyHeader,MyList,MyFooter},
		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){
				this.todos = this.todos.filter( todo => 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>
	...
style>

15. TodoList 案例总结

## 总结TodoList案例

1. 组件化编码流程:

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

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

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

    ​			2). 一些组件在用:放在他们共同的父组件上(<span style="color:red">状态提升span>)。

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

2. props适用于:

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

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

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

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

你可能感兴趣的:(前端,vue.js,前端)