主要内容
Vue3优点:
vite是什么:官方文档(opens new window)
vite基本使用:
创建项目 npm init vite-app 项目名称
或者 yarn create vite-app 项目名称
安装依赖 npm i
或者 yarn
启动项目 npm run dev
或者 yarn dev
npm init vite@latest
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
npm install -g @vue/cli
# OR
yarn global add @vue/cli
根实例初始化
在2.x中,通过new Vue()
的方法来初始化
import Vue from 'vue'
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
在3.x中Vue不再是一个构造函数,通过createApp
方法初始化
createApp(App).use(store).use(router).mount('#app')
也支持vue2.x选项API写法
更好的逻辑复用与代码组织
我们都因 Vue 简单易学而爱不释手,它让构建中小型应用程序变得轻而易举。但是随着 Vue 的影响力日益扩大,许多用户也开始使用 Vue 构建更大型的项目。这些项目通常是由多个开发人员组成团队,在很长一段时间内不断迭代和维护的。多年来,我们目睹了其中一些项目遇到了 Vue 当前 API 所带来的编程模型的限制。这些问题可归纳为两类:
更好的类型推导
setup
是一个新的组件选项,作为组件中使用组合API的起点。vue2.x的beforeCreate
执行。setup
函数中 this
还不是组件实例,this
此时是 undefined
setup
返回。<template>
<div class="container">
<h1 @click="say()">{{msg}}h1>
div>
template>
<script>
export default {
setup () {
console.log('setup执行了')
console.log(this)
// 定义数据和函数
const msg = 'hi vue3'
const say = () => {
console.log(msg)
}
return { msg , say}
},
beforeCreate() {
console.log('beforeCreate执行了')
console.log(this)
}
}
script>
总结: setup
组件初始化之前执行,它返回的数据和函数可在模版使用。
回顾vue2.x生命周期钩子函数:
认识vue3.0生命周期钩子函数(7)
setup
创建实例前onBeforeMount
挂载DOM前onMounted
挂载DOM后onBeforeUpdate
更新组件前onUpdated
更新组件后onBeforeUnmount
卸载销毁前onUnmounted
卸载销毁后代码演示
<template>
<div class="container">
container
div>
template>
<script>
import { onBeforeMount, onMounted } from 'vue'
export default {
setup () {
onBeforeMount(()=>{
console.log('DOM渲染前',document.querySelector('.container'))
})
onMounted(()=>{
console.log('DOM渲染后1',document.querySelector('.container'))
})
onMounted(()=>{
console.log('DOM渲染后2',document.querySelector('.container'))
})
},
}
script>
组合API的生命周期钩子有7个,可以多次使用同一个钩子,执行顺序和书写顺序相同。
生命周期可以写多遍,可以把两个功能写在两个不同的生命周期里。去实现不同的逻辑
ref或者reactive替代data中的变量
在2.x中通过组件data的方法来定义一些当前组件的数据
data() {
return {
name: 'iwen',
list: [],
}
}
在3.x中通过ref或者reactive创建响应式对象
import { ref,reactive } from "vue"
export default {
name: 'HelloWorld',
setup(){
const name = ref("iwen")
const state = reactive({
list:[]
})
return{
name,
list
}
}
}
<template>
<div>
<p>{{obj.name}}p>
<p><button @click="updateName">修改名字button>p>
div>
template>
<script>
import { reactive } from 'vue'
export default {
setup () {
// 普通数据
// const obj={
// name:'zs',
// age:18
// }
const obj=reactive({
name:'zs,我是响应式的',
age:18
})
const updateName=()=>{
console.log('updateName....');
obj.name='lisi'
}
return {obj,updateName}
}
}
script>
toRef是函数,转换响应式对象中某个属性为单独响应式数据,并且跟原来是数据的值是关联的。
如上例中,只想访问响应式对象obj中name属性,而不需要name.
通过对象解构出来的数据,不是响应式的。需要使用toRef
<template>
<div>
<p>{{ name }}p>
<p><button @click="updateName">修改button>p>
div>
template>
<script>
import { reactive, toRef } from "@vue/reactivity";
export default {
setup() {
// 定义响应式数据
const obj = reactive({
name: "zs",
age: 18,
});
// 模块中只使用name,
// 注意:从响应式数据中解构出来的数据不再是响应式的!!!
// let {name}=obj;
let name = toRef(obj, "name");
const updateName = () => {
console.log("updateName、、、、");
// toRef转换响应式数据包装成对象,value存放值的位置
name.value = "我是修改之后的lisi 啊";
};
return { name, updateName };
},
};
script>
使用场景:有一个响应式对象数据,但是模版中只需要使用其中一项数据。
toRefs是函数,转换响应式对象中所有属性为单独响应式数据,对象成为普通对象,并且值是关联的
<template>
<div>
<p>{{ name }}p>
<p>{{ age }}p>
<p><button @click="updateName">修改button>p>
div>
template>
<script>
import { reactive, toRef, toRefs } from "@vue/reactivity";
export default {
setup() {
// 定义响应式数据
const obj = reactive({
name: "zs",
age: 18,
});
const obj2={...obj};
console.log(obj2);
const obj3=toRefs(obj)
console.log(obj3);
const updateName = () => {
console.log("updateName、、、、");
obj3.name.value='改了哦、、、'
// 也可以改原来的数据,因为【值是关联的】
// obj.name='也可以改原来的数据'
};
return { ...obj3, updateName };
},
};
script>
使用场景:剥离响应式对象(解构|展开),想使用响应式对象中的多个或者所有属性做为响应式数据。
可以理解的是,用户会纠结用 ref 还是 reactive。而首先你要知道的是,这两者你都必须要了解,才能够高效地使用组合式 API。只用其中一个很可能会使你的工作无谓地复杂化,或反复地造轮子。 使用 ref 和 reactive 的区别,可以通过如何撰写标准的 JavaScript 逻辑来比较
// 风格 1: 将变量分离
let x = 0
let y = 0
function updatePosition(e) {
x = e.pageX
y = e.pageY
}
// --- 与下面的相比较 ---
// 风格 2: 单个对象
const pos = {
x: 0,
y: 0,
}
function updatePosition(e) {
pos.x = e.pageX
pos.y = e.pageY
}
总结一下,一共有两种变量风格:
定义响应式数据:
.value
.value
代码演示
<template>
<div class="container">
<div>{{name}}div>
<div>{{age}}div>
<button @click="updateName">修改数据button>
div>
template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup () {
// 1. name数据
const name = ref('ls')
console.log(name)
const updateName = () => {
name.value = 'zs'
}
// 2. age数据
const age = ref(10)
// ref常用定义简单数据类型的响应式数据
// 其实也可以定义复杂数据类型的响应式数据
// 对于数据未之的情况下 ref 是最适用的
// const data = ref(null)
// setTimeout(()=>{
// data.value = res.data
// },1000)
return {name, age, updateName}
}
}
script>
模板在解析的时候,会判断是不是ref形式创建的数据,直接取出value。所以在模板中使用的时候,省略 .value
使用场景:
定义响应式数据:
演示代码:
<template>
<div class="container">
<div>{{obj.name}}div>
<div>{{obj.age}}div>
<button @click="updateName">修改数据button>
div>
template>
<script>
import { reactive } from 'vue'
export default {
name: 'App',
setup () {
// 普通数据
// const obj = {
// name: 'ls',
// age: 18
// }
const obj = reactive({
name: 'ls',
age: 18
})
// 修改名字
const updateName = () => {
console.log('updateName')
obj.name = 'zs'
}
return { obj ,updateName}
}
}
script>
基本步骤:
落的代码:
<template>
<div class="container">
<div>坐标div>
<div>x: {{x}}div>
<div>y: {{y}}div>
<hr>
<div>{{count}} <button @click="add">累加1button>div>
div>
template>
<script>
import { onMounted, onUnmounted, reactive , ref, toRefs} from 'vue'
const useMouse = () => {
// 1. 记录鼠标坐标
// 1.1 申明一个响应式数据,他是一个对象,包含x y
const mouse = reactive({
x: 0,
y: 0
})
// 1.3 修改响应式数据
const move = (e) => {
mouse.x = e.pageX
mouse.y = e.pageY
}
// 1.2 等dom渲染完毕。去监听事件
onMounted(()=>{
document.addEventListener('mousemove', move)
})
// 1.4 组件销毁,删除事件
onUnmounted(()=>{
document.removeEventListener('mousemove', move)
})
return mouse
}
export default {
name: 'App',
setup () {
const mouse = useMouse()
// 2. 数字累加
const count = ref(0)
const add = () => {
count.value ++ //一定记得.value
}
//单纯 ...mouse 解构是不可以的,需要 toRefs,再解构
return { ...toRefs(mouse), count, add }
}
}
script>
<style scoped lang="less">style>
const useMouse = () => {
// 声明响应式数据
const mouse = reactive({
x: 0,
y: 0,
});
// 修改响应式数据
const move = (e) => {
// console.log(e.pageX);
// console.log(e.pageY);
mouse.x = e.pageX;
mouse.y = e.pageY;
};
// 等dom渲染完毕。去监听事件
onMounted(() => {
document.addEventListener("mousemove", move);
});
// 组件卸载,删除事件
onUnmounted(() => {
document.removeEventListener("mousemove", move);
});
// 一定要return 出去!!!!
return mouse;
};
export default {
setup() {
const mouse = useMouse();
// 定义响应式数据
let count = ref(0);
const add = () => {
count.value++; //一定记得.value
};
// return { mouse };
//单纯 ...mouse 解构是不可以的,需要 toRefs,再解构
return { ...toRefs(mouse), count, add };
},
};
Vue2 :
ref: 获取DOM元素;获取组件事件
Vue3: ref
Vue3 新特性:
Vue2 选项API (data,methods,computd,。。。。。)
Vue3 组合API(方法)
创建根节点:Vue 2: new Vue({}) ;Vue3 :createApp(App).use(router).use(vuex)
Vue3 Setup():起点 beforeCreate() created() ===>this
Vue 2 : data,computd 定义的数据都是响应式
Vue3 定义响应式数据: ref():基本数据类型 reactive() :复杂数据类型
ref :获取DOM 元素,组件
属性:
<h2 ref='myH2'>
h2>
const myH2=ref(null);
onMounted(()=>{
console.log(myH2)
})
reutrn{myH2}
Vue2 生命周期全部都支持
Vue3 on onBeforeMount onUnmounted Vue3的生命周期比Vue2都要早
数据代理方式:
Vue2: Object.defineproperty(obj,‘name’) ==> 会对 data, computed所有属性进行遍历代理
getter 依赖收集 [] ,setter
Vue3 Proxy 13种 ===》MDN
在2.x中
computed: {
storeData () {
return this.$store.state.storeData
},
}
在3.x中
import {computed} from 'vue'
setup(){
const storeData = computed(() => store.state.storeData)
return {
storeData
}
}
const newMoney = computed({
// 访问,
get() {
// 该函数的返回值,就是计算属性的值
return money.value * 6;
},
// 修改它的依赖的值
set(val) {
money.value = val / 6;
}
});
在2.x版本的时候代码如下
export default {
data() {
return {
counter: 0
}
},
watch: {
counter(newValue, oldValue) {
console.log('The new counter value is: ' + this.counter)
}
}
}
在3.x版本的时候代码如下
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
watch函数
定义计算属性:
watch函数,是用来定义侦听器的
监听ref定义的响应式数据
监听多个响应式数据数据
监听reactive定义的响应式数据
监听reactive定义的响应式数据,某一个属性
深度监听
默认执行
<template>
<div class="container">
<div>
<p>count的值:{{count}}p>
<button @click="add">改数据button>
div>
<hr>
<div>
<p>{{obj.name}}p>
<p>{{obj.age}}p>
<p>{{obj.brand.name}}p>
<button @click="updateName">改名字button>
<button @click="updateBrandName">改品牌名字button>
div>
div>
template>
<script>
import { reactive, ref, watch } from 'vue'
export default {
name: 'App',
setup () {
const count = ref(0)
const add = () => {
count.value++
}
// 当你需要监听数据的变化就可以使用watch
// 1. 监听一个ref数据
// watch(count, (newVal,oldVal)=>{
// console.log(newVal,oldVal)
// })
const obj = reactive({
name: 'ls',
age: 10,
brand: {
id: 1,
name: '宝马'
}
})
const updateName = () => {
obj.name = 'zs'
}
const updateBrandName = () => {
obj.brand.name = '奔驰'
}
// 2. 监听一个reactive数据
watch(obj, ()=>{
console.log('数据改变了')
})
watch(()=>obj.brand, ()=>{
console.log('brand数据改变了')
},{
deep: true,
immediate: true
})
return {count, add, obj, updateName, updateBrandName}
}
}
script>
父传子:
<template>
<div class="container">
<h1>父组件h1>
<p>{{money}}p>
<hr>
<Son :money="money" />
div>
template>
<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'App',
components: {
Son
},
// 父组件的数据传递给子组件
setup () {
const money = ref(100)
return { money }
}
}
script>
<template>
<div class="container">
<h1>子组件h1>
<p>{{money}}p>
div>
template>
<script>
import { onMounted } from 'vue'
export default {
name: 'Son',
// 子组件接收父组件数据使用props即可
props: {
money: {
type: Number,
default: 0
}
},
setup (props) {
// 获取父组件数据money
console.log(props.money)
}
}
script>
子传父
<template>
<div class="container">
<h1>父组件h1>
<p>{{money}}p>
<hr>
+ <Son :money="money" @change-money="updateMoney" />
div>
template>
<script>
import { ref } from 'vue'
import Son from './Son.vue'
export default {
name: 'App',
components: {
Son
},
// 父组件的数据传递给子组件
setup () {
const money = ref(100)
+ const updateMoney = (newMoney) => {
+ money.value = newMoney
+ }
+ return { money , updateMoney}
}
}
script>
<template>
<div class="container">
<h1>子组件h1>
<p>{{money}}p>
+ <button @click="changeMoney">花50元button>
div>
template>
<script>
import { onMounted } from 'vue'
export default {
name: 'Son',
// 子组件接收父组件数据使用props即可
props: {
money: {
type: Number,
default: 0
}
},
// props 父组件数据
// emit 从上下文context中解构出emit,触发自定义事件的函数
+ setup (props, {emit}) {
// 获取父组件数据money
console.log(props.money)
// 向父组件传值
+ const changeMoney = () => {
+ emit('change-money', 50)
+ }
+ return {changeMoney}
}
}
script>
这里要值得一说的是Provide / Inject的应用,他们在3.x中得到了增强
<template>
<div>
<provideAndInject />
div>
template>
<script>
import { provide } from "@vue/composition-api"; // 父组件引入 provide
import provideAndInject from "./components/provideAndInject"; // 引入子组件
export default {
name: "app",
components: {
provideAndInject
},
setup() {
// provide('数据名称', 要传递的数据)
provide("customVal", "我是父组件向子组件传递的值");
}
};
script>
子组件
<template>
<div>
<h3>{{ customVal }}h3>
div>
template>
<script>
// 子组件导入 inject
import { inject } from "@vue/composition-api";
export default {
setup() {
//调用 inject 函数,通过指定的数据名称,获取到父级共享的数据
const customVal = inject("customVal");
return {
customVal
};
}
};
script>
在2.x版本对store对象的引用是通过this对象
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
}
})
在3.x版本对store引用变为createStore方法
import { createApp } from 'vue'
import { createStore } from 'vuex'
// Create a new store instance.
const store = createStore({
state () {
return {
count: 0
}
},
mutations: {
increment (state) {
state.count++
}
}
})
const app = createApp({ /* your root component */ })
// Install the store instance as a plugin
app.use(store)
在组件中引用store对象
import { useStore } from "vuex"
setup(){
const store = useStore();
const storeData = computed(() =>store.state.counter)
return{
storeData
}
}
值得注意的是,我们在使用vuex大多会引用扩展运算符,如果使用扩展运算符根据目前官网vuex文档4.x版本 (opens new window),仍然需要在methods中进行处理
代码组织能力
在2.x版本中,data和methods代码过多的时候,会不容易维护,过多的属性和过多的方法无论是阅读还是维护都给开发者造成了很大的困扰。 在3.x版本中,我们可以更好组织代码,例子如下:
我们分离一个数据处理文件helloworld.js
import { ref,reactive } from "vue"
export function article(){
const currentArt = ref("测试数据");
const artLists = reactive({
name:"标题",
content:"内容"
})
function changeArt(){
currentArt.value = "新测试数据"
}
return{
currentArt,
artLists,
changeArt
}
}
我们可以在分离一个网络请求文件处理NetWork.js
import { ref,reactive } from "vue"
import axios from "axios"
export function getBanner(){
const netState = reactive({
banner:{}
})
function http(url){
axios.get(url).then(res =>{
netState.banner = res.data
})
}
return {
netState,
http
}
}
我们可以在主业务文件中如下引入:
import { article } from "./HelloWorld";
import { getBanner } from "./NetWork";
export default {
setup(props, ctx) {
const { netState, http } = getBanner();
onMounted(() => {
http("http://iwenwiki.com/api/blueberrypai/getIndexBanner.php")
});
const { currentArt, artLists, changeArt } = article();
return {
currentArt,
artLists,
changeArt,
netState
};
},
};
状态管理
Vuex是一个很棒的状态管理库,它简单易懂,且与Vue集成良好。为什么不直接使用Vuex?因为即将发布的Vue3公开了底层的响应式系统,并引入了构建应用程序的新方法。新的响应式系统功能强大,可用于共享状态管理。
你需要一个共享状态吗
在某些情况下,多个组件之间数据共享困难,以至于需要集中的状态管理。情况包括:
如果上述情况都不存在,那么很容易做决定:你不需要它。 如果存在上述情况呢?最直接的答案就是使用Vuex,这是一个经过实战检验的解决方案,而且效果不错。 如果不想添加另一个依赖项或者不想做过多的配置?那么现在就可以与Composition API一起,用新的Vue3内置的方法解决这些问题。
新的解决方案
共享状态必须符合两个标准:
响应式
Vue3对外暴露了其响应式系统的众多函数,你可以使用reactive
函数创建一个reactive
变量(也可以使用ref
函数)
import { reactive } from 'vue';
export const state = reactive({ counter: 0 });
从响应式函数返回的对象是一个Proxy
对象,它可以监听其属性的更改。当在组件模板中使用时,每当响应值发生变化时,组件就会重新渲染:
<template>
<div>{{ state.counter }}div>
<button type="button" @click="state.counter++">Incrementbutton>
template>
<script>
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({ counter: 0 });
return { state };
}
};
script>
可用性
上面的示例非常适合单个组件,但多组件共享状态就不适用了。为了克服这个问题,你可以使用Vue3的provide
与inject
:
import { reactive, provide, inject } from 'vue';
export const stateSymbol = Symbol('state');
export const createState = reactive({ counter: 0 });
export const setCounter = (num) =>{
createState.counter = num
}
export const useState = () => inject(stateSymbol);
export const provideState = () => provide(
stateSymbol,
createState
);
当你将Symbol
作为键值传递给provide
时,该值将通过inject
方法使任何组件可用。键在提供和检索时使用相同的Symbol
名称。
如上图,如果你在最上层的组件上提供值,那么它将在所有组件中都可用,或者你也可以在应用的主文件上调用provide
import { createApp } from 'vue';
import App from './App.vue';
import { stateSymbol, createState } from './store';
createApp(App).provide(stateSymbol, createState).mount('#app');
<template>
<div class="about">
<h1>This is an about page</h1>
<p>{{ state.counter }}</p>
<button @click="changeCounter">修改</button>
</div>
</template>
<script>
import { useState,setCounter } from "../store"
export default{
setup(){
function changeCounter(){
setCounter(100)
}
return{
state:useState(),
changeCounter
}
}
}
</script>
通过使用Vue3的响应式系统和依赖注入机制,我们已经从局部状态管理变为全局状态管理,本方案可以在小型应用中取代Vuex。 但是如果大型项目或者有使用Vuex的特性的情况下,这种方案就不合适了,例如:你不知道谁做的状态更改,因为状态可以直接更改,不受任何限制。 所以基于此,大型项目仍然需要使用Vuex。
在2.x中通过EventBus的方法来实现组件通信
全局事件总线机制:基于自定义事件
var EventBus = new Vue()
Vue.prototype.$EventBus = EventBus
...
this.$EventBus.$on()
this.$EventBus.$emit()
在3.x中移除了$on, $off等方法(参考rfc),而是推荐使用mitt方案来代替
import mitt from 'mitt'
const emitter = mitt()
// listen to an event
emitter.on('foo', e => console.log('foo', e) )
// fire an event
emitter.emit('foo', { a: 'b' })
安装:
npm i mitt -S
utils/mitter.js
mitt库默认导出一个函数,充当事件总线对象==》EventBus
import mitt from 'mitt'
const emitter = mitt()
export default emitter
需要的组件导入
import emitter from 'utils/mitter.js'
//触发
emitter.emit('事件名','参数')
监听的组件
import emitter from 'utils/mitter.js'
//监听
emitter.on('事件名',(val)=>{
// val
})
// 第二种写法: 第一个参数写成 *,监听所有的事件触发
emmiter.on('*',(eventType,msg)=>{
console.log(`监听到的事件类型:${eventType},参数:${msg}`);
})