npm install -g @vue/cli
yarn global add @vue/cli
查看安装版本:vue --version
如果低于4.5.6可以更新一下,老版本没有创建vue3的选项
输入vue create vuedemo
会跳出三个菜单
? Please pick a preset: (Use arrow keys) //请选择预选项
> Default ([Vue 2] babel, eslint) //使用Vue2默认模板进行创建
Default (Vue 3 Preview) ([Vue 3] babel, eslint) //使用Vue3默认模板进行创建
Manually select features //手动选择(自定义)的意思
因为要使用TypeScript进行开发,所以选择第三项
? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Choose Vue version
(*) Babel
( ) TypeScript
( ) Progressive Web App (PWA) Support
( ) Router
( ) Vuex
( ) CSS Pre-processors //CSS预处理器
(*) Linter / Formatter //格式化工具
( ) Unit Testing //单元测试
( ) E2E Testing //E2E测试
这里需要多选一个TypeScript的选项(按空格),然后再按回车进入下一层
? Choose a version of Vue.js that you want to start the project with (Use arrow keys)
> 2.x
3.x (Preview)
这里要选择 3.x 的版本
Use class-style component syntax?
不使用class-style可以选n
Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? (Y/n)
使用TypeScript和Babel的形式编译 JSX的选n
? Pick a linter / formatter config: (Use arrow keys)
> ESLint with error prevention only
ESLint + Airbnb config
ESLint + Standard config
ESLint + Prettier
TSLint (deprecated)
练习一般默认就好,其他情况根据需要选择
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)
>(*) Lint on save //保存的时候进行Lint
( ) Lint and fix on commit //需要帮你进行fix(修理),这项我们不进行选择
选一
Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
选择配置文件是单独存放还是直接存放在package.json,这里选择单独存放
Save thisas a preset for future projects? (y/N)
是否保存刚才的配置,下次继续使用。根据需要选择
? Pick the package manager to use when installing dependencies:
> Use Yarn
Use NPM
如果你同时安装了npm和yarn,它还会最后询问选哪个,根据习惯选择
命令:vue ui
访问http://localhost:80
创建 → 在此处创建新项目 → 输入项目名 → 选择yarn或npm→ 下一步
选择手动
根据自己的项目来进行配置
根据需要选择是否保存这个配置
这时候终端就会开始构建项目了。出现该界面说明构建完成
|-node_modules -- 所有的项目依赖包都放在这个目录下
|-public -- 公共文件夹
---|favicon.ico -- 网站的显示图标
---|index.html -- 入口的html文件
|-src -- 源文件目录,编写的代码基本都在这个目录下
---|assets -- 放置静态文件的目录,比如logo.pn就放在这里
---|components -- Vue的组件文件,自定义的组件都会放到这
---|App.vue -- 根组件,这个在Vue2中也有
---|main.ts -- 入口文件,因为采用了TypeScript所以是ts结尾
---|shims-vue.d.ts -- 类文件(也叫定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件
|-.browserslistrc -- 在不同前端工具之间公用目标浏览器和node版本的配置文件,作用是设置兼容性
|-.eslintrc.js -- Eslint的配置文件,不用作过多介绍
|-.gitignore -- 用来配置那些文件不归git管理
|-package.json -- 命令配置和包管理文件
|-README.md -- 项目的说明文件,使用markdown语法进行编写
|-tsconfig.json -- 关于TypoScript的配置文件
|-yarn.lock -- 使用yarn后自动生成的文件,由Yarn管理,安装yarn包时的重要信息存储到yarn.lock文件中
{
//----
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
//----
}
import { createApp } from "vue"; // 引入vue文件,并导出`createApp`
import App from "./App.vue"; // 引入自定义组件,你在页面上看的东西基本都在这个组件里
createApp(App).mount("#app"); // 把App挂载到#app节点上,在public目录下的index.html找节点
App.vue
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2>hello vue3</h2>
<div>请选择</div>
</div>
<div>
<button
v-for="(item, index) in temps"
v-bind:key="index"
@click="selectTempFun(index)"
>
{{ index }} : {{ item }}
</button>
</div>
<div>你选择了【{{ selectTemp }}】</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
export default defineComponent({
name: "App",
setup() {
const temps = ref(["vue2", "vue3"]);
const selectTemp = ref("");
const selectTempFunc = (index: number) => {
selectTemp.value = temps.value[index];
};
//因为在模板中这些变量和方法都需要条用,所以需要return出去。
return {
temps,
selectTemp,
selectTempFunc,
};
},
});
</script>
上面的代码所有的变量和方法都混淆在一起,且在使用setup中要改变和读取一个值还要加上value
Reactive:是一个函数(方法),里边接受的参数是一个 Object
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2> hello vue3</h2>
<div>请选择</div>
</div>
<div>
<button
v-for="(item, index) in data.temps"
v-bind:key="index"
@click="data.selectTempFun(index)"
>
{{ index }} : {{ item }}
</button>
</div>
<div>你选择了【{{ data.selectTemp }}】</div>
</template>
<script lang="ts">
import { ref, reactive } from "vue";
export default {
name: "App",
setup() {
const data = reactive({
temps: ["vue2", "vue3"],
selectTemp: "",
selectTempFun: (index: number) => {
data.selectTemp = data.temps[index];
},
});
return {
data,
};
},
};
</script>
上面的代码定义data时没有做类型注解,而是使用了TypeScrip的类型推断,实际开发工作一般不允许出现类型推断,不严谨。
定义接口作为类型注解
interface DataProps {
temps: string[];
selectTemp: string;
selectTempFun: (index: number) => void;
}
使用
cosnt data: DataProps = ...
上面的代码每次输出变量前面都要加一个data,但是不能通过…解构对象返回
return {
…data
}
这样返回的值会失去响应式能力,使用toRef函数可以解决这个问题
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<div>
<h2> hello vue3</h2>
<div>请选择</div>
</div>
<div>
<button
v-for="(item, index) in temps"
v-bind:key="index"
@click="selectTempFun(index)"
>
{{ index }} : {{ item }}
</button>
</div>
<div>你选择了【{{selectTemp }}】</div>
</template>
<script lang="ts">
import { reactive, toRefs } from "vue";
interface DataProps {
temps: string[];
selectTemp: string;
selectTempFun: (index: number) => void;
}
export default {
name: "App",
setup() {
const data: DataProps = reactive({
temps: ["vue2", "vue3"],
selectTemp: "",
selectTempFun: (index: number) => {
data.selectTemp = data.temps[index];
},
});
const refData = toRefs(data);
return {
...refData
};
},
};
</script>
< keep-alive >组件会将数据保留在内存中
使用Vue3钩子函数需要引入
import { reactive, toRefs, onMounted, onBeforeMount, onBeforeUpdate, onUpdated} from "vue"
在beforeCreate和created之前运行的,可以用它来代替这两个钩子函数
<script lang="ts">
import { reactive, toRefs } from "vue";
interface DataProps {
temps: string[];
selectTemp: string;
selectTempFun: (index: number) => void;
}
export default {
name: "App",
setup() {
console.log("1-开始创建组件-----setup()");
const data: DataProps = reactive({
temps: ["vue2", "vue3"],
selectTemp: "",
selectTempFun: (index: number) => {
data.selectTemp = data.temps[index];
},
});
onBeforeMount(() => {
console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
});
onMounted(() => {
console.log("3-组件挂载到页面之后执行-----onMounted()");
});
onBeforeUpdate(() => {
console.log("4-组件更新之前-----onBeforeUpdate()");
});
onUpdated(() => {
console.log("5-组件更新之后-----onUpdated()");
});
const refData = toRefs(data);
return {
...refData
};
},
};
</script>
运行,浏览器效果如下
1 - 开始创建组件---- - setup();
2 - 组件挂载到页面之前执行---- - onBeforeMount();
3 - 组件挂载到页面之后执行---- - onMounted();
4 - 组件更新之前---- - onBeforeUpdate();
5 - 组件更新之后---- - onUpdated();
beforeCreate() {
console.log("1-组件创建之前-----beforeCreate()");
},
beforeMount() {
console.log("2-组件挂载到页面之前执行-----BeforeMount()");
},
mounted() {
console.log("3-组件挂载到页面之后执行-----Mounted()");
},
beforeUpdate() {
console.log("4-组件更新之前-----BeforeUpdate()");
},
updated() {
console.log("5-组件更新之后-----Updated()");
}
Vue2--------------vue3
beforeCreate -> setup()
created -> setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
activated -> onActivated
deactivated -> onDeactivated
errorCaptured -> onErrorCaptured
状态跟踪,会跟踪页面上所有响应式变量和方法的状态,也就是只要页面有update的情况就会跟踪,生成一个event对象,可以通过event对象进行调试
import { .... ,onRenderTracked,} from "vue";
在setup()函数中进行引用
onRenderTracked((event) => {
console.log("状态跟踪组件----------->");
console.log(event);
});
状态触发,不会跟踪每一个值,只跟踪变化的值
import { .... ,onRenderTriggered,} from "vue";
在setup()函数中进行引用
onRenderTriggered((event) => {
console.log("状态触发组件--------------->");
console.log(event);
});
onRenderTriggered的event属性有
key:那边变量发生了变化
newValue:更新后变量的值
oldValue:更新前变量的值
target:目前页面中的响应变量和函数
效果和watch很像
先去掉上面代码的钩子函数
watch与钩子函数、return同级
{
//.....
const finalText = ref("element");
const finalAction = () => {
finalText.value = finalText.value + "done | ";
};
return { ...refData, finalText, finalAction};
}
template模板中,调用这些变量和方法
<div><button @click="finalAction">语言选择完毕</button></div>
<div>{{ finalText }}</div>
引入watch
import {... , watch} from "vue"
watch函数有两个参数,第一个是要监听的值,第二个是个回调函数
watch(finalText, (newValue, oldValue) => {
console.log(`new--->${newValue}`);
console.log(`old--->${oldValue}`);
document.title = newValue;
});
watch([finalText, () => data.selectTemp], (newValue, oldValue) => {
console.log(`new--->${newValue}`);
console.log(`old--->${oldValue}`);
document.title = newValue[0];
});
注意,因为selectTemp是在reactive函数中的值,不能直接监听,需要用一个函数去解决(类似getter函数的效果)
这是为了保持和Vue2的一致性,Vue2中的解决方案是要么是ref或者get/set方法的形式,或者开启设置watchOptions的deep为true,也就是深度监听模式
vue2版本一般会使用mixins进行代码重用
vue3则更加简单
先清空代码
<template>
<div>
</div>
</template>
<script lang="ts">
import { ref } from "vue";
const app = {
name: "App",
setup() {
},
};
export default app;
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
实现时间功能
//....
const app = {
name: "App",
setup() {
//.....
const nowTime = ref("00:00:00");
const getNowTime = () => {
const now = new Date();
const hour = now.getHours() < 10 ? "0" + now.getHours() : now.getHours();
const minu =now.getMinutes() < 10 ? "0" + now.getMinutes() : now.getMinutes();
const sec =now.getSeconds() < 10 ? "0" + now.getSeconds() : now.getSeconds();
nowTime.value = hour + ":" + minu + ":" + sec;
setTimeout(getNowTime, 1000); //每一秒执行一次这个方法
};
//...
return {
//....
nowTime,
getNowTime,
};
},
};
export default app;
在template中使用
<div>{{ nowTime }}</div>
<div><button @click="getNowTime">显示时间</button></div>
将时间功能的逻辑抽出进行模块化
在src新建一个文件夹hooks(所有抽离出来的功能模块都放到这个文件夹里),然后新建NowTime.ts
import { ref } from "vue";
const nowTime = ref("00:00:00");
const getNowTime = () => {
const now = new Date();
const hour = now.getHours() < 10 ? "0" + now.getHours() : now.getHours();
const minu =
now.getMinutes() < 10 ? "0" + now.getMinutes() : now.getMinutes();
const sec =
now.getSeconds() < 10 ? "0" + now.getSeconds() : now.getSeconds();
nowTime.value = hour + ":" + minu + ":" + sec;
setTimeout(getNowTime, 1000);
};
export { nowTime, getNowTime }
引入并使用模块
import { nowTime, getNowTime } from "./hooks/useNowTime";
//....
return {nowTime,getNowTime};
写一个随机选择器模块
<template>
<div
<div>随机选择一只宠物</div>
</div>
</template>
<script lang="ts">
import { reactive, toRefs, ref, watch } from "vue";
const app = {
name: "App",
setup() {
return {};
},
};
export default app;
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
安装:npm install axios --save
新建文件URLAxios.ts
import { ref } from 'vue'
import axios from 'axios'
function urlAxios(url: string) {
const result = ref(null)
const loading = ref(true)
const loaded = ref(false)
const error = ref(null)
axios.get(url).then((res) => {
loading.value = false
loaded.value = true
result.value = res.data
}).catch(e => {
error.value = e
loading.value = false
})
return {
result,
loading,
loaded,
error
}
}
export default urlAxios
App.vue调用模块
<template>
<div>
<div>随机选择一只宠物</div>
<div v-if="loading">Loading.....</div>
<img v-if="loaded" :src="result.message" />
<div></div>
</div>
</template>
<script lang="ts">
import { ref } from "vue";
import urlAxios from "./hooks/urlAxios";
const app = {
name: "App",
setup() {
const { result, loading, loaded } = urlAxios(
"https://dog.ceo/api/breeds/image/random"
);
return { result, loading, loaded };
},
};
export default app;
</script>
可以把写好的vue组件放到指定的html节点下,而不是跟随vue固定在id=app的结点下
弹出组件Modal.vue
<template>
<div id="center">
<h2>gogogo</h2>
</div>
</template>
<script lang="ts">
export default {};
</script>
<style>
#center {
width: 200px;
height: 200px;
border: 2px solid black;
background: white;
position: fixed;
left: 50%;
top: 50%;
margin-left: -100px;
margin-top: -100px;
}
</style>
在vue组件中引入
//...
import modal from "./components/Modal.vue";
const app = {
name: "App",
components: {
modal,
},
//...
}
//...
运行查看页面元素,发现所有的dom都是在app节点下,如果想改变节点,在Vue2非常困难,vue3只需要使用teleport函数
在index.html增加一个节点
<div id="app"></div>
<div id="modal"></div>
修改Modal.vue,用<teleport>标签进行包裹,用to属性指向需要渲染的DOM节点
<template>
<teleport to="#modal">
<div id="center">
<h2>JSPang11</h2>
</div>
</teleport>
</template>
运行,modal组件相关的元素都是id=modal的节点下
Vue2判断异步请求的状态是一件必须的事情,但是这都要自己处理,根据请求是否完毕展示不同的界面(比如loading),在Vue3中给我们提供了Suspense组件可以更方便完成这些事。
新建Async.vue组件
<template>
<h1>{{ result }}</h1>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup() {
return new Promise((resolve, reject) => {
setTimeout(() => {
return resolve({ result: "done" });
}, 2000);
});
},
});
</script>
清空App.vue的其他代码
<template>
<div>
</div>
</template>
<script lang="ts">
const app = {
name: "App",
components: { },
setup() {
return {};
},
};
export default app;
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
引入写好的组件,并在components中声明
import Async from "./components/Async.vue";
const app = {
name: "App",
components: { AsyncShow },
setup() {
return {};
},
};
使用Suspense
<template>
<div>
<Suspense>
<template #default>
<Async />
</template>
<template #fallback>
<h1>Loading...</h1>
</template>
</Suspense>
</div>
</template>
把上面用setTimeout模拟异步请求改为使用真实的请求
新建AsyncImg.vue组件
<template>
<img :src="result && result.imgUrl" />
</template>
<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
async setup() { //promise 语法糖 返回之后也是promise对象
const rawData = await axios.get('https://apiblog.jspang.com/default/getGirl')
return {result:rawData.data}
}
})
</script>
清空App.vue的其他代码并引入AsyncImg.vue
import AsyncImg from "./components/AsyncImg.vue";
const app = {
name: "App",
components: { AsyncImg },
setup() {
return {};
},
};
模板部分
<template>
<div>
<Suspense>
<template #default>
<async-img />
</template>
<template #fallback>
<h1>Loading...</h1>
</template>
</Suspense>
</div>
</template>
在vue3.x的版本中,可以使用onErrorCaptured钩子函数来捕获异常
import { onErrorCaptured} from "vue";
用法
const app = {
name: "App",
components: { AsyncImg },
setup() {
onErrorCaptured((error) => {
console.log(`error====>`,error)
return true
})
return {};
},
};
这样,所有的请求异常都会被捕获并打印到控制台