https://www.bilibili.com/video/BV1Zy4y1K7SH
注意不需要写el,所有组件由vm管理, vm决定组件服务于哪一个容器
const school = Vue.extend({
template:``,
data(){}
})
1.全局注册,这样所有的vm都可以使用该组件
2.局部注册,这样对应的vm可以使用该组件
Vue.component('school', school) //全局注册,第一个参数是名字,第二个是组件
new Vue({
el: '#root',
//局部注册
components: {
school,
}
})
<div id="root">
<student></student>
</div>
1.组件里的data一定要写成函数,因为同一组件可能会在多处地方调用,同一组件在不同的地方属性应该相互独立,所以不能A.data=B.data,而是,通过函数data()返回一个对象,使得A=data(),b=data()
2.简写:
const school = Vue.extend(options)
//可以简写成
const school = options
在组件内部注册组件,在template里面编写组件标签
const school = Vue.extend({
name: 'school',
template: `学校名称:{{name}}
`,
data() {
return {
name: '尚硅谷',
}},
components: {//注册组件(局部)
student
}})
1.Vue帮我们修改了VueComponent原型对象的原型,使下面代码成立。
VueComponent.prototype.__proto__ === Vue.prototype
2.所以组件实例对象vc可以访问到Vue原型上的属性和方法。(以后需要这功能)
import App from './App'
new Vue({
el: '#root',
template:` `,
component: {App},
})
<template>
<div>
<School></School>
<Student></Student>
</div>
</template>
<script>
import School from './School'
import Student from './Student'
export default {
name:'App',
components:{
School,
Student,
}
}
├── 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.js是完整版的Vue,包含:核心功能 + 模板解析器。
vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。
vue.runtime.xxx.js比vue.js小很多。
在vue下的package.json,里的module,指定了我们默认import vue时引入的是dist/vue.runtime.esm.js
import Vue from 'vue' //引入的是残缺的Vue,没有模板解析器,需要用render函数
new Vue({
render: h => h(App)
//render(createElement) createElement函数返回一个Vnode节点
//render函数得到这个节点后,返回给mount函数,
//mount函数渲染成真实的Dom节点,并挂在到根节点上,也就不需要el了
}).$mount('#app')
1.ref可以理解为id的替代者
2.在html标签上获取的是真实DOM元素,在组件标签上是组件实例对象(vc)
使用方式:
<h1 ref="title">.....</h1>
<School ref="scho"></School>
获取方式:this.$refs.xxx
methods: {
showDOM() {
console.log(this.$refs.title);//dom节点h1
console.log(this.$refs.scho); //实例对象(vc)
},
所谓混合就是把2个组件共有的配置项提取出来,然后共同使用。
hunhe.js:暴露出你的配置项
export const hunhe = {
methods: {
showName() {
alert(this.name);
},
},
mounted() {
console.log("你好啊!");
},
};
全局混入:在main.js下引入,然后Vue.mixin(xxx),这样所有的vm,vc上都会有该属性
局部混入:将混合引入当前文件,然后在vm/vc的配置项里设置:mixins:[‘xxx1’,‘xxx2’]
1.Vue会帮我们对象里的install方法,install可以接收vm的构造者,也就是Vue构造函数
2.Vue.use(pluins):通过Vue.use使用插件,这样就会帮我们调用install方法
pugin.js:
暴露出一个对象,如果对象里有install方法,Vue会调用
export default {
install(Vue) {
Vue.filter("mySlice", function (value) {
return value.slice(0, 4);
});
}
main.js:
import Vue from "vue";
import App from "./App.vue";
import plugins from "./plugins";//引入插件
//应用(使用)插件
Vue.use(plugins);
作用:让样式只在对应的组件局部生效,防止冲突。
写法:
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
该方法接受一个键名作为参数,返回键名对应的值。
该方法接受一个键名作为参数,并把该键名从存储中删除。
该方法会清空存储中的所有数据。
npm i axios
配置一个代理服务器,代理服务器和你本地都是8080,但因为代理服务器是服务器,它与5050交换数据不受跨域的影响,服务器之间不受跨域影响。
开启代理服务器:
①使用nginx
②vue-cli(4行代码),在vue.config.js里配置
axios:axios返回的是promise对象
现在电脑上有2台服务器,一台是vue-cli或者chrome帮我们开的,8080,一台是我们自己用node开的5050,现在要实现它们之间的交互,我们需要自己创建一个代理服务器,它的端口也是8080.
vue.config.js:
module.exports = {
pages:{
index:{
entry:'src/main.js'//入口
},
},
LintOnSave:false,//关闭语法检查
//开启代理服务器
devServer:{// proxy:代理
//代理服务器就是与本地一样的8080,我们需要设置的是往哪代理
proxy:'http://localhost:5050'
}
}
App.vue
import axios from 'axios'
export default{
name:'App',
methods:{
getStudents(){
//这里实际上访问的就是http://localhost:5050/students
axios.get('http://localhost:8080/students').then(
response=>{
console.log('请求成功',response.data)},
error=>{
console.log('请求失败',error.message)}
)}}}
vue.config.js:
module.exports = {
devServer:{
proxy:{
'/haha':{
target:'http://localhost:5050',
//正则匹配以 /haha 开头的字符,转化为''
parthRewrite:{'^/haha':''},
//用于支持websocket,默认为true,可以不写
ws:true,
//改变:5050收到的是5050发过来的请求
//不改变:5050收到的是8080发过来的请求
//改变的是请求头中host的值
//默认为true,可以不写
changeOrigin:true
},
'/haha2000':{},
}}}
App.vue
axios.get('http://localhost:8080/haha/students').then()
使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
props配置项:让组件接收外部传过来的数据
props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
<component name='tom' :age='3' :set-name='setName'><component>
props: ['name', 'age', 'setName']
props: {
name: String,
age: Number,
setName: Function
}
props: {
name: {type: String, required: true, default:老王},
}
用于子组件向父组件通信
app.vue:
<student v-on:haha='demo'> </student>
//表示给student的实例对象vc绑定了一个事件,事件名为haha,事件被触发会调用demo函数
methods:{
demo(value){
console.log(value)
}
}
student.vue:
<button @click='onclick()'></button>
export default{
name:'student',
methods:{
onclick(){
//通过vc上的emit方法,调用haha事件,并传递数据
this.$emit('haha',this.name)
}
}
}
修改App.vue
<student ref='stu'> </student>
methods:{
demo(value){
console.log(value)
}
}
mounted(){//给student的实例stu绑定,如果事件haha触发了,调用demo
this.$refs.stu.$on('haha',this.demo)
//只调用一次
//this.$refs.stu.$once('haha',this.demo)
}
给谁定义的,就给谁解绑
this.$off('haha')//解绑一个
this.$off(['haha','haha2'])//解绑多个
this.$off()//解绑所有
通过this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
定义:一种组件间通信的方式,适用于任意组件间通信。
注意 :&on,$emit都在Vue的原型对象上,如果想通过xxx.调用这2个方法,那么xxx的是vm或者vc
new Vue({
......
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
},
......
})
(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){
demo(data){......}
}
......
mounted() {
this.$bus.$on('xxxx',this.demo)
}
(2)提供数据:
vc和vm都可以访问Vue.prototype
this.$bus.$emit('xxxx',数据)
(3)最后最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件
一种组件间通信的方式,适用于任意组件间通信。
因为消息队列的原因需要在beforeDestroy里面取消订阅,不然可能会出错
pubsub.subscribe('hello',function(a,b){ //需要数据的人
//subscribe订阅消息,如果有人发布了消息就会执行回调
//回调函数可以接受两个参数 a是订阅名hello b是传递过来的666
console.log('有人发布了hello消息,hellow消息的回调执行了',a,b)
//有人发布了... hello 666
})
pubsub.publish('hello',666) //发布消息 给数据的人
this.pubId=pubsub.subscribe('hello',(msg,data)=>{
//使用消息与订阅一般需要操作data数据,但是这里的this是undefined
//vue保证this都是vc或者vm,但消息与订阅不是vue的产物,所以这里用箭头函数使得它没有this,this去外层查找
})
发布的消息会保存在消息队列里面
需要:发布->传给订阅->销毁
不销毁会出现消息队列里有多个发布,因为这些发布都是同名的所以订阅会执行多个获取发布。
//销毁方式
beforeDestroy(){
PubSub.unsubscribe(this.pubId)
}
作用:让父组件可以向子组件指定位置插入html结构
传递方式:适用于 父组件 => 子组件
分类:默认插槽、具名插槽、作用域插槽
<Son>
<div> html结构1 </div>
</Son>
<template>
<div>
<h2>你好</h2>
<!--slot表示往哪插入,如下,不设置slot,不知道往h3上面显示还是下面显示-->
<slot>我是默认值,父亲没给我具体内容的时候,我会出现,我可以是默认显示的html </slot>
</div>
</template>
<Son>
<template slot="center">
<div>html结构1</div>
</template>
<!-- 使用solot指定要插的槽名 在template标签下可以使用v-slot简写 -->
<template v-slot:footer>
<div>html结构2</div>
</template>
</Son>
<template>
<div>
<!-- 定义插槽 -->
<slot name="center">默认html</slot>
<slot name="footer">默认html2</slot>
</div>
</template>
理解:数据在子组件的自身,但根据数据生成的结构由父组件来决定。
通信:父亲传递html结构给子组件。
将子组件的数据haha与msg33传给插槽的使用者Father。(并不是真的传递,是父组件将html结构传给子组件后,子组件往这段结构里面传递)
<template>
<div>
<!-- 这里的:haha可以起名为games -->
<slot :haha="games" msg33="abc123"></slot>
</div>
</template>
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽']
}
},
父组件帮子组件定义的html结构要想获得子组件传过来的数据,必须使用标签,且有scope或者slot-scope属性用来接收传过来的数据。scopeData是一个对象。
<Son>
<!-- 可以使用scope也可以使用slot-scope -->
<template scope="scopeData">
<ul>
<li v-for="(g,index) in scopeData.haha" :key="index"></li>
</ul>
<h2>{{scopeData.msg33}}</h2>
</template>
</Son>
<Son>
<!-- 可以使用scope也可以使用slot-scope -->
<template scope="{{haha}}">
<ul>
<li v-for="(g,index) in haha" :key="index"></li>
</ul>
<h2>{{scopeData.msg33}}</h2>
</template>
</Son>
Vuex管理数据其实就是用state对象管理数据
dispatch:调度
methods:{
increment(){
this.$store.dispatch('jia',2)
//dispatch会把参数2发送给action里面的jia函数
//第一个参数调用action里的哪个方法,第二个是传递的数据
}
}
const actions={
jia(context,value){
console.log(value)
//一般mutations里的函数写成大写,用于区分
context.commit('JIA',value)
}
}
mutations里面的函数都可以接受2个参数:
const mutations={
JIA(state,value){
console.log(state,value)
state.sum+=value
}
}
当不需要进行网络请求或者定时器操作时,可以不用dispatch去调用Actions
直接用commit,仍需要注意的是,Mutations里的函数名需要大写。
methods:{
increment(){
this.$store.commit('JIA',2)
}
}
//引入Vue核心库
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex) //Vuex是Vue的插件,所以需要引入Vue使用Vue.use方法
//准备actions对象——响应组件中用户的动作
const actions = {}
//准备mutations对象——修改state中的数据
const mutations = {}
//准备state对象——保存具体的数据
const state = {}
//创建并暴露store
export default new Vuex.Store({
actions,
mutations,
state
})
tip:如果Actions、Mutations、State代码量很长,也可以将他们分离成js文件。
import store from './store/index'
new Vue({
el:'#app',
render: h => h(App),
store //store:store
})
const getters = {
bigSum(state){//可以接收state
return state.sum * 10
}
}
//记得注册getters
export default new Vuex.Store({
state,
actions,
mutations,
getters
})
需要使用这四个方法需要从vuex里面引入
import {mapState, mapMutations,mapGetters,mapActions} from 'vuex';
mapState()用于帮助我们映射state中的数据为计算属性
mapState返回的是{sum:f,school:f,subject:f}的形式,所以需要使用扩展运算符展开
computed: {
...mapState({haha:'sum'})
}
haha是函数名称,sum是state里面的sum属性,…mapState({haha:‘sum’})等效于下面这句
haha(){
return this.$store.state.sum
}
当函数名称与属性名称相同时的写法
①对象写法
computed: {
...mapState({sum:'sum',school:'school',subject:'subject'})
}
②数组写法
注意:不能…mapState({sum,school,subject})。
因为对象在sum:sum的时候可以简写。但这里需要的是sum:‘sum’,所以不能简写。
computed: {
...mapState(['sum','school','subject']),
}
用于帮助我们映射getters中的数据为计算属性
computed: {
//借助mapGetters生成计算属性:bigSum(对象写法)
...mapGetters({bigSum:'bigSum'}),
//借助mapGetters生成计算属性:bigSum(数组写法)
...mapGetters(['bigSum'])
},
用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数.
靠mapMutations生成:increment、idecremennt(对象形式)
...mapMutations({increment:'JIA',decremennt:'JIAN'})
//等效于:
increment(value){
this.$store.commit('JIA',value)
}
我们会发现我们没有给它指定value是多少,所以在我们调用方法的时候,我们需要手动传递。
<button @click='increment(num)'>+</button>
//这里的num是vm/vc自身的属性
靠mapActions生成:JIA、JIAN(数组形式)
...mapMutations(['JIA','JIAN'])
//等效于:
JIA(){
this.$store.commit('JIA')
}
用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数。参数也是需要我们使用的时候手动传递。
methods:{
//靠mapActions生成:incrementOdd、incrementWait(对象形式)
...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
//靠mapActions生成:incrementOdd、incrementWait(数组形式)
...mapActions(['jiaOdd','jiaWait'])
//等效于
jiaOdd(){
this.$store.dispatch('jiaOdd')
}
}
用mapMutations和mapActions生成的方法:
如果需要传递参数请在调用的时候传,如 @click=‘jiaOdd(this.n)’,不传参数默认传递的是event事件对象。
import router from './router'
new Vue({
router
})
export default new Router({
routes: [
{ // 一般路由
path: '/about',
component: About
},
{ // 自动跳转路由
path: '/',
redirect: '/about'//设置往哪跳转
}
]
})
//: 用来生成路由链接
<router-link to="/xxx">Go to XXX</router-link>
//: 用来控制路由组件界面在当前界面的哪个地方显示
<router-view></router-view>
//还可以配合v-if控制是否显示当前标签
<router-view v-if="false"/>
通过children配置子级路由
跳转(要写完整路径)
<router-link to="/home/news">News</router-link>
routes:[
{
path:'/about',
component:About,
},
{
path:'/home',
component:Home,
children:[ //通过children配置子级路由
{
path:'news', //此处一定不要写:/news
component:News
},
{
path:'message',//此处一定不要写:/message
component:Message
}
]
}
]
作用:简化路由的跳转
routes:[
{path:'/home',
name:'jia'
component:Home,
children:[
{
path:'message',
name:'msg'
component:Message,
}
]
}
]
简化跳转:
//简化前
<router-link to='/home/message'>
//简化后 直接通过名字体跳转,但得写成字符串包含对象形式
<router-link :to="{name:'msg'}">
//可以配合传递参数
<router-link :to='{
name:'msg',
id:m.id,
title:m.title
}'>
可以sessionStorage/localStorage/cookie 进行离线缓存存储,用vuex也可以,不过有些大材小用,具体因场景而异
标签可以帮助我们往别的组件跳转,并携带数据跳转:
路由配置:
routes:[
{path:'/home',
component:Home,
children:[
{
path:'message',
component:Message,
children:[
{
path:'detail',
coponent:Detail,
}
]
}
]
}]
<router-link to='/home/message/detail?id=666&title=你好啊'>
//接受数据的组件调用$route查看路由里的数据
this.$route.query.id //666
this.$route.query.title //你好啊
<router-link to='/home/message/detail?id=m.id & title=m.title'>
this.$route.query.id //m.id
this.$route.query.title //m.title
<router-link :to='`/home/message/detail?id=${m.id}&title=${m.title}`'>
this.$route.query.id //666
this.$route.query.title //你好啊
<router-link :to="{
path:'/home/message/detail'
query:{
id:m.id,
title:m.title
}
}">
this.$route.query.id //666
this.$route.query.title //你好啊
需要注意的是使用对象写法只能携带name参数(name:‘xiangqing’),不能携带path参数(path:’/home/message/detail’)
//常数
<router-link :to='`/home/message/detail/666/你好啊`'>
//变量
<router-link :to='`/home/message/detail/${m.id}/${m.title}`'>
//对象写法
<router-link :to="{
name:'xiangqing'
params:{
id:m.id,
title:m.title
}
}">
使用这种方法需要改路由的配置,要不然不知道**/666/你好啊**对应的是params里的哪个些参数。
children:[
{
name:'xiangqing'
path:'detail/:id/:title',
coponent:Detail,
}
]
接受数据:
this.$route.params.id //666
this.$route.params.title //你好啊
点击某个按钮跳转到别的组件
this.$router.push("home/message/detail")
this.$router.push({ name: 'dedede', params: { id: 123 ,title:'你好啊'}})
//修改路由配置
children:[
{
name:'dedede'
path:'detail',
coponent:Detail/:id/:title,
}
]
//接收参数
this.$route.params.id
this.$router.push({ path: 'home/message/detail', query: { id: 123 }});
//也可以这样写
this.$router.push({ path: 'home/message/detail?id=666'});
//接受参数
this.$route.query.id
1.让路由组件更方便的收到参数,子组件使用每个参数都得this.$route.params.xxx,这样太麻烦了。
props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
//1.路由里
{//第一种写法:
name:'xiangqing',
path:'detail/:id',
component:Detail,
props:{a:900}
}
//2.组件里添加配置项,跟组件间通信用的同一个props:
props:['a']
props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
{//第二种写法
name:'xiangqing',
path:'detail/:id',
component:Detail,
props:true
}
//接收
props:['id','title']
props值为函数,该函数返回的对象中的每一组key-value都会通过props传给Detail组件.
{//第三种写法:
name:'xiangqing',
path:'detail/:id',
component:Detail,
//props可以接收$route,这里是个形参,$route可以写成abc
props($route){
return {
id:$route.query.id,
title:$route.query.title
}
}
}
//接收
props:['id','title','a','b']
但这种方法不够简洁,就好像是把子组件里的代码写到了路由里,利用解构赋值改写:
{//最优版:
name:'xiangqing',
path:'detail/:id',
component:Detail,
props({query:{id,title}}){
//这里也可以实现第一种写法的效果,传递死数据
return {id,title,a:1,b:'hello'}
}
}
//接收
props:['id','title','a','b']
控制路由跳转时操作浏览器历史记录的模式。
push是追加历史记录,设置为push,页面可以前进,后退。
replace是替换当前记录,不可以前进,后退。
路由跳转时候默认为push
<router-link replace></router-link>
作用:不借助
实现路由跳转,让路由跳转更加灵活
使用:
this.$router.forward() //前进
this.$router.back() //后退
this.$router.go() //可前进也可后退 里面放的是数字,1前进,-1后退
作用:
让不展示的路由组件保持挂载,不被销毁。
使用:
<keep-alive include="News"> //只缓存news组件
<router-view></router-view>
</keep-alive>
路由组件所独有的两个钩子。
activated()
路由组件被激活时触发,也就是当前页面出现了该组件的时候触发deactivated()
路由组件失活时触发。适用的情况:当前组件被缓存了,当前组件有持续的定时器任务,切走后会在后台消耗性能,所以切走页面的时候让它停止。
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。
模式选择:
export default new Router({
mode: 'history',//不设置默认是 mode:'hash',
}
按需引入:
装 babel-plugin-component:
npm install babel-plugin-component -D
在babel.config.js追加以下内容。
追加这些内容后就不需要我们自己引入样式了,脚手架会根据我们使用了什么组件,自动帮我们引入对应的样式。
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',//原先就有
["es2015", { "modules": false }]
],
plugins: [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
在 main.js 中写入以下内容。
import Vue from 'vue';
import { Button, Select } from 'element-ui';//按需引入组件
import App from './App.vue';
//全局注册组件
Vue.component(Button.name, Button);//Button.name就是el-button
Vue.component(Select.name, Select);
//或写为 Vue.use(Button) Vue.use(Select)
new Vue({
el: '#app',
render: h => h(App)
});