VUE

【vue学习】生命周期

2019-08-08  本文已影响0人  前端菜篮子

马老师的VUE生命周期教程

image.png

Vue实例

虽然没有完全遵循 MVVM 模型,但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。

一个 Vue 应用由一个通过new Vue() 创建的根 Vue 实例,以及可选的嵌套的、可复用的组件树组成。所有的 Vue 组件也都是 Vue 实例,并且接受相同的选项对象 (一些根实例特有的选项除外)。

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

面试中的问题

  1. 请描述下vue的生命周期是什么?
  2. vue生命周期总共有几个阶段?
  3. vue生命周期的作用是什么?
  4. 组件进来请求接口时你是放在哪个生命周期?为什么?
  5. DOM渲染在哪个周期中就已经完成了?
  6. 第一次加载页面时会触发哪几个钩子?
  7. vue在created和mounted这两个生命周期中请求数据有什么区别呢?
  8. watch的属性用箭头函数定义结果会怎么样?
  9. 在vue项目中如果methods的方法用箭头函数定义结果会怎么样?


    image

API中的钩子函数

image

所有的生命周期钩子自动绑定 this 上下文到实例中,因此你可以访问数据,对属性和方法进行运算。这意味着你不能使用箭头函数来定义一个生命周期方法 (例如 created: () => this.fetchTodos())。这是因为箭头函数绑定了父上下文,因此 this 与你期待的 Vue 实例不同,this.fetchTodos 的行为未定义。

大部分钩子在服务器端渲染期间不被调用。

5种处理Vue异常的方法

既然学到了钩子函数errorCaptured,那么我们就来学习下vue的异常处理

错误大全

为了测试各种异常处理技巧,来触发三种类型的错误。

  1. 引用一个不存在的变量:
<div id="app" v-cloak>
    Hello, {{name}}
</div>

上述代码运行后不会抛出错误,但是在控制台会有[Vue warn]消息。

image
你可以在 Codepen 查看例子,页面可以正常显示
image
  1. 将变量绑定到一个被计算出来的属性,计算的时候会抛出异常
<div id="app" v-cloak>
    Hello, {{name2}}
</div>

<script>
    const app = new Vue({
        el: "#app",
        computed: {
            name2() {
                return x;
            }
        }
    });
</script>

运行上述代码会在控制台抛出一个[Vue warn]和一个常规的错误,网页白屏。

image
  1. 执行一个会抛出异常的方法
<div id="app" v-cloak>
    <button @click="doIt">Do It</button>
</div>

<script>
    const app = new Vue({
        el: "#app",
        methods: {
            doIt() {
                return x;
            }
        }
    });
</script>

这个错误在控制台也[Vue warn]和常规报错。和上一个错误的区别在于,只有你点击了按钮才会触发函数调用,才会报错。

上面 3 个例子并不代表所有类型的错误。这 3 种是比较常见的错误。

异常处理技巧

  1. errorHandler[2.2+]
/**
err指代 error 对象,
info是一个 Vue 特有的字符串,
vm指代 Vue 应用本身。
记住在一个页面你可以有多个 Vue 应用。
这个 error handler 作用到所有的应用。
*/
Vue.config.errorHandler = function(err, vm, info) {
    console.log(`Error: ${err.toString()}\nInfo: ${info}`);
};
/**
上面第一种错误不会触发 errorHandler,它只是一个 warning。

第二种错误会抛出错误被 errorHandler 捕获:
Error: ReferenceError: x is not defined
Info: render

第三种错误也会被捕获:
Error: ReferenceError: x is not defined
Info: v-on handler

记住info里面的信息也是非常有用的。
*/
  1. warnHandler[2.4+]
/**
msg和vm都容易理解,trace代表了组件树。
*/
Vue.config.warnHandler = function(msg, vm, trace) {
    console.log(`Warn: ${msg}\nTrace: ${trace}`);
};
/**
第一个错误被warnHandler捕获:
Warn: Property or method 'name' is not defined on the instance but referenced during render. 
Make sure that this property is reactive, either in the data option, 
or for class-based components, by initializing the property. 
See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Trace:(found in <Root>)
*/
  1. renderError
/**
和前面两个不同,这个技巧不适用于全局,和组件相关。
并且只适用于非生产环境。
*/
const app = new Vue({
    el: "#app",
    renderError(h, err) {
        return h("pre", { style: { color: "red" } }, err.stack);
    }
});

第一个例子是没有效果的,因为只是一个 warning。第二个例子就会在网页上显示具体的错误信息。

image
老实说,我没觉得这个比直接看控制台好多少。但是,如果你们的 QA 团队或则测试对浏览器控制台不熟悉的话,这个还是蛮有用的。
  1. errorCaptured
Vue.component("cat", {
    template: `
<div><h1>Cat: </h1>
  <slot></slot>
</div>`,
    props: {
        name: {
            required: true,
            type: String
        }
    },
    errorCaptured(err, vm, info) {
        console.log(`cat EC: ${err.toString()}\ninfo: ${info}`);
        return false;
    }
});

Vue.component("kitten", {
    template: "<div><h1>Kitten: {{ dontexist() }}</h1></div>",
    props: {
        name: {
            required: true,
            type: String
        }
    }
});
<!--注意 kitten 组件的代码是有 BUG 的。-->
<div id="app" v-cloak>
    <cat name="my cat">
        <kitten></kitten>
    </cat>
</div>

捕获的信息如下:


image
  1. window.onerror (不仅仅针对 Vue)
    最后也是最重要的一个候选项 window.onerror。它是一个全局的异常处理函数,可以抓取所有的 JavaScript 异常。
window.onerror = function(message, source, line, column, error) {
  console.log('ONE ERROR HANDLER TO RULE THEM ALL:', message);
}

Vue.config.productionTip = false;
Vue.config.devtools = false;

Vue.config.errorHandler = function(err, vm, info) {
  //oopsIDidItAgain();
  console.log(`Error: ${err.toString()}\nInfo: ${info}`);
}

const app = new Vue({
  el:'#app',
  methods:{
    doIt() {
      return x;
    }
  }
})

Vue选项/数据

学习vue的生命周期,这里也顺便看下vue选项中的数据:

image
  1. propsData

错误用法:


image
image

正确用法:


image
  1. props 可以是数组或对象,用于接收来自父组件的数据。
// 简单语法
Vue.component('props-demo-simple', {
  props: ['size', 'myMessage']
})

// 对象语法,提供验证
Vue.component('props-demo-advanced', {
  props: {
    // 检测类型
    height: Number,
    // 检测类型 + 其他验证
    age: {
      type: Number,
      default: 0,
      required: true,
      validator: function (value) {
        return value >= 0
      }
    }
  }
})
  1. data
  1. computed
computed: {
    // 仅读取
    aDouble: function () {
      return this.a * 2
    },
    // 读取和设置
    aPlus: {
      get: function () {
        return this.a + 1
      },
      set: function (v) {
        this.a = v - 1
      }
    }
  }
  1. methods
methods: {
    plus: function () {
      this.a++
    }
}
  1. watch
    一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch对象的每一个属性。
var vm = new Vue({
  data: {
    a: 1,
    b: 2,
    c: 3,
    d: 4,
    e: {
      f: {
        g: 5
      }
    }
  },
  watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    // 方法名
    b: 'someMethod',
    // 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
    },
    // 该回调将会在侦听开始之后被立即调用
    d: {
      handler: 'someMethod',
      immediate: true
    },
    e: [
      'handle1',
      function handle2 (val, oldVal) { /* ... */ },
      {
        handler: function handle3 (val, oldVal) { /* ... */ },
        /* ... */
      }
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { /* ... */ }
  }
})

data中为啥不能用-$开头的属性

<template>
 <div class="hello">
    {{test}}
    {{_tttttttttt}}
    {{$tttttt}}
  </div>
</template>
<script>
import h222 from './h2'
export default {
  name: 'HelloWorld',
  data () {
    return {
      _tttttttttt: '__',
      $tttttt: '$$',
      test: 'test'
    }
  },
  mounted () {
    console.log(this)
  }
}
</script>

这么一个简单例子,但是vue却会提示你报错了,错误如下:


image
Object.defineProperty(target, key) {
    enumerable: true,
    configurable: true,
    get: function() {
        // 这里获取数据
    },
    set: function() {
        // 这里设置参数,通知更新视图
    }
}
/**
可如果仅仅是这样的话,我们在vue中是没法通过this.xxx来获取变量的,
而必须是通过this.$data.xxx。因此vue的变量都挂在在$data或_data上。
所以vue内部还做了一层代理,如下
*/
/**
target是vue实例,key为_data,
这样就能通过访问this.xxx = this._data.xxx了
*/
function proxy (target, sourceKey, key) {
  sharedPropertyDefinition.get = function proxyGetter () {
    return this[sourceKey][key]
  };
  sharedPropertyDefinition.set = function proxySetter (val) {
    this[sourceKey][key] = val;
  };
  Object.defineProperty(target, key, sharedPropertyDefinition);
}
/**
所以那么我们的变量为什么还是不存在呢,
那是因为vue做了一个检测,
检测你的变量的开头是否为_或$,
如果使用了那么就不会使用代理了,
变量只会存在$data上或_data上。我们来看下源码:
*/
function initData (vm) {
  var data = vm.$options.data;
  data = vm._data = typeof data === 'function'
    ? getData(data, vm)
    : data || {};
  if (!isPlainObject(data)) {
    data = {};
    process.env.NODE_ENV !== 'production' && warn(
      'data functions should return an object:\n' +
      'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
      vm
    );
  }
  // proxy data on instance
  var keys = Object.keys(data);
  var props = vm.$options.props;
  var methods = vm.$options.methods;
  var i = keys.length;
  while (i--) {
    var key = keys[i];
    if (process.env.NODE_ENV !== 'production') {
      if (methods && hasOwn(methods, key)) {
        warn(
          ("Method \"" + key + "\" has already been defined as a data property."),
          vm
        );
      }
    }
    if (props && hasOwn(props, key)) {
      process.env.NODE_ENV !== 'production' && warn(
        "The data property \"" + key + "\" is already declared as a prop. " +
        "Use prop default value instead.",
        vm
      );
    } else if (!isReserved(key)) {
        // 这边处理代理,所以isReserved处理了是否要进行代理
      proxy(vm, "_data", key);
    }
  }
  // observe data
  observe(data, true /* asRootData */);

  function isReserved (str) {
      var c = (str + '').charCodeAt(0);
      return c === 0x24 || c === 0x5F // 这边判断chartCode是否为_和$
   }
}
<template>
 <div class="hello">
    {{test}}
    {{_data._tttttttttt}}
    {{_data.$tttttt}}
    {{$data._tttttttttt}}
    {{$data.$tttttt}}
  </div>
</template>
<script>
import h222 from './h2'
export default {
  name: 'HelloWorld',
  data () {
    return {
      _tttttttttt: '__',
      $tttttt: '$$',
      test: 'test'
    }
  },
  mounted () {
    console.log(this.$data._tttttttttt)
    console.log(this.$data.$tttttt)
    console.log(this._data._tttttttttt)
    console.log(this._data.$tttttt)
  }
}
<script>
上一篇下一篇

猜你喜欢

热点阅读