vue特点:
1.声明式渲染(把数据显示在页面上)
2.数据驱动视图(先有数据,再有视图)
3.双向数据绑定(数据变化,视图也变化。视图变化,数据也变化)
4.组件化开发(类似于函数的提炼)
三大件:
el: 用来指定vue实例管理的容器
//可以是选择器,也可以是dom结构
//只能是普通dom,不能是html,body
//不在此容器的dom,不会受vue实例影响
data:它是一个对象,用来规定数据
//它的作用是把数据显示在页面上,data就是用来约定数据的
//在后面学习,这个部分的数据会是从ajax接口中获取
methods:它是一个对象,用来定义函数表达式
//推荐写法 es6定义函数方法
changName (){
...
}
插值表达式:{{}}
作用:输出值
{{}} 中可以写data数据字段名
data数据表达式运算(字符串拼接,算数运算,三元表达式...)
{{}} 不能写js语句:(定义变量,if,for...)
不能访问不是vue实例中的实例
指令:
v-text和v-html
new Vue({
el: '#app',
data: {
str: 'vue',
strHtml: '<h1>vue</h1>'
}
})
---------html
<div id="app">
<p>{{str}}</p>
//v-text 设置标签中全部内容 但是不解析标签
<p v-text='str'></p>
//v-html 可以转义内容中的html标签
<p v-html='strHtml'></p>
</div>
v-if和v-show
//显示与隐藏
v-if='布尔值' v-show='布尔值'
<!-- 用法相似:可以显示隐藏元素
但是:
v-if 为false 时 直接移除元素
v-show为false 时 display:none
-->
双向数据绑定 v-model:
v-model
双向绑定表单控件
<div id="app">
<p>{{message}}</p>
</div>
<input type="text" v-model="message">
//v-model 是个特殊属性
//当数据变化时,dom会自动更新,
//表单值变了时,数据也会发生改变
new Vue({
el:'#app',
data:{
message:'hello'
}
})
v-model修饰符:
.lazy 默认情况下,v-model在每次input事件触发后都会将输入框的值与数据进行同步
当添加了 lazy 修饰符后 将会在鼠标失去焦点时或change事件后进行同步
//
.number 失去焦点后 将会解析输入的内容 如果是纯数值 将被parseFloat解析 返回数字类型
如果输入的是 "112adas " 以数字开头的将会 解析为 112 ,其他的无法解析将返回原值
//
.trim 自动过滤掉首尾空格
事件绑定
v-on:事件名=“事件处理函数”
v-on: 可以简写成 @
<div id="app">
<input type='number' v-model='number'>
<button v-on:click="increment">点击+1</button>
</div>
事件处理函数写在methods中:
const app = new Vue({
el: '#app',
data: { //属性
number: 1
},
methods: { //方法
increment: function () {
this.number++
}
}
})
事件合并:
用一个事件触发另一个事件,避免多次触发
@事件名 = $event.target.触发的事件名
体验:
<div id="app">
<p>{{ message }}</p>
</div>
// 实例化
const app = new Vue({
el: '#app', //管理id为app的入口节点,也就是说#app所有节点都被vue管理
data: { //响应式数据 就是数据发生改变 所有绑定数据的都会改变
message: 'Hello Vue.js'
}
})
console.log(app.message) // Hello Vue.js
app.message = '可以改变所有的message'
// 所有绑定了{{ message }} 的在message改变时也会跟着改变
阻止冒泡:
vue中的事件也有冒泡机制
在子元素上的点击事件,也会触发父元素的点击事件
如果要阻止冒泡,只需要给事件添加修饰符 .stop
v-on:click.stop=''
阻止默认行为:
阻止默认行为
添加修饰符.prevent
<a href='www.baidu.com' v-on:click.prevent></a>
也可以既不跳转也不冒泡
v-on:click.prevent.stop
v-for 列表渲染
快速批量生成列表结构
格式1:完整格式
<ul>
<li v-for="(item,index) in arrhao" :key="index"> {{item}} </li>
</ul>
格式2:简写格式
可以省略 :key 和索引变量
<li v-for='item in aihao'>{{item}}</li>
v-bind:
动态绑定标签上的属性的值
可简写为 :
<img src='' art='' title=''/>
<img v-bind:src='imgSrc' :art='***'/>
new Vue({
el:'#app',
data:{
imgSrc:'img/1.jpg'
}
})
v-bind绑定class:
通过动态绑定class 来控制类样式
三元表达式形式: <div class='box' :class='cls ? 'red':'blue'></div>
解析:如果cls=true 则添加red 反之 blue
对象形式: <div :class='{red : 为true的值或表达式 }'></div>
<div :class='redObj'></div> vue实例中 data:{redObj:{red:true}}
解析: {red : ### } 如果为true 才会添加到class 为false则什么都不做
数组形式: <div :class='redArry'></div> vue实例中 data:{redArry : ['red','algin']}
解析: 所有在数组中的类都将会被添加到 class 中
v-bind绑定style:
通过动态绑定style 来控制行内样式
对象形式: <div :style='{color:'red' , 'font-size':'20px'}'></div>
数组形式: <div :style="[{color:'red'},{},{}...]"></div>
过滤器:
本质就是一个函数 将原来的值当形参传入 修改后返回来
如:将字符串用0补足 ES6新方法 .padStart(6,'*')
如:将单词首字母大写
// 格式:
// filters: {
// 过滤器名:function(value,其他参数){
// return String(val).padStart(5,0)
// }
// }
<p> {{ msg | f1 }} </p> //相当于f1(msg) 得到 00vue
data:{
msg:'vue'
},
filters:{
f1(val){
return String(val).padStart(5,0)
}
}
全局过滤器:
Vue.filter(过滤器名, 函数)
全局过滤器定义格式与局部相同 , 只是位置不同
千分位:
let s=1561.2;
let b = s.toFixed(2).replace(/(\d)(?=(\d{3})+.)/g,'$1,');
// 1,561.20
// 正则表达式解析 : (/(\d)(?=(\d{3})+\.)/g, '$1,')
// 一个数字(\d) 他的后面要有三个数字加. 每隔3个数 加一个 ,
自定义指令:
定义:
directives:{
指令1:{
inserted(el){
}
},
指令2:{
inserted(el){
}
},
}
使用:
<元素 v-指令名></元素>
注意: 指令名为小写 inserted 为固定写法 inserted参数为使用自定义指令的那个dom标签
模糊查询:
http://localhost:3000/list ?name_like=头
计算属性 :computed
作用: 对现有的数据进行加工(运算) ,得到新的数据项
格式: computed: {
num() {
return this.num1 + this.num2
}
}
使用: 在模板中 {{ 计算属性名 }}
在实例内部 this.计算属性名
生命钩子: created
在vue实例创建完成后调用 -> 可以设置页面初始化的渲染
组件:
- 是可复用的vue实例
定义一个组件 :
components : {
组件1:{ 配置项 } //组件名建议用大驼峰
//配置项与vue实例中的基本一致 (没有el) data(){ return {} }
//data 格式固定写法
}
使用一个组件 :
在父组件中以标签形式使用
<div id="app">
<zujian1></zujian1>
</div>
props:
- 用于接收父组件传递的值
组件之间的通讯:
父传子 :
把父组件中定义的数据传给子组件
原理:父组件通过自定义属性传给子组件,在子组件中通过 props 属性接收
父组件--: 自定义属性 = '数据'
子组件--: props: ['自定义属性名'] 再用this.自定义属性 就能得到数据
子传父 :
父组件 定义自定义事件触发函数 -- @abc='fn'
子组件触发 this.$emit('父组件的自定义事件名', 数据)
父组件 methods:{
fn(obj){
obj为子组件传递过来的数据
}
}
全局组件:
格式 :
Vue.component('mybtn' ,{
template:`<button>全局组件</button>`
})
适用场景: 方便于代码共享
路由:
- 根据地址栏变化(不发请求),去局部更新不同的页面内容—> 根据哈希值 hash
- SPA:单页面应用程序 一个系统的所有功能在一个页面上实现(移动端比较多)
- 优点:整体不刷新,用户体验好
- 缺点: 首次加载比较慢
vue-router插件:
链接 : https://cdn.bootcss.com/vue-router/3.1.3/vue-router.min.js
使用:
//VueRouter 构造器创建路由实例
const router=new VueRouter({
router : [
{path:'路径' ,component : 组件名 }
{path:'路径' ,component : {template:`<div></div>`} }
]
})
//使用路由实例
new Vue ({
el:'#app',
router : 路由对象名 // 路由对象名与router一致的话可以直接写 router
})
//设置路由出口 就是渲染在哪就放到哪
<div id="app">
<router-view></router-view>
</div>
路由链接导航:
router-link 组件
<div id="app">
<router-link to='跳转地址'>登录</router-link>
<router-link to='跳转地址'>注册</router-link>
<router-view></router-view>
</div>
router-link 将会被解析成 a 标签
-
router-link的不同写法
-
声明式导航
字符串写法: <router-link to='/list'>列表</router-link> 对象写法: <router-link :to='{path:'/list'}'>主页</router-link>
带查询参数的对象写法:
带路径参数的对象写法:
语法中path与params不能同存在 用name 代替path
-
编程式导航
通过 js 调用一个导航函数 (this.$router.push) 格式 : 字符串 --- this.$router.push('/login') 对象 --- this.$router.push({path:'/login'}) 路径传参--- this.$router.push({name:"login" ,params:{id:'100'}}) 查询传参--- this.$router.push({path:'login' ,query: {id:'002'}})
-
-
动态路由
- 不同的路由地址,指向同一个组件,此时需要使用动态路由
+
- 不同的路由地址,指向同一个组件,此时需要使用动态路由
vue声明周期:
beforeCreate:
数据初始化之前调用
特点: 获取不了数据
使用场景: 几乎不用
created:(重要)
数据初始化之后调用
特点: 获取数据/方法
场景: ajax请求,获取本地数据
beforeMount: 编译挂载之前调用
特点: 打印DOM标签是之前的 {{ name }}
Mount: 编译挂载后使用 (操作DOM)
打印编译DOM标签之后 {{ 张三 }}
场景: 操作DOM + 发送ajax
beforeUpdate: 更新数据之前调用
特点: 在数据更新之前调用可以 获取更新之前的数据
Update: 数据更新后调用
特点: 获取更新之后的数据
beforeDestroy : 销毁之前
清除开发者自己的一些实例销毁后还会执行的(如:定时器)
Deatroyed: 销毁之后
侦听器:watch
侦听数据变化
侦听普通属性值:
watch : {
name(newVal,oldVal) {
newVal为最新的值
oldVal为旧值
}
}
侦听对象的属性:
watch : {
obj : {
deep: true,
handler(newVal) {
newVal为对象最新的
}
}
}
侦听数组的变化:
watch : {
arry () {
handler(newVal) {
newVal为数组最新的
}
}
}
响应式原理:
响应式流程: data => obServer => defineReactive => defineProperty => data(get/set) => 响应式数据
动态添加的数据不是响应式的:
- 对象:
const vm = new Vue({
data: {
a: 1,
obj: {
}
}
})
vm.a = 3 是响应式的
vm.b = 2 非响应式
this.obj.age = 18 非响应
必须在 data 上才能走过响应式流程,转换成响应式数据
解决方法:
格式: Vue.set(obj, propertyName, value)
Vue.set(vm, 'b' , 2) 响应式数据
对象 =>
Vue.set(this.obj, 'age' , 18) 方法1
this.$set(this.obj, 'age' , 18) 方法2
2.数组
const vm = new Vue({
data: {
arr: [1,2,3]
}
})
this.arr[1] = 6 非响应
this.arr.length = 1 非响应
Vue底层封装了数组的七个方法可以实现将其转换为响应式数据
push , pop , shift , unshift , splice , sort , reverse
解决方法:
$set :
this.$set(this.arr , 1 , 6) arr数组 , 将索引1 , 改为6
封装的 splice 方法 :
this.arr.splice (1 , 1 , 6)
参数1 : 下标开始
参数2 : 删除一个
参数3 : 新增/替换
处理长度:
this.arr.splice(1) 从下标1开始 删除到最后
异步DOM更新及$nextTick
vue中数据发生改变,dom中的数据也会跟着改变,但是这个过程是异步的 vue的数据发生改变之后,DOM不会立即更新,等渲染完成的时候才会更新DOM 这样我们只能获取到改变之前的值
在实际开发中,有可能需要在数据改变之后,获取到DOM数据
这时候就可以使用 $nextTick 函数
当vue更新完DOM后 ,会自动调用$nextTick函数 确保获取到最新值
this.$nextTick(() => {
let result = document.querySelector('p').innerText
result将会是最新的数据
})
其他写法:
1.
Vue.nextTick(()=> {...})
2.
Vue.nextTick(() => {...}).then()
3.
await Vue.nextTick()
...
数据组件间传递:
事件总线:
1. 创建一个 空Vue实例
const bus = new Vue()
2. 发布事件
bus.$emit('事件名', 要发布到总线上的数据 )
3. 接收到事件
bus.$on('事件名', 处理函数) // 回调函数参数为发布的数据
子传父:
1. 在子组件标签上创建自定义事件 <son @sons="fn"></son>
2. 父组件在methods定义 这个 fn () { }
3. 在子组件中调用这个自定义事件
created () {
this.$emit('sons', 要传递给父组件的数据)
}
2.1 返回父组件, 在父组件中接收
fn (val) { 参数为子组件传递过来的数据 }
父传子:
1. 在子组件标签上定义自定义属性 <son :sons='son'></son>
2. 在子组件中通过props 配置项, 来接收指定的数据
props: ['son']
3. 在子组件中就能正常的调用 son 这个值
{{son}} this.son
跨域:
反向代理:
// 反向代理
devServer: {
// 设置代理
proxy: {
"/": { // "/cats" 以什么开头的要跨域
target: 'http://api.zhuishushenqi.com/', // 域名
ws: true, // 是否开启websockets
changOrigin: true, // 开启代理: 在本地会创建一个虚拟服务器,然后发送请求 的数据,并同时接受请求的数据,
// 这样服务器和服务端进行数据交互就不会有跨域问题
pathRewrite: {
"^/cats": "/cats"
}
}
}
}
注意: 关闭axios 设置的全局请求域名
VueX
开始:
下载引入:
// index.js 引入vuex 包
import Vue from 'vue'
import Vuex from 'vuex'
// 加载
Vue.use(Vuex)
// main.js
import store from './index.js'
new Vue({
store // 绑定到Vue上
})
使用
//index.js
创建store
const store = new Vuex.Store({
state: { //状态容器
count: 10,
}
})
//组件中 foo.vue
mapState: 管理多个状态, 生成计算属性
import { mapState } from 'vuex' //引入辅助函数 mapState
//计算属性中
computed: {
...mapState({
count: 'count' // 如果与自定义计算属性的名称相同,将会覆 盖自定义计算属性
})
}
// 使用:
<p> {{count}} </p> // 10
状态改变
改变store中的值:
<p> {{count}} </p> // 10
<button @click="insts">自增</button> // 点击自增count
//index.js
mutations: 改变store 中状态的唯一途径 就是 提交 (commit)mutation
mutations一旦提交就会形成一条历史记录
// 与 state (状态容器)同级
mutations: {
increment(state){
state.count ++
}
}
//foo.vue
methods: {
insts(){
this.$store.commit('increment')
}
}
store中的计算属性: Getter
// index.js
state: {
todos: [
{ id: 1, text: '吃饭', done: true },
{ id: 2, text: '吃饭1', done: false },
{ id: 3, text: '吃饭2', done: false },
]
},
getters: {
remaining: state => {
return state.todos.filter(item =>
item.done === false
).length
}
}
//foo.vue
引入 mapGetters
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性
import { mapGetters } from './index.js'
computed: {
...mapGetters({
remaining: 'remaining'
})
}
<p> todos未完成的数量为: {{ remaining }} </p>
计算属性中的 返回一个函数调用
//index.js
getters: {
getTodoById:state => {
return id => {
return state.todos.find(item => item.id === id)
}
}
}
//foo.vue
...mapGetters({
getTodoById: 'getTodoById'
})
<p>todos第二个的名字是: {{ getTodoById(2).text }} </p>
Mutation: 同步函数(同步事务)
//foo.vue
methods: {
insts(){
this.$store.commit('increment', {num:2})
}
}
//index.js
mutation:{
increnent(state, payload){
state.count += payload.num
}
}
// 对象方法简写:
// foo.vue
methods: {
insts(){
this.$store.commit({
type: 'increment',
num: 2
})
}
}
//index.js 不变
使用常量替代 Mutation 事件类型
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// index.js
import { SOME_MUTATION } from './mutation-types.js'
import { mapMutations } from 'vuex'
mutations: {
[SOME_MUTATION](state){
state.message = '你好, 世界'
}
}
// foo.vue
methods: {
...mapMutations(['SOME_MUTATION'])
//或
...mapMutations(
{changeMutation :'SOME_MUTATION'})
}
<button @click="SOME_MUTATION"> 更改 message </button>
//或
<button @click="changeMutation"> 更改 message </button>
Action :分发 Action
// index.js
actions : {
increment({commit}, {num: }){
setTimeout(()=> {
commit({
type: 'increment',
num
})
})
}
}
// foo.vue
<button @click="asyncIncrement">异步按钮(增加)</button>
asyncIncrement(){
this.$store.dispatch('increment', {
num: 3
})
}
Module : 将 store 分割成模块
const moduleA = {
state: {...},
mutations: {...},
actions: {...},
getters: {...},
...
}
const moduleB = {
state: {...},
mutations: {...},
actions:{...},
...
}
// 绑定
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
// 使用
store.state.a
store.state.b
// 创建modules文件夹
// 创建foo.js ,bar.js , index.js
// foo.js
export default{
state: {
a: 123
},
mutations: {...}
}
// index.js
// 引入modules
import foo from './modules/foo'
// 注册
const store = new Vuex.Store({
modules : {
foo
}
})
//foo.vue 使用
<p> {{ $store.state.foo.a }} </p> // 123
命名空间问题
// module 模块内部的action, mutation , getter 是注册在全局的 $store命名空间上的
// 如果想要一个模块具有高度的封装度 和 复用性 , 可以通过添加
namespaced: true的方式使其成为带命名空间的模块, 当模块被注册后,它的所有getter,action及mutation都会自动根据模块注册的路径调整命名
// index.js 注册为 index
export default {
namespaced: true;
gettrts: {
abc(state){
return state.a + '...'
}
}
}
//加命名空间后调用
<p> {{$store.getters['index/abc'] }} </p>
----------- 局部 ------------
// foo.vue
// 映射局部模块的 state
...mapState('foo',{ // 模块名称
a: 'a'
})
使用:
<p> {{ a }} </p>
// 映射局部模块的 getters
...mapGetters('foo', ['abc'])
使用:
<p> {{ abc }} </p>