vue3 于 2020.9.18 正式发布,2022.2.7 成为新的默认版本
项目能干啥
1. Vue2 过渡到 Vue3
两者90% 的写法完全一致,区别为:
1. Vue3的 template 支持多个根标签,vue2 不支持
2. Vue3的 主函数为 createApp(),而Vue2 的是 new Vue()
3. createApp(组件) 接收的是组件,而new Vue{template, render} 接收的是对象
4. Vue3采用组合式API,而Vue2是选项式API,在对应的属性中写对应的功能模块
组合式API 是组件的另外一个选项;是启动页面后自动执行的函数,位于
created和beforCreated钩子之前,取代了这两个钩子。在setup()函数中定义的变量和方法,要return出去,才可使用。setup可直接写在<script>标签中,称为setup语法糖。
2. JS 过渡到 TS
3. 实现 Antd / Element / iView 这样的 UI 框架
项目技术栈
Vue3 / TS / VueRouter / Vite构建工具 / Volar
与
vetur相同,volar是一个针对vue的vscode插件。安装 Volar 后,注意禁用 vetur
环境搭建 windows
- 安装Node.js稳定版、npm下载加速
npm config set registry https://registry.npmmirror.com/
npm config get registry // 查看源地址
环境搭建 Mac
- 卸载掉 Node.js,若其不是通过homebrew安装的
- 安装 Homebrew
- 安装 Node.js 终端运行
brew install node npm下载加速、安装yarn、安装编辑器 同windows
使用 Vite 搭建官网
yarn global add create-vite-app@1.18.0
cva gulu-ui-1
cd gulu-ui-1
yarn // 相当于 yarn install
yarn dev
初始化项目
<!-- 项目首页 -->
<!DOCTYPE html>
...
<body>
<!-- 容器 -->
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
// src/main.js
import {createApp} from 'vue' // 用于创建app实例的主函数 vue2 是new Vue()
import App from './App.vue'
import './index.css'
createApp(App).mount('#app') // createApp接受App组件并将其挂载到div id=app上
// app.vue
<template>
<img alt="Vue logo" src="./xx" />
<HelloWorld msg="Hello" />
</template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components:{
HelloWorld:HelloWorld // 标签名:组件引入 ,可简写为 HelloWorld
]
}
</script>
- 写一个组件
// Frank.vue
// vue 3 Snippets 插件可以快速模板输入代码
<template>
<div>我的第一个组件</div>
</template>
// App.vue 改造一下
<template>
<div>hi</div>
<Frank />
</template>
<script>
import Frank from './components/Frank.vue'
export default {
name: 'App',
components:{
Frank // 标签名:组件引入
}
}
</script>
安装并初始化 vue-router
- 用于页面切换
npm info vue-router versions // 查看版本号
yarn add vue-router@4.0.0-beta.3
- 初始化 vue-router
// 创建 History 对象 js文件重命名为 ts 将index.html 也改为 main.ts
// main.ts
import {createWebHashHistory, createRouter} from 'vue-router'
import Frank from './components/Frank.vue'
const history = createWebHashHistory()
const router = createRouter({
history:history,
routes:[
{path:'/', component:Frank} // 此处易错写为 components
]
})
const app = createApp(App)
app.use(router)
app.mount('#app')
// src/shims-vue.d.ts
// 解决TS理解.vue文件的问题
declare module '*.vue'{
import {ComponentOptions} from 'vue'
const componentOptions:ComponentOptions
export default componentOptions
}
- 指定展示组件的位置:
//App.vue
<template>
<div>hi</div>
<router-view /> // 替换掉 <Frank /> 展示的内容见路由表
</template>
<script>
export default {
name: 'App',
}
</script>
- 再添加一个组件
// Frank2.vue
<template>
Frank2
</template>
- 添加 Frank2 组件的路由表
// main.ts
import Frank2 from './components/Frank2.vue'
routes:[
{path:'/', component:Frank},
{path:'/xxx', component:Frank2}
]
- 添加 router-link 实现在页面直接跳转
// App.vue
<template>
<div> 导航栏 |
<router-link to="/">Frank</router-link> |
<router-link to="/xxx">Frank2</router-link>
<hr/>
<router-view />
<div>
</template>
<script>
export default {
name: 'App',
}
</script>
写官网首页
// src/views/Home.vue
...
<style lang="scss" scoped>
</style>
// 解决找不到 sass 模块
yarn add -D sass@1.26.10
- 封装 Topnav 导航栏
// Topnav.vue
<template>
<div class="topnav">
<div class="logo">LOGO</div>
<ul class="menu">
<li>菜单1</li>
<li>菜单2</li>
</ul>
</div>
</template>
<script lang="ts">
export default {
}
</script>
- 在首页中引入 Topnav 组件
// src/views/Home.vue
...
<template>
<div>
<Topnav/>
</div>
</template>
<script lang="ts">
import Topnav from '../components/Topnav.vue'
export default {
components:{Topnav}
}
</script>
<style lang="scss" scoped>
</style>
用依赖provide注入inject实现组件间数据通信
用vue提供的provide和inject这两个api,实现点击切换aside,点击一次LOGO显示组件列表,再点一次LOGO隐藏。具体的实现过程是,用provide标记menuVisible变量是可以被所有后代组件访问的,在后代组件中用inject可以访问到menuVisible变量。
// 01 在App中放入一个 menuVisible 的变量
const menuVisible = ref(false) // ref是个盒子,值为false
// 02 把 menuVisible 变量标记为所有后代都可以使用
provide(’xxx‘, menuVisible)
// 03 子组件获取 menuVisible 变量
const menuVisible = inject<Ref<boolean>>('xxx')
// 04 当用户点击LOGO时,切换显示隐藏列表
<div class="logo" @click="toggleMenu">LOGO</div>
const toggleMenu = ()=>{
// ref类型的变量只是容器,取值需加.value属性
menuVisible.value = !menuVisible // 取反后赋值给变量
}
return {toggleMenu}
// 05 显示的区域随着menuVisible的值变化
<aside v-if="menuVisible">
更多setup函数的介绍,详见我的另一篇博文:熬夜梳理一下Vue 3 中setup函数及周边
- 用
provide标记变量
// App.vue
<template>
<router-view />
</template>
<script lang="ts">
import {ref, provide} from vue
export default {
name: 'App',
// setup 函数是处于 生命周期函数 beforeCreate 和 Created 两个钩子函数之间的函数
// setup 中是无法 使用 data 和 methods 中的数据和方法 是 Composition API(组合API)的入口
setup(){
// 安装 Auto Import 插件,可以输入 ref 按Tab键自动引入
const menuVisible = ref(false) // false为默认值
// provide 接收两个参数,第一个是string
provide(’xxx‘, menuVisible) // set
}
}
</script>
- 用
inject访问
// Topnav.vue
<script lang="ts">
import {Ref, inject} from vue
export default {
name: 'App',
setup(){
// 01版
const menuVisible = inject('xxx') //get
// 02版 用 ts 标注类型
const menuVisible = inject<Ref<boolean>>('xxx') //get
}
}
</script>
// Doc.vue
setup(){
const menuVisible = inject<Ref<boolean>>('xxx')
return {menuVisible}
}
- 添加修改
menuVisible变量状态的事件
// Topnav.vue
...
<div class="logo" @click="toggleMenu">LOGO</div>
...
export default {
name: 'App',
setup(){
const menuVisible = inject<Ref<boolean>>('xxx')
const toggleMenu = ()=>{
// ref类型的变量只是容器,取值需加.value属性
menuVisible.value = !menuVisible // 取反后赋值给变量
}
return {toggleMenu}
}
}
- 用
v-if来显示切换结果
// Doc.vue
<Topnav />
<div class="content">
<!-- 当 menuVisible 变化时, aside标签显示内容也随之变化 -->
<aside v-if="menuVisible">
...
手机页面上的切换按钮
@media(max-width:500px){
> .menu{display:none;}
> .logo{margin:0 auto;}
}
// App.vue
setup(){
// 通过获取页面宽度来决定初始值为ture还是false
const width = document.documentElement.clientWidth;
const menuVisible = ref(width <= 500 ? false : true)
}
嵌套路由
点击组件,显示对应文档:
// main.ts
import SwitchDemo from './xxx'
routes:[
{path:'/', component:Home},
{path:'/doc', component:Doc, children:[
{path:'switch', component:SwitchDemo},
]}
]
指定路由页面显示区域:
// Doc.vue
<main>
<router-view />
</main>
路由间切换
// App.vue
setup(){
router.afterEach(()=>{
if(width<=500){
menuVisible.value = false;
}
})
}
Switch 组件封装
需求分析:开关,外观设计参考组件库;
API设计:组件怎么用 <Switch value="true"/>当value为true时;
// lib/Switch.vue 底层组件
<template>
<div> Switch 组件 </div>
</template>
// components/SwitchDemo.vue 使用组件
<template>
<div>
<Switch />
</div>
</template>
<script lang="ts">
import Switch from './xx'
export default {
components: {Switch},
</script>
- 控制初始状态、更新状态并显示切换状态
// components/SwitchDemo.vue 使用组件
<template>
<div>
<!-- 通过添加 value 属性和 input 事件让外界知道当前状态是开或关-->
<Switch :value="y" @input="y = $event"/>
<!-- value 属性控制每一次的 value 对应状态,触发事件之后更新一次-->
<!-- input 事件通过 $event 拿到最新的值,其为 emit 的第二个参数-->
<!-- emit(事件名, 事件参数)-->
</div>
</template>
<script lang="ts">
import Switch from './xx'
export default {
components: {Switch},
setup(){
const y = ref(true)
return {y}
}
</script>
- 底层组件用
props接收value - 底层组件发出
input事件,用context.emit('input', xx)即可
// lib/Switch.vue 底层组件
<template>
<button @click="toggle" :class="{checked:value}">
<span></span>
</button>
<div>{{value}}</div>
</template>
<script lang="ts">
import {ref} from 'vue'
export default {
props:{
value:Boolean
},
setup(props, context){
const toggle = ()=>{
// toggle 作用是把当前的值取反,通过input事件发给外面 对应使用组件的 @input="y = $event"
context.emit('input', !props.value)
// emit 的第二个参数 !props.value 会被当做 $event
}
return {toggle}
}
}
用props和$event实现父子组件通信
<Switch :value="y" @input="y = $event"/>
// 接收两个参数