Vue

vue.js 核心知识点六

2019-02-14  本文已影响23人  我跟你蒋

目录

- 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
解决
  <child :child-data="asyncData" v-if="asyncData"></child>

当asyncData有值得时候,在加载子组件

watch:{
  childData(val){
      this.flGoods = val;
      console.log('子组件 数据处理',val) 
      this.updata()
  }
},
methods: {
   updata () { // 既然created只会执行一次,但是又想监听改变的值做其他事情的话,只能搬到这里咯
    console.log(this.test)// 1
   }
  }
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渲染完了后再初始化滚动。

<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>
<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()的东西供这两个组件连接),才能相互触发

<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>

大概逻辑:使用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文件

上一篇下一篇

猜你喜欢

热点阅读