基础
watch
watch: {
msg(newVal, oldVal){
console.log('msg更改了', newVal, oldVal)
},
msg: {
handler(newVal, oldVal){
console.log('msg更改了', newVal, oldVal)
},
deep: true //深度监听
}
}
生命周期
- beforeCreate
- created:创建完组件
- beforeMount
- mounted: template挂载到DOM上
- beforeUpdate
- updated:界面发生更新
- beforeDestroy
- destroyed
指令
v-model
v-model会忽略所有表单元素的value、checked、selectedattribute 的初始值而总是将 Vue 实例的数据作为数据来源
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
- text 和 textarea 元素使用
valueproperty 和input事件; - checkbox 和 radio 使用
checkedproperty 和change事件; - select 字段将
value作为 prop 并将change作为事件
示例:
- input type=text: v-model => @input + value,
<input type="text" v-model="msg" />
<input type="text" :value="msg" @input="msg=$event.target.value" />
- input type=radio
<input type="radio" value="男" v-model="sex" />
<input type="radio" value="女" v-model="sex" />
- input type=checkbox
<!--单选 isAgree为布尔值-->
<input type="checkbox" v-model="isAgree"
<input type="checkbox" checked @change="isAgree = $event.target.checked">是否同意
<!--多选 hobbies为数组-->
<input type="checkbox" value="篮球" v-model="hobbies" />篮球
<input type="checkbox" value="足球" v-model="hobbies" />足球
<input type="checkbox" name="" id="" @change="changeFruits" value="篮球">篮球
<input type="checkbox" name="" id="" @change="changeFruits" value="足球">足球
changeFruits($event){
if($event.target.checked){
this.fruits.push($event.target.value)
}else{
this.fruits = this.fruits.filter(item => item !== $event.target.value)
}
},
- select
<!--单选 fruit为string-->
<select v-model="fruit">
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
</select>
<!--多选 fruits为数组-->
<select v-model="fruits" multiple>
<option value="苹果">苹果</option>
<option value="香蕉">香蕉</option>
</select>
- v-model的修饰符
- .lazy
<!-- 在“change”时而非“input”时更新 --> <input v-model.lazy="msg">- .number 自动将用户的输入值转为数值类型
- .trim 自动过滤用户输入的首尾空白字符
v-once
不需要表达式,只渲染元素和组件一次,不随着数据的改变而改变
<span v-once>This will never change: {{msg}}</span>
v-cloak
不需要表达式,防止闪烁
[v-cloak] {
display: none;
}
<div v-cloak> {{ message }} </div>
不会显示,直到编译结束。
事件修饰符
- @click.stop: 阻止冒泡
- @click.once: 只触发一次
- @click.prevent: 阻止默认事件
- @click.native: 监听组件根元素事件
- @click.capture: 事件捕获,内部元素触发的事件先在此处理,然后才交由内部元素进行处理
- @click.self: 只当在 event.target 是当前元素自身时触发处理函数,即事件不是从内部元素触发的
按键修饰符
- @keyup.enter: 监听回车
- @keyup.tab/delete/esc/space/up/down/left/right/13
数据传递
HTML 中的 attribute 名是大小写不敏感的,所以在通过属性传参时,不支持驼峰命名,但是在接收和使用时,是可以用驼峰的。
父传子 Prop
- type: String, Number, Boolean, Array, Object, Function, Symbol、Date
- 在传入非字符串的静态数据时,我们都需要用v-bind或者
:来告诉Vue,这是一个JavaScript表达式而不是一个字符串。 传入动态数据时,也是用b-bind。 - 如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的
v-bind。<blog-post v-bind="post"></blog-post>,其中post是一个对象。 - 注意那些 prop 会在一个组件实例创建之前进行验证,所以实例的 property (如
data、computed等) 在default或validator函数中是不可用的 - 父组件中直接调用子组件中的方法:
- this.$children[0].showMessage()
- 给组件设置ref="wel" => this.$refs.wel.showMessage()
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
子传父
- 子: this.$emit('toEvent', args)
- 父: @toEvent="prentEvent"
- 子组件中,通过
this.$parent, 获取父组件对象(Vue对象) - 子组件中,通过
this.$root,获取根组件对象(Vue对象)
插槽
让组件有扩展性
默认插槽
<!--cpn子组件-->
<template>
<div class="hello">
<button>这里是子组件默认内容</button>
<slot></slot>
</div>
</template>
<!--父组件-->
<Cpn>
父组件中子组件的内容
</Cpn>
具名插槽
v-slot只能添加在<template>上,具名插槽在书写的时候可以使用缩写,v-slot用#来代替- 一个不带
name的<slot>出口会带有隐含的名字“default”
<slot name="header"></slot>
<slot></slot>
作用域插槽
- 默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确
- 只要出现多个插槽,请始终为所有的插槽使用完整的基于
<template>的语法
<!-- 子组件 -->
<template>
<div class="container">
<header>
<slot name="header" :user="user">{{user.firstName}}</slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer" :counter=counter></slot>
</footer>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data(){
return {
counter: 0,
user: {
firstName: 'zhang',
lastName: 'san'
}
}
},
}
</script>
<template>
<div class="home">
<HelloWorld :msgA="msg" ref="aaa" >
<div>大家好我是父组件</div>
<template v-slot:header="scope">
{{scope.user.lastName}}
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template #footer="slotProps">
{{slotProps.counter}}
<p>Here's footer info</p>
</template>
</HelloWorld>
<button @click=showChildFun>点击触发子组件的方法</button>
</div>
</template>
知识点梳理
- 计算属性:有缓存,有变化才会重新计算
- methods:每次都会重新计算
- @click调用方法时,省略了(), 但是方法本身是需要一个参数的,会将event事件作为参数传到方法中。调用方法时,手动获取到event
- 需要手动传入event)
- 登录切换input的复用问题:给不同的input加上key属性
- v-if:不会存在DOM中,切换是删掉/生成,适合切换频率低的
- v-show:display:none,切换只是改变行内样式,适合切换频率高的
- 响应式数组方法:push、pop、shift、unshift、splice(删除、插入、替换元素)、sort、reverse
- 通过数组索引更改,不是响应式的。this.letters[0] = 'aaa',这样页面是不会渲染的。可以用Vue.set(this.letters, 0, 'aaa')来使页面进行渲染。
- js高阶函数
- filter
- map
- reduce
vue-router
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import User from '../views/User.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue') //懒加载
},
{
//动态路由
path: '/user/:id', //this.$route.params.is
component: User
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</div>
<router-view/>
</div>
</template>
- this.$router 访问路由器
- back
- forward
- go:在 history 记录中向前或者后退多少步
- push:向 history 栈添加一个新的记录
- replace:跟
router.push很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录
点击
<router-link>时,这个方法会在内部调用,所以说,点击<router-link :to="...">等同于调用router.push(...)
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
this.$router.push({ name: 'user', params: { id: '123' }})
this.$router.push({ path: `/user/12` })
this.$router.push(`/user/12` )
// 这里的 params 不生效 **如果提供了 `path`,`params` 会被忽略**
router.push({ path: '/user', params: { userId }}) // -> /user
- this.$route 访问当前路由
- fullPath: "/about"
- hash: ""
- matched: [{…}]
- meta: {}
- name: "About"
- params: {}
- path: "/about"
- query: {}
重定向
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
})
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
})
别名
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
})
内置组件
keep-alive
include- 字符串或正则表达式。只有名称匹配的组件会被缓存。exclude- 字符串或正则表达式。任何名称匹配的组件都不会被缓存。max- 数字。最多可以缓存多少组件实例。
vuex
- state一开始就被定义到store中的属性才有响应式,后面添加的属性不在监听范围,不是响应式
- Vue.set(state.info, 'address', 'xxx'), vue.delete(state.info, 'age')这样新增属性或删除属性,是响应式的
- 新版的VUE_CLI4,就算是直接新增的属性,也是响应式的了, delete state.info.address也是响应式的
- Mutation需遵守 Vue 的响应规则,而且 必须是同步函数
- 可以使用常量替代 Mutation 事件类型
- 提交Mutation:this.$store.commit('xxx')
- Action 提交的是 mutation,而不是直接变更状态
- Action 可以包含任意异步操作
- Action 通过
store.dispatch方法触发
//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
counter: 100
},
mutations: {
increment(state, payload) {
console.log(payload)
state.counter += 5
}
},
actions: {
increment(context) {
context.commit('increment')
},
async actionA({ commit }) {
commit('gotData', await getData())
},
async actionB({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
},
modules: {
},
getters: {
poweCounter(state) {
return state.counter * state.counter
}
}
})
//官方建议:state定义在外,但可以不抽离
//mutations,actions,getters,modules都抽离出去。例如modules抽离成一个文件夹modules,里面有moduleA.js
//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
<template>
<div class="home">
<h1>{{counter}}</h1>
<h1>{{$store.state}}</h1>
<h1>{{$store.getters.poweCounter}}</h1>
<h1>{{poweCounter}}</h1>
<button @click=add>数字加1</button>
</div>
</template>
<script>
// @ is an alias to /src
import {mapState, mapGetters, mapMutations, mapActions} from 'vuex'
export default {
name: 'Home',
data() {
return {
msg: 'success',
};
},
computed: {
...mapState(['counter']),
...mapGetters(['poweCounter'])
},
methods: {
add(){
this.increment(5)
/**
*
* this.$store.commit('increment', 5);
this.$store.commit({
type: 'increment',
counter: 5
})
*/
},
...mapMutations(['increment']),
...mapActions(['increment'])
}
}
</script>
vite
- npm init vite-app vite_study
- cd vite_study
- npm install
- nppm run dev
vue3
- 组件的html模板可以没有跟标签
- setup 组合API
- 初始化执行一次(在beforeCreate之前,只执行一次)
- setup在执行时,当前组件还没有创建出来,组件的实例对象this不能用(为undefined),也就不能用this调用data/computed/methods/methods...
- 组合API的入口
- 返回一个对象,属性和data合并,方法和methods合并
- setup尽量不要与data和method混用
- setup不能是async函数
- setup参数(props, context)
- props: 是一个对象,父组件传给子组件的数据,在子组件中用props接收的所有属性
- context
- attrs:包含没有在props配置中声明的属性的对象(当前组件上的所有属性)
- emit方法: this.$emit
- slots: this.$slots
- ref
- 定义一个响应式的数据
- 返回一个Ref对象,有一个value属性
- 操作数据通过.value
- 在组件中不需要通过.value
- ref中存放{},会通过reactive转为Proxy对象
- reactive
- 定义多个响应式数据,必须是一个对象,才可响应
- 返回一个Proxy代理对象
//obj为目标对象,直接更新目标对象的值,不是响应式的。添加属性或删除属性,界面都不会更新
//user为代理对象。添加属性界面会更新,属性也会添加到obj中;删除属性,界面更新,obj中对象的属性也会删除
const user = reactive(obj)