vue2与vue3的区别简介
1.api的区别
vue2 逻辑比较分散 可读性差 可维护性差 api被默认全部引入( Tree shaking) vue3 逻辑分明 可维护性 高 api按需引入( Tree shaking没用到就不会给你打包减少体积)
2.双向绑定原理的区别
vue2基于Object.defineProperty():
原理:备份数据再循环遍历数据。
缺点:监听不到数组length的改变和对象动态属性的改变和删除属性(在creacted未定义之后的改变,使用$set)
vue3基于Proxy
let proxyObj = new Proxy(obj,{
get : function (target,prop) {
return prop in target ? target[prop] : 0
},
set : function (target,prop,value) {
target[prop] = 888;
}
3.dom更新的区别
Vue2中,每次更新diff,都是全量对比
Vue3则只对比带有标记的( patch flag(补丁标记)),这样大大减少了非动态内容的对比消耗
4.vue3 api的写法
Setup 函数式编程 也叫vue Hook
例如 ref reactive watch computed toRefs toRaws
5.Fragment
vue2中只能有一个根节点
vue3中允许template的多个根节点,且支持render jsx的写法,类似react
vue3的具体使用
创建项目
1. vite脚手架
#使用 NPM:
npm init vite@latest
#yarn 【推荐】
yarn create vite
#pnpm
pnpm create vite
......配置项目名字等....
cd vue3-demo
yarn
code . //项目自动在vscode中打开
yarn dev
显示ip的运行地址,默认不显示ip
重新启动项目后
2.create-vue脚手架
快捷安装vue3+vite的工具,还有中文选择需要的模块,不再手动安装vite
使用条件:
1. 已安装 18.3 或更高版本的 Node.js :node -v
2. 安装vue3最新版本:
npm list vue (查看vue版本)
npm create vue@latest
vscode 推荐插件
- 禁用 vetur插件(vue2使用)
-
vscode 插件推荐 Vue Language Features (Volar)(已经停止维护:vue - official替代) -
vue3-snippets-for-vscode
项目的结构搭建
安装插件后的快捷键快速创建自定义组件的模板结构 - v3 -------> v3p(volar)
- vinit(vue3-snippets-for-vscoder)
scrips标签的两种写法
代码的编写
ref 数据响应
用于基本数据的拦截,或者获取dom连接
- 基本数据类型绑定
- 创建 RefImpl 响应式 Proxy 对象 ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持
- 定义数据之后,模板里面直接使用值
- 修改数据 用.value 属性修改
- 响应式状态需要明确使用响应式 APIs 来创建。和从 setup() 函数中返回值一样,ref 值在模板中使用的时候会自动解包
-
<template> {{变量名}} <button @click='change'>点击我</button> //注意:事件内容写在html中时,变量名已经是一个字符串,因此无.value!!!! <button @click='变量名=新值'>点击我</button> </template> #0、引入ref 函数(快捷键入:ref) import { ref } from 'vue' #1、创建响应式数据 const 变量名 = ref(初始值) #2、修改响应式数据 注意别修改原数据 const change = ()=> { 变量名.value = 修改之后的值 } function reverseMessage() { // 通过其 .value 属性 // 访问/修改一个 ref 的值。 message.value = message.value.split('').reverse().join('') } #3、创建dom 绑定必须变量名 相同 const 变量名 = ref(null)
更改变量的值
-
//普通值:a的值不是响应式的,所有更改后不会更新视图 const changeA=()=>{ a = 'AAAAA' console.log(' a : ', a ); } //ref拦截的值:.value const changeB = ()=>{ //只有ref创建对象中的value值是被拦截的,不是整个对象 /*注意我们不需要在模板中写 .value, 因为在模板中 ref 会自动“解包”。*/ b.value = 'BBBBBBB' }
获取dom
-
reactive 数据响应
-
作用: 引用类型的数据响应定义
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
-
<template> {{state.属性1}} </template> <script setup> #引入reactive 函数 import { reactive } from 'vue' #创建响应式数据:通过proxy代理进行数据的劫持 const state = reactive({ 属性1:值1, }) #修改响应式数据 const state= ()=>{ state.name = '小狼' state.k1.k2 = 999 } </script>
toRef
作用:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref 。
即,把对象中的属性解构出来,不用再对象.属性名
-
<div>解构对象属性:{{age}}</div> //0、引入toRef import { reactive, toRef } from 'vue'; const obj = reactive({ name:'reactive', age:'12', son:{name:'vue'} }) // 2、解构对象属性,直接使用属性名 const age = toRef(obj,'age') console.log('age: ', age); console.log('obj: ', obj.age); // 3、更改值 function changeToRef(){ //也会更改其他使用age的值 age.value = 222 }
refs
-
批量解构出对象的属性
-
//引入refs import {toRefs } from 'vue'; #创建响应式数据 const state = reactive({ name: '小樱', age: 11, hobs: ['唱歌', '跳舞'], hands: { left: 100 } }) #响应的ref数据改变会影响原代理对象 const { name, age, hobs, hands } = toRefs(state) const change = () => { name.value = '小狼' age.value = 12 hobs.value[0] = '洗澡澡' hands.value.left = 200 }
computed
-
当你需要依赖现有的响应式数据,根据一定逻辑得到一个新的数据
- 与computed配置功能一致
- 只有getter
- 有getter和setter
-
vue2写法:
-
computed:{ sum(){} ----- //sum :function(){} } computed:{ sum:{ ------//sum:{ } get:()=>{}, set:()=>{}, } }
vue3写法:
-
let a = ref(12) let b = ref(13) const sum = computed(() => { //----sum :function(){} return a.value + b.value//25 }) const reduce = computed({ //----sum : { } get() { return a.value - b.value }, set() { return a.value - b.value }, })
watch
- 与watch配置功能一致
- 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
- 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
- 通过配置deep为true, 来指定深度监视
-
vue2的写法:注意:watch中不能使用箭头函数
-
watch:{ sum(){}//----sum :function(){} } watch:{ sum/'form.newPwd':{//----sum : { } hander(){}, deep:true } }
vue3
-
// 1.监听单个 基本数据 watch(name, (va1, val2) => { console.log('监听单个 基本数据', va1, val2) }) // 2.监听单个 复合数据 watch(obj,//()=>JSON.parse(JSON.stringify(obj)) (va1, val2) => { console.log('参数: obj', va1, val2) /*//新旧值相同,这是因为它们的引用指向同一个对象/数组 Proxy(Object) {age: '19', sex: '女', love: '小狗', ho: '小猫', s111: '1111', …}, Proxy(Object) {age: '19', sex: '女', love: '小狗', ho: '小猫', s111: '1111', …} */ // console.log('参数:()=>JSON.parse(JSON.stringify(obj))', va1, val2); /* {age: '19', sex: '女', love: '小狗', ho: '小猫', s111: '1111', …} {age: '18', sex: '女', love: '小狗', ho: '小猫', s111: '1111', …} */ }, ) // 3.监听多个数据 watch([name, ho], ([va1, v1], [va2, v2]) => { console.log(' 监听多个数据1', va1, va2)//vvvvv vue3 console.log(' 监听多个数据2', v1, v2)//小猫 小猫 }) watch([name, ho], (v1, v2) => { console.log(' 监听多个数据', v1, v2) //(2) ['vvvvv', '小猫'](2) ['vue3', '小猫'] }) // 4.监听单个 对象属性 watch(() => obj.age, (va1, val2) => { console.log(' 监听单个 对象属性', va1, val2) }) /*5.watchEffect: - 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据 - 默认初始时就会执行第一次, 从而可以收集需要监视的数据 - 监视数据发生变化时回调 */ watchEffect(() => { console.log('watchEffect观察 :>> ', name.value); console.log('watchEffect观察 :>> ', obj.age); })
组件传值
-
组件的引入与使用
快捷引入:直接键入chil(父子组件放在同一层级)
-
<template> <h1>props:父传子(父组件)</h1> //2.组件的使用(组件名为两个首字母大写,使用时可以使用-隔开) <ChildVue></ChildVue> <!--另外种使用方法: <child-vue></child-vue> --> </template> <script setup> //1.引入组件,不用componnents注册了 import ChildVue from "./Child.vue"; </script>
1.父传子
- 父组件传值:同vue2,使用 :变量名1='变量名2'
-
<ChildItemVue :toChild="toChild"></ChildItemVue> <ChildItemVue :tochildReact="tochildReact"></ChildItemVue> <script setup> import { reactive, ref } from "vue"; import ChildItemVue from "./ChildItem.vue";//child快捷键,同级时 //------- 父传子的值---- const toChild = ref('child,吃饭没得?饿不饿?') const tochildReact = reactive({ text: '渴了没得?喝奶茶不?' }) </script>
-
子组件取值:defineProps({ })//新版本不用import,已经内置,快捷键define
-
vue2写法
-
-
props:['toChild'], props:{ toChild:{ typeof: String, default: '父组件没有传值' } }
vue3写法
-
defineProps({ // toChild: String, toChild: { typeof: String, default: '莫得' }, tochildReact: { typeof: () => { return {} }, default: { text: '莫得' } } })
-
-
2.子传父
setup函数中没有this,因此没有this.$emit,需要使用defineEmits函数注册类似$emit的函数
vue2写法
子发:事件触发this.$emit('toFather',data)
父收:@toFather="getChild"
vue3写法
子发:
const toFatherval = '想吃小龙虾~~'
const $emit = defineEmits(['toFather'])//声明并定义$emit,绑定触发事件
const toFatherClick = () => {
$emit('toFather', toFatherval)
}
父收:
const fromChild = ref('还没说')
const getChild = (data) => {
console.log('child说', data);
fromChild.value = data
}
状态机vuex, pinia
vue2: vuex3
1. 下载安装(注意版本与vue版本对应:vue2 :vuex 3; vue3: vuex 4)
yarn add vuex -S
2. 引入(vuex单独存储数据,所以store文件夹>index.js)
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex)
3. 使用
import Vue from 'vue'
import Vuex from 'vuex' Vue.use(Vuex)
//3.1 创建一个数据仓库用来存储等会的交互数据
const store = new Vuex.Store({
/**
state: {},//vuex的基本数据,用来存储变量
mutations: {},//变更新数据的方法,必须是同步的(如果需要异步使用action)
getters: {},//从基本数据(state)派生的数据,相当于state的计算属性
modules: {},//模块化vuex,可以让每一个模块拥有自己的state
actions: {}
和mutation的功能大致相同,不同之处在于
1. Action 提交的是 mutation,而不是直接变更状态。
2. Action 可以包含任意异步操作。
*/
//数据存储(存储在state中, 只有交互要传递的数据,才会放入vuex的state中!)
state: {
name: '张三',
list: [],
obj: {}
.....
}
//getters:相当于computed
getters:{
getName(state ){
return `名字为${state.name}`
}}
//改变,所有state的改变,都必须定义mutation去修改,不能在vue组件中直接改
mutations: {
changeName(state, val){
state.name = 'xxx'
}
}
// actions异步修改 state
actions:{
changeAction( state,val){
setTimeOut(()=>{
state.name=val
},1000)
}
}
})
//3.2 暴露此仓库
export default store
3.3 把仓库注入到当前项目中(main.js中)
import store from './xxxxx'
new Vue({ store, .... })
组件取值
//1.直接取值:
{{this.$store.state.list}}
{{this.$store.getters.xx}}
//2.computed取值:(因为vuex的数据无视组件层级,直接在组件计算属性中获取即可)
computed: {
list(){
return this.$store.state.list
}
}
{{ list }} || this.list
触发store函数
this.$store.commit( 'changeName','XX' ) //mutations
this.$store.dispatch( 'changeAction','XX' ) //actions
pinia
vue2,vue3都可使用,配置方式不同
如果你的应用使用的 Vue 版本低于 2.7,你还需要安装组合式 API 包:@vue/composition-api
如果你正在使用 Vue CLI,你可以试试这个个非官方插件。
本文适用于vue3
1.安装
yarn add pinia
# 或者使用 npm
npm install pinia
2.定义store文件方式一 :对象作为参数
/stores/counter.js
import { defineStore } from 'pinia'
export const useCounterStore1 = defineStore('counter1', {
//state
state: () => ({
sum: 100
}),
//getters
getters: {
doubleSum(state) {//doubleSum:()=>{}
return state.sum * 2
}
},
//actions
actions: {
addSum() {//错误写法:addSum: () => {}
console.log('actions:addSum', this.sum--);
}
}
})
/**
与 Vue 的选项式 API 类似,我们也可以传入一个带有 state、actions 与 getters 属性的 Option 对象,
你可以认为 state 是 store 的数据 (data),getters 是 store 的计算属性 (computed),而
actions 则是方法 (methods)。
*/
使用store
import { useCounterStore1 } from '@/stores/counter';
const store = useCounterStore1()
//state
console.log(store1.sum);//100
//getters
console.log(store1.doubleSum);//200
const com = computed(() => {
return store1.doubleSum
})
console.log(com.value);//200
//actions
setTimeout(() => {
store1.addSum()
console.log( store1.sum);//99
}, 2000)
2.定义store文件方式二:方法作为参数
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
//ref() 就是 state 属性
const count = ref(0)
//computed() 就是 getters
const doubleCount = computed(() => count.value * 2)
//function() 就是 actions
function increment() {
count.value++
}
//你必须在 setup store 中返回 state 的所有属性
return { count, doubleCount, increment }
})
使用store
import { useCounterStore } from '@/stores/counter';
const store = useCounterStore()
//1.访问store中的变量state
console.log('store:ref===>', store.count);//2
//使用store中的computed值
const doubleValue = computed(() => store.doubleCount)
// 调用store中的异步函数actios
setTimeout(() => {
store.increment()
}, 1000)
3. vue3的main.js引入
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
const pinia = createPinia()
const app = createApp(App)
app.use(pinia)
app.mount('#app')
4. 使用 Store的注意点
<script setup>
//1.访问store中的变量state
console.log('store', store.doubleCount);//0
//const { name, doubleCount } = store
// ❌ 这将不起作用,因为它破坏了响应性
// 这就和直接解构 `props` 一样
</script>
为了从 store 中提取属性时保持其响应性,你需要使用 storeToRefs()。它将为每一个响应式属性创建引用。当你只使用 store 的状态而不调用任何 action 时,它会非常有用。请注意,你可以直接从 store 中解构 action,因为它们也被绑定到 store 上:vue
<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>