Nuxtjs3 备忘

作为一个本职工作是基础设施开发的后端人员,我将js比作linux系统,将vue比作docker/containerd,而nuxt就相当于kubernetes了。当然这个类比并不是非常准确。前端比容器大战要柔和的多,感觉大家都在底层上重复建设。对比容器技术,swarm/k8s/mesos也就5年左右就分出了胜负。前端目前主流还是react/vue/Angular这三家,基于这三家之上又有各自的服务端框架(更底层typescript倒是已经统一了),导致前端学习门槛较高,框架概念、设计模式其实也走了一遍后端框架的老路,很多概念后端开发可以平移过来。我比较相信Taylor Otwell(Laravel的创造者),他说vue好,所以我就跟着他一起用了。

前两篇记录了点vue2vue3的笔记,这一篇记录下nuxtjs3,这是官网,2022 年 4 月 20 日正式发布 nuxt3,代号为“Mount Hope”

Nuxt3是基于 Vue3 发布的 SSR 框架:

  • SPA应用:也就是单页应用,这些多是在客户端的应用,不能进行SEO优化(搜索引擎优化)。
  • SSR应用:在服务端进行渲染,渲染完成后返回给客户端,每个页面有独立的URL,对SEO友好,对应企业网站、商品展示 、博客这类型的展示型网站。

这是官网显示的一些优势说明:

  • 更轻量:以现代浏览器为基础的情况下,服务器部署和客户端产物最多减小75倍。
  • 更快:用动态服务端代码来优化冷启动。
  • Hybird:增量动态生成和其他高级模式现在都成为可能。
  • Suspense: 导航前后可在任何组件中获取数据。
  • Composition API : 使用Composition API 和 Nuxt3的composables 实现真正的可复用性。
  • Nuxt CLI : 权限的零依赖体验,助你轻松搭建项目和集成模块。
  • Nuxt Devtools :专属调试工具,更多的信息和快速修复,在浏览器中高效工作。
  • Nuxt Kit :全新的基于 TypeScript 和跨版本兼容的模块开发。
  • Webpack5 : 更快的构建速度和更小的构建包,并且零配置。
  • Vite:用Vite作为你的打包器,体验轻量级的快速HMR。
  • Vue3 : 完全支持Vue3语法,这一点特别关键。
  • TypeScript:由原生TypeScript和ESM构成,没有额外配置步骤。

安装

npx是npm从5.2版开始增加的命令

官方链接

npx nuxi init nuxt3-test  # 快速创建

image-20220517113727498

npm install
npm run dev

image-20220517113912229

生成文件如下:

image-20220517114211393

这里要重点提的是 nuxt.config.js 文件,所有配置都在里面。

一些常用的目录如下:

- pages               // 页面目录,Nuxt.js 框架读取该目录下所有的 .vue 文件并自动生成对应的路由配置。
- components          // 组件目录
- assets              // 静态资源目录
- public              // 不需要 vue 处理的静态资源,此类文件不会被 Nuxt.js 调用 Webpack 进行构建编译处理。 服务器启动的时候,该目录下的文件会映射至应用的根路径 / 下。
- layouts             // 项目布局目录
- common              // 存储js文件
- plugins             // nuxt扩展的js文件
- store               // vuex的配置文件,Nuxt.js 框架集成了 Vuex 状态树 的相关功能配置,在 store 目录下创建一个 index.js 文件可激活这些配置。

配置参考链接:

删除根目录下的app.vue,然后pages目录下新建index.vue文件,就会自动关联vue-router,支持动态路由。

因为我是容器化环境开发的,所以改了一下运行端口到80端口,访问方便一些。如果发现配置不生效,可以尝试重启一卡容器(多个容器在同一个目录下启动npm run时感觉会冲突,就遇到有些配置不生效了)。

image-20220519113053640

一些官网备忘

setup函数中,可以useHead使用与元标记对应的键的元属性对象进行调用:title, titleTemplate, base, script, style, metaand link, 以及htmlAttrsand bodyAttrs。还有两个速记属性charsetviewport

data-fetching

Nuxt 提供useFetchuseLazyFetchuseAsyncDatauseLazyAsyncData处理应用程序中的数据获取。

state-management

useState是一个对 SSR 友好的ref 替代品。它的值将在服务器端渲染后(在客户端水化期间)被保留,并使用唯一键在所有组件之间共享。

server-routes

Nuxt 会自动扫描 、 和 目录中的文件~/server/api~/server/routes~/server/middleware注册具有 HMR 支持的 API 和服务器处理程序。

每个文件都应该导出一个用defineEventHandler().

路由

Nuxt使用了自动加载路由的方案,他会根据你项目的目录来自动创建router对象;

一个简单的示例,pages下的目录示例及其对应的路由如下所示:

test:
  a.vue     -- 对应路径:/test/a
  index.vue -- 对应路由:/test
index:
  _id:
    a.vue      -- 对应路由:/{id}/a
    index.vue  -- 对应路由:/{id}
  index.vue    -- 对应路由:/,会被包含到上一级的index.vue的<nuxt-child>中
index.vue      -- 对应路由:/,其中如果包含有nuxt-child元素,那么会将index目录下的index.vue加载到nuxt-child元素位置  
  • 在 Nuxt.js 里面定义带参数的动态路由,需要创建对应的以下划线作为前缀的 Vue 文件 或 目录。
  • 如果你想将_id设置为必选的路由,需要在 users/_id 目录内创建一个 index.vue 文件。

路由参数校验

Nuxt.js 可以让你在动态路由组件中定义参数校验方法。

举个例子: pages/users/_id.vue

export default {
  validate ({ params }) {
    // 必须是number类型
    return /^\d+$/.test(params.id)
  }
}

如果校验方法返回的值不为 true或Promise中resolve 解析为false或抛出Error , Nuxt.js 将自动加载显示 404 错误页面或 500 错误页面。

未知嵌套深度的动态嵌套路由

如果您不知道URL结构的深度,您可以使用_.vue动态匹配嵌套路径。这将处理与更具体请求不匹配的情况。

文件结构:

pages/
--| people/
-----| _id.vue
-----| index.vue
--| _.vue
--| index.vue

知识点

一些代码只是为了做代码的结构备忘,不代表逻辑正确。

前端的语法糖太多了,我觉得这些语法糖是前端晦涩的一大原因。

不过,“无他,但手熟尔”,在前端也是一样。

  1. 命令行nuxi

    npx nuxi init nuxt3-test  # 快速创建
    
  2. layout 布局 html(静态)

    layout文件夹
    page文件夹
    
  3. 引入 css

    <style scope>
    .abc {}
    </style>
    
  4. 应用状态state

    <script setup>
      const options = reactive({
          xxx: "xxx"
      });
    </script>
       
    <template>
      <button class="abc" :class="options.xxx === 'xxx' && 'option-active'">abc</button>
    </template>
    
  5. a标签替换为 NuxtLink 标签

    <NuxtLink to="/">Home</NuxtLink>
    
  6. 简单的typescript

    我觉得typescript的语法有点像golang。 看着也挺舒服的~

    先定义个interface类型,一些选项/常量用enum定义,再const一个实例。

    <script setup lang="ts">
      interface OptionState {
        xxx: string,
        yyy: string
      }
         
      enum Xxx {
        AAA = "aaa",
        BBB = "bbb"
      }
       
      const obj := OptionState{
          xxx: Xxx.AAA,
          yyy: Xxx.BBB
      }
         
      const options = reactive<OptionState>({
          xxx: Xxx.AAA,
          yyy: Xxx.BBB
      });
    </script>
    
  7. import 其他文件里定义的内容

    import {OptionState} from "@/data"  // 其他文件要 export特定的定义
    

    教程有的为了简化内容,直接准备一个json文件,然后import数据进来:

    <script setup lang="ts">
    import {xxx} from "@/data.json"
         
    const rest = {
      first: [...xxx].splice(0,25),
      second: [...xxx].spice(25,25),
    };
    </script>
         
    <template>
      <Row v-for="r in rest.first" :key="r.id" />
      <Row v-for="r in rest.second" :key="r.id" />
    </template>
         
    
  8. 更新state

    <script setup lang="ts">
      const computeSelectedNames = () => {
        const filterNames = names
        	.filter((name) => name.xxx === options.xxx )
        	.filter((name) => name.yyy === options.yyy )
        	.filter((name) => {
            return ...
          })
             
          selectdNames.value = filterdNames.map((name) => name.xxx )
           
      };
      const selectdNames = ref<string[]>{[]}
    </script>
       
    <template>
      <button class="abc" @click="computeSelectName">Find</button>
      {{ selectedNames }}
    </template>
    
  9. 注意拆分component,以及通过props在父子组件中传值

    <script setup lang="ts">
      interface OptionProps{
        option: {
            id: string,
          	title: string,
            catagory: string,
            index: number
        };
      }
         
      const props = defindProps<OptionProps>()
    </script>
    

    在父组件中传入:

       
    
   
   
   
   大写开头的标签就是component,有一些是 `nuxt` 保留的,用来作为头部header内容的,类似于 `metadata`

   

10. 通过事件触发父子组件传值。

    ```vue
    // 父:
    <script setup lang="ts">
      const removeName = (index: number) => {
        const filteredNames = [...selectdNames.value]
        filterNames.splice(index,1)
        selectedName.value = filterNames 
      }
    </script>
    
    <template>
      <CardName v-for="(name, index) in selectedNames" :key="name" :name="name" @remove="()=> removeName(index)" :index="index" />
    </template>
    
    // 子CardName:
    <script setup lang="ts">
      interface NameProps{
        name: string;
        index: number
      }
      
      const props = defindProps<NameProps>();
      const emit = defindEmits(["remove"])
      
      const removeName = () => {
        emit("remove", props.index)
      }
    </script>
    
    <template>
      <p @click="removeName">xxx </p>
    </template>

  1. 常用的循环语句

    v-for
    
  2. Pages, 基于文件的路由route

    <script setup lang="ts">
    // import {}  from "#app"; // 用来让编辑器不报错的,其实没什么用。
    import xxx from "@/data.json"
          
    const route = useRoute();
    const name = route.params.name;
          
    const x = xxx.find(r => r.name === name)
    </script>
        
    <template>
    {{ name }}
    </template>
    
  3. 404页面

  4. Layouts

    创建 layouts 文件夹,并创建default.vue文件。

    在page中声明使用特定的layout

    <template>
      <div>
        <NuxtLayout name="custom">
          <Xxx />
        </NuxtLayout>
      </div>
    </template>
    

    在layout中插入slot(假设layout名error)

    <template>
      <div>
        <slot name="xxx" />
        <div>...</div>
      </div>
    </template>
    

    在page中:

    <template>
      <div>
        <NuxtLayout name="error">
          <template #xxx>
            <h1></h1>
          </template>
        </NuxtLayout>
        <div>...</div>
      </div>
    </template>
    
  5. metadata

    image-20220523155833406

    为了方便控制,也可以写在script里,

    image-20220523160139589

    也可以使用 outside stylesheets 方式:

    在nuxt.config.js里配置。

    export default defineNuxtConfig({
        charset: "utf-8",
        viewport: "width=device-width, initial-scale=1, maximum-scale=1",
        meta: {
            link: [
                { rel: "stylesheet", href: "/assets/css/style.css" },
                { rel: "stylesheet", href: "/assets/css/font-awesome.min.css" },
            ],
            script: [
                { src: '/assets/js/jquery.min.js' },
              ],
        }
    })
    
  6. 网页数据状态设计

  7. composable,useState

    用来保存状态。不能用 ref 做跟踪,因为这里没有render,会造成内存泄漏。

    image-20220523162132417

    image-20220523162252693

  8. Global State

    image-20220523162655826

    image-20220523162728965

  9. 生成静态网站

    目前还是测试阶段,官方不建议用于生产。

参考资料


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