美文网首页Vue
vue.js 核心知识点六

vue.js 核心知识点六

作者: 我跟你蒋 | 来源:发表于2019-02-14 18:06 被阅读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
    解决
    • 方法一 使用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文件

    相关文章

      网友评论

        本文标题:vue.js 核心知识点六

        本文链接:https://www.haomeiwen.com/subject/fzpdeqtx.html