1.数据双向绑定方面
vue2通过使用 Object.defineProperty 来劫持对象属性的 geter 和 seter 操作,当数据发生改变发出通知
// 数据
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使用ES6的新特性porxy
// 数据
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;
}
区别 Vue2.x版本中的双向绑定不能检测到下标的变化,proxy可以劫持整个对象,并返回一个新对象
这⾥是引相⽐于vue2版本,使⽤proxy的优势如下
1.defineProperty只能监听某个属性,不能对全对象监听
proxy可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)
2.可以监听数组,不⽤再去单独的对数组做特异性操作
通过Proxy可以直接拦截所有对象类型数据的操作,完美⽀持对数组的监听。
2.创建项目
查看版本:查看当前版本,如果是2开头说明当前使用的是vue-cli2,3开头的话就是vue-cli4
vue --version
如果无法识别vue命令说明没有安装vue-cli,使用以下说明进行安装
npm install -g vue-cli
如果是旧项目2.0版本到3.0切换得同学,即卸载当前版本,安装另外的版本
npm uninstall -g vue-cli
npm install -g @vue/cli
从3.0降到2.0:
npm uninstall -g @vue/cli
npm install -g vue-cli
vue2项目初始化参数介绍
vue init webpack cli2-test
//项目名称
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项目初始化参数介绍
vue create cli3-test
//选择一个配置方式
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?
3.目录结构
vue2
vue3
vue-cli2.0与3.0在目录结构方面,有明显的不同 vue-cli3.0移除了配置文件目录,config 和 build 文件夹 同时移除了 static 静态文件夹,新增了 public 文件夹,打开层级目录还会发现, index.html 移动到 public 中
3.0 config文件已经被移除,但是多了env.production和env.development文件,除了文件位置,实际配置起来和2.0没什么不同
没了config文件,跨域需要配置域名时,从config/index.js 挪到了vue.config.js中,配置方法不变 移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中 在 src 文件夹中新增了 views 文件夹,用于分类 视图组件 和 公共组件
4.vue3.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: {}
5.setup()函数
(1)setup函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数 也就说在 setup函数中是无法 使用 data 和 methods 中的数据和方法的
(2)setup函数是 Composition API(组合API)的入口
(3)在setup函数中定义的变量和方法最后都是需要 return 出去的 不然无法再模板中使用 注意: (1)由于在执行 setup函数的时候,还没有执行 Created 生命周期方法,所以在 setup 函数中,无法使用 data 和 methods 的变量和方法
(2)由于我们不能在 setup函数中使用 data 和 methods,所以 Vue 为了避免我们错误的使用,直接将 setup函数中的this修改成了 undefined
(3)setup函数只能是同步的不能是异步的
变动
在vue2中
我们需要在 props 里面设置接收参数
我们需要在 data 里面设置变量
我们需要在 computed 里面设置计算属性
我们需要在 watch 里面设置监听属性
我们需要在 methods 里面设置事件方法
你会发现 Options APi 都约定了我们该在哪个位置做什么事,这反倒在一定程度上也强制我们进行了代码分割。
现在用 Composition API,不再这么约定了,于是乎,代码组织非常灵活,我们的控制代码写在 setup 里面即可。
setup函数提供了两个参数 props和context,重要的是在setup函数里没有了this,在 vue3.0 中,访问他们变成以下形式: this.xxx => context.xxx
1.props参数
setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
但是,因为 props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作:
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
如果 title 是可选的 prop,则传入的 props 中可能没有 title 。在这种情况下,toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef 替代它:
import { toRef } from 'vue'
setup(props) {
const title = toRef(props, 'title')
console.log(title.value)
}
2.context参数
传递给 setup 函数的第二个参数是 context。context 是一个普通 JavaScript 对象,暴露了其它可能在 setup 中有用的值:
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}
context 是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对 context 使用 ES6 解构。
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
3.ref,reactive----data, methods
<template>
<div id="setup">
{{name}}
<p>{{age}}</p>
<button @click="add()">+</button>
<br>
<p>{{admin}}</p>
{{pass}}
</div>
</template>
<script>
import {ref,reactive,toRefs} from 'vue'
export default {
name:'setup',
setup(){
const name =ref('小四')
const age=ref(7)
function add(){
age.value++ //想改变值或获取值 必须.value
}
const testmr = reactive({
admin: '小小鸭',
pass: 20
});
return { //必须返回 模板中才能使用
name,age,add,...toRefs( testmr )
}
}
}
</script>
4.watch
watch 第一个参数可以传递参数,也可以传递函数
例:第一个参数为传递参数
//点击按钮 输出state,改变
<template>
<button @click="bianhua">数字: {{ state.count }}</button>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
setup () {
let state = reactive({count: 0})
let bianhua= () => state.count++;
watch(state, () => {
console.log(state, '改变')
})
return { state, bianhua}
}
}
</script>
例:第一个参数为函数
//点击按钮 输出旧的 新的数字 ,改变
<template>
<button @click="change">count is: {{ state.count }}</button>
</template>
<script>
import { reactive, watch } from 'vue'
export default {
setup () {
let state = reactive({count: 0})
let change = () => state.count++;
watch(() => state.count, (oldVlaue, newValue) => {
console.log(oldVlaue, newValue, '改变')
})
return { state, change }
}
}
</script>
6.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 里面直接书写函数即可。
6.对文件的引用
- Vue2.x中new出的实例对象,所有的东西都在这个vue对象上,这样其实无论你用到还是没用到,都会跑一遍。
- vue3.0中可以用ES module imports按需引入,如:keep-alive内置组件、v-model指令,等等
7.语法方面
(1)v-model语法糖废弃,改用 modelValue
<input v-model="value" />
<input modelValue="value" />
(2)弃用全局API new Vue ,使用 createApp
const app = Vue.createApp({})
(3)弃用Vue.prototype,在Vue3中,我们可以使用如下定义方式
const app = Vue.createApp({})
app.config.globalProperties.$http = () => {}
(4)全局方法现在全部在app实例上
`app.directive`,`app.use`等
(5)现在你需要手动挂载根节点
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
(6)不能再使用Vue.nextTick/this.$nextTick,Vue3中你可以用
import { nextTick } from 'vue'
nextTick(() => {
// something
})
(7)Vue3允许template设置key。
(8)正式弃用scopedSlots正式弃用,旧的不去新的不来。
(9)监听数组变化需要使用deep属性,否则只能监听到整个数组被替换。
(10)弃用ref
(11)filter被移除,我X,不能再使用|了。
(12)移除事件API,once,$off不再使用。EventBus方法也不再使用。
8.更精准的变更通知
2.x 版本中,使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行
3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。