一些简单的 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

二、脚手架

  1. 命令备忘

    npm install -g @vue/cli
    vue --version
    vue create hello-world
    
  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. 字面量赋值

    image-20220511154311040


使用命令行保存网站的ssl证书

使用命令行,发现 curl 某个请求时报错:

ERROR: The certificate of ‘xxx’ is not trusted.
ERROR: The certificate of ‘xxx’ has expired.

这就奇怪了,只在某个服务器上才有问题,其他服务器又没有问题。遂把证书抓下来看看啥情况。

openssl s_client -connect {HOSTNAME}:{PORT} -showcerts

# 例如:
openssl s_client -connect blog.kelu.org:443 -showcerts

即可下载证书。

另外如果 curl 可以使用 -k 跳过证书验证。


kubernetes dashboard 安装并免密访问

官方的文档有点心累了。一个简单的需求硬是扣扣搜搜分成多个步骤,不一次性给出来。

索性记录现成的方案。

相关镜像如果在国内无法下载,可以在这里参考镜像名,用国外服务器 docker savedocker load加载到本地。

# Copyright 2017 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: v1
kind: Namespace
metadata:
  name: kubernetes-dashboard

---

apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard

---
kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-external
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 9090
      targetPort: 9090
  selector:
    k8s-app: kubernetes-dashboard

---
kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-external-test
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 9090
      targetPort: 9090
      nodePort: 30000
  type: NodePort
  selector:
    k8s-app: kubernetes-dashboard

---
apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-certs
  namespace: kubernetes-dashboard
type: Opaque

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-csrf
  namespace: kubernetes-dashboard
type: Opaque
data:
  csrf: ""

---

apiVersion: v1
kind: Secret
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-key-holder
  namespace: kubernetes-dashboard
type: Opaque

---

kind: ConfigMap
apiVersion: v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard-settings
  namespace: kubernetes-dashboard

---

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard-v2
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: kubernetes-dashboard
  name: kubernetes-dashboard
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: kubernetes-dashboard
  template:
    metadata:
      labels:
        k8s-app: kubernetes-dashboard
    spec:
      containers:
        - name: kubernetes-dashboard
          image: k8s.gcr.io/dashboard:v2.0.0-rc5
          ports:
            - containerPort: 9090
              protocol: TCP
          args:
            - --namespace=kubernetes-dashboard
            # Uncomment the following line to manually specify Kubernetes API server Host
            # If not specified, Dashboard will attempt to auto discover the API server and connect
            # to it. Uncomment only if the default does not work.
            # - --apiserver-host=http://my-address:port
          volumeMounts:
            - name: kubernetes-dashboard-certs
              mountPath: /certs
              # Create on-disk volume to store exec logs
            - mountPath: /tmp
              name: tmp-volume
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /
              port: 9090
            initialDelaySeconds: 30
            timeoutSeconds: 30
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      volumes:
        - name: kubernetes-dashboard-certs
          secret:
            secretName: kubernetes-dashboard-certs
        - name: tmp-volume
          emptyDir: {}
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "beta.kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule

---

kind: Service
apiVersion: v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  ports:
    - port: 8000
      targetPort: 8000
  selector:
    k8s-app: dashboard-metrics-scraper

---

kind: Deployment
apiVersion: apps/v1
metadata:
  labels:
    k8s-app: dashboard-metrics-scraper
  name: dashboard-metrics-scraper
  namespace: kubernetes-dashboard
spec:
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      k8s-app: dashboard-metrics-scraper
  template:
    metadata:
      labels:
        k8s-app: dashboard-metrics-scraper
      annotations:
        seccomp.security.alpha.kubernetes.io/pod: 'runtime/default'
    spec:
      containers:
        - name: dashboard-metrics-scraper
          image: k8s.gcr.io/metrics-scraper:v1.0.3
          ports:
            - containerPort: 8000
              protocol: TCP
          livenessProbe:
            httpGet:
              scheme: HTTP
              path: /
              port: 8000
            initialDelaySeconds: 30
            timeoutSeconds: 30
          volumeMounts:
          - mountPath: /tmp
            name: tmp-volume
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            runAsUser: 1001
            runAsGroup: 2001
      serviceAccountName: kubernetes-dashboard
      nodeSelector:
        "beta.kubernetes.io/os": linux
      # Comment the following tolerations if Dashboard must not be deployed on master
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      volumes:
        - name: tmp-volume
          emptyDir: {}