vue+前端速通
前端小知识
CSS Display(显示) 与 Visibility(可见性
CSS 盒子模型
CSS 选择器
p 标签选择
.p 类选择
#p id选择
[tag=value] 属性选择器
组合选择器被称为 组合器(Combinators)。一共有4种组合器:
’ ‘ 后代组合器(Descendant combinator)
’>’ 直接子代组合器(Child combinator)
’~’ 一般兄弟组合器(General sibling combinator)
’+’ 紧邻兄弟组合器(Adjacent sibling combinator)
CSS position
- 1、absolute(绝对定位),其位置相对于最近已定位的父元素,如果元素没有已定位的父元素那么它的位置相对于。因此absolute应该放在某个容器内,并且只有这一个元素。否则会相互重叠。
- 2、static(默认的静态定位),即没有定位,遵循正常的文档流对象,静态定位的元素不受top、left、right、bottom影响。
- 3、relative(相对定位),其位置相对其正常时的位置。
- relative只能根据上一个relative进行位置改变,若上一个是absolute则直接叠加在上一个上面。相对定位元素经常被用来作为绝对定位元素的容器块。
- 考虑一个父元素static元素作为容器,而其内有一个子元素relative元素改变left等位置,那么这个子元素会根据定位,而不是父元素,链接相对定位内的绝对定位CSS-Tricks - CSS-Tricks。因此absolute元素如果需要改变定位父元素不为static。
- 4、fixed,元素的位置相对于浏览器窗口,是固定位置。即使窗口是滚动的它也不会移动。
- 5、sticky(粘性定位),基于用户滚动位置来定位,在未滚动出目标区域时,它的行为就像position:relative;它的表现就像 position:fixed;,它会固定在目标位置。元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。这个特定阈值指的是 top, right, bottom 或 left 之一,换言之,指定 top, right, bottom 或 left 四个阈值其中之一,才可使粘性定位生效。否则其行为与相对定位相同。
一些和position配合使用的属性,在position为static时无效。
| 属性 | 说明 | 值 |
|---|---|---|
| bottom | 定义了定位元素下外边距边界与其包含块下边界之间的偏移,向上推 | auto *length % *inherit |
| left | 定义了定位元素左外边距边界与其包含块左边界之间的偏移。 | auto *length % *inherit |
| right | 定义了定位元素右外边距边界与其包含块右边界之间的偏移。 | auto *length % *inherit |
| top | 定义了一个定位元素的上外边距边界与其包含块上边界之间的偏移。 | auto *length % *inherit |
CSS display
块级元素(block)特性:
- 总是独占一行,表现为另起一行开始,而且其后的元素也必须另起一行显示;
- 宽度(width)、高度(height)、内边距(padding)和外边距(margin)都可控制;
内联元素(inline)特性:
- 和相邻的内联元素在同一行;
- 宽度(width)、高度(height)、内边距的top/bottom(padding-top/padding-bottom)和外边距的top/bottom(margin-top/margin-bottom)都不可改变,就是里面文字或图片的大小;
块级元素主要有:
- address , blockquote , center , dir , div , dl , fieldset , form , h1 , h2 , h3 , h4 , h5 , h6 , hr , isindex , menu , noframes , noscript , ol , p , pre , table , ul , li
内联元素主要有:
- a , abbr , acronym , b , bdo , big , br , cite , code , dfn , em , font , i , img , input , kbd , label , q , s , samp , select , small , span , strike , strong , sub , sup ,textarea , tt , u , var
可变元素(根据上下文关系确定该元素是块元素还是内联元素):
- applet ,button ,del ,iframe , ins ,map ,object , script
CSS中块级、内联元素的应用:
利用CSS我们可以摆脱上面表格里HTML标签归类的限制,自由地在不同标签/元素上应用我们需要的属性。
主要用的CSS样式有以下三个:
- display:block – 显示为块级元素
- display:inline – 显示为内联元素
- display:inline-block – 显示为内联块元素,表现为同行显示并可修改宽高内外边距等属性
我们常将所有元素加上display:inline-block样式,原本垂直的列表就可以水平显示了。
CSS 伪类
| 选择器 | 示例 | 示例说明 |
|---|---|---|
| :before | p:before | 在每个元素之前插入内容 |
| :after | p:after | 在每个元素之后插入内容 |
| :link | a:link | 选择所有未访问链接 |
| :visited | a:visited | 选择所有访问过的链接 |
| :active | a:active | 选择正在活动链接 |
| :hover | a:hover | 把鼠标放在链接上的状态 |
JS Promise
Promise 是一个 ECMAScript 6 提供的类,目的是更加优雅地书写复杂的异步任务。
const promise = new Promise((resolveCBName, rejectCBName) => {
resolveCB(42);
}).then()
-
Promise对象的构造函数,接收一个函数作为参数,这个函数有两个函数参数。注意:这两个函数参数仅仅传递状态和参数,并不对参数本身做任何操作(例如打印、包装)。并且这两个参数的名字随便起名,因为我们不能定义这两个函数参数,这两个参数仅仅为形参名字,这两个函数参数被Promise对象接管。
-
这两个函数参数作用:
-
传递状态
const promise = new Promise((resolveCBName, rejectCBName) => { resolveCBName(); //进入then rejectCBName(); //之前的状态不可逆转 console.log("init") //但是后面的代码仍然会执行 }).then(() => {console.log("fullfilled")}) .catch(() => {console.log("rejected")}) //init //fullfilledconst promise = new Promise((resolveCBName, rejectCBName) => { //resolveCBName(); rejectCBName(); //进入catch }).then(() => {console.log("fullfilled")}) .catch(() => {console.log("rejected")}) //rejected -
传递参数
const promise = new Promise((resolveCBName, rejectCBName) => { resolveCBName(1); }).then((msg) => {console.log("fullfilled "+msg)}) //then使用一个值作为参数接收一开始的resolveCBName .catch((msg) => {console.log("rejected "+msg)}) //fullfilled 1const promise = new Promise((resolveCBName, rejectCBName) => { //resolveCBName(1); rejectCBName(2); }).then((msg) => {console.log("fullfilled "+msg)}) .catch((msg) => {console.log("rejected "+msg)}) //rejected 2
-
-
注意,在传入的函数中,必须显式调用resolveCBName或rejectCBName,否则运行完构造函数参数后直接退出。
const promise = new Promise((resolveCBName, rejectCBName) => { }).then(() => { console.log("fullfilled") }).catch(() => { console.log("rejected") }) //打印为空 -
链式调用时,后面的使用return表示接受或者throw表示拒绝,亦或者新建Promise对象。
const promise = new Promise((resolveCBName, rejectCBName) => { resolveCBName() }).then(() => { return 1//或者不写return,默认返回,都是表示接受 }).then((msg) => { console.log("fullfilled " + msg) throw "2" }).catch((msg) => { console.log("rejected " + msg) }) //fullfilled 1 //rejected 2新建Promise对象
const promise = new Promise((resolveCBName, rejectCBName) => { resolveCBName() }).then(() => { return new Promise((resolveCBName, rejectCBName) => { resolveCBName(1) }) }).then((msg) => { console.log("fullfilled " + msg) return new Promise((resolveCBName, rejectCBName) => { rejectCBName(2) }) }).catch((msg) => { console.log("rejected " + msg) }) //fullfilled 1 //rejected 2 -
实际上,
catch(msg)是then(null, msg)的缩略形式。二者是等同地位的。then的两个参数分别代表接受和拒绝。可以使用then而不使用catch。const promise = new Promise((resolveCBName, rejectCBName) => { resolveCBName() }).then(() => { throw "2" }).then( (msg) => {console.log("fullfilled " + msg)}, (msg) => {console.log("rejected " + msg) }) //rejected 2如果我们省略了
then方法的第二个参数,那么在 Promise 对象被拒绝时会抛出一个未捕获的错误。因此,在使用then方法时,建议始终提供第二个参数以处理拒绝情况,或者在链式调用中使用catch方法来处理拒绝情况。
JS Module
js的模块机制经历了以下几个阶段:
- 模块模式(Module Pattern):这是最早的一种模拟模块系统的方法,使用立即执行函数表达式(IIFE)来创建一个私有作用域,然后通过返回一个对象来暴露需要公开的变量或函数。这种方法依赖于闭包和函数作用域。
- CommonJS:这是Node.js使用的一种模块系统,使用require()函数来同步地加载一个模块,并使用module.exports或exports对象来导出一个模块。这种方法依赖于文件系统和运行时环境。
- AMD:这是一种异步加载模块的方法,主要用于浏览器端。使用define()函数来定义一个模块,并使用require()函数来异步地加载一个或多个依赖模块。这种方法依赖于回调函数和网络请求。
- UMD:这是一种兼容CommonJS和AMD两种规范的通用方法,可以在不同的环境中运行。使用define()函数来检测当前环境,并根据不同情况选择合适的加载方式。
- ES6 Modules:这是目前最新和最推荐的一种原生支持的模块系统,使用import和export关键字来导入或导出一个模块,并且支持静态分析和按需加载。这种方法依赖于语言层面提供的特性。
模块模式
// 模块模式
const foo = (function () {
const sayName = (name) => {
console.log(`Hey, my name is ${name}`);
};
// 暴露公开的变量或函数
return {
callSayName: (name) => sayName(name),
};
})();
//使用
foo.callSayName('name')
CommonJS
// CommonJS
// 导出一个模块
module.exports = {
a: 1,
b: 2,
};
// 或者
exports.a = 1;
exports.b = 2;
// 导入一个模块
const foo = require("./foo.js");
ES6 Modules
// ES6 Modules
import defaultExport from "module-name";
import { default as alias } from "module-name";
import * as name from "module-name"; //作为命名空间 使用:name.doThings();
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
//同时导入default和其他,default必须放在前面
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name"; //作为副作用引入
VUE-选项式API
父组件向传子组件传递数据
<TestPage :msg=msg @injectMsg="getChildMsg" ref="testRef"></TestPage>
props
是单向的,响应式的
在父组件vue文件template中,声明
<ChildPage v-bind:childValName=passedValue></hildPage>
在子组件vue文件script中,声明
props: {
//提前定义接受的变量
msg: {
type: String,
default: 'nihao',
// required: true
}
},
provide/inject
是单向的,响应式的
在父组件vue文件script中,声明
provide() {
return {
pmsg: this.obj
}
}
在子组件vue文件script中,声明
inject:['pmsg']
$refs,不推荐
父组件可以通过 $refs 获取子组件实例,然后调用子组件的方法或访问子组件的属性。
在父组件vue文件template中,声明
<child-component ref="child"></child-component>
在父组件vue文件script中,声明
this.$refs.child.childData
子组件向父组件传递数据
$emit
子组件注册事件,父组件监听,调用回调函数。
在子组件vue文件script中,声明
methods: {
//发送父亲
sendParent: function () {
//定义事件,等待父监听
this.$emit('injectMsg', this.name);
}
},
在父组件vue文件template中,声明
<ChildPage v-on:injectMsg="getChildData"></ChildPage>
在父组件vue文件script中,声明
methods: {
//接收孩子,收到孩子返回的信息
getChildData: function (value) {
this.msg = value
}
},
使用v-on监听这个事件名称,监听到后就调用某个函数,这个函数的入参是子组件提供的。
同$refs
调用子组件的回调函数,子组件的回调函数里返回子组件数据
状态函数
| 名称 | 功能 |
|---|---|
| computed | 计算属性,根据现有的数据计算出衍生出的数据 |
| watch | 监听数据变化,执行对应的回调函数 |
| $watch | 与 watch 类似,但是是实例方法,可以直接在组件实例上调用 |
| $nextTick | 在下次 DOM 更新周期之后执行回调函数 |
| $refs | 访问组件实例或 DOM 元素 |
| provide/inject | 父组件向子组件注入依赖 |
| props | 父组件向子组件传递数据 |
| $emit | 子组件向父组件触发事件 |
| $on | 父组件监听子组件触发的事件 |
以下是这些状态函数的功能说明:
- computed:计算属性,根据现有的数据计算出衍生出的数据,当计算所依赖的数据发生变化时会自动重新计算。
- watch:监听数据变化,执行对应的回调函数,可以监听单个数据的变化,也可以监听多个数据的变化。
- $watch:与 watch 类似,但是是实例方法,可以直接在组件实例上调用,用法与 watch 相同。
- $nextTick:在下次 DOM 更新周期之后执行回调函数,通常用于在修改数据后更新 DOM 后执行一些操作。
- $refs:访问组件实例或 DOM 元素,通过 ref 属性注册,可以在组件实例上使用 $refs 访问。
- provide/inject:父组件向子组件注入依赖,提供了一种依赖注入的方式,让子组件可以访问父组件的数据或方法。
- props:父组件向子组件传递数据,通过在子组件上绑定 props 属性来实现。
- $emit:子组件向父组件触发事件,可以传递数据,父组件可以在子组件上绑定自定义事件并在回调函数中接收数据。
- $on:父组件监听子组件触发的事件,用于接收子组件触发的自定义事件,并执行对应的回调函数。
生命周期函数
| 阶段 | 函数 | 功能 |
|---|---|---|
| 创建阶段 | beforeCreate | 实例刚被创建,数据观测(data observer)和事件机制(event/watcher)均未初始化 |
| created | 实例已经创建完成,数据观测(data observer)已经初始化,但是还未挂载(mount) | |
| beforeMount | 实例即将被挂载到页面上 | |
| mounted | 实例已经被挂载到页面上,此时实例的元素可以访问到 | |
| 更新阶段 | beforeUpdate | 组件更新之前调用,此时虚拟 DOM 并未重新渲染 |
| updated | 组件更新完成之后调用,此时虚拟 DOM 已经重新渲染 | |
| 销毁阶段 | beforeUnmount | 实例即将被销毁,此时实例还可以访问 |
| unmounted | 实例已经被销毁,此时实例上的所有东西都不可访问 |
组件插空
子组件可以预留一些空位,父组件进行填入。
在子组件vue文件template中,声明
<slot name="s1" v-bind:numlist=numlist></slot>
在父组件vue文件template中,声明
<SlotContent>
<template v-slot:s1="slotMsg">
slotContent: {{ slotMsg.numlist }}
</template>
</SlotContent>
具名插空v-slot:name。对于数据,子组件使用v-bind绑定子组件数据,父组件使用一个自己定义的变量,再通过这个变量访问子组件数据。
vue-router路由
第一步:定义路由
使用js模块机制,可以单独定义在router.js
import {createRouter, createWebHashHistory, createWebHistory} from 'vue-router'
//预先导入组件与加载,不是"懒加载"。懒加载在只有使用时才加载,提升性能,是建议的方式。
//...
import Sidebar from "../views/Sidebar.vue";
// 定义一些路由
// 每个路由都需要映射到一个组件。
//router是一个数组格式,里面是路由对象
const routes = [
{//重定向路由
path: '/',
redirect: '/home'
},
{//没什么特别的路由,path指定路径,component指定组件
path: '/home',
component: Home
},
{
#1 path: '/user/:id',//动态路由,匹配/user/123 见例1
#2 name: 'user', //名字,用于跳转时指定名字 见例2
component: () => import('../views/User.vue'), //懒加载
#3 props: true, //用于绑定path里的参数和组件里的props组件 见例3
beforeEnter: (to, from, next) => { //每路守卫,根据来源和去向信息进行访问拦截
// console.log(to, from)
next()
}
},
{
#4 path: '/user/:id*', //正则匹配,匹配/user?id=1234,query形式,见例4
component: NotFound
},
{
path: '/:path(.*)*', //正则匹配,()表示path匹配的字符串,括号后表示对整个参数的正则,使用?、+、*
component: NotFound
},
{
path: '/parent',
alias: '/father', //别名
component: Parent,
children: [ //路由分组,只是路由关系,并不决定组件之间的关系,组件跳转仍需写全路径例如/parent/st1
{
path: 'st1', //相当于/parent/st1
component: ST1
},
{
path: 'st2',
component: ST2
}
]
},
{
path: '/onshow',
#5 components: { //components复数,表示同一路径下将要显示多个组件,见例5
default: OnShow,
Topbar: Topbar,
Sidebar: Sidebar
}
}
]
// 创建路由实例并传递 routes 配置
const router = createRouter({
// history函数。createWebHashHistory,createWebHistory
history: createWebHistory(),
routes, // 刚才定义的数组
})
//全球守卫
router.beforeEach((to, from, next) => {
// console.log(to, from)
next()
})
//导出
export default router;
例1 使用动态路由
父组件
<router-link to="/user/123">Go to User</router-link>
<router-view></router-view>
需要显示的组件,获取id=123
this.$route.params.id
例2 使用具名路由 + params 实现动态路由
父组件
<!--使用path后就不能再使用params,因此params必须搭配name,等同于/user/123-->
<router-link :to="{name:'user',params:{id:'123'}}">Go to User</router-link>
<router-view></router-view>
需要显示的组件,获取id=123
this.$route.params.id
例3 使用props 实现动态路由
父组件
<router-link to="/user/123">Go to User</router-link>
<router-view></router-view>
需要显示的组件,获取id=123,在script中指定props,相当于父子组件的props,可在当前组件通过this.id直接访问
export default {
props: ['id']
}
例4 使用具名路由 + query 实现动态路由
父组件
<router-link :to="{path:'/user',query:{id:'123'}}">Go to User</router-link>
<router-view></router-view>
需要显示的组件,获取id=123
this.$route.query.id
例5 实现多组件一页面
父组件需要显示的位置,这个router-view位置是和router-link位置绑定的
<router-view></router-view>
<router-view name="Topbar"></router-view>
<router-view name="Sidebar"></router-view>
需要显示的组件无需特殊处理。
第二步,在main.js中使用使用路由
const app = createApp(App)
app.use(router)
app.mount('#app')
通过js代码控制路由(编程式导航)
// route是当前活跃的路由的对象,router是全局的路由对象
this.$router.push('/parent')
// 等同于
this.$router.push({path:'/parent'})
// 使用name才能带参数,使用path不能带params
this.$router.push({name: 'user', params: {id: 1234}})
// 带query,等同于 /parent/?id=1234, 还可以使用replace替代当前,不会在history里留下记录
this.$router.push({path: '/user', query: {id: 1234}, replace: true})
//前进正数,后退负数
this.$router.go(-1)
使用js从网络获取数据
这里创建了一个存储池。
import {reactive} from "vue";
import axios from 'axios'
const store = {
state: reactive({msg: "helloworld"}),
updateMsg: function () {
this.state.msg = 'nihao'
},
updateFromNet1: function () {
//原生fetch
fetch('http://localhost:8080').then((res) => {
return res.json()
}).then((res) => {
this.state.msg = res.name
}
)
},
updateFromNet2: function () {
//三方库axios
axios.get('http://localhost:8080').then((res) => {
this.state.msg = res.data.name
})
}
}
export default store
利用reactive绑定对象,在App.vue父组件和相关子组件中可以利用provide/inject,快捷传递响应式数据。
VUEX
出现原因:大型软件数据流过于复杂,这里集中管理。
vuex具有五个概念。
| 组件 | 功能 |
|---|---|
| state | 存储应用的状态数据,可以被所有组件访问 |
| getters | 计算或过滤state中的数据,可以被所有组件使用 |
| mutations | 同步修改state中的数据,只能通过commit触发 |
| actions | 异步修改state中的数据,可以包含任意逻辑,只能通过dispatch触发 |
| modules | 将store分割成多个模块,每个模块有自己的state、getters、mutations和actions |
定义
首先使用JS模块化定义。
//store/index.js
import {createStore} from 'vuex'
import axios from 'axios'
import moduleA from './aStore'
export default createStore({
state: { //单一状态,基本数据
count: 0,
fatherName: 'Bob'
},
getters: {//相当于computed选项,对state的进一步计算
getCount(state) {
return '计数:' + state.count
},
//第二个参数代表自身
getDescripCount(state, getters) {
return 'Descrip ' + getters.getCount
}
},
mutations: {//使用这个里面的函数改变数据
//必须通过this.$store.commit('funName', payload)触发
//首个参数必须是state,第二个是args集成到一个对象
increment(state, payload) {
state.count += payload.inc
},
changeName(state, payload) {
state.fatherName = payload.args + ' ' + payload.name
}
},
actions: {//Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。
//必须通过this.$store.dispatch('funName', args)触发,本质上是对调用mutation的封装
//类似mutations的state参数, context是当前store对象
getLastNameAndChange(context, args) {
fetch('http://localhost:8081')
.then((res) => {
return res.json()
})
.then((res) => {
//必须使用mutation
context.commit('changeName', {name: res.name, args: args})
})
},
getLastName() {
axios.get('http://localhost:8081').then((res) => {
console.log(res.data.name)
})
}
},
modules: {
moduleA
}
})
vuex模块,aStore.js
export default {
state: {//通过$store.state.moduleA.age获取,多加了一个模块名称
age: 14
},
getters: {
//state: 当前节点的, getters: 当前节点的,
//rootState: 根节点的, rootGetters: 根节点的
getAllInfo(state, getters, rootState, rootGetters) {//直接通过$store.getters.getAllInfo,不需要多加
return state.age.toString() + ' ' + rootState.count + ' ' + rootGetters.getDescripCount
}
},
mutations: {//直接通过$store.commit('updateAge')
updateAge(state) {
state.age = 19
}
},
actions: {}//直接通过$store.dispatch('funcName')
}
然后在main.js中use即可。
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')
使用
<template>
<div class="home">
<div>{{ $store.state.count }}</div>
<div>{{ count }}</div>
<div>{{ $store.state.fatherName }}</div>
<div> {{ $store.getters.getCount }}</div>
<div> {{ $store.getters.getDescripCount }}</div>
<div>
<!--使用dispatch调用action-->
<button @click="$store.dispatch('getLastNameAndChange', 'John')">change father name</button>
</div>
<div>
<button @click="$store.dispatch('getLastName')">get last name in console</button>
</div>
<div>{{ $store.state.moduleA.age }}</div>
<div>{{ $store.getters.getAllInfo }}</div>
<div>
<!--使用commit调用mutation-->
<button @click="$store.commit('updateAge')">update age</button>
</div>
</div>
</template>
<script>
// @ is an alias to /src
import {mapState} from 'vuex'
export default {
name: 'HomeView',
computed: {
// 必须使用computed才能使得vuex的state和getter响应式
otherFunc: function () {
return ''
},
// //使用mapState 通过别名映射
// ...mapState({
// //count1: state => state.count
// count1: 'count'
// }),
//直接使用原来的名字
...mapState(['count'])
}
}
</script>
VUE bug排除
栈溢出
- watch中修改监控的变量
- watch直接监控ref,解决:放到箭头函数里
网页刷新后没有东西,但是服务器重新编译后重连后出现
- 异步关系错乱