2.1 脚手架

使用前置:

第一步(没有安装过的执行):全局安装 @vue/cli

npm install -g @vue/cli

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

vue create xxxxx

第三步:启动项目

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

脚手架demo

components:

就直接把单文件组件的 School.vue 和 Student.vue 两个文件直接拿来用,不需要修改。

App.vue:

引入这两个组件,注册一下这两个组件,再使用。

<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<Student></Student>
<School></School>
</div>
</template> <script>
import School from './components/School.vue'
import Student from './components/Student.vue' export default {
name: 'App',
components: {
School,
Student
}
}
</script> <style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>

main.js:

入口文件

import Vue from 'vue'
import App from './App.vue' Vue.config.productionTip = false new Vue({
render: h => h(App),
}).$mount('#app')

接下来就要详细讲解 main.js 中的 render 函数

render函数

插入一个小知识:

使用 import 导入第三方库的时候不需要 加 './'

导入我们自己写的:

import App from './App.vue'

导入第三方的

import Vue from 'vue'

不需要在 from 'vue' 加 './' 的原因是第三方库 node_modules 人家帮我们配置好了。

我们通过 import 导入第三方库,在第三方库的 package.json 文件中确定了我们引入的是哪个文件

通过 module 确定了我们要引入的文件。

回到 render 函数

之前的写法是这样:

import App from './App.vue'

new Vue({
el:'#root',
template:`<App></App>`,
components:{App},
})

如果这样子写,运行的话会引发如下的报错

报错的意思是,是在使用运行版本的 vue ,没有模板解析器。

从上面的小知识可以知道,我们引入的 vue 不是完整版的,是残缺的(为了减小vue的大小)。所以残缺的vue.js 只有通过 render 函数才能把项目给跑起来。

来解析一下render

// render最原始写的方式
// render是个函数,还能接收到参数a
// 这个 createElement 很关键,是个回调函数
new Vue({
render(createElement) {
console.log(typeof createElement);
// 这个 createElement 回调函数能创建元素
// 因为残缺的vue 不能解析 template,所以render就来帮忙解决这个问题
// createElement 能创建具体的元素
return createElement('h1', 'hello')
}
}).$mount('#app')

因为 render 函数内并没有用到 this,所以可以简写成箭头函数。

new Vue({
// render: h => h(App),
render: (createElement) => {
return createElement(App)
}
}).$mount('#app')

再简写:

new Vue({
// render: h => h(App),
render: createElement => createElement(App)
}).$mount('#app')

最后把 createElement 换成 h 就完事了。

算啦算啦,把简写都整理一遍吧,js里的简写确实多哇。

对象内写方法最原始的:

let obj = {
name: 'aaa',
work: function (salary) {
return '工资' + salary;
}
}

ES6 简化版:

let obj = {
name: 'aaa',
work(salary) {
return '工资' + salary;
}
}

箭头函数简化版:

let obj = {
name: 'aaa',
work: (salary) => {
return '工资' + salary;
}
}

箭头函数再简化(最终版):

// 只有一个参数就可以把圆括号去了,函数体内部只有一个 return 就可以把大括号去掉,return去掉
let obj = {
name: 'aaa',
work: salary => '工资' + salary;
}

这样就可以理解 render 函数的简写方式了。

来个不同版本 vue 的区别

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

    • vue.js是完整版的Vue,包含:核心功能+模板解析器。

    • vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。

  • 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。

脚手架中的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>

2.2 vue 零碎的一些知识

ref属性

  • 被用来给元素或子组件注册引用信息(id的替代者)

  • 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)

  • 使用方式:

    • 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>

    • 获取:this.$refs.xxx

具体案例

<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>

props配置项

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

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

  3. 接收数据:

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

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

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

props:{
name:{
type:String, //类型
required:true, //必要性
default:'老王' //默认值
}
}
  1. 备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

示例代码:

父组件给子组件传数据

App.vue

<template>
<div>
<!-- 多个vc互不影响 -->
<!-- v-bind的作用重要 获取后面表达式值的赋给props的属性,不然student组件接受的是字符串 -->
<student name="张三" sex="男" :age="18" />
</div>
</template> <script>
import Student from "./components/Student"; export default {
name: "App",
components: {
Student,
},
};
</script>

Student.vue

<template>
<div>
<!-- 注意props传递过来的值是不能改的(尽量避免去改,控制台会有警告) -->
<h1>{{ msg }}</h1>
<h1>学生姓名:{{ name }}</h1>
<h1>学生性别: {{ sex }}</h1>
<h1>学生年龄: {{ myAge }}</h1>
<button @click="updateAge">尝试修改年龄</button>
</div>
</template> <script>
export default {
name: "Student",
data() {
console.log(this);
return {
//如果props和data存在同名的属性,会报错,但已props传递的属性值为主,props优先
//注意props属性名不能是vue底层已征用的属性名(比如key, ref等等)
msg: "我一个学生",
myAge: this.age, //把props传递过来的值当成vc的状态,这样改age是不会出问题的,因为你没有直接去修改props
};
},
methods: {
updateAge() {
this.myAge++;
},
},
props: ["name", "age", "sex"], // 简单申明接受 //限制props中属性的类型 类型错误了会提示错误信息
// props: {
// name: String,
// sex: String,
// age: Number
// } // 接受的同时对数据:进行类型限制+默认值的指定+必要性的限制
// props: {
// name: {
// type: String, // name的类型是字符串
// required: true, // name是必要的
// },
// age: {
// type: Number,
// default: 99, //默认值
// },
// sex: {
// type: String,
// required: true,
// },
// },
};
</script>
<style scoped>
.demo {
background: aqua;
}
</style>

mixin(混入)

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

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

  2. 使用方式:

    第一步定义混合:

{
data(){....},
methods:{....}
....
}
  1. 第二步使用混入:

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

mixin.js

export const mixin = {
methods: {
showName() {
alert(this.name)
}
},
//挂载完毕就执行
mounted() {
console.log('你好啊')
}
} export const shareData = {
data() {
return {
x: 100,
y: 200
}
}
}

Student.vue

<template>
<div>
<h2 @click="showName">姓名:{{ name }}</h2>
<h2>性别: {{ sex }}</h2>
</div>
</template> <script>
import { mixin, shareData } from "../mixin";
export default {
name: "Student",
data() {
console.log(this);
return {
name: "张三",
sex: "男",
x: 666, //如果混合中配置了与data(或者配置了相同的methods)相同的属性值,则以你的配置的属性为主(而不以mixin为主)
};
},
mounted() {
console.log("你好啊啊!!!!"); //但对于生命周期钩子是都会保存的(混合的钩子比你配置的钩子先跑)
},
mixins: [mixin, shareData],
};
</script>

School.vue

<template>
<div>
<h2 @click="showName">学校名称:{{ name }}</h2>
<h2>学校地址: {{ address }}</h2>
</div>
</template> <script>
//引入混合
import { mixin, shareData } from "../mixin";
export default {
name: "School",
data() {
console.log(this);
return {
name: "wust",
address: "武汉科技大学",
};
},
mixins: [mixin, shareData],
};
</script>

全局混入不建议使用

插件

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制。

通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:

// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin) new Vue({
// ...组件选项
})

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

定义插件:

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

具体案例:

plugin.js

export default {
install(Vue) {
//vue帮你调用install方法
// console.log('install');
// console.log(Vue); //vm的构造函数Vue //注意配置一定要new vue实例之前确定
Vue.filter('mySlice', function (val) {
return val.slice(0, 4);
}); //全局指令
Vue.directive('fbind', {
// 指令与元素成功绑定时(一上来)
bind(el, binding) {
// console.log('bind')
el.value = binding.value;
},
//指令被插入页面时
inserted(el, binding) {
// console.log('inserted')
el.focus();
},
//指令所在模版被重新解析时
update(el, binding) {
// console.log('update');
el.value = binding.value;
}
}); // 引入混入
Vue.mixin({
data() {
return {
x: 100,
y: 200
}
}
}); //给vue原型上添加一个方法 vc/vm都可以使用
Vue.prototype.hello = function () {
alert('hello')
}
}
}

main.js

// 引入插件
import plugin from './plugin' // 使用插件
Vue.use(plugin)

然后就可以在别的组件使用插件里的功能了。

School.vue

<template>
<div>
<h2>学校名称:{{ name | mySlice }}</h2>
<h2>学校地址: {{ address }}</h2>
<button @click="test">测试一下hello方法</button>
</div>
</template> <script>
export default {
name: "School",
data() {
console.log(this);
return {
name: "wut@重庆",
address: "武汉科技大学",
};
},
methods: {
test() {
this.hello();
},
},
};
</script>

Student.vue

<template>
<div>
<h2>姓名:{{ name }}</h2>
<h2>性别: {{ sex }}</h2>
<input v-fbind:value="name" />
</div>
</template> <script>
export default {
name: "Student",
data() {
console.log(this);
return {
name: "张三",
sex: "男",
};
},
};
</script>

scoped样式

  1. 作用:让样式在局部生效,防止冲突。

  2. 写法:<style scoped>

具体案例:

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

Schoool.vue

<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址: {{ address }}</h2>
</div>
</template> <script>
export default {
name: "School",
data(){
console.log(this);
return {
name: 'wust university',
address: '武汉科技大学'
}
},
}
</script> <style scoped>
/*scoped代表局部的*/
.demo{
background: skyblue;
}
</style>

总结TodoList案例

  1. 组件化编码流程:

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

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

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

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

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

  2. props适用于:

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

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

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

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

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"
:clearAllDoneTodo="clearAllDoneTodo"
/>
</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,
List,
MyFooter,
},
data() {
return {
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) => {
return todo.id !== id;
});
},
// 全选or取消全选
checkAllTodo(done) {
this.todos.forEach((todo) => (todo.done = done));
},
// 清除所有已经完成的todozz
clearAllDoneTodo() {
this.todos = this.todos.filter((todo) => {
return !todo.done;
});
},
},
};
</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.addTodo(todoObj);
// 清空输入
this.title = "";
},
},
props: ["addTodo"],
};
</script>

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"], //在vc身上,直接用
};
</script>

Item.vue

<template>
<li>
<label>
<!-- 这里勾选和取消勾选可以使用change和click作为事件处理-->
<input
type="checkbox"
:checked="todo.done"
@change="handleCheck(todo.id)"
/>
<!--v-model数据的双向绑定,checkbox使用v-model来双向绑定其是否被勾选,也可以实现效果但不推荐(因为其实修改了props中的数据)-->
<!--这里修改了从List修改过来的props,这里的不允许改是浅层次,就是如果props是一个对象则这个修改这个对象的某一个属性vue是放行的-->
<!-- <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",
methods: {
// 勾选or取消勾选
handleCheck(id) {
// 通过App组件将对应的todo对象的done值去反
this.checkTodo(id);
},
// 删除
handleDelete(id) {
if (confirm("确定删除吗?")) {
this.deleteTodo(id);
}
},
},
// 声明接受todo对象
props: ["todo", "checkTodo", "deleteTodo"],
};
</script>

MyFooter.vue

<template>
<!--隐式类型转换-->
<div class="todo-footer">
<label>
<!--这里也可用v-model来替代,此时不需要计算属性了-->
<!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> -->
<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", "clearAllDoneTodo"],
computed: {
total() {
return this.todos.length;
},
doneTotal() {
// const x = this.todos.reduce((pre, current) => {
// console.log("@", pre, current);
// return pre + (current.done ? 1 : 0);
// }, 0);
// return x;
// 简写
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
},
isAll: {
get() {
return this.doneTotal === this.total && this.total > 0;
},
set(value) {
this.checkAllTodo(value);
},
},
},
methods: {
// checkAll(e) {
// this.checkAllTodo(e.target.checked);
// },
clearAll() {
this.clearAllDoneTodo();
},
},
};
</script>

2.3 浏览器本地存储

Cookie

Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。

Cookie的特性:

  • Cookie一旦创建成功,名称就无法修改

  • Cookie是无法跨域名的,也就是说a域名和b域名下的cookie是无法共享的,这也是由Cookie的隐私安全性决定的,这样就能够阻止非法获取其他网站的Cookie

  • 每个域名下Cookie的数量不能超过20个,每个Cookie的大小不能超过4kb

  • 有安全问题,如果Cookie被拦截了,那就可获得session的所有信息,即使加密也于事无补,无需知道cookie的意义,只要转发cookie就能达到目的

  • Cookie在请求一个新的页面的时候都会被发送过去

Cookie 在身份认证中的作用

客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动 将 Cookie 保存在浏览器中。

随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份。

Cookie 不具有安全性

由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全 性。因此不建议服务器将重要的隐私数据,通过 Cookie 的形式发送给浏览器。

注意:千万不要使用 Cookie 存储重要且隐私的数据!比如用户的身份信息、密码等。

Session

Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了session是一种特殊的cookie。cookie是保存在客户端的,而session是保存在服务端。

为什么要用session 由于cookie 是存在用户端,而且它本身存储的尺寸大小也有限,最关键是用户可以是可见的,并可以随意的修改,很不安全。那如何又要安全,又可以方便的全局读取信息呢?于是,这个时候,一种新的存储会话机制:session 诞生了

session原理 当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。

图示:

session我觉得可以简单理解为一个表,根据cookie传来的值查询表中的内容

session 标准工作流程

我在 node.js 中详细演示了一遍 session 的使用,具体看了另一篇博客:https://blog.csdn.net/hangao233/article/details/123089029

LocalStorage

LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了。

LocalStorage的优点:

  • 在大小方面,LocalStorage的大小一般为5MB,可以储存更多的信息

  • LocalStorage是持久储存,并不会随着页面的关闭而消失,除非主动清理,不然会永久存在

  • 仅储存在本地,不像Cookie那样每次HTTP请求都会被携带

LocalStorage的缺点:

  • 存在浏览器兼容问题,IE8以下版本的浏览器不支持

  • 如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage

  • LocalStorage受到同源策略的限制,即端口、协议、主机地址有任何一个不相同,都不会访问

LocalStorage的常用API:

// 保存数据到 localStorage
localStorage.setItem('key', 'value'); // 从 localStorage 获取数据
let data = localStorage.getItem('key'); // 从 localStorage 删除保存的数据
localStorage.removeItem('key'); // 从 localStorage 删除所有保存的数据
localStorage.clear(); // 获取某个索引的Key
localStorage.key(index)

LocalStorage的使用场景:

  • 有些网站有换肤的功能,这时候就可以将换肤的信息存储在本地的LocalStorage中,当需要换肤的时候,直接操作LocalStorage即可

  • 在网站中的用户浏览信息也会存储在LocalStorage中,还有网站的一些不常变动的个人信息等也可以存储在本地的LocalStorage中

SessionStorage

SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。

SessionStorage与LocalStorage对比:

  • SessionStorage和LocalStorage都在本地进行数据存储

  • SessionStorage也有同源策略的限制,但是SessionStorage有一条更加严格的限制,SessionStorage只有在同一浏览器的同一窗口下才能够共享

  • LocalStorage和SessionStorage都不能被爬虫爬取

SessionStorage的常用API:

// 保存数据到 sessionStorage
sessionStorage.setItem('key', 'value'); // 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key'); // 从 sessionStorage 删除保存的数据
sessionStorage.removeItem('key'); // 从 sessionStorage 删除所有保存的数据
sessionStorage.clear(); // 获取某个索引的Key
sessionStorage.key(index)

SessionStorage的使用场景

由于SessionStorage具有时效性,所以可以用来存储一些网站的游客登录的信息,还有临时的浏览记录的信息。当关闭网站之后,这些信息也就随之消除了。

具体案例:

localStorage

<!DOCTYPE html>
<html lang="en">
<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="removeAllData()">清空所有数据</button>
</body>
<script type="text/javascript">
function saveData(){
localStorage.setItem('msg1', 'panyue,hello');
//对象转换为字符串
localStorage.setItem('msg2',JSON.stringify({
name: 'panyue',
age: '21',
school: 'wust(WuHan)'
}))
} function readData(){
console.log(localStorage.getItem('msg1'))
console.log(JSON.parse(localStorage.getItem('msg2')));
console.log(localStorage.getItem('text')); //读不出来就是null Json.parse(null)仍然是null注意
} function deleteData(){
localStorage.removeItem('msg2');
} function removeAllData(){
localStorage.clear();
}
</script>
</html>

sessionStorage

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>sessionStorage</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="removeAllData()">清空所有数据</button>
</body>
<script type="text/javascript">
function saveData(){
sessionStorage.setItem('msg1', 'panyue,hello');
//对象转换为字符串
sessionStorage.setItem('msg2',JSON.stringify({
name: 'panyue',
age: '21',
school: 'wust(WuHan)'
}))
} function readData(){
console.log(sessionStorage.getItem('msg1'))
console.log(JSON.parse(sessionStorage.getItem('msg2')));
console.log(sessionStorage.getItem('text')); //读不出来就是null Json.parse(null)仍然是null注意
} function deleteData(){
sessionStorage.removeItem('msg2');
} function removeAllData(){
sessionStorage.clear();
}
</script>
</html>

TodoList本地存储案例

data() {
return {
todos: JSON.parse(localStorage.getItem("todos")) || [],
};
}, // 监视todos
 watch: {
    // 浅监视,只监视了数组的变化,没有监视数组内部的变化
    // todos(value) {
    //   localStorage.setItem("todos", JSON.stringify(value));
    // },
    // 深度监视
    todos: {
      deep: true,
      handler(value) {
        localStorage.setItem("todos", JSON.stringify(value));
      },
    },
  },
 

2.4 组件自定义事件

组件自定义事件是一种组件间通信的方式,适用于:子组件 ===> 父组件

使用场景

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

绑定自定义事件:

第一种方式,在父组件中:<Demo @atguigu="test"/><Demo v-on:atguigu="test"/>

具体代码

App.vue

<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<Student @atguigu="getStudentName"/>
</div>
</template> <script>
import Student from './components/Student' export default {
name:'App',
components:{Student},
data() {
return {
msg:'你好啊!',
studentName:''
}
},
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
}
}
}
</script> <style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>

Student.vue

<template>
<div class="student">
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template> <script>
export default {
name:'Student',
data() {
return {
name:'张三',
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
}
},
}
</script> <style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>

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

使用 this.$refs.xxx.$on() 这样写起来更灵活,比如可以加定时器啥的。

具体代码

App.vue

<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student"/>
</div>
</template> <script>
import Student from './components/Student' export default {
name:'App',
components:{Student},
data() {
return {
studentName:''
}
},
methods: {
getStudentName(name,...params){
console.log('App收到了学生名:',name,params)
this.studentName = name
},
},
mounted() {
this.$refs.student.$on('atguigu',this.getStudentName) //绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
},
}
</script> <style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>

Student.vue

<template>
<div class="student">
<button @click="sendStudentlName">把学生名给App</button>
</div>
</template> <script>
export default {
name:'Student',
data() {
return {
name:'张三',
}
},
methods: {
sendStudentlName(){
//触发Student组件实例身上的atguigu事件
this.$emit('atguigu',this.name,666,888,900)
}
},
}
</script> <style lang="less" scoped>
.student{
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>

若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

触发自定义事件:this.$emit('atguigu',数据)

使用 this.$emit() 就可以子组件向父组件传数据

解绑自定义事件this.$off('atguigu')

代码

this.$off('atguigu') //解绑一个自定义事件
// this.$off(['atguigu','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件

组件上也可以绑定原生DOM事件,需要使用native修饰符。

代码

<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" @click.native="show"/>

注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

代码示例

App.vue

<template>
<div class="app">
<h1>{{ msg }},学生姓名是:{{ studentName }}</h1>
<!--通过绑定一个自定义事件实现了子给父传递数据(自定义事件绑在子组件上) 第一种写法使用@或v-on-->
<!--once代表改事件只执行一次-->
<!-- <Student @personalEvent="getStudentName" @demo="demo"/>-->
<!--第二种写法使用ref绑定事件--->
<Student ref="student" @click.native="show" />
<!--通过父组件给子组件传递函数类型的props实现了子给父传递数据-->
<School :getSchoolName="getSchoolName" />
</div>
</template> <script>
import Student from "@/components/Student";
import School from "@/components/School";
export default {
name: "App",
components: {
School,
Student,
},
data() {
return {
msg: "helloこんにちは",
studentName: "",
};
},
methods: {
getSchoolName(name) {
console.log(`app收到了学校名,${name}`);
},
getStudentName(name, ...params) {
console.log("自定义");
console.log(`app收到了学生名, ${name}`);
this.studentName = name;
console.log(`剩余参数,${params}`);
},
demo() {
console.log("demo事件被触发了");
},
show() {
console.log(`123`);
},
},
mounted() {
//可以通过ref拿到组件实例对象
// setTimeout(() => {
// this.$refs.student.$on('personalEvent', this.getStudentName); //当App组件一挂载完毕经过三秒我就在Student组件上绑定事件
// //this.$refs.student.$once('personalEvent', this.getStudentName); //注意此时事件只执行一次就不执行了(once),一次性
// },3000)
//注意这里回调要写成剪头函数,不然this会丢失,这个this就是(vc)app,而不是(vc)student
this.$refs.student.$on("personalEvent", (name) => {
console.log(this);
console.log(name);
this.studentName = name;
});
},
};
</script> <style>
/*
全局的样式是不需要加scoped
全局共享
*/
.app {
background: gray;
padding: 5px;
}
</style>

School.vue

<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址: {{ address }}</h2>
<button @click="sendSchoolName">把学校名传递给app</button>
</div>
</template> <script>
export default {
name: "School",
data() {
console.log(this);
return {
name: "wust university",
address: "武汉科技大学",
};
},
methods: {
sendSchoolName() {
this.getSchoolName(this.name);
},
},
props: ["getSchoolName"],
};
</script> <style scoped>
/*scoped代表局部的*/
.demo {
background: skyblue;
padding: 5px;
}
</style>

Student.vue

<template>
<div class="student">
<h2>姓名:{{ name }}</h2>
<h2>性别: {{ sex }}</h2>
<h2>当前求和为:{{ number }}</h2>
<button @click="add">点我加一</button>
<button @click="sendStudentName">把学生名传递给app</button>
<button @click="unbind">解绑自定义(personalEvent)事件</button>
<button @click="death">销毁当前student组件的实例对象</button>
</div>
</template> <script>
export default {
name: "Student",
data() {
console.log(this);
return {
name: "张三",
sex: "男",
number: 0,
};
},
methods: {
add() {
console.log(`add回调被调用了`);
this.number++;
},
sendStudentName() {
//emit触发绑定在指定vc上的自定义事件 vc实例对象可以使用该方法
//后面多余参数演示es6的参数收集
this.$emit("personalEvent", this.name, 666, 777, 888);
// this.$emit('demo'); //同时触发两个事件
// this.$emit('click'); 如果在组件身上使用原生事件不加native修饰符则会让vue认为你这是自定义事件
},
unbind() {
//解绑事件
this.$off("personalEvent"); //这种写法只能解绑一种自定义事件
//this.$off([ 'personalEvent', 'demo' ]);// 解绑多个事件,参数为包含多个事件名的数组
// this.$off(); //比较暴力,有几个自定义事件就全给你解绑了
},
death() {
this.$destroy(); //销毁当前组件实例,销毁后所有该实例的自定义事件都不奏效了
},
},
};
</script> <style scoped>
.student {
background: orange;
padding: 5px;
margin-bottom: 10px;
}
</style>

TodoList自定义版本

App.vue

<MyHeader @addTodo="addTodo" />
<MyFooter
:todos="todos"
@checkAllTodo="checkAllTodo"
@clearAllDoneTodo="clearAllDoneTodo"
/>

Student.vue

// 组件自定义事件,把数据传给父组件
this.$emit("addTodo", todoObj);

2.5 全局事件总线

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

  2. 安装全局事件总线:

new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})

  3.使用事件总线:

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

methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}

           2.提供数据:this.$bus.$emit('xxxx',数据)

  4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。

示例代码(学生组件将数据传给学校组件)

main.js

//引入Vue
import Vue from "vue";
//引入App
import App from './App'; //关闭Vue的生产提示
Vue.config.productionTip = false; // Vue.prototype.x = { a: 1, b: 2 } // 创建一个vc(第一种方法)
// const Demo = Vue.extend({});
// const d = new Demo() // 此时这个d就是组件实例对象==》vc
// Vue.prototype.x = d; new Vue({
el: '#app',
render: h => h(App),
// 第二种方法
beforeCreate() {
//此时这个this就是vm,只不过这个时候还并没有去解析模版
Vue.prototype.$bus = this; // 安装全局事件总线
}
});

School.vue

<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址: {{ address }}</h2>
</div>
</template> <script>
export default {
name: "School",
data() {
return {
name: "wust university",
address: "武汉科技大学",
};
},
mounted() {
// console.log("School组件", this);
this.$bus.$on("hello", (data) => {
console.log("我是School组件,收到数据", data);
});
},
// 实例销毁前,解绑hello事件(自定义组件中为解绑,因为组件销毁了,实例也会销毁,事件绑定也会解绑)
beforeDestroy() {
this.$bus.$off("hello");
},
};
</script> <style scoped>
/*scoped代表局部的*/
.demo {
background: skyblue;
padding: 5px;
}
</style>

Student.vue

<template>
<div class="student">
<h2>姓名:{{ name }}</h2>
<h2>性别: {{ sex }}</h2>
<button @click="sendStudentName">把学生名传递给School组件</button>
</div>
</template> <script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
mounted() {
// console.log("Student组件", this.x);
},
methods: {
sendStudentName() {
this.$bus.$emit("hello", 666);
},
},
};
</script> <style scoped>
.student {
background: orange;
padding: 5px;
margin-bottom: 10px;
}
</style>

TodoList事件总线版本

Item.vue

  methods: {
// 勾选or取消勾选
handleCheck(id) {
// 通过App组件将对应的todo对象的done值去反
this.$bus.$emit("checkTodo", id);
},
// 删除
handleDelete(id) {
if (confirm("确定删除吗?")) {
this.$bus.$emit("deleteTodo", id);
}
},
},

App.vue

mounted() {
this.$bus.$on("checkTodo", this.checkTodo);
this.$bus.$on("deleteTodo", this.deleteTodo);
},
beforeDestroy() {
this.$bus.$off("checkTodo");
this.$bus.$off("deleteTodo");
},

2.6 消息订阅与发布

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

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

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

      methods:{
      demo(data){......}
      }
      ......
      mounted() {
      this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
       
    4. 提供数据:pubsub.publish('xxx',数据)

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

示例代码

订阅消息

School.vue

<template>
<div class="demo">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址: {{ address }}</h2>
</div>
</template> <script>
import pubsub from "pubsub-js";
export default {
name: "School",
data() {
return {
name: "wust university",
address: "武汉科技大学",
};
},
mounted() {
// console.log("School组件", this);
// this.$bus.$on("hello", (data) => {
// console.log("我是School组件,收到数据", data);
// });
this.pubId = pubsub.subscribe("hello", function (msgName, data) {
console.log(this); // undefined,跟自定义事件中的this一样,需要写成箭头函数,this是vc
console.log("有人发布了hello消息,hello消息的回调执行了", msgName, data);
});
},
// 实例销毁前,解绑hello事件(自定义组件中为解绑,因为组件销毁了,实例也会销毁,事件绑定也会解绑)
beforeDestroy() {
// this.$bus.$off("hello");
// 取消订阅
pubsub.unsubscribe(this.pubId);
},
};
</script> <style scoped>
/*scoped代表局部的*/
.demo {
background: skyblue;
padding: 5px;
}
</style>

发布消息

Student.vue

<template>
<div class="student">
<h2>姓名:{{ name }}</h2>
<h2>性别: {{ sex }}</h2>
<button @click="sendStudentName">把学生名传递给School组件</button>
</div>
</template> <script>
import pubsub from "pubsub-js";
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
mounted() {
// console.log("Student组件", this.x);
},
methods: {
sendStudentName() {
// this.$bus.$emit("hello", 666);
pubsub.publish("hello", 666);
},
},
};
</script> <style scoped>
.student {
background: orange;
padding: 5px;
margin-bottom: 10px;
}
</style>

2.7 nextTick

  1. 语法:this.$nextTick(回调函数)

  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。

  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

具体案例

    // 编辑
handleEdit(todo) {
// todo是否有isEdit属性
if (Object.prototype.hasOwnProperty.call(todo, "isEdit")) {
todo.isEdit = true;
} else {
// 给todo添加响应式的属性
this.$set(todo, "isEdit", true);
}
// 更新完数据后,新的dom获取焦点
this.$nextTick(function () {
this.$refs.inputTitle.focus();
});
},

TodoList订阅与发布及nextTick版本

完整版

main.js

//引入Vue
import Vue from "vue";
//引入App
import App from './App'; //关闭Vue的生产提示
Vue.config.productionTip = false; new Vue({
el: '#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
});

App.vue

<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader @addTodo="addTodo" />
<List :todos="todos" />
<MyFooter
:todos="todos"
@checkAllTodo="checkAllTodo"
@clearAllDoneTodo="clearAllDoneTodo"
/>
</div>
</div>
</div>
</template> <script>
import pubsub from "pubsub-js";
import MyHeader from "@/components/MyHeader.vue";
import List from "@/components/List.vue";
import MyFooter from "@/components/MyFooter.vue";
import PubSub from "pubsub-js"; export default {
name: "App",
components: {
MyHeader,
List,
MyFooter,
},
data() {
return {
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
updateTodo(id, title) {
this.todos.forEach((todo) => {
if (todo.id === id) todo.title = title;
});
},
// 删除一个todo
deleteTodo(_, id) {
this.todos = this.todos.filter((todo) => {
return todo.id !== id;
});
},
// 全选or取消全选
checkAllTodo(done) {
this.todos.forEach((todo) => (todo.done = done));
},
// 清除所有已经完成的todozz
clearAllDoneTodo() {
this.todos = this.todos.filter((todo) => {
return !todo.done;
});
},
},
// 监视todos
watch: {
// 浅监视,只监视了数组的变化,没有监视数组内部的变化
// todos(value) {
// localStorage.setItem("todos", JSON.stringify(value));
// },
// 深度监视
todos: {
deep: true,
handler(value) {
localStorage.setItem("todos", JSON.stringify(value));
},
},
},
mounted() {
this.$bus.$on("checkTodo", this.checkTodo);
this.$bus.$on("updateTodo", this.updateTodo);
// this.$bus.$on("deleteTodo", this.deleteTodo);
this.pubId = pubsub.subscribe("deleteTodo", this.deleteTodo);
},
beforeDestroy() {
this.$bus.$off("checkTodo");
this.$bus.$off("updateTodo", this.updateTodo);
// this.$bus.$off("deleteTodo");
pubsub.unsubscribe(this.pubId);
},
};
</script> <style lang="css">
/*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-eidt {
color: #fff;
background-color: skyblue;
border: 1px solid rgb(103, 159, 180);
margin-right: 5px;
} .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>

Item.vue

<template>
<li>
<label>
<!-- 这里勾选和取消勾选可以使用change和click作为事件处理-->
<input
type="checkbox"
:checked="todo.done"
@change="handleCheck(todo.id)"
/>
<!--v-model数据的双向绑定,checkbox使用v-model来双向绑定其是否被勾选,也可以实现效果但不推荐(因为其实修改了props中的数据)-->
<!--这里修改了从List修改过来的props,这里的不允许改是浅层次,就是如果props是一个对象则这个修改这个对象的某一个属性vue是放行的-->
<!-- <input type="checkbox" v-model="todo.done"/> -->
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<input
type="text"
v-show="todo.isEdit"
:value="todo.title"
@blur="handleBlur(todo, $event)"
ref="inputTitle"
/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<button
v-show="!todo.isEdit"
class="btn btn-eidt"
@click="handleEdit(todo)"
>
编辑
</button>
</li>
</template> <script>
import pubsub from "pubsub-js";
export default {
name: "Item",
methods: {
// 勾选or取消勾选
handleCheck(id) {
// 通过App组件将对应的todo对象的done值去反
this.$bus.$emit("checkTodo", id);
},
// 删除
handleDelete(id) {
if (confirm("确定删除吗?")) {
// this.$bus.$emit("deleteTodo", id);
pubsub.publish("deleteTodo", id);
}
},
// 编辑
handleEdit(todo) {
// todo是否有isEdit属性
if (Object.prototype.hasOwnProperty.call(todo, "isEdit")) {
todo.isEdit = true;
} else {
// 给todo添加响应式的属性
this.$set(todo, "isEdit", true);
}
// 更新完数据后,新的dom获取焦点
this.$nextTick(function () {
this.$refs.inputTitle.focus();
});
},
// 失去焦点回调(真正执行修改逻辑)
handleBlur(todo, e) {
// 已经有了isEdit属性,不需再用添加响应式属性的方法了
todo.isEdit = false;
if (!e.target.value.trim()) return alert("输入不能为空");
this.$bus.$emit("updateTodo", todo.id, e.target.value);
},
},
// 声明接受todo对象
props: ["todo"],
};
</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: #ddd;
} li:hover button {
display: block;
}
</style>

List.vue

<template>
<ul class="todo-main">
<Item v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" />
</ul>
</template> <script>
import Item from "./Item"; export default {
name: "List",
components: {
Item,
}, props: ["todos"], //在vc身上,直接用
};
</script> <style scoped>
/*main*/
.todo-main {
margin-left: 0;
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>

MyFooter.vue

<template>
<!--隐式类型转换-->
<div class="todo-footer">
<label>
<!--这里也可用v-model来替代,此时不需要计算属性了-->
<!-- <input type="checkbox" :checked="isAll" @change="checkAll" /> -->
<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"],
computed: {
total() {
return this.todos.length;
},
doneTotal() {
// const x = this.todos.reduce((pre, current) => {
// console.log("@", pre, current);
// return pre + (current.done ? 1 : 0);
// }, 0);
// return x;
// 简写
return this.todos.reduce((pre, todo) => pre + (todo.done ? 1 : 0), 0);
},
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() {
this.$emit("clearAllDoneTodo");
},
},
};
</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>

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 };
// 组件自定义事件,把数据传给父组件
this.$emit("addTodo", todoObj);
// 清空输入
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>
  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 图示:

  1. 写法:

    1. 准备好样式:

      • 元素进入的样式:

        1. v-enter:进入的起点

        2. v-enter-active:进入过程中

        3. v-enter-to:进入的终点

      • 元素离开的样式:

        1. v-leave:离开的起点

        2. v-leave-active:离开过程中

        3. v-leave-to:离开的终点

    2. 使用<transition>包裹要过渡的元素,并配置name属性:

      <transition name="hello">
      <h1 v-show="isShow">你好啊!</h1>
      </transition>
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。

具体案例(单个元素过渡)

<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template> <script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script> <style scoped>
h1{
background-color: orange;
} .v-enter-active{
animation: move 0.5s linear;
} .v-leave-active{
animation: move 0.5s linear reverse;
} @keyframes move {
from{
transform: translateX(-100%);
}
to{
transform: translateX(0px);
}
}
</style>

name 的作用可以让让不同的元素有不同的动画效果

<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template> <script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script> <style scoped>
h1{
background-color: orange;
} .hello-enter-active{
animation: move 0.5s linear;
} .hello-leave-active{
animation: move 0.5s linear reverse;
} @keyframes move {
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>

使用第三库的具体案例(随便看看,这个不重要)

库的名称:Animate.css

安装:npm i animate.css

引入:import 'animate.css'

<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<!--transition 只能包裹一个元素而transition-group可以包含多个元素-->
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
>
<h1 v-show="isShow" key="1">你好!</h1>
<h1 v-show="isShow" key="2">Shanghai</h1>
</transition-group>
</div>
</template> <script>
import "animate.css";
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script> <style scoped>
h1 {
background-color: orange;
}
</style>

TodoList动画版本

<transition name="todo" appear>
<li>
<label>
<!-- 这里勾选和取消勾选可以使用change和click作为事件处理-->
<input
type="checkbox"
:checked="todo.done"
@change="handleCheck(todo.id)"
/>
<!--v-model数据的双向绑定,checkbox使用v-model来双向绑定其是否被勾选,也可以实现效果但不推荐(因为其实修改了props中的数据)-->
<!--这里修改了从List修改过来的props,这里的不允许改是浅层次,就是如果props是一个对象则这个修改这个对象的某一个属性vue是放行的-->
<!-- <input type="checkbox" v-model="todo.done"/> -->
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<input
type="text"
v-show="todo.isEdit"
:value="todo.title"
@blur="handleBlur(todo, $event)"
ref="inputTitle"
/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">
删除
</button>
<button
v-show="!todo.isEdit"
class="btn btn-eidt"
@click="handleEdit(todo)"
>
编辑
</button>
</li>
</transition> .todo-enter-active {
animation: atguigu 1s;
} .todo-leave-active {
animation: atguigu 1s reverse;
} @keyframes atguigu {
from {
transform: translateX(100%);
}
to {
transform: translateX(0px);
}
}

2.9 vue脚手架配置代理

解决跨域问题的方法

方法1:后端的cors方法,后端加字头;

方法2:jsonp,用的很少;

方法3:代理服务器。

代理服务器的方法,一种方法是nigx(反向代理),另一种方法是vue-cli生成代理服务器。

可以用来解决跨域的问题

方法一

在vue.config.js中添加如下配置:

// 开启代理服务器
devServer:{
proxy:"http://localhost:5000"
}

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。

  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。

  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

方法二

编写vue.config.js配置具体代理规则:

module.exports = {
devServer: {
proxy: {
'/api1': {// 匹配所有以 '/api1'开头的请求路径
target: 'http://localhost:5000',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api1': ''}//代理服务器将请求地址转给真实服务器时会将 /api1 去掉
},
'/api2': {// 匹配所有以 '/api2'开头的请求路径
target: 'http://localhost:5001',// 代理目标的基础路径
changeOrigin: true,
pathRewrite: {'^/api2': ''}
}
}
}
}
/*
changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
changeOrigin默认值为true
*/

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。

  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

案例

<template>
<div>
<button @click="getStudents">获取学生信息</button>
<button @click="getCars">获取汽车信息</button>
</div>
</template> <script>
import axios from 'axios';
export default {
name: "App",
methods:{
getStudents(){
axios.get('/api/students') //其实这里就是请求http://localhost:8080/students只不过把请求转移给了端口5001
.then(res => console.log(res.data))
.catch(e => console.log(`msg: ${e.message}, stack: ${e.stack}`));
},
getCars(){
axios.get('/demo/cars') //同理
.then(res => console.log(res.data))
.catch(e => console.log(`msg: ${e.message}, stack: ${e.stack}`));
}
}
}
</script>

2.10 github搜索案例(vue-resource版本)

App.vue

<template>
<div class="container">
<Search/>
<List/>
</div>
</template> <script>
import Search from "@/components/Search";
import List from "@/components/List";
export default {
name: "App",
components:{
Search,
List
}
}
</script>
<style lang="css">
</style>

List.vue

<template>
<div class="row">
<!--展示用户列表-->
<div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login">
<a :href="user.html_url" target="_blank">
<img :src="user.avatar_url" style='width: 100px'/>
</a>
<p class="card-text">{{ user.login }}</p>
</div>
<!---欢迎词-->
<h1 v-show="info.isFirst">Welcome!</h1>
<!--加载中--->
<h1 v-show="info.isLoading">Loading...</h1>
<!---错误信息-->
<h1 v-show="info.errMsg">Something has been wrong, errorMessage: {{ info.errMsg }}</h1>
</div>
</template> <script>
export default {
name: "List",
data(){
return {
info : {
isFirst: true, //是否为第一次使用
users:[],
isLoading: false, //是否在加载中
errMsg: '',
}
}
},
mounted() {
this.$bus.$on('updateListData', (dataObj) => {
// console.log(`我是list,接到了数据data:`, users);
// this.isFirst = isFirst;
// this.isLoading = isLoading;
// this.errMsg = errMsg;
// this.users = users;
// 两个对象进行比较,有的保留
this.info = { ...this.info, ...dataObj };
});
} }
</script> <style scoped>
.album {
min-height: 50rem; /* Can be removed; just added for demo purposes */
padding-top: 3rem;
padding-bottom: 3rem;
background-color: #f7f7f7;
} .card {
float: left;
width: 33.333%;
padding: .75rem;
margin-bottom: 2rem;
border: 1px solid #efefef;
text-align: center;
} .card > img {
margin-bottom: .75rem;
border-radius: 100px;
} .card-text {
font-size: 85%;
}
</style>

Search.vue

<template>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input type="text" placeholder="enter the name you search" v-model="keyword"/>&nbsp;
<button @click="searchUsers">Search</button>
</div>
</section>
</template> <script>
export default {
name: "Search",
data() {
return {
keyword: '',
}
},
methods:{
//使用全局事件总线在组件间传递数据
searchUsers(){
this.$bus.$emit('updateListData', {
isFirst: false,
isLoading: true,
errMsg: '',
users: []
})
// get请求携带数据
this.$http.get(`https://api.github.com/search/users?q=${this.keyword}`)
.then(res => {
console.log(res.data.items);
this.$bus.$emit("updateListData", {
isLoading: false,
errMsg: '',
users: res.data.items
});
})
.catch(e => {
console.log(`请求失败:${e.message}`)
this.$bus.$emit("updateListData", {
isLoading: false,
errMsg: e.message,
users: []
});
});
}
}
}
</script> <style scoped> </style>

2.11 slot插槽

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件

  2. 分类:默认插槽、具名插槽、作用域插槽

  3. 使用方式:

默认插槽:

父组件中:
<Category>
<div>html结构1</div>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot>插槽默认内容...</slot>
</div>
</template>

具名插槽:

父组件中:
<Category>
<template slot="center">
<div>html结构1</div>
</template> <template v-slot:footer>
<div>html结构2</div>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">插槽默认内容...</slot>
<slot name="footer">插槽默认内容...</slot>
</div>
</template>

作用域插槽:

  1. 理解:数据在组件的自身(子组件),但根据数据生成的结构需要组件的使用者(父组件)来决定。(games数据在Category(子)组件中,但使用数据所遍历出来的结构由App(父)组件决定)

  2. 具体编码:

父组件中:
<Category>
<template scope="scopeData">
<!-- 生成的是ul列表 -->
<ul>
<li v-for="g in scopeData.games" :key="g">{{g}}</li>
</ul>
</template>
</Category> <Category>
<template slot-scope="scopeData">
<!-- 生成的是h4标题 -->
<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
</template>
</Category>
子组件中:
<template>
<div>
<!-- 通过数据绑定就可以把子组件的数据传到父组件 -->
<slot :games="games"></slot>
</div>
</template> <script>
export default {
name:'Category',
props:['title'],
//数据在子组件自身
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
}
</script>

vue2(脚手架、组件)的更多相关文章

  1. 通信vue2.0组件

    vue2.0组件通信各种情况总结与实例分析   Props在vue组件中各种角色总结 在Vue中组件是实现模块化开发的主要内容,而组件的通信更是vue数据驱动的灵魂,现就四种主要情况总结如下: 使用p ...

  2. Vue2.0+组件库总结

    转自:https://blog.csdn.net/lishanleilixin/article/details/84025459 UI组件 element - 饿了么出品的Vue2的web UI工具套 ...

  3. 转:Vue2.0+组件库总结

    UI组件 element - 饿了么出品的Vue2的web UI工具套件 Vux - 基于Vue和WeUI的组件库 mint-ui - Vue 2的移动UI元素 iview - 基于 Vuejs 的开 ...

  4. vue2.0组件传值

    props down   emit up 嘿嘿    如果是第一次接触vue2.0组件传值的肯定很疑惑,这是什么意思(大神总结的,我也就是拿来用用) “down”—>指的是下的意思,即父组件向子 ...

  5. vue2.0组件库

    UI组件 element - 饿了么出品的Vue2的web UI工具套件 Vux - 基于Vue和WeUI的组件库 mint-ui - Vue 2的移动UI元素 iview - 基于 Vuejs 的开 ...

  6. 【干货】Vue2.x 组件通信方式详解,这篇讲全了

    前言 vue是数据驱动视图更新的框架, 我们平时开发,都会把页面不同模块拆分成一个一个vue组件, 所以对于vue来说组件间的数据通信非常重要,那么组件之间如何进行数据通信的呢? 首先我们需要知道在v ...

  7. Vue2.0组件间数据传递

    Vue1.0组件间传递 使用$on()监听事件: 使用$emit()在它上面触发事件: 使用$dispatch()派发事件,事件沿着父链冒泡: 使用$broadcast()广播事件,事件向下传导给所有 ...

  8. vue2 递归组件--树形

    递归组件,官方给的教程太简便了,根本看不出到底怎么用.于是自己查网摸索了一下,这儿只把核心思想写出来. 效果如下图,点击后打开二级菜单,再点击后打开三级. //js //引子//思想:当v-if='f ...

  9. Vue2.0组件之间通信(转载)

    Vue中组件这个特性让不少前端er非常喜欢,我自己也是其中之一,它让前端的组件式开发更加合理和简单.笔者之前有写过一篇Vue2.0子父组件通信,这次我们就来聊一聊平级组件之间的通信. 首先我们先搭好开 ...

  10. vue2+element组件库开发

    Vue2:https://cn.vuejs.org/v2/guide/single-file-components.html element组件库:http://element-cn.eleme.io ...

随机推荐

  1. MySQL中 int(11)和int(10)有没有区别!!

    结论:int(11)  int(3)  int(20)  若不勾选填充0,那么效果统统一样,若勾选了填充0:查询出来的效果 会是这样: int(11)  00000000123 int(3)  123 ...

  2. Delphi库单元结构

    单元(unit)是组成Pascal 程序的单独的源代码模块,单元由函数和过程组成,这些函数和过程能被主程序调用. 一个标准的单元文件格式如下: unit Unit1: //单元头 interface ...

  3. [Ngbatis源码学习][SpringBoot] 由BeanFactoryPostProcessor想到

    由BeanFactoryPostProcessor想到 在看Ngbatis源码时看到了对BeanFactoryPostProcessor后置处理器的使用,对其的使用并不是很了解,在此做一些学习和总结. ...

  4. python使用pandas库读写excel文件

    操作系统 : Windows 10_x64 Python 版本 : 3.9.2_x64 平时工作中会遇到Excel数据处理的问题,这里简单介绍下怎么使用python的pandas库读写Excel文件. ...

  5. EGF:指数型生成函数

    对于一个数列 \(<a_n>\),定义其指数型生成函数(EGF)\(\hat{a}(x)=\displaystyle\sum_{n\ge 0}\dfrac{a_n}{n!}x^n\). 例 ...

  6. Java中“==”与equals()

    1 前言 1.1 内存分区 Java中有6种存储区域(参考Java的六大存储区域),如下: 寄存器(register):位于处理器内部,处理速度最快,空间比较珍贵: 栈(stack):位于通用RAM中 ...

  7. maven打包更改版本号

    引入依赖 <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>versions-mave ...

  8. Spring Boot学生信息管理系统项目实战-2.字典管理和模板管理

    1.获取源码 源码是捐赠方式获取,详细请QQ联系我 :) 2.实现效果 3.项目源码 只挑重点讲,详细请看源码. 3.1 字典管理 字典管理这里分为字典的编码和名称和字典数据的增删改查. 前端页面: ...

  9. 《深入理解Java虚拟机》(六) 调优策略 -- 笔记

    目录 一.操作文档类功能,大量大对象直接进入老年代 问题现象 解决方法 通过单一Java虚拟机管理大量的内存 同一台服务器上部署若干Java虚拟机 二.异步请求大量累积 三.排查问题 排查问题: 可能 ...

  10. 用Taro写一个微信小程序——引入外部字体

    小程序直接用.ttf字体文件,在开发工具看没有问题,但是打包编译会提示这个字体文件打包上传不成功. 一.字体文件转换为css 1.打开https://transfonter.org/ 注意选择Base ...