1.小兔鲜
所有都折叠 ctrl + k,ctrl+0
所有都展开 ctrl + k,ctrl+j
当前结构渲染5次
<BaseBrandItem v-for="item in 5" :key="item"><BaseBrandItem>
2.scoped样式冲突
- 结构:只能有一个根元素
- 样式:全局样式(默认):影响所有组件
局部样式:scoped下样式,只作用于当前组件 - 逻辑:el根实例独有,data是一个函数,其他配置项一致
scoped原理:
- 给当前组件模板的所有元素,都会被添加上一个自定义属性 data-v-hash值,不同的hash值区分不同的组件
- css选择器后面,被自动处理,添加上了属性选择器 div[data-v-5f6a9d56]
3. data是一个函数
组件的data选项必须是一个函数
每次创建新的组件实例,都会新执行一次data函数,得到一个新对象
export default {data() {return {count: 100,}}}
4.组件通信
组件与组件之间的数据传递,想用其他组件的数据=>组件通信
组件关系:父子关系和非父子关系
组件通信解决方案:
- 父子关系:props和$emit
- 非父子关系:
provide&inject
eventbus
通用解决方案:Vuex(适合复杂业务场景)
父子关系:
父传子
props后面的属性名必须与子组件里面的属性名一致
子传父
父组件对消息进行监听,@后面的名字与子组件中$emit中的第一个名字一样 @changeTitle=“handleChange”,handleChange是父组件的处理函数
子组件
<script>
export default {name: 'Son-Child',props: ['title'],methods: {changeFn() {// 通过this.$emit() 向父组件发送通知this.$emit('changTitle','传智教育')},},
}
</script>
父组件
<!-- 2.父组件对子组件的消息进行监听 --><Son :title="myTitle" @changTitle="handleChange"></Son></div>
</template><script>
import Son from './components/Son.vue'
export default {name: 'App',data() {return {myTitle: '学前端,就来黑马程序员',}},components: {Son,},methods: {// 3.提供处理函数,提供逻辑handleChange(newTitle) {this.myTitle = newTitle},},
}
</script>
5.props详解
prop是组件上注册的一些自定义属性
作用:向子组件传递数据
可以传递任意数量,任意类型的prop
把hobby对象里面的标点符号改为、
{{hobby.join(‘、’)}}
子组件
<template><div class="userinfo"><h3>我是个人信息组件</h3><div>姓名:{{username}}</div><div>年龄:{{age}}</div><div>是否单身:{{isSingle}}</div><div>座驾:{{car.brand}}</div><div>兴趣爱好:{{hobby.join('、')}}</div></div>
</template><script>
export default {props:['username','age','isSingle','car','hobby']
}
</script>
父组件
export default {data() {return {username: '小帅',age: 28,isSingle: true,car: {brand: '宝马',},hobby: ['篮球', '足球', '羽毛球'],}},components: {UserInfo,},
}
props校验
为组件的prop指定验证要求,不符合要求,控制台有错误提示
语法:
- 类型校验:
props: {校验的属性名:类型 //String Number Boolean Array Object Function
}
- 非空校验、默认值、自定义校验
// 完整写法(类型、默认值、非空、自定义校验)props: {w: {type: Number,//类型required: true,//是否必填default: 0, //默认值validator(val) {//自定义校验逻辑if (val >= 100 || val <= 0) {console.error('传入的范围必须是0-100之间')return false} else {return true}},},},
props vs data
共同点:都可以给组件提供数据
不同点:
data:数据是自己的,随便改
props:数据是外部的,不能直接改,要遵循单项数据流
如:prop传的count不能在结构里写++,或–,只能在methods里面写加或减函数,函数里用子传父$emit,用count+1,不能写++
父组件
一旦老爹改了,视图也跟着改了
$emit通知父组件要修改,其prop更新,会单项向下流动,影响到子组件
methods:{handleChange(newVal){// console.log(newVal);this.count = newVal}}
6.小黑记事本-组件版
渲染功能:
1.提供数据: 提供在公共的父组件 App.vue
2.通过父传子,将数据传递给TodoMain
3.利用 v-for渲染
添加功能:
1.手机表单数据 v-model
2.监听事件(回车+点击都要添加)
3.子传父,讲任务名称传递给父组件 App.vue
4.进行添加 unshift(自己的数据自己负责)
5.清空文本框输入的内容
6.对输入的空数据 进行判断
删除功能
1.监听事件(监听删除的点击) 携带id
2.子传父,讲删除的id传递给父组件的App.vue
3.进行删除filter(自己的数据 自己负责)
底部合计:父传子 传list 渲染
清空功能:子传父 通知父组件 → 父组件进行更新
持久化存储:watch深度监视list的变化 -> 往本地存储 ->进入页面优先读取本地数据
根组件
<template><!-- 主体区域 --><section id="app"><!--导入,注册,使用--><ToDoHeader @add="handleAdd"></ToDoHeader><ToDoMain @del="handleDel" :list="list"></ToDoMain><ToDoFooter @clear="handleClear" :list="list"></ToDoFooter></section>
</template><script>
import ToDoHeader from './components/ToDoHeader.vue';
import ToDoMain from './components/ToDoMain.vue';
import ToDoFooter from './components/ToDoFooter.vue';
export default {data () {return {list: JSON.parse(localStorage.getItem('list')) || [{id: 1, name:'打篮球'},{id: 2, name: '游泳'},]}},components: {ToDoHeader,ToDoMain,ToDoFooter},watch: {list: {deep: true,handler(newVal) {localStorage.setItem('list', JSON.stringify(newVal))},},},methods: {handleAdd(Todoname) {this.list.unshift({id: +new Date ,name: Todoname})},handleDel(id) {this.list = this.list.filter((item)=> item.id !== id)},handleClear() {this.list = []}},}
</script>
7.非父子通信-event bus 事件总线
- 创建一个都能访问到的事件总线(空vue实例)
import Vue from 'vue'
const Bus = new Vue()
export default Bus
- A组件(接收方),监听Bus实例的事件
created() {Bus.$on('sendMsg', (msg) => {// console.log(msg)this.msg = msg})},
- B组件(发送方),触发Bus实例的事件
methods: {sendMsgFn() {Bus.$emit('sendMsg', '今天天气不错,适合旅游')},},
8.非父子通信-provide&inject
作用:跨层级共享数据
根组件:
provide() {return {// 简单类型 是非响应式的//拿的是下面data的数据color: this.color,// 复杂类型 是响应式的userInfo: this.userInfo,}},data() {return {color: 'pink',userInfo: {name: 'zs',age: 18,},}},
子组件:
用inject获取元素后,直接用双括号渲染
<template><div class="grandSon">我是GrandSon{{ color }} -{{ userInfo.name }} -{{ userInfo.age }}</div>
</template><script>
export default {inject: ['color', 'userInfo'],
}
</script>
9.v-model原理,详解
是value属性和input属性的和写,提供数据的双向绑定
数据变,视图变:value
视图变,数据变@input
注意:$event用于在模板中,获取时间的形参
表单类组件封装&v-model简化代码
- 表单类组件封装
这就是data()里面的数据可以改,props里面获取到的属性不能改,那么v-model就无法绑定props里面的值了的问题,因为表单提交,父组件,子组件不能改动,所以要吧v-model进行拆解。
子组件
<template><div><select :selectId="selectId" @change="changeCity"><option value="101">北京</option><option value="102">上海</option><option value="103">武汉</option><option value="104">广州</option><option value="105">深圳</option></select></div>
</template>
<script>
export default {props: {selectId: String},methods: {//子传父selectCity(e) {this.$emit('changeCity',e.target.value )}}
}
</script>
- 父组件v-model简化代码,实现子组件和父组件双向绑定
子组件:props通过value接收,事件触发input
父组件中:v-model给组件中直接绑数据(:value+@input)
父组件
<template><div class="app"><BaseSelectv-model="selectId"></BaseSelect></div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {data() {return {selectId: '102'}},components: {BaseSelect,},
}
</script>
10. .sync修饰符
作用:可以实现子组件与父组件数据的双向绑定,简化代码
特点:prop属性,可以自定义,非固定为value
场景:封装弹框类的基础组件,visible属性true显示false隐藏
本质:就是 :属性名 和@update:属性名 合写
父组件
<template><div class="app"><button @click="openDialog">退出按钮</button><!-- isShow.sync => :isShow="isShow" @update:isShow="isShow=$event" --><BaseDialog :isShow.sync="isShow"></BaseDialog></div>
</template>
<script>
import BaseDialog from './components/BaseDialog.vue'
export default {data() {return {isShow: false,}},methods: {openDialog() {this.isSHow = true}},// console.log(document.querySelectorAll('.box')); components: {BaseDialog,},
}
</script>
11.ref 和$refs
作用:
- 获取dom元素
- 组件实例
特点:查找范围=>当前组件内(更精确稳定)
获取dom元素目标标签-添加ref属性
<div ref="chartRef">我是渲染图标的容器</div>恰当时机,通过this.$refs.xxx获取目标标签
mounted() {console.log(this.$refs.chartRef)var myChart = echarts.init(this.$refs.chartRef)
}
组件实例目标组件-添加ref属性
<BaseForm ref="baseForm"></BaseForm>恰当时机,通过this.$refs.xxx获取目标组件,就可以调用组件对象里面的方法。this.$refs.baseForm.组件方法()
12.vue异步更新和$nextTick
由于vue异步更新,我们需要用到$nextTick,等dom更新完后才会触发此方法里的函数体。
语法
this.$nextTick(()=>{this.$refs.inp.focus()
})
methods: {editFn() {// 1.显示文本框this.isShowEdit = true// 2.让文本框聚焦 (会等dom更新完之后 立马执行nextTick中的回调函数)this.$nextTick(() => {console.log(this.$refs.inp)this.$refs.inp.focus()})
setTimeOut也可以,但是$nextTick更精准
13.自定义指令
自己定义的指令,可以封装一些dom操作,扩展额外功能
全局注册指令-在mian.js
v-指令名(focus) ,写在app.vue里
// 1. 全局注册指令
Vue.directive('focus', {// inserted 会在 指令所在的元素,被插入到页面中时触发inserted (el) {// el 就是指令所绑定的元素// console.log(el);el.focus()}
})
局部指令
// 2. 局部注册指令directives: {// 指令名:指令的配置项focus: {inserted (el) {el.focus()}}}
14.自定义指令的值
实现一个color指令,传入不同的颜色,给标签设置文字颜色
- 用‘等号’绑定具体的参数值
- 通过binding.value可以拿到指令值,指令值修改会触发update函数
<div v-color="color"</div>
directives: {color: {// 1. inserted 提供的是元素被添加到页面中时的逻辑inserted (el, binding) {// binding.value 就是指令的值el.style.color = binding.value},// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑update (el, binding) {el.style.color = binding.value}}}
<template><div><h1 v-color="color1">指令的值1测试</h1><h1 v-color="color2">指令的值2测试</h1></div>
</template><script>
export default {data () {return {color1: 'red',color2: 'orange'}},directives: {color: {// 1. inserted 提供的是元素被添加到页面中时的逻辑inserted (el, binding) {// console.log(el, binding.value);// binding.value 就是指令的值el.style.color = binding.value},// 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑update (el, binding) {console.log('指令的值修改了');el.style.color = binding.value}}}
}
</script>
15.自定义指令-v-loading指令封装
使用伪元素定位,而不是新加一个标签,会更方便,只需添加移除类即可
- inserted,设置默认状态
- update,更新类名状态
<template><div class="main"><div class="box" v-loading="isLoading"><ul><li v-for="item in list" :key="item.id" class="news"><div class="left"><div class="title">{{ item.title }}</div><div class="info"><span>{{ item.source }}</span><span>{{ item.time }}</span></div></div><div class="right"><img :src="item.img" alt=""></div></li></ul></div><div class="box2" v-loading="isLoading2"></div></div>
</template><script>
// 安装axios => yarn add axios
import axios from 'axios'// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {data () {return {list: [],isLoading: true,isLoading2: true,}},async created () {// 1. 发送请求获取数据const res = await axios.get('http://hmajax.itheima.net/api/news')setTimeout(() => {// 2. 更新到 list 中,用于页面渲染 v-forthis.list = res.data.data//结束后this.isLoading = false}, 2000)},directives: {loading: {inserted (el, binding) {binding.value ? el.classList.add('loading') : el.classList.remove('loading')},//当数据变化时做出改动update (el, binding) {binding.value ? el.classList.add('loading') : el.classList.remove('loading')}}}
}
</script><style>
.loading:before {content: '';position: absolute;left: 0;top: 0;width: 100%;height: 100%;background: #fff url('./loading.gif') no-repeat center;
}
16.插槽-默认插槽
插槽分为两类:默认插槽(定制一处结构)+具名插槽(定制多处结构)
让自己内部的一些结构支持自定义
<!-- 1. 在需要定制的位置,使用slot占位,写在子组件 --><slot></slot>
<!-- 2. 在使用组件时,组件标签内填入内容,写在app.vue根组件 --><MyDialog><div>你确认要删除么</div></MyDialog>
插槽-后备内容(默认值)
在slot标签内,放置内容,作为默认显示内容
使用Mydialog时,里面不传内容,则会传slot里面的。
若是有内容,则Mydialog替换slot里面的
17.插槽-具名插槽
为了使用多个插槽
语法:
- 多个slot使用name属性区分名字
子组件
<div class="dialog-header"><!-- 一旦插槽起了名字,就是具名插槽,只支持定向分发 --><slot name="head"></slot></div><div class="dialog-content"><slot name="content"></slot></div><div class="dialog-footer"><slot name="footer"></slot></div>
- template配合v-slot:名字 来对应 (定向)分发标签
根组件
<MyDialog><!-- 需要通过template标签包裹需要分发的结构,包成一个整体 --><template v-slot:head><div>我是大标题</div></template><template v-slot:content><div>我是内容</div></template><template #footer><button>取消</button><button>确认</button></template></MyDialog>
- v-slot:插槽名可以简化为 #插槽名
<template #footer><button>取消</button><button>确认</button></template>
插槽-作用域插槽-插槽的传参语法
作用:定义slot插槽的同时可以传值,给插槽上绑定数据,将来使用组件时可以用。
场景:封装表格组件
- 给slot标签以添加属性方式传值
<!-- 1. 给slot标签,添加属性的方式传值 --><slot :row="item" msg="测试文本"></slot>
- 所有添加属性,都会被收集到一个对象中
{ id: 2, msg: '测试文本' }
- 在template中通过 #插槽名="obj"接收,默认插槽名为default
<MyTable :data="list"><!-- 3. 通过template #插槽名="变量名" 接收 --><template #default="obj"><button @click="del(obj.row.id)">删除</button></template></MyTable>
整体效果
根组件
<template><div><MyTable :data="list"><template #default="obj"><button @click="del(obj.row.id)">删除</button></template></MyTable><MyTable :data="list2"><template #default="{row}"><button @click="show(row)">查看</button></template></MyTable></div>
</template><script>
import MyTable from './components/MyTable.vue'
export default {data () {return {list: [{ id: 1, name: '张小花', age: 18 },{ id: 2, name: '孙大明', age: 19 },{ id: 3, name: '刘德忠', age: 17 },],list2: [{ id: 1, name: '赵小云', age: 18 },{ id: 2, name: '刘蓓蓓', age: 19 },{ id: 3, name: '姜肖泰', age: 17 },]}},methods: {del(id){this.list = this.list.filter(item=> item.id !==id)},show(row){alert(`姓名:${row.name} 年纪:${row.age}`)}},components: {MyTable}
}
</script>
18.商品列表
// my-tag 标签组件的封装
// 1. 创建组件 - 初始化
// 2. 实现功能
// (1) 双击显示,并且自动聚焦
// v-if v-else @dbclick 操作 isEdit
// 自动聚焦:
// 1. $nextTick => $refs 获取到dom,进行focus获取焦点
// 2. 封装v-focus指令// (2) 失去焦点,隐藏输入框
// @blur 操作 isEdit 即可// (3) 回显标签信息
// 回显的标签信息是父组件传递过来的
// v-model实现功能 (简化代码) v-model => :value 和 @input
// 组件内部通过props接收, :value设置给输入框// (4) 内容修改了,回车 => 修改标签信息
// @keyup.enter, 触发事件 $emit('input', e.target.value)// ---------------------------------------------------------------------
// my-table 表格组件的封装
// 1. 数据不能写死,动态传递表格渲染的数据 props
// 2. 结构不能写死 - 多处结构自定义 【具名插槽】
// (1) 表头支持自定义
// (2) 主体支持自定义
19.路由的基本使用
路由就是路径和组件之间的对应关系
router-view控制组件展示的位置
<router-view></router-view>
// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
// 3. 安装注册 Vue.use(Vue插件)
// 4. 创建路由对象
// 5. 注入到new Vue中,建立关联// 2个核心步骤
// 1. 建组件(views目录),配规则
// 2. 准备导航链接,配置路由出口(匹配的组件展示的位置)
import Find from './views/Find'
import My from './views/My'
import Friend from './views/Friend'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化const router = new VueRouter({// routes 路由规则们// route 一条路由规则 { path: 路径, component: 组件 }routes: [{ path: '/find', component: Find },{ path: '/my', component: My },{ path: '/friend', component: Friend },]
})Vue.config.productionTip = falsenew Vue({render: h => h(App),router
}).$mount('#app')
20.组件存放目录问题
页面组件,放在views里面,页面展示,配合路由使用
复用组件,页面里面的小复用组件,放在components里面,数据展示,用于复用
21.路由的封装分离
import Find from '@/views/Find'
import My from '@/views/My'
import Friend from '@/views/Friend'import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化// 创建了一个路由对象
const router = new VueRouter({// routes 路由规则们// route 一条路由规则 { path: 路径, component: 组件 }routes: [{ path: '/find', component: Find },{ path: '/my', component: My },{ path: '/friend', component: Friend },]
})//导出路由
export default router
22.用router-link代替a标签实现高亮
<template><div><div class="footer_wrap"><router-link to="/find">发现音乐</router-link><router-link to="/my">我的音乐</router-link><router-link to="/friend">朋友</router-link></div><div class="top"><!-- 路由出口 → 匹配的组件所展示的位置 --><router-view></router-view></div></div>
</template>
router-link-active 模糊匹配(更多) to=“/find” => 地址栏 /find /find/one /find/two …
router-link-exact-active 精确匹配 to=“/find” => 地址栏 /find
23.自定义匹配的类型
// link自定义高亮类名linkActiveClass: 'active', // 配置模糊匹配的类名linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
跳转传参
一旦说到声明式导航就是routerlink
- 查询参数传参
- to=“/path?参数名=值”
子组件热门搜索:<router-link to="/search/黑马程序员">黑马程序员</router-link><router-link to="/search/前端培训">前端培训</router-link><router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
- $router.query.参数名
在模板中可以省略this,但是在js中的created中要加this才行
子组件<p>搜索关键字: {{ $route.params.words }} </p><p>搜索结果: </p><script>
export default {name: 'MyFriend',created () {// 在created中,获取路由参数// this.$route.query.参数名 获取查询参数// this.$route.params.参数名 获取动态路由参数console.log(this.$route.params.words);}
}
</script>
- 动态路由传参
- 配置动态路由
const router = new VueRouter({routes: [{ path: '/home', component: Home },{ path: '/search/:words?', component: Search }]
})
- 配置导航链接
to=“/path/参数值”
- 对应页面组件接收传递过来的值
$router.params.参数名
区别:
查询参数传参(适合多个参数)
跳转:to=“/path?参数名=值&参数名2=值”
获取: r o u t e r . q u e r y . 参数名动态路由传参(更优雅,适合单个参数传参)配置动态路由: p a t h : ′ / s e a r c h / : w o r d s ′ 跳转: t o = " / p a t h / 参数值 " 获取: router.query.参数名 动态路由传参(更优雅,适合单个参数传参) 配置动态路由:path: '/search/:words' 跳转:to="/path/参数值" 获取: router.query.参数名动态路由传参(更优雅,适合单个参数传参)配置动态路由:path:′/search/:words′跳转:to="/path/参数值"获取:router.params.参数名
动态路由参数可选符
/search/:words表示必须要传参数,如果不传参数,也希望匹配,加个可选符 ?
24. 路由重定向
重定向:强制转换路径
语法:
{ path: '/', redirect: '/home' },
vue路由-404
作用:当路径找不到匹配时,给个提示页面
位置:配在路由最后
语法:
const router = new VueRouter({routes: [{ path: '/', redirect: '/home' },{ path: '/home', component: Home },{ path: '/search/:words?', component: Search },{ path: '*', component: NotFound }]
})
模式设置
history,以后上线需要服务器端支持
- mode: ‘history’
const router = new VueRouter({// 注意:一旦采用了 history 模式,地址栏就没有 #,需要后台配置访问规则mode: 'history',routes: [{ path: '/', redirect: '/home' },{ path: '/home', component: Home },{ name: 'search', path: '/search/:words?', component: Search },{ path: '*', component: NotFound }]
})
25.编程式导航-基本跳转
- path 路径跳转(简易方便)
methods: {goSearch () {// 1. 通过路径的方式跳转// (1) this.$router.push('路由路径') [简写]this.$router.push('/search')
或者:// (2) this.$router.push({ [完整写法]// path: '路由路径' // })this.$router.push({path: '/search'})}}
- 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
methods: {goSearch () {// 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径// this.$router.push({// name: '路由名'// })this.$router.push({name: 'search'})}}
编程式导航-路由跳转传参
两种跳转方式对于两种传参方式都支持
跳转方式:
- path路径跳转传参(query传参)
- name命名路由跳转传参
path路径跳转传参语法
this.$router.push('路由路径?参数名=参数值')
this.$router.push(`/search?key=${this.inpValue}`)
完整写法this.$router.push({ [完整写法] 更适合传参path: '路由路径',query: {参数名: 参数值,参数名: 参数值}})
动态路由传参
this.$router.push(`/search/${this.inpValue}`)
前提是以修改路由路径
{ name: 'search', path: '/search/:words?', component: Search },
查询参数:
query ?
动态路由:
params /
name命名路由跳转传参语法:
query传参this.$router.push({ [完整写法] 更适合传参name: '路由名字',query: {参数名: 参数值,参数名: 参数值}})
动态路由传参,params传params接收
this.$router.push({name: 'search',params: {words: this.inpValue}})
路径长选path,参数多选query
26.面经基础版
组件缓存keep-alive
把原来已经加载过的数据缓存下来
keep-alive的三个属性(组件名数组):
- include: 只有匹配的组件会被缓存
- exclude: 任何匹配的组件都不会被缓存
- max: 最多可以缓存多少组件实例
一般2与3一起,1与2不同时使用
27. 自定义创建项目
28.ESlint代码规范
JavaScript Standard Style 规范说明:https://standardjs.com/rules-zhcn.html
规则中的一部分:
(1)字符串使用单引号 ‘abc’
(2)无分号 const name = ‘zs’
(3)关键字后加空格 if(name = ‘ls’) {…}
(4)函数名后加空格 function name (arg) {…}
(5)坚持使用全等 = = = 摒弃 = =
解决方案:
- 手动修正
- 自动修正