Vue 基础 - 04

110 阅读6分钟

Vue 响应式的前端视图层框架

库:定义方法进行调用

框架:框架中约定的规范进行方法实例化,给模版进行操作

两个简单的demo

  <div id="app">
    <input type="text" v-on:input='inputCb'>
    <div>{{ message }}</div>
  </div>
  <script>
      var app = new Vue({
        el: '#app',
        data: {
          message: 'Hello world!'
        },
        methods: {
          inputCb(e) {
            this.message = e.target.value;
          }
        }
      })
  </script>

Vue 引入

  1. 通过CDN: 包含了vue中所有功能,适合小型项目或部分使用vue 1.1 引用全部vue.js,运行时编译及渲染 1.2 引用部分vue.js,仅引入渲染部分
  <div id="app">
    <input type="text" v-model="message">
    <div>The message is {{ message }}</div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  <script>
      var app = new Vue({
        el: '#app',
        data: {
          message: 'Hello world!'
        }
      })
  </script>

运行时的编译: 加载之后 首先显示html,加载过 vue 之后,模版才会被编译成内部可以识别的变量,再通过data 渲染出来 Kapture 2022-02-01 at 18.03.22.gif

这种形式需要引入全部的vue.js

优化:不进行运行时的编译而是定义一个render方法

  <div id="app">
    <!-- 嵌套解构中 message
    <div>
      <span>{{ message }}</span>
    </div> -->
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  <script>
      var app = new Vue({
        el: '#app',
        data: {
          message: 'Hello world!'
        },
        render(createElement) {
          return createElement(
            'div',
            null,
            [createElement('span', this.message)])
        }
      })
  </script>

Kapture 2022-02-01 at 18.12.58.gif

静态编译方法后,只需要引入vue.runtime.js,在vue cli当中会静态编译

  1. 通过Vue Cli: 包含了所有的webpack功能

Vue 中的细节

  1. 模版中的 {{}} 只接受表达式

什么是表达式? expression & statement

expression: 一般指单独的一段可运行代码,一般具有返回值 statement: 一般指多个表达式的集合,也具有上下文的语义

一般在JS中常见

  1. 赋值表达式 如:a = 5 返回值就为5,如果加了分号则为语句 a = 5;
  2. 逗号表达式 1,2,3
  3. 布尔表达式
  4. 函数表达式(匿名函数)
  5. 三目运算符
  // 函数表达式
  var a = function () {}

  // 函数语句
  function b() {}
  1. 标签中的新属性 v-...
  2. v-bind: 我们能将 data 中的值绑定到当前属性中,可简写为 :
  <div v-bind:title='message'>我绑定了 title 为 {{ message }}</div>
  1. v-on: 能够绑定实例中配置的事件,可简写为 @
  <input type="text" v-on:input='inputCb'>

  <script>
    methods: {
      inputCb(e) {
        this.message = e.target.value;
      }
    }
  </script>
  1. v-for: 列表级别渲染,迭代渲染所有子元素,渲染多个当前标签元素(需要写在当前的循环体上,可以使用在数组及对象上) 在对象循环中,浏览器循环的 key 和 value 的顺序可能会不同
  <ul>
    <li v-for="(item, index) in items1">
      {{ item.value }} - {{ index }}
    </li>
  </ul>
  <ul>
    <li v-for="(value, key) in items2">
      {{ value }} - {{ key }}
    </li>
  </ul> 

  items1: [{
    value: 1
  }, {
    value: 2
  }],
  items2: {
    key1: 'value1',
    key2: 'value2',
  } 
  1. v-if/v-else/v-show: 控制自元素视图显隐
  <div v-if="message.length >=5">这里的message 长度 大 于5</div>
  <div v-else="message.length < 5">这里的message 长度 小 于5</div>

v-if 和 v-show 区别

v-show 是通过css display: none来实现显示隐藏;而v-if是通过添加或删除该dom节点实现显示和隐藏

  1. v-model: 应用于表单,创建与控元素的双向绑定
  2. v-html: 将最终值的结果渲染为html
  3. v-text: 等同于直接在文本处使用 {{ x }}

计算属性

大部分时候,我们在模版也就是 html 中写表达式会让模版变得复杂,所以我们可以通过计算属性来简化模版。

但大多数时候,我们也可以通过定义方法的形式来直接在表达式内部调用函数,不过计算属性也可以模拟出使用参数的形式。

  // 使用computed
  <div v-if="validLength">这里的message 长度 大 于5</div>
  <div v-else="message.length < 5">这里的message 长度 小 于5</div>

  computed: {
    validLength() {
      return this.message.length >=5
    }
  },

  // 使用methods
  <div v-if="validLengthFn()">这里的message 长度 大 于5</div>
  <div v-else="message.length < 5">这里的message 长度 小 于5</div>

  methods: {
    validLengthFn() {
      return this.message.length >=5
    }
  }

methods 与 计算属性 computed

methods 只要当前组件重新渲染了,对于函数来说都会调用一次

改变message 的值,触发重新渲染,那么调用函数没问题;但是改变message2 的值,触发了整个组件的重新渲染,那么这时候也会触发methods

  methods: {
    validLengthFn() {
      console.log('validLengthFn trigger')
      return this.message.length >=5
    }

Kapture 2022-02-01 at 17.37.24.gif

使用 computed:改变 message 的值,触发重新渲染;但改变 message2 的值,虽然整个组件重新渲染了,但是 computed 依赖的值没有边,因此不会触发

  computed: {
    validLength() {
      console.log('validLength trigger')
      return this.message.length >=5
    }

Kapture 2022-02-01 at 17.41.40.gif

computed 和 methods 区别:

  1. computed 计算属性利用对象属性的getter,表现形式为一个变量赋值,故而无法传值,而methods 可以传值
  2. 使用 computed 是惰性求值,依赖的值不发生变化不会重新计算,但是函数执行一次计算一次

vue.js 组件

上面示例中的 new Vue 实例化之后,在具体的元素上实例化了根节点。对于框架而言,一般都会给用 户提供一种复用的方式,而在 vue 中,我们可以通过定义组件的方式,来实现模版的复用来减少代码。

通常通过在实例上面定义 component 属性来实现

组件的 data 为一个函数,彼此间的 data 不会受影响

  <div id="app">
    <hello-world></hello-world>
    <hello-world></hello-world>
    <hello-world></hello-world>
  </div>
  Vue.component('hello-world', {
    data: function () {
      return {
        message: 'Hello World!'
      }
    },
    template: '<p> {{ message }} </p>'
  })

  new Vue({
    el: '#app'
  });

组件间数据传递

  1. 父 -> 子

    1.1 父组件自定义属性,传递数据变量到子组件

      <blog-post :post="post">   
    
      data () {
        return {
          post: [
            { id: 1, title: 'My journey with Vue', context: 'How fun it is!!' },
            { id: 2, title: 'Blogging with Vue', context:'What a frameworks!' },
            { id: 3, title: 'Why Vue is so fun', context: 'I don\'t think so!' }
          ],
        }
      }  
    

    1.2 子组件通过props 属性进行接受并使用

      <div v-for="item in post" :key="item.id">
          <h3>{{ item.title }}</h3>
          <p>{{ item.context }}</p>
      </div>
    
      props: {
        post: Array
      }
    
  2. 子 -> 父

    2.1 子组件 emit 一个自定义事件给父组件

      <button @click="$emit('enlarge-size', 0.1)">Enlarge FontSize</button>
    

    2.2 父组件进行监听并使用

      <blog-post 
          :post="post" :style="{ fontSize: postFontSize + 'em'}"
          @enlarge-size="postFontSize += $event"
    />
    

有状态组件与无状态组件 (states & status)

大部分时候,我们需要区分一些具有副作用的组件,例如某些组件我们需要发送 ajax 请求之后渲染一些 数据,这时候我们就需要将这部分数据内容进行一个区分,推荐做法将 UI 部分渲染于子组件中,做一个只通过传入数据渲染的无状态组件,而在有副作用组件中维护所有的数据。

为什么列表渲染需要key?

列表子元素都是一样的,框架本身不希望频繁渲染列表,需要明白每个子元素所在的为止,比如更新某个子元素,很难区分整个列表都需要更新还是只更新某个;这时候就需要key,相当于列表组件中的唯一标识,当改变时具体更新哪一个内容。

  <hello-world v-for="item in items" :item="item" :key="item.id"></hello-world>
  Vue.component('hello-world', {
    // data: function () {
    //   return {
    //     message: 'Hello World!'
    //   }
    // },
    props: ['item'],
    template: '<p> {{ item.title }} </p>'
  })

  new Vue({
    el: '#app',
    data: {
      items: [
        {id: 1, title: 'item1'},
        {id: 2, title: 'item2'},
        {id: 3, title: 'item3'},
      ]
    }
  });

上述例子中,items 模拟后台获取数据,对于new Vue组件来说,他是一个有副作用的有状态组件,需要在其生命周期中获取一部分数据再去渲染;但对于hello world中来说,只需要从父组件中获取item对象内容,渲染出对于数据的值,自己内部不需要维护 data 就是一个单纯的 UI 组件,为一个无状态组件

在实际工作中,有意识分割有副作用的组件和无副作用的组件,尽量可以把副作用集中在一个组件中,同时渲染出无状态的 纯 UI 组件

生命周期 - 不同时机定义方法

image.png

什么时候会发送请求?

生命周期中,只有 beforeCreate 和 created 两个生命周期函数会在服务端渲染 vue 组件时用到,只需要在mounted 周期内发送数据,beforeCreated 和 created 发送数据有可能造成冲突 将el 外部的HTML作为 template 编译?? 相当于调用outerHTML,即包含 <div id='app'>