vue学习
在最近的一次项目中,前端js框架里使用了vue
。vue的编程思想比较新颖,用起来也感觉高效便捷。由于个人原因,做完这个项目之后可能就接触不到前端的内容了,所以记录这个项目中对vue
的学习和理解。对自己的要求是可以不记得具体的技术,但是要记住这个框架的思想。
vue简介
vue是一个JavaScript框架,最大的特点是响应式
响应式原理
:意思就是在改变数据的时候,视图会跟着更新
对比jquery来说,jquery是通过语法操作html中的标签,从而改变标签的属性,值等。而vue是需要改变的html标签绑定一个变量,当变量发生变化时html中的标签的值也发生变化。
vue3.0安装
1. 安装node
sudo tar xf node-v14.16.1-linux-x64.tar.xz -C /opt
cd /opt/node-v14.16.1-linux-x64/bin
sudo ln -s /opt/node-v14.16.1-linux-x64/bin/node /usr/local/bin/node
sudo ln -s /opt/node-v14.16.1-linux-x64/bin/npm /usr/local/bin/npm
# 验证版本
node -v
npm -v
2. 装淘宝镜像
npm install -g cnpm --registry=https://registry.npm.taobao.org
sudo ln -s /opt/node-v14.16.1-linux-x64/bin/cnpm /usr/local/bin/cnpm
cnpm -v
3. vue环境安装
cnpm -g vue @vue/cli
sudo ln -s /opt/node-v14.16.1-linux-x64/bin/vue /usr/local/bin/vue
vue -V
vue3.0要求4.5以上的版本
4. 创建新项目
vue create todolist
启动项目
访问页面
vue 项目架构
一个新建的vue项目架构如下:
assets: 存放静态资源、图标、图片
components:存放一般组件,公共组件,通用组件
router:配置路由。vue中实现页面跳转需要定义的路由
sotre: 配置状态管理
views:vue 主要业务实现的位置
App.vue:页面的入口文件,通常是入口页面。上面的首页就是该文件中的。
组件
组件是vue功能的集合。可以把每一个.vue
文件看成一个组件,包括App.vue也是一个组件,一个关于主页面的组件。组件的结构由三部分组成:
- template
- script
- style
<template>
</template>
<script>
</script>
<style scoped lang="scss">
</style>
组件结构
一个组件包括很多内容,大体上可以分为:
defineComponent
引入- 子组件引入(可选)
- 组件定义
- 组件名称
- 接收父组件的数据(可选)
- 定义子组件(可选)
- setup 定义变量和函数
setup中定义变量和函数,最后都要通过return返回,只有return中返回的变量才能被页面上响应。
//------------------------数据的展示------------------------
<template>
</template>
//--------------------------数据的定义和返回---------------------
<script>
// 所有的组件都需要从vue中定义
import {defineComponent} from 'vue'
import NavHeader from '@/components/navHeader/NavHeader'
// 调用defineComponent中的组件
export default defineComponent({
name: 'Home', // 组件名称
// 接收父组件的数据
props: {
},
// 定义子组件
components:{
},
setup(props, ctx) {
// 数字
let num = ref(10)
return {
num
}
}
})
</script>
<style scoped lang='scss'>
</style>
数据结构
vue中常见的数据结构包括:
- 数字
- 字符串
- 字典
- 数组
ref
用来定义响应式变量,只可以定义单个变量
reactive
用来打包定义变量,可以定义多个变量
数据定义
//------------------------数据的展示------------------------
<template>
<div>
<nav-header></nav-header>
<nav-main></nav-main>
<nav-footer></nav-footer>
{{ data.num }}
{{ data.name }}
{{ data.arr.slice(0,2) }}
{{ data.obj }}
</div>
</template>
//--------------------------数据的定义和返回---------------------
<script>
// 所有的组件都需要从vue中定义
import {defineComponent, ref, reactive} from 'vue'
// 调用defineComponent中的组件
export default defineComponent({
name: 'Home', // 组件名称
// 接收父组件的数据
props: {
},
// 定义子组件
components:{
NavHeader,
NavMain,
NavFooter
},
setup(props, ctx) {
// 数字
let num = ref(10)
// 字符串
let name = ref('jack')
// 数组
let arr = ref(['a', 'b', 'c', 'd'])
// 字典
let obj = ref({age: 20})
// reactive 方法
let data = reactive({
name: 'jack',
age: 20,
ojb: {
price: 20
},
arr: ['a', 'b', 'c', 'd']
})
return {
data
}
}
})
</script>
<style scoped lang='scss'>
</style>
在实际的编程过程中,似乎不需要用关键字ref
和 reactive
。如在未来网络学院的开发过程中,一个vue文件定义的相应式变量如下:
return {
tableConfig: {
config: tableConfigBase(),
tableData: [],
selectedRows: [],
},
createModal: {
visible: false
},
createForm: {
dialog_title: '添加讲师',
name: '',
title: '',
profile: '',
img: [],
},
searchForm: {
keywords: ''
},
createRules: {
name: [
{ required: true, message: '请输入名称', trigger: 'change' },
{ min: 2, max: 20, message: '长度在 2~20 个字符内', trigger: 'change' },
{validator: nameUniqueAcq, trigger: 'blur'},
{validator: urlAcq, trigger: 'blur'},
],
title: [
{ required: true, message:'请输入头衔', trigger: 'change' },
{ min: 1, max: 500, message: '长度在 2~500 个字符内', trigger: 'change' },
],
profile: [
{ required: false, message: '长度在 200 个字符内', trigger: 'change' },
],
img: [
{ required: true, message: '请上传图片', trigger: 'change'}
],
},
pager:{},
disabledDel: 'disabled',
disableDis: 'disabled',
disableEn: 'disabled',
}
vue基础语法
指令
Vue.js的指令是以v-开头的,它们作用于HTML元素,指令提供了一些特殊的特性,将指令绑定在元素上时,指令会为绑定的目标元素添加一些特殊的行为,我们可以将指令看作特殊的HTML特性(attribute)。
作用
:
指令的作用是当表达式的值改变时,相应地将某些行为应用到 DOM 上。
v-if
v-if可以实现条件渲染,Vue会根据表达式的值的真假条件来渲染元素。
<a v-if="ok">yes</a>
如果属性值ok为true,则显示。否则,不会渲染这个元素。
v-else
v-else是搭配v-if使用的,它必须紧跟在v-if或者v-else-if后面,否则不起作用。
<div>ok的值为:{{ ok }}</div>
<div v-if='ok'>如果值为true显示</div>
<div v-else>如果值为false显示</div>
v-for
用v-for指令根据遍历数组来进行渲染
两种渲染方式
<div v-for="(item,index) in items"></div> //使用in,index是一个可选参数,表示当前项的索引
<div v-for="item of items"></div> //使用of
let list = ref([11,23,34,45,56])
<div v-for='(index, item) in list'>
<span>{{ item }}--{{ index }}</span>
</div>
v-model
这个指令用于在表单上创建双向数据绑定。限制在<input>
、<select>
、<textarea>
、components
中使用
<input v-model='input_value' @keydown.enter="enter"/>
let input_value = ref('')
let enter = () => {
console.log(input_value.value)
}
![image_1f5i6uiup1irig8hr8pdd4fr2q.png-21.3kB][15]
<1> lazy 默认情况下,v-model同步输入框的值和数据。可以通过这个修饰符,转变为在change事件再同步。
<input v-model.lazy="msg">
<2> number 自动将用户的输入值转化为数值类型
<input v-model.number="msg">
<3> trim:自动过滤用户输入的首尾空格
<input v-model.trim="msg">
v-on
v-on主要用来监听dom事件,以便执行一些代码块。表达式可以是一个方法名。
简写为:@
<button @click='change_ok'>点击按钮</button>
let change_ok = () => {
console.log('00000')
ok.value = !ok.value
}
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a>
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>
<!-- 点击事件将只会触发一次 -->
<a v-on:click.once="doThis"></a>
<!-- 滚动事件的默认行为 (即滚动行为) 将会立即触发 -->
<!-- 而不会等待 `onScroll` 完成 -->
<!-- 这其中包含 `event.preventDefault()` 的情况 -->
<div v-on:scroll.passive="onScroll">...</div>
v-bind
v-bind用来动态的绑定一个或者多个特性。没有参数时,可以绑定到一个包含键值对的对象。常用于动态绑定class和style。以及href等。
简写为一个冒号 :
<a v-bind:href='herf'>百度</a>
<img :src='img_url' alt='leishen'>
let img_url = ref('https://image-1300284638.cos.ap-nanjing.myqcloud.com/leishen.jpeg')
常用的v-bind
标签
- v-bind:style
- v-bind:title
- v-bind:src
v-html
:
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML ,你需要使用 v-html 指令:
<div v-html="rawHtml"></div>
这个 div 的内容将会被替换成为属性值 rawHtml,直接作为 HTML——会忽略解析属性值中的数据绑定。
动作监听
动作监听的流程:
- 标签上定义监听函数
@click='监听函数名'
- setup中实现监听函数
- setup中return监听函数
<template>
<!-- <one></one> -->
<button @click='alert_action'>点击事件</button>
</template>
<script>
import {defineComponent, ref, reactive, computed} from 'vue'
import one from '@/components/one/One'
export default defineComponent({
name: 'Main', // 组件名称
components:{
one
},
setup() {
let alert_action = () => {
alert('你点击了我')
}
return {
alert_action
}
}
})
</script>
<style scoped lang="scss">
</style>
vue全局使用
引用组件
子组件可以定义在components
中,而views中往往定义父组件。在父组件中使用子组件的流程为:
- 子组件定义
<template>
<div>这是子组件</div>
</template>
<script>
import {defineComponent, ref, reactive, computed} from 'vue'
export default defineComponent({
name: 'One', // 组件名称
setup() {
}
})
</script>
<style scoped lang="scss">
</style>
- 父组件定义
<template>
<one></one>
</template>
<script>
import {defineComponent, ref, reactive, computed} from 'vue'
// 从components中的one文件夹下的One.vue中引入
import one from '@/components/one/One'
export default defineComponent({
name: 'Main', // 组件名称
// 组件本地化
components:{
one
},
setup() {
}
})
</script>
<style scoped lang="scss">
</style>
父子组件中传递数据
- 父组件传递给子组件
在父组件使用的子组件中加入参数传递:num='num'
<template>
<one :num='num'></one>
</template>
<script>
import {defineComponent, ref, reactive, computed} from 'vue'
import one from '@/components/one/One'
export default defineComponent({
name: 'Main', // 组件名称
components:{
one
},
setup() {
let alert_action = () => {
alert('你点击了我')
}
let num = ref(100)
return {
alert_action,
num
}
}
})
</script>
<style scoped lang="scss">
</style>
子组件中props
用来接收父组件的传参,定义好变量,然后直接使用
<template>
<div>这是子组件</div>
<div>父组件传递过来的是: {{ num }}</div>
</template>
<script>
import {defineComponent, ref, reactive, computed} from 'vue'
export default defineComponent({
name: 'One', // 组件名称
props: {
num:{
type: Number
}
},
setup(props) {
}
})
</script>
<style scoped lang="scss">
</style>
- 子组件传递给父组件
子组件传值给父组件叫事件分发
。通过ctx.emit分发事件
- 子组件
<template>
<div>这是子组件</div>
<div>父组件传递过来的是: {{ num }}</div>
<button @click='send'>点击传递参数</button>
</template>
<script>
import {defineComponent, ref, reactive, computed} from 'vue'
export default defineComponent({
name: 'One', // 组件名称
props: {
num:{
type: Number
}
},
setup(props, ctx) {
let send = () => {
// 使用事件分发
ctx.emit("send", 200)
}
return {
send
}
}
})
</script>
<style scoped lang="scss">
</style>
父组件
<template>
// 父组件接收send事件,将其在send_father函数中处理
<one :num='num' @send='send_father'></one>
<div>父组件收到的子组件的传参: {{ recv_value }}</div>
</template>
<script>
import {defineComponent, ref, reactive, computed} from 'vue'
import one from '@/components/one/One'
export default defineComponent({
name: 'Main', // 组件名称
components:{
one
},
setup() {
let alert_action = () => {
alert('你点击了我')
}
let num = ref(100)
let recv_value = ref(0)
let send_father = (val) => {
recv_value.value = val
}
return {
alert_action,
num,
send_father,
recv_value
}
}
})
</script>
<style scoped lang="scss">
</style>
全局共享变量之状态管理
在store中的index.js中可以定义全局使用的变量和方法。全局使用指的是所有组件可以修改,共享使用。
使用这些变量或者函数的方法是:
import {useStore} from 'vuex'
setup(props, ctx) {
let store = useStore()
//
store.commit('addTodo',{
title: value.value,
complete: false
}
路由
在router文件夹下存放着vue的路由信息
路由就是页面的静态路由。
根路由:每一个vue项目都有一个根路由,该路由的主要作用是页面的默认路由。
{
path: '/',
name: 'Start',
component: Start
},
path: 路由路径
name:路由的名字
component: 该路由对应的组件。该组件必须先引入进来,并且是只需要显示引入的。其他组件由于不需要立刻显示,所以使用懒加载,即不立即加载到vue项目中。
其他路由的加载
{
path: '/home',
name: 'Home',
component: () => import('../views/Home.vue')
},
路由的使用
import {useRouter} from 'vue-router'
setup(props, ctx) {
// router是全局路由对象
let router = useRouter()
let start = () =>{
// 使用push的方式,跳转路由
router.push({
// 路由的目的地可以用name:模块
name:'Home',
// 也可以是path:路径。两者二选一
path: '/home'
})
}
}
路由的传参
路由的传参有两种方式,分别是
- query 类似于get,显示传参,地址栏可以看到参数,刷新保存参数
- params 类似于post,地址栏看不到参数,刷新不保存参数
query
setup(props, ctx) {
// router是全局路由对象
let router = useRouter()
let start = () =>{
// 使用push的方式,跳转路由
router.push({
name:'Home',
query:{
name: name.value,
num: num.value,
}
})
}
}
使用
import {useRouter, useRoute} from 'vue-router'
// route是当前路由对象
let route = useRoute()
console.log(route.query)
有一点需要注意数字类型的参数会变成字符串类型
params
params传参只能用name来路由
let start = () =>{
router.push({
name:'Home',
params:
{
name: name.value,
num: num.value,
}
})
}
接收参数:
import {useRouter, useRoute} from 'vue-router'
// route是当前路由对象
let route = useRoute()
console.log(route.params)
插槽
定义
:
插槽就是子组件中的提供给父组件使用的一个占位符,用
https://www.cnblogs.com/mandy-dyf/p/11528505.html
vue2 和vue3的区别
建立数据 data
这里就是Vue2与Vue3 最大的区别 — Vue2使用选项类型API(Options API)对比Vue3合成型API(Composition API)
选项类型
:
旧的选项型API在代码里分割了不同的属性(properties):data,computed属性,methods
合成类型
:
新的合成型API能让我们用方法(function)来分割,相比于旧的API使用属性来分组,这样代码会更加简便和整洁。
vue2
export default {
props: {
title: String
},
data () {
return {
username: '',
password: ''
}
},
methods: {
login () {
// 登陆方法
}
}
}
Vue2 的选项型API是把methods分割到独立的属性区域的。我们可以直接在这个属性里面添加方法来处理各种前端逻辑。
vue3
export default {
props: {
title: String
},
setup () {
const state = reactive({
username: '',
password: ''
})
const login = () => {
// 登陆方法
}
return {
login,
state
}
}
}
Vue3 的合成型API里面的setup()方法也是可以用来操控methods的。创建声名方法其实和声名数据状态是一样的。— 我们需要先声名一个方法然后在setup()方法中返回(return), 这样我们的组件内就可以调用这个方法了。
项目代码结构
所有的vue代码都写在views当中
公共组件存放在components中
有关网络请求的axios都存放在network中
页面跳转路由存放在router中
vue在实际项目中的使用技巧
vue与后端交互
vue通过 axios
与后端交互。通过引入axios中的各种方法,完成网络请求
import { fetch, post, del, postForm } from '../base/axios'
const URL = {
listUrl: '/page/list/',
createUrl: '/page/create/',
delUrl: '/page/delete/',
disableUrl: '/page/disable/',
enableUrl: '/page/enable/',
pageStateChange: '/page/page_state_change/',
getFiles: '/page/get_files/'
}
export function pageList (data) {
return fetch(URL.listUrl, data)
}
export function pageCreate(form) {
return postForm(URL.createUrl, form)
}
export function pageDelete(page_id) {
return del(URL.delUrl + page_id + '/')
}
export function pageDisable(data) {
return fetch(URL.disableUrl, data)
}
export function pageEnable(data) {
return fetch(URL.enableUrl, data)
}
export function pageStateChange(data) {
return fetch(URL.pageStateChange, data)
}
export function pageGetFiles(page_id) {
return fetch(URL.getFiles + page_id + '/')
}
普通post请求:
export function post (url, data = {}) {
return new Promise((resolve, reject) => {
// var qs = require('querystring')
axios.create({
headers: {'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': 'application/x-www-form-urlencoded'},
}).post(url, data).then(response => {
resolve(response.data)
}, err => {
reject(err)
})
})
}
form-data的请求
export function postForm (url, data= {}) {
return new Promise((resolve, reject) => {
axios.create({
withCredentials: true,
headers: {'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': "multipart/form-data"},
}).post(url, data).then(response => {
resolve(response.data)
}, err => {
reject(err)
})
})
}
在vue文件中的网络请求为:
import { pageList, pageDelete, pageDisable, pageEnable, pageCreate, pageStateChange, pageGetFiles } from 'api/page/index'
pageList(data).then((res =>{
this.tableConfig.tableData = res.data;
this.pager.total = res.total
})).catch(err => {
this.$alert(`${ err }`, '提示', {
confirmButtonText: '确定',
});
});
vue 导入组件和方法
vue使用的过程中需要导入组件或者方法,方法如axios请求。导入的语法是:
- 导入组件需要使用大括号
- 导入方法需要用大括号包裹
import { useRouter } from 'vue-router'
import { tableConfigBase } from 'utils/common.js'
import { pageList, pageDelete, pageDisable, pageEnable, pageCreate, pageStateChange, pageGetFiles } from 'api/page/index'
import { showConfirmMessageBox, showMessage } from 'api/base/message'
import UploadFile from 'components/common/UploadFile'
import Pagination from 'components/common/Pagination'
导入组件是从别的文件中导入组件,除需要使用大括号之外,还需要本地引入
export default {
components: {
UploadFile,
Pagination
},
.....
}
导入方法指的是从js文件中能够导入一个函数。如 showConfirmMessageBox
,就是:
export function showConfirmMessageBox (message) {
return ElMessageBox.confirm(message, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
center: true
})
}
vue属于js的一个库,理所当然可以使用js。想要引用一个js,需要js的函数export
vue异步变同步的方法
axios是异步请求,如果想要将异步变成同步,使用async
和await
两个关键字即可。
async
修饰 vue函数
await
修饰 axios请求方法
async editRow(index, row){
this.createForm.name = row.name
this.createForm.desc = row.desc
this.createForm.page_url = row.page_url
this.createForm.edit = true
this.createForm.page_id = row.id
await pageGetFiles(row.id).then((res => {
if (res.result == 0) {
for (var i = 0; i < res.page_files.length; i++) {
this.createForm.file.push({'page_file_id': res.page_files[i].id, 'name': res.page_files[i].name})
}
}
}))
this.createModal.visible = true
},
vue中formdata的使用
formdata 用于有静态资源从vue传输到django中,如图片和文件等。使用如下:
创建: var data = new FormData()
添加:
data.append('name', this.createForm.name)
data.append('desc', this.createForm.desc)
data.append('page_url', this.createForm.page_url)
data.append('edit', this.createForm.edit)
data.append('page_id', this.createForm.page_id)
特别注意,如果添加文件也是使用中格式
data.append('new_files', file.raw)
如果添加多个文件,那么直接用同一个key,会被打包成列表
取值:data.get(key)
如果value是一个数组,取值方法: data.getAll(key)
vue代码
var data = new FormData();
data.append('name', this.createForm.name)
data.append('desc', this.createForm.desc)
data.append('page_url', this.createForm.page_url)
data.append('edit', this.createForm.edit)
data.append('page_id', this.createForm.page_id)
this.createForm.file.forEach((file) => {
if (file.raw) {
data.append('new_files', file.raw)
}else{
data.append('old_files', file.page_file_id)
}
})
export function postForm (url, data= {}) {
return new Promise((resolve, reject) => {
axios.create({
withCredentials: true,
headers: {'X-CSRFToken': getCookie('csrftoken'), 'Content-Type': "multipart/form-data"},
}).post(url, data).then(response => {
resolve(response.data)
}, err => {
reject(err)
})
})
}
export function pageCreate(form) {
return postForm(URL.createUrl, form)
}
pageCreate(data)
django的接收:
# 接收到formdata的出文件之外的数据
data = request.POST
# 接收文件,getlist是接收多个文件 get是接收单个文件
new_files = request.FILES.getlist('new_files')
vue校验
在input输入框中需要一些校验,以下面的demo为例
<el-dialog title="添加页面" v-model="createModal.visible" width="800px" :before-close="closeDialog">
<div class="demo-dialog">
<el-form :model="createForm" :rules="createRules" ref="createForm" label-width="100px">
<el-form-item label="名称" prop="name">
<el-input v-model="createForm.name"></el-input>
</el-form-item>
<el-form-item label="描述" prop="desc">
<el-input v-model="createForm.desc"></el-input>
</el-form-item>
<el-form-item label="网址" prop="page_url">
<el-col>
<el-input v-model="createForm.page_url" >
<template #prepend>page/</template>
</el-input>
</el-col>
</el-form-item>
<el-form-item label="页面文件" prop="file">
<upload-file :allowed-type="['html']"
v-model:file-list="createForm.file"
:multiple="true">支持上传单个或多个html文件
</upload-file>
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="submitCancel">取 消</el-button>
<el-button type="primary" @click="submitForm('createForm')">确 定</el-button>
</span>
</template>
</el-dialog>
校验规则
createRules: {
position: [
{ required: true, message: '请选择位置', trigger: 'change' },
],
name: [
{ required: true, message: '请输入认证名称', trigger: 'change' },
{ min: 2, max: 50, message: '长度在 2~50 个字符内', trigger: 'change' },
],
desc: [
{ required: false, max: 200, message: '长度在 200 个字符内', trigger: 'change' },
],
page_url: [
{ required: true, message: '请填写链接', trigger: 'change' },
{ min: 1, max: 50, message: '长度在 1~50 个字符内', trigger: 'change' },
{validator: urlAcq, trigger: 'change'}
],
file: [
{ required: true, message: '请上传html文件', trigger: 'change' },
{validator: indexHtmlAcq, trigger: 'change'}
]
},
自定义校验 上传文件包含index.html
const indexHtmlAcq = (rule, files, callback) => {
let index_html_num = 0
files.forEach((file) =>{
if(file.name == 'index.html'){
index_html_num = index_html_num + 1
}
})
if(index_html_num == 0){
callback(new Error('必须添加一个index.html文件'))
}else if(index_html_num > 1){
callback(new Error('只能添加一个index.html文件'))
}
return callback()
}
自定义校验 输入框中只允许输入数字
,字母
,-
, _
等
const urlAcq = (rule, str, callback) =>{
const reg =/^[-_a-zA-Z0-9]+$/;
if(!reg.test(str)){
callback(new Error('url中包含特殊字符'))
}
return callback()
}
清空校验的红色提示
this.$nextTick(() => {
this.$refs["createForm"].clearValidate()
})