VUE

131 阅读5分钟

基础

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 会忽略所有表单元素的 valuecheckedselected attribute 的初始值而总是将 Vue 实例的数据作为数据来源

v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;
  • checkbox 和 radio 使用 checked property 和 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 (如 datacomputed 等) 在 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时:@click=fun(123,event时: @click=fun(123, 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)