Vue.js快跑:构建触手可及的高性能Web应用
译者序
- 如果没有深厚的编程功底以及设计能力,想要优雅地使用React会存在一些障碍;而对于Angular来说,对于个人开发者和中小型的应用又略显得“重”了,学习曲线也比较陡峭。但Vue对于个人开发者和中小型应用更友好,学习曲线也是渐进性的,非常适合用来入门和学习MVVM框架中所涉及的通用知识和开发理念。
前言
- 更新页面中大量具有交互性的部分、处理复杂的状态、使用客户端路由、高效简洁地编写和组织代码
- 框架所包含的功能使我们能够创建一个功能完备的Web应用程序,包括操作复杂的数据并将其显示在页面上、处理客户端路由而不是依赖服务端、有时甚至允许我们只需访问服务器一次并完成初始下载就能构建一个完整的网站。
- 一种功能强大、能够创建DOM和监听事件的模板语法,无须操心数据变化带来相应模板更新的响应式原理以及使维护数据变得更容易的功能。
为什么选择Vue.js
- 不使用框架,项目最终会成为一团不可维护的代码,而绝大部分代码所要处理的工作,框架都已经为我们抽象出来了。
- 这是因为实际的应用逻辑和视图逻辑是完全隔离的
- vue-router用于路由控制——根据应用的不同URL来显示不同的内容。vuex用于状态管理——通过一个全局数据中心在组件间共享数据。vue-test-utils用于Vue组件的单元测试。
安装和设置
- 不能在body元素上进行初始化
- 并将该实例的el属性指向之前提到的div元素。
- vue-loader是一个webpack的加载器,
- $ npm install --global vue-cli $ vue init webpack
模板(Template)、数据(Data)和指令(Directive)
- Vue的核心是将数据显示在页面上,这一功能通过模板实现。为正常的HTML添加特殊的属性——被称作指令——借助它来告诉Vue我们想要实现的效果以及如何处理提供给它的数据
- data对象,我们通过它告诉Vue想在template上显示哪些内容。
- 设有v-if属性的元素只有传递给指令的值为真时才会显示,否则,该元素不会被写入页面。
- Vue支持在v-if中进行简单的表达式运算
- 在JavaScript中实现业务逻辑,而在template中实现视图逻辑
- 除了使用指令,也可以通过插值的方式将数据传递给模板
- location.pathname是当前页面URL的路径值
- 如果将整个数组或对象输出到页面上,Vue会输出JSON编码后的值
v-if vs v-show
- v-if指令可以控制一个元素的显示和隐藏
- 如果v-if指令的值为假[插图],那么这个元素不会被插入DOM。
- 和v-show指令比较一下,该指令使用CSS样式控制元素的显示/隐藏。
- 首先,因为使用v-if隐藏的内部元素不会被显示,Vue不会尝试生成对应的HTML代码;而对于v-show指令,事实并非如此。这意味着隐藏尚未加载的内容时,v-if指令更好一些。
- 使用v-if会有性能开销。每次插入或者移除元素时都必须要生成元素内部的DOM树,这在某些时候是非常大的工作量。而v-show除了在初始创建开销时之外没有额外的开销。如果希望频繁地切换某些内容,那么v-show会是最好的选择。
模板中的循环
- v-for,这个指令通过遍历一个数组或者对象,将指令所在的元素循环输出到页面上。
- v-for指令同样支持对象的遍历。
- 请记住参数的顺序是:(value, key)。
属性绑定
- v-bind指令用于将一个值绑定到一个HTML属性上。
- v-bind是指令的名称,type是指令的参数
- 一种简写方式是可以省略v-bind部分,而用冒号代替
响应式
- Vue将JavaScript中的值输出到HTML中并转化为DOM
- Vue还监控data对象的变化,并在数据变化时更新DOM
- created函数方法会在应用初始化完成后执行。
- Vue的响应式能力除了在使用插值将数据输出到页面上时有效,在将data对象的属性作为指令值时也同样有效。
- 响应式是令Vue如此强大的一个极其重要的特性和部分。
- Vue修改了每个添加到data上的对象,当该对象发生变化时Vue会收到通知,从而实现响应式
- 另外一种方法是,使用Object.defineProperty()覆写这个属性:
- 因为getter/setter方法是在Vue实例初始化的时候添加的,只有已经存在的属性是响应式的;当为对象添加一个新的属性时,直接添加并不会使这个属性成为响应式的
- vm.dogs.splice(newLength); 这一方法只能用于缩短数组,并不能扩展它的长度。
双向数据绑定
- 作为替代,可以使用v-model指令,它作用于输入框元素,将输入框的值绑定到data对象的对应属性上,因此输入框不但会接收data上的初始值,而且当输入内容更新时,data上的属性值也会更新。
- 使用v-model时一定要记住,如果设置了value、checked和selected属性,这些属性会被忽略。
- 单选框有一点不同,因为同一个v-model会对应多个不同的元素,其name属性也会被忽略,存储在data中的值等于当前选中的单选输入框的value属性的值:
- 尽管可以继续使用name属性,但Vue会忽略它,况且单选框没有这个属性也可以正常运行(同一时间只有一个单选框被选中)。
动态设置HTML
- 记住,只用v-html处理你信任的数据。
方法
- 在Vue的模板里,函数被定义为方法来使用。
- 任何可以使用JavaScript表达式的地方都可以使用方法
- 如果numbers发生变化——例如增加或移除一个数字——这个方法会被重新调用来处理新的数字,从而输出到页面上的信息也会随之更新。
- 在方法中,this指向该方法所属的组件。可以使用this访问data对象的属性和其他方法:
- .reduce((sum, val)
计算属性
- 计算属性介于data对象的属性和方法两者之间:可以像访问data对象的属性那样访问它,但需要以函数的方式定义它。
- 首先,计算属性会被缓存:如果在模板中多次调用一个方法,方法中的代码在每一次调用时都会执行一遍;但如果计算属性被多次调用,其中的代码只会执行一次,之后的每次调用都会使用被缓存的值。只有当计算属性的依赖发生变化时,代码才会被再次执行
- 除了能像上例展示的那样获取计算属性的值,还可以设置计算属性的值,并且在设置过程中做一些操作。
- 计算属性适用于执行更加复杂的表达式,这些表达式往往太长或者需要频繁地重复使用,所以不想在模板中直接使用。计算属性往往和其他计算属性或者data对象一起使用,基本上就像是data对象的一个扩展和增强版本。
侦听器
- 侦听器可以监听data对象属性或者计算属性的变化。
- 在Vue中,通常有比侦听器更好的方式来处理问题——通常会使用计算属性
- 侦听器使用起来很简单:只要设置要监听的属性名就可以
- 侦听器很适合用于处理异步操作
- 为了监听这个对象的属性变化,可以在侦听器的名称中使用.操作符,就像访问这个对象属性一样
- 当监听的属性发生变化时,侦听器会被传入两个参数:所监听属性的当前值和原来的旧值。
- 监听整个对象被称作深度监听,通过将deep选项设置为true来开启这一特性:
过滤器
- 过滤器是一种在模板中处理数据的便捷方式
- 尽管我们可以将这一逻辑拆分为一个方法,但这次我们要把这一逻辑拆分为过滤器,因为这样可读性更好而且可以全局使用:
- 例如增加货币换算功能——只需要修改一次而不是修改每一处用到它的代码。
- 过滤器同样可以接收参数
- 过滤器是组件中唯一不能使用this来访问数据或者方法的地方
使用ref直接访问元素
- 使用ref访问一个元素,只需要将这个元素的ref属性设置为字符串,然后可以使用这个字符串访问元素
输入和事件
- 可以使用v-on指令将事件侦听器绑定到元素上
- 如果使用方法,事件对象会作为第一个参数传入。这个事件对象是原生的DOM事件对象,如果你使用JavaScript内建的.addEventListener()方法添加事件监听器,会得到相同的事件对象,而且它非常有用,例如可以获取键盘事件的keyCode值。
- 可以将v-on:click简写为@click。
- 如果想要阻止事件继续传播,以避免在父级元素上触发事件,可以使用.stop修饰符:
- Vue还为最常用的按键提供了别名:.enter、.tab、.delete、.esc、.space、.up、down、.left和right。
生命周期钩子
- 生命周期钩子是一系列会在组件生命周期——从组件被创建并添加到DOM,到组件被销毁的整个过程——的各个阶段被调用的函数。
- Vue有8个生命周期钩子
- beforeCreate在实例初始化前被触发。· created会在实例初始化之后、被添加到DOM之前触发。· beforeMount会在元素已经准备好被添加到DOM,但还没有添加的时候触发。· mounted会在元素创建后触发(但并不一定已经添加到了DOM,可以用nextTick来保证这一点)。· beforeUpdate会在由于数据更新将要对DOM做一些更改时触发。· updated会在DOM的更改已经完成后触发。· beforeDestroy会在组件即将被销毁并且从DOM上移除时触发。· destroyed会在组件被销毁后触发。
自定义指令
- 一共有5个钩子函数,分别是bind、inserted、update、componentUpdated和unbind。
- · bind钩子函数会在指令绑定到元素时被调用。 · inserted钩子会在绑定的元素被添加到父节点时被调用——但和mounted一样,此时还不能保证元素已经被添加到DOM上。可以使用this.$nextTick来保证这一点。 · update钩子会在绑定该指令的组件节点被更新时调用,但是该组件的子组件可能此时还未更新。 · componentUpdated钩子和updated钩子类似,但它会在组件的子组件都更新完成后调用。 · unbind钩子用于指令的拆除,当指令从元素上解绑时会被调用。
过渡和动画
- Vue提供了
组件,它会向内部的带有v-if指令的元素添加类名,因此可以使用这个组件来为进入或离开的元素添加CSS过渡动画。 - 它的工作原理是,Vue获取tansition组件的name属性值,然后使用它在过渡的各个节点为包含的元素添加类名。当元素被添加到文档或者从文档中移除时,会分别应用enter和leave两类过渡。
- 除了CSS动画,
组件还提供了用于实现JavaScript动画的钩子。使用这些钩子 - 这些钩子会以事件的形式在transition组件上触发
- 但是CSS过渡通常性能更好,所以尽量使用CSS过渡,除非你需要的效果无法用纯CSS过渡实现。
第2章 Vue.js组件
- 组件是一段独立的、代表了页面的一个部分的代码片段。它拥有自己的数据、JavaScript脚本,以及样式标签。
- 而且由于组件是独立的,还可以确保组件中的代码不会影响任何其他组件或产生任何副作用。
组件基础
- 通过components配置对象,将这个组件传入你的app
- 也可以注册一个全局的组件,只需像下面这样调用Vue.component()方法
数据、方法和计算属性
- 每个组件可以拥有它们自己的数据、方法和计算属性,以及所有在前面章节中出现过的属性——就像Vue实例一样
- Vue实例中的data属性是一个对象,然而组件中的data属性是一个函数。
传递数据
- 可以使用props属性来传递数据
- props属性的值表示可以传入组件的属性的名称
- 除了可以传递一个的简单数组,来表明组件可以接收的属性的名称,也可以传递一个对象,来描述属性的信息,比如它的类型、是否必须、默认值以及用于高级验证的自定义验证函数。
- 如果一个prop可以是多个类型中的一个,你就可以为它传递一个包含所有有效类型的数组,例如price: [Number, String, Price]
- 也可以指定一个prop是否是必需的,或者在没有传入值时,给它设定一个默认值。
- 你可以传递一个验证函数,该函数以prop的值为参数,在prop有效时应该返回true,而无效时则返回false。
- 在HTML中通过kebab形式指定的属性,会在组件内部自动转换为camel形式
- 在父级实例中设定prop的值时,可以使用v-bind指令将该prop与某个值绑定。那么无论何时只要这个值发生变化,在组件内任何使用该prop的地方都会更新。
- 如果prop的值不是字符串,那么就必须使用v-bind指令
- 双向数据绑定在某些情况下可能很有用。如果想要使用双向绑定,可以使用一个修饰符来实现:.sync修饰符
- 在某些情况下,将触发事件的逻辑封装到计算属性中会有利于代码的组织
- 如果仅仅想要更新从prop传入的值,而不关心父级组件的值的更新,你可以在一开始的data函数中通过this来引用prop的值,将它复制到data对象中
使用插槽(slot)将内容传递给组件
- 除了将数据作为prop传入到组件中,Vue也允许传入HTML
- 不仅可以传入字符串,也可以传入任何你想要的HTML,甚至是其他的Vue组件。
- 如果为
元素设定了内容,那么该内容会在组件没有接收到内容时被当作默认内容使用。 - 传递给组件的内容会替换掉它里面的
元素输出到页面上。 - 可以将数据传回slot组件,使父组件中的元素可以访问子组件中的数据。
- 任何传递给
的属性都可以用slot-scope属性中定义的变量来获取。 - 作为一种简写方式,你可以解构slot-scope的属性,就像解构函数参数一样。
自定义事件
- 调用this.$emit()函数可以触发一个自定义事件,它接收一个事件名称以及其他任何你想要传递的参数。
- 在组件内部代码中,还可以使用$on方法来监听组件自身触发的事件。
混入
- 混入是一种代码组织方式,可以在多个组件间横向复用代码。
- 只要将混入对象添加到组件中,那么该组件就可以获取到存储在混入对象中的任何东西。
- 混入对象可以引用几乎任何Vue组件所能引用的东西,就好像它是组件本身的一部分一样
- 对于生命周期钩子——诸如created()和beforeMount()这样的——Vue会将它们添加到一个数组中并全部执行
- 对于重复的方法、计算属性或其他任何非生命周期钩子属性,组件中的属性会覆盖混入对象中的属性
- 官方的Vue代码风格指南建议对于混入中的私有属性(不应该在混入之外使用的方法、数据和计算属性),应该在它们的名称前面添加前缀。
vue-loader和.vue文件
- vue-loader提供了一种方法,可以在.vue文件中以有条理并且易于理解的语法编写基于单个文件的组件。
- 你必须使用预处理器,因为它无法直接在浏览器中工作。
非Prop属性
- 如果为某个组件设置的属性并不是用作prop,该属性会被添加到组件的HTML根元素上。
- 大部分属性都像这样,会覆盖组件内部模板中的同名属性,但是class和style稍微聪明一点,同名的值会被合并。
- 需要注意的是,组件属性中的background-color样式覆盖掉了内部模板中的background-color样式。
组件和v-for指令
- 当使用v-for指令遍历一个数组或是对象,并且给定的数组或对象改变时,Vue不会再重复生成所有的元素,而是智能地找到需要更改的元素,并且只更改这些元素。
- 使用v-for指令时可以设置一个key属性,通过它可以告诉Vue数组中的每个元素应该与页面上哪个元素相关联,从而删除正确的元素。
- click事件的处理函数是通过.native修饰符添加的,因为这就是为组件添加原生DOM事件监听器的方式。没有.native修饰符,事件处理函数将不会被调用。
- 这是Vue的差异对比机制引起的
- 我经常看到有人将key设置为数组的下标(例如在前面例子中设置:key="i")。如果不是什么特殊情况,那么你不会想要这么做的
- 在组件中使用v-for指令时key属性并不是可选的,正如你在前面的示例中看到的
第3章 使用Vue添加样式
- v-bind:class和v-bind:style两者都有专门的功能,帮助你通过数据设置class属性和内联样式。
Class绑定
- 如果传递一个数组给v-bind:class,数组中的类名将会被拼接到一起
内联样式绑定
- 注意,我们用的是fontWeight,而不是font-weight。Vue会自动将该对象的属性由驼峰命名转为它们对应的CSS属性,这意味着不用再操心如何转义属性名中的短横杠了。
- Vue会自动为你添加浏览器前缀:如果设置的某个样式需要浏览器兼容性前缀,Vue会自动把它加上。
- 还可以使用数组提供多个值,来设置浏览器最终支持的值:
用vue-loader实现Scoped CSS
- 如果我们在style标签上添加了scoped特性,Vue就会自动处理关联的CSS与HTML,使编写的CSS只影响到该组件中的HTML。
- 可以看到Vue已经为组件中的每个元素添加了一个data属性,然后又将它添加到了CSS选择器中,使样式只应用在这些元素上。
用vue-loader实现CSS Modules
- 作为scoped CSS的替代方案,可以用vue-loader实现CSS Modules
预处理器
- 首先通过npm安装sass-loader和node-sass,然后在style标签上添加lang="scss":
第4章 render函数和JSX
- createElement接收3个参数:将要生成的元素的标签名称、包含配置信息的数据对象(诸如HTML特性、属性、事件侦听器以及要绑定的class和style等)和一个子节点或是包含子节点的数组。
标签名称
- 标签名称是最简单的,也是唯一一个必需的参数。它可以是一个字符串,或是一个返回字符串的函数
- render函数中也可以访问this,所以可以将标签名称设置为data对象的某个属性、prop、计算属性或是任何类似的东西
- 这是render函数优于template的一个很大的优势,在template中动态设置标签名称并不是那么容易的,并且代码可读性也不好。
数据对象
- 数据对象是设置一系列配置属性的地方,这些属性会影响生成的组件或元素。
- 由于this.buttonText是render函数的一个依赖,无论何时只要buttonText更新,render函数就会被再次调用,然后DOM也会自动更新,就和template一样。
- 请注意,class和style并没有在attrs属性中,它们是单独设置的。这是因为v-bind指令的特性;如果仅仅将class或者style设置为attrs对象的一个属性,就不能将class设置为数组或是对象,或者将style设置为对象。
子节点
- 第三个也是最后一个参数是用来设置元素的子节点的。它可以是一个数组也可以是一个字符串。如果是一个字符串,那么它的值会作为元素的文本内容被输出;如果是一个数组,可以在数组中再次调用createElement函数,来构建一个复杂的DOM树。
JSX
- 在babel-plugin-transform-vue-jsx插件的帮助下,可以使用JSX来编写render函数,Babel插件会将JSX语法编译成Vue能理解的createElement函数调用形式。
- 如果导入的组件存储在首字母大写的变量中,则不需要在components对象中指定或是使用Vue.component()函数注册,就可以使用该组件。
第5章 使用vue-router实现客户端路由
- 路由就是取一个路径(如users/12345/ports)来确定应该在页面上显示什么内容的行为。
安装
- 而如果用的是诸如webpack的打包工具,那么就要调用Vue.use(VueRouter)来安装vue-router:
基本用法
- 首先,当应用初始化时,要把它传入到Vue实例中。然后,为了让它显示到页面上,需要添加一个特殊的组件
。 - 在模板中,将
放到任何你想让路由所返回的组件被显示的地方。
HTML5 History模式
- vue-router默认使用URL hash来存储路径。
动态路由
- vue-router支持动态路径匹配,也就是说可以通过使用一种专门的语法来指定路径规则,而所有匹配到该规则的路由下的组件都能被访问到。
- 在组件实例中,可以通过使用属性this.$route来获取当前的路由对象。
- 注意,我们把通常位于mounted内部的逻辑移动到了一个方法下面(译注:即init()方法),在mounted钩子和beforeRouteUpdate守卫里都会调用这个方法。这就避免了重复的代码——我们几乎总是会想要在这两个地方做些相同的事。
- beforeRouteUpdate新增于Vue 2.2,所以它并不适用于之前的版本。在2.2版本之前,必须监听$route对象的变化:
- 除了在组件中使用this.$route.params,还可以让vue-router将params作为路由组件的props传入
- 要想让vue-router改为将userId作为组件的一个属性传入,你可以在路由中指定props:true:
嵌套路由
- 嵌套路由允许指定子路由,并且用另一个
来显示其内容。
重定向和别名
- 可以指定一个redirect属性,用于替代component:
- 另一种重定向的方法是,给组件取一个别名。比如,如果你想让设置页面从/settings和/preferences都可被访问,可以给/settings路由取一个叫/preferences的别名:
链接导航
- 使用
来代替标签。它用起来与传统的锚定标签类似: - 在hash模式下,前面的链接会带你前往#/user/1234,而使用history模式的话,则会带你去向/user/1234。
- 当
组件的to属性中的路径与当前页面的路径相匹配时,链接就被激活了(active) - 在默认情况下,在组件上使用v-on就可以监听该组件触发的自定义事件,这个在第2章已经见过了。而对于原生事件,就可代之以.native修饰符来监听:
- router.push()会向history栈添加一个新的记录——因此,如果用户按下返回键,路由器就会跳转到上一个路由——而router.replace()则替换了当前的history记录,所以返回键只能让你回到之前的路由。
- 最后是router.go(),它能让你在历史记录中前进和后退,就像按了前进键和后退键。后退一条记录,你就用router.go(-1),而前进10条记录,就用router.go(10)。
导航守卫
- 你可以为路由器添加一个router.beforeEach()守卫。该守卫被传入3个参数:to、from以及next,其中from和to分别表示导航从哪里来和到哪里去,而next则是一个回调,在里面你可以让vue-router去处理导航、取消导航、重定向到其他地方或者注册一个错误。
- 当使用嵌套路由时,to.meta指向的是子路由的元信息,而非其父路由
- 除了在路由器上定义beforeEach和afterEach守卫,你还可以对每个单独的路由定义beforeEnter守卫
- beforeEnter守卫与beforeEach表现完全一致,只不过这种守卫作用于每一个单独的路由而非所有。
- 在beforeRouteEnter中this是undefined,因为此时组件还尚未被创建。但是,可以在next里传一个回调,该回调会被传入组件实例并作为其第一个参数
路由顺序
- vue-router在内部通过遍历路由数组的方式来挑选被显示的路由,并选取其中匹配到当前URL的第一个。
- 可以利用vue-router会按顺序搜索路由直到与通配符(*)匹配的特点,来渲染一个显示错误页面。
- 如果想让子路由的错误页面也能在父组件中显示,则需要在子路由数组中添加该通配符路由
路由命名
- 给路由命名,并采取名称而不是路径来标识它们,这意味着路由和路径变得不再那么耦合紧密了:改变路由的路径时,只需要改变路由器中的路径,而不需要再去检查所有现存的链接并对它们做出更新。
第6章 使用vuex实现状态管理
- 组件之间的所有通信都采用事件(events)方式(子组件往父组件通信)和属性(props)方式(父组件往子组件通信)
State及其辅助函数
- state表示数据在vuex中的存储状态,它就像一个在应用的任何角落都能访问到的庞大对象——是的,它就是单一数据源
- vuex提供了一个辅助函数mapState,它返回一个被用作计算属性的函数对象。
- mapState函数以一个对象作为参数,并将其中的各个键值分别映射到一个计算属性。如果键值给定的是函数,则该函数会以state作为其第一个参数被调用,从而使你能够从这个参数上获取state的值
- 3种方法,它们从store中获取完全相同的值
- 建议你尽量使用数组写法——它阅读起来十分简单——而在需要进行处理时则使用函数的写法。
- 应用级的state放入vuex中以便组件之间共享,而只用于单个组件内部的简单state则作为组件本地state。
Getter
- vuxe为我们提供了getters属性,它使我们能够将通常会被重复使用的代码移动到vuex store内部,从而避免产生冗余。
- 通过使用getter的第二参数,不同getter之间可以互相访问。
- 和state一样,getter方法也有辅助函数,省去了每次都要调用this.$store.getters。getter辅助函数和mapState用起来类似,不过不支持函数写法。
Mutation
- mutation是一个函数,它对state进行同步变更,通过调用store.commit()并传入mutation名称的方式来达成。
- mutation只能实现同步变更state对象。如果需要实现异步变更,那么应该使用action。
Action
- 用mutation只能做到同步变更,而action则用于实现异步变更。
- 使用store.dispatch()方法来在组件中调用getMessages
- action也存在一个mapActions辅助函数,用于将普通方法映射到action:
- 在action中使用参数解构是一种相当标准的做法,以代替对context对象的引用
- 还是得通过提交mutation来变更。
Promise与Action
- 调用dispatch也会返回一个promise对象,运用它就可以在action运行结束时去运行其他代码。
Module
- 每个module都只是一个对象,并且拥有其自身的state、getter、mutation以及action,通过使用modules属性即可将它们添加到store当中
- 在getter中,可以通过其第三个参数rootState属性来访问根节点状态,而在action中,则可以通过context对象的rootState属性来访问。
- 要让vuex为模块创建命名空间,需要在模块对象中加入namespaced:true
第7章 对Vue组件进行测试
- JavaScriptTesting with Jasmine
介绍vue-test-utils
- vue-test-utils是一个协助你编写测试的Vue官方库。
挂载选项
- slots是用来传递components或者HTML字符串的。
- mocks是用来为组件实例增加属性的
附录B Vue与React
- React和Vue中的每个组件都拥有自己的状态:在React中,它被称作state并使用setState()方法进行存储,而在Vu e中,它叫作data,并通过更改数据对象进行存储。在这两种库中,
- React和Vue都具有单向数据流。可以通过props将数据传递到组件内部,但是你无法直接修改它。