赞
踩
vue2已经渐行渐远,vue3早已甚嚣尘上。vue3作为尤大的苦心孤诣之作,在开源世界也是响当当的存在,三分天下有其一。尤大也携名兼千万之资远赴新加坡,全职献身给vue。
AngularJS和React已经如火如荼了,那尤大为何还整个Vue干啥?首先,2013年的时间点是群雄并起,AngularJS和React尚未一统江湖。感于Angular的繁复,不接地气,尤大说,我把一个template加上一些数据和事件处理,成为一个开箱即用,领包入住的可复用组件,岂不妙哉?于是,一个学艺术的文科生撸起袖子,干了一件理科生干的事情。这一干不得了,一个伟大的前端框架就此诞生了。
vue3的口号是”渐进式的Javascript框架“,个人理解是你可以深度使用它,也可以浅度使用它,可以由浅入深地使用它。
Vue 的口号“渐进式框架”,背后就是这个过程中形成的分层 API 设计。新手可以通过 CDN script、基于 HTML 的模板以及直观的 Options API 顺利学习入门。而专家可以通过全功能的 CLI、渲染函数以及 Composition API 来处理复杂需求。
言归正传,简单回顾一下vue的几个基础概念:
vue3总体内容既多也不多。说它多,就是细节多,与外部其它前端技术关联的东西多。说不多,就是跟我们使用相关的就那么几招,下面一一道来。
一个vue组件由三部分构成:<template>, <script>和<style>,定义在一个.vue文件中。除了vue结尾的文件,通常还有.ts和.tsx文件,ts文件定义任意的typescript内容,tsx是JSX语法的typescript版。掌握jsx + typescript + composition api定义组件能大大加快大家的开发步伐。
vue3推荐采用<script setup>语法糖定义一个组件。
cool_component.vue:
<template>
<div>my name is {{username}}, my ID is {{id}}, {{sayHello('hi, nihao')}}</div>
<button @click="setname('haha')">setme</button>
</template>
<script lang="ts" setup>
import {ref, defineProps} from "vue";
const props = defineProps({id: Number, email: String});
const username = ref('Hello World!');
const sayHello = (msg) => {return `Hello: ${msg}, my id is ${props.id}, my email is ${props.email}`};
const setname = (newname) => {
username.value = newname;
}
</script>
<style scoped>
...
</style>
所谓setup语法糖就是在script标签里加了一个setup属性。使用该组件:
import CoolComponent from 'cool_component.vue'
......
<template>
<CoolComponent id=10 email='test@user.com'>...</CoolComponent>
</template>
注意,上面组件名称CoolComponent你可以随意取,和template里保持一致即可。
如果不采用setup语法糖,则写法为:
<template>
<div>my name is {{username}}, my ID is {{id}}, {{sayHello('Hi, nihao')}}</div>
<button @click="setname('haha')">setme</button>
</template>
<script lang="ts">
import {ref} from "vue";
export default {
name: "CoolComponent",
props: {
id: 0,
email: ''
},
setup(props, ctx){
const username = ref('Hello World!');
const sayHello = (msg) => {return `Hello: ${msg}`};
const setname = (newname) => {
username.value = newname;
}
return{
username,
sayHello,
setname,
}
}
};
</script>
<style scoped>
</style>
相比setup函数繁琐了不少。上面的export default {}写法也可以写成:
export default defineComponent({})
这么写更方便开发工具做类型推导,是推荐写法。当然,直接采用setup()函数定义组件更简洁。
defineComponent原型:
function defineComponent(
component: ComponentOptions | ComponentOptions['setup']
): ComponentConstructor
interface ComponentOptions {
provide?: object | ((this: ComponentPublicInstance) => object)
}
defineComponent()用多种写法。
第一种写法,在一个.tsx文件中使用:
export const MyComponent = defineComponent(() => {
const prop1 = ref();
return () => <div :prop1={editor} />;
});
注意return的内容采用了JSX的语法。
还可以:
const MyLink = defineComponent({
name: 'my-link',
setup (_, { slots }) {
const node = inject(nodeMetadata, {}).node
const href = node?.value?.attrs?.href
return () => <a target="_blank" href={href}>{slots.default?.()}</a>
}
})
这个组件的作用是可以包裹任意一个html标签,为它添加链接。
第二种写法:还可以结合render()和h()函数定义:
export const MyComponent = defineComponent({
name: 'MyComponent',
setup() {
return {
//...组件的数据
}
},
render() {
return h(
'span', // vnode的tag name
{ class: ns.e('label') }, // vnode的属性
renderLabelFn ? renderLabelFn({ node, data }) : label // 第三个参数为组件vnode的内容
)
},
})
注意h()函数的三个参数。第一个参数可以是一个已知的html标签名称,也可以是一个vue组件。第二个参数是组件的属性。第三个参数不仅仅可以是字符串, 还可以是"VNode"或两者混合。
组件可以嵌套,形成树状结构,那就涉及到如何通信和交换数据了。
父组件parent.vue:
<template>
<CoolComponent ref="coolComponentRef" id=10 email='test@user.com'></CoolComponent>
</template>
<script lang="ts" setup>
import CoolComponent from "@/components/cool_component.vue";
import {onMounted, ref} from "vue";
const coolComponentRef = ref()
onMounted(() => {
console.log(coolComponentRef.value.sayHello('OKOK'))
})
</script>
注意:CoolComponent增加了一个ref,相当于给这个组件的实例增加了一个引用,然后定义对应的变量coolComponentRef关联到它。直接运行上面父组件会报错,提示sayHello()方法找不到,这是因为我们在子组件cool_component.vue中没有暴露该方法。
需要在cool_component.vue的setup函数末尾添加一行:
defineExpose({sayHello})
子组件的数据发生变化后,需要通知到父组件,这种通信采用vue的事件机制。父组件可以通过 v-on (缩写为 @) 来监听事件。
子组件通过defineEmits申明事件:
子组件cool_component.vue:
<template>
<div>my name is {{username}}, my ID is {{id}}, {{sayHello('hi, nihao')}}</div>
<button @click="setname('haha')">setme</button>
<button @click="updateData()">updateData</button>
</template>
<script lang='ts' setup>
const emit = defineEmits(["onUpdateData"]); // 申明事件
const updateData = () => {
emit("onUpdateData", {id: 0, content: 'just a test'});
}
// ... 其它部分定义同上
</script>
父组件定义如下:
<template>
<CoolComponent ref="coolComponentRef" id=10 email='test@user.com' @onUpdateData="handleUpdate"/> </template>
<script lang='ts' setup>
function handleUpdate(data:any) {
console.log('data:', data)
}
// ... 其它部分定义同上
</script>
如果几个子组件都需要共用父组件的某个方法,可以在父组件里采用provide输出公用方法,然后子组件里inject注入使用。
父组件:
<script setup lang="ts">
function doSave() {
const content = editorRef.value.getContent()
onSave({content, contentFormat: contentFormat.value})
}
function exportPDF() {
exportToPDF(pageId.value, pageTitle.value)
}
provide('actions', { doSave, exportPDF })
</script>
子组件:
<script setup lang="ts">
import { inject } from "vue";
const onSave= inject("actions").doSave;
const exportPDF = inject("actions").exportPDF;
</script>
<template>
<el-menu-item index="1" @click="exportPDF">导出为PDF</el-menu-item>
<el-menu-item index="1" @click="onSave">保存文章</el-menu-item>
</template>
父组件里获得数据,更新子组件的属性,然后子组件采用watch()函数,变化后刷新自己:
<script setup lang="ts">
const content = ref()
const contentFormat = ref()
function doSomething() {
content.value = 'anything...'
contentFormat = 'html'
}
</script>
<template>
<CoolEditor ref="editorRef" :content="content" :contentFormat="contentFormat" @on-save="onSave"
</CoolEditor>
</template>
在子组件cool_editor.vue里:
<script setup lang="ts">
function refresh(content, format) {
contentStr.value = content
contentFormat.value = format
if (format =='html') {
htmlEditorRef.value.setValue(content)
}
if (format =='md') {
mdEditorRef.value.setValue(content)
}
}
watch(
() => [props.content, props.contentFormat],
(val) => {
let format = val[1] || 'html'
refresh(val[0], format)
}
)
</script>
vue里有很多变量是和数据绑定的,数据变,变量的值也变。例如:
<div v-for="key in keys">
<el-input v-model="'model_' + key" :ref="'input-'+ key">
</div>
可以采用$refs[‘input-’+key]访问每个el-input元素。
到上面为止,我们可以轻松地写一个自己的组件,也能把多个组件复合在一起,形成一个更大的组件。但是,要在页面上显示我们的组件,通常离不开路由和Layout这两样东西。这个路由器是vue之外的一个单独组件完成的,名称为vue-router。
下面是一个路由定义的文件:
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
import HomePage from "@/components/home.vue";
import SimpleLayout from "@/layout/SimpleLayout.vue";
import UserList from "@/components/users.vue";
import AdminConsole from "@/components/admin.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'home',
component: HomeView
},
{
path: '/about',
name: 'about',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/AboutView.vue')
},
{
path: "/home",
component: HomePage
},
{
path: "/simplelayout",
component: SimpleLayout,
children: [
{
path: "/simplelayout/view1",
component: UserList,
}
],
},
{
path: "/admin",
component: AdminConsole,
children: [
{
path: "/admin/users",
component: UserList,
}
],
}
]
})
export default router
每个路由将path和component关联在一起,这样我们前端做点击进行导航时,相应的区域就能渲染对应的组件。这个区域可以通过router-view定义,可以是app实例的挂载点。如果我们通过router-link进行跳转,则对应的渲染区域是app实例挂载点。
大家看到,上面的定义了多条路由,每条路由可以通过children指定下级路由,下级路由对应的渲染区域是父级路由的component属性指定的vue组件。即:
import Layout from '/xxx/xxx/layout.vue'
...
path: "/admin",
component: Layout, //父级路由的component指定了一个vue组件,这个vue组件就是Layout组件
children: [
{
path: "/admin/users",
component: UserList,
}
],
上面layout.vue的通常定义形式如下:
<template>
<div>simple layout</div>
<router-view>
<template #default="{ Component, route }">
<component
:is="Component"
:key="route.fullPath"
/>
</template>
</router-view>
</template>
<script>
export default {
name: "Layout"
}
</script>
<style scoped>
</style>
大家看到,这里采用了router-view这个组件,这是vue-router提供的。里面定义的template部分即是被渲染的区域,这里又用到component这个组件,这是vue内置的,是用来动态显示的,:is="Component"这里的Component即是子路由传入的vue组件。看上去有一点魔法,但也无需深究,这是框架作者要完成的事。
之前的版本采用vuex管理状态,现在vue团队又提供了一个新的选项:pinia。关于其用法,后面笔者会补充。
vue3提供了一组组合式API (Composition API) ,它是一系列API 的集合,使我们可以使用函数而不是声明选项的方式书写Vue 组件。 它是一个概括性的术语,涵盖了以下方面的API: 响应式API:例如 ref() 和 reactive() ,使我们可以直接创建响应式状态、计算属性和侦听器。所谓响应式数据,就是当数据发生变化时,界面也跟着变。谓之数据和视图绑定。
目前vue3+vite+ts+pinia基本成了标配,整个生态越来越完善。
nuxt3: 一款基于Vue3的混合开发框架。
vite: 前端开发与构建工具.
vueuse: 针对vue3 composition api的工具库。
vue-router4
pinia、vuex4
element plus、ant design vue、quasar、vuetify3beta、naive ui、prime vue,View UI Plus,
tdesign
ionic、vant、varlet、nutui、waveui
本文简明扼要地介绍了vue3的最基本内容,帮助大家快速起步和消化vue3的关键内容。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。