1. 前端框架的作用-组件化开发
像python flask, golang gin一样,前端框架是规范了开发模式的js库,规范主要包括
- 组件化
- 建立数据和视图的关系
当使用前端框架开发,实际上是在写【组件】
前端开发核心理念:组件化开发。一个组件就是python/java中的class
,golang中的struct
2. 写vue是在写什么
我们用vue框架写前端应用就是在写一个个的Vue组件
一个Vue组件就是一个.vue
文件(SFC, Single-File Component, a.k.a. *.vue
file),定义了一个视图(by template section),样式(by style section)以及这个视图对应的数据和业务逻辑(by script section)
- 定义component(编写.vue文件) 就像定义一个 python class / golang type
- 在一个 component A 里使用另一个 component B,等于 A 使用
B class/type
创建一个实例对象- 一个component重要的内部属性(不对外暴露)有:data method watch computed
使用Vue开发应用,先要定义好组件(一般来说,App.vue是整个组件树的root),然后调用Vue的createApp
方法,传入root component,即可将所有组件渲染
2.1 start a Vite-powered Vue project in vue3
npm init vue@latest
This command will install and execute create-vue
, the official Vue project scaffolding tool. Follow the prompt and you will easily get a Vite-powered Vue project.
refer to https://vuejs.org/guide/quick-start.html#creating-a-vue-application
2.2 Vue project 的 3个核心文件
index.html
,main.js
和App.vue
是Vue.js应用程序的基本文件
index.html
是Vue应用程序的入口页面。在这个页面中,Vue应用程序的JavaScript和CSS文件通常被加载和引用,同时该文件指定Vue根DOM元素,使用Vue控制的DOM将被插入到该元素内,例如<div id="app"></div>
。
main.js
是Vue应用程序的主入口文件。在这个文件中,Vue实例被创建且挂载到指定的DOM元素上。在这个文件中通常还包括一些全局配置,如路由配置和Vuex Store配置。
App.vue
是Vue应用程序中的主要组件,在这个文件中定义了Vue应用程序的视图层部分。它是一个组合了HTML、CSS和JavaScript的Vue单文件组件,用于构建页面和交互逻辑。
App.vue
文件由三部分组成:<template>
(模板)、<script>
(脚本)和<style>
(样式)。模板部分定义了组件的结构和渲染内容,脚本部分实现了组件的功能和交互逻辑,样式部分定义了组件的样式。它们组合起来形成了一个完整的Vue组件,可以重复使用和组合形成更为复杂的应用。
3. SFC in vue
3.1 definition of SFC
SFC(Single-File Component, a.k.a. *.vue
file) is a special file format that allows us to encapsulate the 1. template, 2. logic, and 3. styling of a Vue component
in a single file.
SFC是定义vue component的.vue
文件,一个SFC定义一个vue component。一个组件的定义由模版、组件逻辑和组件样式3部分组成
Here's a basic example SFC, in Composition API style:
<!-- Each *.vue file can contain at most one <script> block (excluding <script setup>) -->
<script setup>
// ref 是 vue 提供的一种响应式引用类型,本质上和string/number一样,都是数据类型。【类似golang的指针】
// 如下创建了字符串'Hello World!'的响应式引用,并赋值给greeting
import { ref } from 'vue'
const greeting = ref('Hello World!')
</script>
<template>
<p class="greeting_style">{{ greeting }}</p>
</template>
<style>
.greeting_style {
color: red;
font-weight: bold;
}
</style>
As we can see, Vue SFC is a natural extension of the classic trio of HTML, CSS and JavaScript.
3.2 three blocks in SFC
The <template>
, <script>
, and <style>
blocks encapsulate and colocate the view, logic and styling of a component in the same file. The full syntax is defined in the SFC Syntax Specification.
- script部分,定义的是控制视图的逻辑
- template部分,定义的是无逻辑的视图本身(比如什么位置展示什么数据,负责界面和显示)
- style部分,定义的是视图的样式
3.3 script setup
3.3.1 how script setup work
The code inside <script setup>
is compiled as the content of this component's setup()
function.
<script setup>
will execute every time an instance of the component is created, like __init__
in python
<script setup>
中的顶层的导入和变量声明可在同一组件的模板(template)中直接使用。可以理解为模板中的表达式和 <script setup>
中的代码处在同一个作用域中
在添加了setup
的script标签中,我们不必声明和方法,这种写法会自动将所有顶级变量、函数,均会自动暴露给模板(template
)使用
With
<script setup>
, we don't need named exports or default exports in SFCs anymore
we can simply define variables and use them in the template.
top-level bindings inside<script setup>
are exposed directly to template
<script setup>
// variable
const msg = 'Hello!'
// functions
function log() {
console.log(msg)
}
// imports
import { capitalize } from './helpers'
</script>
<template>
<button @click="log">{{ msg }}</button>
<div>{{ capitalize('hello') }}</div>
</template>
这里强调一句 “暴露给【当前template】,跟暴露给外部不是一回事”. 暴露给【当前template】的变量和函数类似当前组件的私有方法和私有属性,而暴露给外部的变量和函数类似当前组件的公开方法和公开属性
To explicitly expose properties in a <script setup>
component, use the defineExpose
compiler macro:
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
When a parent gets an instance of this component via template refs, the retrieved instance will be of the shape { a: number, b: number }
(refs are automatically unwrapped just like on normal instances).
3.3.2 what to defined in script setup
在setup部分内的函数可以有两种返回值:
若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
若返回一个渲染函数:则可以自定义渲染内容。(了解)
注意:setup本身不能是async函数,如果 setup 函数是一个异步函数,那么它将返回一个 Promise 对象,而不是一个普通对象。由于 Vue 的模板引擎不能处理 Promise 对象,所以在模板中就无法访问到返回的对象中的属性。
例如,以下代码将无法正常工作:
export default {
setup: async () => {
const data = await fetchData()
return {
data
}
}
}
在这个例子中,setup 函数返回的是一个 Promise 对象,而不是一个包含 data 属性的对象,所以在模板中无法访问到 data。
如果你需要在 setup 函数中使用异步操作,你可以使用 async/await 结合 ref 或 reactive 来实现:
import { ref } from 'vue'
export default {
setup() {
const data = ref(null)
const fetchData = async () => {
data.value = await fetchData()
}
fetchData()
return {
data
}
}
}
3.4 template
3.4.1 template是什么
所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析
在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码
3.4.2 template作用是什么
Templates should only be responsible for mapping the state to the UI.
说白了就是设计页面组件
3.4.3 template访问范围
模板中的表达式
将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math
和 Date
,没有显式包含在列表中的全局对象将不能在模板内表达式中访问,可以自行在 app.config.globalProperties
上显式地添加它们,供所有的 Vue 表达式使用
3.4.4 template中的指令(vue directive)
指令是由 Vue 提供的、带有 v-
前缀的特殊 attribute
指令的期望值均为一个 JavaScript 表达式!!!! (除了少数几个例外,即之后要讨论到的 v-for
、v-on
和 v-slot
)
一个指令的任务是在其表达式的值变化时响应式地更新 DOM
某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind
指令来响应式地更新一个 HTML attribute:
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>
这里 href
就是一个参数,它告诉 v-bind
指令将表达式 url
的值绑定到元素的 href
attribute 上
指令的样例如下图所示
vue built-in directives:
- v-text
- v-html
- v-show
- v-if
- v-else
- v-else-if
- v-for
- v-on
- v-bind
- v-model
- v-slot
- v-pre
- v-once
- v-memo
- v-cloak
在模版中
-
文本绑定 使用
{{ componentInstance.xxxattr }}
. 如果这个componentInstance是ref
类型,则当componentInstance的value发生变化时,页面内容也会响应式变化 -
属性绑定 使用指令
v-bind
. 如果绑定的value是 null 或者 undefined,那么该 attribute 将会从渲染的元素上移除.v-bind
指令需要一个attribute“参数”,在指令名后通过一个冒号隔开做标识,指定绑定value到哪个attribute
To make it easier to learn, let call the component <child>
, the component's prop foo
and the property we are binding bar
.
With quotes:
<child v-bind:foo="bar"></child>
binds the value of the bar
property in the current scope to the child's foo
prop
anything within the quotes will be evaluated as a javascript expression. So v-bind:foo="bar + 1"
(given bar
equals 1) would bind the value 2 to the child's foo
prop.
-
v-model
实现数据的双向绑定先看数据双向绑定的手工实现
-
v-bind
绑定ref数据,让ref数据的变化可以反应到页面上。ref data -> template -
v-on
添加事件监听器. template -> ref data
v-model
这一个指令把上面两件事都干了,它会根据所使用的element自动使用对应的 DOM 属性和事件组合
以下的两个input框是一模一样的<script setup> import { ref } from 'vue' const message = ref('') </script> <template> <p>Message is: {{ message }}</p> <input v-model="message" placeholder="v-model mode input" /> <br> <br> <input :value="message" @input="(event) => {message = event.target.value}" placeholder="manual bind mode input" /> </template>
-
vue框架中如何实现父子组件交互
- 编写带有
property
和emit
的子组件MyChildComponent.vue
<!-- MyChildComponent.vue -->
<template>
<div>
<h2>子组件</h2>
<p>{{ message }}</p>
</div>
</template>
<script>
import { watch } from 'vue'
const props = defineProps({
message: {
type: String,
default: 'Hello from child component!',
},
});
// 定义子组件可以emit的事件. messageChange, fakeEmit都是emit的事件名称
const emit = defineEmits(['messageChange', 'fakeEmit']);
// 侦听一个关于props.message的 getter 函数
watch(
() => props.message,
(new_msg) => {
console.log(`new_msg is: ${new_msg}`);
// 产生messageChange事件,并将new_msg作为事件值
emit('messageChange', new_msg);
}
)
// 定义该组件的公开属性和公开方法
const public_variable = ref(1);
const public_func = () => {
console.log('this is public_func');
}
defineExpose({
public_variable,
public_func
})
</script>
props
是实例化子组件时的入参,类似python __init__
的参数;emit
是子组件可以产生的事件,一定程度上类似returned value,可以在实例化子组件时使用@
注册事件的handler
上面代码中,这个子组件接受一个名为 message
的 prop,并在模板中显示它;并且它可以产生messageChange
和fakeEmit
事件
- 在父组件中,导入这个子组件。在这个例子中,我们创建一个名为
MyParentComponent.vue
的父组件:
<!-- MyParentComponent.vue -->
<template>
<div>
<h1>父组件</h1>
<MyChildComponent ref="childComponentRef" :message="parentMessage" @message-change="handleMessageChange" />
</div>
</template>
<script>
// import 子组件。假设子组织在同一目录下
import MyChildComponent from './MyChildComponent.vue'
// childComponentRef将作为template中MyChildComponent的`ref` attribute的值
const childComponentRef = ref(null);
const parentMessage = ref('Hello from parent component!');
// 子组件messageChange事件的handler
const handleMessageChange = () => {
console.log("child's saying that msg changed");
};
// 直接操作DOM element,需使用Template Refs. https://vuejs.org/guide/essentials/template-refs
// callChildPubFunc直接调用子组件公开方法
const callChildPubFunc = () => {
// childComponentRef.value表示子组件实例本身
childComponentRef.value.public_func();
};
</script>
在父组件中,我们首先使用 import 语句导入子组件,就可以在父组件的模板中使用子组件标签(在这里是 <MyChildComponent />
)。
然后,通过在子组件标签上使用 v-bind
(简写为 :)将父组件中的数据传递给子组件的 prop。在这个例子中,我们将 parentMessage
数据传递给子组件的 message
prop。现在,当你在项目中使用 MyParentComponent
时,它将包含并显示 MyChildComponent
子组件,子组件将显示从父组件传递的消息。