Vue学习笔记
总目录
文章目录
如何搭建VUE框架
1.安装node
● 安装node.js安装vue前电脑中必须已经安装成功node.js
○ node.js
○ 下载完成后,执行安装程序,直接进行安装即可
○ 安装完成后,打开命令行窗口(cmd)
○ 输入node -v 和 npm -v 可查看安装的node.js和npm的版本号
2.安装编辑器
● vscode
3.安装vue
以管理员身份打开命令提示符(cmd)或者在vscode中的终端(terminal)执行操作(以管理员身份打开vscode)
● 使用npm进行安装
○ 缺点安装速度慢,因为资源在国外,可以使用国内镜像
● 使用国内镜像资源安装
● 首先切换淘宝镜像
npm config set registry http://registry.npm.taobao.org/
● 安装vue
○ 使用npm安装vue的脚手架
npm install –g @vue/cli
4.创建Vue项目
4.1创建文件
创建文件夹用vscode打开 ,点击终端按钮,cd 进入到你要创建的文件夹里
切换到其他盘时先d:
输入命令:
vue create vuetest
4.2选择配置信息
- 通过上下方向键选择,然后回车确认
选择Manually select features 自定义配置。
- 之后按空格键选择要安装的资源配置,带*号说明选中啦
一般选择 Babel Router VueX CssPre…选完直接回车
4.3选择版本
上下方向键选择版本,这里我们选择vue2.x(3也可以,3会兼容2的语法),直接回车
4.4路径模式选择
-
是否使用history router?
我选n
4.5请选择Css预处理语言
- 主要为css解决浏览器兼容,简化css代码等问题
选择less
4.6如何存放配置
选择in package.json
我都会统一集成在package.json文件
上述操作后结果:
4.7是否保存本地配置?我选N
这里选择yes会保存一个模版,以后创建项目可以直接选择,但是大家都是刚学习应该多练手,不建议保存
之后就开始搭建了
这里搭建可能会有点慢(卡住惹,可以试试再开一个终端(不明原因的推动它进程了(其实应该只是心理作用
4.8搭建完毕
搭建完毕的显示就像这样啦~
到这里VUE项目就搭建完毕啦
5.启动运行vue项目
输入命令 进入vuetest的路径里
cd vuetest
一定要注意先进入vuetest中!
执行启动命令
npm run serve
运行完的结果:
就能浏览器打开这两个网址了~
App.vue
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</div>
</template>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
Vue框架引入Axios
首先已经创建好了Vue框架,安装好了node.
之后进入项目文件夹 使用命令
1. 命令安装 axios 和 vue-axios
以管理员身份
npm install axios --save
//一个个复制
npm install vue-axios --save
2.package.json查看版本
3.之后在main.js完整引入
import axios from 'axios'
import VueAxios from 'vue-axios'
// VueAxios 与 axios 的位置不能交换,否则出现 TypeError: Cannot read property 'protocol' of undefined
Vue.use( VueAxios , axios)
完整代码:
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';//引入elementui
import 'element-ui/lib/theme-chalk/index.css';//引入elementui
Vue.config.productionTip = false
Vue.use(ElementUI);//引入elementui
import axios from 'axios'
import VueAxios from 'vue-axios'
// VueAxios 与 axios 的位置不能交换,否则出现 TypeError: Cannot read property 'protocol' of undefined
Vue.use(VueAxios, axios)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
4.如何使用
举例:在HomeView.vue中的
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
中name: 'HomeView',
之后初始化方法:
methods:{
getList(){
this.axios.get('http://localhost:3312/user/getList',{
params:{
page:this.query.page,
size:this.query.size,
name:this.query.name,
phone:this.query.phone,
city:this.query.city
}
}).then((resp)=>{
console.log(resp);
this.tableData = resp.data.content.list;
this.total = resp.data.content.total;
})
},
},
像这样:
<template>
<div class="home">
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
</div>
</template>
<script>
// @ is an alias to /src
export default {
name: 'HomeView',
methods: {
getList() {
this.axios.get('http://localhost:3312/user/getList', {
params: {
page: this.query.page,
size: this.query.size,
name: this.query.name,
phone: this.query.phone,
city: this.query.city
}
}).then((resp) => {
console.log(resp);
this.tableData = resp.data.content.list;
this.total = resp.data.content.total;
})
},
},
components: {
HelloWorld
}
}
</script>
学习笔记——1. VUE基础
VUE简介
什么是vue?
一套用于构建用户界面的渐进式JavaScript框架。
渐进式:
VUE可以自底向上逐层的应用。
简单应用:只需一个轻量小巧的核心库
复杂应用:可以引入各式各样的vue插件
作者GitHub主页→yyx990803 (Evan You) (github.com)
vue特点
-
采用组件化模式,提高代码复用率、且让代码更好维护。
-
声明式编码,让编码人员无需直接操作DOM,提高开发效率。
命令式编码&声明式编码对比
/*命令式编码*/
// 准备html字符串
let htmlStr='';
// 遍历数据拼接html字符串
persons.forEach(p=>{
htmlStr += `<li>${p.id} - ${p.name} - ${p.age}</li>`
});
// 获取list元素
let list = document.getElementById('list')
// 修改内容(亲自操作DOM)
list.innerHTML = htmlStr
/*声明式编码*/
<ul id="list">
<li v-for="p in persons">
{{p.id}} - {{p.name}} - {{p.age}}
</li>
</ul>
- 使用虚拟DOM+优秀Diff算法,尽量复用DOM节点。
原生JavaScript实现:
Vue实现:
-
学习Vue之前要掌握的JavaScript基础知识
-
ES6语法规范
-
ES6模块化
-
包管理器
-
原型、原型链
-
数组常用方法
-
axios
-
promise
-
…
-
vue官方文档链接→Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)
简单教程
声明式渲染
Vue的核心是声明式渲染:通过扩展于标准HTML的模核心板语法,我们可以根据JavaScript的状态来描述HTML应该是什么样子的。当状态改变时,HTML会自动更新。
能在改变时触发更新的状态被称作是响应式的。我们可以使用Vue的reactive()
API来声明响应状态。由reactive()
创建的对象都是JavaScript Proxy,其行为与普通对象一样。
<script setup>
import { reactive } from 'vue'
const counter = reactive({
count: 0
})
console.log(counter.count) // 0
counter.count++
console.log(counter.count) // 1
</script>
reactive()
只适用于对象(包括数组和内置类型,比如Map
和Set
)。而另一个APIref()
可以接受任何值类型。ref
会返回一个包裹对象,并在.value
属性下暴露其内部值。
<script setup>
import { ref } from 'vue'
const message = ref('Hello World!')
console.log(message.value) // "Hello World!"
message.value = 'Changed'
console.log(message.value) // "Changed"
</script>
完整测试:
<script setup>
import { ref, reactive } from 'vue'
const counter = reactive({ count: 0 })
const msg = ref('Welcome to the test page.')
const ms = reactive({ str: 'abc' })
console.log(counter.count) // 0
counter.count++;
console.log(counter.count) // 1
console.log(msg.value) // Welcome to the test page.
console.log(ms.str) // abc
ms.str = 'ABC';
console.log(ms.str); // ABC
</script>
想必聪明的你已经发现上面几段js代码标签有些不一样的地方了吧~
我们在组件<script setup>
块中声明响应式状态,可以直接在模板中使用。下面展示了一种如何使用双花括号语法,根据counter
对象和msg
ref的值渲染动态文本:
<template>
<h1>{{ msg }}</h1>
<p>count is: {{ counter.count }}</p>
<p>{{ ms.str }}</p>
</template>
注意此时我们不需要使用.value
,因为它会在这个语境下被自动解包,使使用更简单。
- 双花括号
{{...}}
中的内容并不只限于标识符或路径——我们也可以用任何有效的JavaScript表达式来填入其中,并渲染。比如:
<h1>{{ message.split('').reverse().join('') }}</h1>
效果:
我们前面创建vue项目的时候已经有了几个生成页,直接在已有的网页做也test挺方便的(),所以这里在介绍一些如何直接在生成的模板里做test吧
比如,我们在components
文件夹下新建一个testVue.vue文件,并填入以下内容:
<script setup>
import { ref, reactive } from 'vue'
const counter = reactive({ count: 0 })
const msg = ref('Welcome to the test page.')
const ms = reactive({ str: 'abc' })
console.log(counter.count)
</script>
<template>
<h1>{{ msg }}</h1>
<p>Count is:{{ counter.count }}</p>
<p>{{ ms.str }}</p>
</template>
<script>
export default {
name:'testVue'
}
</script>
此时这个Vue中是一个名为testVue的模板,我们可以在App.vue中将这部分引入:
<template>
<testVue/>
</template>
<script>
// 将testVue从文件中引入
import testVue from './components/testVue.vue';
export default {
name: 'App',
components: {
// 在此处我们把testVue加入组件中就能在页面上使用并渲染渲染这个内容
testVue
}
}
</script>
然后npm run serve
一下:
网页呈现:
控制台输出:
有一个黄色warning…先不管它qwq
Attribute绑定
在vue中,mustache语法(即双花括号)只能用于文本插值。为了给Attribute绑定一个动态值,需要用v-bind
指令:
<div v-bind:id="dynamicId"></div>
Vue指令是由v-
开头的一种特殊attribute。它们是Vue模板语法的一部分。和文本插值类似,指令的值可以访问组件状态的JavaScript表达式
事件监听
<h1>点击次数:{{ counter.clickcount }}</h1>
<!-- 可以使用 v-on 指令监听 DOM 事件 -->
<button v-on:click="increment">点击次数增加</button>
<!-- 因为其经常使用,v-on 也有一个简写语法: -->
<button @click="resetbtn">点击次数重置</button>
此处,increment
引用了一个在 <script setup>
中声明的函数:
<script setup>
import { reactive } from 'vue'
const counter = reactive({ count: 0 ,clickcount:0})
function increment() {
counter.clickcount++;
}
function resetbtn() {
counter.clickcount = 0;
}
</script>
在函数中,我们可以通过修改 reactive 来更新组件状态。
事件处理函数也可以使用内置表达式,并且可以使用修饰符简化常见任务。
表单绑定
我们可以同时使用v-bind和v-on在表单的输入元素上创建双向绑定。
<script setup>
import { ref } from 'vue'
const text = ref('')
function onInput(e) {
text.value = e.target.value
}
</script>
<template>
<div>
<input :value="text" type="text" @input="onInput" placeholder="在此输入...">
<p>上文本输入框的内容:{{ text }}</p>
</div>
</template>
有一种简化双向绑定的指令——v-model
指令,我们看看它的用法:
<script setup>
import { ref } from 'vue'
const text = ref('')
</script>
<template>
<input v-model="text" placeholder="Type here">
<p>{{ text }}</p>
</template>
v-model
会将被绑定的值与<input>
的值自动同步,这样就不必在使用事件处理函数了。
v-model
不仅支持文本输入框,也支持诸如多选框、单选框、下拉框之类的输入类型。细节参考Vue内置指令
条件渲染
我们可以用v-if
指令来有条件地渲染元素,只有当v-if
对应的值为真值Truethy的时候渲染内容,如果为假值,对应元素将被移除。
同时,我们可以结合v-else
和v-else-if
来表示其他分支。
样例代码:
<script setup>
import { ref } from 'vue'
const awesome = ref(true)
function toggle() {
awesome.value = !awesome.value
}
</script>
<template>
<button @click="toggle">toggle</button>
<h1 v-if="awesome">Vue is awesome!</h1>
<h1 v-else>Oh no 😢</h1>
</template>
列表渲染
我们可以使用v-for
指令来渲染一个基于源数组的列表:
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
这里的todo是一个局部变量,表示当前正在迭代的数组元素。它只能在v-for
所绑定的元素上或者其内部访问,就像函数的作用域。(xxx in xxxs的这种写法接触过python的话应该还是很熟悉的qwq)
注意:我们给每个todo对象设置了唯一的id,并且将它作为特殊的key attribute
绑定到每个<li>
上。key
使得Vue能够精确地移动每个<li>
,以匹配对应的对象在数组中的位置。
更新列表有两种方式:
-
在源数组上调用变更方法:
todos.value.push(newTodo)
-
使用新的数组代替原数组:
todos.value=todos.value.filter(/*...*/)
filter
是一个过滤器,它可以用来筛选出符合条件的,丢弃不符合条件的,同时,它也能实现对筛选出来的数据的一些加工。
接下来展示一个简单的todo列表demo,并实现对列表进行简单的增删操作的实例应用:
<script setup>
import { ref } from 'vue'
// 给每个todo对象一个唯一的
let id = 0;
const newTodo = ref();
const todos = ref([
{
id: id++,
text: 'Learn HTML'
},
{
id: id++,
text: 'Learn CSS'
},
{
id: id++,
text: 'Learn JavaScript'
},
{
id: id++,
text: 'Learn React'
},
{
id: id++,
text: 'Learn Vue'
}
])
function addTodo() {
// 增
const thenewone = {
id: id++,
text:newTodo.value
}
todos.value.push(thenewone);
newTodo.value = '';
}
function removeTodo(todo) {
// 删
todos.value = todos.value.filter((t) => t != todo)
}
</script>
<template>
<div>
<h2>我的ToDoList:</h2>
<form @submit.prevent="addTodo">
<div>
<span>添加新的todo:</span>
<input v-model="newTodo">
<button>添加</button>
</div>
</form>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo)">移除</button>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'myTodolist',
}
</script>
计算属性
我们继续以todo列表为基础,现在,我们给每一个 todo 添加了切换功能。这是通过给每一个 todo 对象添加 done
属性来实现的,并且使用了 v-model
将其绑定到复选框上:
<li v-for="todo in todos">
<input type="checkbox" v-model="todo.done">
...
</li>
下一个可以添加的改进是隐藏已经完成的 todo。我们已经有了一个能够切换 hideCompleted
状态的按钮。但是应该如何基于状态渲染不同的列表项呢?
介绍一个新 API:computed()
。它可以让我们创建一个计算属性 ref,这个 ref 会动态地根据其他响应式数据源来计算其 .value
:
提示:在Vue2.x中我们会使用filter来过滤数据,但是在Vue3.x中移除了这个过滤器,替之以computed()
来实现功能。
简单结构:
<script setup>
import { ref, computed } from 'vue'
const hideCompleted = ref(false)
const todos = ref([
/* ... */
])
const filteredTodos = computed(() => {
// 根据 `todos.value` & `hideCompleted.value`
// 返回过滤后的 todo 项目
})
</script>
- <li v-for="todo in todos">
+ <li v-for="todo in filteredTodos">
计算属性会自动跟踪其计算中所使用的到的其他响应式状态,并将它们收集为自己的依赖。计算结果会被缓存,并只有在其依赖发生改变时才会被自动更新。
现在,试着添加 filteredTodos
计算属性并实现计算逻辑。如果实现正确,在隐藏已完成项目的状态下勾选一个 todo,它也应当被立即隐藏。
我们把上述运用到我们的todo中
<script setup>
import { ref,computed } from 'vue'
// 给每个todo对象一个唯一的id
let id = 0;
const newTodo = ref();
const todos = ref([
{
id: id++,
text: 'Learn HTML',
done:true,
},
{
id: id++,
text: 'Learn CSS',
done:true,
},
{
id: id++,
text: 'Learn JavaScript',
done:true,
},
{
id: id++,
text: 'Learn React',
done:false,
},
{
id: id++,
text: 'Learn Vue',
done:false,
}
])
const filteredTodo = computed(() => {
return hideCompleted.value ? todos.value.filter((t) => !t.done) : todos.value
})
function addTodo() {
// 增
const thenewone = {
id: id++,
text: newTodo.value,
done:false,
}
todos.value.push(thenewone);
newTodo.value = '';
}
function removeTodo(todo) {
// 删
todos.value = todos.value.filter((t) => t != todo)
}
const hideCompleted = ref(false)
</script>
<template>
<div>
<h2>我的ToDoList:</h2>
<form @submit.prevent="addTodo">
<div>
<span>添加新的todo:</span>
<input v-model="newTodo">
<button>添加</button>
</div>
</form>
<ul>
<li v-for="todo in filteredTodo" :key="todo.id">
<input type="checkbox" v-model="todo.done">
<span :class="{done:todo.done}">{{ todo.text }}</span>
<button @click="removeTodo(todo)">移除</button>
</li>
</ul>
<button @click="hideCompleted = !hideCompleted">
{{ hideCompleted ? '展示所有事项':'隐藏已完成事项' }}
</button>
</div>
</template>
<script>
export default {
name: 'myTodolist',
}
</script>
<style>
.done{
text-decoration: line-through;
}
</style>
生命周期和模板引用
到目前为止,Vue为我们处理了所有的DOM更新,但有时候我们还是想手动操作DOM,这时候我们就需要模板引用——也就是指向模板中一个DOM元素的ref。我们需要一个特殊的ref attribute来实现模板引用,就像这样:
<p ref="pElementRef">hello</p>
要访问该引用我们需要声明一个同名的ref:
const pElementRef = ref(null)
注意这个 ref 使用 null
值来初始化。这是因为当 <script setup>
执行时,DOM 元素还不存在。模板引用 ref 只能在组件挂载后访问。
要在挂载之后执行代码,我们可以使用 onMounted()
函数:
import { onMounted } from 'vue'
onMounted(() => {
// 此时组件已经挂载。
})
这被称为生命周期钩子——它允许我们注册一个在组件的特定生命周期调用的回调函数。还有一些其他的钩子如 onUpdated
和 onUnmounted
。更多细节请查阅生命周期图示。
现在,尝试添加一个 onMounted
钩子,然后通过 pElementRef.value
访问 <p>
,并直接对其执行一些 DOM 操作。(例如修改它的 textContent
)。
<script setup>
import { ref, onMounted } from 'vue'
const pElementRef = ref(null)
onMounted(() => {
pElementRef.value.textContent = 'mounted!'
})
</script>
<template>
<p ref="pElementRef">hello</p>
</template>
侦听器
有时我们需要响应性地执行一些“副作用”——例如,当一个数字改变时将其输出到控制台。我们可以通过侦听器来实现它:
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newCount) => {
// 没错,console.log() 是一个副作用
console.log(`new count is: ${newCount}`)
})
watch()
可以直接侦听一个 ref,并且只要 count
的值改变就会触发回调。watch()
也可以侦听其他类型的数据源。
一个比在控制台输出更加实际的例子是当 ID 改变时抓取新的数据。在右边的例子中就是这样一个组件。该组件被挂载时,会从模拟 API 中抓取 todo 数据,同时还有一个按钮可以改变要抓取的 todo 的 ID。
接下来,我们来尝试实现一个侦听器,使得组件能够在按钮被点击时抓取新的 todo 项目。
组件
真正的 Vue 应用往往是由嵌套组件创建的。
父组件可以在模板中渲染另一个组件作为子组件。要使用子组件,我们需要先导入它:
import ChildComp from './ChildComp.vue'
然后我们就可以在模板中使用组件,就像这样:
<ChildComp />
这个在最开始介绍过哩,可以看看之前的test样例里引入的逻辑过程。
Props
子组件可以通过 props 从父组件接受动态数据。首先,需要声明它所接受的 props:
<!-- ChildComp.vue -->
<script setup>
const props = defineProps({
msg: String
})
</script>
注意 defineProps()
是一个编译时宏,并不需要导入。一旦声明,msg
prop 就可以在子组件的模板中使用。它也可以通过 defineProps()
所返回的对象在 JavaScript 中访问。
父组件可以像声明 HTML attributes 一样传递 props。若要传递动态值,也可以使用 v-bind
语法:
<ChildComp :msg="greeting" />
Emits
除了接收 props,子组件还可以向父组件触发事件:
<script setup>
// 声明触发的事件
const emit = defineEmits(['response'])
// 带参数触发
emit('response', 'hello from child')
</script>
emit()
的第一个参数是事件的名称。其他所有参数都将传递给事件监听器。
父组件可以使用 v-on
监听子组件触发的事件——这里的处理函数接收了子组件触发事件时的额外参数并将它赋值给了本地状态:
<ChildComp @response="(msg) => childMsg = msg" />
插槽
除了通过 props 传递数据外,父组件还可以通过插槽 (slots) 将模板片段传递给子组件:
<ChildComp>
This is some slot content!
</ChildComp>
在子组件中,可以使用 <slot>
元素作为插槽出口 (slot outlet) 渲染父组件中的插槽内容 (slot content):
<!-- 在子组件的模板中 -->
<slot/>
<slot>
插口中的内容将被当作“默认”内容:它会在父组件没有传递任何插槽内容时显示:
<slot>Fallback content</slot>
学习笔记——2. vue-cli
Vue手脚架指的就是vue-cli,它是一个快速构建 单页面应用程序(SPA)环境配置的工具。
cli——command-line-interface命令行界面。
单页面应用程序(SPA)
单页面应用程序简称SPA,指一个Web网站中只有唯一的一个HTML页面,所有的功能与交互都在这唯一的一个页面内完成。
特点:
- 将所有功能局限于一个web页面中,仅在该web页面初始化的时候加载相应的JS、CSS、HTML资源
- 一旦页面加载完毕,SPA不会因为页面的操作而进行页面的重新加载或跳转,而是使用JavaScript动态的变化HTML的内容,从而实现页面与用户的交互
Vue项目目录结构介绍
我们先看看这个生成的目录:
(我的这个由于之前演示删掉了一些文件夹啥的qwq)
目录结构介绍:
- node_modules:存放第三方依赖包
- public:公共的静态资源
- src:项目源代码目录
- assets:存放静态资源文件
- components:自定义组件
- App.vue:项目根组件
- main.js:项目的打包入口文件
- package.json:项目的包管理配置文件
我们的项目是怎么运行的?
之前Vue基础部分我们讲了好多语法好多组件,但是最终我们需要将项目跑起来,npm run serve
可以方便我们做测试时看效果,但是我们还要从JavaScript的角度去看看我们的项目是怎么运行起来的。
我们看看index.html里面是什么东西:
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
通过我们强大的脑测这个index.html根本不是我们run出来的页面啊
不过捏,其实就是这个index.html
我们通过检查可以看到这样的elements结构:
而我们所有写的页面啊组件啊其实都在这个<div id="app" data-v-app>...</div>
当中包裹着。
如何实现这个包裹呢?
其实主要是这个main.js啦。我们先来看看它的内容:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
通过这样的语句,我们就能把App.vue组件渲染到index.html指定区域(div#app)中。
这是从已有的示例文档中找到的思路。总结一下就是:
现在我是一个index.html,我要在浏览器中展示东西,现在我要展示一个vue组件,这个组件在App.vue中,名叫App(export default{name:'App',...}
),于是我在布置好Vue环境(import Vue from 'vue'
)之后将App组件从App.vue当中引入(import App from './App.vue'
)并布置到index.html当中的对应位置,这个位置就是我们预留的<div id="app"></div>
而手脚架帮助我们完成了整个逻辑中烦复的部分,让我们的工作变得简单。
学习笔记——3. vue-router
为什么要使用Vue Router路由
我们在学习前面两节的时候都在单页应用中渲染内容。前文我们简单介绍了SPA的特点,这里再罗列一下它的优缺点:
-
优点:
- 无刷新体验,这个应该是最显著的有点,由于路由分发直接在浏览器端完成,页面是不刷新,对用户的响应非常及时,因此提升了用户体验;
- 完全的前端组件化,前端开发不再以页面为单位,更多地采用组件化的思想,代码结构和组织方式更加规范化,便于修改和调整;
- API 共享,如果你的服务是多端的(浏览器端、Android、iOS、微信等),单页应用的模式便于你在多个端共用 API,可以显著减少服务端的工作量。容易变化的 UI 部分都已经前置到了多端,只受到业务数据模型影响的 API,更容易稳定下来,便于提供鲁棒的服务;
- 组件共享,在某些对性能体验要求不高的场景,或者产品处于快速试错阶段,借助于一些技术(Hybrid、React Native),可以在多端共享组件,便于产品的快速迭代,节约资源。
-
缺点:
- 首次加载大量资源,要在一个页面上为用户提供产品的所有功能,在这个页面加载的时候,首先要加载大量的静态资源,这个加载时间相对比较长;
- 较高的前端开发门槛,MVC 前置,对前端工程师的要求提高了,不再是『切切图,画画页面这么简单』;同时工作量也会增加数倍,开发这类应用前端工程师的数量往往多于后端;
- 不利于 SEO,单页页面,数据在前端渲染,就意味着没有 SEO,或者需要使用变通的方案。
既然这样,我们还是期望可以有页面跳转功能。这时候我们再来看看最初的App.vue是如何实现简单的跳转的:
<template>
<div id="app">
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<router-view/>
</div>
</template>
<style lang="less">
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
}
nav {
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
}
</style>
其中的这部分就是Vue路由部分:
<nav>
<!-- 使用router-link组件来导航 -->
<!-- 通过to属性来指定链接 -->
<!-- 而这个router-link会被渲染成为一个a链接标签 -->
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link>
</nav>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view/>
其中主要实现功能的就是:
- router-link标签:跳转的链接,to=""是必须的属性,双引号中的内容是我们接下来在JS文件中定义的路由path,这可以在Vue Router可以在不重新加载的情况下更改URL、处理URL的生成以及编码。
- router-view标签:展示我们匹配到的组件的区域,可以放在任何地方,以适应我们的布局。
在js文件中定义路由path
JavaScript设置路由的语法组件:
// 1. 定义路由组件.
// 也可以从其他文件导入
const Home = { template: '<div>Home</div>' }
const About = { template: '<div>About</div>' }
// 2. 定义一些路由
// 每个路由都需要映射到一个组件。
// 我们后面再讨论嵌套路由。
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
]
// 3. 创建路由实例并传递 `routes` 配置
// 你可以在这里输入更多的配置,但我们在这里
// 暂时保持简单
const router = VueRouter.createRouter({
// 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
history: VueRouter.createWebHashHistory(),
routes, // `routes: routes` 的缩写
})
// 5. 创建并挂载根实例
const app = Vue.createApp({})
//确保 _use_ 路由实例使
//整个应用支持路由。
app.use(router)
app.mount('#app')
// 现在,应用已经启动了!
从默认样例中理解逻辑…
我们先理解最后这个整合内容:
/src/main.js:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
createApp(App).use(store).use(router).mount('#app')
这里主要就是把App、router、store等组件引入,并置于我们包裹的createApp(App)中,并渲染在#app标签中。
我们按照这个引入路径去找到router:
router下有一个index.js,内容如下:
import { createRouter, createWebHashHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const 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(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
]
const router = createRouter({
history: createWebHashHistory(),
routes
})
export default router
这里就是打包的router组件的内容了,指令也很简单,基本望文生义。注意一些写法:
这里就是设置跳转路径了,样例还教了如何实现懒加载:
{
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(/* webpackChunkName: "about" */ '../views/AboutView.vue')
}
const router = createRouter({
history: createWebHashHistory(),
// 这里的routes是routes:routes的简写
routes
})
理解逻辑之后我们简单在其后添加一个test页面,并在App.vue中配置router-link:
<router-link to="/test">Test</router-link>
获得这样的效果:
此时可以发现我们点击不同标签对应的url已经变化。
思路ok,那么我们在test里再学习写子路由;
嵌套路由
一些应用的UI由多层嵌套组件组成,同时组件里还有导航路由,像这样:
/user/johnny/profile /user/johnny/posts
+------------------+ +-----------------+
| User | | User |
| +--------------+ | | +-------------+ |
| | Profile | | +------------> | | Posts | |
| | | | | | | |
| +--------------+ | | +-------------+ |
+------------------+ +-----------------+
这就是嵌套路由,接下来我们来实现一下这个!
还是原来的test标签页,我们这次加上一个username:
<template>
<nav>
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/test/Asuka/">Test</router-link>
</nav>
<router-view/>
</template>
这时候直接点击将会进入#/test/Asuka/
页面
接下来我们尝试在#/test/Asuka/
页面中添加home
、posts
、profiles
这三个页面。
首先我们先写这三个组件:
// home
<template>
<div class="user">
<h2>It is UserHome of {{ $route.params.username }}</h2>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'UserHome',
}
</script>
// posts
<template>
<div class="user">
<h2>It is UserPost of {{ $route.params.username }}</h2>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'UserPost',
}
</script>
// profiles
<template>
<div class="user">
<h2>It is UserProfile of {{ $route.params.username }}</h2>
<router-view></router-view>
</div>
</template>
<script>
export default {
name: 'UserProfile',
}
</script>
然后在testView.vue里给他们设置渲染位置
testView.vue:
<template>
<div id="test">
<!-- 这里就是接下来的子路由的link文字显示位置 -->
<router-link to="home">Home</router-link> |
<router-link to="posts">Posts</router-link> |
<router-link to="profiles">Profiles</router-link>
<h2>User:{{ $route.params.username }}</h2>
<RouterView></RouterView>
</div>
</template>
<script>
import UserPost from '@/components/UserPost.vue';
export default {
name: 'testView',
components: {
UserPost,
},
}
</script>
<style>
#test {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #2c3e50;
}
</style>
{{$route.params.username}}
这个等会讲qwq
当然了,火眼金睛的小朋友已经发现这次写to=""
有一点不一样。
<template>
<div id="test">
<router-link to="home">Home</router-link> |
<router-link to="posts">Posts</router-link> |
<router-link to="profiles">Profiles</router-link>
<h2>User:{{ $route.params.username }}</h2>
<RouterView></RouterView>
</div>
</template>
我们肯定保证父路由url+子路由地址是可以被查找到的,先前我们在App.vue中写成了下面这样:
<router-link to="/test/Asuka/">Test</router-link>
注意到这里的路径后带着一个/
,这样两边的路径一继承,就会是#/test/Asuka/home
这样的路径了。
关于路由匹配语法可以看这里
接下来,我们在路由中设置子路由,我们看新写的部分:
const routes = [
{
path: '/test/:username/', // 注意这种写法qwq
name: 'test',
component: () => import('../views/testView.vue'),
children: [
{
path: 'home', // path和to一致
name: 'testUserHome', // 我们当然可以给子路由也命名啦
component:()=>import('@/components/UserHome.vue')
},
{
path: 'posts',
name:'testUserPost',
component:()=>import('@/components/UserPost.vue')
},
{
path: 'profiles',
name:'testUserProfile',
component:()=>import('@/components/UserProfile.vue')
}
]
}
]
像这样我们的路由就能正常使用了。
带参数的动态路由匹配
在上面我们已经使用过这样的带参数的动态路由匹配了:
{{$route.params.username}}
和path: '/test/:username/'
很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如,我们可能有一个 User
组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,我们可以在路径中使用一个动态字段来实现,我们称之为 路径参数 :
const User = {
template: '<div>User</div>',
}
// 这些都会传递给 `createRouter`
const routes = [
// 动态字段以冒号开始
{ path: '/users/:id', component: User },
]
现在像 /users/johnny
和 /users/jolyne
这样的 URL 都会映射到同一个路由。
路径参数 用冒号 :
表示。当一个路由被匹配时,它的 params 的值将在每个组件中以 this.$route.params.xxxx
的形式暴露出来。因此,我们可以通过更新 User
的模板来呈现当前的用户 ID:
const User = {
template: '<div>User {{ $route.params.id }}</div>',
}
你可以在同一个路由中设置有多个 路径参数,它们会映射到 $route.params
上的相应字段。例如:
匹配模式 | 匹配路径 | $route.params |
---|---|---|
/users/:username | /users/eduardo | { username: 'eduardo' } |
/users/:username/posts/:postId | /users/eduardo/posts/123 | { username: 'eduardo', postId: '123' } |
除了 $route.params
之外,$route
对象还公开了其他有用的信息,如 $route.query
(如果 URL 中存在参数)、$route.hash
等。
具体可以点击这里学习演示代码(还没写)
编程式导航
除了使用<router-link>
创建a标签来定义导航链接,我们还可以借助router的实例方法,通过编写代码来实现导航。
接下来介绍4种编程导航实现:导航到不同位置、替换当前位置、横跨历史、篡改历史
导航到不同位置
在Vue实例中,我们可以通过$router
访问路由实例,所以我们可以使用this.$router.push
实现这个功能。
想要导航到不同的 URL,可以使用 router.push
方法。这个方法会向 history 栈添加一个新的记录,所以,当用户点击浏览器后退按钮时,会回到之前的 URL。
当你点击 <router-link>
时,内部会调用这个方法,所以点击 <router-link :to="...">
相当于调用 router.push(...)
:
声明式 | 编程式 |
---|---|
<router-link :to="..."> | router.push(...) |
该方法的参数可以是一个字符串路径,或者一个描述地址的对象。例如:
// 字符串路径
router.push('/users/eduardo')
// 带有路径的对象
router.push({ path: '/users/eduardo' })
// 命名的路由,并加上参数,让路由建立 url
router.push({ name: 'user', params: { username: 'eduardo' } })
// 带查询参数,结果是 /register?plan=private
router.push({ path: '/register', query: { plan: 'private' } })
// 带 hash,结果是 /about#team
router.push({ path: '/about', hash: '#team' })
注意:如果提供了 path
,params
会被忽略,上述例子中的 query
并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的 name
或手写完整的带有参数的 path
:
const username = 'eduardo'
// 我们可以手动建立 url,但我们必须自己处理编码
router.push(`/user/${username}`) // -> /user/eduardo
// 同样
router.push({ path: `/user/${username}` }) // -> /user/eduardo
// 如果可能的话,使用 `name` 和 `params` 从自动 URL 编码中获益
router.push({ name: 'user', params: { username } }) // -> /user/eduardo
// `params` 不能与 `path` 一起使用
router.push({ path: '/user', params: { username } }) // -> /user
当指定 params
时,可提供 string
或 number
参数(或者对于可重复的参数可提供一个数组)。任何其他类型(如 undefined
、false
等)都将被自动字符串化。对于可选参数,你可以提供一个空字符串(""
)来跳过它。
由于属性 to
与 router.push
接受的对象种类相同,所以两者的规则完全相同。
替换当前位置
它的作用类似于 router.push
,唯一不同的是,它在导航时不会向 history 添加新记录,正如它的名字所暗示的那样——它取代了当前的条目。
声明式 | 编程式 |
---|---|
<router-link :to="..." replace> | router.replace(...) |
也可以直接在传递给 router.push
的 routeLocation
中增加一个属性 replace: true
:
router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })
横跨历史
该方法采用一个整数作为参数,表示在历史堆栈中前进或后退多少步,类似于 window.history.go(n)
。
例子
// 向前移动一条记录,与 router.forward() 相同
router.go(1)
// 返回一条记录,与 router.back() 相同
router.go(-1)
// 前进 3 条记录
router.go(3)
// 如果没有那么多记录,静默失败
router.go(-100)
router.go(100)
还有一个方法:$router.back()
在使用中发现,使用r o u t e r . b a c k ( ) 和 router.back()和router.back()和router.go(-1)作用相同,都是返回原页面。但如果原页面路由携带参数,使用以上两个方法返回的原页面路由参数消失,此时使用$router.back(-1)返回原页面路由参数仍存在。
// go(-1): 原页面表单中的内容会丢失;
this.$router.go(-1) // 后退+刷新
this.$router.go(0) // 刷新
this.$router.go(1) // 前进
// back(): 原页表表单中的内容会保留;
this.$router.back() // 后退
this.$router.back(0) // 刷新
this.$router.back(1) // 前进
篡改历史
其实,router.push
、router.replace
和 router.go
是window.history.pushState
、window.history.replaceState
和 window.history.go
的翻版,它们确实模仿了 window.history
的 API。
所以用法是基本相同的。
值得一提的是,无论在创建路由器实例时传递什么样的 history配置,Vue Router 的导航方法( push
、replace
、go
)都能始终正常工作。
学习笔记——4. vuex
学习笔记——5. element-ui
这个写的时候是用的vue2演示记录的,不过,vue3.x有好多不一样的地方,仅做参考吧。
Vue框架引入element-ui
首先已经创建好了Vue框架,安装好了node.
之后进入项目文件夹 使用命令
cd vuetest
一定要注意是在项目文件内!
1.命令引入
以管理员身份
如果是vue 2.x用这个:
npm i element-ui -S
vue 3.x用这个:
npm install element-plus --save
这里可能安装比较慢…
2.package.json查看版本
之后就在package.json,看到下载好的依赖版本
package.json位置:
dependencies中有了"element-ui": "^2.15.13"
3.之后在main.js完整引入
这是之前的,没有引入之前的
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
引入element ui
增加了3个
import ElementUI from 'element-ui';//引入elementui
import 'element-ui/lib/theme-chalk/index.css';//引入elementui
Vue.use(ElementUI);//引入elementui
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import ElementUI from 'element-ui';//引入elementui
import 'element-ui/lib/theme-chalk/index.css';//引入elementui
Vue.config.productionTip = false
Vue.use(ElementUI);//引入elementui
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
4.复制代码测试
可以在views>HomeView.vue中测试代码
原始代码:
<template>
<div class="home">
<img alt="Vue logo" src="../assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
</script>
进入element官网复制代码
https://element.eleme.cn/
选择顶部导航栏的组件,可以在里面随便选几段代码
比如本例选择了几个按钮的代码
可以删除<template>
中<div class="home">
的内容,在其中粘贴我们的测试代码
<template>
<div class="home">
<el-row>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</el-row>
<el-row>
<el-button icon="el-icon-search" circle></el-button>
<el-button type="primary" icon="el-icon-edit" circle></el-button>
<el-button type="success" icon="el-icon-check" circle></el-button>
<el-button type="info" icon="el-icon-message" circle></el-button>
<el-button type="warning" icon="el-icon-star-off" circle></el-button>
<el-button type="danger" icon="el-icon-delete" circle></el-button>
</el-row>
</div>
</template>
<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'
export default {
name: 'HomeView',
components: {
HelloWorld
}
}
</script>
使用npm run serve
运行之后,在浏览器打开:
Vue模板语法
Vue 使用一种基于 HTML 的模板语法,使我们能够声明式地将其组件实例的数据绑定到呈现的 DOM 上。所有的 Vue 模板都是语法层面合法的 HTML,可以被符合规范的浏览器和 HTML 解析器解析。
在底层机制中,Vue 会将模板编译成高度优化的 JavaScript 代码。结合响应式系统,当应用状态变更时,Vue 能够智能地推导出需要重新渲染的组件的最少数量,并应用最少的 DOM 操作。
文本插值
最基本的数据绑定形式是文本插值,它使用的是"Mustache"语法(也就是双大括号{{}}
)。
<span>Message: {{ msg }}</span>
如上,双大括号标签会被替换为相应组件实例中msg
属性的值,msg
属性更改也会同步更新。(很好理解吧!
原始HTML
上面的双大括号会将数据解释为纯文本(就像innertext()
一样),如果我们想像innerHTML()
一样使得数据中的标签啊什么的也被解释渲染,就可以用v-html指令
。
/* const rawHtml=ref('<span style="color: red">这是一段红色的文本。</span>')*/
<p>rawHtml的内容:{{ rawHtml }}</p>
<p>使用v-html 之后渲染的:<span v-html="rawHtml"></span></p>
效果:
v-html
attribute 被称为一个指令。指令由 v-
作为前缀,表明它们是一些由 Vue 提供的特殊 attribute,它们将为渲染的 DOM 应用特殊的响应式行为。这里我们做的事情简单来说就是:在当前组件实例上,将此元素的 innerHTML 与 rawHtml
属性保持同步。
span
的内容将会被替换为 rawHtml
属性的值,插值为纯 HTML——数据绑定将会被忽略。注意,我们不能使用 v-html
来拼接组合模板,因为 Vue 不是一个基于字符串的模板引擎。在使用 Vue 时,应当使用组件作为 UI 重用和组合的基本单元。
一个摘自Vue.js的Warnning:
在网站上动态渲染任意 HTML 是非常危险的,因为这非常容易造成 XSS 漏洞。请仅在内容安全可信时再使用 v-html
,并且永远不要使用用户提供的 HTML 内容。
Attribute绑定
双大括号不能在HTML attributes中使用,所以我们想要响应式地绑定一个attribute,一个使用v-bind指令:
- v-bind指令
<div v-bind:id="dynamicId"></div>
v-bind
指令指示 Vue 将元素的 id
attribute 与组件的 dynamicId
属性保持一致。如果绑定的值是 null
或者 undefined
,那么该 attribute 将会从渲染的元素上移除。
-
- 简写
因为 v-bind
非常常用,我们提供了特定的简写语法:
<div :id="dynamicId"></div>
开头为 :
的 attribute 可能和一般的 HTML attribute 看起来不太一样,但它的确是合法的 attribute 名称字符,并且所有支持 Vue 的浏览器都能正确解析它。此外,他们不会出现在最终渲染的 DOM 中。简写语法是可选的。
学过这种简写语法之后,我们的示例都将使用简写语法,这也是在实际开发中更常见的用法。
- 布尔型Attribute
布尔型 attribute依据true / false值来决定 attribute 是否应该存在于该元素上。disabled
就是最常见的例子之一。
注意:v-bind
在这种场景下的行为略有不同:
<button :disabled="isButtonDisabled">Button</button>
当 isButtonDisabled
为真值或一个空字符串 (即 <button disabled="">
) 时,元素会包含这个 disabled
attribute。而当其为其他假值时 attribute 将被忽略。
演示:
<script setup>
import { ref, reactive } from 'vue'
const isButtonDisabled1 = ref(true)
const isButtonDisabled2 = ref(false)
const isButtonDisabled3 = ref('')
const isButtonDisabled4 = ref('1111')
</script>
<template>
<div>
<button :disabled="isButtonDisabled1">Button1</button>
<button :disabled="isButtonDisabled2">Button2</button>
<button :disabled="isButtonDisabled3">Button3</button>
<button :disabled="isButtonDisabled4">Button4</button>
</div>
</template>
<script>
export default {
name:'testVue'
}
</script>
效果:
- 动态绑定多个值
如果有一个像这样的包含多个 attribute 的 JavaScript 对象:
const objectOfAttrs = {
id: 'container',
class: 'wrapper'
}
通过不带参数的 v-bind
,我们可以将它们绑定到单个元素上:
<div v-bind="objectOfAttrs"></div>
使用JavaScript表达式
至此,我们仅在模板中绑定了一些简单的属性名。但是 Vue 实际上在所有的数据绑定中都支持完整的 JavaScript 表达式:
<script setup>
const testin = reactive({
num:999,
ok:false,
message:"Hello World! HaHa",
id:1
})
</script>
<template>
<div>
<!-- 运算 -->
<p>{{ testin.num + 1 }}</p>
<!-- 三元表达式 -->
<p>{{ testin.ok ? 'YES' : 'NO' }}</p>
<!-- 一些操作 -->
<p>{{ testin.message ? testin.message.split('').reverse().join('') : "nono" }}</p>
<div :id="`list-${testin.id}`">list-{{testin.id}}</div>
</div>
</template>
效果:
我们在检查中查看属性:
这些表达式都会被作为 JavaScript ,以当前组件实例为作用域解析执行。
在 Vue 模板内,JavaScript 表达式可以被使用在如下场景上:
-
- 在文本插值中 (双大括号)
-
- 在任何 Vue 指令 (以
v-
开头的特殊 attribute) attribute 的值中
- 在任何 Vue 指令 (以
- 仅支持表达式
每个绑定仅支持单一表达式,也就是一段能够被求值的 JavaScript 代码。一个简单的判断方法是是否可以合法地写在 return
后面。
因此,下面的例子都是无效的:
<!-- 这是一个语句,而非表达式 -->
{{ var a = 1 }}
<!-- 条件控制也不支持,请使用三元表达式 -->
{{ if (ok) { return message } }}
- 调用函数
可以在绑定的表达式中使用一个组件暴露的方法:
<time :title="toTitleDate(date)" :datetime="date">
{{ formatDate(date) }}
</time>
绑定在表达式中的方法在组件每次更新时都会被重新调用,因此不应该产生任何副作用,比如改变数据或触发异步操作。
这里可以看看实时显示时间的计时器组件:
timeDisplay.vue:
<script>
export default {
name: 'timeDiaplay',
data() {
return {
date:new Date()
}
},
mounted() {
// 用一个变量_this指向Vue实例this,保证作用域一致
let _this = this;
// 一个定时器(默认就是1s一变)
this.timer = setInterval(function () {
_this.date=new Date()
})
},
methods: {
toTitleDate(d) {
return d.toString()
},
formateDate(d) {
let year = d.getFullYear();
let month = d.getMonth() + 1; // getMonth()从0开始所以给它+1
let date = d.getDate();
let hours = d.getHours();
let minutes = d.getMinutes();
let seconds = d.getSeconds();
return year + '年' + month + '月' + date + '日' + hours + '时' + minutes + '分' + seconds + '秒';
},
},
beforeDestory() {
if (this.timer) {
clearInterval(this.timer)
}
}
}
</script>
<template>
<div id="app">
<p>实时展示时间:</p>
<p><span :title="toTitleDate(date)">{{ formateDate(date) }}</span></p>
</div>
</template>
记得在testVue.vue中导入这个显示时间组件qwq
效果:
- 受限的全局访问
模板中的表达式将被沙盒化,仅能够访问到有限的全局对象列表。该列表中会暴露常用的内置全局对象,比如 Math
和 Date
。
没有显式包含在列表中的全局对象将不能在模板内表达式中访问,例如用户附加在 window
上的属性。然而,你也可以自行在app.config.globalProperties
上显式地添加它们,供所有的 Vue 表达式使用。
指令 Directives
指令是带有 v-
前缀的特殊 attribute。Vue 提供了许多内置指令。内置指令一览
指令 attribute 的期望值为一个 JavaScript 表达式 (除了少数几个例外,即之后要讨论到的 v-for
、v-on
和 v-slot
)。一个指令的任务是在其表达式的值变化时响应式地更新 DOM。以 v-if
为例:
<p v-if="seen">Now you see me</p>
这里,v-if
指令会基于表达式 seen
的值的真假来移除/插入该 <p>
元素。
- 参数 Arguments
某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。例如用 v-bind
指令来响应式地更新一个 HTML attribute:
<a v-bind:href="url"> ... </a>
<!-- 简写 -->
<a :href="url"> ... </a>
这里 href
就是一个参数,它告诉 v-bind
指令将表达式 url
的值绑定到元素的 href
attribute 上。在简写中,参数前的一切 (例如 v-bind:
) 都会被缩略为一个 :
字符。
另一个例子是 v-on
指令,它将监听 DOM 事件:
<a v-on:click="doSomething"> ... </a>
<!-- 简写 -->
<a @click="doSomething"> ... </a>
这里的参数是要监听的事件名称:click
。v-on
有一个相应的缩写,即 @
字符。我们之后也会讨论关于事件处理的更多细节。
- 动态参数
同样在指令参数上也可以使用一个 JavaScript 表达式,需要包含在一对方括号内:
<!--
注意:参数表达式有一些约束(见下文)
-->
<a v-bind:[attributeName]="url"> ... </a>
<!-- 简写 -->
<a :[attributeName]="url"> ... </a>
这里的 attributeName
会作为一个 JavaScript 表达式被动态执行,计算得到的值会被用作最终的参数。举例来说,如果你的组件实例有一个数据属性 attributeName
,其值为 "href"
,那么这个绑定就等价于 v-bind:href
。
相似地,你还可以将一个函数绑定到动态的事件名称上:
<a v-on:[eventName]="doSomething"> ... </a>
<!-- 简写 -->
<a @[eventName]="doSomething">
在此示例中,当 eventName
的值是 "focus"
时,v-on:[eventName]
就等价于 v-on:focus
。
-
- 动态参数值的限制
动态参数中表达式的值应当是一个字符串,或者是 null
。特殊值 null
意为显式移除该绑定。其他非字符串的值会触发警告。
-
- 动态参数语法的限制
动态参数表达式因为某些字符的缘故有一些语法限制,比如空格和引号,在 HTML attribute 名称中都是不合法的。例如下面的示例:
<!-- 这会触发一个编译器警告 -->
<a :['foo' + bar]="value"> ... </a>
如果你需要传入一个复杂的动态参数,我们推荐使用计算属性替换复杂的表达式,也是 Vue 最基础的概念之一。
当使用 DOM 内嵌模板 (直接写在 HTML 文件里的模板) 时,我们需要避免在名称中使用大写字母,因为浏览器会强制将其转换为小写:
<a :[someAttr]="value"> ... </a>
上面的例子将会在 DOM 内嵌模板中被转换为 :[someattr]
。如果你的组件拥有 “someAttr” 属性而非 “someattr”,这段代码将不会工作。单文件组件内的模板不受此限制。
- 修饰符 Modifiers
修饰符是以点开头的特殊后缀,表明指令需要以一些特殊的方式被绑定。例如 .prevent
修饰符会告知 v-on
指令对触发的事件调用 event.preventDefault()
:
<form @submit.prevent="onSubmit">...</form>
Vue内置指令
官方文档地址:(内置指令 | Vue.js (vuejs.org))
- v-text
更新元素的文本内容。
-
- 期望的绑定值类型:
string
- 期望的绑定值类型:
-
- 详细信息
v-text
通过设置元素的textContent属性来工作,因此它将覆盖元素中所有现有的内容。如果你需要更新textContent
的部分,应该使用mustache interpolations 代替。 -
- 示例
<span v-text="msg"></span> <!-- 等同于 --> <span>{{msg}}</span>
- v-html
更新元素的innerHTML。
-
- 期望的绑定值类型:
string
- 期望的绑定值类型:
-
- 详细信息
v-html
的内容直接作为普通 HTML 插入—— Vue 模板语法是不会被解析的。如果你发现自己正打算用 v-html
来编写模板,不如重新想想怎么使用组件来代替。
在单文件组件,scoped
样式将不会作用于 v-html
里的内容,因为 HTML 内容不会被 Vue 的模板编译器解析。如果想让 v-html
的内容也支持 scoped CSS,可以使用CSS modules或使用一个额外的全局 <style>
元素,手动设置类似 BEM 的作用域策略。
-
- 示例
<div v-html="html"></div>
- v-show
基于表达式值的真假性,来改变元素的可见性。
-
- 期望的绑定值类型:
any
- 期望的绑定值类型:
-
- 详细信息
v-show
通过设置内联样式的display
CSS 属性来工作,当元素可见时将使用初始display
值。当条件改变时,也会触发过渡效果。
- v-if
基于表达式值的真假性,来条件性地渲染元素或者模板片段。
-
- 期望的绑定值类型:
any
- 期望的绑定值类型:
-
- 详细信息
当
v-if
元素被触发,元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假,那么其内部的内容根本都不会被渲染。可用于
<template>
表示仅包含文本或多个元素的条件块。当条件改变时会触发过渡效果。
当同时使用时,
v-if
比v-for
优先级更高。不过并不推荐在一元素上同时使用这两个指令。
- v-else
表示 v-if
或 v-if
/ v-else-if
链式调用的“else 块”。
-
- 无需传入表达式
-
- 详细信息
- 限定:上一个兄弟元素必须有
v-if
或v-else-if
。 - 可用于
<template>
表示仅包含文本或多个元素的条件块。
-
- 示例
<div v-if="Math.random() > 0.5"> Now you see me </div> <div v-else> Now you don't </div>
- v-else-if
表示 v-if
的“else if 块”。可以进行链式调用。
-
- 期望的绑定值类型:
any
- 期望的绑定值类型:
-
- 详细信息
- 限定:上一个兄弟元素必须有
v-if
或v-else-if
。 - 可用于
<template>
表示仅包含文本或多个元素的条件块。
-
示例
<div v-if="type === 'A'"> A </div> <div v-else-if="type === 'B'"> B </div> <div v-else-if="type === 'C'"> C </div> <div v-else> Not A/B/C </div>
- v-for
基于原始数据多次渲染元素或模板块。
-
- 期望的绑定值类型:
Array | Object | number | string | Iterable
- 期望的绑定值类型:
-
- 详细信息
指令值必须使用特殊语法
alias in expression
为正在迭代的元素提供一个别名:<div v-for="item in items"> {{ item.text }} </div>
或者,你也可以为索引指定别名 (如果用在对象,则是键值):
<div v-for="(item, index) in items"></div> <div v-for="(value, key) in object"></div> <div v-for="(value, name, index) in object"></div>
v-for
的默认方式是尝试就地更新元素而不移动它们。要强制其重新排序元素,你需要用特殊 attributekey
来提供一个排序提示:<div v-for="item in items" :key="item.id"> {{ item.text }} </div>
v-for
也可以用于 Iterable Protocol 的实现,包括原生Map
和Set
。
- v-on
给元素绑定事件监听器。
-
- 缩写:
@
- 缩写:
-
- 期望的绑定值类型:
Function | Inline Statement | Object (不带参数)
- 期望的绑定值类型:
-
- 参数:
event
(使用对象语法则为可选项)
- 参数:
-
- 修饰符
.stop
- 调用event.stopPropagation()
。.prevent
- 调用event.preventDefault()
。.capture
- 在捕获模式添加事件监听器。.self
- 只有事件从元素本身发出才触发处理函数。.{keyAlias}
- 只在某些按键下触发处理函数。.once
- 最多触发一次处理函数。.left
- 只在鼠标左键事件触发处理函数。.right
- 只在鼠标右键事件触发处理函数。.middle
- 只在鼠标中键事件触发处理函数。.passive
- 通过{ passive: true }
附加一个 DOM 事件。
-
- 详细信息
事件类型由参数来指定。表达式可以是一个方法名,一个内联声明,如果有修饰符则可省略。
当用于普通元素,只监听原生 DOM 事件。当用于自定义元素组件,则监听子组件触发的自定义事件。
当监听原生 DOM 事件时,方法接收原生事件作为唯一参数。如果使用内联声明,声明可以访问一个特殊的
$event
变量:v-on:click="handle('ok', $event)"
。v-on
还支持绑定不带参数的事件/监听器对的对象。请注意,当使用对象语法时,不支持任何修饰符。 -
- 示例
<!-- 方法处理函数 --> <button v-on:click="doThis"></button> <!-- 动态事件 --> <button v-on:[event]="doThis"></button> <!-- 内联声明 --> <button v-on:click="doThat('hello', $event)"></button> <!-- 缩写 --> <button @click="doThis"></button> <!-- 使用缩写的动态事件 --> <button @[event]="doThis"></button> <!-- 停止传播 --> <button @click.stop="doThis"></button> <!-- 阻止默认事件 --> <button @click.prevent="doThis"></button> <!-- 不带表达式地阻止默认事件 --> <form @submit.prevent></form> <!-- 链式调用修饰符 --> <button @click.stop.prevent="doThis"></button> <!-- 按键用于 keyAlias 修饰符--> <input @keyup.enter="onEnter" /> <!-- 点击事件将最多触发一次 --> <button v-on:click.once="doThis"></button> <!-- 对象语法 --> <button v-on="{ mousedown: doThis, mouseup: doThat }"></button>
监听子组件的自定义事件 (当子组件的“my-event”事件被触发,处理函数将被调用):
<MyComponent @my-event="handleThis" /> <!-- 内联声明 --> <MyComponent @my-event="handleThis(123, $event)" />
- v-bind
动态的绑定一个或多个 attribute,也可以是组件的 prop。
-
- 缩写:
:
或者.
(当使用.prop
修饰符)
- 缩写:
-
- 期望:
any (带参数) | Object (不带参数)
- 期望:
-
- 参数:
attrOrProp (可选的)
- 参数:
-
- 修饰符
.camel
- 将短横线命名的 attribute 转变为驼峰式命名。.prop
- 强制绑定为 DOM property。3.2+.attr
- 强制绑定为 DOM attribute。3.2+
-
用途
当用于绑定
class
或style
attribute,v-bind
支持额外的值类型如数组或对象。详见下方的指南链接。在处理绑定时,Vue 默认会利用
in
操作符来检查该元素上是否定义了和绑定的 key 同名的 DOM property。如果存在同名的 property,则 Vue 会将它作为 DOM property 赋值,而不是作为 attribute 设置。这个行为在大多数情况都符合期望的绑定值类型,但是你也可以显式用.prop
和.attr
修饰符来强制绑定方式。有时这是必要的,特别是在和自定义元素打交道时。当用于组件 props 绑定时,所绑定的 props 必须在子组件中已被正确声明。
当不带参数使用时,可以用于绑定一个包含了多个 attribute 名称-绑定值对的对象。
-
- 示例
<!-- 绑定 attribute --> <img v-bind:src="imageSrc" /> <!-- 动态 attribute 名 --> <button v-bind:[key]="value"></button> <!-- 缩写 --> <img :src="imageSrc" /> <!-- 缩写形式的动态 attribute 名 --> <button :[key]="value"></button> <!-- 内联字符串拼接 --> <img :src="'/path/to/images/' + fileName" /> <!-- class 绑定 --> <div :class="{ red: isRed }"></div> <div :class="[classA, classB]"></div> <div :class="[classA, { classB: isB, classC: isC }]"></div> <!-- style 绑定 --> <div :style="{ fontSize: size + 'px' }"></div> <div :style="[styleObjectA, styleObjectB]"></div> <!-- 绑定对象形式的 attribute --> <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div> <!-- prop 绑定。“prop” 必须在子组件中已声明。 --> <MyComponent :prop="someThing" /> <!-- 传递子父组件共有的 prop --> <MyComponent v-bind="$props" /> <!-- XLink --> <svg><a :xlink:special="foo"></a></svg>
.prop
修饰符也有专门的缩写,.
:<div :someProperty.prop="someObject"></div> <!-- 等同于 --> <div .someProperty="someObject"></div>
当在 DOM 内模板使用
.camel
修饰符,可以驼峰化v-bind
attribute 的名称,例如 SVGviewBox
attribute:<svg :view-box.camel="viewBox"></svg>
如果使用字符串模板或使用构建步骤预编译模板,则不需要
.camel
。
- v-model
在表单输入元素或组件上创建双向绑定。
-
- 期望的绑定值类型:根据表单输入元素或组件输出的值而变化
-
- 仅限:
<input>
<select>
<textarea>
components
-
修饰符
- v-slot
用于声明具名插槽或是期望接收 props 的作用域插槽。
-
- 缩写:
#
- 缩写:
-
- 期望的绑定值类型:能够合法在函数参数位置使用的 JavaScript 表达式。支持解构语法。绑定值是可选的——只有在给作用域插槽传递 props 才需要。
-
- 参数:插槽名 (可选,默认是
default
)
- 参数:插槽名 (可选,默认是
-
- 仅限:
<template>
components
(用于带有 prop 的单个默认插槽)
-
- 示例
<!-- 具名插槽 --> <BaseLayout> <template v-slot:header> Header content </template> <template v-slot:default> Default slot content </template> <template v-slot:footer> Footer content </template> </BaseLayout> <!-- 接收 prop 的具名插槽 --> <InfiniteScroll> <template v-slot:item="slotProps"> <div class="item"> {{ slotProps.item.text }} </div> </template> </InfiniteScroll> <!-- 接收 prop 的默认插槽,并解构 --> <Mouse v-slot="{ x, y }"> Mouse position: {{ x }}, {{ y }} </Mouse>
- v-pre
跳过该元素及其所有子元素的编译。
-
- 无需传入
-
- 详细信息
元素内具有
v-pre
,所有 Vue 模板语法都会被保留并按原样渲染。最常见的用例就是显示原始双大括号标签及内容。 -
- 示例
<span v-pre>{{ this will not be compiled }}</span>
- v-once
仅渲染元素和组件一次,并跳过之后的更新。
-
- 无需传入
-
- 详细信息
在随后的重新渲染,元素/组件及其所有子项将被当作静态内容并跳过渲染。这可以用来优化更新时的性能。
<!-- 单个元素 --> <span v-once>This will never change: {{msg}}</span> <!-- 带有子元素的元素 --> <div v-once> <h1>comment</h1> <p>{{msg}}</p> </div> <!-- 组件 --> <MyComponent v-once :comment="msg" /> <!-- `v-for` 指令 --> <ul> <li v-for="i in list" v-once>{{i}}</li> </ul>
从 3.2 起,你也可以搭配
v-memo
的无效条件来缓存部分模板。
- v-memo
-
- 期望的绑定值类型:
any[]
- 期望的绑定值类型:
-
- 详细信息
缓存一个模板的子树。在元素和组件上都可以使用。为了实现缓存,该指令需要传入一个固定长度的依赖值数组进行比较。如果数组里的每个值都与最后一次的渲染相同,那么整个子树的更新将被跳过。举例来说:
<div v-memo="[valueA, valueB]"> ... </div>
当组件重新渲染,如果
valueA
和valueB
都保持不变,这个<div>
及其子项的所有更新都将被跳过。实际上,甚至虚拟 DOM 的 vnode 创建也将被跳过,因为缓存的子树副本可以被重新使用。正确指定缓存数组很重要,否则应该生效的更新可能被跳过。
v-memo
传入空依赖数组 (v-memo="[]"
) 将与v-once
效果相同。与
v-for
一起使用v-memo
仅用于性能至上场景中的微小优化,应该很少需要。最常见的情况可能是有助于渲染海量v-for
列表 (长度超过 1000 的情况):<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p> <p>...more child nodes</p> </div>
当组件的
selected
状态改变,默认会重新创建大量的 vnode,尽管绝大部分都跟之前是一模一样的。v-memo
用在这里本质上是在说“只有当该项的被选中状态改变时才需要更新”。这使得每个选中状态没有变的项能完全重用之前的 vnode 并跳过差异比较。注意这里 memo 依赖数组中并不需要包含item.id
,因为 Vue 也会根据 item 的:key
进行判断。警告
当搭配
v-for
使用v-memo
,确保两者都绑定在同一个元素上。v-memo
不能用在v-for
内部。v-memo
也能被用于在一些默认优化失败的边际情况下,手动避免子组件出现不需要的更新。但是一样的,开发者需要负责指定正确的依赖数组以免跳过必要的更新。
- v-cloak
用于隐藏尚未完成编译的 DOM 模板。
-
无需传入
-
详细信息
该指令只在没有构建步骤的环境下需要使用。
当使用直接在 DOM 中书写的模板时,可能会出现一种叫做“未编译模板闪现”的情况:用户可能先看到的是还没编译完成的双大括号标签,直到挂载的组件将它们替换为实际渲染的内容。
v-cloak
会保留在所绑定的元素上,直到相关组件实例被挂载后才移除。配合像[v-cloak] { display: none }
这样的 CSS 规则,它可以在组件编译完毕前隐藏原始模板。 -
示例
[v-cloak] { display: none; }
<div v-cloak> {{ message }} </div>
直到编译完成前,
<div>
将不可见。
Vue中事件处理细节
生命周期图示
路由的匹配语法
大多数应用都会使用 /about
这样的静态路由和 /users/:userId
这样的动态路由,就像我们刚才在动态路由匹配中看到的那样,但是 Vue Router 可以提供更多的方式!
TIP:为了简单起见,所有的路由都省略了 component
属性,只关注 path
值。
在参数中自定义正则
当定义像 :userId
这样的参数时,我们内部使用以下的正则 ([^/]+)
(至少有一个字符不是斜杠 /
)来从 URL 中提取参数。这很好用,除非你需要根据参数的内容来区分两个路由。想象一下,两个路由 /:orderId
和 /:productName
,两者会匹配完全相同的 URL,所以我们需要一种方法来区分它们。最简单的方法就是在路径中添加一个静态部分来区分它们:
const routes = [
// 匹配 /o/3549
{ path: '/o/:orderId' },
// 匹配 /p/books
{ path: '/p/:productName' },
]
但在某些情况下,我们并不想添加静态的 /o
/p
部分。由于,orderId
总是一个数字,而 productName
可以是任何东西,所以我们可以在括号中为参数指定一个自定义的正则:
const routes = [
// /:orderId -> 仅匹配数字
{ path: '/:orderId(\\d+)' },
// /:productName -> 匹配其他任何内容
{ path: '/:productName' },
]
现在,转到 /25
将匹配 /:orderId
,其他情况将会匹配 /:productName
。routes
数组的顺序并不重要!
确保转义反斜杠( \
),就像我们对 \d
(变成\\d
)所做的那样,在 JavaScript 中实际传递字符串中的反斜杠字符。
可重复的参数
如果你需要匹配具有多个部分的路由,如 /first/second/third
,你应该用 *
(0 个或多个)和 +
(1 个或多个)将参数标记为可重复:
const routes = [
// /:chapters -> 匹配 /one, /one/two, /one/two/three, 等
{ path: '/:chapters+' },
// /:chapters -> 匹配 /, /one, /one/two, /one/two/three, 等
{ path: '/:chapters*' },
]
这将为你提供一个参数数组,而不是一个字符串,并且在使用命名路由时也需要你传递一个数组:
// 给定 { path: '/:chapters*', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 产生 /
router.resolve({ name: 'chapters', params: { chapters: ['a', 'b'] } }).href
// 产生 /a/b
// 给定 { path: '/:chapters+', name: 'chapters' },
router.resolve({ name: 'chapters', params: { chapters: [] } }).href
// 抛出错误,因为 `chapters` 为空
这些也可以通过在右括号后添加它们与自定义正则结合使用:
const routes = [
// 仅匹配数字
// 匹配 /1, /1/2, 等
{ path: '/:chapters(\\d+)+' },
// 匹配 /, /1, /1/2, 等
{ path: '/:chapters(\\d+)*' },
]
Sensitive 与 strict 路由配置
默认情况下,所有路由是不区分大小写的,并且能匹配带有或不带有尾部斜线的路由。例如,路由 /users
将匹配 /users
、/users/
、甚至 /Users/
。这种行为可以通过 strict
和 sensitive
选项来修改,它们既可以应用在整个全局路由上,又可以应用于当前路由上:
const router = createRouter({
history: createWebHistory(),
routes: [
// 将匹配 /users/posva 而非:
// - /users/posva/ 当 strict: true
// - /Users/posva 当 sensitive: true
{ path: '/users/:id', sensitive: true },
// 将匹配 /users, /Users, 以及 /users/42 而非 /users/ 或 /users/42/
{ path: '/users/:id?' },
],
strict: true, // applies to all routes
})
可选参数
你也可以通过使用 ?
修饰符(0 个或 1 个)将一个参数标记为可选:
const routes = [
// 匹配 /users 和 /users/posva
{ path: '/users/:userId?' },
// 匹配 /users 和 /users/42
{ path: '/users/:userId(\\d+)?' },
]
请注意,*
在技术上也标志着一个参数是可选的,但 ?
参数不能重复。
如果你需要探究你的路由是如何转化为正则的,以了解为什么一个路由没有被匹配,或者,报告一个 bug,你可以使用路径排名工具。它支持通过 URL 分享你的路由。