一、组件
如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展,但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。如果我们将一个个功能块拆分后,就可以像搭建积木一下来搭建我们的项目。1.0、SPA
SPA指的是Single Page Application,就是只有一张Web页面的应用。单页应用程序 (SPA) 是加载单个HTML 页面并在用户与应用程序交互时动态更新该页面的Web应用程序。 浏览器一开始会加载必需的HTML、CSS和JavaScript,所有的操作都在这张页面上完成,都由JavaScript来控制。因此,对单页应用来说模块化的开发和设计显得相当重要。
单页Web应用,顾名思义,就是只有一张Web页面的应用。浏览器一开始会加载必需的HTML、CSS和JavaScript,之后所有的操作都在这张页面上完成,这一切都由JavaScript来控制。因此,单页Web应用会包含大量的JavaScript代码,复杂度可想而知,模块化开发和设计的重要性不言而喻。
速度:更好的用户体验,让用户在web app感受native app的速度和流畅
MVVM:经典MVVM开发模式,前后端各负其责
ajax:重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交
路由:在URL中采用#号来作为当前视图的地址,改变#号后的参数,页面并不会重载
优点:
1.分离前后端关注点,前端负责View,后端负责Model,各司其职;
2.服务器只接口提供数据,不用展示逻辑和页面合成,提高性能;
3.同一套后端程序代码,不用修改兼容Web界面、手机;
4.用户体验好、快,内容的改变不需要重新加载整个页面
5.可以缓存较多数据,减少服务器压力
6.单页应用像网络一样,几乎随处可以访问—不像大多数的桌面应用,用户可以通过任务网络连接和适当的浏览器访问单页应用。如今,这一名单包括智能手机、平板电脑、电视、笔记本电脑和台式计算机。
缺点:
1.SEO问题
2.刚开始的时候加载可能慢很多
3.用户操作需要写逻辑,前进、后退等
4.页面复杂度提高很多,复杂逻辑难度成倍
1.1、什么是组件?
组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素, Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以是原生 HTML 元素的形式,以 is 特性扩展。
组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
这和我们嵌套 HTML 元素的方式类似,Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。Vue 同样也能很好地配合原生 Web Component。如果你想知道 Vue 组件与原生 Web Components 之间的关系,可以阅读此章节。
1.2、定义一个组件
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue
文件中,这被叫做单文件组件 (简称 SFC):
<script setup> import { ref } from 'vue' const count = ref(0) </script> <template> <button @click="count++">You clicked me {{ count }} times.</button> </template>
当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义:
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
},
template: `
<button @click="count++">
You clicked me {{ count }} times.
</button>`
// 或者 `template: '#my-template-element'`
}
这里的模板是一个内联的 JavaScript 字符串,Vue 将会在运行时编译它。你也可以使用 ID 选择器来指向一个元素 (通常是原生的 <template>
元素),Vue 将会使用其内容作为模板来源。
上面的例子中定义了一个组件,并在一个 .js
文件里默认导出了它自己,但你也可以通过具名导出在一个文件中导出多个组件。
1.3、使用组件
我们会在接下来的指引中使用 SFC 语法,无论你是否使用构建步骤,组件相关的概念都是相同的。示例一节中展示了两种场景中的组件使用情况。
要使用一个子组件,我们需要在父组件中导入它。假设我们把计数器组件放在了一个叫做 ButtonCounter.vue
的文件中,这个组件将会以默认导出的形式被暴露给外部。
<script setup>
import ButtonCounter from './ButtonCounter.vue'
</script>
<template>
<h1>Here is a child component!</h1>
<ButtonCounter />
</template>
通过 <script setup>
,导入的组件都在模板中直接可用。
当然,你也可以全局地注册一个组件,使得它在当前应用中的任何组件上都可以使用,而不需要额外再导入。关于组件的全局注册和局部注册两种方式的利弊,我们放在了组件注册这一章节中专门讨论。
组件可以被重用任意多次:
<h1>Here is a child component!</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />
你会注意到,每当点击这些按钮时,每一个组件都维护着自己的状态,是不同的 count
。这是因为每当你使用一个组件,就创建了一个新的实例。
在单文件组件中,推荐为子组件使用 PascalCase
的标签名,以此来和原生的 HTML 元素作区分。虽然原生 HTML 标签名是不区分大小写的,但 Vue 单文件组件是可以在编译中区分大小写的。我们也可以使用 />
来关闭一个标签。
如果你是直接在 DOM 中书写模板 (例如原生 <template>
元素的内容),模板的编译需要遵从浏览器中 HTML 的解析行为。在这种情况下,你应该需要使用 kebab-case
形式并显式地关闭这些组件的标签。
<!-- 如果是在 DOM 中书写该模板 -->
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
请看 DOM 模板解析注意事项了解更多细节。
1.4、传递 props
1.4.1、不使用语法糖
父组件App.vue:
<template> <Counter :data="data" title="超级计算器" /> </template> <script lang="ts"> import { reactive } from "vue"; import Counter from "./components/Counter.vue"; export default { setup() { let data = reactive([1, 2, 3]); return { data }; }, components: { Counter, }, }; </script>
子组件Counter.vue:
<template> <div> <h2>{{ title }}</h2> <button @click="n++">n的当前值为:{{ n }}</button> {{ data }} </div> </template> <script lang="ts"> import { defineComponent, ref } from "vue"; export default defineComponent({ name: "Counter", setup() { let n = ref(0); return { n }; }, props: ["title", "data"], }); </script> <style></style>
1.4.2、使用语法糖
父组件App.vue
<template> <Counter :data="data" title="超级计算器" /> </template> <script lang="ts" setup> import { reactive } from "vue"; import Counter from "./components/Counter.vue"; let data = reactive([1, 2, 3]); </script>
子组件Counter.vue
<template> <div> <h2>{{ title }}</h2> <button @click="n++">n的当前值为:{{ n }}</button> {{ data }} </div> </template> <script lang="ts" setup> import { ref } from "vue"; let n = ref(0); defineProps(["title", "data"]); </script> <style></style>
其它写法
<template> <div> <h2>{{ title }}</h2> <button @click="n++">n的当前值为:{{ n }}</button> {{ data }} </div> </template> <script lang="ts" setup> import { ref } from "vue"; let n = ref(0); //简单写法 // defineProps(["title", "data"]); //ts强类型写法 defineProps<{ title: string; data: number[]; }>(); //非ts强类型写法 defineProps({ title: { type: String, default: "" }, data: Array, }); </script> <style></style>
如果我们正在构建一个博客,我们可能需要一个表示博客文章的组件。我们希望所有的博客文章分享相同的视觉布局,但有不同的内容。要实现这样的效果自然必须向组件中传递数据,例如每篇文章标题和内容,这就会使用到 props。
Props 是一种特别的 attributes,你可以在组件上声明注册。要传递给博客文章组件一个标题,我们必须在组件的 props 列表上声明它。这里要用到 defineProps
宏:
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
</script>
<template>
<h4>{{ title }}</h4>
</template>
defineProps
是一个仅 <script setup>
中可用的编译宏命令,并不需要显式地导入。声明的 props 会自动暴露给模板。defineProps
会返回一个对象,其中包含了可以传递给组件的所有 props:
const props = defineProps(['title'])
console.log(props.title)
TypeScript 用户请参考:为组件 props 标注类型
如果你没有使用 <script setup>
,props 必须以 props
选项的方式声明,props 对象会作为 setup()
函数的第一个参数被传入:
export default {
props: ['title'],
setup(props) {
console.log(props.title)
}
}
一个组件可以有任意多的 props,默认情况下,所有 prop 都接受任意类型的值。
当一个 prop 被注册后,可以像这样以自定义 attribute 的形式传递数据给它:
<BlogPost title="My journey with Vue" />
<BlogPost title="Blogging with Vue" />
<BlogPost title="Why Vue is so fun" />
在实际应用中,我们可能在父组件中会有如下的一个博客文章数组:
const posts = ref([
{ id: 1, title: 'My journey with Vue' },
{ id: 2, title: 'Blogging with Vue' },
{ id: 3, title: 'Why Vue is so fun' }
])
这种情况下,我们可以使用 v-for
来渲染它们:
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
留意我们是如何使用 v-bind
来传递动态 prop 值的。当事先不知道要渲染的确切内容时,这一点特别有用。
以上就是目前你需要了解的关于 props 的全部了。如果你看完本章节后还想知道更多细节,我们推荐你深入阅读关于 props 的完整指引。
1.4.3、默认值与验证
(1)、不使用setup语法糖的形式
props: { // 基础类型检测 (`null` 意思是任何类型都可以) propA: Number, // 多种类型 propB: [String, Number], // 必传且是字符串 propC: { type: String, required: true }, // 数字,有默认值 propD: { type: Number, default: 100 }, // 数组/对象的默认值应当由一个工厂函数返回 propE: { type: Object, default: function () { return { message: 'hello' } } }, // 自定义验证函数 propF: { validator: function (value) { return value > 10 } } }
(2)、使用setup语法糖的形式
<template> <div> <h2>{{ title }}</h2> <button @click="clickHandler">n的当前值为:{{ n }}</button> {{ data }} </div> </template> <script lang="ts" setup> import { defineComponent, ref } from "vue"; let n = ref(0); let emit = defineEmits(["countAdd"]); function clickHandler() { n.value++; emit("countAdd", n.value); } type Props = { title: string; data: number[] }; withDefaults(defineProps<Props>(), { title: "超级计算器", data: () => [1, 2, 3], }); </script> <style></style>
1.5、监听事件
让我们继续关注我们的 <BlogPost>
组件。我们会发现有时候它需要与父组件进行交互。例如,要在此处实现 A11y 的需求,将博客文章的文字能够放大,而页面的其余部分仍使用默认字号。
在父组件中,我们可以添加一个 postFontSize
ref 来实现这个效果:
const posts = ref([
/* ... */
])
const postFontSize = ref(1)
在模板中用它来控制所有博客文章的字体大小:
<div :style="{ fontSize: postFontSize + 'em' }">
<BlogPost
v-for="post in posts"
:key="post.id"
:title="post.title"
/>
</div>
然后,给 <BlogPost>
组件添加一个按钮:
<!-- BlogPost.vue, 省略了 <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button>Enlarge text</button>
</div>
</template>
这个按钮目前还没有做任何事情,我们想要点击这个按钮来告诉父组件它应该放大所有博客文章的文字。要解决这个问题,组件实例提供了一个自定义事件系统。父组件可以通过 v-on
或 @
来选择性地监听子组件上抛的事件,就像监听原生 DOM 事件那样:
<BlogPost
...
@enlarge-text="postFontSize += 0.1"
/>
子组件可以通过调用内置的 $emit
方法,通过传入事件名称来抛出一个事件:
<!-- BlogPost.vue, 省略了 <script> -->
<template>
<div class="blog-post">
<h4>{{ title }}</h4>
<button @click="$emit('enlarge-text')">Enlarge text</button>
</div>
</template>
因为有了 @enlarge-text="postFontSize += 0.1"
的监听,父组件会接收这一事件,从而更新 postFontSize
的值。
我们可以通过 defineEmits
宏来声明需要抛出的事件:
<!-- BlogPost.vue -->
<script setup>
defineProps(['title'])
defineEmits(['enlarge-text'])
</script>
这声明了一个组件可能触发的所有事件,还可以对事件的参数进行验证。同时,这还可以让 Vue 避免将它们作为原生事件监听器隐式地应用于子组件的根元素。
和 defineProps
类似,defineEmits
仅可用于 <script setup>
之中,并且不需要导入,它返回一个等同于 $emit
方法的 emit
函数。它可以被用于在组件的 <script setup>
中抛出事件,因为此处无法直接访问 $emit
:
<script setup>
const emit = defineEmits(['enlarge-text'])
emit('enlarge-text')
</script>
TypeScript 用户请参考:为组件 emits 标注类型
如果你没有在使用 <script setup>
,你可以通过 emits
选项定义组件会抛出的事件。你可以从 setup()
函数的第二个参数,即 setup 上下文对象上访问到 emit
函数:
export default {
emits: ['enlarge-text'],
setup(props, ctx) {
ctx.emit('enlarge-text')
}
}
以上就是目前你需要了解的关于组件自定义事件的所有知识了。如果你看完本章节后还想知道更多细节,请深入阅读组件事件章节。
1.5.1、使用语法糖
Counter.vue
<template> <div> <h2>{{ title }}</h2> <button @click="clickHandler">n的当前值为:{{ n }}</button> {{ data }} </div> </template> <script lang="ts" setup> import { ref } from "vue"; let n = ref(0); defineProps<{ title: string; data: number[]; }>(); const emit = defineEmits(["countAdd"]); function clickHandler() { n.value++; emit("countAdd", n); } </script> <style></style>
App.Vue
<template> <Counter :data="data" title="超级计算器" @countAdd="countAddHandler" /> </template> <script lang="ts" setup> import { reactive } from "vue"; import Counter from "./components/Counter.vue"; let data = reactive([1, 2, 3]); function countAddHandler(n) { console.log(n); } </script>
1.5.2、不使用语法糖
Counter.vue
<template> <div> <h2>{{ title }}</h2> <button @click="clickHandler">n的当前值为:{{ n }}</button> {{ data }} </div> </template> <script lang="ts"> import { defineComponent, ref } from "vue"; export default defineComponent({ name: "Counter", setup(props, context) { let n = ref(0); function clickHandler() { n.value++; context.emit("oncounter", n.value); } return { n, clickHandler }; }, props: ["title", "data"], }); </script> <style></style>
App.vue
<template> <Counter :data="data" title="超级计算器" @oncounter="countAddHandler" /> </template> <script lang="ts" setup> import { reactive } from "vue"; import Counter from "./components/Counter.vue"; let data = reactive([1, 2, 3]); function countAddHandler(n) { console.log(n); } </script>
1.6、通过插槽来分配内容
一些情况下我们会希望能和 HTML 元素一样向组件中传递内容:
<AlertBox>
Something bad happened.
</AlertBox>
我们期望能渲染成这样:
This is an Error for Demo Purposes
Something bad happened.
这可以通过 Vue 的自定义 <slot>
元素来实现:
<template>
<div class="alert-box">
<strong>This is an Error for Demo Purposes</strong>
<slot />
</div>
</template>
<style scoped>
.alert-box {
/* ... */
}
</style>
如上所示,我们使用 <slot>
作为一个占位符,父组件传递进来的内容就会渲染在这里。
以上就是目前你需要了解的关于插槽的所有知识了。如果你看完本章节后还想知道更多细节,请深入阅读组件插槽章节。
1.7、动态组件
有些场景会需要在两个组件间来回切换,比如 Tab 界面:
上面的例子是通过 Vue 的 <component>
元素和特殊的 is
attribute 实现的:
<!-- currentTab 改变时组件也改变 -->
<component :is="tabs[currentTab]"></component>
在上面的例子中,被传给 :is
的值可以是以下几种:
- 被注册的组件名
- 导入的组件对象
你也可以使用 is
attribute 来创建一般的 HTML 元素。
当使用 <component :is="...">
来在多个组件间作切换时,被切换掉的组件会被卸载。我们可以通过 <KeepAlive>
组件强制被切换掉的组件仍然保持“存活”的状态。
1.8、DOM 模板解析注意事项
如果你想在 DOM 中直接书写 Vue 模板,Vue 则必须从 DOM 中获取模板字符串。由于浏览器的原生 HTML 解析行为限制,有一些需要注意的事项。
TIP
请注意下面讨论只适用于直接在 DOM 中编写模板的情况。如果你使用来自以下来源的字符串模板,就不需要顾虑这些限制了:
- 单文件组件
- 内联模板字符串 (例如
template: '...'
) <script type="text/x-template">
1.8.1、大小写区分
HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。这意味着当你使用 DOM 内的模板时,无论是 PascalCase 形式的组件名称、camelCase 形式的 prop 名称还是 v-on 的事件名称,都需要转换为相应等价的 kebab-case (短横线连字符) 形式:
// JavaScript 中的 camelCase
const BlogPost = {
props: ['postTitle'],
emits: ['updatePost'],
template: `
<h3>{{ postTitle }}</h3>
`
}
<!-- HTML 中的 kebab-case -->
<blog-post post-title="hello!" @update-post="onUpdatePost"></blog-post>
1.8.2、闭合标签
我们在上面的例子中已经使用过了闭合标签 (self-closing tag):
<MyComponent />
这是因为 Vue 的模板解析器支持任意标签使用 />
作为标签关闭的标志。
然而在 DOM 模板中,我们必须显式地写出关闭标签:
<my-component></my-component>
这是由于 HTML 只允许一小部分特殊的元素省略其关闭标签,最常见的就是 <input>
和 <img>
。对于其他的元素来说,如果你省略了关闭标签,原生的 HTML 解析器会认为开启的标签永远没有结束,用下面这个代码片段举例来说:
<my-component /> <!-- 我们想要在这里关闭标签... -->
<span>hello</span>
将被解析为:
<my-component>
<span>hello</span>
</my-component> <!-- 但浏览器会在这里关闭标签 -->
1.8.3、元素位置限制
某些 HTML 元素对于放在其中的元素类型有限制,例如 <ul>
,<ol>
,<table>
和 <select>
,相应的,某些元素仅在放置于特定元素中时才会显示,例如 <li>
,<tr>
和 <option>
。
这将导致在使用带有此类限制元素的组件时出现问题。例如:
<table>
<blog-post-row></blog-post-row>
</table>
自定义的组件 <blog-post-row>
将作为无效的内容被忽略,因而在最终呈现的输出中造成错误。我们可以使用特殊的 is
attribute 作为一种解决方案:
<table>
<tr is="vue:blog-post-row"></tr>
</table>
当使用在原生 HTML 元素上时,is
的值必须加上前缀 vue:
才可以被解析为一个 Vue 组件。这一点是必要的,为了避免和原生的自定义内置元素相混淆。
以上就是你需要了解的关于 DOM 模板解析的所有注意事项,同时也是 Vue 基础部分的所有内容。祝贺你!虽然还有很多需要学习的,但你可以先暂停一下,去用 Vue 做一些有趣的东西,或者研究一些示例。
完成了本页的阅读后,回顾一下你刚才所学到的知识,如果还想知道更多细节,我们推荐你继续阅读关于组件的完整指引。
二、生命周期
三、示例下载
https://gitee.com/zhangguo5/vue3_-chapter1.git
四、视频
【Vue3 + Vuex + Pinia + TypeScript + Router】 https://www.bilibili.com/video/BV1at4y1F75D?share_source=copy_web&vd_source=475a31f3c5d6353a782007cd4c638a8a
三、作业
3.1、请完成课程中的所有示例。
3.2、请定义一个vue分页组件,可以实现客户端分页功能,接收参数
参考代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>vue分页组件</title> <style> .page { font-weight: 900; height: 40px; text-align: center; color: #888; margin: 20px auto 0; background: #f2f2f2; } .pagelist { font-size: 0; background: #fff; height: 50px; line-height: 50px; } .pagelist span { font-size: 14px; } .pagelist .jump { border: 1px solid #ccc; padding: 5px 8px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; cursor: pointer; margin-left: 5px; } .pagelist .bgprimary { cursor: default; color: #fff; background: #337ab7; border-color: #337ab7; } .jumpinp input { width: 55px; height: 26px; font-size: 13px; border: 1px solid #ccc; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; text-align: center; } .ellipsis { padding: 0px 8px; } .jumppoint { margin-left: 30px; } .pagelist .gobtn { font-size: 12px; } .bgprimary { cursor: default; color: #fff; background: #337ab7; border-color: #337ab7; } .pagelist .jump.disabled{ pointer-events: none; background: #ddd; } </style> </head> <body> <div id="app"> <div> <div class="page" v-show="show"> <div class="pagelist"> <span class="jump" :class="{disabled:pstart}" @click="{current_page--}">上一页</span> <span v-show="current_page>5" class="jump" @click="jumpPage(1)">1</span> <span class="ellipsis" v-show="efont">...</span> <span class="jump" v-for="num in indexs" :class="{bgprimary:current_page==num}" @click="jumpPage(num)">{{num}}</span> <span class="ellipsis" v-show="ebehind">...</span> <span :class="{disabled:pend}" class="jump" @click="{current_page++}">下一页</span> <span v-show="current_page<pages-4" class="jump" @click="jumpPage(pages)">{{pages}}</span> <span class="jumppoint">跳转到:</span> <span class="jumpinp"><input type="text" v-model="changePage"></span> <span class="jump gobtn" @click="jumpPage(changePage)">GO</span> </div> </div> </div> </div> <script src="http://www.jq22.com/jquery/vue.min.js"></script> <script> var newlist = new Vue({ el: '#app', data: { current_page: 1, //当前页 pages: 50, //总页数 changePage:'',//跳转页 nowIndex:0 }, computed:{ show:function(){ return this.pages && this.pages !=1 }, pstart: function() { return this.current_page == 1; }, pend: function() { return this.current_page == this.pages; }, efont: function() { if (this.pages <= 7) return false; return this.current_page > 5 }, ebehind: function() { if (this.pages <= 7) return false; var nowAy = this.indexs; return nowAy[nowAy.length - 1] != this.pages; }, indexs: function() { var left = 1, right = this.pages, ar = []; if (this.pages >= 7) { if (this.current_page > 5 && this.current_page < this.pages - 4) { left = Number(this.current_page) - 3; right = Number(this.current_page) + 3; } else { if (this.current_page <= 5) { left = 1; right = 7; } else { right = this.pages; left = this.pages - 6; } } } while (left <= right) { ar.push(left); left++; } return ar; }, }, methods: { jumpPage: function(id) { this.current_page = id; }, }, }) </script> </body> </html>View Code
3.3、请完使用vue2实现图书列表与详细展示功能,效果如下:
3.4、使用Vue 组件(component)完成一个精美的日历,要求IOS , 安卓, PC 的IE9+都能运行,如下图所示:
标签:vue,title,props,Vue,生命周期,Vue3,组件,ref From: https://www.cnblogs.com/best/p/16847621.html