vuejs3 备忘

上一篇记录了vue2的一些备忘,这一篇记录vue3和2的不同的地方。

2020.09.18发布vuejs 3.0.0,代号 one piece。官网:https://v3.cn.vuejs.org/

官方针对版本3的说明:https://github.com/vuejs/core/releases/tag/v3.0.0

一、更新内容

  1. 性能提升
  2. 源码升级
    1. tree-shaking
  3. 更好支持TS
  4. 新特性
    1. composition API

二、安装

配置下代理,备忘

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. vue-cli

    4.5.0版本以上

    npm install -g @vue/cli # 安装/升级
       
    vue create xxx
    cd xxx
    npm run serve
    
  2. vite

    想代替 webpack,由vue团队打造的打包工具。

    HMR

    npm init vite@latest
    npm init vite-app my-vue-app
    npm init vite@latest my-vue-app -- --template vue
       
    cd my-vue-app
    npm install 
    npm run dev
    
  3. 代码结构对比

    Vue2:

    image-20220516105708052

    vue3:

    image-20220516115404231

    createApp 工厂函数,返回的app对象比vm更轻

    image-20220516115831732

三、入门

  1. setup方法

    image-20220516135431724

  2. reactive、ref、watch

    组件中的数据、方法都要配置到setup中(不需要往props和data、func里塞了)。

    创建独立的响应式值作为 refs

    watch只能监听ref的基本类型,要监听对象就用reactive。watchEffect也不错,但是感觉会影响性能?

    ref类似vue2中get和set来实现响应式。

    reactive是vue3中新增的proxy实例对象来实现响应式,搭配toRefs实现。

    <template>
      <div>
        <span>8</span>
        <button @click="count ++">Increment count</button>
        <button @click="nested.count.value ++">Nested Increment count</button>
      </div>
    </template>
       
    <script>
      import { ref } from 'vue'
      export default {
        setup() {
          const count = ref(0)
          return {
            count,
       
            nested: {
              count
            }
          }
        }
      }
    </script>
    

    image-20220516121609701

    ref 作为响应式对象的 property 被访问或更改时,为使其行为类似于普通 property,它会自动解包内部值.

    使用解构的两个 property 的响应性都会丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联。

    import { reactive, toRefs } from 'vue'
       
    const book = reactive({
      author: 'Vue Team',
      year: '2020',
      title: 'Vue 3 Guide',
      description: 'You are reading this book right now ;)',
      price: 'free'
    })
       
    let { author, title } = toRefs(book)
       
    title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref
    console.log(book.title) // 'Vue 3 Detailed Guide'
    

    image-20220516165948633

    image-20220516122757072

  3. compute

    vue3里可以按照vue2的compute使用。不过我们现在搞到setup里了。

    只读computed,接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。

    const count = ref(1)
    const plusOne = computed(() => count.value + 1)
       
    console.log(plusOne.value) // 2
    

    读写computed

    const count = ref(1)
    const plusOne = computed({
      get: () => count.value + 1,
      set: val => {
        count.value = val - 1
      }
    })
       
    plusOne.value = 1
    console.log(count.value) // 0
    
  4. 生命周期

    可以通过直接导入 onX 函数来注册生命周期钩子:

    import { onMounted, onUpdated, onUnmounted } from 'vue'
       
    const MyComponent = {
      setup() {
        onMounted(() => {
          console.log('mounted!')
        })
        onUpdated(() => {
          console.log('updated!')
        })
        onUnmounted(() => {
          console.log('unmounted!')
        })
      }
    }
    
  5. 自定义hook

    一般放在hook文件夹里,感觉很不错啊,这种引用方式。

    image-20220516163730910

    image-20220516163539285

    image-20220516163933726

    image-20220516163849888

  6. 其它composition api

    • readonly
    • shallowReadonly
    • toRaw
    • markRaw
    • customRef,注意防抖~
    • provide和inject,祖孙组件通信。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。
    • isRef,isReacrive,isReadonly,isProxy
  7. composition api的优势

    就我个人感觉,更像后端的一个class,定义变量和方法都在setup里,改起来舒服。

    配合hook使用就起飞🛫️。

  8. fragment

  9. Teleport

    用来UI定位的,舒服舒服,直接把内容传送到任意一个元素去。

  10. suspense

四、原理精进

  1. vue3如何做到交互性?

    参考vue2的内容,原理是一样的,只不过换了一个方法实现——ES6的Proxy Reflect对象。

    它们的区别仅在于:

    • Proxy返回的是bool值,defineProperty返回的是原始对象。
    • ES6语法更严格。

    通过ES6的 new Proxy(),可以创建一个代理用来代替另一个对象,实现基本操作的拦截和自定义。这个代理与目标对象表面上可以被当作同一个对象来看待。

    image-20220530180855430

    image-20220530181123151


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


使用命令行保存网站的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 跳过证书验证。