1.Vue.js 是什么 #

2.安装 #

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>

3.声明式渲染 #

<body>
    <!--这是 HTML 中的一个 div 元素,它的 id 是 "app"-->
    <div id="app">
        <!--这是 Vue.js 的插值表达式,它会被 Vue.js 替换为 Vue 实例中 data 对象的 `message` 属性的值-->
        {{ message }}
    </div>
    <!--这行代码是在 HTML 文件中引入 Vue.js 库。这是使用 Vue.js 的前提-->
    <script src="vue.js"></script>
    <script>
        //创建了一个新的 Vue 实例,并将其赋值给变量 app
        //Vue 实例是 Vue.js 应用的基础,每个 Vue 应用都是通过创建一个新的 Vue 实例开始的
        var app = new Vue({
            //这是 Vue 实例的一个选项,它告诉 Vue 实例要管理的 HTML 元素
            //'#app' 是一个选择器,它选择了 id 为 'app' 的元素,也就是我们前面提到的 div 元素
            el: '#app',
            //这是 Vue 实例的另一个选项,它定义了 Vue 实例的数据模型
            //在这个例子中,数据模型只有一个属性 message,其值为 'Hello Vue!'。
            data: {
                message: 'Hello Vue!'
            },
            template:`<span> {{ message }}</span>`
        })
    </script>
</body>

3.1 Vue实例 #

在 Vue.js 中,每一个 Vue 应用都是通过构造函数 Vue 创建一个新的 Vue 实例开始的。

一个 Vue 实例其实与一个 JavaScript 对象非常相似,它接收一个选项对象,其中可以包含各种可选参数,如数据、方法等 以下是一个简单的 Vue 实例的创建过程:

var app = new Vue({
  el: '#app',
  data: {
    message: 'hello'
  }
});

在这个例子中,new Vue() 创建了一个新的 Vue 实例。这个实例接收一个选项对象作为参数,该对象中定义了一个 el 属性和一个 data 对象。

你可以像操作普通 JavaScript 对象一样操作 Vue 实例。例如,你可以在实例上访问数据、调用方法,以及改变它的数据属性的值。

console.log(app.message); // 'hello'
app.message = 'world!';
console.log(app.message); // 'world'

以上的代码展示了如何访问和改变 Vue 实例的数据。所有在 data 对象中声明的属性都会被 Vue 实例代理,所以你可以直接通过 vm.message 访问和修改 message 属性的值。

在 Vue 实例被创建之后,你也可以使用 $data 属性直接访问原始数据对象,并且修改数据仍会触发视图的更新。

console.log(vm.$data.message); // 'world'
vm.$data.message = 'hello';
console.log(vm.$data.message); // 'hello'

注意,Vue 实例的数据是响应式的:当 data 中的数据变化时,所有依赖这些数据的视图都将立即更新。这让状态管理变得非常直接和易于理解。

3.2 插值表达式 #

<div id="app">
  {{ message.split('').reverse().join('')}}
</div>

<script>
var app = new Vue({
  el: '#app',
  data: {
    message: 'hello'
  }
})
</script>

4.MVVM #

5. v-text #

Vue.js 中的 v-text 是一个用来更新元素的 textContent 的指令。这个指令的值会被转换为字符串,然后会成为指定元素的文本内容。如果这个值的数据变动了,那么元素的文本内容也会随之更新。

下面是一个简单的例子:

<div id="app">
  <span v-text="message"></span>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!'
  }
})
</script>

在这个例子中,我们创建了一个新的 Vue 实例,绑定到了 id 为 app 的元素。这个 Vue 实例有一个数据属性 message,我们用 v-text 指令将这个属性绑定到了一个 <span> 元素的文本内容上。所以,<span> 元素的内容会是 message 的值,即 'Hello, Vue!'。如果 message 的值发生改变,<span> 元素的内容也会自动更新。

需要注意的是,v-text 会覆盖元素中原有的内容。如果你需要插入一些原有内容,那么你可能需要使用 v-html 指令或者双大括号 {{ }} 插值。但是,在大多数情况下,你应该尽量避免使用 v-html,因为它会增加跨站脚本攻击(XSS)的风险。

{{ }} 插值相比,v-text 不会有闪烁问题(即在 Vue 实例加载完成前,用户会看到未编译的插值标签)。这是因为 v-text 不会将元素的内容插入到插值表达式中,而是直接替换掉元素的内容。

6. v-html #

在 Vue.js 中,v-html 是一个用于更新元素的 innerHTML 的指令。不同于 v-text,该指令可以解析并渲染 HTML 内容。

这是一个简单的 v-html 示例:

<div id="app">
  <span v-html="message"></span>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    message: '<strong>Hello, Vue!</strong>'
  }
})
</script>

在这个例子中,我们创建了一个新的 Vue 实例,它绑定到了 id 为 app 的元素上。Vue 实例有一个数据属性 message,我们使用 v-html 指令将这个属性绑定到 <span> 元素的 HTML 内容上。所以,<span> 元素的内容将是 message 的值,即 'Hello, Vue!'。如果 message 的值发生变化,<span> 元素的 HTML 内容也会自动更新。

要注意的是,v-html 会覆盖元素的原始内容。如果你想要在现有内容的基础上插入新的内容,你可能需要其他的解决方案,如使用 Vue 的组件系统。

另一个重要的警告是,你应当避免使用 v-html 来插入用户提供的内容,并且无法保证这些内容的安全性。因为这样做会让你的应用程序暴露于跨站脚本攻击(XSS)的风险。只有在你能够完全信任需要通过 v-html 插入的内容的安全性,或者你已经对内容进行了足够的消毒处理时,才可以使用 v-html

7. v-cloak #

v-cloak 是 Vue.js 的一个特殊指令,其作用是隐藏未编译的 Vue.js 标签,直至实例准备就绪。这个指令可以解决初始化渲染慢导致的闪烁问题。

当 Vue.js 应用加载的时候,如果渲染速度稍慢,用户可能会短暂地看到原始的 HTML 模板,例如 {{ message }} 这样的插值表达式,这种现象被称为 "闪烁"。v-cloak 与 CSS 规则配合使用,可以隐藏未编译的 Vue.js 标签,直到编译结束后再显示。

下面是一个简单的例子:

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
  <style>
    [v-cloak] {
      display: none;
    }
  </style>
</head>

<body>
  <div id="app" v-cloak>
    {{ message }}
  </div>
  <script src="vue.js"></script>
  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello, Vue!'
      }
    })
  </script>
</body>

在这个例子中,我们创建了一个新的 Vue 实例,绑定到了 id 为 app 的元素。我们在这个元素上使用了 v-cloak 指令。然后我们在 CSS 中定义了一个规则,该规则隐藏了所有带有 v-cloak 属性的元素。所以,当 Vue 实例还未准备就绪时,这个元素会被隐藏。当 Vue 实例准备就绪,编译结束后,v-cloak 指令会被删除,元素就会被显示出来。

需要注意的是,v-cloak 会等待 Vue 实例结束编译。如果你的 Vue 应用较大,有很多需要编译的模板,那么可能会出现页面在一段时间内什么也没有的情况。在这种情况下,可能需要考虑使用不同的策略,比如显示一个加载指示器。

8. v-pre #

在 Vue 2 中,v-pre 是一个特殊的指令,其功能是跳过这个元素及其子元素的编译过程。可以用它来显示原始的 Mustache 标签。当你的元素包含大量静态内容时,使用 v-pre 可以降低编译的开销。

这是一个示例:

<body>
  <div id="app" >
    <span v-pre>{{ this will not be compiled }}</span>
  </div>
  <script src="vue.js"></script>
  <script>
    new Vue({
    el: '#app',
    data: {

    }
  })
  </script>
</body>

在上述代码中,{{ this will not be compiled }} 文本将被直接渲染,而不会被作为 Vue 模板字符串处理。

需要注意的是,v-pre 指令不仅会跳过元素本身,还会跳过它的所有子元素,所以在使用时需要小心。

9. v-bind #

在 Vue 2 中,v-bind 是一个非常常见和重要的指令,用于绑定一条表达式到元素的一个属性。

例如,你可以使用 v-bind 指令来动态地绑定 title 属性:

<body>
  <div id="app" >
    <div v-bind:title="title">title</div>
  </div>
  <script src="vue.js"></script>
  <script>
    new Vue({
    el: '#app',
    data: {
      title: 'title'
    }
  })
  </script>
</body>

在上面的例子中,title 是一个在 Vue 实例中定义的数据属性。这个 div 元素的 title 将等于 title 的值。

10. v-on #

10.1 methods #

在 Vue 2 中,methods 是 Vue 实例的一个选项,它包含我们定义的方法。这些方法可以在 Vue 实例的模板中通过事件绑定来调用,也可以在 Vue 实例的其他方法或计算属性中调用。

<div id="app">
  <button v-on:click="sayHello">Click me</button>
</div>
<script>
  new Vue({
    el: '#app',
    data: {
      greeting: 'Hello Vue!'
    },
    methods: {
      sayHello: function() {
        alert(this.greeting);
      }
    }
  })
</script>

在这个例子中,我们创建了一个新的 Vue 实例并将其挂载到 id 为 "app" 的 div 元素上。然后我们定义了一个 data 对象和一个 methods 对象。data 对象包含了我们需要在页面上显示的数据,methods 对象包含了我们在页面上的交互逻辑。我们在模板中的 button 元素上使用 v-on 指令来绑定 sayHello 方法,这样当用户点击这个按钮时,sayHello 方法就会被调用。

10.2 v-on #

在 Vue 2 中,v-on 是一个用于监听 DOM 事件的指令。你可以使用它来触发 JavaScript 方法或者运行一段内联的 JavaScript 代码。这是一个非常重要的功能,因为用户交互(比如点击按钮,提交表单等)是由 DOM 事件来驱动的。

以下是一个使用 v-on 来监听按钮点击事件的例子:

<button v-on:click="sayHello">Say hello</button>

在这个例子中,sayHello 是一个在 Vue 实例的 methods 对象中定义的方法。当用户点击这个按钮时,sayHello 方法将会被调用。

你也可以用 v-on 指令来监听键盘事件、鼠标事件、表单事件等等。只需要改变 v-on 后面的事件名就可以了。

v-on 指令有一个简写形式,就是 @。例如,v-on:click="sayHello" 可以简写为 @click="sayHello"

如果你想要监听的事件比较复杂,或者需要访问到事件对象,你可以在 v-on 指令的表达式中使用特殊的变量 $event。例如:

<button v-on:click="sayHello($event)">Say hello</button>

在这个例子中,sayHello 方法将接收到一个事件对象作为参数。这个事件对象包含了事件的详细信息,比如事件的类型、触发事件的元素、键盘的按键等等。

你可以像下面这样创建一个新的 Vue 实例并挂载到页面上的一个元素:

<body>
  <div id="app">
    <button v-on:click="sayHello">Say hello</button>
    <p>{{ message }}</p>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      },
      methods: {
        sayHello: function(event) {
          this.message = 'Hello from button click!'
        }
      }
    })
  </script>
</body>

在这个例子中,我们创建了一个新的 Vue 实例并将其挂载到 id 为 "app" 的 div 元素上。然后我们定义了一个 data 对象和一个 methods 对象。data 对象包含了我们需要在页面上显示的数据,methods 对象包含了我们在页面上的交互逻辑。

注意,我们需要先通过一个 script 标签引入 Vue.js 库,然后再创建 Vue 实例。

在这个例子中,我们有一个按钮和一个段落文本。点击按钮后,sayHello 方法会被调用,然后将 message 数据属性的值更新为 "Hello from button click!"。因为 message 已经与段落文本绑定,所以段落文本也会自动更新。

10.3 $event #

在 Vue.js 中,$event 是一个特殊的变量,它用于访问原生 DOM 事件在方法内部的参数。当你需要在方法内访问原生事件对象时,可以使用特殊的 $event 变量。

以下是一个例子:

<body>
  <div id="app">
    <button v-on:click="handleClick($event)">Click me</button>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello Vue!'
      },
      methods: {
        handleClick: function (event) {
          console.log(event.target); // 打印出点击的按钮元素
        }
      }
    })
  </script>
</body>

在这个例子中,v-on:click="handleClick($event)" 会在按钮被点击时触发 handleClick 方法。$event 将被解析为原生 DOM 事件对象,然后作为第一个参数传递给 handleClick 方法。

10.4 修饰符 #

10.4.1 事件修饰符 #

10.4.1.1 .stop #

在 Vue.js 中,.stop 事件修饰符用于阻止事件冒泡。当你在元素上使用 .stop 修饰符时,如果此元素的事件被触发,那么这个事件不会向上冒泡至父元素。

这是一个简单的例子:

<body>
  <div id="app">
    <div id="parent" @click="parentClicked">
      <button id="child" @click.stop="childClicked">Click me</button>
    </div>
  </div>

  <script>
    new Vue({
      el: '#app',
      methods: {
        parentClicked() {
          alert('Parent clicked!');
        },
        childClicked() {
          alert('Child clicked!');
        }
      }
    });
  </script>
</body>

在 Vue 实例中,你可以定义 parentClickedchildClicked 这两个方法:

在这个例子中,如果你点击 "Click me" 按钮(即 #child 元素),将会触发 childClicked 方法,并且由于 .stop 修饰符的存在,事件将不会冒泡至 #parent 元素,所以 parentClicked 方法不会被触发。你将只看到 "Child clicked!" 的警告框。

如果你移除 .stop 修饰符,那么点击 "Click me" 按钮将会先触发 childClicked 方法,然后事件冒泡至 #parent 元素,再触发 parentClicked 方法,你将看到两个警告框:"Child clicked!" 和 "Parent clicked!"。

这就是 .stop 事件修饰符的基本用法,它可以帮助你更精确地控制事件的行为,尤其是在复杂的 DOM 结构中。

10.4.1.2 .prevent #

在 Vue2 中,.prevent 是一个事件修饰符,用于告诉 Vue 的事件系统,我们希望阻止原生事件的默认行为。这对应于在 JavaScript 中调用 event.preventDefault()

这个修饰符常用在提交表单的时候,我们可能希望阻止表单的自动提交,而是使用 AJAX 来处理。

下面是一个简单的示例:

<body>
  <div id="app">
    <form id="myForm">
      <input type="text" id="messageInput" />
      <button type="submit">Submit</button>
    </form>
  </div>

  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: ''
      },
      mounted() {
        const form = document.getElementById('myForm');
        const messageInput = document.getElementById('messageInput');

        form.addEventListener('submit', (event) => {
          event.preventDefault();
          this.message = messageInput.value;
          this.submitForm();
        });
      },
      methods: {
        submitForm() {
          console.log('Form submitted: ', this.message);
        }
      }
    });
  </script>
</body>

在这个例子中,我们在 submit 事件上添加了 .prevent 修饰符,这样当用户点击 "Submit" 按钮时,浏览器的默认提交行为(会重新加载页面)就被阻止了。相反,我们的 submitForm 方法会被调用,我们可以在这个方法中处理表单提交,比如通过 AJAX 来提交。

10.4.1.3 .capture #

在Vue2中,修饰符被用作一种特殊的指令后缀,它告诉Vue如何在DOM级别处理原生事件。".capture"是其中一种事件修饰符,它使事件监听器在事件捕获模式下触发,而非默认的冒泡模式。

在解释.capture修饰符之前,我们首先需要了解事件的冒泡和捕获模式。

事件冒泡和捕获:

.capture修饰符就是使Vue在捕获模式下监听DOM事件。

下面是一个简单的使用.capture修饰符的示例:

<body>
  <div id="myDiv" @click.capture="handleClick">
    <button>Click me</button>
  </div>
  <script>
    new Vue({
    el: '#myDiv',
    methods: {
      handleClick: function() {
        alert('div clicked!');
      }
    }
  });
  </script>
</body>

在这个例子中,当你点击按钮时,捕获模式确保了div的点击事件先于按钮的点击事件触发,尽管实际上按钮被点击了。这就是.capture修饰符的作用。

10.4.1.4 .self #

在 Vue2 中,.self 是一个事件修饰符,用于确保只有当事件在该元素本身(而不是子元素)触发时,才会触发事件回调函数。这对应于 JavaScript 中对事件的目标元素(event.target)和当前元素(event.currentTarget)的比较。

这个修饰符常用在你希望事件只在特定元素上触发,而不是在其子元素上触发时。

以下是一个简单的例子:

<body>
  <div id="app">
    <div  @click.capture="captureClick" @click="handleClick">
      <button>Click me</button>
    </div>
  </div>
  <script>
    new Vue({
      el: '#app',
      methods: {
        captureClick: function () {
          alert('div captureClicked!');
        },
        handleClick: function () {
          alert('div clicked!');
        }
      }
    });
  </script>
</body>

在这个例子中,divClicked 方法只有在点击 <div> 本身(而不是按钮)时才会被触发。如果你点击 "Click Me" 按钮,事件不会触发,因为 .self 修饰符使得事件只在 <div> 元素上触发。这样可以让你更精确地控制事件的触发位置。

10.4.1.5 .once #

在Vue2中,.once修饰符用于指定事件监听器在触发一次之后就自动解除绑定,即这个监听器只会被触发一次。

下面是一个简单的使用.once修饰符的示例:

<body>
  <div id="app">
    <button @click.once="handleClick">Click me</button>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        counter: 0
      },
      methods: {
        handleClick: function () {
          this.counter++;
          alert('Button clicked for the ' + this.counter + ' time(s)');
        }
      }
    });
  </script>
</body>

在这个例子中,按钮的点击事件只会触发一次。当你点击按钮,浏览器会弹出一个对话框,显示“Button clicked for the 1 time(s)”。但是,如果你再次点击按钮,无论你点击多少次,都不会再次触发事件,因此不会再次弹出对话框。

这就是.once修饰符的作用。它使你能够方便地限制事件处理器的触发次数,而无需在JavaScript代码中手动解除事件绑定。

10.4.1.6 .passive #

在 Vue2 中,.passive 是一个事件修饰符,用于增强性能,特别是在移动端。这对应于在 JavaScript 中对事件调用 addEventListener 方法时使用 { passive: true } 选项。

当你添加了 .passive 修饰符后,你就告诉浏览器你不打算阻止事件的默认行为。这样,浏览器就可以在事件处理程序运行的同时做一些性能优化。这对于一些高频触发的事件(例如滚动或触摸事件)特别有用。

下面是一个简单的示例:

<body>
  <div id="app">
    <div @scroll.passive="onScroll">
      内容
    </div>
  </div>
  <script>
    new Vue({
      el: '#app',
      methods: {
        onScroll(event) {
          console.log('Scrolled!');
          // 处理滚动事件
        }
      }
    });
  </script>
</body>

在这个例子中,onScroll 方法会在 <div> 发生滚动时被触发。使用 .passive 修饰符可以在处理滚动事件的同时,优化页面的滚动性能。

但是请注意,因为 .passive 修饰符告诉浏览器你不会阻止事件的默认行为,所以如果你尝试在使用 .passive 修饰符的事件处理程序中调用 event.preventDefault(),这将无效。

10.4.2 按键修饰符 #

在 Vue2 中,为了处理键盘事件,Vue 提供了一些按键修饰符。这些修饰符允许你在事件触发时更加精确地处理不同的按键。这些修饰符可以在 v-on 或其缩写 @ 后使用。

以下是一些常用的按键修饰符:

  1. .enter:只有在按下 Enter 键时才触发事件处理函数
  2. .tab:只有在按下 Tab 键时才触发事件处理函数
  3. .delete:只有在按下 Delete 或 Backspace 键时才触发事件处理函数
  4. .esc:只有在按下 Esc 键时才触发事件处理函数
  5. .space:只有在按下 Space 键时才触发事件处理函数
  6. .up:只有在按下 Up 键时才触发事件处理函数
  7. .down:只有在按下 Down 键时才触发事件处理函数
  8. .left:只有在按下 Left 键时才触发事件处理函数
  9. .right:只有在按下 Right 键时才触发事件处理函数

以下是一个使用 .enter 按键修饰符的示例:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <input @keyup.enter="submit" />
  </div>
  <script>
    new Vue({
      el: '#app',
      methods: {
        submit() {
          console.log('Enter key was pressed');
        }
      }
    })
  </script>
</body>
</html>

在这个例子中,当在输入框中按下 Enter 键时,submit 方法会被调用。

你也可以使用按键的 ASCII 码作为修饰符,例如 `@keyup.13="submit",其中 13 是 Enter 键的 ASCII 码。但是,使用名字(如.enter`)通常更容易理解。

请注意,Vue2 也支持系统修饰符(如 .ctrl.alt.shift.meta),可以与按键修饰符一起使用,例如 `@keydown.ctrl.enter="submit",这样只有在同时按下 Ctrl 键和 Enter 键时,submit` 方法才会被调用。

10.4.3 鼠标修饰符 #

在 Vue2 中,为了处理鼠标按钮事件,Vue 提供了一些鼠标按钮修饰符。这些修饰符使你能够在事件触发时更精确地处理不同的鼠标按钮。

以下是一些常用的鼠标按钮修饰符:

  1. .left:只有在按下鼠标左键时才触发事件处理函数
  2. .right:只有在按下鼠标右键时才触发事件处理函数
  3. .middle:只有在按下鼠标中键时才触发事件处理函数

以下是一个使用 .left 鼠标按钮修饰符的示例:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <button @click.left="leftClick">Click me</button>
  </div>

  <script>
    new Vue({
      el: '#app',
      methods: {
        leftClick() {
          console.log('Left mouse button was clicked');
        }
      }
    })
  </script>
</body>
</html>

在这个例子中,我们创建了一个 Vue 实例,附加到了 ID 为 app 的 div 上。当点击按钮并且是用鼠标左键点击时,leftClick 方法会被调用,控制台将打印出 'Left mouse button was clicked'。

需要注意的是,.left.right.middle 这些修饰符只在 Vue 2.2.0 及以上版本中可用。

11. this #

在 Vue.js 中,methods 是一个特殊的对象,它包含了可以在 Vue 组件中使用的方法。在这些方法中,this 关键字被自动绑定到当前 Vue 实例。

举个例子,假设我们有一个 Vue 组件,它包含一个 data 属性和一个 methods 属性:

new Vue({
  el: '#app',
  data: {
    message: 'Hello, Vue!'
  },
  methods: {
    sayHello: function() {
      console.log(this.message);
    }
  }
});

在这个例子中,sayHello 方法中的 this 指向的是 Vue 实例,所以 this.message 就是 data 中的 message。因此,当我们调用 sayHello 方法时,它将会打印出 'Hello, Vue!'

需要注意的是,this 的绑定只在 Vue 组件的生命周期和事件处理器中是自动的。如果你在其他地方,比如 setTimeoutsetInterval 的回调函数中使用 this,你可能需要手动绑定 this,或者使用箭头函数来保留 this 的绑定。例如:

methods: {
  sayHello: function() {
    setTimeout(() => {
      console.log(this.message);
    }, 1000);
  }
}

在这个例子中,我们使用了箭头函数,它不会创建自己的 this 绑定,而是继承了外层 sayHello 方法的 this 绑定。因此,this.message 仍然是 data 中的 message,即使我们在 setTimeout 的回调函数中使用它。

12. v-model #

在 Vue.js 中,v-model 是一个非常重要的指令,它可以用来创建双向数据绑定。这意味着,当你在表单元素(如 input 或 textarea)上使用 v-model 指令时,这些元素的值将与 Vue 实例的数据属性绑定在一起。当我们在表单元素中输入值时,Vue 实例的数据属性将会自动更新;反之,当 Vue 实例的数据属性发生变化时,表单元素的值也会相应地更新。

这是一个简单的例子:

<body>
  <div id="app">
    <input v-model="message" placeholder="edit me">
    <p>Message is: {{ message }}</p>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        message: 'Hello'
      }
    })
  </script>
</body>
</html>

在上述代码中,input 元素的值与 message 数据属性绑定在一起。当你在 input 元素中输入文本时,message 的值会自动更新,反之亦然。

v-model 在内部实际上是一种语法糖,它将 v-bind 用于 prop 和 v-on 用于事件组合在一起。例如,对于一个 input 元素,v-model="message" 在内部等同于 v-bind:value="message" v-on:input="message = $event.target.value"

使用 v-bindv-on 的指令组合来替换 v-model 指令,你的代码将会如下所示:

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <input v-bind:value="message" v-on:input="updateMessage($event)" placeholder="edit me">
    <p>Message is: {{ message }}</p>
  </div>
  <script>
    new Vue({
      el: '#app',
      data() {
        return {
          message: ''
        }
      },
      methods: {
        updateMessage(event) {
          this.message = event.target.value;
        }
      }
    })
  </script>
</body>

这里,v-bind:value="message"message 数据属性绑定到 input 元素的值。然后,v-on:input="updateMessage($event)" 会在每次 input 事件触发(也就是用户输入内容)时更新 message 的值。

为了实现 v-on:input 的功能,我们在 Vue 实例的 methods 对象中定义了一个名为 updateMessage 的方法。这个方法接受一个事件对象作为参数(这里我们用 $event 表示这个事件对象),然后将 message 数据属性设置为 input 元素的当前值(也就是事件对象的 target.value 属性)。

13. v-once #

在 Vue 2 中,v-once 是一个特殊的指令,它能够阻止元素和所有的子元素被再次编译。这意味着,元素和所有子元素的 Vue 指令将只会被编译一次,然后将保持原样,不会再发生任何改变。这在你想要优化更新性能的时候会很有用,因为它可以防止不必要的重渲染。

以下是一个简单的示例:

<body>
  <div id="app">
    <span v-once>This will never change: {{msg}}</span>
  </div>
  <script>
    var app = new Vue({
      el: '#app',
      data: {
        msg: 'hello'
      }
    })
    app.msg = 'world'
  </script>
</body>

在上述例子中,msg 数据属性的值将被渲染到 span 元素中,但是因为 v-once 的存在,无论 msg 的值怎么变化,span 元素的内容都不会改变。换句话说,span 元素及其所有子元素的内容和行为在被编译和渲染一次后,将不再改变。

v-once 指令在你知道一些内容永远不会改变,或者出于性能考虑希望减少渲染成本时非常有用。

14. v-show #

在 Vue 2 中,v-show 是一个用来控制元素可见性的指令。与 v-if 指令不同,v-show 指令不会真正地添加或移除元素,而是通过 CSS 的 display 属性来切换元素的可见性。

v-show 的表达式的值为 true 时,元素会被显示;当值为 false 时,元素会被隐藏。但无论如何,元素始终在 DOM 中。

以下是一个简单的示例:

<body>
  <div id="app">
    <div v-show="isShowing">Hello Vue!</div>
    <button v-on:click="toggleVisibility">Toggle visibility</button>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        isShowing: true
      },
      methods: {
        toggleVisibility: function () {
          this.isShowing = !this.isShowing;
        }
      }
    })
  </script>
</body>

在这个例子中,当 isShowing 的值为 true 时,div 元素将被显示;当 isShowing 的值为 false 时,div 元素将被隐藏。我们还有一个按钮,点击这个按钮会调用 toggleVisibility 方法,该方法将切换 isShowing 的值。

v-show 指令对于频繁切换元素的可见性来说是一个好的选择,因为它不会导致频繁地添加和移除元素。然而,如果你的元素在初始渲染时就不应该可见,或者不经常改变可见性,那么使用 v-if 指令可能会更好,因为 v-if 指令不会在元素不可见时渲染和占用 DOM 资源。

15. v-if #

在 Vue 2 中,v-if 是一个条件指令,用于根据表达式的真假值来决定是否渲染元素。

如果 v-if 的表达式的值为 true,那么元素将被渲染;如果值为 false,那么元素将不被渲染。

以下是一个简单的示例:

<body>
  <div id="app">
    <div v-if="showMessage">Hello Vue!</div>
    <button v-on:click="toggleMessage">Toggle message</button>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        showMessage: true
      },
      methods: {
        toggleMessage: function () {
          this.showMessage = !this.showMessage;
        }
      }
    })
  </script>
</body>

在这个例子中,当 showMessage 的值为 true 时,div 元素将被渲染;当 showMessage 的值为 false 时,div 元素将不被渲染。我们还有一个按钮,点击这个按钮会调用 toggleMessage 方法,该方法将切换 showMessage 的值。

需要注意的是,v-if 是“惰性”的,也就是说,如果在初始渲染时条件为 false,那么这个元素将不会被渲染,也不会执行任何与之相关的渲染成本。只有当条件第一次变为 true 时,才会开始渲染元素。

另外,你还可以使用 v-elsev-else-if 指令来添加更多的条件分支。

<body>
  <div id="app">
    <div v-if="value === 'A'">Value is A</div>
    <div v-else-if="value === 'B'">Value is B</div>
    <div v-else>Value is neither A nor B</div>
    <button v-on:click="changeValue">Change value</button>
  </div>
  <script>
    new Vue({
      el: '#app',
      data: {
        value: 'A'
      },
      methods: {
        changeValue: function () {
          if (this.value === 'A') {
            this.value = 'B';
          } else if (this.value === 'B') {
            this.value = 'C';
          } else {
            this.value = 'A';
          }
        }
      }
    })
  </script>
</body>

16. v-for #

v-for 是 Vue.js 中的一个内置指令,它用于渲染一个列表。它需要以 item in items 形式的特殊语法,其中 items 是原始数据数组,item 是数组元素迭代的别名。

下面是一个基本的使用示例:

<body>
  <div id="app">
    <ul >
      <li v-for="item in items">
        {{ item.message }}
      </li>
    </ul>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        items: [
          { message: 'hello' },
          { message: 'world' }
        ]
      }
    })
  </script>
</body>

在这个例子中,Vue.js 会为 items 数组的每个元素创建一个 <li> 元素,并将元素的内容设置为该元素的 message 属性的值。

v-for 块中,我们拥有对父作用域属性的完全访问权。v-for 还支持一个可选的第二个参数为当前项的索引。

<body>
  <div id="app">
    <ul id="container">
      <li v-for="(item, index) in items">
        {{ index }} - {{ item.message }}
      </li>
    </ul>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        items: [
          { message: 'hello' },
          { message: 'world' }
        ]
      }
    })
  </script>
</body>

在这个例子中,我们不仅显示了每个元素的 message 属性的值,还显示了元素在 items 数组中的索引。

需要注意的一点是,当 Vue.js 更新使用 v-for 渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是简单地复用此处每个元素,这是一个默认的性能优化。要强制其重新排序元素,你需要提供一个唯一的 key 属性。

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <ul id="container">
      <li v-for="(item, index) in items" :key="item.id">
        {{ index }} - {{ item.message }}
      </li>
    </ul>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        items: [
          { message: 'hello' },
          { message: 'world' }
        ]
      }
    })
  </script>
</body>

在这个例子中,每个 item 都有一个唯一的 id,这个 id 被用作 key。这样,即使 items 数组的顺序被改变,Vue.js 也会重新排序 DOM 元素以匹配 items 数组的顺序。

17. template #

在 Vue 中,<template> 标签是一种基础结构,用于声明 Vue 实例或组件的 HTML 模板。它并不会被包含在最终的 HTML 结构中,它仅仅是一个声明模板的容器。它的内容会被作为 Vue 实例或组件的模板进行编译。

<template> 标签内部可以使用 Vue 的模板语法,包括数据插值、指令、过滤器等等。

以下是一个基础的 Vue 实例使用 <template> 标签的例子:

<body>
  <div id="app">
    <ul id="container">
      <template v-for="(item, index) in items">
        <li >
          {{ index }} - {{ item.message }}
        </li>
        <li>
          {{ index }} - {{ item.message }}
        </li>
      </template>
    </ul>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        items: [
          { message: 'hello' },
          { message: 'world' }
        ]
      }
    })
  </script>
</body>

在这个例子中,<template> 标签中的 <p>{{ message }}</p> 就是 Vue 实例的模板。{{ message }} 是 Vue 的数据插值语法,它会被 Vue 实例的 data 中的 message 属性的值替换。

18. :class #

在 Vue.js 中,你可以使用 v-bind:class(或简写为 :class)指令来动态绑定 CSS 类名。v-bind:class 可以接收一个对象或数组作为参数,提供一种灵活的方式来设置元素的 CSS 类。

  1. 对象语法:

你可以传递一个对象给 v-bind:class,以动态地切换 CSS 类:

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
  <style>
    .active{
     color: green;
    }
  </style>
</head>

<body>
  <div id="app">
    <div :class="{ active: isActive }">内容</div>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        isActive: true
      }
    })
  </script>
</body>

在这个例子中,.active 类的存在与否将取决于数据属性 isActive 的 truthiness(即当 isActive 的值为 truthy 时,.active 类存在,否则不存在)。

你也可以在对象中添加多个字段来动态切换多个类:

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
  <style>
    .active{
     color: green;
    }
    .inActive{
     color: red;
    }
  </style>
</head>

<body>
  <div id="app">
    <div :class="{ active: isActive ,inActive: !isActive }">内容</div>
    <button @click="changeActive">切换</button>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        isActive: true
      },
      methods: {
        changeActive() {
          this.isActive = !this.isActive
        }
      }
    })
  </script>
</body>
  1. 数组语法:

你可以传递一个数组给 v-bind:class,以应用一个类列表:

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
  <style>
    .basic{
      background-color: gray;
    }
    .active{
     color: green;
    }
  </style>
</head>

<body>
  <div id="app">
    <div :class="[{ active: isActive },'basic']">内容</div>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        isActive: true
      }
    })
  </script>
</body>

19. :style #

在 Vue.js 中,你可以使用 v-bind:style(或简写为 :style)指令来动态地绑定内联样式。与 v-bind:class 类似,你可以使用对象语法或数组语法来绑定样式。

  1. 对象语法:

你可以直接通过对象语法来设置样式。对象内的属性名就是样式名,可以使用 camelCase 或 kebab-case(需要用引号括起来)。

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
</head>

<body>
  <div id="app">
    <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">内容</div>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        activeColor: 'red',
        fontSize: 30
      }
    })
  </script>
</body>

在这个例子中,字体颜色将会被设置为 activeColor,字体大小将会被设置为 fontSize

  1. 数组语法:

你也可以传递一个数组给 v-bind:style,以应用多个样式对象:

<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <div :style="[styleObject1, styleObject2]">内容</div>
  </div>
  <script>
    var vm = new Vue({
      el: '#app',
      data: {
        styleObject1: {
          color: 'red',
          fontSize: '30px'
        },
        styleObject2: {
          fontWeight: 'bold'
        }
      }
    })
  </script>
</body>

在这个例子中,styleObject1styleObject2 都是数据对象,它们的属性将被用作 CSS 样式。

20. Todos #

以下是一个使用 Vue.js 2 编写的简单的待办事项应用的示例:

HTML 部分:

<body>
  <div id="todo-app">
    <input v-model="newTodo" @keyup.enter="addTodo">
    <ul>
      <li v-for="(todo, index) in todos" :key="index">
        {{ todo.text }}
        <button @click="removeTodo(index)">X</button>
      </li>
    </ul>
  </div>
  <script>
    var app = new Vue({
      el: '#todo-app',
      data: {
        newTodo: '',
        todos: []
      },
      methods: {
        addTodo: function () {
          if (this.newTodo.trim() !== '') {
            this.todos.push({ text: this.newTodo });
            this.newTodo = '';
          }
        },
        removeTodo: function (index) {
          this.todos.splice(index, 1);
        }
      }
    });
  </script>
</body>

在这个应用中,你可以在输入框中键入一个待办事项,然后按 Enter 键添加待办事项。每个待办事项都会被添加到一个列表中,并且每个待办事项旁边都有一个按钮,你可以点击这个按钮来删除待办事项。

以下是这个应用的工作原理:

addTodo 方法会检查 newTodo 是否为空,如果不为空,就把 newTodo 添加到 todos 数组,然后清空 newTodoremoveTodo 方法会接收一个索引,然后从 todos 数组中删除对应索引的待办事项。

21. 选项卡 #

当然可以,以下是一个使用 Vue2 的简单选项卡组件的示例:

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
</head>
<body>
  <div id="app">
    <div>
      <button v-for="tab in tabs" :key="tab" @click="activeTab = tab">
        {{ tab }}
      </button>
    </div>
    <div v-if="activeTab === 'Tab 1'">
      Content of Tab 1
    </div>
    <div v-if="activeTab === 'Tab 2'">
      Content of Tab 2
    </div>
    <div v-if="activeTab === 'Tab 3'">
      Content of Tab 3
    </div>
  </div>

  <script>
    new Vue({
      el: '#app',
      data: {
        tabs: ['Tab 1', 'Tab 2', 'Tab 3'],
        activeTab: 'Tab 1'
      }
    })
  </script>
</body>
</html>

在这个例子中,我们创建了一个 Vue 实例,附加到了 ID 为 app 的 div 上。我们有一个名为 tabs 的数据项,包含三个选项卡的名称。activeTab 数据项保存当前活动的选项卡的名称。

我们用 v-for 指令为每个选项卡创建一个按钮,并在点击按钮时将 activeTab 设置为该选项卡的名称。然后,我们根据 activeTab 的值显示相应选项卡的内容。这是一个基本的选项卡系统的实现,你可以根据需要添加更多的功能或样式。

22. 用户注册表单 #

22.1 表单修饰符 #

Vue.js 提供了一些修饰符来帮助处理表单:

  1. .lazy: 在默认情况下,v-model在每次 input 事件触发后将输入框的值与数据进行同步。你可以添加 lazy 修饰符,从而将同步改为在 change 事件发生时:

    <!-- 在“change”时而非“input”时更新 -->
    <input v-model.lazy="msg" >
    
  2. .number: 如果想自动将用户的输入值转为 Number 类型(如果原始输入能够被 parseFloat() 解析的话),你可以给 v-model 添加 number 修饰符:

    <input v-model.number="age" type="number">
    

    这通常很有用,因为即使在 type="number" 输入框中,HTML 输入元素的值也总是会返回字符串。如果你想要用户输入的就是一个被解析为数字的值,那就使用 v-model.number

  3. .trim: 如果你想自动过滤用户输入的首尾空格,你可以给 v-model 添加 trim 修饰符:

    <input v-model.trim="msg">
    

这些修饰符也可以链式调用:

<input v-model.trim.number.lazy="value">

在上述例子中,用户的输入将会:

22.2 用户注册表单 #

<!DOCTYPE html>
<html>
<head>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
  <style>
    #app {
      display: flex;
      justify-content: center;
      flex: 1;
    }
  </style>
</head>
<body>
  <div id="app">
    <form @submit.prevent="submitForm" style="width:15%">
      <label>全名:</label>
      <input type="text" v-model.lazy.trim="form.name"><br/>
      <label>邮箱:</label>
      <input type="email" v-model.trim="form.email"><br/>
      <label>密码:</label>
      <input type="password" v-model="form.password"><br/>
      <label>年龄:</label>
      <input type="number" v-model.number="form.age"><br/>
      <label>性别:</label>
      <input type="radio" id="man" value="man" v-model="form.gender">
      <label for="man"></label>
      <input type="radio" id="female" value="female" v-model="form.gender">
      <label for="female"></label><br/>
      <label>国家:</label>
      <select v-model="form.country">
        <option v-for="country in COUNTRIES" :value="country.value" :key="country.value">{{ country.title }}</option>
      </select><br/>
      <label>爱好:</label>
      <template v-for="hobby in HOBBIES" :key="hobby.value">
        <input type="checkbox" :id="hobby.value" :value="hobby.value" v-model="form.hobbies">
        <label :for="hobby.value">{{hobby.title}}</label>
      </template><br/>
      <label>简介:</label>
      <textarea v-model="form.bio"></textarea><br/>
      <button type="submit">注册</button>
    </form>
    <div style="width:15%">
      <p>全名: {{ form.name }}</p>
      <p>邮箱: {{ form.email }}</p>
      <p>密码: {{ form.password }}</p>
      <p>年龄: {{ form.age }}</p>
      <p>性别: {{ form.gender }}</p>
      <p>国家: {{ form.country }}</p>
      <p>爱好: {{ form.hobbies.join(', ') }}</p>
      <p>简介: {{ form.bio }}</p>
    </div>
  </div>
  <script>
    const COUNTRIES = [
      { value: 'cn', title: '中国' },
      { value: 'us', title: '美国' },
      { value: 'uk', title: '英国' }
    ];
    const HOBBIES = [
      { value: 'football', title: '足球' },
      { value: 'basketball', title: '篮球' },
      { value: 'baseball', title: '棒球' }
    ]
    new Vue({
      el: '#app',
      data: function
      () {
        return {
          HOBBIES,
          COUNTRIES,
          form: {
            name: '',
            email: '',
            password: '',
            gender: 'man',
            country: 'cn',
            hobbies: [],
            bio: '',
            age: 18,
          },
          checkAll: false,
          isIndeterminate: true,
        }
      },
      methods: {
        submitForm() {
          console.log(this.form);
        }
      }
    })
  </script>
</body>
</html>

22.3 ElementUI #

<!DOCTYPE html>
<html>

<head>
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <style>
    #app {
      display: flex;
      justify-content: center;
      flex: 1;
    }
  </style>
</head>

<body>
  <div id="app">
    <el-form @submit.native.prevent="submitForm" style="width:25%">
      <el-form-item label="全名:">
        <el-input v-model.lazy.trim="form.name"></el-input>
      </el-form-item>
      <el-form-item label="邮箱:">
        <el-input v-model.trim="form.email"></el-input>
      </el-form-item>
      <el-form-item label="密码:">
        <el-input v-model="form.password" show-password></el-input>
      </el-form-item>
      <el-form-item label="年龄:">
        <el-input-number 
          v-model.number="form.age" 
          controls-position="right"></el-input-number>
      </el-form-item>
      <el-form-item label="性别:">
        <el-radio-group v-model="form.gender">
          <el-radio label="man"></el-radio>
          <el-radio label="female"></el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item label="国家:">
        <el-select v-model="form.country" placeholder="请选择一个国家">
          <el-option v-for="country in COUNTRIES" 
            :label="country.title" 
            :value="country.value"
            :key="country.value"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item label="爱好:">
        <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选</el-checkbox>
        <div style="margin: 15px 0;"></div>
        <el-checkbox-group v-model="form.hobbies" @change="handleCheckedHobbiesChange">
          <el-checkbox v-for="hobby in HOBBIES" 
            :label="hobby.value" 
            :key="hobby.value">{{hobby.title}}</el-checkbox>
        </el-checkbox-group>
      </el-form-item>
      <el-form-item label="简介:">
        <el-input type="textarea" v-model="form.bio"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" native-type="submit">注册</el-button>
      </el-form-item>
    </el-form>
    <div style="width:25%;margin-left:50px">
      <h2>表单预览:</h2>
      <p>全名: {{ form.name }}</p>
      <p>邮箱: {{ form.email }}</p>
      <p>密码: {{ form.password }}</p>
      <p>年龄: {{ form.age }}</p>
      <p>性别: {{ form.gender }}</p>
      <p>国家: {{ form.country }}</p>
      <p>爱好: {{ form.hobbies.join(', ') }}</p>
      <p>简介: {{ form.bio }}</p>
    </div>
  </div>
  <script>
    const COUNTRIES = [
      { value: 'cn', title: '中国' },
      { value: 'us', title: '美国' },
      { value: 'uk', title: '英国' }
    ];
    const HOBBIES = [
      { value: 'football', title: '足球' },
      { value: 'basketball', title: '篮球' },
      { value: 'baseball', title: '棒球' }
    ]
    new Vue({
      el: '#app',
      data: function () {
        return {
          HOBBIES,
          COUNTRIES,
          form: {
            name: '',
            email: '',
            password: '',
            gender: 'man',
            country: 'cn',
            hobbies: [],
            bio: '',
            age: 18,
          },
          checkAll: false,
          isIndeterminate: true,
        }
      },
      methods: {
        submitForm() {
          console.log(this.form);
        },
        handleCheckAllChange(val) {
          this.form.hobbies = val ? HOBBIES.map(hobby => hobby.value) : [];
          this.isIndeterminate = false;
        },
        handleCheckedHobbiesChange(value) {
          let checkedCount = value.length;
          this.checkAll = checkedCount === HOBBIES.length;
          this.isIndeterminate = checkedCount > 0 && checkedCount < HOBBIES.length;
        }
      }
    })
  </script>
</body>
</html>

23.自定义指令 #

在 Vue.js 中,自定义指令是一种可以复用的功能,这个功能可以直接应用在 HTML 元素上。自定义指令提供了一种方法来封装 DOM 操作,这样你就可以在应用程序的多个组件中重用这些操作。

每个 Vue 实例在创建时都会调用 Vue.directive(name, definition) 来全局注册自定义指令。其中,name 是指令的名字,而 definition 可以是一个对象或函数。如果是对象,那么这个对象可以包含一些生命周期钩子函数,这些函数在指令的不同阶段被调用。这些阶段包括:

23.1 bind #

在 Vue 中,binding 是一个对象,传递给指令钩子函数的第二个参数。它包含了一些有用的信息,你可以在指令中使用这些信息。以下是 binding 对象的属性:

  1. name:指令的名字,不包括 v- 前缀。
  2. value:指令的绑定值,例如 v-my-directive="1 + 1" 中的 2
  3. oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
  4. expression:字符串形式的指令表达式,例如 v-my-directive="1 + 1" 中的 "1 + 1"
  5. arg:传给指令的参数,可选,例如 v-my-directive:foo 中的 "foo"
  6. modifiers:一个包含修饰符的对象,例如 v-my-directive.foo.bar 中的 { foo: true, bar: true }

因此,binding 提供了一种灵活的方式,可以用来在自定义指令中处理各种情况。例如,你可以根据 binding.valuebinding.oldValue 的不同来决定是否更新 DOM,或者根据 binding.modifiers 的存在与否来改变指令的行为。

  <body>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
    <div id="app">
     <div v-draggable:absolute.ctrl="styleObj"></div>
    </div>
    <script>
        //定义一个指令 名字叫draggable,再定义一个对象,里面编写很多钩子函数
        //def 指的就是指令的定义对象
        //name: "draggable" 就是指令的名字,就是v-后面这个单词
        //rawName: "v-draggable" 就是加上前面的v-的原生的指令名称
        //modifiers: {disable:true} 指令修饰符
        Vue.directive('draggable',{
            //这就是一个钩子函数,你不需要自己调用此函数,只需要写好放在这就可以
            bind(el,binding){
                console.log(binding);
                //如果有disable这个修饰符,那就直接返回,不再去支持拖动
                if(binding.modifiers.disable)return;
                let isCtrl = false;
                //获取指令的参数
                el.style.position=binding.arg;
                //通过binding.value获取样式对象
                let {value:styleObj} = binding;//let styleObj=binding.value;
                //把样式对象里的样式赋给真实的DOM元素
                for(let key in styleObj){
                    el.style[key]=styleObj[key];
                }
                let isDragging = false;//当前是否正在拖拽
                let offsetX,offsetY;
                //给当前这个DIV添加鼠标按下的事件
                el.addEventListener('mousedown',(event)=>{
                    //如果需要按下ctrl键,但当前并没有按下ctrl键的话就不能拖
                    if(binding.modifiers.ctrl && !isCtrl){
                        return;
                    }
                    offsetX=event.offsetX;
                    offsetY=event.offsetY;
                    isDragging=true;
                });
                //当我们移动鼠标的时候,计算元素的最新的位置 
                el.addEventListener('mousemove',(event)=>{
                   if(isDragging){
                    el.style.left = event.clientX-offsetX +'px';
                    el.style.top = event.clientY-offsetY+'px';
                   }
                });
                //当松下的时候,停止拖动
                el.addEventListener('mouseup',(event)=>{
                    isDragging=false;
                });
                //监听按键事件,当按下ctrl键的话就把isCtrl=true
                document.addEventListener('keydown',(event)=>{
                    if(event.key==='Control')isCtrl=true;
                });
                  //监听按键事件,当松开ctrl键的话就把isCtrl=false
                document.addEventListener('keyup',(event)=>{
                    if(event.key==='Control')isCtrl=false;
                });
            }
        });
        var vm = new Vue({
            el:'#app',
            data:{
                styleObj:{width:'100px',height:'100px',backgroundColor: 'green'}
            }
        })
    </script>
  </body>

23.2 inserted #

自定义指令的使用方法非常简单,只需要在你想要添加自定义行为的 HTML 元素上添加 v-your-directive。例如,如果你的指令名字是 focus,那么你可以在任何元素上添加 v-focus,这样就能在该元素上应用这个指令的行为。

这里有一个自定义指令的简单示例:

Vue.directive('focus', {
  inserted: function(el) {
    // 聚焦元素
    el.focus();
  }
});

然后在模板中使用它:

<input v-focus>

这样,当页面加载并且该指令被插入到 DOM 中时,这个元素就会自动获取焦点。

23.3 update #

在 Vue 2 中,自定义指令的 update 钩子函数在元素的数据发生变化时会被调用。以下是一个使用 update 钩子的例子,这个自定义指令 v-color 会根据绑定的值改变元素的背景颜色:

  <body>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
    <div id="app">
        <div v-color="color">color</div>
        <button @click="changeToRed">变红</button>
    </div>
    <script>
        Vue.directive('color',{
           //当本DOM元素创建之后先调用bind
           //然后会把DOM元素添加到父DOM上进行渲染。inserted
            bind(el,binding){
                el.style.color = binding.value;
            },
            //在元素的数据或者说值发生变化时调用
            update(el,binding){
                el.style.color = binding.value;
            }
        });
        var vm = new Vue({
            el:'#app',
            data:{color:'green'},
            methods:{
              changeToRed(){
                this.color = 'red'
              }
            }
        })
    </script>
  </body>

23.4 componentUpdated #

componentUpdated 钩子函数会在包含组件的 VNode 及其子 VNode 全部更新后调用。

这里有一个例子,使用 componentUpdated 钩子函数在组件更新后,检查元素的内容,并根据长度改变元素的背景颜色:

  <body>
  <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
    <div id="app">
      <input v-checklength="username" v-model="username">
    </div>
    <script>
      //检查内容的长度,如果小于6位变红,大于等于6位变绿
        Vue.directive('checklength',{
          bind(el){
            el.style.color ='gray';
          },
          componentUpdated(el,binding){
            let {value} = el;
            if(value.length<6){
              el.style.color ='red';
            }else{
              el.style.color ='green';
            }
          }

        });
        var vm = new Vue({
            el:'#app',
            data:{username:''}
        })
    </script>
  </body>

在 Vue 的自定义指令中,updatecomponentUpdated 是两个钩子函数,它们在指令所在的元素更新时被调用,但具体的调用时间和调用条件有所不同。

  1. update 钩子:当指令所在的元素更新时调用,但并不保证其子节点也已更新。这个钩子函数会在指令所在组件的 VNode 更新时调用,但在其子 VNode 更新之前调用。

  2. componentUpdated 钩子:指令所在的元素及其子节点都更新完成后调用。这个钩子函数会在指令所在组件的 VNode 及其子 VNode 更新完成后调用。

下面是一个示例,使用 updatecomponentUpdated 钩子跟踪并打印更新的次数:

let updateCount = 0;
let componentUpdatedCount = 0;

Vue.directive('count-updates', {
  update: function(el, binding, vnode, oldVnode) {
    updateCount++;
    console.log(`Update count: ${updateCount}`);
  },
  componentUpdated: function(el, binding, vnode, oldVnode) {
    componentUpdatedCount++;
    console.log(`Component Updated count: ${componentUpdatedCount}`);
  }
});

在这个示例中,每当指令所在的元素或组件更新时,update 钩子就会增加 updateCount 并打印更新的次数。同样,每当指令所在的元素和其子节点都更新完成时,componentUpdated 钩子就会增加 componentUpdatedCount 并打印更新的次数。

23.5 unbind #

在 Vue 中,unbind 钩子函数在指令从元素上解绑时被调用。这个钩子函数通常被用来执行清理操作,比如移除事件监听器。

这是一个使用 unbind 钩子函数的示例,该指令 v-click-outside 用于点击元素外部时触发某个操作:

<body>
    <script src="https://unpkg.com/vue@2.6.14/dist/vue.js"></script>
    <div id="app">
        <div v-draggable:absolute.ctrl="styleObj"></div>
    </div>
    <script>
        //定义一个指令 名字叫draggable,再定义一个对象,里面编写很多钩子函数
        //def 指的就是指令的定义对象
        //name: "draggable" 就是指令的名字,就是v-后面这个单词
        //rawName: "v-draggable" 就是加上前面的v-的原生的指令名称
        //modifiers: {disable:true} 指令修饰符
        Vue.directive('draggable', {
            //这就是一个钩子函数,你不需要自己调用此函数,只需要写好放在这就可以
            bind(el, binding) {
                console.log(binding);
                //如果有disable这个修饰符,那就直接返回,不再去支持拖动
                if (binding.modifiers.disable) return;
                let isCtrl = false;
                //获取指令的参数
                el.style.position = binding.arg;
                //通过binding.value获取样式对象
                let { value: styleObj } = binding;//let styleObj=binding.value;
                //把样式对象里的样式赋给真实的DOM元素
                for (let key in styleObj) {
                    el.style[key] = styleObj[key];
                }
                let isDragging = false;//当前是否正在拖拽
                let offsetX, offsetY;
                //给当前这个DIV添加鼠标按下的事件
                el.addEventListener('mousedown', (event) => {
                    //如果需要按下ctrl键,但当前并没有按下ctrl键的话就不能拖
                    if (binding.modifiers.ctrl && !isCtrl) {
                        return;
                    }
                    offsetX = event.offsetX;
                    offsetY = event.offsetY;
                    isDragging = true;
                });
                //当我们移动鼠标的时候,计算元素的最新的位置 
                el.addEventListener('mousemove', (event) => {
                    if (isDragging) {
                        el.style.left = event.clientX - offsetX + 'px';
                        el.style.top = event.clientY - offsetY + 'px';
                    }
                });
                //当松下的时候,停止拖动
                el.addEventListener('mouseup', (event) => {
                    isDragging = false;
                });
                el.onKeyDown = (event) => {
                    if (event.key === 'Control') isCtrl = true;
                }
                //监听按键事件,当按下ctrl键的话就把isCtrl=true
                document.addEventListener('keydown', el.onKeyDown);
                el.onKeyUp = (event) => {
                    if (event.key === 'Control') isCtrl = false;
                }
                //监听按键事件,当松开ctrl键的话就把isCtrl=false
                document.addEventListener('keyup', el.onKeyUp);
            },
            unbind(el, binding) {
                console.log('unbind')
                document.removeEventListener('keydown', el.onKeyDown);
                document.removeEventListener('keyup', el.onKeyUp);
             }

        });
        var vm = new Vue({
            el: '#app',
            data: {
                styleObj: { width: '100px', height: '100px', backgroundColor: 'green' }
            }
        })
        vm.$destroy();
    </script>
</body>

在这个例子中,当点击发生在元素外部时,会调用 handleClickOutside 方法。当 v-click-outside 指令从元素上解绑时,事件监听器会被移除,以避免不必要的内存使用。

在 Vue.js 中,unbind 钩子函数会在以下情况下被触发:

  1. 指令从元素上被解绑,即元素或组件被销毁。
  2. Vue 组件在被销毁的过程中(例如,由于 v-if 条件不再满足,或者组件不再被路由使用)。
  3. 当用于绑定指令的 Vue 实例被销毁时。

这个钩子函数常常用于执行清理操作,比如移除指令在 bind 钩子函数中绑定的事件监听器,以防止内存泄露。

24.钩子函数 #

在原生 JavaScript 中,并没有内置的 "钩子函数" 概念,但我们可以借用该概念来理解一些特定的功能。

"钩子函数" 的概念主要是指在程序的特定阶段或事件发生时被自动调用的函数。在某种意义上,你可以将事件处理程序视为一种钩子函数。例如,你可能会在按钮被点击时运行一些代码:

button.addEventListener('click', function() {
  console.log('Button clicked!');
});

在这个例子中,函数就是一个 "钩子",它 "钩住" 了 click 事件,并在每次点击按钮时运行。

然而,真正的 "钩子函数" 更常见于那些允许你插入自己的代码以影响其行为的库和框架。这些库和框架会提供一些函数或方法,在特定的时间或在某些事件发生时,它们会被自动调用。你可以在这些函数或方法中添加自己的代码,从而改变程序的行为。这就是所谓的 "钩子函数"。