创建vue3.0项目
yarn global add @vue/cli
安装脚手架版本必须大于4.5.0
升级脚手架
yarn global upgrade @vue/cli
项目2.x版本升级3.x版本
Vue add vue-next
使用vite创建新的3.x项目
1- npm init vite-app 项目名称
2- 进入项目文件,进行依赖安装 yarn
3- 启动项目yarn dev
注意:只挂载的实例,其他都没有 、 多个根标签报错是因为eslint问题,升级eslint即可
composition API
setup
- setup是composition API 的入口,没有this
- 执行时间在beforeCreate之前,比所有的生命周期都早
- 需要有返回值(对象),返回值才可以在模板中使用,但是不是响应式的
setup() {
const car = { //只是一个普通对象,数据不是响应式的
brand: "宝马",
};
return { car };
},
setup的参数
-
context:上下文对象,可以通过es6语法解构setup(props, {attrs, slots, emit})
- attrs: 获取当前组件标签上所有没有通过props接收的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
<template>
<div>
<div>son组件--{{ sonPrice }}--{{ car }}</div>
<button @click="changeColor">改颜色</button>
<hr />
<Sun></Sun>
</div>
</template>
<script>
import { inject } from "vue";
import Sun from "./Sun.vue";
export default {
components: { Sun },
props: ["car"],
setup(props, context) {
// props 获取组建中定义的props
// context-->用于提交事件emit 等
console.log(props);
const sonPrice = inject("price");
const changeColor = () => {
context.emit("changeColor", "yellow");
//-->2.x:this.$emit('changeColor','yellow')
};
return {
sonPrice,
changeColor,
};
},
};
</script>
reactive
- 接受一个普通对象(复杂类型),返回一个代理对象,实现数据响应式变化
<button @click="car.brand = '奔驰'">修改</button>
//
import { reactive } from "vue";//1-先引入reactive
export default {
setup() {
const car = reactive({//2-接受一个普通对象,返回一个代理对象
brand: "宝马",
});
return { car };
},
};
ref
-
接受一个简单数据类型,返回一个响应式的对象
- 也可以接受一个复杂类型,当接受的是复杂类型时,内部会自动调用reactive
-
这个响应式对象只有一个属性---->value
-
在script中要使用xxx.value,但是在模板中直接使用xxx,会直接解套,不需要xxx.value
<template>
<div>
<div>{{ car.brand }}</div>
<button @click="car.brand = '奔驰'">修改</button>
<div>money--{{ money }}</div> //5-在模板中不需要使用value属性
<div> 名字:{{zs.name}}</div> ref接受复杂类型
<button @click="zs.age++">{{zs.age}}</button> ref接受复杂类型
</div>
</template>
<script>
import { reactive, ref } from "vue";//1- 引入ref这个函数
export default {
setup() {
const money = ref(100); //2-接受一个简单数据类型
console.log(money.value++); //3-js中要使用value这个属性
const zs = ref({ 接受复杂类型,内部实质是调用了reactive
name:'xiaoming',
age:18
})
const car = reactive({
brand: "宝马",
});
return { car, money,zs };//4-依旧需要return出去
},
};
</script>
toRefs
- 把一个响应式对象变成普通对象,但是变化后的对象都是一个ref是响应式的
- 常规写法,将所有的数据放到state中,在使用toRefs展开运算符return
<template>
<div>
<div>{{ car.brand }}</div>
<button @click="car.brand = '奔驰'">修改</button>
<div>money--{{ money }}</div>
<button @click="money++">加钱</button>
</div>
</template>
<script>
import { reactive, toRefs } from "vue";//1-导入toRefs
export default {
setup() {
const state = reactive({ //2-将所有的数据都放在一起
money: 100,
car: {
brand: "宝马",
},
});
return {
...toRefs(state), //3- toRefs包裹state,再展开
};
},
};
</script>
readonly
- 只读---->不希望被人修改 —>用于不希望子组件修改父组件的值
<template>
<div>
<div>{{ car.brand }}</div>
<button @click="car.brand = '奔驰'">修改</button>
<div>money--{{ money }}</div>
<button @click="money++">加钱</button>
</div>
</template>
<script>
import { reactive, toRefs,ref, readonly } from "vue";//1-导入readonly
export default {
setup() {
const money = ref(100)
const state = readonly({ // 使用readonly包裹不允许修改的数据
car: {
brand: "宝马",
},
});
return {
...toRefs(state),
money:readonly(money) //也可以在这里包裹
};
},
};
</script>
computed
- 计算属性 两种写法
1- 只写一个函数,得到的是一个只读的计算属性
2- 对象形式,get函数是的得到的额属性,set是设置的,实现数据响应式变化
<template>
<div>今年的年龄:<input type="text" v-model="age" /></div>
<div>明年的年龄:<input type="text" v-model="nextAge" /></div>
<div>后年的年龄:<input type="text" v-model="nextAge2" /></div>
</template>
<script>
import { ref, computed } from "vue";//先导入computed
export default {
setup() {
const age = ref(18);
//1-传入一个函数getter,得到一个不允许修改的计算属性
const nextAge = computed(() => {
return +age.value + 1;
});
//2-传入一个对象,包括get和set,可以创建一个允许修改的计算属性
const nextAge2 = computed({
get() {
return +age.value + 2;
},
set(value) {
age.value = value - 2;
},
});
return { age, nextAge, nextAge2 };
},
};
</script>
watch
- 侦听器
<template>
<div>年龄:<input type="text" v-model="age" /></div>
<div>钱: <input type="text" v-model="money" /></div>
<div>车:{{ car.brand }}</div>
<button @click="car.brand = '奔驰'">改车名</button>
</template>
<script>
import { ref, computed, toRefs, reactive, watch } from "vue"; //1-先引入watch
export default {
setup() {
const age = ref(18);
const state = reactive({
money: 100,
car: {
brand: "宝马",
},
});
/*
watch可以有三个参数
参数1: 监听简单数据类型时,直接写属性名
监听复杂数据类型时,写成函数形式,返回值必须是监听的属性名
参数2:监听的属性发送变化时触发的函数
参数3:额外配置,监听对象的属性时 { deep:true , immediate:false },
deep:深度监听,immediate: true-->页面一开始就监听,false-->值变了才监听
*/
watch(
age, //监听简单数据类型
(value, oldvalue) => {
console.log(`age变化了${value}---${oldvalue}`);
}
);
watch(
() => state.money, //监听复杂数据类型
(value, oldvalue) => {
console.log(`state.money变化了${value}--${oldvalue}`);
}
);
watch(
() => state.car.brand,
(value, oldvalue) => {
console.log(`state.car变化了${value}--${oldvalue}`);
},
{ // 复杂类型内部对象的属性
deep: true,
immediate: false,
}
);
//监听整个state,因为是reactive元素,所以监听state不需要写函数形式
watch(
state,
(value)=>{
console.log(`state变化了--${value}`)
}
)
return { ...toRefs(state), age };
},
};
</script>
生命周期
- setup (将2.x版本中beforeCreate和created)
- onBeforeMount
- onMounted
- onBeforeUpdate
- onUpdated
- onBeforeUnmount
- onUnmounted
所有的钩子函数都在setup里面写
onMounted(()=>{
。。。
})
//写法都是回调函数的形式
组件通信
使用emit通信(父子组件通信)
//父组件中
<Son :price='price' @changePrice='changePrice' ></Son> // 4 - 注册同样的事件
<script>
import { provide, reactive, readonly, ref, toRefs } from "vue";
export default {
setup(){
let price = ref(999);
const changePrice =(val)=>{
price.value = val //5- 拿到传来的值.....
}
returun{ price,changePrice }
}
}
</script>
//Son组件中
<div>{{price}}</div>
<button @click="changePrice">改值</button>
<script>
import { provide, reactive, readonly, ref, toRefs } from "vue";
export default {
emits:{ // 3--->校验
changePrice:()=>{
// 检验逻辑 --返回值是布尔值
return true / flase
},
changeFn:null//--->不需要校验则为null
}
props:['price']
setup(props,context){ //1-提供 context
const changePrice=()=>{
context.emit('changePrice',123) //2-提交emit事件
}
returun{ changePrice }
}
}
</script>
$attrs 通信 (爷孙组件通信)
- 与2.x 不一样的地方是,中间组件不再需要注册 v-on:$listeners , 其他基本不变
//父组件中载入子组件
<Son :attrsValue="attrsValue" @changeAttrsValue="changeAttrsValue"></Son>
<script>
setup(){
let propsValues = reactive({
attrsValue: {
attrsVal: "attrsVal",
},
});
const changeAttrsValue = (val) => {
console.log(val);
};
return(){
...toRefs(propsValues),
changeAttrsValue
}
}
</script>
//子组件中载入孙组件
//在中中间组价添加 $attrs ,作为中间站
<Sun v-bind="$attrs" @changeAttrsValue="changeAttrsValue"></Sun>
<script>
import Sun from "./Sun.vue";
export default {
components: { Sun },
// props: ["attrsValue"], 若在子组件中接受的父组件传来的props,则在孙组件中将接收不到了
setup(props, context) {
const changeAttrsValue = (val) => {
console.log(val);
};
return {
changeAttrsValue,
};
},
};
</script>
//孙组件
<template>
<div>
孙组件--{{ attrsValue }}
<button @click="changeAttrsValue">改attrsValue</button>
</div>
</template>
<script>
export default {
props: ["attrsValue"],
setup(props, context) {
console.log("attrsValue", props);
const changeAttrsValue = () => {
context.emit("changeAttrsValue", "changeAttrsValue--->");
//触发子传父的事件,不论父组件还是子组件中注册了接受的事件,都将可以接受到传来的值
};
return {
changeAttrsValue,
};
},
};
</script>
依赖注入 -->跨组件通信/provide与inject
provide与inject
- 从父组件中提供数据 -->provide
- 在子(后代)组件中接受----->injecte
//父组件
<template>
<div>
<div>价钱:{{ price }}</div>
<button @click="price++">加钱</button>
<Demo></Demo>
</div>
</template>
<script>
import Demo from "./Demo1.vue";
import { ref, provide } from "vue"; //1-先引入provide
export default {
components: { Demo }, //注册组件
setup() {
const price = ref(111);
const changeMoney = () => {
price.value = parseInt(price.value) + 10;
};
provide("price", price); // 2---提供数据
provide("changeMoney", changeMoney); //3----也可以提供方法
return {
price ,
changeMoney
};
},
};
</script>
//子组件 Demo1.vue
<template>
<div>我是demo组件_{{ price }}</div>
<button @click="changeMoney">demo的按钮</button>
<Sun></Sun>
</template>
<script>
import Sun from "./Sun.vue";
import { inject } from "vue";//1-先引入inject
export default {
components: { Sun }, //注册组件
setup() {
const price = inject('price'); //获取数据
const changeMoney = inject("changeMoney");//获取方法
return {
price,
changeMoney
};
},
};
</script>
//孙组件 Sun.vue
<template>
<div>我是孙组件_{{ price }}</div>
<button @click="changeMoney">demo的按钮</button>
<Sun></Sun>
</template>
<script>
import { inject } from "vue";//1-先引入inject
export default {
components: { Sun }, //注册组件
setup() {
const price = inject('price'); //获取数据
const changeMoney = inject("changeMoney");//获取方法
return {
price,
changeMoney
};
},
};
</script>
跨组件通讯mitt.js / 兄弟组件通信—>eventBus
-
跨组件通讯mitt.js ----->vue2.x/ 兄弟组件通信—>eventBus
-
装包
npm i mitt -s
-
创建文件mitt.js
//mitt.js//1-创建文件
import mitt from 'mitt'
const emitter = mitt();
export default emitter;
- 兄弟组件1
<template>
<div>
我是子组件2
</div>
<button @click='changeMsg'>点击修改msg</button>
</template>
<script>
import { ref } from 'vue'
import emitter from '../mitt' //2--需要用到的组件引入
export default {
name: '',
setup() {
const changeMsg = () => {
emitter.emit('change-msg','123')//3-发布事件,需要传递的值
}
return {
changeMsg,
}
},
}
</script>
- 兄弟组件2
<template>
<div>
我是子组件1
<h1>{{msg}}</h1>
</div>
</template>
<script>
import { ref, onUnmounted } from 'vue'
import emitter from '../mitt'
export default {
name: '',
setup() {
//初始化
const msg = ref('hello')
const changeMsg = (val) => {
console.log(val) // '123' //---使用接收到的参数
}
// 监听事件,更新数据
emitter.on('change-msg', changeMsg) // 4---在接受的组件进行事件监听
// 显式卸载
onUnmounted(() => {
emitter.off('change-msg', changeMsg)//5--在组件销毁钩子函数中进行事件解绑
})
return {
msg,
changeMsg,
}
},
}
</script>
vue3.x配合vuex
-
安装vuex
yarn add vuex@next
-
创建store-----语法跟2.x配合vuex一样
//多模块
import login from './login.js'
import { createStore } from 'vuex' //1-引入createStore
const state = {
storeValue: {
name: 'zs11',
age: 18,
},
}
const getters = {
gettersName: (state) => state.storeValue.name,
}
const mutations = {
updateValName: (state, data) => {
console.log(data)
state.storeValue.name = data
},
updateValAge(state, data) {
state.storeValue.age = state.storeValue.age + data
},
}
const actions = {
asyncChangeAge(context, data) {
context.commit('updateValAge', data)
},
}
export default createStore({
state,
getters,
actions,
mutations:{login}
})
//login.js
const state = {
loginState: 'login模块',
}
const mutations = {
setLoginState(state, data) {
console.log(data)
state.loginState = data
},
}
export default {
namespaced: true, //2-私有空间
state,
mutations,
}
- 在main.js注册引入创建好的store进行注册
//main.js
import { createApp } from 'vue'
import App from './App.vue'
import store from './store/index.js' //1-引入store
import './index.css'
createApp(App).use(store).mount('#app') //2- .use(store) 进行注册store
- 在组件中使用----->只有得到store的写法与2.x略有区别,使用store的写法基本一样
<script>
import { useStore } from "vuex"; //1-引入store
import { computed, provide, reactive, readonly, ref, toRefs } from "vue";
export default {
setup() {
const store = useStore(); //2-得到store
const storeVal = computed(() => {
return store.state.storeValue; // 3- 使用主store的state
});
const gettersName = computed(() => {
return store.getters.gettersName; // 4- 使用主store的getters
});
const changeGetters=()=>{
store.commit('updateValName','修改后值') // 5- 使用主store的mutations的方法
}
const asyncChangeAge=()=>{
store.dispatch('asyncChangeAge',10) // 6- 使用主store的actions的方法
}
const loginVal =computed(()=>{
return store.state.login.loginState // 7- 使用主login模块的state
})
const setLoginState=()=>{
console.log(store);
store.commit('login/setLoginState','xxxx') // 8- 使用主login模块的mutations的方法
}
return {
storeVal,
gettersName,
changeGetters,
asyncChangeAge,
loginVal,
setLoginState
};
},
};
模板refs–获取dom元素,进而操作dom
<template>
<div>
<h1 ref="hRef">钩子呢函数</h1> // 3-指定ref
<Demo ref="dRef"></Demo>
</div>
</template>
<script>
import Demo from "./Demo1.vue";
import { ref, provide, onMounted } from "vue"; //1-先引入ref
export default {
components: { Demo },
setup() {
const hRef = ref(null)//2- 初始化ref
const dRef = ref(null)
onMounted(()=>{
console.log(hRef.value); // 3- 操作ref元素
console.log(dRef.value.changeMoney());//可以拿到子组件中的方法进行调用
})
const changeMoney = () => {
console.log("执行了");
price.value = parseInt(price.value) + 10;
console.log(price.value);
};
provide("changeMoney", changeMoney);
return { hRef,dRef };
},
};
</script>
vue3.x路由
- 安装
npm install vue-router@4
- 初始化router
- 2.x 的路径@ ------>3.x–> /src/view/Ids.vue ,可以写/src代表 @
import { createRouter,
createWebHistory ,//history模式
createWebHashHistory //hash模式
} from 'vue-router' // 1-引包
import { defineAsyncComponent } from "vue"; // 路由的懒加载--->性能优化,优化首屏加载速度
import Login from '/src/view/Login.vue'
import My from '/src/view/My.vue'
import Son from '/src/view/Son.vue'
import Sun from '/src/view/Sun.vue'
const routes = [
{ path: '', redirect: { path: '/login' } }, //重定向路由
{ path: '/login', component: Login },
{
path: '/my',
component: My,
children: [ //嵌套子路由
{ path: '/my', component: Son },
{ path: '/my/sun', component: Sun },
],
},
{ path: '/ids/:id',
component:defineAsyncComponent( //动态路由-->组件经过懒加载
()=> import('/src/view/Ids.vue')
)
},
]
export default createRouter({
history: createWebHistory(),//---->history模式----->路径不带#
history:createWebHashHistory(),//---->hash模式---->路径带#--->具体哪种看项目需要
routes, // !!!!----> 此处必须为routes
})
- 挂载
//main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index' //1-引入
import './index.css'
createApp(App).use(router).mount('#app') // 2-use(router)
- 页面中使用
<template>
<div>login页面</div>
<button @click="goMy">去My</button>
// 模板中使用路由与2.x语法完全一样, 例如:$route.params.id
</template>
<script>
import { useRoute, useRouter } from "vue-router"; //1-引包
export default {
setup() {
const router = useRouter();//----->类比this.$router,使用方法相同
const route = useRoute();//------->类比this.$route ,使用方法相同
console.log(route.path);
const goMy = () => {
router.push({
path: "/my", //---->跳路由
query: { //---->传递参数
name: "zs",
age: 18,
},
});
const goIds = () => {
router.push(`/ids/${123}`) //---->跳转至动态路由
};
};
return { goMy , goIds};
},
};
</script>