首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Vue基础:响应式

最近换了东家,比较忙、比较累,但博客的更新速度不能明显下降。Fighting!

写在前面

Vue不是框架(前端框架往往需要解决路由、试图管理、数据持久化等流程),Vue只关注视图层。

webpack构建Vue项目

使用webpack构建Vue项目,在配置文件中会看到如下代码:

module.exports = {
  // ...
  resolve: {
    alias: {
      // 使用完整版构建,而非运行时构建版本
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
}
  • 完整版:同时包含编译器和运行时的构建。
  • 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。
  • 运行时:用来创建 Vue 实例,渲染并处理 virtual DOM 等行为的代码。基本上就是除去编译器的其他一切。
// 需要编译器
new Vue({
  template: '<div>{{ hi }}</div>'
})
// 不需要编译器
new Vue({
  render (h) {
    return h('div', this.hi)
  }
})

所以,项目中往往我们需要采用完整版,而非运行时构建版本!

压缩Vue

npm安装的Vue,并不是压缩后的版本。然而,CommonJS 和 ES Module 构建同时保留原始的 process.env.NODE_ENV 检测,以决定它们应该运行在什么模式下。可以通过控制该环境变量,对Vue进行压缩,以减少最终文件的大小。

var webpack = require('webpack')
module.exports = {
  // ...
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production')
      }
    })
  ]
}
  • UMD:UMD 构建可以直接通过 <script> 标签用在浏览器中。Unpkg CDN 的 https://unpkg.com/vue 默认文件就是运行时 + 编译器的 UMD 构建 (vue.js)。
  • CommonJS:CommonJS 构建用来配合老的打包工具比如 browserifywebpack 1。这些打包工具的默认文件 (pkg.main) 是只包含运行时的 CommonJS 构建 (vue.runtime.common.js)。
  • ES Module:ES module 构建用来配合现代打包工具比如 webpack 2rollup。这些打包工具的默认文件 (pkg.module) 是只包含运行时的 ES Module 构建 (vue.runtime.esm.js)。这也是为什么上述需要通过别名指定加载Vue的完整版本!

数据驱动

View(Dom) ==> DomListener(Vue) ==> Model(Pojo) View(Dom) <== Directives(Vue) <== Model(Pojo)

每个指令都会观察一片数据,管理其相关的Dom操作;数据变化,Dom操作自行发生变化!所有的元素都是响应式的!

需要注意的是:在Vue中数据流是单向的

<input v-model="message">实现所谓的双向,不过是<input v-bind:value='message' v-on:input="message = arguments[0]"的语法糖。数据传递机制完全和子父组件通信方式一致,所以要让组件的 v-model 生效,子组件它应该:

  • 接受一个 value 属性
  • 在有新的值时触发 input 事件
 components: {
    'child-component': {
      props: ['value'],
      template: `
      <div>
        <input v-bind:value="value"
               @input="updateValue($event.target.value)">
        </input>
      </div>
      `,
      methods: {
        updateValue(value) {
          // 同样需要触发input事件,只是父组件中无需显示声明@inpt
          this.$emit('input', value);
        }
      }
    }
  }

完整示例参考地址:https://jsfiddle.net/381510688/uyppvvL0/

响应式原理

Vue将遍历Data对象所有的属性,并使用 Object.defineProperty (ES5方法,Vue只支持IE9+d的原因) 把属性全部转为 getter/setter。

每个组件实例都有相应的watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。

Vue**异步**执行DOM更新,如果同一个watcher被多次触发,只会一次推入到队列中。Vue在内部尝试对异步队列使用原生的 Promise.thenMutationObserver,如果执行环境不支持,会采用setTimeout(fn, 0)代替。

Vue.js 通常鼓励开发人员沿着“数据驱动”的方式思考,避免直接接触DOM,但是有时我们确实要这么做。为了在数据变化之后等待Vue完成更新DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)或者vm.$nextTick(),这样回调函数在DOM更新完成后就会调用。

需要注意的是,对于对象添加新成员,需要如下处理:

// 全局方法
Vue.set(vm.someObject, 'b', 2);
// 实例方法
this.$set(this.someObject,'b',2);

可以使用 Object.assign()_.extend() 方法来添加属性。但是,添加到对象上的新属性不会触发更新。在这种情况下可以创建一个新的对象,让它包含原对象的属性和新的属性(开发中会经常遇到):

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

数组:改变原数组的方法,如:push()、pop()、shift()、unshift()、splice()、sort()、reverse()可以检测变化;不改变原数组的方法,如filter()、concat()、slice()不会自动变化。

// 用新数组替换旧数组(Vue做了相关处理,非常高效的操作)
example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

对于直接修改数组某一项值,或者修改其长度,可以通过以下方式实现:

Vue.set(example1.items, indexOfItem, newValue)
example1.items.splice(newLength)

计算属性、方法和观察者

computed vs methods vs watch

var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // a computed getter
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
  }
})

对于任何复杂逻辑,你都应当使用计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖于 vm.reversedMessage 的绑定也会更新。计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter。

computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}

运行 vm.fullName = 'John Doe' 时,setter 会被调用,vm.firstNamevm.lastName 也相应地会被更新

注意:三者中都不能使用箭头函数,箭头函数绑定了父级作用域的上下文,所以this将不会按照期望指向Vue实例!

<div id="app">
  <input type="text" v-model="message">
  <p>{{computedMsg}}</p>
  <p>{{methodMsg()}}</p>
  <p>{{watchMsg}}</p>
</div>

<script>
  const app = new Vue({
    el: '#app',
  data() {
    return {
        message: '',
        watchMsg: ''
    }
  },
  computed: {
    computedMsg() {
        return this.message.toUpperCase();
    }
  },
  methods: {
    methodMsg() {
        return this.message.toUpperCase();
    }
  },
  watch: {
    message() {
        return  this.watchMsg = this.message.toUpperCase();
    }
  }
});
</script>  

示例地址:https://jsfiddle.net/381510688/trmvsnve/

  • computed不能被watch(本身就是响应式的),计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算。注意,如果实例范畴之外的依赖是不会触发计算属性更新的;
  • 每当触发重新渲染时,method调用方式将总是再次执行函数;
  • watch是更通用的方式来观察和响应Vue实例上的数据变动,不要滥用,在数据变化响应时,执行异步操作或开销较大的操作可以选用watch,其他情况尽量使用computed。
下一篇
举报
领券