Vue3(快速上手)

2023-11-04

Vue2 与 Vue3 的区别

数据双向数据绑定

Vue2.0 数据绑定 是通过 Object.defineProperty 来劫持对象属性的 geter 和 seter 操作,当数据发生改变发出通知
Object.defineProperty详解


// 数据
let data = {
	title: '',
	// 备份数据
	_data: {}
}
// 定义特性
Object.defineProperty(data, 'title', {
	// 定义特性属性或者特性方法
	// 取值方法
	get() {
		// console.log('get')
		// 注意:不能通过自身属性取值
		// return this.title
		// 返回备份的数据
		return this._data.title;
	},
	// 赋值方法
	set(value) {
		// this指向对象
		// 注意:不能为自身属性赋值
		// this.title = value
		// 我们可以向备份数据中存储
		this._data.title = value;
		// console.log('set')
		// 更新视图
		updateView(this._data)
	}
})
// 视图模板
let tpl = document.getElementById('app').innerHTML
// 实现更新视图的方法
function updateView(data) {
	// 处理模板
	let html = tpl.replace(/{{(\w+)}}/g, (match, $1) => {
		// 从data中获取数据
		return data[$1] || ''
	})
	// 更新视图
	document.getElementById('app').innerHTML = html;
}

Vue3.0 数据绑定 是用过ES6的新特性 proxy 来劫持数据,当数据改变时发出通知

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
Proxy有想要了解的可以看看


  <!DOCTYPE html>
  <html lang="en">
  <head>
      <meta charset="UTF-8">
      <meta name="yingaxiang" content="width=device-width, initial-scale=1.0">
      <title>vue3.0数据双向绑定</title>
  </head>
  <body>
      <div>
         <input type="text" id="input">
         <span id="text"></span>
     </div>
 </body>
 </html>
 <script>
     var obj = {};
     var obj1 = new Proxy(obj, {
         // target就是第一个参数obj, receive就是返回的obj(返回的proxy对象)
         get: function (target, key, receive) {
             // 返回该属性值
             return target[key];
         },
         set: function (target, key, newVal, receive) {
             // 执行赋值操作
             target[key] = newVal;
             document.getElementById('text').innerHTML = target[key];
         }
     })
     document.addEventListener('keyup', function (e) {
         obj1[0] = e.target.value;
     });
 </script>

总结

Vue2.x 版本中的双向绑定不能检测到小标的变化

proxy可以劫持整个对象,并返回一个新对象

创建项目

查看当前版本,如果是2开头说明当前使用的是vue-cli2,3开头话是vue-cli4

vue --version

如果无法识别vue命令说明没有安装vue-cli,使用以下说明进行安装
安装3.0版本: 目前新项目搭建脚手架默认安装得是3.0版本

npm install -g vue-cli

如果是旧项目2.0版本到3.0切换,即卸载当前版本,安装另外的版本

 npm uninstall -g vue-cli
 
 npm install -g @vue/cli

如果想从新版本降到旧版本

npm uninstall -g @vue/cli

npm install -g vue-cli

项目初始化

vue2.0

项目初始化

vue init webpack cli2-test

vue init <模板名称(webpack比较常用)> [项目名称]

参数介绍


//项目名称
 
Project name ...
 
//作者的信息,会默认从git中读取信息
 
Project description ...
 
Author ...
 
//vue build的选项 1.runtime-compiler 2.runtime-only (一般选第一个就好)
 
vue build ...
 
//是否安装vue-router,一般选用YES,省去手动创建路由
 
Install vue-router? ..
 
//是否使用ESLint检测代码规范,规范可根据选项选择不同的规范库或者自己添加规范
 
use ESLint to link your code
 
//是否写单元测试 (一般不使用)
 
Set up unit tests
 
//是否使用Nightwatch来进行e2e测试 (2代表to e to e 点对点)
 
Setup e2e test with Nightwatch?
 
//使用npm或者yarn包管理工具
 
use npm
 
use yarn

vue3.0

初始化

vue create cli3-test

3.0初始化,vue create [项目名称]

参数介绍

//选择一个配置方式
 please pick a perset  (一般选最后一个Manually select features(手动选择特性) )
 //选择对于你的工程所需要的特性 (用空格选择)
 check the features needed for your project
( ) Babel //转码器,可以将ES6代码转为ES5代码,从而在现有环境执行。 
( ) TypeScript// TypeScript是一个JavaScript(后缀.js)的超集(后缀.ts)包含并扩展了 JavaScript 的语法,需要被编译输出为 JavaScript在浏览器运行,目前较少人再用
( ) Progressive Web App (PWA) Support// 渐进式Web应用程序
( ) Router // vue-router(vue路由)
( ) Vuex // vuex(vue的状态管理模式)
( ) CSS Pre-processors // CSS 预处理器(如:less、sass)
( ) Linter / Formatter // 代码风格检查和格式化(如:ESlint)
( ) Unit Testing // 单元测试(unit tests)
( ) E2E Testing // e2e(end to end) 测试
 //对应的配置文件单独生成还是放在package.json里
 where do you prefer placing config for babel
 //要不要把刚才自己选择的配置保存下来
 save this as a preset for future projects?

项目目录结构

Vue2.0版本目录
在这里插入图片描述

Vue2.0版本目录
在这里插入图片描述

总结

vue-cli2.03.0在目录结构方面,有明显的不同

vue-cli3.0移除了配置文件目录,config 和 build 文件夹

同时移除了 static 静态文件夹,新增了 public 文件夹,打开层级目录还会发现, index.html 移动到 public3.0 config文件已经被移除,但是多了.env.production和env.development文件,除了文件位置,实际配置起来和2.0没什么不同

没了config文件,跨域需要配置域名时,从config/index.js 挪到了vue.config.js中,配置方法不变

移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中
在 src 文件夹中新增了 views 文件夹,用于分类 视图组件 和 公共组件

3.0版本中项目环境变量配置文件没有了(dev.env.js / prod.env.js)

我们可以通过在项目根目录下手动创建不同环境的配置文件,具体的环境变量名称由package.json中运行参数决定,下面举个例子添加development、production和uat版本的环境变量:

//  .env.delelopment
NODE_ENV=development
VUE_APP_MODE=development
BASE_URL=/develop
// .env.production
NODE_ENV=production
VUE_APP_MODE=production
BASE_URL=/api
 
// .env.uat
NODE_ENV=production
VUE_APP_MODE=uat
BASE_URL=/uat

不同得环境发不同的包

// package.json
----
"scripts": {
   "serve": "vue-cli-service serve",
   "build:uat": "vue-cli-service build --mode uat", // 通过 --mode来运行不同的环境,自动识别到.env.uat配置文件
   "build:production": "vue-cli-service build --mode production",
   "lint": "vue-cli-service lint"
 },

3.0版本中不同环境的webpack配置文件也没有了(webpack.base.conf.js / webpack.dev.conf.js / webpack.prod.conf.js)
同样,我们也可以再根目录中创建vue.config.js文件来进行webpack和vue的一些配置


const path = require('path')
 
module.exports = {
 publicPath: './', // 基本路径,打包时加上.
 outputDir: process.env.outputDir, // 输出文件目录
 lintOnSave: false, // eslint-loader 是否在保存的时候检查
 // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md
 // webpack配置
 chainWebpack: (config) => {
   config.resolve.symlinks(true)
 },
 configureWebpack: (config) => {
   if (process.env.VUE_APP_MODE === 'production') {
     // 为生产环境修改配置...
     config.mode = 'production'
   } else {
       // 为开发环境修改配置...
       config.mode = 'development'
   }
   Object.assign(config, {
     // 开发生产共同配置
     resolve: {
       alias: {
         '@': path.resolve(__dirname, './src'),
         '@c': path.resolve(__dirname, './src/components'),
         '@p': path.resolve(__dirname, './src/views')
       } // 别名配置
     }
   })
 },
 productionSourceMap: false, // 生产环境是否生成 sourceMap 文件
 // css相关配置
 css: {
   // extract: true, // 是否使用css分离插件 ExtractTextPlugin
   sourceMap: false, // 开启 CSS source maps?
   loaderOptions: {
     css: {}, // 这里的选项会传递给 css-loader
     less: {
       modifyVars: {
         // less vars,customize ant design theme
 
         // 'primary-color': '#F5222D',
         // 'link-color': '#F5222D',
         // 'border-radius-base': '4px'
       },
       // DO NOT REMOVE THIS LINE
       javascriptEnabled: true
     },
     postcss: {
       plugins: [
         // 把px单位换算成rem单位
         require('postcss-pxtorem')({
           rootValue: 75, // 换算的基数(设计图750的根字体为32)
           selectorBlackList: ['.van-'], // 要忽略的选择器并保留为px。
           propList: ['*'], // 可以从px更改为rem的属性。
           minPixelValue: 2 // 设置要替换的最小像素值。
         }),
         require('autoprefixer')
       ]
       // plugins: [
       //   require('autoprefixer')
       // ]
     } // 这里的选项会传递给 postcss-loader
   }, // css预设器配置项 详见https://cli.vuejs.org/zh/config/#css-loaderoptions
   // modules: false, // 启用 CSS modules for all css / pre-processor files.
   requireModuleExtension: true
 },
 parallel: require('os').cpus().length > 1, // 是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
 pwa: {}, // PWA 插件相关配置 see https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-pwa
 // webpack-dev-server 相关配置
 devServer: {
   open: false, // 自动打开浏览器
   host: '0.0.0.0', // 允许外部ip访问
   port: 8000, // 端口
   https: false, // 启用https
   overlay: {
     warnings: true,
     errors: true
   }, // 错误、警告在页面弹出
   // proxy: 'http://localhost:4000' // 配置跨域处理,只有一个代理
   proxy: {
       '/api': {
           target: '<url>',
           ws: true,
           changeOrigin: true
       },
       '/foo': {
           target: '<other_url>'
       }
   },  // 配置多个代理
 },
 // 第三方插件配置
 pluginOptions: {}

Composition API (setup 函数)

Composition API 介绍

Vue2时的方式在代码很少的时候,逻辑结构还是蛮清晰的,但是随着组件功能越来越多,代码量越来越大,整体内容全部放在其中肯定会显得臃肿。因为每个功能模块的代码会散落分布在各个位置,让整个项目的内容难以阅读和维护。这也导致我们需要考虑用其他方式来维护代码,比如Vuex。

在这里插入图片描述

到了Vue3,它会根据逻辑功能来进行组织,把同一个功能的不同代码都放在一起,或者把它们单独拿出来放在一个函数中,所以Composition API又被称为基于函数组合的API。
在这里插入图片描述

为什么要使用这种方式?

1、首先Composition API是根据逻辑相关性组织代码的,这样可以提高代码的可读性和可维护性。

2、这种方式可以更好地重用逻辑代码。比如,在Vue2中如果想重用逻辑代码,可能会发生命名冲突,以及关系不清。
在这里插入图片描述
同时需要说明的是,在Vue3中,Composition API是可选的,并不是一定要使用这种新方式,也就是说我们依然可以使用以前的结构和用法。


setup函数

现在我们进入到Vue3项目里,学习使用setup函数,它就是使用Composition API的入口。

注意点:setup函数是Vue3中新增的函数,它是我们在编写组件时,使用Composition API的入口。同时它也是Vue3中新增的一个生命周期函数,会在beforeCreate之前调用。因为此时组件的data和methods还没有初始化,因此在setup中是不能使用this的。所以Vue为了避免我们错误的使用,它直接将setup函数中的this修改成了undefined。并且,我们只能同步使用setup函数,不能用async将其设为异步。


简单使用

首先,给一个setup函数的使用简例:

<template>
  <div id="app">
    <p>{{ name }}</p>
  </div>
</template>

<script>
  // 不要忘记import
  import { ref } from 'vue'
  export default {
    setup(){
      const name = ref('王路飞')
      return { name }
    }
  }
</script>

这里ref函数的作用就是创建并返回一个响应式引用。此时需要注意的是,这个name并不会返回一个字符串类型的值,而是一个响应式对象。而通过return返回的对象中的属性,就都可以在模板中使用了。

如果我们想要在setup中使用methods的用法,我们需要做如下操作:

<template>
  <div id="app">
    <p>{{ name }}</p>
    <p>{{ age }}</p>
    <button @click="addOne">加一</button>
  </div>
</template>
<script>
  import { ref } from 'vue'
  export default {
    setup(){
      const name = ref('王路飞')
      const age = ref(17)
      function addOne(){
        age.value++
      }
      return {name, age, addOne}
    }
  }
</script>

刚才说到,在setup里是无法使用this的,并且age不是一个number类型数据,而是一个响应式对象,所以无法直接改变它的值。那么修改方法就是使用value。同理,如果我们想修改name的值,也需要使用value的方式。

如果我们想要在setup中使用computed的用法,我们需要做如下操作:

<template>
  <div id="app">
    <p>姓名:{{ name }}</p>
    <p>年龄:
      <button @click="changeAge(-1)">-</button>
      {{ age }}
      <button @click="changeAge(1)">+</button>
    </p>
    <p>出生年份:{{year}}</p>
  </div>
</template>

<script>
  import { ref, computed } from 'vue'
  export default {
    setup(){
      const name = ref('王路飞')
      const age = ref(17)
      const year = computed(() => {
        return 2020 - age.value
      })
      function changeAge(val){
        age.value += val
      }
      return {name, age, changeAge, year}
    }
  }
</script>

在这种情况下,如果我们想修改year的值,无法通过year.value的方式获取,我们需要使用getter和setter。

当属性被私有之后,外界无法直接访问,所以需要提供公共的访问方式,让外界可以间接地访问,并不是所有的属性都需要定义,被外界需要的才需要,对当前类可以控制外别访问属性的方式(我让你怎么访问就怎么访问)

一般提供给get方式获得私有属性的值,通过set定义私有属性的值 格式:方法名 get(set)+属性名(首字母大写)

<template>
  <div id="app">
    <p>姓名:{{ name }}</p>
    <p>年龄:
      <button @click="changeAge(-1)">-</button>
      {{ age }}
      <button @click="changeAge(1)">+</button>
    </p>
    <p>出生年份:
      <button @click="changeYear(-1)">-</button>
      {{ year }}
      <button @click="changeYear(1)">+</button>
    </p>
  </div>
</template>

<script>
  import { ref, computed } from 'vue'
  export default {
    setup(){
      const name = ref('王路飞')
      const age = ref(17)
      const year = computed({
        get: () => {
          return 2021 - age.value
        },
        set: val => {
          age.value = 2021 - val
        }
      })
      function changeAge(val){
        age.value += val
      }
      function changeYear(val){
        year.value += val
      }
      return {name, age, changeAge, year, changeYear}
    }
  }
</script>

现在通过上面的内容,我们知道,我们想要使用这些值,需要使用ref,获取值还要.value,而且这些响应式对象还需要通过return返回。先不说这样写大家觉得累不累,但至少如果响应式对象很多(当然一定会很多),那么return里要写的内容可太长了。所以这样操作显然还是比较繁琐的。除此以外,ref函数只能监听简单类型的变化,不能监听复杂类型的变化,比如对象和数组。

在这里插入图片描述
综上所述,Vue就提供了另一种定义响应式对象的方式就是:允许我们定义一个响应式对象,然后把我们想使用的值都放在对象内,当作对象属性。而如果我们想这么用,那我们需要先导入一个函数,reactive,它的作用就是创建并返回一个响应式对象。经过筛减,我们的代码现在如下所示:

<template>
  <div id="app">
    <p>姓名:{{ data.name }}</p>
    <p>年龄:
      <button @click="changeAge(-1)">-</button>
      {{ data.age }}
      <button @click="changeAge(1)">+</button>
    </p>
    <p>出生年份:
      <button @click="changeYear(-1)">-</button>
      {{ data.year }}
      <button @click="changeYear(1)">+</button>
    </p>
  </div>
</template>

<script>
  import { reactive,computed } from 'vue'
  export default {
    setup(){
      const data = reactive({
        name: '王路飞',
        age: 17,
        year: computed({
          get: () => {
            return 2020 - data.age;
          },
          set: val => {
            data.age = 2020 - val;
          }
        })
      });
      function changeAge(val){
        data.age += val;
      }
      function changeYear(val){
        data.year += val;
      }
      return { data, changeAge, changeYear };
    }
  }
</script>

需要注意的是,之前的age.value,现在变成了data.age,这部分一定要注意观察区别。也就是说用这种方式,除了methods现在所有的内容都放在了data里,方便我们的查阅。而且在template里,之前只写age,现在也要写成data.age。


reactive的注意点

reactive参数必须是对象(json / arr),否则无法实现响应式。

setup(){
    let state = reactive(123);
    function myFn(){
      state = 666;  //由于在创建响应式数据的时候传递的不是一个对象,所以无法实现响应式
      console.log(state); //输出666,但是页面无变化
    }
    return { state, myFn };
}

setup(){
    let state = reactive({
      age: 17
    });
    function myFn(){
      state.age = 666;
      console.log(state); //输出666,页面变化
    }
    return { state, myFn };
}

setup(){
    let state = reactive([1,3,5]);
    function myFn(){
      state[0] = 100;
      console.log(state); // 页面变化
    }
    return { state, myFn };
}

并且在console.log(state)后,我们可以发现Proxy,那也就证明了,Vue3中的响应式数据是通过ES6的Proxy来实现的。
在这里插入图片描述
如果给reactive传递了其他对象,默认情况下修改对象,界面不会自动更新。如果想更新,可以通过重新赋值的方式。

setup(){
    let state = reactive({
      time: new Date()
    });
    function myFn(){
      // 直接修改,页面不会更新:
      state.time.setDate(state.time.getDate() + 1 );
      console.log(state.time); // 日期变更,页面无变化

      // 重新赋值
      const newTime = new Date(state.time.getTime());
      newTime.setDate(state.time.getDate() + 1);
      state.time = newTime;
      console.log(state.time); // 日期变更,页面更新
    }
    return { state, myFn };
}

ref的注意点

虽然刚才提到ref用起来比较繁琐,但reactive也有一些问题。由于刚才证明了reactive必须传递一个对象,所以导致在企业开发中,如果我们只想让某个变量实现响应式的时候会非常麻烦,所以之后就需要考虑该如何选择使用reactive和ref。一定需要注意:ref函数只能监听简单类型的变化,不能监听复杂类型的变化,比如对象和数组。

但实际上ref底层的本质其实是reactive,系统会自动根据我们给ref传入的值将它转换成ref(xx) -> reactive({value:xx})。所以这也就是为什么,我们从ref获取值,需要用value的原因了。

setup(){
    let age = ref(17)
    function myFn(){
      age.value = 20;
    }
    return { age,myFn };
}

而我们在template里不需要使用age.value的原因是Vue会自动帮我们添加.value。

Vue判断响应式对象是否为ref的方法:其中有一个__v_isRef值,它会记录该数据到底是ref还是reactive。
在这里插入图片描述
除此以外,Vue2时也有个ref:

<template>
  <div id="app">
    <div ref="box">我是div</div>
  </div>
</template>

<script>
  export default {
    setup(){
      console.log(this.$refs.box)
    }
  }
</script>

但直接在setup里这么使用,是不行的,它无法识别,因为没有定义。
在这里插入图片描述
那么在其中,可以这么去用:

<template>
  <div id="app">
    <div ref="box">我是div</div>
  </div>
</template>


<script>
  import { ref,onMounted } from 'vue'

  export default {
    setup(){
      let box = ref(null);  // reactive({value: null})

      onMounted(()=>{
        console.log('onMounted',box.value); //到了相应生命周期才会执行,结果:<div>我是div<div>
      });

      console.log(box.value);  //虽然放在后侧也会先执行,结果:null

      return { box };
    }
  }
</script>

在这里插入图片描述


递归监听和非递归监听

默认情况下,无论是ref还是reactive都是递归监听。递归监听就是无论在响应式对象中有多少层内容,每一层它都会去进行Proxy监听。因此递归监听的问题就是,如果数据量比较大,它会非常消耗性能。

let state = reactive({
  a: 'a',
  b: {
    c: 'c',
    d: {
      e: 'e'
    }
  }
})

在这里插入图片描述
而对于非递归监听,它用到的是shallowRef和shallowReactive。

let state = shallowReactive({
  a: 'a',
  b: {
    c: 'c',
    d: {
      e: 'e'
    }
  }
})

这时我们可以看到,除了第一层以外,都没有了proxy监听,也就无法对除了第一层以外的这些内容进行更新:
在这里插入图片描述
但是对于ref有些特殊:

setup(){
    let state = shallowRef({
      a: 'a',
      b: {
        c: 'c',
        d: {
          e: 'e'
        }
      }
    })
    function myFn(){
      state.value.a = '1'
      state.value.b.c = '3'
      state.value.b.d.e = '5'
      console.log(state)
      console.log(state.value)
      console.log(state.value.b)
      console.log(state.value.b.d)
    }
    return { state,myFn };
}

测试一下可以发现,虽然我们对state.value.a进行了修改,但是在页面中却没有发生更新,难道ref连第一层都不能监听了?

其实是因为,刚才提到ref和reactive的转换关系,ref(xx) -> reactive({value:xx}),同理的,shallowRef(xx) -> shallowReactive({value:xx})。所以第一层的监听是state.value。并且我们需要如下面代码这样修改才能监听到变化,因为我们之前提到ref是无法监听复杂类型数据的变化的,那就只能通过重新赋新对象的方式了:

setup(){
    let state = shallowRef({
      a: 'a',
      b: {
        c: 'c',
        d: {
          e: 'e'
        }
      }
    })
    function myFn(){
      state.value = {
        a: '1',
        b: {
          c: '3',
          d: {
            e: '5'
          }
        }
      }
      console.log(state)
      console.log(state.value)
      console.log(state.value.b)
      console.log(state.value.b.d)
    }
    return { state,myFn };
}

而且通过这种方式,就不仅能修改第一层a的值,甚至能修改之后所有内容的值。所以简单来说,shallowRef貌似没什么限制效果。除此以外,我们还可以看到,shallow的值已经是true了:
在这里插入图片描述
那既然我们为其设置非递归监听,却还能修改其他层级的数据,那我们之后修改其他层级的数据只能通过重新创建新对象的方式来实现吗?那么Vue就为我们提供了一种方式:triggerRef。这个triggerRef就会查看之前state中发生变化的数据,然后主动帮我们更新。

<script>
  import { shallowRef,triggerRef } from 'vue'

  export default {
    setup(){
        let state = shallowRef({
          a: 'a',
          b: {
            c: 'c',
            d: {
              e: 'e'
            }
          }
        })
        function myFn(){
          state.value.b.d.e = '5'
          triggerRef(state)
        }
        return { state,myFn };
    }
  }
</script>

虽然之前所有内容都是成对出现,但这里是没有triggerReactive的,也就是说,非递归监听下,我们就真没办法修改其中任意一层的数据了,更何况递归监听下,修改响应式对象的数据是很简单的,不需要这种方式的存在。

所以综上所述,一般情况下我们使用 ref和reactive即可,只有在需要监听的数据量比较大的时候,才考虑使用shallowRef和shallowReactive。


toRefs

除此以外,代码还能再简洁一点,就是需要导入toRefs函数,这个函数的作用就是将一个响应式函数的对象,转变为普通的对象。但是这个普通的对象里的内容,又都是响应式对象,所以还需要解构,也就是应该这么使用: …toRefs(data)。

这么做之后,在template里就不需要写data了,完整代码如下:

<template>
  <div id="app">
    <p>姓名:{{ name }}</p>
    <p>年龄:
      <button @click="changeAge(-1)">-</button>
      {{ age }}
      <button @click="changeAge(1)">+</button>
    </p>
    <p>出生年份:
      <button @click="changeYear(-1)">-</button>
      {{ year }}
      <button @click="changeYear(1)">+</button>
    </p>
  </div>
</template>

<script>
  import { reactive,computed,toRefs } from 'vue'
  export default {
    setup(){
      const data = reactive({
        name: '王路飞',
        age: 17,
        year: computed({
          get: () => {
            return 2020 - data.age;
          },
          set: val => {
            data.age = 2020 - val;
          }
        })
      });
      function changeAge(val){
        data.age += val;
      }
      function changeYear(val){
        data.year += val;
      }
      return { ...toRefs(data), changeAge, changeYear };
    }
  }
</script>

再在此的基础上,我们还能把data部分的数据拿出来,写在一个函数中,这样就更方便我们区分各个功能模块了:

<script>
  import { reactive,computed,toRefs } from 'vue'
  export default {
    setup(){
      const { data, changeAge, changeYear } = test();
      return { ...toRefs(data), changeAge, changeYear };
    }
  }
  function test(){
    const data = reactive({
      name: '王路飞',
      age: 17,
      year: computed({
        get: () => {
          return 2020 - data.age;
        },
        set: val => {
          data.age = 2020 - val;
        }
      })
    });
    function changeAge(val){
      data.age += val;
    }
    function changeYear(val){
      data.year += val;
    }
    return { data, changeAge, changeYear };
  }
</script>

那这样一来,我们就还可以把test函数从这个vue组件当中提取出来,放在一个js文件当中集中管理相关功能了。
在这里插入图片描述

toRef

上面用到了toRefs,但其实还有一个toRef,功能一样。
首先先看个结论:

setup(){
    let obj = {name: 'zs'};
    let state = ref(obj.name);

    function myFn(){
      state.value = 'ls';
      console.log(state.value);  // 输出ls,页面更新
      console.log(obj.name);  // 输出zs
    }
    return { state,myFn };
}

从这里可以发现,如果利用ref将某一个对象中的属性变成响应式的数据,那我们修改响应式的数据是不会影响到原始数据的。

但当我们使用toRef将某一个对象中的属性变成响应式的数据,那我们修改响应式的数据是会影响到原始数据的。但是此时就不会触发页面的更新了。

setup(){
    let obj = {name: 'zs'};
    let state = toRef(obj, 'name');

    function myFn(){
      state.value = 'ls';
      console.log(state.value); // 输出ls,页面不更新
      console.log(obj.name);    // 输出ls,页面不更新
    }
    return { state,myFn };
}

所以ref和toRef的区别是:

ref是对原始数据的复制,修改响应式数据不会影响原始数据,同时数据发生改变,界面就会自动更新。
toRef是对原始数据的引用,修改响应式数据会影响原始数据,但数据发生改变,界面不会自动更新。

toRef的应用场景就是:响应式数据和原始数据相关联,但我们又不想更新数据后更新界面,那么就可以使用toRef。

所以toRefs还可以这么用:

<template>
  <div id="app">
    <p>{{name}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
  import { toRefs } from 'vue'

  export default {
    setup(){
        let obj = {name: 'zs', age: 17};
        let state = toRefs(obj);

        function myFn(){
          state.name.value = 'ls';
          state.age.value = '16';
        }
        return { ...toRefs(state), myFn };
    }
  }

</script>

customRef

customRef 的作用是返回一个ref对象,可以显示地控制依赖追踪和触发响应。

<template>
  <div id="app">
    <p>{{age}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
  import { customRef } from 'vue'

  function myRef(value){
    return customRef((track, trigger)=>{
      return{
        get(){
          track();  //告诉Vue这个数据需要追踪变化
          return value;
        },
        set(newValue){
          value = newValue;
          trigger(); //告诉Vue触发界面更新
        }
      }
    });
  }
  export default {
    setup(){
      let age = myRef(18);
      function myFn(){
        age.value += 1;
      }
      return { age,myFn };
    }
  }
</script>

这里补充一下,我之前使用@vue/cli的时候,这个方式去调用json数据会报错。但是根据报错信息去检查时,发现自己写的没有问题,所以在这里沉思一段时间,查阅资料也没找到解决方法。后来改用Vite创建项目,发现问题直接解决,暂时不知道原因,但之前提到过Vite是Vue作者开发的一款意图取代webpack的工具,也许Vite中帮我们处理了json数据。因此如果使用@vue/cli遇到json数据报错,却找不到原因的话,可以考虑使用Vite创建项目进行尝试

言归正传,之前提到过,setup是不能异步使用的,也就是在这里不能用async和await去获取json数据,那么之后获取更多数据,就要在其中写这么多的回调函数显然不方便管理。这时,我们可以考虑使用customRef:

<script>
  import { customRef } from 'vue'

  function myRef(value){
    return customRef((track, trigger)=>{
      fetch(value)
              .then((res)=>{
                return res.json();
              })
              .then((data)=>{
                console.log(data);
                value = data;
                trigger();
              })
              .catch((err)=>{
                console.log(err);
              })
      return{
        get(){
          track();  //告诉Vue这个数据需要追踪变化
          return value;
        },
        set(newValue){
          value = newValue;
          trigger(); //告诉Vue触发界面更新
        }
      }
    });
  }
  export default {
    setup(){
      let state = myRef('../public/data.json');
      return { state };
    }
  }
</script>

参数props和context

setup函数,它其实是可以设置一些参数的,一个叫做props,一个叫做context。

这个props参数是用来获取在组件中定义的props的。如下所示:

 export default {
    props:{
      title: String
    },
    setup(props){
      const data = reactive({
        name: '王路飞',
        age: 17,
        year: computed({
          get: () => {
            return 2020 - data.age;
          },
          set: val => {
            data.age = 2020 - val;
          }
        })
      });
      function changeAge(val){
        data.age += val;
        console.log(props.title)
      }
      function changeYear(val){
        data.year += val;
      }
      return { ...toRefs(data), changeAge, changeYear };
    }
  }

需要注意的是props.title,也就是通过props传递过来的值都只是可读的,我们无法修改。并且我们定义的所有props都是响应式的,我们可以监听props的值,一旦发生变化我们就做出相应。比如我们想监听title的值,我们需要使用watch,如下代码:

<script>
  import { reactive,computed,toRefs,watch } from 'vue'
  export default {
    props:{
      title: String
    },
    setup(props){
      const data = reactive({
        name: '王路飞',
        age: 17,
        year: computed({
          get: () => {
            return 2020 - data.age;
          },
          set: val => {
            data.age = 2020 - val;
          }
        })
      });
      function changeAge(val){
        data.age += val;
        console.log(props.title)
      }
      function changeYear(val){
        data.year += val;
      }
      watch(() => props.title, (newTitle, oldTitle) => {
        console.log(newTitle,oldTitle)
      })
      return { ...toRefs(data), changeAge, changeYear };
    }
  }
</script>

对于第二个参数context,我们之前说到在setup里是不能使用this的,但如果我们有些功能需要使用this,我们就可以使用context来获取attribute,获取插槽,或者发送事件。比如:

setup(props, context){
  const data = reactive({
    name: '王路飞',
    age: 17,
    year: computed({
      get: () => {
        return 2020 - data.age;
      },
      set: val => {
        data.age = 2020 - val;
      }
    })
  });
  function changeAge(val){
    data.age += val;
    console.log(props.title)
  }
  function changeYear(val){
    data.year += val;
  }
  watch(() => props.title, (newTitle, oldTitle) => {
    console.log(newTitle,oldTitle);
    context.emit('title-change');
  })
  return { ...toRefs(data), changeAge, changeYear };
}

它们的使用方法和以前还是一样,比如使用emit,在父组件中依然根据这里的名称,去用on来接收就可以了。


toRaw

我们现在知道,在setup函数里,只有响应式的数据发生改变,页面才有可能发生更新,也就是在setup函数里一个普通对象发生改变,是无论如何都不能引起页面更新的。那现在我们去做这些操作:

setup(){
    let obj = {name: 'zs', age: 17};
    let state = reactive(obj);

    console.log(obj === state); //  false

    function myFn(){
      // 对obj操作无法让页面发生更新,但是会修改obj数据
      obj.name = 'ls'
      console.log(obj);
      // 对state操作才能让页面发生更新,同时会修改obj数据
      // state.name = 'ls';
      // console.log(state);
    }
    return { state,myFn };
}

那么这里的obj和state是什么关系呢?
它们是引用关系,state的本质是一个Proxy对象,在这个Proxy对象中引用了obj。
在这里插入图片描述
说到toRaw的作用,它会从reactive或ref中得到原始数据(引用)。也就是说:

let obj = {name: 'zs', age: 17};
let state = reactive(obj);
let obj2 = toRaw(state);
// let state = ref(obj);
// let obj2 = toRaw(state.value);

console.log(obj === state); // false
console.log(obj === obj2);  // true

那么这么做有什么用?
因为ref和reactive每次修改都会被追踪,都会更新UI界面,所以如果有一些操作不需要追踪,不需要更新界面,那么这个时候toRaw的作用就体现出来了,因为它拿到原始数据,对原始数据进行修改就不会被追踪了,从而让性能提升。

除此以外还有一个markRaw。如果某数据永远都不想被追踪,就可以使用markRaw。

let obj = {name: 'zs', age: 17};
obj = markRaw(obj);
let state = reactive(obj);

readonly

readonly和它的名字一样,数据只能是只读的,并且是递归只读的。也就是它里面所有层级都是只读的。比如下面代码:

<template>
  <div id="app">
    <p>{{state.name}}</p>
    <p>{{state.attr.age}}</p>
    <p>{{state.attr.height}}</p>
    <button @click="myFn">按钮</button>
  </div>
</template>

<script>
  import { readonly,isReadonly,shallowReadonly } from 'vue'

  export default {
    setup(){
      let state = readonly({name: 'ls', attr:{age: 18,height: 1.88}});
      function myFn(){
        state.name = 'zs';
        state.attr.age = 16;
        state.attr.height = 1.66;
      }
      return {state, myFn};
    }
  }
</script>

在这里插入图片描述
shallowReadonly就可以让只有第一层是只读的

setup(){
  let state = shallowReadonly({name: 'ls', attr:{age: 18,height: 1.88}});
  function myFn(){
    state.name = 'zs';
    state.attr.age = 16;
    state.attr.height = 1.66;
	console.log(state);
  }
  return {state, myFn};
}

在这里插入图片描述
虽然设置shallowReadonly只有第一层是只读的,但是对其它内容进行修改,却无法让视图发生更新。

isReadonly就很简单了:

function myFn(){
  console.log(isReadonly(state));
}

对于只读类型数据,第一时间就可以想到const。const和readonly的区别:
我们知道如果const声明了一个对象,对象内部数据再重新赋值,依然是可以修改的,那么const做到的其实是赋值保护,即不能给变量重新赋值。但readonly是属性保护,即不能给属性重新赋值。


了解了Composition API之后,需要说的是,它的本质其实就是Vue2.x的Option API。setup函数return时,它就会把响应式对象写入到data里,把methods方法写入到methods里,其他功能都是同理。

Vue2.0 与 Vue3.0 生命周期函数比较

在这里插入图片描述


<template>
 
<router-link to="/">点这里去首页</router-link>
 
<hr>
 
<div class="home">
 
这里是一个计数器 >>> <span class="red">{{count}}</span> <br>
 
<button @click="countAdd">点击加数字</button>
 
</div>
 
</template>
 
<script>
 
// 你需要使用到什么生命周期,就引出来什么生命周期
 
import {
 
onBeforeMount,
 
onMounted,
 
onBeforeUpdate,
 
onUpdated,
 
onBeforeUnmount,
 
onUnmounted,
 
ref
 
} from 'vue'
 
 
export default {
 
// setup 函数,就相当于 vue 2.0 中的 created
 
setup () {
 
	const count = ref(0)
	 
	// 其他的生命周期都写在这里
 
	onBeforeMount (() => {
	 
		count.value++
	 
		console.log('onBeforeMount', count.value)
	 
	})
	 
	onMounted (() => {
	 
		count.value++
	 
		console.log('onMounted', count.value)
	 
	})
	 
	// 注意,onBeforeUpdate 和 onUpdated 里面不要修改值,会死循环的哦!
	 
	onBeforeUpdate (() => {
	 
		console.log('onBeforeUpdate', count.value)
	 
	})
	 
	onUpdated (() => {
	 
		console.log('onUpdated', count.value)
	 
	})
	 
	onBeforeUnmount (() => {
	 
		count.value++
	 
		console.log('onBeforeUnmount', count.value)
	 
	})
	 
	onUnmounted (() => {
	 
		count.value++
	 
		console.log('onUnmounted', count.value)
	 
	})
	 
	// 定义一个函数,修改 count 的值。
	 
	const countAdd = () => {
	 
		count.value++
	 
	}
	 
	return {
	 
		count,
		 
		countAdd
	 
	}
}
 
</script>

首先,在 vue 3.0 中,生命周期是从 vue 中导出的,我们需要用到哪些,就导出哪些。

可能不少看官会认为多次一举,但实则不然。vue 提供这么多的生命周期,有几个是我们常用的?在大多数的组件中,我们用不到生命周期。即便是页面级别的应用,可能用到最多的是 onMounted 即可。

当然,那些绑定时间的操作会用到解绑,因此会用到 onUnmounted。其它的生命周期,正常情况下是基本用不到的。所以,通过引入使用的这种设定,可以减少我们的最终编译的项目的体积。而且,这样的引入使用,更加的逻辑清晰。

其次,除 setup 之外,其他的生命周期函数,都是在 setup 里面直接书写函数即可。

对文件的引用上

Vue2.x中new出的实例对象,所有的东西都在这个vue对象上,这样其实无论你用到还是没用到,都会跑一变。

vue3.0中可以用ES module imports按需引入,如:keep-alive内置组件、v-model指令,等等

项目的启动

Vue2.x 版本启动			npm run dev

Vue3.x 版本启动			npm run serve

语法方面

  • v-model语法糖废弃,改用 modelValue
<input v-model="value" />
 
<input modelValue="value" />
  • 弃用全局API new Vue ,使用 createApp
const app = Vue.createApp({})
  • 弃用Vue.prototype,在Vue3中,我们可以使用如下定义方式
const app = Vue.createApp({})

app.config.globalProperties.$http = () => {}
  • 全局方法现在全部在app实例上,例如:
`app.directive`,`app.use`
  • 现在你需要手动挂载根节点

main.js
 
 
import { createApp } from 'vue'
 
import App from './App.vue'
 
 
createApp(App).mount('#app')
  • 不能再使用Vue.nextTick/this.$nextTick,Vue3中你可以用:
import { nextTick } from 'vue'
nextTick(() => {
  // something
})
  • Vue3允许template设置key。
  • 正式弃用scopedSlots正式弃用,旧的不去新的不来。
  • 监听数组变化需要使用deep属性,否则只能监听到整个数组被替换。
  • 弃用 c h i l d r e n ,访问子组件可以使用 children,访问子组件可以使用 children,访问子组件可以使用ref.
  • filter被移除。
  • 移除事件API, o n , on, on,once,$off不再使用。EventBus方法也不再使用

新加入了 TypeScript 以及 PWA 的支持

更精准的变更通知

2.x 版本中,使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行
3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Vue3(快速上手) 的相关文章

随机推荐

  • iframe 相互获取值

    链接 https www cnblogs com henuyuxiang p 7427155 html
  • Hp 笔记本开机不进入 grub 引导 ubuntu与windows选择界面

    我在预装了 windows 的机器上安装 ubuntu 每次想启动 ubuntu 时都需要按下 F9 才能进入引导选择界面 使用 ubuntu 的 efibootmgr 和 boot repair 等工具都不起作用 只是改变了 ubuntu
  • git常规操作

    场景一 从项目A的dev分支复制到项目B的dev分支上 1 将项目B clone 到本地 git clone b master 项目B的git地址 2 将项目A的git地址 添加至本地的remote git remote add upstr
  • 每月摘录--2023年7月

    企业 证监会最新消息 对蚂蚁集团及旗下机构处以罚款 含没收违法所得 71 23 亿元 并要求蚂蚁集团关停违规开展的 相互宝 业务 并依法补偿消费者利益 极客公园 7 月 7 日消息 据央行公布的行政处罚信息显示 今日 财付通支付科技有限公司
  • CoordinatorLayout的简单使用,android开发app的详细过程

    效果展示 代码展示
  • 军工重组

    http bar stockstar com p8448439 1 html 下周可千万别洗出来 2660到现在用了没多久就临近3000点 只要地产一起来马上就到3600了 地产现在不涨并不是不想涨 而是只要地产一起来马上就到3600 多数
  • VSCode安装教程最新,包教包会!

    一 VScode下载 1 进入VScode官网 官网地址 https code visualstudio com 点击 Download 进入下载 不要点击 Download for Windows Stable Build 否则它会自动帮
  • 编译Linux内核生成Image和System.map文件

    p span style font family 华文楷体 font size 12pt background color rgb 255 255 255 一直想琢磨琢磨Linux内核 便开始看 Linux内核完全注释 可是发现一头雾水 所
  • 用java实现计算器四则运算以及混合运算

    贴代码 本例测试是基于junit eclipse可安装对应 的java包 我用的是idea 添加插件即可 import java io BufferedReader import java io IOException import jav
  • eclipse 配置 C++

    前言 最近有项目需要c 但是c 自从离校那时就没碰过了 所以要重新学习下 因为曾经为了做自己的博客网站 学了java 下载了eclipse 也是在eclipse上写的博客网站的 所以对eclipse还是相对熟悉的 而且平时写代码都是用vim
  • android手势识别opencv,较为成熟的安卓项目--人面识别,手势识别向

    一 人脸识别 1 目标检测 目标追踪 人脸检测 人脸识别 效果 2 Android下使用OpenCV实现人脸检测 效果 3 人脸标识 效果 4 人脸检测 github https github com VernonVan Face 效果 主
  • Jmeter 中随机函数__Random 的使用

    前段时间 在做接口测试时 经常遇到接口参数需要输入不同的内容或者手机号码等 不允许输入重复的参数内容 比如不同的手机号码 那此时可以通过Random 随机函数来解决此问题 以前的文章有介绍过使用time函数来实现 详见 http blog
  • RuntimeError: Error(s) in loading state_dict for Net(让人心累的错误)

    RuntimeError Error s in loading state dict for Net size mismatch for classifier 4 weight xxxxxx 后面一堆错误 这个是model py 千万千万别
  • 【DL】第 6 章:语言建模

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 小程序跳转:云开发之h5跳小程序

    目录 前言 前提条件 注意 实现步骤 更多前端知识 前言 此方案是我在实际开发中的全部过程 因为我也是第一次做小程序的云开发 一开始根据这个文档就遇到了一些坑 所以在这里我做了更详细的步骤分解 非个人主体并且已认证的 微信认证 小程序 使用
  • tictoc例子理解10-12

    tictoc10 12 tictoc 10 几个模块连接 发送消息直到模块3收到消息 tictoc 11 新增信道定义 tictoc 12 双向连接信息简化定义 tictoc 10 几个模块连接 发送消息直到模块3收到消息 让我们用几个 n
  • 【机器学习】 Matlab 实现多种分类器(感知机、KNN、Logistic、最大熵、决策树、朴素贝叶斯)的二分类

    写在之前 这是本人的统计学习方法作业之一 老师要求一定要用Matlab编程 本人在此之前未曾大量使用Matlab 因此某些算法可能因为不知道函数或者包而走了弯路 代码高亮查了一下 没找到Matlab的所以用了C的 部分算法参考了某些算法的p
  • github中fork分支和pullrequest的最佳实践

    github中fork分支和pullrequest的最佳实践 github中fork分支和pullrequest的最佳实践 最近在参与一个国外的github开源项目 遇到自己fork了源库 一段时间之后 源库已经更新了一些内容 这样 自己f
  • 【uni-app】使用uni-app实现简单的登录注册功能

    文章目录 前言 一 页面布局 二 注册页面 1 注册接口使用 2 注册成功提示 3 注册成功页面跳转 4 完整代码 三 登录页面 1 登录接口使用 2 本地存储使用 3 完整代码 总结 前言 大家好 今天和大家分享一下如何在uni app中
  • Vue3(快速上手)

    Vue2 与 Vue3 的区别 数据双向数据绑定 Vue2 0 数据绑定 是通过 Object defineProperty 来劫持对象属性的 geter 和 seter 操作 当数据发生改变发出通知 Object defineProper