一些简单的 Vue.js 笔记

前端不是我的主业,不过太久不用,捡起来每次都觉得头疼,还是稍微做点笔记吧。

首先是官方文档:https://cn.vuejs.org/v2/guide/,虽然目前到3版本了,还是先了解下v2版本的知识,并不复杂。

一、基本方法

基本就是官网内容。

  1. 格式

     <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
        
     <div id="app">
          
     </div>
        
     var app = new Vue({
       el: '#app',
       data: {
         message: 'Hello Vue!'
       }
     })
    

    image-20220510110633818

  2. 数据绑定、css绑定、事件绑定和语法糖

    • v-bind:value=<expression> 单项绑定,语法糖 :value=<expression>
    • v-model:value=<expression> 双向绑定(只用表单),语法糖 v-model=`
    • v-bind:class=xxx ,语法糖 :class=<str>
    • v-on:click.stop=<expression> 事件绑定,语法糖 @click.stop=<expression>
      • .stop
      • .prevent
  3. 计算属性

    func 里this指针指向的是vm

    computed不吃性能,尽量用它

     var vm = new Vue({
       el: '#example',
       data: {
         message: 'Hello'
       },
          
       computed: {
         // 计算属性的 getter
         reversedMessage: function () {
           // `this` 指向 vm 实例
           return this.message.split('').reverse().join('')
         }
       },
          
       watch: {
         // 如果 `question` 发生改变,这个函数就会运行
         question: function (newQuestion, oldQuestion) {
           this.answer = 'Waiting for you to stop typing...'
           this.debouncedGetAnswer()
         }
       },
          
       created: function () {
         this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
       },
          
       methods: {
         getAnswer: function () {
         }
      },
         
      mounted(){
         
      }
     })
    

    由vue管理的method,要以普通方式定义。

    否则应该用箭头函数,例如:

    getAnswer: function (()=>{})
    

    此时它的this为window。

    另外,指令directive相关的 this 都是 window

  4. 循环语句和条件语句

    循环:

     <ul id="example-1">
       <li v-for="item in items" :key="item.message">
            
       </li>
     </ul>
        
     <ul id="example-2">
       <li v-for="(item, index) in items">
          -  - 
       </li>
     </ul>
        
     <div v-for="(value, name, index) in object">
       . : 
     </div>
    

    条件:

     <div v-if="type === 'A'"></div>
     <div v-else-if="type === 'B'"></div>
     <div v-else-if="type === 'C'"></div>
     <div v-else></div>
    
  5. Vue 无法监控数组内容的改变

    https://cn.vuejs.org/v2/guide/list.html#数组更新检测

    对象因为有setter和getter,修改model可以监控到,view也会变动。

    但直接操作数组不奏效,因为数组没有这两个方法,引发view变动则需要调用如下方法:

    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()
  6. 过滤器,用于一些常见的文本格式化

  7. 生命周期

    一旦 data 数据变化就会重新解析模版。

    • create

    • mounted做初始化,把初始DOM放入页面时,仅调用一次。

    • update
    • destroy
  8. 组件

    • 创建

      <template>
      </template>
            
      <script>
      import ComponentA from './ComponentA'
      import ComponentC from './ComponentC'
            
      export default {
        name: ''
        data(){
        },
        components: {
          ComponentA,
          ComponentC
        },
        // ...
      }
      </script>
            
      <style>
      </style>
            
      或者
            
      const vc = Vue.extend({ })
      
    • 注册

      new Vue({
        el: '#app',
        components: {
          'component-a': ComponentA,
          'component-b': ComponentB
        }
      })
      
    • 使用

      <div id="app">
        <component-a></component-a>
        <component-b></component-b>
        <component-c></component-c>
      </div> 
      
  9. 原型对象prototype

    实例的隐式原型属性 __prototype__ 永远指向类(父类)的原型对象

    image-20220508152632174

二、脚手架

国内代理备忘

npm init

npm config set proxy http://10.101.1.1:8118
npm config set https-proxy http://10.101.1.1:8118

npm i -S vue
  1. 命令备忘

    npm install -g @vue/cli
    vue --version
    vue create hello-world
    

    image-20220516103015750

    修改配置,在vue.config.js中添加(参考配置

    module.exports = {
        devServer: {
          host: '0.0.0.0',
          port: 80
        }
    }
    

    image-20220516105420100

    image-20220516105517784

    文件预览

    image-20220516105708052

  2. render

    image-20220509105413880

    vue.runtime.xxx.js 是运行版的vue,没有模版解析器,不能使用template配置项。

  3. vue ref,vue用于获取元素,用来代替以前通过id获取元素的办法。

  4. 数据传递prop

    相当于后端中一个类class实例的只读属性,。

    props: {
      title: String,
      likes: Number,
      isPublished: Boolean,
      commentIds: Array,
      author: Object,
      callback: Function,
      contactsPromise: Promise // or any other constructor
    }
    
  5. 混入 (mixin)

    有点类似于后端的基类和子类,mixin属于基类的方法,多个子类都可以使用基类的方法。

    var vm = new Vue({
      mixins: [mixin],
      methods: {
         ...
      }
    })
    

    可以全局混入,也可以局部混入。

    Vue.mixin({
      xxx: xxx
    })
    
  6. 插件

    // 调用 `MyPlugin.install(Vue)`
    Vue.use(MyPlugin)
    
  7. scope

    解决style类名相同,引入顺序不同,导致冲突的问题。

    <style scoped>
    </style>
    

    不要在App里使用scoped

三、开发步骤(入门)

官网的一些内容和后端思维是互通的,比如状态提升之类的,不要害怕,见人说人话,见鬼说鬼话。

  1. 拆分静态组件,按功能点拆分

  2. 静态组件,不考虑交互

    1. 把html整个塞到app里
    2. 把一个组件的html剪切后立马写占位符,然后塞到组件里。
    3. 把样式也拆了,塞到组件,style加个scoped。
  3. 数据交互设计

    类似于后端,设计数据库和model。

    组件通信的几种方式:

    1. 通过 props 存储数据,数据存在App上。

      1. 父组件定义data,通过props传给子组件(子要在props里接收)
      2. 父组件定义method,通过props传给另一个子组件
      3. 注意子组件的v-model不要改props

      相当于内存存变量。

    2. 自定义事件

      子组件给父组件传数据用。

      在父组件中给子组件绑定自定义事件,事件回调在父组件。(父给子传函数,要求子返回数据)

    3. 全局事件总线

      定义一个所有组件的子组件vc(也可以直接用vm)挂在prototype上,然后使用事件触发。相当于后端的redis。多读单写

    4. 消息订阅&发布

      全局事件总线和消息订阅其实差不多的效果。多读单写

    5. 浏览器localstorage,相当于后端的数据库DB。

    6. vuex

      多读多写

  4. 动态组件

    不同功能的增删查改+统计信息变动

    1. 增删,事件event。

四、高级

  1. 自定义事件

    this.$emit('myEvent')
       
    <my-component v-on:my-event="doSomething"></my-component>
       
    this.$off('myEvent')
    this.$off(['myEvent','xxx'])
    this.$off()
    

    也适用于子给父组件传输数据。

    image-20220509163051261

    自定义事件由父组件运行,props方式由子组件运行。

    还可以通过ref绑定自定义事件。

    image-20220509163610694

  2. 全局事件总线

    定义一个所有组件的子组件vc,然后赋值给prototype。

    image-20220510115509466

    改良版:

    把vm当成所有组件的子组件,这样子?真是奇妙。

    image-20220510120130524

    image-20220511165656535

  3. 消息订阅&发布

    $ npm -i pubsub.js
       
    import pubsub from 'pubsub-js'
       
       
    pubsub.publish('hello',666)
    pubsub.subscribe('hello',function(msgName,data){ })
    pubsub.subscribe('hello',(msgName,data)=>{ })
    
  4. nextTick

  5. 动画/过渡

    其实就是封装了一个 transition 标签。

    <transition name"xxx" appear></transition>
       
    .xxx-enter-active{
    animation: kelu 1s linear;
    }
    .xxx-leave-active{}
       
    @keyframs kelu {
    	from{
    		transform: xxx
    	}
    	to{}
    }
    

    image-20220510163037015

    第三方动画:Animate.css:http://animate.style

  6. 跨域

    axios请求,

    cors(后端解决),代理服务器(前端解决)

    module.exports = {
      devServer: {
        proxy: 'http://localhost:4000'
      }
    }
       
    module.exports = {
      devServer: {
        proxy: {
          '/api': {
            target: '<url>',
            ws: true,
            changeOrigin: true
          },
          '/foo': {
            target: '<other_url>'
          }
        }
      }
    }
       
    # https://github.com/chimurai/http-proxy-middleware#proxycontext-config
    

    image-20220511144557437

    image-20220511145140405

  7. 插槽

    <div class="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
       
    <base-layout>
      <template v-slot:header>
        <h1>Here might be a page title</h1>
      </template>
       
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>
       
      <template v-slot:footer>
        <p>Here's some contact info</p>
      </template>
    </base-layout>
    
  8. vuex

    npm i vuex
       
    import Vuex from 'vuex'
    Vue.use(Vuex)
    

    vuex

    import { createApp } from 'vue'
    import { createStore } from 'vuex'
       
    // 创建一个新的 store 实例
    const store = createStore({
      state () {
        return {
          count: 0
        }
      },
      mutations: {
        increment (state) {
          state.count++
        }
      }
    })
       
    const app = createApp({ /* 根组件 */ })
       
    // 将 store 实例作为插件安装
    app.use(store)
    

    dispatch -> commit ->

    vc -> Action(ctx,value) -> mutation(state,value)

    dispatch可以跳过,直接commit就行了。

    mapState

    // 在单独构建的版本中辅助函数为 Vuex.mapState
    import { mapState } from 'vuex'
       
    export default {
      // ...
      computed: mapState({
        // 箭头函数可使代码更简练
        count: state => state.count,
       
        // 传字符串参数 'count' 等同于 `state => state.count`
        countAlias: 'count',
       
        // 为了能够使用 `this` 获取局部状态,必须使用常规函数
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
      })
    }
    

    image-20220512135239999

  9. 路由

    npm i vue-router@3
       
    import Vue from 'vue'
    import VueRouter from 'vue-router'
       
    Vue.use(VueRouter)
    
    • 一般组件

    • 路由组件

    <router-view></router-view>
    # 一级路由
    
    • 多级路由
    const routes = [
      {
        path: '/user/:id',
        component: User,
        children: [
          {
            path: 'profile',
            component: UserProfile,
          },
          {
            path: 'posts',
            component: UserPosts,
          },
        ],
      },
    ]
    
    • 路由传参

      • query参数

      • params参数

      • props(对象/函数/布尔)

        image-20220512160434852

        解构赋值,连续解构赋值:

        image-20220512160650919

        image-20220512160544087

    • 路由命名

    • 路由历史记录

      声明式 编程式
      <router-link :to="..." replace> router.replace(...)
      <router-link :to="..."> router.push(...)

      路由缓存:

        <keep-alive :include="['xx','yy']">
          <router-view></router-view>
        </keep-alive>
      

      路由组件新增了两个生命周期

      • activated
      • deactivated
    • 路由权限

      数据存在meta里,

      前置路由守卫,主要做鉴权

      // GOOD
      router.beforeEach((to, from, next) => {
        if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' })
        else next()
      })
      

      后置路由守卫作用,一般就是修改页面内容之类的。

五、原理精进

  1. 为什么 vuejs 可以做到reactive交互式

    主要是通过 Object.defineProperty() 方法实现,参考https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty,直接在一个对象上定义一个新属性。

    自定义一个setter(所以必须用object类型)

    image-20220530172233546

    image-20220530172617448

六、其他

  1. 字面量赋值

    image-20220511154311040


苏联为什么会解体? vuejs3 备忘