Vue2

662 阅读8分钟

vue2

vue是用于构建用户界面的渐进式框架。vue的核心库只关注视图层。

安装脚手架

需要先安装node

安装:npm i -g @vue/cli
查看版本:vue -V
创建项目:vue create projectName

vue的优点?

  • 渐进式(通俗的理解就是想用什么就用什么,不强制要求,比如vuex,可用可不用)js框架
  • 数据与视图分开
  • 单页面路由
  • 虚拟DOM
  • 响应式
  • 组件化

vue的缺点?

不利于seo(搜索引擎) 不支持IE8及以下的浏览器(因为vue使用的是ES5中的Object.defineProperty()方法,IE8不支持此方法)

首屏加载时间长(解决办法:首页加载一个loading动画,性能优化)

mvvm的理解?

Model-View-ViewModel:数据模型-视图-视图模型

三者与vue的对应:

  • Model对应Data
  • View对应template
  • ViewModel对应new vue()

三者的关系:

  • Model可以通过数据绑定的方式影响View
  • View可以通过事件绑定的方式影响Model
  • ViewModel是把View和Model连接起来的连接器

mvc

model-view-controller

  • 用户操作view去改变数据
  • view通过controller去改变model
  • model改变后通知view去渲染页面

mvvm、mvc

  • mvvm:双向的通信
  • mvc:单向的通信
  • vm不是替代了controller,而是vm抽离里controller中的数据渲染,其他业务逻辑还是在controller里

为什么只对对象劫持,而要对数组重写?

        因为对象最多几十个属性,拦截起来数量不多,但是数组可能会有成百上千项,拦截起来非常消耗性能,所以重写数组原型上的方法,是比较节省性能的方案。

对象添加、删除属性,无法更新视图?

原因:Object.defineProperty没有对对象新属性进行属性劫持

  • 在组件中使用this.set(obj,key,value)this.set(obj, key, value) this.delete(obj, key)
  • 数组使用this.set(arr,index,value)this.set(arr, index, value) this.delete(arr, index)

虚拟DOM

        虚拟DOM本质是js对象,用js对象模拟DOM树,通过diff算法比较新旧DOM树上的差异,把差异应用到真实DOM树上。 优点:效率高、节约性能

data为什么必须是一个函数?

        因为一个组件可能会被复用,为了避免数据污染,所以data是一个函数并且返回一个对象

data() {
    return {}
}

配置环境变量和模式

// 和src同级目录
.env                在所有的环境中被载入
.env.local          在所有的环境中被载入,但会被 git 忽略
.env.[mode]         只在指定的模式中被载入
.env.[mode].local   只在指定的模式中被载入,但会被 git 忽略

// 比如常用
.env.development
.env.production
.env.test // 必写NODE_ENV,不然报错

// 默认有两项
BASE_URL"/" // 修改不成功
NODE_ENV"development" // 修改成功
VUE_APP_*: // 自定义的变量

修饰符和修饰键

自定义事件修饰符:

.sync 主要用于子组件直接改变父组件的值,目前常用两种方式: 父组件:

第一种使用方式:
    父组件
        <Child :str.sync="str" />

        data() {
            return {
                str: "lxh",
            }
        }

    子组件
        <button @click="change">改变父组件的str</button>

        methods: {
            change() {
                // update: 固定写法
                this.$emit("update:str", "LXH");
            }
        }

第二种使用方式
    父组件
        <Child v-bind.sync="obj" />

        data() {
            return {
                obj: {
                    title: "lxh",
                    age: 25,
                },
            }
        }
        
    子组件
        <button @click="change">改变父组件的str</button>

        methods: {
            change() {
                // update: 固定写法
                this.$emit("update:title", "LXH");
                this.$emit("update:age", 26);
            }
        }

v-model的修饰符:

  • .lazy 失去焦点后才会修改数据
  • .trim 去掉首尾空格
  • .number 将值的类型转为数值类型
  • .number两种特殊情况:
    • 先输入数字,只取前面数字部分,值为number类型
    • 先输入除数字外的任意字符,.number无效,值为string类型

事件修饰符:

  • .stop:阻止冒泡
  • .prevent:阻止默认事件(比如a标签的的跳转)
  • .capture:事件默认是由内往外冒泡,这个的作用是由外往内捕获
  • .self:只有点击事件绑定的本身(event.target)才会触发
  • .once:事件只执行一次
  • .passive:当我们监听元素滚动事件的时候,会一直触发onscroll事件:
    • 在pc端没有什么问题,所以基本上用不到这个修饰符
    • 在移动端会让网页变卡,所以可以使用这个修饰符,相当于给onscroll事件加了.lazy修饰符

注意:修饰符的使用顺序很重要,比如:

  • @click.prevent.self 会阻止所有的点击
  • @click.self.prevent 只会阻止对元素自身的点击

按键修饰符:

一般结合@keyup和@keydown使用

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

系统修饰键

  • .ctrl
  • .shift
  • .alt
  • .meta(windows键)
  • .exact 精确的匹配系统修饰符

鼠标按钮修饰符

  • .left
  • .right
  • .middle

内部指令

  • v-show 显示与隐藏,切换元素的css样式属性display
  • v-if 显示与隐藏,创建、销毁DOM元素实现切换效果
  • v-else-if 前一个兄弟元素必须有v-if或v-else-if
  • v-else 前一个兄弟元素必须有v-if或v-else-if
  • v-for 遍历数组、对象、数字、字符串
  • v-model 双向绑定输入框的值
  • v-on 缩写@,绑定事件
  • v-bind 缩写:,动态绑定变量
  • v-slot 缩写#,插槽名
  • v-once 元素和组件只渲染一次
  • v-html 更新元素的innerHTML
  • v-text 相当于{{}},更新元素的textContent
  • v-pre 跳过这个元素和它的子元素的编译过程
  • v-cloak 这个指令保持在元素上,直到关联实例结束编译

v-cloak和css规则如: [v-cloak]: { display: none; } 这个指令可以隐藏未编译的标签,直到实例准备完毕

注意:

  • v-if和v-for一起使用时,v-for比v-if的优先级高,不推荐一起使用。
  • 频繁显隐使用v-show,否则使用v-if。

组件之间传值

父子组件

  • 父组件传值给子组件,子组件使用props进行接收
  • 子组件传值给父组件,子组件使用 $emit+事件 分发的方式对父组件进行传值
  • 组件中可以使用 parentparent 和 children 获取到父组件实例和子组件实例,进而获取数据
  • 使用 Vuex 进行状态管理
  • 使用 $refs 获取组件实例,进而获取数据
  • 使用 attrs(不能修改,只能访问获取)attrs(不能修改,只能访问获取) 和 listeners,在对一些组件进行二次封装时可以方便传值
父组件使用v-mode给子组件传参:
    父组件:
        <Child v-model="obj">
        
        data() {
            obj: {
                title: "lxh",
            }
        }
        
    子组件:
        <button @click="btn">子组件改变父组件的obj</button>
        
        props: {
            /**
             * 父组件通过v-model的方式传给子组件
             * 默认名叫value
             * 修改默认写法在model对象中的prop
             * 不再此处(props)声明
             * 可以通过this.$attrs.value访问
            **/
            value: {
                type: Object,
            },
        },
        // model 不需要修改默认值就可以不用写model
        model: {
            prop: "value", // 默认值 value
            event: "input", // 默认值 input
        },
        methods: {
            btn() {
                /**
                 * 此处修改父组件的obj
                 * input是默认的写法
                 * 修改默认写法在model对象中的event
                **/
                this.$emit("input", { title: "LXH"})
            }
        }

兄弟组件

  1. Vuex
  2. $parent
  3. 使eventBus 进行跨组件触发事件,进而传递数据,使用如下:
    • $emit() 发送
    • $on() 接收
    • $off() 移除

跨层级组件

  • vuex
  • eventBus
  • rootthis.root 存this.root.name="data",取this.$root.name(此方法不推荐)
  • 使用 provide 和 inject,官方建议我们不要用这个,但是ElementUI源码大量使用

路由

路由懒加载

  • component: () => import('路径')
  • component: (resolve) => require(['路径'], resolve)
  • component: r => require.ensure([], () => r(require('路径')), 'login')

路由模式

history模式

  • 通过pushState和replaceState切换url
  • 优点:符合url地址规范,看起来美观
  • 缺点:
    1. 兼容性比较差
    2. 当用户手动输入地址和刷新页面都会发起url请求,后端需要配置index.html页面,用于用户匹配不到静态资源的情况,否者会出现404错误。

hash模式

  • 通过hashChange()事件监听hash值的变化,根据路由表对应的hash值来判断加载对应的路由和组件。
  • 优点:
    1. 兼容性比较好
    2. 只需要前端的配置,不需要后端的参与
  • 缺点:
    1. 不符合url地址规范
    2. 不美观

动态绑定class和style

  • 动态class对象:<div :class="{'isActive': true, 'red': isRed}"></div>
  • 动态class数组:<div :class="['isActive', isRed ? 'red' : 'pink' ]"></div>
  • 动态style对象:<div :style="{ color: textColor, fontSize: '18px' }"></div>
  • 动态style数组:<div :style="[{ color: tColor, fontSize: '18px' }]"></div>

computed和watch

        computed是依赖已使用的变量,来计算一个结果,并且计算的结果会被缓存,依赖变量的值不改变,直接从缓存中读取结果。computed不支持异步操作。

computed传参数的写法:
    computed: {
        lxh() {
            return (e) => {
                // e就是传过来的数据
                return e
            }
        }
    }
    
写成箭头函数的形式:
    computed: {
        lxh: (_this) => (e) => _this.xxx + e
    }

        watch监听一个变量的变化,并执行相应的回调函数。watch支持异步操作。

watch的完整写法:
    watch: {
        lxh: {
            deep: true, // 开启深度监听
            immediate: true, // 立即监听
            hander() {
                // 异步操作
            }
        }
    }

vue生命周期

  • beforeCreate 创建了实例(此时this有值),但没有初始化数据和响应式处理
  • created 数据初始化和响应式处理完成,可以访问/修改到数据(data)
  • beforeMount render函数在这里被调用,生成虚拟DOM,但还没有转成真实DOM
  • Mounted 真实DOM挂载完毕
  • beforeUpdate 新的虚拟DOM生成,但还没有跟旧的虚拟DOM做对比打补丁
  • updated 新旧虚拟DOM对比打补丁后,进行真实DOM的更新
  • beforeDestroy 实例销毁之前调用,此时还能访问到数据
  • destroyed 实例销毁后调用,访问不到数据

被keep-alive包裹的组件多两个周期函数

  • activated 组件激活时调用
  • deactivated 组件停用是调用

下面这个周期函数用得少了解一下:errorCaptured 子孙组件发生错误时被调用,此时会收到三个参数:错误对象、发生错误组件的实例、错误来源信息的字符串,此钩子可以返回false阻止错误向上传播

vuex

  • state 设置初始状态
  • getters 相当于state的计算属性,接收参数state
  • mutations 唯一更改store中状态的方法,是同步函数,接收两个参数state、value(传过来的数据)
  • actions 用于提交mutations,而不是直接变更状态,可以包含任意异步操作,接收两个参数context(里面有state、commit等属性)、value(传过来的数据)
  • modules 模块化的使用store

不使用模块化

import { mapState, mapGetters, mapMutations, mapActions } from "vuex";

computed: {
    // 获取state的两种方式
    state() {
        return this.$store.state
    }
    ...mapState(['name'])
    
    // 获取getters的两种方式
    getters() {
        return this.$store.getters
    }
    ...mapGetters(['age'])
}
methods: {
    ...mapMutations(['increment'])
    ...mapActions(['decrement'])
    // commit 操作mutations:this.$store.commit('increment', 传过去的值)
    // dispatch 操作actions:this.$store.dispatch('decrement', 传过去的值)
}

// 使用
{{name}} -- {{age}}

使用模块化

第一步开启命名空间:namespaced: true
需要使用到vuex里的modules属性
import { mapState, mapGetters, mapMutations, mapActions } from "vuex";

computed: {
    // 获取state的两种方式
    lxh() {
        return this.$store.state.lxh
    }
    ...mapState('lxh', ['name'])
    
    // 获取getters的两种方式
    getters() {
        return this.$store.getters["lxh/getLxhName"]
    }
    ...mapGetters('lxh', ['age'])
}
methods: {
    ...mapMutations('lxh', ['increment'])
    ...mapActions('lxh', ['decrement'])
    // commit 操作mutations:this.$store.commit('lxh/increment', 传过去的值)
    // dispatch 操作actions:this.$store.dispatch('lxh/decrement', 传过去的值)
}

// 使用
{{name}} -- {{age}}

通过require.context引入全部模块

store/index.js 每个引入modules下的文件

const modulesFiles = require.context('./modules', true, /\.js$/)
const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1')
  const OBJ = modulesFiles(modulePath)
  modules[moduleName] = OBJ.default ? OBJ.default : OBJ[moduleName]
  return modules
}, {})

持久化

将store/index.js文件改成下面格式导出,方便下面的paths属性
const store = new Vuex.Store({
    state: {},
    getters: {},
    mutations: {},
    actions: {},
    mudules: {},
    plugins: {},
})

export default store;

function getStateKeys() {
  setTimeout(() => {
    return Object.keys(store.state)
  });
}

这里介绍插件:vuex-persistedstate
安装:npm i vuex-persistedstate
引入:import createPersistedState from "vuex-persistedstate"
使用:
    plugins: [createPersistedState({
        storage: window.sessionStorage, // 存储位置,默认localStorage
        storage: { // 加密的话可以这样写,btoa加密atob解密window的方法
          getItem: (key) => window.atob(sessionStorage.getItem(key)),
          setItem: (key, val) => sessionStorage.setItem(key, window.btoa(val)),
          removeItem: (key) => sessionStorage.removeItem(key),
        },
        key: 'vuex-keys', // 存在sessionStorage的key值
        // paths: getStateKeys(),// 存哪些数据,可以省略不写
    })],

定义不需要响应式的数据(死数据)

第一种定义在data中,但是在return之上:
    data() {
        this.deadData = "死数据";
        return {}
    }

第二种定义在data中:
    data() {
        return {
            deadData: Object.freeze("死数据")
        }
    }

父子组件执行顺序

        父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

插槽

匿名插槽

父组件:
    <Child>lxh</Child>
    // 旧语法
    <Child><div slot='default'>lxh</div></Child>
    <Child><template slot='default'>lxh</template></Child>
    // 新语法
    <Child><template #default>lxh</template></Child>
    <Child><template v-slot:default>lxh</template></Child>
    
子组件:
    <slot></slot>
    <slot name="default"></slot>

默认是default

具名插槽

子组件:
    <slot name="one"></slot>
    
父组件:
    <Child>
        // 旧语法
        <template slot="one"></template>
        <div slot="one"></div>
        // 新语法
        <template #one></template>
        <template v-slot:one></template>
    </Child>

作用域插槽

子组件:
    <slot :childData="{name: 'lxh'}"></slot>
    
父组件:
    <Child>
        // 旧语法
        <template slot-scoped="{childData}">{{childData.name}}</template>
        // 新语法
        <template #default="{childData}">{{childData.name}}</template>
    </Child>

自定义指令

局部指令

directives: {
    lxh: {
        /**
         * el dom
         * binding {name,value,oldValue,expression,arg,modifiers}
         * 详细含义看文档https://cn.vuejs.org/v2/guide/custom-directive.html
         * vnode 虚拟节点
         * oldVnode 上一个虚拟节点
        **/
        bind(el, binding, vnode) {},
        inserted(el, binding, vnode) {vnode.context.$nextTick(() => {});},
        update(el, binding, vnode, oldVnode) {},
        componentUpdated(el, binding, vnode, oldVnode) {},
        unbind(el, binding, vnode) {},
    },
    // 想在bind和update触发相同行为,不关注其他钩子,这些缩写
    lxh(el, binding) {
        // 改变字体颜色
        el.style.color = "red";
    }
}

全局指令

Vue.directive("lxh", {
    bind(el, binding, vnode) {},
    inserted(el, binding, vnode) {vnode.context.$nextTick(() => {});},
    update(el, binding, vnode, oldVnode) {},
    componentUpdated(el, binding, vnode, oldVnode) {},
    unbind(el, binding, vnode) {},
})

常用指令

export default {
    install(Vue) {
        // 点击复制
        Vue.directive('cope', {
            bind(el, { value }) {
                el.$value = value
                el.handle = () => {
                    if(!el.$value) {
                        console.log('暂无数据')
                    }
                    navigator.clipboard.writeText(el.$value).then(() => {
                        console.log("复制成功");
                    });
                }
                el.addEventListener('click', el.handle)
            },
            componentUpdated(el, { value }) {
                el.$value = value
            },
            unbind(el) {
                el.removeEventListener('click', el.handle)
            },
        })
        // 防抖
        Vue.directive("debounce", {
            bind(el, { value }) {
                let timer;
                el.handle = () => {
                    if (timer) {
                        clearTimeout(timer);
                    }
                    timer = setTimeout(() => {
                        value();
                    }, 1000);
                };
                el.addEventListener("click", el.handle);
            },
            unbind(el) {
                el.removeEventListener("click", el.handle);
            },
        });
    }
}

nextTick

将回调延迟到下次DOM更新循环之后执行this.$nextTick(() => {})

props

基本类型数据不能改,会报错;
引用类型数据可以操作里面的属性
修改可以直接this.obj.name = 'xxx'
新增属性:this.$set(this.obj, 'age', '18')
删除属性:this.$delete(this.obj, 'age')
// 完整版
props: {
    lxh: {
        type: Array, // 类型大写
        default: () => [1, 2, 3], // 默认值
        required: true, // 必传
        validator(e) { // 验证
            // e是lxh的值,必须返回一个真,不然会报错
            return true;
        }
    }
}

// 缩写形式
props: {
    lxh: String,
}

// 数组形式
props: ['xxx', 'yyy', 'lxh'],

hook

常用取消定时器:
    let timer = setInterval(() => {}, 1000);
    this.$once('hook:beforeDestroy', () =>{
        clearInterval(timer);
        timer = null;
    })
    
常用在子组件的周期函数里执行父组件的某一函数
    父组件:
        <Child @hook:mounted='childMountedHandler'></Child>
        
        methods: {
            childMountedHandler() {}
        }

provide和inject

祖先组件:
provide: {}, // 不推荐这种写法

provide() {
    return {
        // 值是基本类型,不是响应式的数据,引用类型就是响应式的数据
        property: 'value',
        xxx: this.changeValue,
        // 通过函数的方式也可以,注意:函数作为value,而不是this.changeValue()
    }
},
// 推荐写法
provide() {
    return {
        parentData: () =>({
            str: 'xxx',
            obj: {
                name: 'xxx',
            }
        })
    }
}
    
后代组件:
inject: ['property'],
inject: {
    xxx: {
        from: 'property', // 重命名
        default: '默认值',
    }
},

// 推荐接收写法
inject: ['parentData'],
computed: {
    parentObj() {
        return this.parentData()
    }
}
// 这样就直接用this.parentObj.str就是具有响应式的数据
this.parentObj.obj.name = 'yyy' // 可以修改
this.parentObj.str = 'yyy' // 修改不了
// 传过来的对象可以修改里面的属性,不能直接修改传过来的变量

transition

// 默认name是v,appear是开始就执行
<transition name="v" appear>
    <div>xxx</div>
</transition>


.v-enter-active {
  animation: ani 2s;
}

.v-leave-active {
  animation: ani 1s reverse;
}

@keyframes ani {
  from {
    transform: translate(-100%);
  }
  to {
    transform: translate(0);
  }
}

// 进入开始
.v-enter {
  transform: translate(-100%);
}
// 进入结束
.v-enter-to {
  transform: translate(0);
}
// 进入行为
.v-enter-active {
  transition: transform 2s linear;
}

// 离开开始
.v-leave {
  transform: translate(0);
}
// 离开结束
.v-leave-to {
  transform: translate(100%);
}
// 离开行为
.v-leave-active {
  transition: transform 1s linear;
}

vue3的改变
    .v-enter -> v-enter-from
    .v-leave -> v-leave-from

第三方动画animate.css
安装:npm install animate.css --save

mixins

组件和混入冲突:

  • 数据合并,优先级:组件、局部、全局;
  • 生命周期函数合并成数组,都执行,执行顺序:全局、局部、组件;
  • methods(方法)directives(指令)等,合并成对象,优先:组件、局部、全局;
mixin.js

export default {
    data() {
        return {}
    },
    mounted() {},
    methods: {},
}
// 局部混入
import mixin from './plugins/mixin.js'
mixins: [mixin],

// 全局混入(不推荐)
Vue.mixin({
    data() {
        return {}
    }
})

自定义插件

src/plugins.js(文件名自定义)

export default {
    install(Vue) {
        /**
        * 可以定义全局过滤器、全局指令、定义混入、Vue原型上添加方法等
        **/
        Vue.prototype.$api = "http://127.128.0.0:8888"
    }
}

src/main.js

import plugins from "./plugins.js";

Vue.use(plugins) // 使用插件

vue.config.js(跨域、优化等)

根目录下新建vue.config.js文件

const path = require("path");
const resolve = (dir) => path.join(__dirname, dir);

const isPro = process.env.NODE_ENV === "production"; // 判断当前环境
let externals = {};
let cdn = { css: [], js: [] };
if (isPro) {
  cdn = {
    /**
     * 国内:
     *     BootCDN网站(https://www.bootcdn.cn)
     *     七牛云(http://staticfile.org/)
     * 国外:
     *     unpkg网站(https://unpkg.com)
     *     cdnjs网站(https://cdnjs.com/)
     *     jsdelivr网站(https://www.jsdelivr.com/)
     **/
    css: [
      "https://unpkg.com/element-ui/lib/theme-chalk/index.css", // element-ui
    ],
    js: [
      "https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js", // vue
      "https://cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js", // vuex
      "https://cdn.jsdelivr.net/npm/vue-router@3.2.0/dist/vue-router.min.js", // vue-router
      "https://unpkg.com/element-ui/lib/index.js", // element-ui
      "https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js", // axios
    ],
  };
  externals = {
    // cdn引入 键值对:键是你要cdn引用的插件名,值是固定的,只能百,'包名':'在项目中引入的名字'
    vue: "Vue",
    vuex: "Vuex",
    "vue-router": "VueRouter",
    "element-ui": "ELEMENT",
    "element-plus": "ElementPlus",
    echarts: "echarts",
    axios: "axios",
    swiper: "swiper",
    xlsx: "XLSX",
  };
}

module.exports = {
  publicPath: isPro ? "./" : "/", // 打包配置
  outputDir: "dist",
  indexPath: "index.html",
  lintOnSave: false,
  productionSourceMap: false,
  devServer: {
    open: true,
    proxy: {
      "/api": {
        target: "http://localhost:3000",
        changOrigin: true,  //允许跨域
        pathRewrite: { "^/api": "" },
      },
    },
  },

  configureWebpack: {
    name: "我的vue2模板", // 设置项目名
    externals: externals,
  },

  chainWebpack: (config) => {
    if (isPro) {
      // 移除 prefetch 插件
      config.plugins.delete("prefetch");
      // 移除 preload 插件
      // config.plugins.delete("preload");
      // 压缩代码
      config.optimization.minimize(true);
      // 分割代码
      config.optimization.splitChunks({
        chunks: "all",
      });
      // 去除console.log
      config.optimization.minimizer("terser").tap((args) => {
        args[0].terserOptions.compress.drop_console = true;
        return args;
      });
    }
    config.plugin("preload").tap(() => [
      {
        rel: "preload",
        fileBlacklist: [/\.map$/, /hot-update\.js$/, /runtime\..*\.js$/],
        include: "initial",
      },
    ]);
    // 注入cdn变量 (打包时会执行)
    config.plugin("html").tap((args) => {
      args[0].cdn = cdn; // 配置cdn给插件
      return args;
    });
    config.resolve.alias
      .set("@", resolve("src"))
      .set("utils", resolve("src/utils"))
      .set("views", resolve("src/views"))
      .set("store", resolve("src/store"))
      .set("assets", resolve("src/assets"))
      .set("router", resolve("src/router"))
      .set("components", resolve("src/components"));
  },
};

public/index.html

<title><%= webpackConfig.name %></title>

<!-- 引入样式 -->
<% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
<link rel="stylesheet" href="<%=css%>" />
<% } %>

<!-- 引入JS -->
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
<script src="<%=js%>"></script>
<% } %>

filters

vue3中已移除过滤器

{{ str | fnName }}
<div :str="str | fnName"></div>

filters: {
    fnName(value) {
        return value;
    },
},

SSR

  • 服务器渲染
  • 基于node.js serve服务器环境开发,所有html代码在服务器渲染
  • 数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
  • 优点:首次加载更快,有更好的用户体验,有更好的seo优化。因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,然而爬虫不会等待数据加载完成,所以vue项目的seo体验不是很好。

vue3

vue2和vue3的区别:

  • 性能更比Vue2强。
  • 打包更科学不再打包没用到的模块
  • Composition API(组合API)
  • Fragment、Teleport、Suspense
  • 更友好的支持兼容TS
  • Custom Renderer API(自定义渲染API)

结束,谢谢。