vue.js 核心知识点六
目录
- 6.1 父组件异步获取动态数据传递给子组件
- 6.2 vue-cli中自定义指令的使用
- 6.3 vue弹窗后如何禁止滚动条滚动?
- 6.4 vue提供的几种脚手架模板
- 6.1 父组件异步获取动态数据传递给子组件
模拟异步
父组件
<template>
<div>
父组件
<child :child-data="asyncData" ></child>
</div>
</template>
<script>
import child from './child'
export default {
data(){
return {
asyncData:''
}
},
components: {
child
},
created() {
// setTimeout模拟异步数据
setTimeout(() => {
this.asyncData = 'async data'
this.asyncObject = {'items': [1, 2, 3]} 第二种情况
console.log('parent finish')
}, 1000)
}
}
</script>
子组件
<template>
<div>
子组件{{childData}}
第二种
子组件<p>{{childObject.items[0]}}</p>
这里很常见的一个问题,就是{{childObject}}可以获取且没有报错,
但是{{childObject.items[0]}}不行,往往有个疑问为什么前面获取到值,后面获取不到呢?
</div>
</template>
<script>
export default {
props: ['childData'],
data: () => ({
}),
created () {
console.log(this.childData) //空值
console.log(this.childObject) // 空值
},
methods: {
}
}
</script>
// 首先传过来的是空,然后在异步刷新值,也开始时候childObject.items[0]等同于''.item[0]这样的操作,
所以就会报下面的错
vue.esm.js?8910:434 [Vue warn]: Error in render function:
"TypeError: Cannot read property '0' of undefined"
问题描述
父组件获取异步数据,并传递给子组件,直接显示没有问题,若对数据进行处理,则拿到的数据都是父组件初始值。
原因
父组件 获取异步数据 还没等到数据返回 子组件 created已经执行完毕
父子组件的生命周期
image
解决
- 方法一 使用v-if可以解决报错问题,和created为空问题
父组件
<child :child-data="asyncData" v-if="asyncData"></child>
当asyncData有值得时候,在加载子组件
- 方法二 子组件使用watch来监听父组件改变的prop,使用methods来代替created
子组件
watch:{
childData(val){
this.flGoods = val;
console.log('子组件 数据处理',val)
this.updata()
}
},
methods: {
updata () { // 既然created只会执行一次,但是又想监听改变的值做其他事情的话,只能搬到这里咯
console.log(this.test)// 1
}
}
- 方法三:在父组件里用Promise方法异步执行数据的赋值:
new Promise((resolve,reject) => {
if (res.status === 200){
resolve(res);
}
}).then((res) => {
this.category = res.data.data.category;
this.adBar = res.data.data.advertesPicture.PICTURE_ADDRESS;
this.bannerSwipePics = res.data.data.slides
this.recommendGoods = res.data.data.recommend;
// 也可异步获取再传给子组件 Promise
this.floorSeafood = res.data.data.floor1;
this.floorBeverage = res.data.data.floor2;
this.floorFruits = res.data.data.floor3;
console.log(this.floorFruits);
this._initScroll();
})
}).catch(err => {
console.log(err);
});
这样也是可以的,异步获取数据导致的报错的情况会在各个场景出现,比如根据数据渲染dom,而对dom有js操作的时候,会因为还没渲染出来而找不到响应的dom元素报错,这里可以用vue提供的$nextTick()函数,或者手动开个setTimeout定时器,延迟获取;使用better-scroll的时候因为dom没有渲染出来而无法获取滚动元素的高度,导致无法滚动,同样可以用vue提供的这个函数,等dom渲染完了后再初始化滚动。
- 方法四 :子组件watch computed data 相结合
parent.vue
<template>
<div>
父组件
<child :child-object="asyncObject"></child>
</div>
</template>
<script>
import child from './child'
export default {
data: () => ({
asyncObject: undefined
}),
components: {
child
},
created () {
},
mounted () {
// setTimeout模拟异步数据
setTimeout(() => {
this.asyncObject = {'items': [1, 2, 3]}
console.log('parent finish')
}, 2000)
}
}
</script>
child.vue
<template>
<div>
<p>{{test}}</p>
</div>
</template>
<script>
export default {
props: ['childObject'],
data: () => ({
test: ''
}),
watch: {
'childObject.items': function (n, o) {
this._test = n[0]
}
},
computed: {
_test: {
set (value) {
this.update()
this.test = value
},
get () {
return this.test
}
}
},
methods: {
update () {
console.log(this.childObject) // {items: [1,2,3]}
}
}
}
</script>
- 方法五 :使用emit,on,bus相结合
parent.vue
<template>
<div>
父组件
<child></child>
</div>
</template>
<script>
import child from './child'
export default {
data: () => ({
}),
components: {
child
},
mounted () {
// setTimeout模拟异步数据
setTimeout(() => {
// 触发子组件,并且传递数据过去
this.$bus.emit('triggerChild', {'items': [1, 2, 3]})
console.log('parent finish')
}, 2000)
}
}
</script>
child.vue
<template>
<div>
子组件
<p>{{test}}</p>
</div>
</template>
<script>
export default {
props: ['childObject'],
data: () => ({
test: ''
}),
created () {
// 绑定
this.$bus.on('triggerChild', (parmas) => {
this.test = parmas.items[0] // 1
this.updata()
})
},
methods: {
updata () {
console.log(this.test) // 1
}
}
}
</script>
这里使用了bus这个库,parent.vue和child.vue必须公用一个事件总线(也就是要引入同一个js,这个js定义了一个类似let bus = new Vue()的东西供这两个组件连接),才能相互触发
- 方法六:使用prop default来解决{{childObject.items[0]}}
parent.vue
<template>
<div>
父组件
<child :child-object="asyncObject"></child>
</div>
</template>
<script>
import child from './child'
export default {
data: () => ({
asyncObject: undefined // 这里使用null反而报0的错
}),
components: {
child
},
created () {
},
mounted () {
// setTimeout模拟异步数据
setTimeout(() => {
this.asyncObject = {'items': [1, 2, 3]}
console.log('parent finish')
}, 2000)
}
}
</script>
child.vue
<template>
<div>
子组件<!--1-->
<p>{{childObject.items[0]}}</p>
</div>
</template>
<script>
export default {
props: {
childObject: {
type: Object,
default () {
return {
items: ''
}
}
}
},
data: () => ({
}),
created () {
console.log(this.childObject) // {item: ''}
}
}
</script>
- 其他方法
将数据存到store,子组件监听数据变化(watch/computed)
大概逻辑:使用vuex全局状态管理,其实简单,
利用vuex的辅助函数(mapState,mapMutations)
mapState是将state里面的数据映射到计算中(computed),
mapMutations也是类似,把vuex中mutations的方法映射到组件里面,
就可以在组件里面直接使用方法了,
在vuex中使用异步(actions)去掉用接口,
然后在接口成功的函数里面取触发同步(mutations)里面的方法,
把得到数据传给mutations里面的方法里并且给state里面的属性赋值,
然后就可以在子组件中使用computed计算中去获取数据并且渲染到页面上,
其实说的有点绕( -_-"),但是看代码就明白了 。
vuex / index.js
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
Vue.use(Vuex)
export default new Vuex.Store({
//定义初始数据
state: {
title: '',
list: [],
isShow: false
},
//同步的方法
mutations: {
//向state 里面设置数据
changeListMutation(state, list) {
state.list = list
},
//在list.vue里面点击下拉选项的时候触发 给state.title赋值
changeTitleMutation(state, title) {
state.title = title
},
//selectinput.vue里面点击input的时候触发 给state.isShow赋值
toggleShow(state, isShow) {
state.isShow = isShow
}
},
//异步的方法
actions: {
//在list.vue里面created生命周期里面触发
getListAction({ commit }) {
axios.get('/mock/5afd9dc0c088691e06a6ab45/example/dataList')
.then((res) => {
commit('changeListMutation', res.data) //调用mutations下面的changeListMutation方法并且传值过去
})
.catch((error) => {
console.log(error)
})
}
}
})
// 触发异步里面的方法是用 this.$store.dispatch('这里是方法名')
// 触发同步里面的方法是用 this.$store.commit('这里是方法名')
父组件 select.vue
<template>
<div class="select">
<div class="wrap">
<selectInput></selectInput>
<list></list>
</div>
</div>
</template>
<script>
// 引入子组件
import selectInput from '@/components/selectInput'
import list from '@/components/list'
export default {
components:{ //加载子组件
selectInput,
list
},
}
</script>
<style>
.select{
background:#4a56fe;
width: 400px;
margin: 100px auto 0;
padding: 40px;
border-radius: 10px;
}
.wrap{
background: #e3e5fe;
border-radius: 10px;
padding: 40px;
}
ul{
list-style: none;
}
</style>
子组件 list.vue
<template>
<div class="list">
<ul>
<li v-for="(item,index) in list" :key="index" v-show="initShow" @click="changeTitle(item.title)">{{item.title}}</li>
</ul>
</div>
</template>
<script>
import {mapState,mapMutations} from 'vuex' // 将vuex中的state数据和mutations中的方法映射到组件中
export default {
//vue 生命周期(created)在实例创建之后,在数据初始化之前被调用
created(){
this.$store.dispatch('getListAction') //调用vuex 中的 getListAction异步方法
},
//计算state数据
computed:{
...mapState({
list:'list',
initShow:'isShow'
})
},
methods:{
changeTitle(title){
this.$store.commit('changeTitleMutation',title)
this.$store.commit('toggleShow',!this.initShow)
}
}
}
</script>
<style>
.list{
padding: 10px 0;
text-align: center;
}
li{
line-height: 30px;
height: 30px;
border-radius: 15px;
cursor: pointer;
color:#535353;
}
li:hover{
background: #ff705b;
color: #fff;
}
</style>`
子组件selectInput.vue
<template>
<div class="inputBox">
<input type="text" readonly :value="getTitle" @click="toggleShow" placeholder="你喜欢什么">
</div>
</template>
<script>
export default {
computed:{
// 获取vuex中的state数据并赋值绑定到 value上面 computed 里面的方法名其实就是相当于 data里面的数据,可以用this.getTitle 去访问
getTitle(){
return this.$store.state.title
},
// 初始化控制下拉选项显示隐藏的状态,如果isShow是false 则不限是下拉菜单,默认是false
initShow(){
return this.$store.state.isShow
}
},
methods:{
//点击input的时候调用该方法,这个方法去触发mutations下面的toggleShow,去改变isShow的状态,默认是isShow等于false, 然后在点击的时候去改变isShow 等于true , !this.initShow就是true,如果是true的话,下拉选项才能出来,并将改变过后的值传给toggleShow方法,去给vuex/store.js 里面的state.isShow赋值。
toggleShow(){
this.$store.commit('toggleShow',!this.initShow)
}
}
}
</script>
<style>
input{
outline: none;
width: 100%;
height: 40px;
line-height: 40px;
border-radius: 10px;
border: 1px solid #d3d3d3;
text-indent: 20px;
color: #535353;
}
</style>
参考 https://www.jb51.net/article/117447.htm
- 6.2 vue-cli中自定义指令的使用
vue中除了内置的指令(v-show,v-model)还允许我们自定义指令
想要创建自定义指令,就要注册指令(以输入框获取焦点为例) 注意:autofocus 在移动版 Safari 上不工作
一、注册全局指令:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el,binding) {
// 当前指令绑定的dom元素
//console.log(el);
// 指令传入的参数、修饰符、值 v-指令名称:参数.修饰符=值
// console.log(binding)
// 聚焦元素
el.focus()
}
})
二、注册局部指令: 组件中也接受一个 directives 的选项
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
使用也很简单:直接在元素上面使用v-focus即可:
<input type="text" v-focus/>
下面再举一个自定义指令的小例子:拖拽
Vue.directive('drag', {
// 当指令绑定到元素上的时候执行
bind(el, binding) {
// console.log('bind');
// 当前指令绑定的dom元素
//console.log(el);
// 指令传入的参数、修饰符、值 v-指令名称:参数.修饰符=值
// console.log(binding)
el.onmousedown = function(e) {
var e = e||event;
let disX = e.clientX - el.offsetLeft;
let disY = e.clientY - el.offsetTop;
document.onmousemove = function(e) {
var e = e||event;
let L = e.clientX - disX;
let T = e.clientY - disY;
if (binding.modifiers.limit) {
if (L < 0) {
L = 0;
}
}
el.style.left = L + 'px';
el.style.top = T + 'px';
};
document.onmouseup = function() {
document.onmousemove = null;
};
return false;
}
}
});
使用也很简单,只用在元素上添加v-drag或者v-drag.limit
<div id="div1" v-drag.limit></div>
<div id="div2" v-drag></div>
- 6.3vue弹窗后如何禁止滚动条滚动?
/***滑动限制***/
stop(){
var mo=function(e){e.preventDefault();};
document.body.style.overflow='hidden';
document.addEventListener("touchmove",mo,false);//禁止页面滑动
},
/***取消滑动限制***/
move(){
var mo=function(e){e.preventDefault();};
document.body.style.overflow='';//出现滚动条
document.removeEventListener("touchmove",mo,false);
}
function toggleBody(isPin){
if(isPin){
document.body.style.height = '100vh'
document.body.style['overflow-y'] = 'hidden'
}
else{
document.body.style.height = 'unset'
document.body.style['overflow-y'] = 'auto'
}
}
toggleBody(1) //在跳出弹窗的时候
toggleBody(0) //弹窗消失的时候
超长的页面怎么办呢
上面直接限制body固然有效,但如果一个页面很长很长,超出了100vh,而我正好滚到中间时弹出弹窗。此时若直接限制body的overflow: hidden则会让页面一下弹到顶部,显然不是好的做法。那么,又该怎么做呢?
对移动端,可以引入touch-action,限制为none,在弹窗消失时再变回auto。但ios的safari上不支持该属性(可以去caniuse上查查,起码2018.11的时候还不支持)。如果我们的app在ios上用的是safari内核,就起不到效果了。
这时候,就需要结合event.preventDefault属性来用了。注意在绑定addEventListener的时候,需要多传一个options,强调这个事件不是passive的,否则谷歌等新版浏览器会报错。同时最好也指定capture: true,这样可以早点禁止该事件。
报错是Unable to preventDefault inside passive event listener due to target being treated as passive.。这是因为谷歌从chrome51之后引入的新优化。事实上,谷歌建议一般情况下,将 passive 标志添加到每个没有调用 preventDefault() 的 wheel、mousewheel、touchstart 和 touchmove 事件侦听器。但是,对于这种禁止了默认事件的eventListener,在这种情况下,反而是要强调它不是消极监听的。因为滚动都不能滚了,无所谓什么优化了。
代码如下(vue版本的):
watch: {
show(v) {
this.toggleContainerTouchAction(v)
if (v) {
document.body.addEventListener('touchmove', this.stopTouch, { passive: false, capture: true })
} else {
document.body.removeEventListener('touchmove', this.stopTouch, { capture: true })
}
},
},
methods: {
toggleContainerTouchAction(v) {
const container = document.querySelector('.container')
if (!container) {
return
}
container.style['touch-action'] = v ? 'none' : 'auto'
},
stopTouch(e) {
e.preventDefault()
},
- 6.4 vue提供的几种脚手架模板
vue-cli 的脚手架项目模板有browserify 和 webpack , 现在自己在用的是webpack , 官网给出了两个模板: webpack-simple 和 webpack 两种。两种的区别在于webpack-simple 没有包括Eslint 检查功能等等功能,普通项目基本用webpack-simple 就足够了.
搭建官方项目模板步骤:
1、npm install vue-cli (安装vue-cli ) 有的时候有看到其它两种写法: --save-dev 和 --save的写法。这两个有一定的区别,我们都知道package.json 中有一个 “dependencies” 和 “devDependencies” 的。dependencies 是用在开发完上线模式的,就是有些东西你上线以后还需要依赖的,比如juqery , 我们这里的vue 和 babel-runtime(Babel 转码器 可以将ES6 转为ES5 ), 而devDependencies 则是在开发模式执行的,比如我们如果需要安装一个node-sass 等等。有的时候看到package.json中安装的模块版本号前面有一个波浪线。例如: ~1.2.3 这里表示安装1.2.x以上版本。但是不安装1.3以上。
2、vue init webpack-simple yourdemoname 下载一个webpack-simple项目,这里的webpack-simple 是固定的,也就是官网的项目模板。youdemoname 这个是你自己项目的名字。 执行这个步骤以后。就会弹出询问 “项目名称..项目描述“等等问题 直接按照提示操作。这个时候对应的项目目录下就出现刚刚建立的项目了。
3、我们还需要把项目的依赖下载下来。使用命令: cd youdemoname 然后执行npm install 就可以了,这个时候你的项目中有多了一个node_modules 目录
4、使用"npm - run - dev" 命令来运行项目 "npm-run-bulid" 来执行发布,会自动生成dist文件