vue的生命周期:
借鉴了react
钩子函数:
change() 挂载完毕,vue完成模板解析,并把初始的真实的dom元素放入到页面后执行
beforeCreate() {
// 数据代理和数据监测创建之前
console.log('beforeCreate')
},
created() {
console.log('created')
},
beforeMount() {
console.log('beforeMount')
},
mounted() {
console.log('mounted')
},
beforeUpdate() {
console.log('beforeUpdate')
},
updated() {
console.log('updated')
},
beforeDestroy() {
console.log('beforeDestroy')
},
destroyed() {
console.log('destroyed')
}
编辑
组件化:
单页面项目
传统的写法:
编辑
组件方式:
编辑
模块化:
一个大的js文件拆分成很多个文件
1、创建组件(非单文件组件):
// 创建组件
const school = Vue.extend({
// 不用写el配置项,因为组件由vm管理
template: `
<div>
<div>{{ school.name }}</div>
<div>{{ school.address }}</div>
</div>
`,
data() {
return {
student: {
name: 'tom',
age: '18'
}
}
}
})
超级简写:
const school = {
template: `
<div>
<div>{{ school.name }}</div>
<div>{{ school.address }}</div>
<div>
<button @click="showName">点我</button>
</div>
</div>
`,
data() {
return {
school: {
name: '尚硅谷',
address: '北京'
}
}
},
methods: {
showName() {
console.log(this.school.name)
}
}
}
2、注册组件(局部注册):
const vm = new Vue({
el: '#app',
components: {
school: school,
student: student
}
})
全局注册组件:
Vue.component('hello',hello)
3、使用组件:
<div id="app">
<school></school>
</div>
组件的命名方式:
MySchool 只能在脚手架里用
my-school
组件的嵌套案例:
const school = {
template: `
<div>
<div>{{ school.name }}</div>
<div>{{ school.address }}</div>
<div>
<student></student>
</div>
</div>
`,
data() {
return {
school: {
name: '尚硅谷',
address: '北京'
}
}
},
components: {
student
}
}
编辑
首先要挂载app组件:
new Vue({
template: `
<app></app>
`,
el: '#app',
// 注册组件
components: {
app
}
})
VueComponent:
每一个组件都是一个VueComponent函数,每次调用Vue.extend(),每次都会new一个VueComponent
组件里函数的this是VueComponent,但是它的结构和vm一样,vm是Vue的实例,但是,VueComponent是vm的子组件
VueComponent.prototype.__proto__=Vue.prototype
所以,vc可以访问到vm上的属性和方法
单文件组件:
文件名:ABC.vue
webpack可以把.vue转换成html、css、js。但是我们通常使用vue-cli脚手架
3种暴露方式:
<script>
// 1、分别暴露,导入时需要{}
export const school = Vue.extend({
data() {
return {
school: {
name: '尚硅谷',
address: '北京'
}
}
}
})
// 2、统一暴露,导入时需要{}
export {
school
}
// 3、默认暴露,一般使用
export default school
// 3、的简写
export default {
name:'School', // 跟文件名保持一致
data() {
return {
school: {
name: '尚硅谷',
address: '北京'
}
}
}
}
</script>
单文件组件的案例:
<template>
<div class="demo">
<div>{{ student.name }}</div>
<div>{{ student.age }}</div>
</div>
</template>
<script>
export default {
name: 'Student',
data() {
return {
student: {
name: 'tom',
age: '18'
}
}
}
}
</script>
<style>
.demo {
background: red;
}
</style>
App.vue管理所有的组件:
<template>
<div class="demo">
<School></School>
<Student></Student>
</div>
</template>
<script>
import School from './School'
import Student from './Student'
export default {
name: 'App',
components: {
School,
Student
}
}
</script>
<style>
.demo {
background: red;
}
</style>
因为.vue文件不能new Vue,所以我们还需要写一个main.js:
import App from './App'
new Vue({
el: '#app',
template: `<App></App>`,
components: {App}
})
这时候没有容器,所以我们还需要写我们项目中唯一一个index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--容器-->
<div id="app"></div>
<script src="./js/vue.js"></script>
<!--入口文件-->
<script src="main.js"></script>
</body>
</html>
创建脚手架vue-cli:
全局安装脚手架:
npm i -g @vue/cli
创建一个vue项目:
vue create vue-demo
编辑
vue-cli的结构:
package.json:包管理文件
package-lock.json:包版本控制文件
assets目录:放静态资源
components目录:放所有的组件
public目录:里面有项目的唯一一个index.html文件
npm run build:构建项目,打包项目,所以功能都写完了,最后构建成项目
@/:src目录下
<!--使IE浏览器用最高的渲染级别渲染页面-->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<!--开启移动端理想视口-->
<meta name="viewport" content="width=device-width,initial-scale=1.0"><%= BASE_URL %>:public目录下
<%= htmlWebpackPlugin.options.title %>:在package.json里面找网站名,webpack里面的插件完成的功能
<noscript></noscript>:如果浏览器不支持script,那么这个标签里面的内容就会显示
render: h => h(App):创建App元素,因为引入的是esm的js,为了减少体积
.esm.js:采用了es6的模块化的语法的js
vue.config.js:vue-cli的默认配置文件,可以设置关闭语法检查
项目启动,脚手架会运行main.js
开发模式启动:
npm run dev
或者生产模式启动:
npm run serve
main.js:
// 该文件是整个项目的入口文件
// 引入Vue
import Vue from 'vue'
// 引入App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭vue的生产提示
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
ref属性:
<div ref="title">{{ msg }}</div>
<School ref="sch"></School>
console.log(this.$refs.title) // 真实dom元素
console.log(this.$refs.sch) // vc
props:表示从外界接收属性
复用组件时,但是数据不同,数据和方法从外部传入组件内
不建议在内部组件修改传入的值,解决方案:在内部组件再定义一个属性
<Student name="tom" sex="女" age="18"></Student>
第一种方式:
export default {
name: "Student",
data() {
return {}
},
// 简单接收
props: ['name', 'sex', 'age']
}
第二种方式:
<!--数值类型的age-->
<Student name="tom" sex="女" :age="18"></Student>
props: {
name: String,
sex: String,
age: Number
}
第三种方式:
props: {
name: {
type: String,
required: true
},
sex: {
type: String,
required: true
},
age: {
type: Number,
default:18
}
}
mixin混合:
导入js文件,使用js文件内的属性,方法,以及统一挂载方法
如果混合与组件有相同属性,混合的属性不导入,挂载的方法会结合
showName.js:
export const showName = {
methods: {
showName() {
console.log(this.name)
}
}
}
School.vue:
<div @click="showName">学校名:{{ name }}</div>
data() {
return {
}
},
mixins:[showName]
全局的混合:
main.js:
import {showName} from "./showName";
Vue.mixin(showName)
插件:
plugins.js:
export default {
install(Vue, a, b) {
console.log(Vue, a, b)
}
}
main.js:
// 导入插件
import plugins from "./plugins";
// 使用插件
Vue.use(plugins,1,2)
scoped:
让样式只在局部生效,防止冲突
两个模块中的style样式出现类名重复,导致后导入的组件覆盖先导入的组件
解决方案:<style scoped></style>即可解决
localStorage浏览器本地存储:
用例:搜索引擎的搜索历史,浏览器关闭还在
// 保存
localStorage.setItem('user', JSON.stringify(p))
// 读取,如果读不到是null
let user = localStorage.getItem('user')
// 删除
localStorage.removeItem('user')
// 清空
localStorage.clear()
sessionStorage浏览器会话存储:
特点:浏览器关闭会清除
// 保存
sessionStorage.setItem('user', JSON.stringify(p))
// 读取,如果读不到是null
let user = sessionStorage.getItem('user')
// 删除
sessionStorage.removeItem('user')
// 清空
sessionStorage.clear()
自定义事件:
适用于子组件给父组件传值
Student.vue
<div><button @click="sendStudentName">点我</button></div>
methods:{
sendStudentName(){
// 自定义事件
this.$emit('sendStudent',this.name)
}
}
App.vue:
第一种接收方式:
<Student @sendStudent="getStudentName"></Student>
methods: {
getStudentName(val,...params) {
console.log(val,params)
}
}
第二种接收方式:
mounted() {
// 挂载后,4秒后绑定了一个事件
setTimeout(() => {
this.$refs.student.$on('sendStudent', this.getStudentName)
}, 4000)
}
解绑事件:
Student.vue:
<button @click="unbind">解绑事件</button>
methods: {
unbind() {
this.$off('sendStudent')
}
}
解绑多个事件:
this.$off(['sendStudent','demo'])
解绑当前组件的全部自定义事件:
this.$off()
全局事件总线:
自定义事件绑定在vm上
使用一个组件来辅助其他组件交互数据,使得其他兄弟组件可以交互数据和方法
main.js:
beforeCreate() {
// 全局事件总线
Vue.prototype.$bus = this
}
School.vue:
mounted() {
this.$bus.$on('hello', (val) => {
// 绑定事件
console.log('i am school ', val)
})
},
beforeDestroy() {
// 解绑事件
this.$bus.$off('hello')
}
Student.vue:
methods:{
sendStudentName(){
// 触发事件
this.$bus.$emit('hello','tom')
}
}
消息订阅与发布:
一种组件间通信的方式
我们使用这个库:
npm i pubsub-js@1.6.0
发送方,Student.vue:
// 消息订阅与发布
import pubsub from 'pubsub-js'
export default {
name: "Student",
methods: {
sendStudentName() {
// 发送消息
pubsub.publish('hello', 'tom')
}
}
}
接收方,School.vue:
import pubsub from 'pubsub-js'
export default {
name: "School",
data() {
return {
pubId: '',
}
},
methods: {
hello(msgName, data) {
console.log('hello 接收消息执行了,消息名:' + msgName + ',数据:' + data)
}
},
mounted() {
// 接收消息
this.pubId = pubsub.subscribe('hello', this.hello)
},
beforeDestroy() {
// 解绑事件
pubsub.unsubscribe(this.pubId)
}
}
$nextTick 会在dom节点更新之后再执行
// 获取焦点
this.$nextTick(() => {
this.$refs.inputTitle.focus()
})
动画:
<button @click="isShow=!isShow">点我</button>
<!--appear 开局显示-->
<transition appear name="my">
<h1 v-show="isShow">hello</h1>
</transition>
<style>
@keyframes my_animation {
from {
transform: translateX(-100px);
}
to {
transform: translateX(0px);
}
}
.my-enter-active {
animation: my_animation 1s;
}
.my-leave-active {
animation: my_animation 1s reverse;
}
</style>
过渡:
.my-enter,.my-leave-to{
transform: translateX(-100%);
}
.my-enter-active,.my-leave-active{
transition: 1s linear;
}
.my-leave-to,.my-enter {
transform: translateX(0);
}
多个元素的过度:
<transition-group appear name="my">
<h1 v-show="!isShow" :key="1">hello</h1>
<h1 v-show="isShow" :key="2">尚硅谷</h1>
</transition-group>
使用第三方动画库:
npm i animate.css
import 'animate.css'
axios:
npm i axios
import axios from 'axios'
解决跨域问题:
同源策略:协议、ip、端口都一致
前端配置代理,方案一:
请求本服务器的地址:
axios.get('http://localhost:8080/student/getStudents/').then(res => {
console.log('ok')
console.log(res.data)
}).catch(e => {
console.log(`请求失败:${e.message}`)
})
本服务器再转发到后台服务器的地址:
const {defineConfig} = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true,
lintOnSave: false, // 关闭eslint校验
devServer: { // 配置devServer
// host: 'localhost', // 项目运行的ip
// port: 80, // 项目运行的端口号
proxy: 'http://localhost:8081', // 服务器的地址
},
})
方案二:
devServer: {
proxy: {
'/api': { // 匹配所有以/api开头的请求路径
target: 'http://localhost:8081', // 后端服务器的路径
changeOrigin: true, // 模拟后端服务器的端口,也就是8081
pathRewrite: {'^/api': ''}
},
}
},
插槽:
默认插槽:
App.vue:
<Category :listData="foods" title="美食">
<img src="@/assets/logo.png">
</Category>
<Category :listData="games" title="游戏">
<ul>
<li v-for="(item,index) in games" :key="index">{{ item }}</li>
</ul>
</Category>
Category.vue:
<div class="category">
<h3>{{ title }}分类</h3>
<!--定义一个默认插槽-->
<slot>
<!--没有给插槽传值的情况的默认值-->
</slot>
</div>
具名插槽:
<slot name="footer"></slot>
<a href="javascript:" slot="footer">单机游戏</a>
作用域插槽:
子传父形式
语法:
Category.vue:
<template>
<div class="category">
<h3>{{ title }}分类</h3>
<slot :games="games"></slot>
</div>
</template>
<script>
export default {
name: "Ctegory",
props: ['title'],
data() {
return {
games: ['红色警戒', '穿越火线', '劲舞团', '超级玛丽'],
}
}
}
</script>
App.vue:
<Category title="游戏">
<template scope="{games}">
<ul>
<li v-for="(item,index) in games" :key="index">{{ item }}</li>
</ul>
</template>
</Category>