文章是从有道云笔记转到csdn博客,如果存在图片丢失问题或者错误,可阅读原文与留言,谢谢!
文章目录
之前有段时间学习了vue,并做了一个简单项目,隔了半年多再去用vue,已经忘了七七八八了,学习了的东西总要留下一点痕迹吧,也方便后期翻翻看看复习下。
vue相对传统的JS开发,个人感受最大不同,也是算是优势吧
- 可以单向或双向响应绑定数据,可以避免传统JS频繁操作DOM;
- 可以组件化,提高UI的复用性;
- 视图、数据、结构可以很好的分离,更接近MVVM思想;
- 由于是单页面,加载速度与传统html页面会更快、更流畅,体验更好。
vue项目结构
vue单文件结构
<!--template节点 一级节点-->
<template>
<div id="container">
<!--html内容结构区,所有的内容都写这个div容器内,而且只能一个根节点容器-->
<img alt="Vue logo" src="./assets/logo.png">
<!--使用子组件 ,当前vue可以称为父组件-->
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
/*通过ES6模块化语句import导入vue组件,又称为子组件 */
import HelloWorld from './components/HelloWorld.vue'
/*export用于声明可以导出vue组件,会在main.js导入使用 ES6模块化语句*/
export default {
name: 'vue',
/*声明注册组件,HelloWorld是组件名称,可以在html结构中使用*/
components: {
HelloWorld
},
created: () => {
console.log("App.vue")
},
mounted: () => {
}
}
</script>
<style>
<!--样式编辑区-->
</style>
既然有父子组件的称呼,当然是少不了两者之间的数据交互,这个后面再介绍。
项目结构
├── README.md 项目介绍
├── index.html 入口页面
├── build 构建脚本目录
│ ├── build-server.js 运行本地构建服务器,可以访问构建后的页面
│ ├── build.js 生产环境构建脚本
│ ├── dev-client.js 开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新
│ ├── dev-server.js 运行本地开发服务器
│ ├── utils.js 构建相关工具方法
│ ├── webpack.base.conf.js wabpack基础配置
│ ├── webpack.dev.conf.js wabpack开发环境配置
│ └── webpack.prod.conf.js wabpack生产环境配置
├── config 项目配置
│ ├── dev.env.js 开发环境变量
│ ├── index.js 项目配置文件
│ ├── prod.env.js 生产环境变量
│ └── test.env.js 测试环境变量
├── mock mock数据目录
│ └── hello.js
├── package.json npm包配置文件,里面定义了项目的npm脚本,依赖包等信息
├── src 源码目录
│ ├── main.js 入口js文件
│ ├── app.vue 根组件
│ ├── components 公共组件目录(高复用业务组件、纯样式组件)
│ │ └── title.vue
│ ├── assets 资源目录,这里的资源会被wabpack构建
│ │ └── images
│ │ └── logo.png
│ ├── routes 前端路由
│ │ └── index.js
│ ├── store 应用级数据(state)
│ │ └── index.js
│ └── views 页面目录
│ ├── hello.vue
│ └── notfound.vue
├── static 纯静态资源,不会被wabpack构建。
└── test 测试文件目录(unit&e2e)
└── unit 单元测试
├── index.js 入口脚本
├── karma.conf.js karma配置文件
└── specs 单测case目录
└── Hello.spec.js
- 基础库: vue.js、vue-router、vuex、whatwg-fetch
- 编译/打包工具:webpack、babel、node-sass
- 单元测试工具:karma、mocha、sinon-chai
- 本地服务器:express
index.html、main.js和App.vue这三者的关系,这里用最简单方式来测试三者的关系,通过日志观察方式可以知道大概的关系:
index.html
<script type="text/javascript">
console.log("index.html")
</script>
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
console.log('main.js')
//#app 这个ID是index.html中div的
new Vue({
el: '#app',
//注册路由,让整个应用拥有路由功能
router,
//在声明注册App.vue组件
components: { App },
template: '<App/>'
})
App.vue
//vue生命周期created函数
created: () => {
console.log("App.vue")
}
最后日志打印:
- index.html:是整个项目入口。
- main.js:是逻辑入口,相当于桥梁,承接着index.html与App.vue的联系。
- App.vue:第一个组件,组件入口。
https://www.cnblogs.com/Renyi-Fan/p/12484173.html
模板语法
插值
文本
//这个msgText需要在data属性中定义
//在其他地方改变了msgText的值时,span标签也会跟着改变,这就是响应式
<span>{
{msgText}}</span>
data() {
return {
msgText: "今天的天气很好",
}
}
指令
指令一般分为两种,一种是vue自带的,带v-
开头的,比如v-bind
、v-on
、v-html
等,另外一种就是自定义指令,是在子组件中定义了props
相关属性,自定义指令与v-bind
指令都是简称绑定指令
指令的作用:当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
v-bind
v-bind 指令常用用于响应式地更新 HTML attribute,比如src、href、border等HTML属性,其中:
后面就是需要绑定的属性名称。
//注意img标签,通过v-bind来动态改变值,其中url不能是在`assets`中
<img v-bind:src="url">
<a v-bind:href="url">
另外v-bind指令可以缩写,把冒号前面的v-bind省略,直接写成:
<img :src="url">
<a :href="url">
自定义绑定指令
自定义指令往往用于父组件向子组件的数据传递,指令名称必须在子组件中的props定义声明。比如HelloWorld.vue子组件在App.vue父组件中使用:
HelloWorld子组件
<template>
<div class="hello">
<h1>{
{ title }}</h1>
<img v-bind:src="imgUrl" />
</div>
</template>
<script>
export default {
name: 'HelloWorld',
//在props声明属性名称
props: {
title: String,
imgUrl:String,
}
}
</script>
App.vue父组件
<template>
<div>
<!--3、使用组件 通过:引用子组件定义的属性名称 即指令-->
<HelloWorld :title="title" :imgUrl="url" />
</div>
</template>
<script>
//1、导入子组件,并设置注册组件的名称HelloWorld
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
components: {
//2、注册导入的组件
HelloWorld
},
data() {
return {
title: "今天的天气很好",
url: "https://private.yinshua86.com/upload_tmp/56219-pexels-valeriia-miller-3685530.jpg?imageView2/2/w/260&attname=pexels-valeriia-miller-3685530.jpg"
}
}
}
</script>
如果使用data属性中的变量,必须需要带:
来绑定引用子组件的props指令,data属性的图片URL不能是本地静态链接,必须是网络链接,不然是会失效;如果是绑定的文本属性,可以把:
去掉,直接初始化静态值,比如 title="今天天气真好"
v-on
v-on指令一般是用于DOM监听,比如click、copy、change事件等
<!-- 完整语法 -->
<button v-on:click="doSomething">点击</button>
<!-- 缩写 -->
<button @click="doSomething">点击</button>
接着在methods
属性中定义声明doSomething
方法
methods:{
doSomething(){
console.log('点击了')
}
}
data属性
data属性必须直接返回一个已声明初始对象的函数,这个对象也不能间接使用外部的对象。
//正确用法,使用函数返回对象
data() {
return {
title: 'Hello'
}
}
//错误写法,会导致再次打开页面时,显示上次数据
data: {
title: 'Hello'
}
//错误写法,同样会导致多个组件实例对象数据相互影响
const obj = {
title: 'Hello'
}
data() {
return {
obj
}
}
条件渲染
开发中基本那里都会用到if(…) else(…)条件判断语句,在vue中同样也支持,有两种条件指令
- v-if和v-else if、v-else
- v-show
v-if和v-else if、v-else
<template>
<div>
<div v-if="score >= 80">成绩很优秀 {
{score}}</div>
<div v-else-if="score < 80 && score >= 60">成绩良好 {
{score}}</div>
<div v-else>成绩很差 {
{score}}</div>
<button v-on:click="doSomething"> 点击</button>
</div>
</template>
<script>
export default {
name: 'app',
data() {
return {
score:100,
}
},
methods:{
doSomething(){
console.log('点击了')
//取0 - 100随机整数
this.score=Math.ceil(Math.random()*100)
}
}
}
</script>
使用v-if指令DOM元素必须是连续的,如果被分割了运行会不通过的。另外也常常通过v-if指令判断DOM元素是否显示隐藏,前提v-if的条件值为true
就会渲染显示,为false
时就会销毁DOM元素隐藏。
//当score > 60时就会渲染显示,其他值都会销毁
<div v-if="score > 60">
<HelloWorld :title="title" :imgUrl="url" />
</div>
v-if可以设置到
template
根节点元素上。
v-if切换过程中条件块内的事件监听器和子组件元素会被销毁和重建,每重建一次都会渲染一次,如果频繁的切换会带来性能的开销。
v-show
v-show是根据条件来判断是否显示元素的指令,没有else指令
<div>
<div v-show="score >= 80">成绩很优秀 {
{score}}</div>
<div v-show="score < 80 && score >= 60">成绩良好 {
{score}}</div>
<HelloWorld :title="title" />
<div v-show="score > 60">
<HelloWorld :imgUrl="url" />
</div>
<div v-show="score < 60">成绩很差 {
{score}}</div>
<button v-on:click="doSomething"> 点击</button>
</div>
从示例可以看出v-show指令在DOM元素中可以不连续的,这点与v-if使用上有所不同。
v-show不管初始条件是什么,元素只会初始渲染一次,并且只是简单地基于 CSS 进行切换,来控制元素的显示和隐藏。
v-if 和 v-show 区别
- v-if 有更高的切换开销,如果在运行时条件很少改变,则使用 v-if 较好。
- v-show 有更高的初始渲染开销。如果需要非常频繁地切换,则使用 v-show 较好
列表渲染
列表渲染是基于v-for指令来完成,基本使用如下:
<template>
<div id="app">
<div v-for="(item,index) in lists" :key="index">
<p>{
{index}} - {
{item}}</p>
</div>
<div> ------------------------------------------------</div>
<div v-for="(item,index) in listsObj" :key="index">
<p>{
{index}} - {
{item.name}} - {
{item.age}} - {
{item.sex}}</p>
</div>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'App',
data(){
return{
lists:['张三','李四','王老五','王麻子'],
listsObj: [{
id: 0,
name: '张三',
age: 28,
sex: '男'
}, {
id: 1,
name: '李四',
age: 20,
sex: '男'
}]
}
},
created () {
console.log('App.vue')
}
}
</script>
上面的列表渲染的数据是来源于数组,数组渲染列表一般是(item,index) in lists
的方式。
- item:是数组元素的别名,可以随便取名,没有特殊要求
- index:数组的下标,可选
- lists: 数据来源,一般在data属性函数中会初始化声明
- key: key的作用是确保数组元素与html 元素的位置是一致的,保证数据改变时能够准确及时的刷新; key是唯一的,如果是数组用index,如果遍历是对象,需在每个对象元素加上一个唯一id属性。建议使用v-for指令要加上key。
当在组件上使用 v-for 时,key是必须的
运行结果:
遍历对象
<div v-for="(name,value,index) in person" :key="index">
<p>{
{index}} - {
{name}} - {
{value}}</p>
</div>
data() {
return {
person: {
id: 0,
name: '张三',
age: 28,
sex: '男'
}
}
},
遍历对象一般是(name,value,index) in person
方式,参数如下:
- name:对象的属性名称,比如age、sex等
- value:属性名称对应的属性值
- index: 属性名称对应的下标
- person:对象名称
- key:同上
运行结果:
v-model
上面介绍的指令都是单向数据绑定,只能在js代码中触发改变data属性对象的值来刷新html。
v-model指令常用于表单 input、textarea 及 select 元素上实现双向数据绑定。所谓双向数据绑定是,在html元素交互改变的值会同步到js中data属性对象中,在js触发改变的值也会同步html上刷新,这就是双向数据绑定。
<input placeholder="输入名字" v-model="user_name">
<button @click="randomGeneration">随机名字 - {
{user_name}}</button>
data() {
return {
user_name:'',
lists: ['张三', '李四', '王老五', '王麻子'],
},
methods:{
randomGeneration(){
let randomPos=Math.floor(Math.random()*this.lists.length)
this.user_name=this.lists[randomPos]
}
}
插槽
概念
插槽通俗的讲,就是将父组件内容添加到子组件中,在这个添加的过程中有一套规则标准。解决下面几个疑问就可以正确将父组件内容添加到子组件中:
- 内容需要插到子组件中哪个位置 – 内容存放的位置
- 内容在父组件哪个位置编写是合理的 – 内容的来源
- 父组件与子组件是如何数据交互的
内容需要插到子组件中哪个位置?
在子组件中通过slot指令定义声明内容存放的位置。如下:
子组件Child.vue
<template>
<div>
<slot>父组件内容存放的位置</slot>
</div>
</template>
内容在父组件哪个位置编写是合理的?
父组件App.vue
<template>
<div>
<Child><template>插槽测试内容编写区</template></Child>
</div>
</template>
<script type="text/ecmascript-6">
import Child from './components/Child.vue'
export default {
name: 'App',
components:{
Child,
},
}
<script>
注意:在父组件编写子组件插槽内容区域,需要用template元素包裹
运行后,会把父组件中的内容插槽测试内容编写区
替换掉子组件中的父组件内容存放的位置
文本。最终显示
具名插槽
上面示例是子组件只有单个插槽,如果多个插槽,父组件是如何区分的;其实这个很简单,就是在子组件中为每个插槽设置一个name属性来区分,name属性名称是唯一的。如下
子组件Child.vue
<div>
<slot name="one"></slot>
<slot name="two"></slot>
</div>
父组件App.vue
<Child ><template v-slot:one>内容来源1</template></Child>
<Child ><template v-slot:two>内容来源2</template></Child>
在父组件中的template元素通过v-slot
来绑定子组件的插槽属性名称,也可以简写如下
<Child ><template slot="one">内容来源1</template></Child>
<Child ><template slot="two">内容来源2</template></Child>
简单可以认为,通过name属性定义声明的插槽称之为具名插槽
插槽下父子组件的数据交互
前面解决了插槽内容的来源与存放位置这两大问题,只剩最后一步就是数据的交互。
父组件的编译作用域与子组件是不相通的,简单的说就是在父组件不能访问子组件的data属性内容。
正常情况下,父组件向子组件传递数据,会在子组件中定义声明props
内容,同样地我们可以在子组件插槽slot元素中定义一个pros指令来绑定子组件的data属性内容。如下
子组件Child.vue
<template>
<div>
<div>
<!--persons是插槽的prop属性-->
<slot name="one" v-bind:persons="person"></slot>
<!--age是插槽的prop属性-->
<slot name="two" v-bind:age="maxAge"></slot>
</div>
</div>
</template>
<script>
export default {
name: "child",
data() {
return {
person:{
name:"张三"
},
maxAge:27
}
}
}
</script>
注意:name属性相当于给每个插槽设置一个唯一属性
父组件App.vue
<Child >
<template v-slot:one="user">
<div>
内容来源1 访问子组件数据 persons {
{user.persons.name}}
</div>
</template>
</Child>
<Child >
<template v-slot:two="user">
内容来源2 访问子组件数据 age {
{user.age}}
</template>
</Child>
在v-slot:one
插槽name属性后面添加别名(这个别名可以任意取名),然后用别名去调用插槽的prop属性,这样就可以访问子组件的数据。
- 子组件插槽的name属性名称,相对于在子组件自定义了一个绑定指令
- 子组件插槽的name名称别名,可以任意取名
- 子组件插槽的prop属性名称,相对于子组件插槽定义了public方法,提供外界来调用。
运行结果:
路由
路由的作用,是将单一Web页面实现成多视图页面的过程
官方推荐使用vue-router库,首先在项目的package.json
中依赖路由插件:
"dependencies": {
"vue-router": "^3.0.1"
},
路由命名
在Router实例中,在给routes 配置中给路由设置名称name,这个name是路由跳转的唯一标识符,可根据name来跳转哪个组件。
- path:路由的相对路径,如果只有
/
则是根路由,对应的组件会自动渲染到router-view
的元素内 - name:路由的名称,可根据名称来跳转
- component:组件名称,先导入组件再声明
path路由路径,冒号
:
后面是用于路由跳转传参使用
<!--路由跳转触发,router-link 最终会渲染成a标签-->
<router-link :to="{name:'user'}">前往用户中心</router-link>
<!--路由组件容器,目标组件所存放的位置,
如果在应用第一个组件(App.vue)声明了router-view,则会自动加载根路由组件显示;
其他情况都是用于渲染存放路由跳转的目标组件
-->
<router-view ></router-view>
路由跳转
触发路由跳转有两种方式,分别是:
- 在html中使用
router-link
来渲染成a标签 - 在js中编程代码的方式来跳转,比如push等api。
router-link路由跳转
router-link最终把它渲染给a标签,然后用router-link
标签包裹触发元素,如下:
<router-link :to="{name:'user'}">前往用户中心</router-link>
:to
是用于路由跳转的绑定指令name
是在命名的时候所定义的路由名称
上面是无参路由跳转,路由组件传参如下:
<router-link :to="{name:'user',params:{userId:123}}">前往用户中心</router-link>
通过params
声明一个对象,用于初始化需要传参的参数,比如userId
的属性,就是对应在路由路径path带有冒号:
后面的名称
点击跳转最终的路径:
如果有多个参数需要传递,在注册命名路由处用/
分割,如下:
path:'/user/:userId/:userName',
对应router-link触发点
<router-link :to="{name:'user',params:{userId:123,userName:'ABS'}}">前往用户中心</router-link>
编程式路由跳转
在vue内部可通过this.$router.push
实现路由跳转,具体如下:
<div @click="routePush">前往用户中心</div>
routePush() {
this.$router.push({
name: 'user', params: {
'userId': 987,
"userName": 'AAS'
}
})
}
路由获参
在目标路由组件接收参数,在vue的钩子函数mounted
调用this.$route.params.
来获取参数
mounted() {
this.userId= this.$route.params.userId
this.userName= this.$route.params.userName
}
总结
后面待续:
- 网络请求
- 状态管理
参考
- https://www.jianshu.com/p/57f51bcdf1d4
- https://zhuanlan.zhihu.com/p/114502325
- https://router.vuejs.org/zh/guide/
- https://cn.vuejs.org/v2/guide/