本文是为了分享Vue相关知识点,从之前一篇Vue和React对比笔记中节选出来的。
一、数据双向绑定
1.插值表达式
2.指令系统(声明式绑定)
3.Class与Style绑定
4.条件渲染和列表渲染
5.时间处理器
6.表单控件
7.计算属性
8.数据获取(axios)
二、组件及数据流
1.创建组件
2.使用组件
3.生命周期
4.组件之间信息传递
三、状态管理
vuex
四、路由
vue-Router
五、其他
1.过渡系统
2.服务端渲染
Vue也已经升级到2.0版本了,到现在为止,比较流行的MVVM框架有 AngularJS (也有人认为其为MVC)、 ReactJS 和 VueJS ,这三个框架中,以我现在的情况来说(AngularJS2还没有去接触),Vue应该是发展最快的。
为什么现在MVVM框架这么火呢?JQuery挺好用的呀,为什么要去代替它?…
可能会产生这样的疑问,在我看来,MVVM框架的流行是因为随着前端技术的发展对于要求越来越高和前端面对的场景越来越复杂导致了现实对于前端性能的要求也越来越高,这样像JQuery那样频繁的操作DOM节点的方式显然就不太合适了。所以MVVM开始逐渐流行开来,另外我认为JQuery目前来看还是不会被代替的,因为对于一些对性能要求不是很高的前端项目,是用JQuery来开发还是非常爽的。
1.插值表达式
2.指令系统(声明式绑定)
3.Class与Style绑定
4.条件渲染和列表渲染
5.时间处理器
6.表单控件
7.计算属性
8.数据获取(axios)
我理解的数据双向绑定是,MVVM框架中的View层和Model层的数据相互影响。那么,那些行为会引起数据变动呢?
首先,View层(即页面上)的 表单操作 、 触发事件 可能会引起数据变动; 数据请求 也可能会引起数据变动,这个变动我认为更多在Model层;还有一种情况就是, 某一数据变动引起另外关联数据 的改变。
不管是哪一种数据改变,都会导致View层和Model层的变化,View层变动引起页面变化,Model层变动保存数据。
在Vue中,View层中与数据绑定有关的有 插值表达式 、 指令系统 、 *Class和Style 、 事件处理器 和 表单控件 , ajax请求 和 计算属性 也和数据变化有关,下面我们分别说一下这些知识点设计的一些数据绑定问题。
在Vue中, 插值表达式 和 指令 对于数据的操作又称为 模板语法 。
Vue.js 使用了基于 HTML 的模版语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。所有 Vue.js 的模板都是合法的 HTML ,所以能被遵循规范的浏览器和 HTML 解析器解析。
在底层的实现上, Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,在应用状态改变时, Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上。
关于插值表达式的使用,在 Vue官网模板语法的插值 部分有详细的使用说明,不在赘述,需要注意的是, 过滤器 在插值中的使用有时可以起到意想不到的效果。
Vue重的指令估计是从Angular那里学来的,有很多相似的地方,但是也不完全相同。
Vue中的指令我觉着非常的简单,并且就12个,很容易记忆:
[v-cloak] { display: none }
一起用时,这个指令可以隐藏未编译的 Mustache 标签直到实例准备完毕。 大概列举一下,详细使用请参考 Vue API 指令 和 Vue 指南的Class与Style绑定、条件渲染、列表渲染、事件处理器、表单控件绑定 部分内容。
Vue为了方便操作控制元素的样式,专门增强了 v-bind:class 和 v-bind:style ,通过增强的这两个指令可以实用 对象语法 或者 数组语法 对元素的样式进行变动,这也不是本文重点, Vue官方Class与Style绑定 已经说得很详细了。
条件渲染和列表渲染在Vue模板中动态创建模板很不错,让我里面想到了JSP中的EL表达式和Struts中的JSTL(后端模板语言中基本都有),这就可以方便的根据后端传过来的数据进行模板创建了。你懂得,详细语法和使用还是参考 Vue文档列表渲染和条件渲染 ,本篇主题是对比,并不是讲解基础语法,Vue的官方文档我觉着非常给力,简单明了。
在Vue中我们可以通过 v-on 来给元素注册事件,完成 事件注册 ,Vue中的事件处理和平时使用的事件处理不同之处就是,既可以绑定数据处理函数,也可以使用内联处理器。并且,Vue还讲常用的事件方法,如 preventDefault方法 等通过修饰符的方式来方便使用。
你可以用 v-model 指令在表单控件元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
Vue中对于 表单控件 提供的 v-model* 指令非常的使用,可以方便的返回或者设置表单控件的信息。
在Vue中引入了 计算属性 来处理模板中放入太多的逻辑会让模板过重且难以维护的问题,这样不但解决了上面的问题,而且也同时让模板和业务逻辑更好的分离。
Vue计算属性在Vue1.x的版本中,官方推荐的ajax数据请求库是 vue-resource ,但是在Vue2.0的版本中,不再推荐使用,该推荐使用 axios 。
其实这些ajax数据请求的使用都大差不差,随你选择,并且 vue-resource 还是继续支持使用的,在Vue2.0中。
以上八个方面,我个人认为都是和数据绑定相关的一些Vue基本项,只是简单列举,具体内容请查看 Vue文档 或者 API 。
为什么这么多呢?因为Vue中有个 模板 的概念,所以,数据和模板进行数据绑定需要分别来做,而在的React中,你会发现,React的数据绑定虽然也是这八项功能点,但是其是通过JSX语法实现的。
1.创建组件
2.使用组件
3.生命周期
4.组件之间信息传递
前端发展到现在,为了提高开发效率,组件化已经成为一个必然的趋势。而MVVM框架中,如果没有组件化的思想,它都不敢说拿出来宣传(纯属个人意淫)。下面我们再简单介绍一下VueJS中的 组件思想 和 组件之间的数据流 。
上一节中提到过,React中的组件化是其重要的特点之一,因为在Angular1.x的出现,并没有明确提出组件的思想,只是有一个类似的 指令思想 来实现组件化的方式。所以,当React中明确提出 组件 思想后,前端好像重生了(吹的有点大了)。
React中实现组件有两种方式,一种是 createClass 方法,另一种是通过ES2015的思想 类继承React.Component 来实现。
import React from 'react'; export default React.createClass({ render() { return ( <div>hello5</div> ) } })
这样,一个组件就创建完成,并且通过ES2015的模块化思想将其暴露出去了,其他组件就可以引入并使用了。如下:
import React from 'react'; import ReactDom from 'react-dom'; import Hello from './hello.jsx'; ReactDom.render( <Hello/>, document.getElementById('app'); );
OK,这样就使用简单使用了一个组件。
import React from 'react'; classCommentItemextendsReact.Component{ render() { return ( <divclassName="comment"> <span>{ this.props.author }</span> <span>{ this.props.date }</span> <div>{ this.props.children }</div> </div> ) } } export { CommentItem as default };
需要注意的是,这样创建组件的时候,组件名称首字母必须大写(如:CommentItem)。同样,我们使用一下这个组件。
import React from 'react'; import CommentItem from './comment-item'; classCommentListextendsReact.Component{ render() { let CommentNodes = this.props.data.map((comment, index) => { return ( <CommentItemkey={index}author={comment.author}date={comment.date}> {comment.content} </CommentItem> ) }); return ( <div> { CommentNodes } </div> ) } } export { CommentList as default };
这样我们又创建了一个组件,并且在这个组件中我们使用了上面创建的那个组件。
在上面 类继承React.Component来实现 一节中,我们可以看出例子中出现了组件嵌套的情况,仔细想想,组件之间传递信息肯定是必然的。那么React是怎样进行 组件之间的数据通信 的呢?
回答这个问题之前,我们需要考虑一下,组件之间有几种数据通信。首先,第一种比较容易想到,那就是 父子组件 之间的数据通信。第二种也就自然而然的出来了—- 非父子组件 之间的数据通信。
父子组件之间的数据通信细分其实还有两种: 父与子之间 和 子与父之间 。
在React中, 父与子 之间的数据通信是通过 props属性 就行传递的;
而 子与父 之间的数据通信可以通过 父组件定义事件,子组件触发父组件中的事件时,通过实参的形式来改变父组件中的数据 来通信;
下面我们来分别通过例子来再现一下这种场景:
父组件:
import React from 'react'; import CommentItem from './comment-item'; classCommentListextendsReact.Component{ render() { let CommentNodes = this.props.data.map((comment, index) => { return ( <CommentItemkey={index}author={comment.author}date={comment.date}> {comment.content} </CommentItem> ) }); return ( <div> { CommentNodes } </div> ) } } export { CommentList as default };
import React from 'react'; classCommentItemextendsReact.Component{ render() { return ( <divclassName="comment"> <span>{ this.props.author }</span> <span>{ this.props.date }</span> <div>{ this.props.children }</div> </div> ) } } export { CommentItem as default };
通过上面我们可以看出,子组件CommentItem需要父组件传过来的值进行展示,而父组件是这样的:
<CommentItem key={index} author={comment.author} date={comment.date}> {comment.content} </CommentItem>
在父组件中添加了 key
、 author
、 date
属性来向子组件传值。想对象的,子组件通过props对象来获取父组件传过来的值,如下:
<span>{ this.props.author }</span> <span>{ this.props.date }</span> <div>{ this.props.children }</div>
好的,我们再来看一下另一种 子与父 之间的通信。
import React from 'react'; import CommentList from './comment-list'; import CommentForm from './comment-form'; classCommentBoxextendsReact.Component{ constructor(props) { super(props); this.state = {data: []}; } handleCommentSubmit(comment) { let comments = this.state.data; comments.push(comment); this.setState({data: comments}); } render() { return ( <div className="m-index"> <div> <h1>评论</h1> </div> <CommentList data={this.state.data} /> <CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} /> </div> ) } } export { CommentBox as default };
import React from 'react'; classCommentFormextendsReact.Component{ handleClick(){ let author = this.refs.author.value, content = this.refs.content.value; this.props.onCommentSubmit({author, content, date:new Date().getTime()}); this.refs.author.value = ""; this.refs.content.value = ""; } render() { return ( <divclassName="yo-list yo-list-a"> <labelclassName="item item-input"> <inputtype="text"className="yo-input flex"ref="author"placeholder="发布人"/> </label> <labelclassName="item item-input"> <textareaclassName="yo-input flex"ref="content"placeholder="留言内容"></textarea> </label> <label> <buttononClick={this.handleClick.bind(this)}className="yo-btn yo-btn-l">发表评论</button> </label> </div> ) } } export { CommentForm as default };
简单解释一下,子组件是一个表单组件,父组件中引用了该表单子组件,然后子组件中点击button按钮,触发子组件中的处理函数,处理函数通过 refs 获取到表单输入值然后调用父组件中传过来的函数,从而触发父组件中的函数执行改变data数据,data数据变动直接影响的是另一个组件CommentList的变化。
需要注意的是,在获取表单控件内的数据时,我们利用了一个 refs 对象,该对象用于获取真实DOM结构。具体来说就是,在React中组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM,这是React探索性的创新)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff (详细了解 diff 算法 ),它可以极大提高网页的性能表现。
在这里点击button时,input和textarea元素还是虚拟DOM,所以违法获取到输入的值,需要通过 refs 对象获取一下。
React中在处理 非父子组件之间的通信 时,简单的,嵌套不深的非父子组件(如:兄弟组件)可以仍然使用上一节 非父子组件之间通信 中的 事件函数,传形参 的方式来实现。如子组件 CommentList 和子组件 CommentFrom 之间的通信就是这样实现的。
如果,需要通信的两个非父子组件之间嵌套比较深,可以使用Flux和Redux来实现状态管理,这里不做详细阐述,下面会详细对比vue的状态管理进行说明。想先了解的可以看一下阮一峰老师的blog:
Flux 架构入门教程
Redux 入门教程(一):基本用法
Redux 入门教程(二):中间件与异步操作
Redux 入门教程(三):React-Redux 的用法
上面这张图已经很清楚的展示了react组件的声明周期了,就不过多介绍了。这张图摘自 React组件生命周期小结 ,对于理解React组件的声明周期钩子函数很有帮助。
Vue比React出来的要晚一些,自然顺应了前端组件化的大潮,并且个人觉得借鉴了部分React的思想来实现其组件化思想。
Vue默认的是单向数据流,这是Vue直接提出来说明的,父组件默认可以向子组件传递数据,但是子组件向父组件传递数据就需要额外设置了。
父子组件之间的数据通信是通过 Prop 和 自定义事件 实现的,而 非父子组件 可以使用 订阅/发布 模式实现(类似于Angualr中的非父子指令之间的通信),再复杂一点也是建议使用状态管理(vuex)。
我一直觉得Vue的官方文档是我看过最直接、易懂的技术文档,所以就直接给大家贴一个中文链接,自己去跟随尤雨溪学习吧。
Vue 中的组件和数据流
上面对比React和Vue的 组件及数据流 的时候,都提到了 当非父子组件之间嵌套过深 的时候都建议使用状态管理来维护数据的变化,那么到底它们之间的状态管理有什么区别呢?
先放个 官方中文链接 ,还是建议直接看官方文档。然后在放一下小例子去体会一下。
先简单说明一下,vuex状态管理的几个核心概念:
例子来了:
store.js
import Vue from '../libs/vue.js'; import Vuex from '../libs/vuex.min.js'; Vue.use(Vuex); const state = { loginPrePath:['/'] }; const mutations ={ LOGINPREPATH(state,path){ state.loginPrePath.unshift(path); }, LOGINPREPATHSHIFT(state){ state.loginPrePath.shift(); } }; export default new Vuex.Store({ state, mutations });
export default { loginPrePath: ({dispatch,state},path)=>{ console.log('actions loginPrePath:' +path); dispatch('LOGINPREPATH',path); }, loginPrePathShift: ({dispatch,state})=>{ console.log('delete....'); dispatch('LOGINPREPATHSHIFT'); } }
export default { loginPrePath: state=> state.loginPrePath };
<template> ... </template> <script> import Vue from '../libs/vue.js'; import VueResource from '../libs/vue-resource.js'; import Vuex from '../vuex/actions.js'; import VuexGet from '../vuex/getters.js'; Vue.use(VueResource); export default { data(){ return { username: '', password: '', loginBtn: 0 } }, vuex: { actions: { setLoginPrePath: Vuex.loginPrePath }, getters:{ getLoginPrePath: VuexGet.loginPrePath } }, methods: { forget(){ //使用vuex,修改状态值 this.setLoginPrePath({path:this.$route.path,title:'忘记密码'}); this.$router.go({path:'/index2/forget.json'}); }, submit(){ if(this.loginBtn === 3){ if(this.checked){ this.$http.post('/zhixiao/password.json',{password:this.password}).then( (res)=>{ if(res.ok){ console.log("注册成功,正在跳转登录页面"); setTimeout(()=>{ //获取状态值,通过getter var path = this.getLoginPrePath[0].path; this.loginPrePathShift(); this.$router.go(path); },1500); } },(res)=>{ console.log('网络错误,请稍后重试'); } ) }else{ console.log('请选择同意用户协议'); } }else{ console.log('请填写验证码'); } } } } </script>
上面的例子并无实际效果,是我从以前项目中拼出来的(vuex1.0),只是为了说明 loginPrePath 这个状态值在vuex中的使用方式。详细请看Vue官方文档。
要想实现SPA,路由是个不可避免的话题,作为主流的MVVM框架,怎么可能没有官方路由呢,两者的路由也很相似,都是利用各自的组件实现思想来实现的。
还是先贴 官方链接 ,简单易懂。
再给个例子(vue-router1.0),仔细看一下:
app.js
//引用component import index from './components/index.vue'; import main from './components/main.vue'; import my from './components/my.vue'; //APP route import Vue from './libs/vue.js'; import VueRouter from './libs/vue-router.js'; Vue.use(VueRouter); router.map({ '/':{ component: index, subRoutes:{ '/':{ component: main }, '/my':{ name:'my', component: my }, '/results/:key':{ name:'results', component:results } } } }); //启动router router.start(App,'body');
<template> <divclass="box"> <divclass="index-container"> <router-view> </router-view> </div> <footerid="footer"> <ul> <liv-bind:class="getIndex == $index ? 'active' : ''" v-for="data in navList" v-on:click="changePage($index)" v-link="{path:data.path,exact: true}"> <iclass="iconfont">{{{data.icon}}}</i> <p>{{{data.name}}}</p> </li> </ul> </footer> </div> </template> <script> var Vue = require('../libs/vue.js'); var VueResource = require('../libs/vue-resource.js'); import {getIndex} from '../vuex/getters.js'; import {changeIndexPage} from '../vuex/actions.js'; Vue.use(VueResource); export default { vuex: { actions:{ changeIndexPage }, getters:{ getIndex } }, data(){ return { cur: 0, navList:[ {path:'/',icon:'',name:'主页'}, {path:'/lee',icon:'',name:'排行榜'}, {path:'/search',icon:'',name:'发现'}, {path:'/my',icon:'',name:'我的'} ] } }, methods:{ changePage:function(i){ this.changeIndexPage(i); this.cur = this.getIndex; } } } </script>
大概就这样,感觉像是配置的感觉,其实这就是利用的vue中组件思想来实现的,详细看官方文档。
1.过渡系统
2.服务端渲染
https://cn.vuejs.org/v2/guide/transitions.html
https://cn.vuejs.org/v2/guide/transitioning-state.html
说道服务端渲染,其实官方对于使用SSR的原因和好处,解释的都很好,有兴趣的可以看一看 https://cn.vuejs.org/v2/guide/ssr.html
另外官方提供的服务端渲染的例子vue-hackernews2.0也很不错的,可以去借鉴看一看
https://github.com/vuejs/vue-hackernews-2.0