美文网首页Vue技术
vue3.0环境搭建及特性

vue3.0环境搭建及特性

作者: 小姑凉喜欢无脸男 | 来源:发表于2021-09-06 14:57 被阅读0次

    一、环境搭建

    1. Node官网安装Node,推荐下载长期支持版。

    检查是否安装成功Node,在cmd输入 node -v。若出现版本号就是安装成功。

    查看node版本号.png
    安装完node,会附带npm命令。输入npm -v 就能查看npm的版本。
    查看npm版本号
    若需要cnpm命令,安装淘宝镜像 npm install -g cnpm --registry=https://registry.npm.taobao.org
    安装完成后用cnpm -v查看安装的版本号。
    查看cnpm版本号

    windows若出现问题——'cnpm' 不是内部或外部命令,也不是可运行的程序或批处理文件。可以根据cmd上显示的cnpm安装的地址,找到cnpm文件夹拷贝到npm文件夹所在的目录。
    或 npm config set prefix "npm所在的位置"


    cnpm与npm在同一个位置
    2. vue环境
    1.安装vue和vue-cli脚手架
    //最新稳定版(安装和更新的代码相同)
    npm install vue@next
    npm install -g @vue/cli
    //或一起安装
    npm install -g vue @vue/cli
    

    保证vue/cli版本在4.5.0以上,才能更好的支持3.0。
    用vue -V查看安装的版本号检验是都成功安装。


    vue/cli版本号
    2. 创建vue项目
    npm create  <project-name>
    

    如果想构建一个webpack的vue项目

    npm init webpack  <project-name>
    

    也可以使用 Vite 快速构建 Vue 项目(Vite是一个 web 开发构建工具,由于其原生 ES 模块导入方式,可以实现闪电般的冷服务器启动)

    npm init vite <project-name> -- --template vue
    

    安装事项:这里会询问使用哪个模板进行安装,我们选择自定义安装的方法Manually select features


    安装方法选择

    根据需要选择功能


    选择功能
    选择vue版本及其他
    选择vue版本
    image.png

    安装完成,系统默认npm run serve运行


    安装完成

    二、特性

    Vue3.0与Vue2.x比,
    1、性能方面有很大的提升——打包大小减少41%、初次渲染快55%,更新快133%、内存使用减少54%(得益于虚拟DOM的重写和Tree-shaking的优化)。
    2、使用Composition API。Vue2.x使用组件选项来组织逻辑,随着复杂度上升易读性比较差,Vue3.0使用Composition API按照逻辑分类,代码维护和易读性较好。虽然Vue2.x给出Mixin来解决这个问题,但是Mixin会存在命名冲突的问题,不清楚暴露出来变量的最用及重用逻辑到其他Component经常会遇到的问题。
    3、对TypeScript的支持更好

    Tree-shaking

    Vue3最重要的变化之一就是引入了Tree-Shaking,Tree-Shaking带来的bundle体积更小是显而易见的。在2.x版本中,很多函数都挂载在全局Vue对象上,比如nextTick、mixin、set等函数,因此虽然我们可能用不到,但打包时只要引入了vue这些全局函数仍然会打包进bundle中。
    而在Vue3中,所有的API都通过ES6模块化的方式引入,这样就能让webpack或rollup等打包工具在打包时对没有用到API进行剔除,最小化bundle体积;
    简而言之,Tree-Shaking这个打包工具在打包的时候不会把没用到的API打包进去。

    //dog.vue
    export function dogName(){
      return 'dogName'
    }
    export function dogAge(){
      return 'dogAge'
    }
    //index.vue  没使用dogAge所以dogAge不会被打包进去
    import { dogName,dogAge} from './dog'
    dogName()
    

    全局API的修改:main.js中能直接发现这样的变化:

    import { createApp } from 'vue'
    import App from './App.vue'
    import router from './router'
    import store from './store'
    
    createApp(App).use(store).use(router).mount('#app')
    

    创建app实例方式从原来的new Vue()变为通过createApp函数进行创建;不过一些核心的功能比如virtualDOM更新算法和响应式系统无论如何都是会被打包的;这样带来的变化就是以前在全局配置的组件(Vue.component)、指令(Vue.directive)、混入(Vue.mixin)和插件(Vue.use)等变为直接挂载在实例上的方法;我们通过创建的实例来调用,带来的好处就是一个应用可以有多个Vue实例,不同实例之间的配置也不会相互影响,且可以完美的支持tree-shaking:

    const app = createApp(App)
    app.use(/* ... */)
    app.mixin(/* ... */)
    app.component(/* ... */)
    app.directive(/* ... */)
    

    Vue 2.x 中的这些全局 API 受此更改的影响:


    受影响的API
    setup

    先说说 选项式API和组合式 API。

    选项式API

    2.x版本采用的是Options API(选项API)。在 props 里面设置接收参数,在 data 里面设置变量,在 computed 里面设置计算属性,在 watch 里面设置监听属性,在 methods 里面设置事件方法

    使用 (data、computed、methods、watch) 组件选项来组织逻辑通常都很有效。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。
    这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
    如果能够将同一个逻辑关注点相关代码收集在一起会更好。而这正是组合式 API 使我们能够做到的。

    组合式 API

    而Composition API做的就是把同一功能的代码放到一起维护,这样我们需要维护一个功能点的时候,不用去关心其他的逻辑,只关注当前的功能。
    实际使用组合式 API 的地方,在 Vue 组件中,我们将此位置称为 setup。也就是将(生命周期、computed、watch、provide/inject)写在 setup 里。
    注意:
    因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以在生命周期钩子中编写的任何代码都应该直接在 setup 函数中编写。
    没有this。因为setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。
    setup 选项是一个接收 propscontext 的函数,此外,我们将 setup 返回的所有内容都暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。

    export default {
      name: "Button",
      setup() {
        const state = reactive({
          count: 1,
        });
        const num = ref(2);
        function add() {
          state.count++;
          num.value += 10;
        }
        const double = computed(() => state.count * 2);
        return {
          state,
          double,
          num,
          add,
        };
      },
    };
    
    生命周期

    在Vue2.x中有8个生命周期函数:
    beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed

    在vue3中,新增了一个setup生命周期函数,setup执行的时机是在beforeCreate生命函数之前执行,因此在这个函数中是不能通过this来获取实例的;同时为了命名的统一,将beforeDestroy改名为beforeUnmount,destroyed改名为unmounted。增加了onRenderTracked,onRenderTriggered两个钩子函数作追踪调试功能。
    同时,我们可以通过在生命周期函数前加on来访问组件的生命周期。


    生命周期钩子
    vue2->vue3.png

    三、新增功能

    响应式API

    要为 JavaScript 对象创建响应式状态,可以使用 reactive 方法:

    import { reactive } from 'vue'
    
    // 响应式状态
    const state = reactive({
      count: 0
    })
    

    reactive 相当于 Vue 2.x 中的 Vue.observable() API

    reactive函数只接收object和array等复杂数据类型。

    对于一些基本数据类型,比如字符串和数值等,我们想要让它变成响应式,我们当然也可以通过reactive函数创建对象的方式,但是Vue3提供了另一个函数ref:

    import { ref } from 'vue'
    
    const count = ref(0)
    console.log(count.value) // 0
    count.value++
    console.log(count.value) // 1
    

    ref 会返回一个可变的响应式对象,该对象作为一个响应式的引用维护着它内部的值,这就是 ref 名称的来源。该对象只包含一个名为 value 的 property。

    当 ref 作为渲染上下文 (从 setup() 中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动浅层次解包内部值。只有访问嵌套的 ref 时需要在模板中添加 .value

    <template>
      <div>
        <span>{{ count }}</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>
    

    注意:reactive主要负责复杂数据结构,而ref主要处理基本数据结构;但是很多童鞋就会误解ref只能处理基本数据,ref本身也是能处理对象和数组的。
    区别:在语法层面,两个有差异。ref定义的响应式数据需要用[data].value的方式进行更改数据;reactive定义的数据需要[data].[prpoerty]的方式更改数据。
    但是在应用的层面,还是有差异的,通常来说:单个的普通类型的数据,我们使用ref来定义响应式。表单场景中,描述一个表单的key:value这种对象的场景,使用reactive;在一些场景下,某一个模块的一组数据,通常也使用reactive的方式,定义数据。

    访问响应式对象

    vue2.0中,通过vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。因为Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

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

    const count = ref(0)
    const state = reactive({
      count
    })
    
    console.log(state.count) // 0
    
    state.count = 1
    console.log(count.value) // 1
    

    如果将新的 ref 赋值给现有 ref 的 property,将会替换旧的 ref:

    const otherCount = ref(2)
    
    state.count = otherCount
    console.log(state.count) // 2
    console.log(count.value) // 1
    

    当我们处理一些大型响应式对象的property时,我们很希望使用ES6的解构来获取我们想要的值:

    let book = reactive({
      name: 'Learn Vue',
      year: 2020,
      title: 'Chapter one'
    })
    let {
      name,
    } = book
    
    name = 'new Learn'
    console.log(book.name);// Learn Vue
    

    遗憾的是,使用的 property 的响应性丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:

    let book = reactive({
      name: 'Learn Vue',
      year: 2020,
      title: 'Chapter one'
    })
    let {
      name,
    } = toRefs(book)
    
    // 注意这里解构出来的name是ref对象
    // 需要通过value来取值赋值
    name.value = 'new Learn'
    console.log(book.name);// new Learn
    

    使用 readonly 防止更改响应式对象

    let book = reactive({
      name: 'Learn Vue',
      year: 2020,
      title: 'Chapter one'
    })
    const copy = readonly(book);
    //Set operation on key "name" failed: target is readonly.
    copy.name = "new copy";
    
    响应式计算和侦听
    计算值

    有时我们需要的值依赖于其他值的状态。在vue2.x中我们使用computed函数来进行计算属性,在vue3中将computed功能进行了抽离,它接受一个getter函数,并为getter返回的值创建了一个不可变的响应式ref对象:

    const num = ref(0);
    const double = computed(() => num.value * 2);
    num.value++;
    
    console.log(double.value);// 2
    double.value = 4// Warning: computed value is readonly
    

    或者,它可以使用一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。

    const num = ref(0);
    const double = computed({
      get: () => num.value * 2,
      set: (val) => (num.value = val / 2),
    });
    
    num.value++;
    console.log(double.value);// 2
    
    double.value = 8
    console.log(num.value);// 4
    
    侦听

    和computed相对应的就是watch,computed是多对一的关系,而watch则是一对多的关系;vue3也提供了两个函数来侦听数据源的变化:watch和watchEffect。

    watch,它的用法和组件的watch选项用法完全相同,它需要监听某个数据源,然后执行具体的回调函数:

    import { reactive, ref, watch } from "vue";
    
    const state = reactive({
      count: 0,
    });
    
    //侦听时返回值得getter函数
    watch(
      () => state.count,
      (count, prevCount) => {
        console.log(count, prevCount);  // 1 0
      }
    );
    state.count++;
    
    const count = ref(0);
    //直接侦听ref
    watch(count, (count, prevCount) => {
      console.log(count, prevCount, "watch");// 2 0
    });
    count.value = 2;
    

    如果我们来侦听一个深度嵌套的对象属性变化时,需要设置deep:true。

    一般侦听都会在组件销毁时自动停止,但是有时候我们想在组件销毁前手动的方式进行停止,可以调用watch返回的stop函数进行停止。

    const count = ref(0);
    
    const stop = watch(count, (count, prevCount) => { 
      console.log(count, prevCount);// 打印1 0,2 1 后不执行
    });
    
    setInterval(()=>{
       count.value++;
    }, 1000);
    // 停止watch
    setTimeout(()=>{
       stop();
    }, 2500);
    

    还有一个函数watchEffect也可以用来进行侦听,但是都已经有watch了,这个watchEffect和watch有什么区别呢?他们的用法主要有以下几点不同:
    1、watchEffect不需要手动传入依赖
    2、每次初始化时watchEffect都会执行一次回调函数来自动获取依赖
    3、watchEffect无法获取到原值,只能得到变化后的值

    const count = ref(0);
    const state = reactive({
      year: 2021,
    });
    
    watchEffect(() => {
      console.log(count.value);
      console.log(state.year);//0 2021,1,2022...
    });
    watch([() => state.year, count], (newVal, oldVal) => {
      //[2022, 1]  [2021, 0]
      //[2023, 2]  [2022, 1]...
      console.log(newVal, oldVal);
    });
    setInterval(() => {
      count.value++;
      state.year++;
    }, 1000);
    
    Fragment

    所谓的Fragment,就是片段;在vue2.x中,要求每个模板必须有一个根节点,所以我们代码要这样写:

    <template>
      <div>
        <span></span>
        <span></span>
      </div>
    </template>
    

    或者在Vue2.x中还可以引入vue-fragments库,用一个虚拟的fragment代替div;在React中,解决方法是通过的一个React.Fragment标签创建一个虚拟元素;在Vue3中我们可以直接不需要根节点:

    <template>
        <span>hello</span>
        <span>world</span>
    </template>
    
    Teleport

    可以将插槽中的元素或者组件传送到页面的其他位置。就是在一些嵌套比较深的组件来转移模态框的位置。虽然在逻辑上模态框是属于该组件的,但是在样式和DOM结构上,嵌套层级后较深后不利于进行维护(z-index等问题);因此我们需要将其进行剥离出来:

    <template>
      <button @click="showDialog = true">打开模态框</button>
    
      <teleport to="#modal">
        //to可以定义最后组件在哪里显示
        <div class="modal" v-if="showDialog" style="position: fixed">
          我是一个模态框
          <button @click="showDialog = false">关闭</button>
          <child-component :msg="msg"></child-component>
        </div>
      </teleport>
    </template>
    <script>
    export default {
      data() {
        return {
          showDialog: false,
          msg: "hello"
        };
      },
    };
    </script>
    
    <!DOCTYPE html>
    <html lang="">
      <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <title></title>
      </head>
      <body>
        <noscript>
        </noscript>
        <div id="app"></div>
        <div id="modal"></div>//teleport组件将加载在这里
        <!-- built files will be auto injected -->
      </body>
    </html>
    
    
    Suspense

    Suspense是Vue3推出的一个内置组件,它允许我们的程序在等待异步组件时渲染一些后备的内容,可以让我们创建一个平滑的用户体验;Vue中加载异步组件其实在Vue2.x中已经有了,我们用的vue-router中加载的路由组件其实也是一个异步组件:

    export default {
      name: "Home",
      components: {
        AsyncButton: () => import("../components/AsyncButton"),
      },
    }
    

    在Vue3中重新定义,异步组件需要通过defineAsyncComponent来进行显示的定义:

    // 全局定义异步组件
    //src/main.js
    import { defineAsyncComponent } from "vue";
    const AsyncButton = defineAsyncComponent(() =>
      import("./components/AsyncButton.vue")
    );
    app.component("AsyncButton", AsyncButton);
    
    
    // 组件内定义异步组件
    // src/views/Home.vue
    import { defineAsyncComponent } from "vue";
    export default {
      components: {
        AsyncButton: defineAsyncComponent(() =>
          import("../components/AsyncButton")
        ),
      },
    };
    

    同时对异步组件的可以进行更精细的管理:

    export default {
      components: {
        AsyncButton: defineAsyncComponent({
          delay: 100,
          timeout: 3000,
          loader: () => import("../components/AsyncButton"),
          errorComponent: ErrorComponent,
          onError(error, retry, fail, attempts) {
            if (attempts <= 3) {
              retry();
            } else {
              fail();
            }
          },
        }),
      },
    };
    

    这样我们对异步组件加载情况就能掌控,在加载失败也能重新加载或者展示异常的状态。

    四、非兼容的功能

    非兼容的功能主要是一些和Vue2.x版本改动较大的语法,在Vue3上可能存在兼容问题。

    v-model

    非兼容:用于自定义组件时,v-model prop 和事件默认名称已更改:
    prop:value -> modelValue;
    event:input -> update:modelValue;
    非兼容:v-bind 的 .sync 修饰符和组件的 model 选项已移除,可用 v-model 作为代替;
    新增:现在可以在同一个组件上使用多个 v-model 进行双向绑定;
    新增:现在可以自定义 v-model 修饰符。

    <ChildComponent v-model:title="pageTitle" />
    
    <!-- 是以下的简写: -->
    
    <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
    
    v-if 与 v-for 的优先级对比

    2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用
    3.x 版本中 v-if 总是优先于 v-for 生效。

    v-bind合并

    在 2.x,如果一个元素同时定义了 v-bind="object" 和一个相同的单独的 property,那么这个单独的 property 总是会覆盖 object 中的绑定。

    <!-- template -->
    <div id="red" v-bind="{ id: 'blue' }"></div>
    <!-- result -->
    <div id="red"></div>
    

    在 3.x,如果一个元素同时定义了 v-bind="object" 和一个相同的单独的 property,那么声明绑定的顺序决定了它们如何合并。换句话说,相对于假设开发者总是希望单独的 property 覆盖 object 中定义的内容,现在开发者对自己所希望的合并行为有了更好的控制。

    <!-- template -->
    <div id="red" v-bind="{ id: 'blue' }"></div>
    <!-- result -->
    <div id="blue"></div>
    
    <!-- template -->
    <div v-bind="{ id: 'blue' }" id="red"></div>
    <!-- result -->
    <div id="red"></div>
    
    v-for中ref

    在 Vue 2 中,在 v-for 中使用的 ref attribute 会用 ref 数组填充相应的 $refs property。当存在嵌套的 v-for 时,这种行为会变得不明确且效率低下。

    <template>
      <div v-for="item in list" :ref="setItemRef"></div>
    </template>
    <script>
    export default {
      data(){
        list: [1, 2]
      },
      mounted () {
        console.log(this.$refs.setItemRef)  // [div, div]
      }
    }
    </script>
    

    在 Vue 3 中,此类用法将不再自动创建 $ref 数组。要从单个绑定获取多个 ref,请将 ref 绑定到一个更灵活的函数上 (这是一个新特性):

    <template>
      <div v-for="item in 3" :ref="setItemRef"></div>
    </template>
    <script>
    import { reactive, onUpdated } from 'vue'
    export default {
      setup() {
        let itemRefs = reactive([])
    
        const setItemRef = el => {
          itemRefs.push(el)
        }
    
        onUpdated(() => {
          console.log(itemRefs)
        })
    
        return {
          itemRefs,
          setItemRef
        }
      }
    }
    </script>
    
    Data

    非兼容:组件选项 data 的声明不再接收纯 JavaScript object,而是接收一个 function。
    非兼容:当合并来自 mixin 或 extend 的多个 data 返回值时,合并操作现在是浅层次的而非深层次的 (只合并根级属性)。

    在 2.x 中,开发者可以通过 object 或者是 function 定义 data 选项。但是我们知道在组件中如果data是object的话会出现数据互相影响,因为object是引用数据类型。
    在Vue3中,data只接受function类型,通过function返回对象;同时Mixin的合并行为也发生了改变,当mixin和基类中data合并时,会执行浅拷贝合并:

    const Mixin = {
      data() {
        return {
          user: {
            name: 'Jack',
            id: 1
          }
        }
      }
    }
    
    const CompA = {
      mixins: [Mixin],
      data() {
        return {
          user: {
            id: 2
          }
        }
      }
    }
    //2.x
    {
      "user": {
        "id": 2,
        "name": "Jack"
      }
    }
    //3.x
    {
      "user": {
        "id": 2
      }
    }
    

    相关文章

      网友评论

        本文标题:vue3.0环境搭建及特性

        本文链接:https://www.haomeiwen.com/subject/xxolultx.html