Vue学习笔记(二)基于Vue2的TodoList待办事项案例 | localStorage本地存储 | Vue2的发布与订阅 | Vue2支持的动画类 |JavaScript原型对象

尤你 2022-06-23 17:39:00 阅读数:696

学习vue笔记vue2

一、参考资料


二、运行环境


  • Windows11
  • Visual Studio Code v2022
  • Node.js v16.5.01
  • Vue/cli v5.0.6

三、案例需求分析


基于上一次的学习,最终使用Vue-CLI脚手架实现的TodoList案例效果如下图所示:

在这里插入图片描述
现对案例提出几个需求:

  1. 确保页面刷新后,当前的待办事项数据不会丢失。
  2. 提供编辑功能,可以修改已经添加的待办事项。
  3. 为熟悉Vue2动画样式的简单使用,给TodoList添加一个动态的效果。

最终实现的效果如下:

在这里插入图片描述

四、相关知识


4.1 JavaScript 原型对象

参考资料:菜鸟教程 JavaScript 高阶

JavaScript中的对象构造器

function Person(name, age) {

this.name = name;
this.age = age;
}
var me = new Person("uni",22);
var myGirl = new Person("who", 22);

在JavaScript中已存在构造器的实例对象中是不能添加新的属性

比如要添加一个属性 hobby 表示爱好

错误示范:

Person.hobby = "吃饭";

正确示范: (修改原先的对象声明)

function Person(name, age, hobby) {

this.name = name;
this.age = age;
this.hobby = hobby;
}

1. prototype 继承

所有的 JavaScript 对象都会从一个 prototype(原型对象)中继承属性和方法:

比如 JS 自带的一些对象:

  • Date 对象从 Date.prototype 继承
  • Array 对象从 Array.prototype 继承

同理,我们之前定义的Person对象也会从 Person.prototype 继承属性和方法。

所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例,这类似于Java语言中所有的类都是Object的子类,都有Object类的 toString() 方法。

在这里插入图片描述

JavaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象上搜寻,还会搜寻该对象的原型,以及该对象的原型的原型,依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾(即Object)。

简单说,不管是JS内置的Date 对象, Array 对象, 还是我们自定义的 Person 对象都从 Object.prototype 继承。

2. 通过prototype添加属性和方法

适用场景:

  • 在所有已经存在的对象添加新的属性或方法
  • 在对象的构造函数中添加属性或方法

应用案例:

  • 给对象的构造函数添加新的属性
Person.prototype.hobby= "吃饭";
  • 给对象的构造函数添加新的方法
function Person(first, last, age, eyecolor) {

this.name = name;
this.age = age;
}
Person.prototype.hobby = function() {

return this.name + "的爱好是吃饭";
};

具体案例:

先定义一个Person对象的构造器,只有name和age两种属性,根据构造器创建一个uni对象,当点击按钮后就对该对象的prototype原型对象添加一个新的属性和新的方法,然后在通过alert打印出来:

具体实现代码的如下(文件格式为.html):

<!DOCTYPE html>
<head>
<h1>测试内容</h1>
<button onclick="test(this)">初始化对象</button>
</head>
<body>
<script type="text/javascript"> function Person(name, age){
 this.name = name this.age = age } let uni = new Person('uni', 22) function test(btn){
 // 获取上一级的h1标签 let h1 = btn.previousElementSibling // 通过prototype添加属性 Person.prototype.hobby = "吃饭" Person.prototype.showName = function(){
 return "我是: " + this.name } // 返回JSON格式的测试内容 h1.innerHTML = JSON.stringify(uni) // 将对象打印到控制台 console.log(uni) // 通过对象访问原型的属性 alert(uni.showName(), uni.hobby) } </script>
</body>
</html>

运行效果:

点击初始化对象的按钮,弹窗显示了对象通过prototype设置的方法和属性
在这里插入图片描述
点击确定后,对象的内容将以JSON格式显示在 h1 标题,如下图所示
在这里插入图片描述

4.2 Vue的生命周期

根据Vue官方介绍的:查看

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

在英文版中可能描述的会更详细一点:查看

每个Vue组件实例在创建时都会经历一系列初始化步骤
例如,它需要设置数据观察、编译模板、将实例装载到DOM,以及在数据更改时更新DOM。
同时,它还运行称为生命周期挂钩的函数,使用户有机会在特定阶段添加自己的代码。

Vue的生命周期如下图所示:
在这里插入图片描述
根据官方介绍,生命周期钩子(Hooks)就是上图中的红线圆框所表示的函数,本质上就是会在指定阶段执行的函数。

在实现这个案例时,我们主要用到三个钩子函数,分别是

  • beforeCreate() ,Vue中数据代理和数据劫持创建前
  • monted(),即DOM元素挂载后
  • beforeDestory(),Vue组件销毁前

4.2.1 beforeCreate() & 全局事件总线

参考资料:视频资料

在之前我们通过vue创建了多个组件,各个组件有上下层调用的关系,那么根据新的需求,现在就存在组件之间的数据通信问题,比如在点击编辑过后,TodoItem组件需要将编辑后的信息传递给App组件,因为数据是存储在App中的,为了维持Vue提供的MVVM模型,我们尽量不在被引入的子组件中直接去修改从父组件App里props传递的数据。
在这里插入图片描述
Vue的每个组件都是VueComponent的实例对象,根据原型链的知识,它们的__proto__属性是指向Vue原型对象的,这样就可以做到Vue之间的多个组件数据共享。

(下图摘自视频资料)
在这里插入图片描述

图中比较重要的等式:

VueComponent.prototype.__proto__ === Vue.prototype 恒等于 t r u e true true

注意:这里的VueComponent和Vue都是原型对象,而不是实例对象。

数据通信核心思路:提供一个Vue组件,存入Vue.prototype中专门用于数据通信。

在引入Vue的JS代码文件中进行配置

import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({

render: h => h(App),
beforeCreate(){

Vue.prototype.$bus = this
}
}).$mount('#app')

这里我们通过 beforeCreate() 钩子来定义Vue的原型变量,$bus,值就为Vue实例对象本身,这样的话,Vue内部的Vue Component组件实例对象就可以访问到 $bus ,这里的 $bus 称作全局事件的总线。

接下来总结以下全局事件总线的用法:

  1. 全局事件总线是一种Vue2组件之间的通信方式,适用于任意组件之间通信
  2. 配置全局事件总线
new Vue({

...
// 在钩子函数中定义
beforeCreate(){

Vue.prototype.$bus = this
},
})
  1. 使用事件总线
  • 接收数据:A组件想接收数据,则在A组件中给$bus$ 绑定自定义事件,事件的回调留在A组件自身。
methods(){

update(data) {
 ... }
}
...
mounted(){

this.$bus.$on('xxxx', this.update)
}
  • 提供数据
this.$bus.$emit('xxx', 数据)

Vue2的$emit$on接下来会做补充。

4.3 消息发布和消息订阅 & $emit 和 $on

我们可以使用现有的JS插件,比如pubsub来实现这种消息发布与订阅。

在Vue2的各组件数据通信中,为了确保数据能顺利传送 ,通常两个组件之间需要提前对消息进行定义。

所谓的消息通常就是指函数,一方负责定义接收的函数参数以及函数的实现逻辑(消息订阅者),另一方则负责指定调用的函数,以及传入的数据(消息发布者)。

// 安装发布与订阅插件 : npm i pubsub-js
// 订阅消息(返回一个消息ID)
const pubsubId = pubsub.subscribe('消息名称', 回调函数(消息名, 消息参数))
// 发布消息
pubsub.publish('消息名称', 消息参数)
// 取消订阅(根据发布消息的ID)
pubsub.unsubscribe(pubsubId)

除了使用 pubsub.js 插件以外,我们还可以通过 Vue2 的 $emit 和 $on来实现

$on

vm.$on( event, callback )

参数:

  • {string | Array} event (数组只在 2.2.0+ 中支持)
  • {Function} callback

作用:

监听当前实例上的自定义事件。事件可以由 vm.$emit 触发。回调函数会接收所有传入事件触发函数的额外参数。

示例:

// 订阅消息
vm.$on('test', function (msg) {

console.log(msg)
})
// 发布消息
vm.$emit('test', 'hi')
// => "hi"

$emit

vm.$emit( eventName, […args] )

参数:

  • {string} eventName
  • […args]

作用:触发当前实例上的事件。附加参数都会传给监听器回调。

除了绑定事件 $on 以外,还有解绑事件 $off

$off

vm.$off( [event, callback] )
参数:

  • {string | Array} event (只在 2.2.2+ 支持数组)
  • {Function} [callback]

用法:

  • 移除自定义事件监听器

  • 如果没有提供参数,则移除所有的事件监听器

  • 如果只提供了事件,则移除该事件所有的监听器

  • 如果同时提供了事件与回调,则只移除这个回调的监听器。

这里又要用到之前提到的全局事件总线的思路了,之前我们是通过 props 来传参的,通常是用于接收父组件传过来的变量或者函数,在有了全局总线过后,可以直接通过VueComponent组件对象里的原型来获取到 $bus ,所有的组件对象都能获取到 $bus的值,那么就能通过它订阅或者发布消息了。

这里的$bus本质上就是 Vue对象,因为Vue组件之间的原型都是指向Vue实例的,而Vue对象也指向其Vue实例,所以同一个Vue对象引入的Vue组件都可以通过它来进行数据通信。

4.4 Vue2动画

4.4.1 内置组件 transition

transition 是 Vue2内置的组件,它通常用来设置单个HTML标签的动画,多个标签动画则需要使用 transition-group

作用:

<transition> 元素作为单个元素/组件的过渡效果。<transition> 只会把过渡效果应用到其包裹的内容上,而不会额外渲染 DOM 元素,也不会出现在可被检查的组件层级中。

官方提供的案例:

<!-- 简单元素 -->
<transition>
<div v-if="ok">toggled content</div>
</transition>
<!-- 动态组件 -->
<transition name="fade" mode="out-in" appear>
<component :is="view"></component>
</transition>
<!-- 事件钩子 -->
<div id="transition-demo">
<transition @after-enter="transitionComplete">
<div v-show="ok">toggled content</div>
</transition>
</div>
<script type="text/javascript"> new Vue({
 ... methods: {
 transitionComplete: function (el) {
 // 传入 'el' 这个 DOM 元素作为参数。 } } ... }).$mount('#transition-demo') </script>

在本次案例中用到的部分:

<template>
<nav>
<transition appear>
<input name="edit" v-show="todoObj.isEdit" ref="inputTodo" class="come" type="text">
</transition>
</nav>
</template>
<script> import 'animate.css' export default {
 name: 'TodoItem', methods: {
 ... }, // 引入TodoBody传来的todoObj props: ['todoObj'], } </script>
<style scoped> .come{
 animation: fromRight linear 1s; } .to{
 animation: fromRight linear 1s reverse; } /* Vue自动指定name为edit和flag的进入和进出样式 */ .edit-enter-active{
 animation: fromRight linear 1s; } .edit-leave-active{
 animation: fromRight linear 1s reverse; } /* Vue 动画 */ @keyframes fromRight{
 from{
 transform: translateX(100px); opacity: 0; } to{
 transform: translateX(0px); opacity: 100; } } </style>

上述案例中的 input 标签只有在 todoObj.isEdit 值为 true时才显示,为了方便查看Vue2内置的transition组件的使用,我把其他关系不大的内容都忽略了,这里可以看到 -enter-active-leave-active 是固定的一种类名,分别表示进入和离开时加载的动画,除了这两个以外,还有 其他的比如:

  • v-enter
  • v-leave
  • v-appear
  • v-enter-to
  • v-leave-to
  • v-appear-to
  • v-enter-active
  • v-leave-active
  • v-appear-active

其中 v 可以改成 <transition> 标签内的name属性,比如上面的案例中,就定位到了name="edit"的标签。

4.4.2 transition-group

transition-group 是上一个组件的升级版,它可以同时支持设置多个组件的动画。

它和 transition 最大的区别就是,该标签里的所有HTML标签 都需要指定 key

元素作为多个元素/组件的过渡效果。 渲染一个真实的 DOM 元素。默认渲染 ,可以通过 tag attribute 配置哪个元素应该被渲染。

官方案例(这里很明显,它比较适合设置列表、表格之类的样式):

<transition-group tag="ul" name="slide">
<li v-for="item in items" :key="item.id">
{
{ item.text }}
</li>
</transition-group>

4.5 第三方CSS动画库 animate.css

安装的命令

npm install animate.css --save

使用:在任意组件中的 <script> 中导入css

import 'animate.css'

定义在动画标签:

<template>
<transition name="animate__animated anmate__bounce" enter-active-class="animate__backInDown" leave-active-class="animate__backOutUp" appear>
<div id="app">
<h1><img src="./assets/logo.png">基于Vue2的TodoList案例</h1>
<TodoHeader></TodoHeader>
<TodoBody></TodoBody>
<TodoFooter></TodoFooter>
</div>
</transition>
</template>
<script> import TodoHeader from './components/TodoHeader.vue' import TodoBody from './components/TodoBody.vue' import TodoFooter from './components/TodoFooter.vue' import 'animate.css' export default {
 name: 'App', components: {
TodoHeader, TodoBody, TodoFooter}, } </script>
<style scoped> h1{
 text-align: center;} img{
 width: 50px; height: 50px;} #app{
 width: 600px; margin: 0 auto; } </style>

关于样式的配置同样是放在 <transition>标签上,主要就是以下三句:

name="animate__animated anmate__bounce"
enter-active-class="animate__backInDown"
leave-active-class="animate__backOutUp"

首先name值是固定的,根据这个插件的特点,只有对标签设置固定的name,配置animate.css相关的动画才会有效。

前面的 enter-active-class 这个其实是Vue2提供的属性,毕竟 <transition> 是Vue2内置的一个组件,更多的属性可参考:查看,后面的类名就是插件提供的,可以在官网找(官网的效果很好,点击后有动画):

https://animate.style/

在这里插入图片描述

4.6 HTML5 存储

HTML5 webstorage

HTML5 web 存储,一个比 cookie 更好的本地存储方式。

浏览器存储数据的两个对象为:

  • localStorage - 用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除。

  • sessionStorage - 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。

我们在浏览网页时,大部分网站都会保留一些信息到我们的浏览器当中。比如访问 Baidu
在这里插入图片描述

这里可以看到,当我想搜索一些内容时,它会提示搜索历史,那么这个搜索历史是存储在哪里的呢?

是 Cookie 还是 Session ?

其实二者都不是,而是存储在HTML5 支持的 localStorage对象中。

我们可以通过浏览器调试工具直接查看:

在这里插入图片描述

可以通过 JavaScript 来设置数据存储,这样就能记录用户的一些输入信息,比较适合搜索引擎。

在使用 web 存储前,应检查浏览器是否支持 localStorage 和 sessionStorage

(这两个都是JS的内置对象,在JS代码中可以直接调用)

if(typeof(Storage)!=="undefined")
{

// 是的! 支持 localStorage sessionStorage 对象!
// 一些代码.....
} else {

// 抱歉! 不支持 web 存储。
}

localStorage 常用命令(sessionStorage类似)

// 存储
localStorage.setItem("name", "uni");
// 获取
localStorage.getItem("name");
// 删除单个
localStorage.removeItem("name");
// 删除所有
localStorage.clear()
// 得到某个索引的Key
localStorage.key(index);

提示: localStorage 中的键/值对通常以字符串存储,可以按自己的需要转换该格式。

对象存储可以通过JSON类型来格式化,将所有的待办事项(Array类型)通过JS存储到 localStorage

// 存Array
localStorage.setItem("todoList", JSON.stringify(todoList));
// 读取Array
const todoList = localStorage.getItem("todoList, JSON.parse(todoList));

五、实现代码


项目结构:
在这里插入图片描述

5.1 vue.config.js 项目配置

脚手架默认生成的配置,没有变动

const {
 defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({

transpileDependencies: true,
})

5.2 main.js

程序的入口文件,该构建Vue实例,同时指定组件相互通信的 $bus

import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({

render: h => h(App),
beforeCreate(){

Vue.prototype.$bus = this
}
}).$mount('#app')

5.3 App.vue

<template>
<transition name="animate__animated anmate__bounce" enter-active-class="animate__backInDown" leave-active-class="animate__backOutUp" appear>
<div id="app">
<h1><img src="./assets/logo.png">基于Vue2的TodoList案例</h1>
<TodoHeader @fnAddTodo="fnAddTodo" ></TodoHeader>
<TodoBody :todoList="todoList"></TodoBody>
<TodoFooter :todoList="todoList"></TodoFooter>
</div>
</transition>
</template>
<script> import TodoHeader from './components/TodoHeader.vue' import TodoBody from './components/TodoBody.vue' import TodoFooter from './components/TodoFooter.vue' export default {
 name: 'App', data(){
 return {
 todoList: JSON.parse(localStorage.getItem('todoList')) || [] } }, methods: {
 // 函数: 添加新的待办事项 fnAddTodo(todoObj){
 this.todoList.unshift(todoObj) }, // 函数: 切换待办事项的状态 fnCheckTodo(id){
 this.todoList.forEach((todoObj) => {
 if(id === todoObj.id) todoObj.done = !todoObj.done }) }, // 函数: 删除待办事项 fnDeleteTodo(id){
 this.todoList = this.todoList.filter(todoObj => todoObj.id !== id) }, // 函数: 批量操作待办事项的状态 fnUpdateCheckTodo(done){
 this.todoList.forEach((todoObj) => todoObj.done = done) }, // 函数: 清除已完成的待办事项 fnClearFinishedTodo(){
 if(confirm('确认要删除吗?')) this.todoList = this.todoList.filter((todoObj) => !todoObj.done) }, // 函数: 更新待办事项的内容 fnUpdateTodo(id, title){
 this.todoList.forEach((todoObj) => {
 if(todoObj.id === id) todoObj.title = title }) } }, mounted(){
 this.$bus.$on('fnCheckTodo', this.fnCheckTodo) this.$bus.$on('fnUpdateTodo', this.fnUpdateTodo) this.$bus.$on('fnDeleteTodo', this.fnDeleteTodo) this.$bus.$on('fnClearFinishedTodo', this.fnClearFinishedTodo) this.$bus.$on('fnUpdateCheckTodo', this.fnUpdateCheckTodo) }, beforeDestroy(){
 this.$bus.$off('fnCheckTodo') this.$bus.$off('fnUpdateTodo') this.$bus.$off('fnDeleteTodo') this.$bus.$off('fnUpdateTodo') this.$bus.$off('fnClearFinishedTodo') this.$bus.$off('fnUpdateCheckTodo') }, components: {
TodoHeader, TodoBody, TodoFooter}, // 监视属性值, 若有修改则将其放到本地缓存 watch: {
 todoList: {
 deep: true, handler(value){
 localStorage.setItem('todoList', JSON.stringify(value)) } } } } </script>
<style scoped> h1{
 text-align: center;} img{
 width: 50px; height: 50px;} #app{
 width: 600px; margin: 0 auto; } </style>

5.4 TodoHeader.vue

<template>
<div>
<label for="add">待办事项</label>
<input id="add" v-model="title" @keydown.enter="add" placeholder="请输入任务标题, 敲回车确认">
<button @click="add">添加</button>
</div>
</template>
<script> // 第三方库 npm i nanoid import {
nanoid} from 'nanoid' export default {
 name: 'TodoHeader', data() {
 return {
 title: '' } }, methods: {
 // 添加Todo add(){
 if(!this.title.trim()) return alert('提示:输入的待办事项不能为空!') // 封装 Todo 对象 const todoObj = {
 id: nanoid(), title: this.title, done: false } // 调用回调函数 this.$emit('fnAddTodo', todoObj) // 清空 this.title = '' } } } </script>
<style scoped> div{
 background: gray; padding: 20px; font-size: 1.2em; } button, input{
 font-size: 1.1em; } button{
 float: right; } </style>

5.5 TodoBody.vue

<template>
<div>
<ul>
<li v-for="todoObj in todoList" :key="todoObj.id">
<TodoItem :todoObj="todoObj"></TodoItem>
</li>
</ul>
</div>
</template>
<script> import TodoItem from './TodoItem.vue' export default {
 name: 'TodoBody', components: {
TodoItem}, props: ['todoList'] } </script>
<style scoped> div{
 background: pink; padding: 20px; font-size: 1.1em; } ul{
 list-style: none; padding-left: 0px; } </style>

5.6 TodoFooter.vue

<template>
<nav v-show="totalTodoList">
<input type="checkbox" v-model="isAll">
已完成 {
{ totalFinishedTodo }} / 全部 {
{ totalTodoList }}
<button @click="clearFinishedTodo">清除已完成</button>
</nav>
</template>
<script> export default {
 name: 'TodoFooter', props: ['todoList'], methods: {
 clearFinishedTodo(){
 this.$bus.$emit('fnClearFinishedTodo') } }, // 计算属性 computed:{
 // 计算已完成的待办事项 totalFinishedTodo(){
 return this.todoList.reduce((pre, todoObj) => pre + (todoObj.done ? 1 : 0), 0) }, // 所有的待办事项 totalTodoList(){
 return this.todoList.length }, isAll: {
 // 全选按钮默认的勾选条件: 所有待办事项都已经完成 get(){
 return this.totalTodoList > 0 && this.totalFinishedTodo == this.totalTodoList }, set(value){
 return this.$bus.$emit('fnUpdateCheckTodo',value) } } } } </script>
<style scoped> nav{
 background: greenyellow; padding: 20px; font-size: 1.1em; } input{
 height: 1.5em; width: 1.5em; } button{
 float:right; font-size: 1.1em; } </style>

5.7 TodoItem.vue

<template>
<nav>
<input type="checkbox" :checked="todoObj.done" @change="handleCheckTodo(todoObj.id)">
<span name="flag" v-show="!todoObj.isEdit">{
{ todoObj.title }}</span>
<transition appear>
<input name="edit" v-show="todoObj.isEdit" ref="inputTodo" class="input come" type="text" :value="todoObj.title" @blur="handleSaveTodo(todoObj, $event)">
</transition>
<button @click="handleDeleteTodo(todoObj.id)">删除</button>
<button @click="handleEditTodo(todoObj)">编辑</button>
</nav>
</template>
<script> import 'animate.css' export default {
 name: 'TodoItem', methods: {
 handleCheckTodo(id){
 this.$bus.$emit('fnCheckTodo', id) }, handleDeleteTodo(id){
 if(confirm('确认要删除Id为 [' + id + '] 的待办事项吗?')) this.$bus.$emit('fnDeleteTodo', id) }, handleEditTodo(todoObj){
 // 判断待办事项是否有isEdit属性 if(!Object.hasOwn(todoObj, 'isEdit')){
 // 若没有则动态设置为true this.$set(todoObj, 'isEdit', true) } else{
 // 若有则直接设置为true todoObj.isEdit = true } // 聚焦到input, 调用$nextTick, 等页面渲染后才执行回调函数 this.$nextTick(function(){
 this.$refs.inputTodo.focus() }) }, handleSaveTodo(todoObj, event){
 // 取消编辑 todoObj.isEdit = false if(!event.target.value.trim()) return alert('事项内容不能为空!') this.$bus.$emit('fnUpdateTodo', todoObj.id, event.target.value) } }, // 引入Body传来的todoObj 和 App传来的 fnCheckTodo方法 props: ['todoObj'], } </script>
<style scoped> nav{
 background: rgb(196, 100, 100); padding: 20px; font-size: 1.1em; } button{
 float:right; font-size: 1.1em; display: none; } input{
 width:1.5em; height:1.5em } .input{
 font-size:1.1em; width: 200px; } nav:hover{
 background: rgb(31, 101, 167); } nav:hover button{
 display: block; } .come{
 animation: fromRight linear 1s; } .to{
 animation: fromRight linear 1s reverse; } /* Vue自动指定name为edit和flag的进入和进出样式 */ .edit-enter-active{
 animation: fromRight linear 1s; } .edit-leave-active{
 animation: fromRight linear 1s reverse; } /* Vue 动画 */ @keyframes fromRight{
 from{
 transform: translateX(100px); opacity: 0; } to{
 transform: translateX(0px); opacity: 100; } } </style>

六、总结


这两天通过一个TodoList 待办事项的案例对Vue2的学习进行了拓展,个人感觉Vue开发非常方便,而且逻辑层次分的很清楚,不会像之前那样,HTML和JS都杂糅在一起,看起来很混乱,现在使用Vue将不同的部分划分为不同的组件,而每一个组件中都有相应的 HTML、JS和CSS,非常方便。

版权声明:本文为[尤你]所创,转载请带上原文链接,感谢。 https://blog.csdn.net/Unirithe/article/details/125390549