Vue
约 2204 字大约 7 分钟
javascriptvue
2024-08-13
Vue 核心 API&& 组件设计
- 官方推荐使用的引用组件的命名形式
// 推荐使用小写
import Cart from "./components/Cart"; <
cart > < /cart>;
// 中间添加 -
import HelloWorld from "./components/Cart"; <
hello - world > < /hello-world>;- checkbox
<input type="checkbox" v-model="item.isActive" />一定要遵守单项数据流
父组件的数据要在父组件进行修改
element-ui 已经被 vue-cli 纳入组件库
vue add element-ui 使用按需引入
// src/plugins/element.js
import Vue from "vue";
import {
Button,
Form,
FormItem,
Input
} from "element-ui";
Vue.use(Button);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Input);
// src/main.js
import "./plugins/element.js";- element-ui form 表单的校验
rules: {
username: [{
required: true,
message: "请输入用户名"
},
{
min: 6,
max: 10,
message: "请输入6~10的用户名"
}
],
password: [{
required: true,
message: "请输入密码"
}]
}需要思考的问题
1、input 是自定义组件,它是怎么实现双向绑定?
2、FormItem 是怎么知道执行校验的,它是怎么知道 input 状态的?它是怎么获取对应数据模型的?
3、Form 是怎么进行全局校验的?它用什么办法把数据模型和校验规则传递给内部组件?
- 数据校验包
async-validator
- 数据校验包
import Schema from 'async-validator'
validator() {
// 进行校验
const rules = this.KForm.rules[this.prop]
const value = this.KForm.model[this.prop]
const descriptor = {
[this.prop]: rules
}
const schema = new Schema(descriptor)
schema.validate({
[this.prop]: value
}, errors => {
if (errors) {
this.errMessage = errors[0].message
this.errStatus = true
} else {
this.errMessage = ''
this.errStatus = false
}
})
}设计思想:
- from 绑定数据模型 添加校验规则
- formitem label prop 校验和显示错误
- input
主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中
// 父级组件
provide() {
return {
someval: '来自app.vue'
};
},
// 子组件注入 someval
inject: ["someval"]slot插槽路由传参
/page/123可是使用props:['id']拿this.id === 123命名视图(使用 name 进行区分)
有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有
sidebar(侧导航) 和main(主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果router-view没有设置名字,那么默认为default。
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s):
const router = new VueRouter({
routes: [{
path: "/",
components: {
default: Foo,
a: Bar,
b: Baz
}
}]
});- 组件内的守卫
export default {
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能! 获取组件实例 this
// 因为当守卫执行前,组件实例还没有被创建
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id 在 /foo/1 和 /foo/2 之间跳转的时候
// 由于会渲染同样的FOO组件,因此组件实例会被复用,而这个钩子会在这个情况下被调用。
// 可以访问组件实例 this
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 this
// 通常用来禁止用户在还未保存修改前突然离开。该导航可以用过next(false)来取消
}
};vuex mapActions将两者进行映射 简化代码
你在组件中使用 this.$store.dispatch('xxx') 分发 action,或者使用 mapActions 辅助函数将组件的 methods 映射为 store.dispatch 调用(需要先在根节点注入 store ):
import {
mapActions
} from "vuex";
export default {
// ...
methods: {
...mapActions([
"increment", // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
"incrementBy" // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: "increment" // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
};Vue 源码解析

简化:

Vue 项目实战
- 权限控制
// 路由守卫
router.beforeEach((to, from, next) => {
if (to.meta.auth) {
// 需要登录
const token = localStorage.getItem("test-token");
if (token) {
next();
} else {
next({
path: "/login",
query: {
redirect: to.path
}
});
}
} else {
next();
}
});// 用户拦截请求和响应
import axios from "axios";
export default function(vm) {
// 设置请求拦截器
axios.interceptors.request.use(config => {
// 获取token
const token = localStorage.getItem("token");
if (token) {
// 如果存在令牌这添加token请求头
config.headers.Authorization = "Bearer " + token;
}
return config;
});
// 响应拦截器
// 参数1表示成功响应
// 这里只关心失败响应
axios.interceptors.response.use(null, err => {
if (err.response.status === 401) {
// 没有登录或者令牌过期
// 清空vuex和localstorage
vm.$store.dispatch("logout");
// 跳转login
vm.$router.push("/login");
}
return Promise.reject(err);
});
}const Koa = require("koa");
const Router = require("koa-router");
// 生成令牌、验证令牌
const jwt = require("jsonwebtoken");
const jwtAuth = require("koa-jwt");
// 生成数字签名的秘钥
const secret = "it's a secret";
const app = new Koa();
const router = new Router();
router.get("/api/login", async ctx => {
const {
username,
passwd
} = ctx.query;
console.log(username, passwd);
if (username == "kaikeba" && passwd == "123") {
// 生成令牌
const token = jwt.sign({
data: {
name: "kaikeba"
}, // 用户信息数据
exp: Math.floor(Date.now() / 1000) + 60 * 60 // 过期时间
},
secret
);
ctx.body = {
code: 1,
token
};
} else {
ctx.status = 401;
ctx.body = {
code: 0,
message: "用户名或者密码错误"
};
}
});
router.get("/api/userinfo", jwtAuth({
secret
}), async ctx => {
ctx.body = {
code: 1,
data: {
name: "jerry",
age: 20
}
};
});
app.use(router.routes());
app.listen(3000);bearer token
Authorization: Bearer
<token>jwt
head.payload.hash
head:type, alr
payload:josn
hash
Axios模块
安装
yarn add axios封装引入
1、分离请求地址配置 src\config\AjaxPath.js
// 通过环境变量判断
const BASEURL =
process.env.NODE_ENV === 'development' ?
'/data/' :
'http://xxx.xxx.xxx.xxx:xxxx/xxx'
// ajax请求地址
const AJAX_PATH = {
getNavMenu: BASEURL + 'NavMenu', // 获取权限菜单信息
getUserInfo: BASEURL + 'UserInfo', //获取用户信息
}
export default AJAX_PATH2、封装axios模块 src\utils\http.js
import axios from 'axios'
import router from '../router/index.js'
const service = axios.create({
timeout: 1000 * 30,
withCredentials: true,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Access-Control-Allow-Origin': '*'
}
})
/*
* 请求拦截
*/
service.interceptors.request.use(
config => {
// 在请求头添加与服务端协商好的token字段
config.headers['token'] = JSON.parse(localStorage.getItem('xxx-token'))
return config
},
error => {
return Promise.reject(error)
}
)
/**
* 响应拦截
*/
service.interceptors.response.use(
response => {
if (response.data && response.data.code === 401) {
// 401, token失效
localStorage.removeItem('xxxx-token')
router.push({
name: 'Login'
})
}
if (response.data && response.data.code === 302) {
router.push('/')
}
return response
},
error => {
if (error.status === 404) {
router.push({
name: '404'
})
}
return Promise.reject(error)
}
)
export default service3、在Vue中引入 src\main.js
import Http from './utils/http.js'
Vue.prototype.$http = HttpAPI
Vue.extend()
v-if、v-for绑定在同一元素怎么办,优先级?
2.x 版本中在一个元素上同时使用 v-if 和 v-for 时, v-for 会优先作用。
3.x 版本中 v-if 总是优先于 v-for 生效。
eslint 可以设置规则 vue/no-use-v-if-with-v-for 进行避免。
参考文章:
为什么这么处理
在 Vue 3 源码中,处理 v-for 和 v-if 指令的逻辑主要位于编译器部分,这包括将模板编译成渲染函数的代码。这些功能通常分布在几个不同的文件中,主要集中在编译器(compiler)目录下。关键文件和代码部分主要包括:
parse函数(位于compiler-core/src/parse.ts): 这个函数负责解析模板,将模板字符串转换成抽象语法树(AST)。在这个阶段,v-for和v-if作为指令被识别,并附加到相应的 AST 节点上。指令处理(可能分布在如
compiler-core/src/transforms/vFor.ts和compiler-core/src/transforms/vIf.ts): 这些文件包含用于处理v-for和v-if的特定逻辑。这些转换函数会检查 AST 节点,应用相应的逻辑,并可能修改或增强 AST。优先级处理:Vue 的编译器在处理指令时遵循特定的优先级。通常,
v-for指令的处理会在v-if之前,这是因为在 AST 转换阶段,处理v-for的逻辑会先于v-if执行。这反映在它们在代码中的调用顺序上。代码生成(位于
compiler-core/src/codegen.ts): 在 AST 转换后,编译器将 AST 转换成渲染函数的代码。在这个阶段,v-for和v-if的处理结果被转换成 JavaScript 代码。
关键代码示例(概念性说明,非实际代码):
// vue3-core/packages/compiler-core/src/compile.ts
export function getBaseTransformPreset(
prefixIdentifiers ? : boolean
): TransformPreset {
return [
[
transformOnce,
transformIf,
transformMemo,
transformFor,
...(__COMPAT__ ? [transformFilter] : []),
...(!__BROWSER__ && prefixIdentifiers ?
[
// order is important
trackVForSlotScopes,
transformExpression
] :
__BROWSER__ && __DEV__ ?
[transformExpression] :
[]),
transformSlotOutlet,
transformElement,
trackSlotScopes,
transformText
],
{
on: transformOn,
bind: transformBind,
model: transformModel
}
]
}
// vue2-core/src/compiler/parser/index.ts
if (inVPre) {
processRawAttrs(element)
} else if (!element.processed) {
// structural directives
// 结构指示
processFor(element)
processIf(element)
processOnce(element)
}
// parse.ts
// 对 if for 进行 warn 提示
if (hasIf && hasFor) {
warnDeprecation(
CompilerDeprecationTypes.COMPILER_V_IF_V_FOR_PRECEDENCE,
context,
getSelection(context, start)
)
break
}要完全理解这些代码的工作原理,建议查阅 Vue 3 的官方源码库,特别是编译器部分的代码。由于 Vue 的源代码经常更新,最准确的方法是直接查看最新版本的代码库。
讲讲v-model的原理
vue的最佳实践是什么?
如果需要你起一个新的vue项目,你会怎么去做,会用哪些工具、框架,需要怎么组织文件结构?
