1. meta&viewport
<meta name="viewport" content="width:device-width, inital-scale:1.0, user-scalable: no, maximum-scale: 1.0, minimum-scale: 1.0"> <!-- 移动端专属标签,PC端不起作用 -->
- width:pixel_value | device-width,pixel_value:直接设置具体数值,大部分安卓手机不支持,但是IOS支持。
- inital-scale:初始缩放比例。以理想视口为参照,例如我们将这个值设置为2,就表示将页面放大为原来的2倍,那么在此时,由于单个元素的尺寸变大,那么窗口的像素容量就会降低,也就是我们能看到的像素数会减少,所以视觉视口会降低为原来的1/2。当这个值为1时,视觉视口内就包含375个CSS像素。
- user-scalable:是否允许缩放(no | yes),默认允许。
- minimum-scale:允许缩放的最小比例
- maximum-scale:允许缩放的最大比例
target-densitydpi:(webkit已经不支持,忽略)- dpi_value:40-700 //每英寸像素点个数
- device-dpi:设备默认像素密度
- high-dpi:高像素密度
- medium-dpi:中等像素密度
- low-dpi:低像素密度。
height,流体布局,也不考虑。
2. v-if、v-show的区别
- v-show:值为false时,渲染但是会解析模板,也就是会编译模板,然后会产生标签结构,只不过不显示罢了。
- v-if:在值为false时,渲染但不解析标签结构,也就是不编译标签结构,也就不会产生标签。
3. Cannot read property 'xxx' of undefined
初始显示异常提示:Cannot read property 'xxx' of undefined,
原因:初始数据是一个空对象,空对象下面的属性均是undefined,当我们从一个undefined上获取属性值时,就会报错。
解决方式:对标签使用 v-if,让标签在没有数据时不进行编译。
4. 文本溢出区域显示 " ... "
overflow: hidden; /* 溢出的文本为隐藏状态 */
text-overflow: ellipsis; /* 当文本溢出时显示... */
white-space: nowrap; /* 文本超过本行不换行 */
5. 页面部分信息,在被点击后全屏显示,点击遮罩部分再消失时的过渡效果
<transition name="fade">
<div></div>
</transition>
<style>
<!-- 产生一个纺锤式的过渡效果,进入时,透明度变化逐渐加快,退出时,透明度变化逐渐变慢 -->
&.fade-enter-active, &.fade-leave-active
tarnsition opacity .5s <!-- 第二个参数专门针对变化的属性,想对哪个属性添加效果,就写哪个属性,如果是多个属性,就写all -->
&.fade-enter, &.fade-leave
opacity 0
<!--
下面是显示时的样式
fade-enter-active:进入过渡的结束状态,元素被插入时就生效,在过渡过程完成后移除。
fade-leave-active:离开过渡的结束状态,元素被删除时生效,离开过渡完成后被删除。
下面是隐藏时的样式
fade-enter:进入过渡的开始状态,元素被插入时生效,只应用一帧后立刻删除。
fade-leave:离开过渡的开始状态,元素被删除时触发,只应用一帧后立刻删除。
-->
</style>
6. Vue 遍历语法 v-for
<li v-for="(good, index) in goods" :key="good.name"></li>
7. 从 state 中引入状态对象
<script>
// 1. 获取到遍历状态的语法mapstate:
import {mapstate} from 'vuex'; // 解构赋值
// 2. 在暴露中书写
export default {
// 3. 遍历 goods,获取该对象,在其他地方需要用到时,可以直接用goods.xxx来调用该对象内的属性
computed: {
...mapstate(['goods'])
}
}
</script>
8. 动态获取数据 :propertyName='dynamicPropertyValue'
<img class="icon" :src="good.icon" v-if="good.icon"></icon>
9. 数据的三大来源:date、props、computed
10. 两栏滑动,右侧滑动左侧跟随的效果的解决思路
- 创建一个数组,专门记录每个 li 顶部的高度以及整个ul的底部高度 tops: [0, 5, 8, 15, 19, 28],这个值在列表第一次显示后进行统计,后面就不在变化,可以放在 data 中
- 创建一个变量 currentIndex 来记录当前化至第几个 li,该数值和下标相对应,也可以放在 data 中
- 滑动过程中,scrollY 的值不断变化
- scrollY 介于 [0, 5) 时,currentIndex = 0,
- scrollY 介于 [5, 8) 时,currentIndex = 1,
- scrollY 介于 [8, 15) 时,currentIndex = 2,
- scrollY 介于 [15, 19) 时,currentIndex = 3,
- scrollY 介于 [19, 28] 时,currentIndex = 4
- 当前所处的位置下标 currentIndex 计算方法:currentIndex = tops.findIndex( (top, index) => {scrollY>=top && scrollY<tops[index+1]} ),这个值由计算得来,因此要放在 computed 中
在向下滑动的过程中,右侧 ul 左上角的高度 scrollY 在不断变化,计算出 scrollY 滑到了哪一个 top 对应的范围内并将 top 值对应所在的下标保存到 currentIndex 中,这个被保存的currentIndex 其实也就对应着我们当前将第几个 li 滑到了ul的最顶端;然后给左侧 li 标签加一个 :class 属性,这个 class 可以动态获取 currentIndex 的值,当 currentIndex 的值也就是当前滑到的 li 的序列和当前的 index 相等时,current 为 yes,也就是当前 li 表现为选中状态。
<div>
<div class="wrapper left">
<ul class="content" ref="leftUl">
<li v-for="(good, index) in goods" :key="index" :class="current: {index === currentIndex}">...</li>
</ul>
<div class="wrapper right">
<ul class="content" ref="rightUl">
<li v-for="(good, index) in goods" :key="good.name" >...</li>
</ul>
<div>
</div>
<script>
// 引入包
import Bscroll from 'better-scroll';
export default{
data(){
scrollY: 0,
tops: []
},
mounted(){},
methods:{
// 初始化滑动
initScroll(){
const leftScroll = new Bscroll(this.$refs.left, {});
const rightScroll = new Bscroll(this.$refs.right, {
probeType: 1;
});
// 绑定滑动监听,监听纵坐标的变化
rightScroll.on('scroll', ({x, y})=>{
this.scrollY = Math.abs(y);
});
// 滑动结束监听,可以监听手动滑动时的结束状态,也可以监听代码滑动和惯性滑动时的结束状态,弥probeType=1即补低频滑动监听时,无法监听惯性滑动的缺点
rightScroll.on('scrollEnd', ({x, y})=>{
this.scrollY = Math.abs(y);
})
},
initTops(){
const tops = [];
let top = 0;
tops.push(top);
// 获取所有ul的所有子元素
const lis = Array.from(this.$refs.rightUl.children);
// 依次遍历子元素,计算出所有子元素的高度,并将其压入tops数组中
lis.forEach(li => {
top += li.clientHight;
top.push(top);
})
this.tops = tops;
}
},
computed:{
const {tops, scrollY} = this;
currentIndex(){
return tops.findIndex( (top, index) => {scrollY>=top && scrollY<tops[index+1]} )
}
},
// 在watch中监视数据的加载情况
watch: {
goods () { //进入到这一步,说明 goods 数据已经更新
this.$nextTick(()=>{
// 将创建Bscroll的操作放在这里,就可以保证在数据完全加载之后才去创建Bscroll
this.initScroll();
})
}
}
}
</script>
proboType有四个值:
- 0:也是默认值,为0时并不会为元素派发事件,也就是说我们虽然给元素绑定了监听,但是并不能起效果,
- 1:低频出发绑定的监听事件,在惯性时不会触发,
- 2:高频出发,惯性不触发,
- 3:高频出发,惯性触发。
11. 通过 const 在 methods 中的方法中定义的属性,在另一个方法内是无法访问到的,我们可以在方法名前加“this.”,将这个方法变为一个全局的属性,这样不论在何处都能访问到这个属性。
12. 如何实现点击左侧分类项,让右侧列表滑至对应位置
给左侧的每个 li 项添加点击监听(这个时候必须先在 BScroll 构造函数中添加配置对象,将 click 设置为 true,这个时候才能让 BScroll 的点击监听函数起作用),并将当前的 index 放入监听回调的参数中,计算这个 index 对应的 tops 中的值,然后通过 scrollTo 方法根据对应 tops 相应的值修改右侧列表的位置。
这个时候由于对右侧列表添加的有滑动监听,右侧列表滑动了,currentIndex 值发生了改变,所以左侧列表每个 li 的 current 也发生了一系列的变化。最终被点击的 li 的颜色就被修改了。
但是由于此时左侧li是根据右侧的变化而变化的,右侧的列表设定的有滑动延迟时,左侧li的颜色变化会非常迟钝,因此我们需要更改左侧的变化逻辑。
我们只需要更改 scrollY 的值,让它等于从 tops 中选出来的数字即可,这个时候会立即计算出当前的 currentIndex,因此左侧的 li 就会在一瞬间发生变化。
13. BScroll 常见问题
我们在 computed 中去计算所有 li 的高度,然后通过 leftScroll 的scrollToElement 方法滑至对应的 li,会报错!
我们添加这个方法后,初始化的时候会直接执行 computed 内的操作,但是包含对 leftScroll、rightScroll 的初始化操作的 nextTick 却在computed之后,因为前者要等到数据完全加载之后才能执行。所以我们在还没有初始化生成 leftScroll 的时候,就去调用这个对象的方法,自然会出错。
我们只需要在执行 scrollToElement 前加一个判断,判断 leftScroll 是否已经存在,只有在 leftScroll 已经存在之后才能去执行 scrollToElement 函数。
14. 如何注册全局组件?
入口为 main.js,我们只需要在这个文件中将我们写好的组件文件声明为一个组件即可,方法:
import Vue from 'vue'
import CartControlfrom './components/CartControl/CartControl.vue'
Vue.compontent('CartControl', CartControl);
15. 如何在接收参数时限制变量类型?
props: {
count: Number // 直接在变量的后面添加数据类型即可对数据进行限制
}
16. 父组件向子组件传递参数的方式
<!-- 父组件 -->
<CartControl :food="food" /> <!-- 此时就可以将food传递给子组件 -->
<!-- 子组件 -->
<script>
export default{
props: {
food: Object <!-- 这样在子组件中就接受到了父组件传来的参数 -->
}
}
</script>
如果此时在父组件中并没有 food 属性,但是父组件通过遍历的方式从 foods 中获取到了 food 属性,如何将 food 属性传递给子组件?
我们在父组件中,可以通过绑定一些事件,将 food 属性传递给一个函数,然后在这个函数中将这个属性保存到 data 中去。在这之后,才能将food从父组件传递到子组件。
<div @click="addFoodtoData(food)"></div >
<script>
export default {
data() {
return{
food: {},
}
},
methods: {
addFoodtoData(food){
this.food = food
}
}
}
</script>
17. Vue 中绑定点击监听的方式:@click=" "
18. 对一个定义但尚未初始化的变量执行++操作会有什么后果? 提示 NaN
var a;
console.log(++a); // NaN
19. props 对象中存放的是从父组件传递过来的参数,而 data 函数中存放的是本组件创建的供自己使用的变量。
20. 规范系列:
数据是谁的,更新的操作就定义给谁。
例如我们在 Goods 组件通过 ...map({foods: state=>state.shop.goods}) 获取到了所有 foods 的集合,尽管在别的组件会遍历 foods 来获取每个 food,但是这才是获取 foods 的起点,所以我们操作 food 的方法也应该在 Goods 调用(操作状态了,所以方法定义是在 action 中)
21. 如何在 Vue 组件中调用 action 中的方法?dispatch
// CartControl 组件
this.$store,dispatch('updateFoodCount', {isAdd, food: this.food}) // 这个时候在 Vue 的组件中即可调用 action 中的 updateFoodCount 方法
// action.js
updateFoodCount({commit}, {isAdd, food}){
if(isAdd){
commit(ADD_FOOD_COUNT, {food})
}else{
commit(REDUCE_FOOD_COUNT, {food})
}
}
22. 在 mutations.js 文件中给状态添加属性的方法:
我们不能直接用 food.count 的方法来添加属性,因为 Vue 是响应式的,这样添加的属性没有数据绑定效果。
正确的方式:
// 在mutation文件中:
import Vue from 'vue'
Vue.set(food, 'count', 1)
23. 如何注册局部组件?
<!-- 在需要这个组件的地方进行局部注册 -->
<script>
import Food from '@/Food/Food.vue'
components:{
Food
}
</script>
24. 如何在父组件中得到子组件标签对象(组件对象)?ref
<!-- Food是子组件,整个代码处在父组件中 -->
<Food :food="food" ref="food" />
<script>
export default{
data:{
food: {}
}
methods:{
// 这个方法在父组件的别的地方会用到
showFood(){
// 更新数据
this.food = food;
// this.$refs.food 就可以获取到父组件中的子组件Food,这就是在父组件中获取子组件的一般性思路
this.$refs.food.toggleShow();
}
}
}
</script>
25. 购物车组件核心思路
错误思路:
当一个数据被放到 getter 中进行计算,数据一旦发生变化就会直接触发 getter。这时我们设想将可以在 getter 中创建一个数组,存放 count>0 的 food,因为在这里,我们的 food 信息包括count 只要发生变化就可以被检测到。由于 food 在 foods 中,而 foods 又在 goods 中,那么也就说 goods 一旦发生任何变化,那么getter就会被检测到。这就会导致我们对goods的任何操作都会触发getter对象。所以这个方法并不适用。
正确思路:
我们可以将 cartFoods 保存为一个状态。在shop.js 中,通过操作 mutation 来操作 cartFoods,mutation 中在增加和减少 food 数量 count 时,有判断 food 是否为0的逻辑,我们只需要在这个时候来选择是添加这个 food 进 cartFoods 或者从 cartFoods 中删除这个 food。
// shop.js
import Vue from 'vue'
import {
ADD_FOOD_COUNT,
REDUCE_FOOD_COUNT
} from '../mutation-types'
export default{
state: {
cartFoods: []
},
mutations: {
[ADD_FOOD_COUNT](state, {food}){
if(food.count){
food.count++;
}else{
Vue.set(food, 'count', 1)
// 进入到这个else表示food.count不存在,这个时候不仅需要我们给food创建count属性,还需要我们在这个时候将这个food添加进cartFood中
state.cartFoods.push(food)
}
},
[REDUCE_FOOD_COUNT](state, {food}){
if(food.count>0){
food.count--;
// 有可能出现count从1减为0的情况,这个时候购物车也就不需要这个food了,因此我们需要在这个时候将其删除
if(food.count == 0){
state.cartFoods.splice(state.cartFoods.indexOf(food), 1);
}
}
}
}
}
这个时候,我们的状态 购物车内的食物 cartFoods 以及向其中添加和删除食物的操作已经定义好了,然后只需要在购物车组件中获取到这个状态,然后将其遍历显示到购物车组件对应的位置上。
<!-- ShopCart.vue -->
<template>...</template>
<script>
import {...mapState} from 'vuex'
export default{
...mapState({
cartFoods: state => state.shop.cartFoods
})
}
</script>
这个时候我们已经可以在购物车组件获取到所有的被加入购物车的食物,然后我们需要计算出食品总数量、总价格。
这两个属性不应该在购物车组件中被定义和计算,因为它所依赖的数据并没有被保存在这里,而是保存在了 shop.js 中。
这两个变量我们只需要直接获取它的值,因此我们可以将其放入shop.js 的 getter 中,这里面的函数的计算结果可以直接在外部通过 ...getter 来调用获取结果。
<!-- shop.js -->
...
<script>
export default{
stata: {
cartFoods: []
},
getter: {
totalCount (state){
return state.cartFoods.reduce((preCount, food)=> preCount+food.count, 0)
},
totalPrice (state){
return state.cartFoods.reduce((prePrice, food)=> prePrice+food.price*food.count, 0)
}
}
}
</script>
这个时候我们的获取总数量和总价格的方法就定义好了,然后我们需要在 ShopCart 组件中对其进行引用:
<!-- ShopCart.vue -->
<script>
import {...getter} from 'vuex'
export default{
...getter(['foodPrice', 'foodCount'])
}
</script>
26. 购物车的Bscroll问题
<!-- ShopCart.vue -->
<script>
computed: {
if(this.isShow){
this.$nextTick(()=>{
new BScroll(this.$refs.foods,{
click: true;
})
})
}
}
</script>
1. 上述代码表示:当 isShow 为 true 时,点击后会异步创建一个滑动 BScroll 对象,当 cartFoods 内的元素数量大于0时 isShow 的值才是 true。在接下来,我们每点一次购物车,让购物车列表显示一次,都会创建一个 BScroll 对象。这时会对数据产生很大的影响。
实现思路:单例模式。
2. 其次,我们还发现一个问题,就是当我们向购物车中添加的对象还不多时,这时点开购物车,还不足以滑动,于是 BScroll 还不会形成滑动,然后我们向其中添加商品,超过了单页的高度,可是还是不会滑动,因为 BScroll 并不会在这个时候智能地去做判断,是否已经超过了单页的高度,所以还是不会滑动,直到我们随意滑动一次,下次才能正常滑动。
解决方法:this.scroll.refresh()。也就是当我们发现已经存在BScroll对象时,点开购物车列表让它强制刷新一次,去判断一下是否已经超过了一页的高度,若超过了就需要产生滑动效果。
<!-- ShopCart.vue -->
<script>
computed: {
if(this.isShow){
this.$nextTick(()=>{
// 增加限制条件,仅仅允许在没有BScroll对象时才允许新建一个BScroll对象
if(!this.scroll){
this.scroll = new BScroll(this.$refs.foods,{
click: true;
})
}else{
// 当存在BScroll对象时,强制刷新一次
this.scroll.refresh();
}
})
}
}
</script>
3. 我们对ul父标签div添加BScroll对象。这个显示隐藏只能有v-show来控制,而不能由v-if来控制,因为Bscroll会在ul上添加一些用来滑动的属性,v-if一旦为false,就会将div彻底删除掉,再次点击购物车时又会创建,但是新建div下的的ul已经没有了提供滑动的属性,但是scroll对象还依然存在,因此就会导致消失后再显示却无法滑动的问题,所以,我们不能用v-if来控制显示隐藏,而应该用不会删除结构的v-show。
27. 节流相关问题
updateFoodCount: throttle(function(isAdd){
console.log("add");
}, 1000)
这个时候,当我们点击相应区域,在点击时和1秒后会各响应一次,也就是会打印两次add,我们需要把1秒后的响应给关掉,可以给throttle添加一个配置:{trailing: false}即可
updateFoodCount: throttle(function(isAdd){
console.log("add");
}, 1000, {trailing: false})
28. params参数路由,使用Mock.js来模拟:根据参数动态响应对应商家信息
Mock.mock(/^\/api\/shop\/d+$/, (options)=>{ // /api/shop/3
const id = options.url.substring(10); // 可以看到从第11位开始,即是数字对应的部分,此时对应的下标就是10
const shop = shops.find(shop=>shop.id == id);
return Mock.mock({code: 0, data: shop || shops[0]}) // 此处还做了兼容性处理,当我们找不到对应id的店铺时,就返回第一个店铺
})
已经在Mock中设置好了对不同参数路由的响应。此时中间环节已经完成。此时已经可以根据请求的不同的路由,根据其id在所有的shops中选择对应的店铺响应回去。
接下来我们要完成起始环节,也就是发送动态路由,和末尾环节,也就是根据Mock传递过来的响应获取到这个店铺的id。将params设置为一个参数,然后将它保存至路由router中,并将其传递给子组件
<!-- MSite.vue -->
<li v-for="shop in shops" :key="shop.id" @click="$router.push=(`/shop/${shop.id}`)"> <!-- 动态获取店铺的id -->
<!-- 然后在router.js中配置对应路由 -->
export default{
path: '/shop/:id', // 让id变成一个参数,存储在$router.params中
props: true // 将所有的 params 参数转换为标签属性传递给子路由
}
<!-- shop.vue -->
<script>
export default {
props: ['id'],
mounted(){
// const id = this.$router.params.id // 在子路由中通过params获取到当前店铺的id
// 当我们配置过props: true,就将id变为了父组件传递给子组件的参数,存储在props中,我们可以直接通过props来获取
const id = this.id;
}
}
</script>
这时我们点击店铺的不同,就会发起不同的请求。
29. 带有动态参数params路由的子路由配置方式
常规路由的子路由的配置方式:
export default [
{
path: '/shop',
name: 'shop',
component: Shop,
children: [
{
path: '/shop/goods',
component: Goods
},
{
path: '/shop/info',
component: Info
},
{
path: '/shop/ratings',
component: Ratings
},
{
path: '',
redirect: '/shop/goods'
}
]
}
]
带有参数的路由的子路由的配置方式:
export default [
{
path: '/shop/:id',
name: 'shop',
props: true, // 将所有params属性转换为标签属性传递给子路由组件
component: Shop,
children: [
{
path: 'goods',
component: Goods
},
{
path: 'info',
component: Info
},
{
path: 'ratings',
component: Ratings
},
{
path: '',
redirect: 'goods'
}
]
}
]
这样配置完后,在父组件中可以这样书写:
<!-- 传统写法 -->
<li v-for="shop in shops" :key="shop.id" @click="$router.push=(`/shop/${shop.id}`)">
<!-- 配置写法:这种方式会使自动跳转失效 -->
<li v-for="shop in shops" :key="shop.id"
@click="$router.push(
name: 'shop',
params: {
id: shop.id
}
)">
但是,使用配置对象的写法,会出现一个问题,配置对象会使得自动跳转失效。而直接路径的方式就不会出现这种问题。
30. 如何更加高效地实现重置一个状态?
1. 我们有对应的修改状态的方法,这个方法在数据接受后,将数据存储到状态中时进行使用。
// shop.js
export default{
mutations:{
[RECEIVE_SHOP](state, {shop={}, cartFoods=[]}){
state.shop = shop,
state.caetFoods = cartFoods
}
}
}
2. 我们对参数 shop、cartFoods 指定形参默认值,这时,当我们给 RECEIVE_SHOP 函数传递一个空的参数时,形参默认值就会起效果,shop、cartFoods 就会被赋予空值、重置数据。
// shop.js
export default{
actions: {
async getShop({commit, state}, id){
// 如果请求的店铺和原来店铺的id相同,就不需要发起请求
if(id == state.shop.id){
return;
}
// 如果不同,就需要先重置状态中的店铺的信息
commit(RECEIVE_SHOP, {})
// 请求id对应店铺
const result = await reqShop(id);
// code = 0表示请求成功
if(result.code === 0){
commit(RECEIVE_SHOP, {result.data});
}
}
}
}
31. 切换组件时与BScroll相关的问题
前面已经说了,我们会通过watch对Goods数据进行一个实时的检测,当数据发生变化时,会对BScroll进行一个初始化,以保证最新的数据可以正确地适应BScroll。但是它会产生一个问题,就是它只会在数据变化时起作用,如果我们只是简单的切换组件,当再次切换回来的时候,BScroll就会失效,这个时候,就需要mounted来实现对BScroll的初始化。检测数据是否已经存在,只要数据已经存在,那么我们就可以初始化BScroll。
32. 缓存数据
缓存数据的核心就是将数据保存至浏览器或本地,下面以保存至浏览器即 sessionStorage 为例:
我们要在 Shop.vue 中将购物中的信息以及商家的部分信息保存至 sessionStorage,首先,我们得先获取到 shop 以及 cartFoods 这两个状态:这两个状态都保存在 shop.js 这个模块中,是里面的状态,我们先获取到 shop 这个模块
<!-- Shop.vue -->
<script>
import {...mapState} from 'vuex'
export default{
computed: {
...mapState({
shop: state.shop // 这时获取的是 {shop:{} 和cartFoods:[]}
})
}
}
</script>
然后我们需要解构出来这两个属性,这两个属性都是在销毁前才需要的,也就是缓存的时候才需要,所以我们只需要在 beforeDestory 这个状态中提取:
beforeDestory(){
const {shop:{id}, cartFoods} = shop;
}
然后将其应用到保存至 sessionStorage 的方法(saveCartFoods)中
// index.js
export saveCartFoods(shopId, cartFoods){
const foodCount = cartFoods.reduce((pre, cartFood)=>{
return pre[cartFood.id] = cartFood.count;
}, {});
sessionStorage.setItem(shopId+"_key", JSON.stringify(foodCount))
}
33. 从缓存中读取数据,并映射成购物车
// index.js
import from 'vue'
export getCartFoods(shop){
const cartFoods = [];
const foodsCount = JSON.parse(sessionStorage.getItem(shop.id+"_key")) || {};
shop.goods.forEach(good=>{
good.foods.forEach(food=>{
const count = foodsCount[good.id]
if(count > 0){
Vue.set(food, 'count', count);
cartFoods.push(good);
}
})
})
}
34. Vue的生命周期方法在刷新时,并不会执行。例如beforeDestory,但是uncload可以。
35. vue-router 的params参数:
注册路由:/shop/:id
传递参数:router.push('/shop/1'); / router.push({path: '/shop/1'}) / router.push({name: 'shop', params: {}}); //会出现一些问题,重定向失效。
如何将params参数转换为标签属性传递给子标签:给在路由页中添加props: true
带参数的路由的子路由:在children中直接写子路由名字,而不需要跟上父路由,例如父路由为:'/shop/:id',子路由直接写:'goods'。若没有params参数则应该写:'/goods'
36. keep-aline: 在它管理的路由间进行切换,路由组件不会死亡。
37. v-mode & :name="name" 二者的区别
- v-mode:可以实现双向数据绑定,自动将数据导入date
- :name:只能实现单向数据绑定,仅能读取数据
:是单纯从data中读取对应的值,数据改变的方向仅仅是从data流向页面,当我们在页面改变这个值的时候,并不会影响到data中对应变量存储的值;而v-model则是在:的基础上,添加了:让数据从页面再流回data的功能。也就是说,再使用v-model的情况下,我们在页面更改数据,也会影响到data中存储的值。
v-mode的实现实质上分为了两步:
- 第一步::value="name",将输入的值传递给子组件。这个时候,会将我们的输入通过value传递给系统定义好的标签input中,input实质上也是个组件,只不过是系统定义好的。所以其实就是相当于通过:value="name" 将我们的输入传递给了input子组件。
- 在子组件中,会将从父组件传递过来的值保存至状态,然后通过事件分发的方式,将这个值从子组件分发出去。
- 第二步:通过$event.target.value,获取系统定义组件以事件分发的方式传递过来的值。异步事件,其实@input地一切都包裹在一个函数中,这个函数的参数就是$event,子组件传递给父组件的是一些数据的集合,存储在$event对象中。所以我们如果想要真正的值,还需要一层一层地来获取。
v-mode底层原理:单向数据流+借助监听更新状态数据
单向数据流:作用跟简单,就是获取状态中的数据用于显示到界面
借助监听更新状态数据:在监听事件中通过$event.target.value实时地获取输入的值,在将这个值赋给对应的data数据
<!-- 原生标签 -->
<input v-mode="name" type="text"> <!-- 实现了双向数据绑定 -->
<!-- 相当于 -->
<input :value="name" @input="name="$event.target.value">
<!--
name="$event.target.value实质上是这样的:(内部还用到了bind)
name = function($event){
return $event.target.value;
}
-->
可以看到,我们的值是在书写v-mode的标签中,实质是通过:name传递给子组件,然后子组件将其存入状态,再通过事件分发的方式,将值分发出去。在标签中,在触发点击监听时,通过$event.target.value获取到这个值,并显示出来
38. v-mode如何用在自定义的组件标签上?
v-model对于一个自定义的标签来说,本质上,还是做了两件事,单向数据流+绑定自定义的监听事件。
原生的标签可以自动将输入的值赋给对应的数据,而自定义的标签需要我们手动获取输入的值,作为input的value,再将这个分发给input事件,然后再赋给对应的对应的数据。
父组件将name的值通过:value传递给子组件,在子组件中需要通过props接收到这个参数,然后再将其传递给input标签。
<!-- 父组件 -->
<MyInput v-model="name">
<!-- 相当于,因为这是一个自定义的组件标签,所以这里面的input其实是一个自定义的事件 -->
<MyInput :value="name" @input="name=$event.target.value">
<!-- 子组件:MyInput -->
<template>
<div>
<input :value="name" @input="$emit('input', $event.target.value)">
</div>
</template>
<script>
export default{
props: [name];
}
</script>
39. 组件并不是任何属性都是响应式的
只有这个属性在data中,才是响应式的。下面的name就不是。
export default{
data(){
this.name = "Jack"; // 这里面的name属性被保存到了组件对象身上,但是并不在组件的data中,因此它并不是响应式的。
return {
a = 1,
b = 2
}
}
}
40. Root跟标签由new Vue()来决定,每new一次,就会产生一个Root根标签
41. $mount('#app')的实质是替换,而不是插入
也就是说app对应的标签会被App替换掉,因此我们并不能再找到app,所以不能给它加样式,只能找到App。
new Vue({
render h => h(App),
store,
i18n,
router
}).$mount('#app');
42. 组件的data不能是一个对象,只能是函数
如果是对象,当一个组件被多次使用时,那么就会产生多个组件公共一个data对象,实质上就相当于是对data对象的浅克隆,单纯的复制了地址。
如果我们使用的是函数就不一样了,每次调用都返回一个data对象,那么每个组件中的data对象都是不一样的。