<body>
<div id="root">
<hello>hello>
<school>school>
<hr>
<student>student>
div>
<hr>
<div id="root2">
<hello>hello>
div>
body>
<script type="text/javascript" >
Vue.config.productionTip = false; // 组织 Vue 在启动时生成生产提示
// 第一步:创建 school 组件
const school = Vue.extend({
template:`
学校名称: {{name}}
学校地址: {{address}}
`,
data(){
return {
name: '南昌大学',
address: '江西南昌'
}
}
})
// 第一步:创建 student 组件
const student = Vue.extend({
template:`
学生姓名: {{name}}
学生年龄: {{age}}
`,
data(){
return {
name: '凌宸',
age: 21
}
}
})
// 第一步:创建 hello 组件
const hello = Vue.extend({
template:`
你好啊!{{name}}
`,
data(){
return {name:'凌宸'}
}
})
// 第二步:注册组件(全局注册)
Vue.component('hello', hello)
new Vue({
el: '#root',
data:{
msg: '你好啊!'
},
// 第二步:注册组件(局部注册)
components:{
school,
student
}
})
new Vue({
el: '#root2',
})
script>
<body>
<div id="root">
<app>app>
div>
body>
<script type="text/javascript" >
Vue.config.productionTip = false;
// 创建 student 组件
const student = Vue.extend({
template:`
学生姓名: {{name}}
学生年龄: {{age}}
`,
data(){
return {
name: '凌宸',
age: 21
}
}
})
// 创建 school 组件
const school = Vue.extend({
name:'school',
template:`
学校名称: {{name}}
学校地址: {{address}}
`,
data(){
return {
name: '南昌大学',
address: '江西南昌'
}
},
// 局部注册
components:{
student
}
})
// 创建 hello 组件
const hello = {
template:`{{msg}}
`,
data(){return {msg:'你好啊'}}
}
// 创建 app 组件
const app = {
template:`
`,
components:{
hello,
school
}
}
// 创建 vm
new Vue({
el: '#root',
components:{ app }
})
script>
<body>
<div id="root">
<school>school>
<hello>hello>
div>
body>
<script type="text/javascript" >
Vue.config.productionTip = false;
// 创建 school 组件
const school = Vue.extend({
template:`
学校名称: {{name}}
学校地址: {{address}}
`,
data(){
return {
name: '南昌大学',
address: '江西南昌'
}
},
})
console.log(school)
// 创建 hello 组件
const hello = {
template:`{{msg}}
`,
data(){return {msg:'你好啊'}}
}
// 创建 vm
new Vue({
el: '#root',
components:{ school, hello }
})
script>
<template>
<div class="demo">
<h2>学校名称: {{name}}h2>
<h2>学校地址: {{address}}h2>
<button @click="showName">点我提示学校名button>
div>
template>
<script>
export default {
name:'School',
data() {
return {
name:'南昌大学',
address:'江西南昌'
}
},
methods: {
showName(){ alert(this.name) }
},
}
script>
<style>
.demo{ background-color: pink; }
style>
<template>
<div >
<h2>学生姓名: {{name}}h2>
<h2>学生年龄: {{age}}h2>
div>
template>
<script>
export default {
name:'Student',
data() {
return {
name:'凌宸',
age:21
}
},
}
script>
<template>
<div>
<school />
<student/>
div>
template>
<script>
// 引入组件
import School from './School'
import Student from './Student'
export default {
name:'App',
components: { School, Student }
}
script>
import App from './App.vue'
new Vue({
el: '#root',
components:{App}
})
<body>
<div id="root">
<App>App>
div>
<script type="text/javascript" src="../js/vue.js">script>
<script type="text/javascript" src="./main.js">script>
body>
html>
PS:将这5个文件放入同一级目录,然后运行index.html 文件,浏览器是没有效果的。需要在 Vue 的脚手架中运行。
CLI (@vue/cli) 是一个全局安装的 npm 包,提供了终端里的 vue命令。它可以通过 vue create快速搭建一个新项目,或者直接通过 vue serve 构建新想法的原型。
将 npm 配置为淘宝镜像。
npm config set registry https://registry.npm.taobao.org
全局安装@vue/cli (仅第一次执行)。
npm install -g @vue/cli
切换到需要创建项目的目录,使用命令创建项目。
vue create xxxx
根据提示进入 xxxx 目录,启动项目,并完成访问,查看 HelloWorld 项目。
npm run serve
├── 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:
vue.js 与 vue.runtime.xxx.js 的区别:
vue.js 是完整版的 Vue,包含:核心功能 + 模板解析器。
vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器。
因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,
需要 render 函数接受的 createElement 函数去指定具体内容。
module.exports = {
pages: {
index: {
// page 的入口
entry: 'src/index/main.js',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,template 中的 title 标签需要是:
// <%= htmlWebpackPlugin.options.title %>
title: 'Index Page',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 当使用只有入口的字符串格式时,
// 模板会被推导为 `public/subpage.html`
// 并且如果找不到的话,就回退到 `public/index.html`。
// 输出文件名会被推导为 `subpage.html`。
subpage: 'src/subpage/main.js'
},
lintOnSave: false // 关闭语法检查
}
<template>
<div>
<h1 v-text="msg" ref="title">h1>
<button @click="showDOM" ref="btn">点我展示上方的 DOM 信息button>
<school ref="sch"/>
div>
template>
<script>
// 引入组件
import School from './components/School'
/*
ref 属性:
被用来给元素或子组件注册引用信息(id 的替代者);
应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc);
使用方式:
打标识: .......
或
获取:this.$refs.xxx
*/
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 组件的实例对象
}
},
}
script>
<template>
<div>
<h1>{{msg}}h1>
<h2>学生姓名: {{name}}h2>
<h2>学生性别: {{sex}}h2>
<h2>学生年龄: {{myAge}}h2>
<button @click="myAge ++">点我年龄加 1button>
div>
template>
<script>
export default {
name:'Student',
data() {
return {
msg:'我是南昌大学的一名学生',
myAge:this.age
}
} ,
// 简单接受
// props:['name', 'sex', 'age'],
// 接受的同时对数据进行类型限制。
/*props:{
name:String,
age:Number,
sex:String
}*/
// 接受的同时对数据进行类型限制,必要性限制,默认值指定。
props:{
name:{
type:String,
required:true,
},
age:{
type:Number,
default:99
},
sex:{
type:String,
required:true
}
}
}
script>
// mixin.js
export const mixin = {
methods:{
showName(){
alert(this.name)
}
}
}
<template>
<div>
<h2 @click="showName">学生姓名: {{name}}h2>
<h2>学生性别: {{sex}}h2>
div>
template>
<script>
// 引入一个 混合
// import {mixin} from '../mixin'
export default {
name:'Student',
data() {
return {
name:'张三',
sex:'男'
}
},
// mixins:[mixin]
}
script>
import Vue from 'vue'
import App from './App.vue'
import {mixin} from './mixin'
// 全局配置 混合
Vue.config.productionTip = false
Vue.mixin(mixin)
new Vue({
render: h => h(App),
}).$mount('#app')
export default {
install(Vue){
// 全局过滤器
Vue.filter('mySlice', function(value){
console.log(value)
return value.slice(0, 4)
})
// 定义全局指令
Vue.directive('big', function(element, binding){
element.innerText = binding.value * 10
})
// 定义混合
Vue.mixin({
data(){
return {
x: 100,
y: 200
}
}
})
// 给 Vue 原型上添加一个方法
Vue.prototype.hello = () => {alert('你好啊!')}
}
}
import Vue from 'vue'
import App from './App.vue'
// 引入插件
import plugin from './plugin'
Vue.config.productionTip = false
Vue.use(plugin)
new Vue({
render: h => h(App),
}).$mount('#app')
组件化编码流程:
props适用于:
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
props传过来的若是对象类型的值,修改对象中的属性时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:''}
},
methods: {
add(e){
// 数据校验
if(!this.title.trim()) return alert('输入数据不能为空')
// 将用户输入封装为 todo 对象
const todo = {id:nanoid(), title:this.title, done:false}
// console.log(todo)
this.addTodo(todo) // 调用 App 中的 addTodo 方法完成添加
// 清空输入框
this.title = ''
}
},
}
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>
<template>
<ul class="todo-main">
<MyItem
v-for="t in todos"
:key="t.id"
:todo="t"
:checkTodo="checkTodo"
:deleteTodo="deleteTodo"
/>
ul>
template>
<script>
// 引入 MyItem 组件
import MyItem from './MyItem'
export default {
name:'MyList',
components:{MyItem},
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>
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handlerCheck(todo.id)"/>
<span>{{todo.title}}span>
label>
<button class="btn btn-danger" @click="handlerDelete(todo.id)">删除button>
li>
template>
<script>
export default {
name:'MyItem',
// 接受传来的 todo 对象
props:['todo', 'checkTodo', 'deleteTodo'],
methods: {
// 勾选 or 取消勾选
handlerCheck(id){
// 通知 App 组件中对应 id 的 done 值 取反
this.checkTodo(id)
},
// 删除一个 todo 项
handlerDelete(id){
if(confirm('确定删除吗?')){
this.deleteTodo(id)
}
}
},
}
script>
<style>
/*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>
<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="deleteAll">清除已完成任务button>
div>
template>
<script>
export default {
name:'MyFooter',
props:['todos', 'checkAllTodo', 'deleteAllTodo'],
computed:{
total(){
return this.todos.length
},
doneTotal(){
return this.todos.reduce((pre,todo) => pre + (todo.done ? 1 : 0) , 0)
},
isAll:{
get(){
return this.total === this.doneTotal && this.total !== 0
},
set(value){
this.checkAllTodo(value)
}
}
},
methods: {
deleteAll(){
if(confirm('确定清楚所有已完成项目吗?')){
this.deleteAllTodo()
}
}
},
}
script>
<style>
/*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>
<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"
:deleteAllTodo="deleteAllTodo" />
div>
div>
div>
template>
<script>
// 引入组件
import MyHeader from './components/MyHeader'
import MyFooter from './components/MyFooter'
import MyList from './components/MyList'
export default {
name:'App',
components: { MyHeader, MyFooter, MyList} ,
data(){
return {
todos: [
{id:'001', title: '学习 Vue', done: false},
{id:'002', title: '学习 Java', done: true},
{id:'003', title: '学习 C++', done: false}
]
}
},
methods: {
// 添加一个 todo 对象
addTodo(todo){
// console.log(todo)
this.todos.unshift(todo)
},
// 勾选 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
deleteAllTodo(){
this.todos = this.todos.filter(todo => !todo.done)
console.log(this.todos)
}
},
}
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;border-radius: 4px;
box-shadow: inset 0 1px 0 rgba(255,255,255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
}
.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>
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)。
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
相关API:
xxxxxStorage.setItem(‘key’, ‘value’); 该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
xxxxxStorage.getItem(‘person’); 该方法接受一个键名作为参数,返回键名对应的值。
xxxxxStorage.removeItem(‘key’); 该方法接受一个键名作为参数,并把该键名从存储中删除。
xxxxxStorage.clear() 该方法会清空存储中的所有数据。
备注:
SessionStorage存储的内容会随着浏览器窗口关闭而消失。
LocalStorage存储的内容,需要手动清除才会消失。
xxxxxStorage.getItem(xxx) 如果xxx对应的value获取不到,那么getItem的返回值是null。
JSON.parse(null) 的结果依然是null。
以 localStorage 为例:
<body>
<h1>localStorageh1>
<button onclick="saveData()" >点我保存一个数据button>
<button onclick="getData()" >点我读取一个数据button>
<button onclick="deleteData()" >点我删除一个数据button>
<button onclick="deleteAllData()" >点我清空一个数据button>
body>
<script type="text/javascript" >
let p = {name:'张三', age:18}
function saveData(){
localStorage.setItem('msg', '你好啊')
localStorage.setItem('p', JSON.stringify(p))
}
function getData(){
console.log(JSON.parse(localStorage.getItem('p')))
}
function deleteData(){
localStorage.removeItem('msg')
}
function deleteAllData(){
localStorage.clear()
}
script>
<script>
// 引入组件
import MyHeader from './components/MyHeader'
import MyFooter from './components/MyFooter'
import MyList from './components/MyList'
export default {
name:'App',
components: { MyHeader, MyFooter, MyList} ,
data(){
return {todos:JSON.parse(localStorage.getItem('todos')) || [] }
},
methods: { .... },
watch:{
todos:{
deep:true,
handler(value){
localStorage.setItem('todos', JSON.stringify(value))
}
}
}
}
script>
一种组件间通信的方式,适用于:子组件 ===> 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
绑定自定义事件:
<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>
<Demo ref="demo"/>
......
mounted(){
this.$refs.xxx.$on('atguigu',this.test)
}
触发自定义事件: this.$emit(‘atguigu’,数据)
解绑自定义事件: this.$off(‘atguigu’)
组件上也可以绑定原生DOM事件,需要使用 native 修饰符。
注意:通过 this.$refs.xxx.$on(‘atguigu’,回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
<template>
<div class="student">
<h2>学生姓名: {{name}}h2>
<h2>学生年龄: {{age}}h2>
<h2>当前的 n 值为 {{number}} h2>
<button @click="add">点我 n + 1button>
<button @click="sendStudnetName" >点我发送学校名给 Appbutton>
<button @click="unbind">点我解绑lingchen事件button>
<button @click="death">点我销毁 Student 实例button>
div>
template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
age: 18,
number: 1
}
},
methods: {
add(){
console.log('add被调用了')
this.number ++
},
sendStudnetName(){
// 触发 Studnet 的实例对象 vc 上绑定的 lingchen 事件
this.$emit('lingchen', this.name)
},
unbind(){
this.$off('lingchen') // 解绑一个自定义事件
// this.$off(['lingchen', 'atguigu']) // 解绑多个自定义事件
// this.$off() // 解绑所有的自定义事件
},
death(){
this.$destroy()
// 销毁了当前 Student 组件的实例对象,销毁后所有的 Student 实例的自定义事件都不奏效。
}
},
}
script>
<style scoped>
.student{background-color: skyblue;padding: 5px; margin-top: 30px;}
style>
<template>
<div class="app">
<h1>{{msg}}h1>
<School :getSchoolName="getSchoolName"/>
<Student v-on:lingchen="getStudentName" @click.native="show"/>
div>
template>
<script>
// 引入组件
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components: { Student, School} ,
data(){
return {
msg:'你好啊!'
}
},
methods: {
getSchoolName(name){
console.log("App 收到了学校名:", name)
},
getStudentName(name){
console.log("App 收到了学生名:", name)
},
show(){
alert('组件使用原生事件 click')
}
},
mounted() {
// this.$refs.student.$on('lingchen', this.getStudentName) // 绑定自定义事件
// this.$refs.student.$once('lingchen', this.getStudentName)// 绑定自定义事件(一次性)
},
}
script>
<style>
.app{ background-color: gray; padding: 5px; }
style>
<MyHeader @addTodo="addTodo"/>
<MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @deleteAllTodo="deleteAllTodo"/>
<script>
import {nanoid} from 'nanoid'
export default {
name:'MyHeader',
// props:['addTodo'] // 不使用 props
data(){
return {title:''}
},
methods: {
add(e){
// 数据校验
if(!this.title.trim()) return alert('输入数据不能为空')
// 将用户输入封装为 todo 对象
const todo = {id:nanoid(), title:this.title, done:false}
// console.log(todo)
// this.addTodo(todo)
this.$emit('addTodo', todo) // 触发 addTodo 事件
// 清空输入框
this.title = ''
}
},
}
script>
<script>
export default {
name:'MyFooter',
props:['todos'], // 不再接受 checkAllTodo 和 deleteAllTodo
computed:{
.....,
isAll:{
get(){
return this.total === this.doneTotal && this.total !== 0
},
set(value){
// this.checkAllTodo(value)
this.$emit('checkAllTodo', value) // 触发 checkAllTodo
}
}
},
methods: {
deleteAll(){
if(confirm('确定清楚所有已完成项目吗?')){
// this.deleteAllTodo()
this.$emit('deleteAllTodo') // 触发 deleteAllTodo
}
}
},
}
script>
一种组件间通信的方式,适用于任意组件间通信。
安装全局事件总线:
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
使用事件总线:
接收数据:A组件想接收数据,则在A组件中给 $bus 绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
提供数据:this.$bus.$emit(‘xxxx’,数据) 。
最好在 beforeDestroy 钩子中,用 $off 去解绑当前组件所用到的事件。
典例
// main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
beforeCreate(){
Vue.prototype.$bus = this // 安装全局事件总线 $bus 就是当前的 vm
}
}).$mount('#app')
<template>
<div class="student">
<h2>学生姓名: {{name}}h2>
<h2>学生年龄: {{age}}h2>
<button @click="sendStudentName">点我发送学生姓名给 School 组件button>
div>
template>
<script>
export default {
name:'Student',
data() {
return {
name:'张三',
age: 18,
}
},
methods: {
sendStudentName(){
this.$bus.$emit('hello', this.name)
}
}
}
script>
<template>
<div class="school">
<h2>学校名称: {{name}}h2>
<h2>学校地址: {{address}}h2>
div>
template>
<script>
export default {
name:'School',
data() {
return {
name:'南昌大学',
address:'江西南昌'
}
},
methods: {
getStudentName(studnetName){
console.log('我是School组件,我拿到了数据:', studnetName)
}
},
mounted() {
this.$bus.$on('hello', this.getStudentName) // 绑定事件
},
beforeDestroy() {
this.$bus.$off('hello') // 解绑事件
}
}
script>
<MyList :todos="todos" />
mounted() {
this.$bus.$on('checkTodo', this.checkTodo)
this.$bus.$on('deleteTodo', this.deleteTodo)
},
beforeDestroy() {
this.$bus.$off('checkTodo')
this.$bus.$off('deleteTodo')
},
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handlerCheck(todo.id)"/>
<span>{{todo.title}}span>
label>
<button class="btn btn-danger" @click="handlerDelete(todo.id)">删除button>
li>
template>
<script>
export default {
name:'MyItem',
// 接受传来的 todo 对象
props:['todo'],
methods: {
// 勾选 or 取消勾选
handlerCheck(id){
// 通知 App 组件中对应 id 的 done 值 取反
// this.checkTodo(id)
this.$bus.$emit('checkTodo', id) //
},
// 删除一个 todo 项
handlerDelete(id){
if(confirm('确定删除吗?')){
// this.deleteTodo(id)
this.$bus.$emit('deleteTodo', id)
}
}
},
}
script>
npm install pubsub-js
import pubsub from 'pubsub-js'
mounted() {
// this.$bus.$on('hello', this.getStudentName) // 绑定事件
// 订阅消息
this.subId = pubsub.subscribe('hello', this.handlerHello)
},
beforeDestroy() {
// this.$bus.$off('hello') // 解绑事件
// 取消订阅
pubsub.unsubsribe(this.subId)
}
sendStudentName(){
// this.$bus.$emit('hello', this.name)
pubsub.publish('hello', this.name)
}
mounted() {
// this.$bus.$on('checkTodo', this.checkTodo)
// this.$bus.$on('deleteTodo', this.deleteTodo)
this.checkSubId = pubsub.subscribe('checkTodo', this.checkTodo)
this.deleteSubId = pubsub.subscribe('deleteTodo', this.deleteTodo)
},
beforeDestroy() {
// this.$bus.$off('checkTodo')
// this.$bus.$off('deleteTodo')
pubsub.unsubscribe(checkSubId)
pubsub.unsubscribe(deleteSubId)
},
// 勾选 or 取消勾选
handlerCheck(id){
// 通知 App 组件中对应 id 的 done 值 取反
// this.checkTodo(id)
// this.$bus.$emit('checkTodo', id)
pubsub.publish('checkTodo', id)
},
// 删除一个 todo 项
handlerDelete(id){
if(confirm('确定删除吗?')){
// this.deleteTodo(id)
// this.$bus.$emit('deleteTodo', id)
pubsub.publish('deleteTodo', id)
}
}
语法: this.$nextTick(回调函数) 。
作用:在下一次 DOM 更新结束后执行其指定的回调。
什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
给 TodoList 添加编辑功能。
修改 MyItem 组件如下:
<template>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handlerCheck(todo.id)"/>
<span v-show="!todo.isEdit">{{todo.title}}span>
<input type="text"
v-show="todo.isEdit"
:value="todo.title"
@blur="handlerBlur(todo, $event)"
ref="inputTitle"
>
label>
<button class="btn btn-danger" @click="handlerDelete(todo.id)">删除button>
<button v-show="!todo.isEdit" class="btn btn-edit" @click="handlerEdit(todo)">编辑button>
li>
template>
<script>
import pubsub from 'pubsub-js'
export default {
name:'MyItem',
// 接受传来的 todo 对象
props:['todo'],
methods: {
// 勾选 or 取消勾选
handlerCheck(id){
// 通知 App 组件中对应 id 的 done 值 取反
// this.checkTodo(id)
// this.$bus.$emit('checkTodo', id)
pubsub.publish('checkTodo', id)
},
// 删除一个 todo 项
handlerDelete(id){
if(confirm('确定删除吗?')){
// this.deleteTodo(id)
// this.$bus.$emit('deleteTodo', id)
pubsub.publish('deleteTodo', id)
}
},
// 编辑
handlerEdit(todo){
if(todo.hasOwnProperty('isEdit')){
todo.isEdit = true;
}else{
this.$set(todo, 'isEdit', true)
}
// 获取焦点
this.$nextTick(function(){
this.$refs.inputTitle.focus()
})
},
// 失去焦点回调(真正执行修改逻辑)
handlerBlur(todo, e){
todo.isEdit = false
console.log('@@@@',todo.id, e.target.value)
if(!e.target.value.trim()) return alert('输入不能为空!')
this.$bus.$emit('updateTodo',todo.id,e.target.value)
}
},
}
script>
.btn-edit {
color: #fff;
background-color: skyblue;
border: 1px solid rgb(35, 154, 200);
margin-right: 5px;
}
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
写法:
准备好样式:
元素进入的样式:
元素离开的样式:
使用 包裹要过度的元素,并配置 name 属性:
<transition name="hello">
<h1 v-show="isShow">你好啊!h1>
transition>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏button>
<transition name="hello">
<h1 v-show="isShow">你好啊!凌宸h1>
transition>
div>
template>
<script>
export default {
name:'Test',
data(){ return { isShow:true } }
}
script>
<style>
h1{ background-color: orange; }
.hello-enter-active{ animation: lingchen 1s linear; }
.hello-leave-active{ animation: lingchen 1s linear reverse; }
@keyframes lingchen {
from{ transform: translateX(-100%); }
to{ transform: translateX(0px); }
}
style>
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏button>
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!h1>
<h1 v-show="isShow" key="2">尚硅谷!h1>
transition-group>
div>
template>
<script>
export default {
name:'Test',
data() { return { isShow:true } },
}
script>
<style scoped>
h1{ background-color: orange; }
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{ transform: translateX(-100%); }
.hello-enter-active,.hello-leave-active{ transition: 0.5s linear; }
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{ transform: translateX(0); }
style>
<template>
<ul class="todo-main">
<transition-group name="todo" appear>
<MyItem v-for="t in todos" :key="t.id" :todo="t" />
transition-group>
ul>
template>
<style>
... (其他原有样式)
.todo-enter-active{ animation: lingchen 1s linear; }
.todo-leave-active{ animation: lingchen 1s linear reverse; }
@keyframes lingchen {
from{ transform: translateX(100%); }
to{ transform: translateX(0px); }
}
style>