Compare commits
11 Commits
ba61fc3655
...
eb49928833
Author | SHA1 | Date |
---|---|---|
|
eb49928833 | |
|
5546635e31 | |
|
bfaaeb8843 | |
|
84e7108798 | |
|
39e7447e70 | |
|
926cb154cb | |
|
85c1ae88d5 | |
|
cdbdbec0f8 | |
|
466b18afed | |
|
61b71b3163 | |
|
dbf4e201ce |
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>模板语法</title>
|
<title>模板语法</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>10-列表渲染</title>
|
<title>10-列表渲染</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>列表过滤</title>
|
<title>列表过滤</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>Vue监测数据的原理_数组</title>
|
<title>Vue监测数据的原理_数组</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<script src="js/dayjs.min.js"></script>
|
<script src="../js/dayjs.min.js"></script>
|
||||||
<title>过滤器</title>
|
<title>过滤器</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
|
<title>自定义指令_函数式</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<h2>当前值:<span v-text="num"></span></h2>
|
||||||
|
<h2>放大十倍值:<span v-big="num"></span></h2>
|
||||||
|
<button @click="num++">点击</button>
|
||||||
|
|
||||||
|
<input type="text" v-fbind="num" />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const vm = new Vue({
|
||||||
|
el: "#app",
|
||||||
|
data: { num: 10 },
|
||||||
|
directives: {
|
||||||
|
// 指令所在模板被重新解析时
|
||||||
|
big(el, binding) {
|
||||||
|
el.innerText = binding.value * 10;
|
||||||
|
},
|
||||||
|
fbind: {
|
||||||
|
bind(el, binding) {
|
||||||
|
console.log("bind");
|
||||||
|
el.value = binding.value;
|
||||||
|
},
|
||||||
|
inserted(el, binding) {
|
||||||
|
console.log("inserted");
|
||||||
|
el.focus();
|
||||||
|
},
|
||||||
|
update(el, binding) {
|
||||||
|
console.log("update");
|
||||||
|
el.value = binding.value;
|
||||||
|
el.focus();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -0,0 +1,135 @@
|
||||||
|
# Vue2 自定义指令全面指南
|
||||||
|
|
||||||
|
指令中的作用域时window!!!
|
||||||
|
|
||||||
|
## 一、指令类型对比
|
||||||
|
|
||||||
|
| 类型 | 语法 | 触发时机 | 适用场景 |
|
||||||
|
| ------ | ------------------------------------- | ------------- | ----------- |
|
||||||
|
| 函数式 | `directives: { big(el, binding) {} }` | bind + update | 简单DOM操作 |
|
||||||
|
| 对象式 | `directives: { fbind: { hooks } }` | 完整生命周期 | 复杂交互 |
|
||||||
|
|
||||||
|
## 二、对象式指令详解
|
||||||
|
|
||||||
|
### 1. 完整生命周期钩子
|
||||||
|
```javascript
|
||||||
|
fbind: {
|
||||||
|
// 指令第一次绑定到元素时调用(初始化设置)
|
||||||
|
bind(el, binding) {
|
||||||
|
el.value = binding.value;
|
||||||
|
},
|
||||||
|
// 被绑定元素插入父节点时调用
|
||||||
|
inserted(el, binding) {
|
||||||
|
el.focus(); // 自动获取焦点
|
||||||
|
},
|
||||||
|
// 所在组件更新时调用
|
||||||
|
update(el, binding) {
|
||||||
|
el.value = binding.value;
|
||||||
|
el.focus(); // 值更新后重新聚焦
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 执行流程示例
|
||||||
|
1. 初始化时:`bind` → `inserted`
|
||||||
|
2. 数据更新时:`update`
|
||||||
|
3. 模板重新解析时:`update`
|
||||||
|
|
||||||
|
## 三、最佳实践建议
|
||||||
|
|
||||||
|
### 1. 输入框增强指令
|
||||||
|
```javascript
|
||||||
|
Vue.directive('input-enhanced', {
|
||||||
|
inserted(el) {
|
||||||
|
el.select(); // 自动选中文本
|
||||||
|
},
|
||||||
|
update(el) {
|
||||||
|
// 防抖处理避免频繁触发
|
||||||
|
clearTimeout(el._timer);
|
||||||
|
el._timer = setTimeout(() => {
|
||||||
|
el.select();
|
||||||
|
}, 300);
|
||||||
|
},
|
||||||
|
unbind(el) {
|
||||||
|
// 清理工作
|
||||||
|
clearTimeout(el._timer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 性能优化技巧
|
||||||
|
```javascript
|
||||||
|
update(el, binding) {
|
||||||
|
if (binding.value !== binding.oldValue) {
|
||||||
|
// 只有值变化时才执行操作
|
||||||
|
el.value = binding.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、高级应用场景
|
||||||
|
|
||||||
|
### 1. 权限控制指令
|
||||||
|
```javascript
|
||||||
|
Vue.directive('permission', {
|
||||||
|
inserted(el, binding) {
|
||||||
|
if (!checkPermission(binding.value)) {
|
||||||
|
el.parentNode.removeChild(el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
使用方式:
|
||||||
|
```html
|
||||||
|
<button v-permission="'admin'">管理按钮</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 拖拽指令实现
|
||||||
|
```javascript
|
||||||
|
Vue.directive('drag', {
|
||||||
|
inserted(el) {
|
||||||
|
el.style.cursor = 'move';
|
||||||
|
el.onmousedown = function(e) {
|
||||||
|
// 拖拽逻辑实现
|
||||||
|
}
|
||||||
|
},
|
||||||
|
unbind(el) {
|
||||||
|
el.onmousedown = null; // 清理事件
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、常见问题解决方案
|
||||||
|
|
||||||
|
1. **指令不生效问题排查**
|
||||||
|
- 检查指令名称是否使用kebab-case(短横线)
|
||||||
|
- 确认绑定值是否为响应式数据
|
||||||
|
- 验证是否在正确的生命周期钩子操作DOM
|
||||||
|
|
||||||
|
2. **动态指令参数**
|
||||||
|
```html
|
||||||
|
<div v-demo:[dynamicArg]="value"></div>
|
||||||
|
```
|
||||||
|
```javascript
|
||||||
|
binding.arg // 获取动态参数
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **多修饰符处理**
|
||||||
|
```javascript
|
||||||
|
if (binding.modifiers.large) {
|
||||||
|
el.style.fontSize = '20px';
|
||||||
|
}
|
||||||
|
if (binding.modifiers.red) {
|
||||||
|
el.style.color = 'red';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、与Vue3的对比
|
||||||
|
|
||||||
|
| 特性 | Vue2 | Vue3 |
|
||||||
|
| --------- | ---------------------- | ------------------- |
|
||||||
|
| 钩子名称 | `bind` → `beforeMount` | |
|
||||||
|
| | `inserted` → `mounted` | |
|
||||||
|
| | 新增`beforeUpdate` | |
|
||||||
|
| 参数传递 | 通过`binding`对象 | 更直观的上下文参数 |
|
||||||
|
| 组合式API | 无 | 支持setup中使用指令 |
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
|
<title>VueComponent构造函数</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<School />
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const School = Vue.extend({
|
||||||
|
name: "School",
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<h2>学校名称:{{name}} </h2>
|
||||||
|
<h2>学校地址:{{address}} </h2>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: "Bunny",
|
||||||
|
address: "昆山县印象花园",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 类型是构造函数
|
||||||
|
// ƒ VueComponent(options) {
|
||||||
|
// this._init(options);
|
||||||
|
// }
|
||||||
|
// 每次调用 Vue.extend 返回都是全新的 VueComponent
|
||||||
|
console.log(School);
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
components: { School },
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</html>
|
|
@ -0,0 +1,150 @@
|
||||||
|
# Vue2 组件系统深度解析
|
||||||
|
|
||||||
|
## 一、组件构造函数原理
|
||||||
|
|
||||||
|
### 1. Vue.extend 核心机制
|
||||||
|
```javascript
|
||||||
|
const School = Vue.extend({
|
||||||
|
name: "School",
|
||||||
|
template: `...`,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: "Bunny",
|
||||||
|
address: "昆山县印象花园"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 每次调用返回全新的构造函数
|
||||||
|
console.log(School); // ƒ VueComponent(options) { this._init(options); }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 组件实例化过程
|
||||||
|
1. **模板编译**:将template编译为render函数
|
||||||
|
2. **选项合并**:合并Vue全局配置和组件选项
|
||||||
|
3. **构造函数生成**:基于Vue基类创建子类
|
||||||
|
4. **实例化**:通过`new Component(options)`创建实例
|
||||||
|
|
||||||
|
## 二、组件注册方式对比
|
||||||
|
|
||||||
|
| 注册方式 | 语法 | 作用域 | 生命周期 |
|
||||||
|
| -------- | ----------------------- | -------------- | ------------ |
|
||||||
|
| 全局注册 | `Vue.component()` | 全应用可用 | 与根实例相同 |
|
||||||
|
| 局部注册 | `components`选项 | 当前组件作用域 | 独立生命周期 |
|
||||||
|
| 动态注册 | `Vue.extend()`+手动挂载 | 灵活控制 | 手动管理 |
|
||||||
|
|
||||||
|
## 三、组件核心特性详解
|
||||||
|
|
||||||
|
### 1. 组件选项合并策略
|
||||||
|
```javascript
|
||||||
|
const Parent = Vue.extend({
|
||||||
|
created() {
|
||||||
|
console.log('Parent Hook');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const Child = Parent.extend({
|
||||||
|
created() {
|
||||||
|
console.log('Child Hook');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 执行顺序:Parent Hook → Child Hook
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 组件隔离机制
|
||||||
|
- **独立作用域**:每个组件实例维护独立的数据副本
|
||||||
|
- **样式隔离**:通过scoped CSS实现
|
||||||
|
- **命名空间**:基于组件树结构的$parent/$children关系
|
||||||
|
|
||||||
|
### 3. 性能优化技巧
|
||||||
|
```javascript
|
||||||
|
// 1. 异步组件
|
||||||
|
const AsyncComp = () => ({
|
||||||
|
component: import('./MyComp.vue'),
|
||||||
|
loading: LoadingComp,
|
||||||
|
error: ErrorComp
|
||||||
|
});
|
||||||
|
|
||||||
|
// 2. 函数式组件
|
||||||
|
const FuncComp = {
|
||||||
|
functional: true,
|
||||||
|
render(h, context) {
|
||||||
|
// 无状态、无实例
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、高级开发模式
|
||||||
|
|
||||||
|
### 1. 动态组件工厂
|
||||||
|
```javascript
|
||||||
|
function createComponent(template, data) {
|
||||||
|
return Vue.extend({
|
||||||
|
template,
|
||||||
|
data() {
|
||||||
|
return {...data};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 组件继承扩展
|
||||||
|
```javascript
|
||||||
|
const BaseList = Vue.extend({
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
// 基础实现
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const UserList = BaseList.extend({
|
||||||
|
methods: {
|
||||||
|
fetchData() {
|
||||||
|
// 扩展实现
|
||||||
|
super.fetchData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 组件通信体系
|
||||||
|
| 通信方式 | 适用场景 | 示例 |
|
||||||
|
| -------------- | -------- | ------------------------------------------ |
|
||||||
|
| Props/Events | 父子组件 | `<Child :value="data" @input="handle">` |
|
||||||
|
| Provide/Inject | 跨级组件 | `provide: { value: 1 }, inject: ['value']` |
|
||||||
|
| Event Bus | 任意组件 | `Vue.prototype.$bus = new Vue()` |
|
||||||
|
| Vuex | 复杂状态 | 集中式状态管理 |
|
||||||
|
|
||||||
|
## 五、调试与问题排查
|
||||||
|
|
||||||
|
### 1. 组件实例检查
|
||||||
|
```javascript
|
||||||
|
mounted() {
|
||||||
|
console.log(this.$options.name); // 组件名
|
||||||
|
console.log(this.$parent); // 父实例
|
||||||
|
console.log(this.$children); // 子实例
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 常见问题解决方案
|
||||||
|
1. **模板不渲染**:
|
||||||
|
- 检查组件是否正确定义和注册
|
||||||
|
- 验证template/content是否有效
|
||||||
|
|
||||||
|
2. **数据不更新**:
|
||||||
|
- 确认data是否为函数返回新对象
|
||||||
|
- 检查props是否响应式
|
||||||
|
|
||||||
|
3. **生命周期异常**:
|
||||||
|
- 避免在beforeCreate访问data
|
||||||
|
- 确保异步操作在mounted后执行
|
||||||
|
|
||||||
|
## 六、Vue3对比说明
|
||||||
|
|
||||||
|
| 特性 | Vue2 | Vue3 |
|
||||||
|
| -------- | ----------- | --------------- |
|
||||||
|
| 组件定义 | Options API | Composition API |
|
||||||
|
| 继承机制 | Vue.extend | defineComponent |
|
||||||
|
| 性能优化 | 手动配置 | 自动优化 |
|
||||||
|
| 片段支持 | 单根节点 | 多根节点 |
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>数据绑定</title>
|
<title>数据绑定</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>Vue中的数据代理</title>
|
<title>Vue中的数据代理</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>事件处理</title>
|
<title>事件处理</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>键盘事件</title>
|
<title>键盘事件</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>计算属性</title>
|
<title>计算属性</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<script src="js/vue@2.7.16.js"></script>
|
<script src="../js/vue@2.7.16.js"></script>
|
||||||
<title>监视属性</title>
|
<title>监视属性</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<School />
|
||||||
|
<Student />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import School from "./compontens/School.vue";
|
||||||
|
import Student from "./compontens/Student.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "App",
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
compontens: {
|
||||||
|
School,
|
||||||
|
Student,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<h2>学校。。。</h2>
|
||||||
|
</template>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<h2>学生</h2>
|
||||||
|
</template>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>单文件内容</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
</body>
|
||||||
|
<script type="module" src="../../js/vue@2.7.16.js"></script>
|
||||||
|
<script type="module" src="./main.js"></script>
|
||||||
|
</html>
|
|
@ -0,0 +1,9 @@
|
||||||
|
import App from "./App.vue";
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: "#app",
|
||||||
|
template: "<App></App>",
|
||||||
|
compontens: {
|
||||||
|
App,
|
||||||
|
},
|
||||||
|
});
|
|
@ -0,0 +1,23 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
|
@ -0,0 +1 @@
|
||||||
|
shamefully-hoist=true
|
|
@ -0,0 +1,24 @@
|
||||||
|
# demo2
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
pnpm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
pnpm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
pnpm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"],
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"module": "esnext",
|
||||||
|
"baseUrl": "./",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"name": "demo2",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"core-js": "^3.8.3",
|
||||||
|
"vue": "^2.6.14",
|
||||||
|
"vuex": "3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.27.4",
|
||||||
|
"@babel/eslint-parser": "^7.12.16",
|
||||||
|
"@babel/preset-env": "^7.27.2",
|
||||||
|
"@vue/cli-plugin-babel": "~5.0.8",
|
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
|
"@vue/cli-service": "~5.0.0",
|
||||||
|
"eslint": "^7.32.0",
|
||||||
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
|
"vue-template-compiler": "^2.6.14"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "@babel/eslint-parser"
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
],
|
||||||
|
"packageManager": "pnpm@10.8.1+sha512.c50088ba998c67b8ca8c99df8a5e02fd2ae2e2b29aaf238feaa9e124248d3f48f9fb6db2424949ff901cffbb5e0f0cc1ad6aedb602cd29450751d11c35023677"
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
||||||
|
ignoredBuiltDependencies:
|
||||||
|
- core-js
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,17 @@
|
||||||
|
<!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>
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<!-- <Demo1 />
|
||||||
|
<hr />
|
||||||
|
<Demo2 />
|
||||||
|
<hr />
|
||||||
|
<Demo3 /> -->
|
||||||
|
<Demo4 />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import Demo1 from "@/views/demo1/index.vue";
|
||||||
|
// import Demo2 from "@/views/demo2/index.vue";
|
||||||
|
// import Demo3 from "@/views/demo3/index.vue";
|
||||||
|
import Demo4 from "@/views/demo4/index.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "App",
|
||||||
|
// components: { Demo1, Demo2, Demo3 },
|
||||||
|
components: { Demo4 },
|
||||||
|
};
|
||||||
|
</script>
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,10 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import store from "./store";
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
render: (h) => h(App),
|
||||||
|
store,
|
||||||
|
}).$mount("#app");
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import Vuex from "vuex";
|
||||||
|
import count from "./modules/count";
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
const store = new Vuex.Store({
|
||||||
|
state: {},
|
||||||
|
mutations: {},
|
||||||
|
actions: {},
|
||||||
|
modules: { count },
|
||||||
|
});
|
||||||
|
|
||||||
|
export default store;
|
|
@ -0,0 +1,41 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import Vuex from "vuex";
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
const count = {
|
||||||
|
namespaced: true,
|
||||||
|
state: { num: 1, sum: 0 },
|
||||||
|
actions: {
|
||||||
|
// 奇数相加
|
||||||
|
incrementOdd(context) {
|
||||||
|
// 可以从上下文中获取sum
|
||||||
|
if (context.state.sum % 2) {
|
||||||
|
context.commit("INCREMENT_ODD");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
incrementWait(context) {
|
||||||
|
setTimeout(() => {
|
||||||
|
context.commit("INCREMENT_WAIT");
|
||||||
|
}, 500);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
// 相加
|
||||||
|
INCREMENT(state) {
|
||||||
|
state.sum += state.num;
|
||||||
|
},
|
||||||
|
// 相减
|
||||||
|
DECREMENT(state) {
|
||||||
|
state.sum -= state.num;
|
||||||
|
},
|
||||||
|
// 奇数相加
|
||||||
|
INCREMENT_ODD(state) {
|
||||||
|
state.sum += state.num;
|
||||||
|
},
|
||||||
|
INCREMENT_WAIT(state) {
|
||||||
|
state.sum += state.num;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default count;
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<div class="demo">
|
||||||
|
<h2>学校名称:{{ name }}</h2>
|
||||||
|
<h2>学校地址:{{ address }}</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "SchoolInfo",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: "Bunny",
|
||||||
|
address: "昆山市印象花园",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<h1 ref="title" v-text="message"></h1>
|
||||||
|
<button @click="ShowDom">输出上方DOM元素</button>
|
||||||
|
<SchoolInfo ref="schoolInfoRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SchoolInfo from "@/views/demo1/components/SchoolInfo.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Demo-1",
|
||||||
|
components: { SchoolInfo },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
message: "学习Vue2",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
ShowDom() {
|
||||||
|
// 获取当前Ref对象内容
|
||||||
|
console.log("@@@", this.$refs.title);
|
||||||
|
// 获取子组件对象实例
|
||||||
|
console.log("@@@", this.$refs.schoolInfoRef);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,168 @@
|
||||||
|
# Vue2 中的 ref 引用机制详解
|
||||||
|
|
||||||
|
## 一、ref 的基本概念
|
||||||
|
|
||||||
|
`ref` 是 Vue2 提供的一种特殊的属性,用于直接访问 DOM 元素或子组件实例。它比传统的 `document.getElementById` 等方式更适合在 Vue 生态中使用。
|
||||||
|
|
||||||
|
## 二、代码解析
|
||||||
|
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<!-- 绑定到DOM元素 -->
|
||||||
|
<h1 ref="title" v-text="message"></h1>
|
||||||
|
<button @click="ShowDom">输出上方DOM元素</button>
|
||||||
|
|
||||||
|
<!-- 绑定到子组件 -->
|
||||||
|
<SchoolInfo ref="schoolInfoRef" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SchoolInfo from "@/components/SchoolInfo.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "App",
|
||||||
|
components: { SchoolInfo },
|
||||||
|
data() {
|
||||||
|
return { message: "学习Vue2" };
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
ShowDom() {
|
||||||
|
// 访问DOM元素
|
||||||
|
console.log(this.$refs.title); // 输出: <h1>学习Vue2</h1>
|
||||||
|
|
||||||
|
// 访问子组件实例
|
||||||
|
console.log(this.$refs.schoolInfoRef); // 输出: SchoolInfo组件实例
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三、ref 的核心特性
|
||||||
|
|
||||||
|
### 1. 绑定目标类型
|
||||||
|
|
||||||
|
| 绑定目标 | 获取内容 | 使用场景 |
|
||||||
|
| --------- | ---------------- | ----------------------- |
|
||||||
|
| DOM元素 | 原生DOM节点 | 直接操作DOM |
|
||||||
|
| 组件 | 组件实例 | 调用子组件方法/访问数据 |
|
||||||
|
| v-for元素 | DOM数组/组件数组 | 操作列表元素 |
|
||||||
|
|
||||||
|
### 2. 生命周期时机
|
||||||
|
|
||||||
|
- **创建**:在组件 `mounted` 钩子之后可用
|
||||||
|
- **更新**:响应式更新后自动同步
|
||||||
|
- **销毁**:组件销毁时自动解除引用
|
||||||
|
|
||||||
|
### 3. 访问方式
|
||||||
|
|
||||||
|
通过组件实例的 `$refs` 对象访问:
|
||||||
|
```javascript
|
||||||
|
this.$refs.refName
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、最佳实践
|
||||||
|
|
||||||
|
### 1. 命名规范
|
||||||
|
- 使用 camelCase 命名(如 `schoolInfoRef`)
|
||||||
|
- 避免使用保留字或Vue内置名称
|
||||||
|
- 保持命名语义化(如 `formRef`, `tableRef`)
|
||||||
|
|
||||||
|
### 2. 安全访问模式
|
||||||
|
```javascript
|
||||||
|
methods: {
|
||||||
|
submitForm() {
|
||||||
|
// 先检查是否存在
|
||||||
|
if (this.$refs.formRef) {
|
||||||
|
this.$refs.formRef.validate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 动态ref(Vue 2.6+)
|
||||||
|
```html
|
||||||
|
<div v-for="item in list" :ref="`item_${item.id}`"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
methods: {
|
||||||
|
getItemRef(id) {
|
||||||
|
return this.$refs[`item_${id}`];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、高级用法
|
||||||
|
|
||||||
|
### 1. 组件通信
|
||||||
|
```javascript
|
||||||
|
// 父组件
|
||||||
|
this.$refs.childComp.doSomething();
|
||||||
|
|
||||||
|
// 子组件 SchoolInfo.vue
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
doSomething() {
|
||||||
|
// 子组件暴露的方法
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 表单验证示例
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<form ref="formRef">
|
||||||
|
<input v-model="form.name" required>
|
||||||
|
<button @click.prevent="validate">提交</button>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
validate() {
|
||||||
|
const form = this.$refs.formRef;
|
||||||
|
if (form.checkValidity()) {
|
||||||
|
// 表单验证通过
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 结合第三方库
|
||||||
|
```javascript
|
||||||
|
mounted() {
|
||||||
|
// 使用ref初始化第三方库
|
||||||
|
this.chart = new Chart(this.$refs.chartCanvas, {
|
||||||
|
// 配置项
|
||||||
|
});
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 清理工作
|
||||||
|
this.chart.destroy();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、注意事项
|
||||||
|
|
||||||
|
1. **响应式限制**:
|
||||||
|
- `$refs` 不是响应式的
|
||||||
|
- 避免在模板或计算属性中使用
|
||||||
|
|
||||||
|
2. **执行时机**:
|
||||||
|
- 在 `mounted` 之前访问会是 `undefined`
|
||||||
|
- 更新后需要等待 `$nextTick` 获取最新引用
|
||||||
|
|
||||||
|
3. **过度使用问题**:
|
||||||
|
- 优先考虑 props/events 通信
|
||||||
|
- 避免形成紧密耦合的组件关系
|
||||||
|
|
||||||
|
4. **与 Vue3 的区别**:
|
||||||
|
- Vue3 中 ref 需要从 `vue` 导入
|
||||||
|
- 组合式 API 中使用 `ref()` 函数创建响应式引用
|
|
@ -0,0 +1,192 @@
|
||||||
|
# Vue2 Props 属性传递机制详解
|
||||||
|
|
||||||
|
## 一、Props 的基本概念
|
||||||
|
|
||||||
|
Props 是 Vue 组件之间数据传递的主要方式,允许父组件向子组件传递数据。它具有明确的单向数据流特性(父组件 → 子组件)。
|
||||||
|
|
||||||
|
## 二、三种 Props 声明方式对比
|
||||||
|
|
||||||
|
### 1. 数组式声明(简单接收)
|
||||||
|
```javascript
|
||||||
|
props: ["name", "sex", "age"]
|
||||||
|
```
|
||||||
|
- **特点**:仅指定属性名
|
||||||
|
- **适用场景**:快速原型开发
|
||||||
|
- **限制**:无类型检查,无验证逻辑
|
||||||
|
|
||||||
|
### 2. 对象式声明(类型检查)
|
||||||
|
```javascript
|
||||||
|
props: {
|
||||||
|
name: String,
|
||||||
|
sex: String,
|
||||||
|
age: Number
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **特点**:添加基础类型检查
|
||||||
|
- **优势**:在开发模式下会发出警告
|
||||||
|
- **不足**:无法设置必填/默认值
|
||||||
|
|
||||||
|
### 3. 配置对象声明(完整配置)
|
||||||
|
```javascript
|
||||||
|
props: {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true // 必须传入
|
||||||
|
},
|
||||||
|
sex: {
|
||||||
|
type: String,
|
||||||
|
default: "男🚹" // 默认值
|
||||||
|
},
|
||||||
|
age: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
validator: value => value >= 6 && value <= 60 // 自定义验证
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **完整功能**:
|
||||||
|
- 类型检查 (`type`)
|
||||||
|
- 必填标记 (`required`)
|
||||||
|
- 默认值 (`default`)
|
||||||
|
- 自定义验证 (`validator`)
|
||||||
|
|
||||||
|
## 三、核心特性详解
|
||||||
|
|
||||||
|
### 1. 类型系统支持
|
||||||
|
Vue 支持以下原生构造函数作为类型:
|
||||||
|
- `String`
|
||||||
|
- `Number`
|
||||||
|
- `Boolean`
|
||||||
|
- `Array`
|
||||||
|
- `Object`
|
||||||
|
- `Date`
|
||||||
|
- `Function`
|
||||||
|
- `Symbol`
|
||||||
|
|
||||||
|
也可以使用自定义构造函数:
|
||||||
|
```javascript
|
||||||
|
props: {
|
||||||
|
author: Person // 检查是否是Person的实例
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 默认值函数
|
||||||
|
当默认值是对象或数组时,必须使用工厂函数:
|
||||||
|
```javascript
|
||||||
|
props: {
|
||||||
|
config: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({ pageSize: 10 })
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 自定义验证器
|
||||||
|
```javascript
|
||||||
|
age: {
|
||||||
|
type: Number,
|
||||||
|
validator: value => {
|
||||||
|
return value >= 6 && value <= 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、最佳实践
|
||||||
|
|
||||||
|
### 1. 命名规范
|
||||||
|
- **Prop 名称**:使用 camelCase(JavaScript)
|
||||||
|
- **HTML 特性**:使用 kebab-case(DOM)
|
||||||
|
```html
|
||||||
|
<!-- 父组件模板 -->
|
||||||
|
<child-component user-name="张三"></child-component>
|
||||||
|
|
||||||
|
<!-- 子组件声明 -->
|
||||||
|
props: ['userName']
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 单向数据流原则
|
||||||
|
- **禁止**子组件直接修改 prop
|
||||||
|
- **正确做法**:
|
||||||
|
```javascript
|
||||||
|
// 子组件中使用data接收
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
localAge: this.age
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 或使用计算属性
|
||||||
|
computed: {
|
||||||
|
normalizedAge() {
|
||||||
|
return this.age + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 类型定义技巧
|
||||||
|
```javascript
|
||||||
|
// 使用PropType增强类型提示
|
||||||
|
import { PropType } from 'vue';
|
||||||
|
|
||||||
|
props: {
|
||||||
|
user: {
|
||||||
|
type: Object as PropType<User>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、高级用法
|
||||||
|
|
||||||
|
### 1. 非Prop特性
|
||||||
|
未在props中声明的特性会自动绑定到组件的根元素上:
|
||||||
|
```html
|
||||||
|
<my-component data-test="123" class="extra"></my-component>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 同步修饰符
|
||||||
|
`.sync` 修饰符实现双向绑定(Vue 2.3+):
|
||||||
|
```html
|
||||||
|
<!-- 父组件 -->
|
||||||
|
<child :title.sync="pageTitle"></child>
|
||||||
|
|
||||||
|
<!-- 等价于 -->
|
||||||
|
<child :title="pageTitle" @update:title="pageTitle = $event"></child>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. v-model 整合
|
||||||
|
自定义组件实现 v-model:
|
||||||
|
```javascript
|
||||||
|
props: ['value'],
|
||||||
|
methods: {
|
||||||
|
updateValue(newVal) {
|
||||||
|
this.$emit('input', newVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、常见问题
|
||||||
|
|
||||||
|
1. **Prop未更新问题**:
|
||||||
|
- 确保父组件数据是响应式的
|
||||||
|
- 对象/数组需要确保是新的引用
|
||||||
|
|
||||||
|
2. **类型检查失败**:
|
||||||
|
- 开发模式下会输出警告
|
||||||
|
- 生产环境会静默失败
|
||||||
|
|
||||||
|
3. **性能优化**:
|
||||||
|
- 避免传递大型对象
|
||||||
|
- 必要时使用 `v-once` 冻结静态内容
|
||||||
|
|
||||||
|
## 七、与 Vue3 的区别
|
||||||
|
|
||||||
|
| 特性 | Vue2 | Vue3 |
|
||||||
|
| ---------- | ------------ | ----------------------- |
|
||||||
|
| 默认值函数 | 必须使用函数 | 可以直接赋值 |
|
||||||
|
| 类型定义 | PropType | 原生TypeScript支持 |
|
||||||
|
| 废弃语法 | .sync | 移除,用v-model参数替代 |
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2>学生姓名:{{ name }}</h2>
|
||||||
|
<h2>学习性别:{{ sex }}</h2>
|
||||||
|
<h2>学生年龄:{{ age + 1 }}</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "SchoolInfo",
|
||||||
|
// 第一种传入方法---简单接受
|
||||||
|
// props: ["name", "sex", "age"],
|
||||||
|
|
||||||
|
// 第二种传入方法---接受同时对数据进行限制
|
||||||
|
// props: {
|
||||||
|
// name: String,
|
||||||
|
// sex: String,
|
||||||
|
// age: Number,
|
||||||
|
// },
|
||||||
|
props: {
|
||||||
|
name: { type: String, required: true /* 必须要传入的 */ },
|
||||||
|
sex: { type: String, default: "男🚹" /* 给一个默认值 */ },
|
||||||
|
age: { type: Number, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SchoolInfo name="Bunny1" sex="女🚺" :age="16" />
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<SchoolInfo name="Bunny2" sex="女🚺" :age="16" />
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
<SchoolInfo name="Bunny3" :age="16" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SchoolInfo from "./components/SchoolInfo.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Demo-2",
|
||||||
|
components: { SchoolInfo },
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,173 @@
|
||||||
|
# Vue2 Mixins 混入机制详解
|
||||||
|
|
||||||
|
## 一、基本概念
|
||||||
|
|
||||||
|
Mixins(混入)是 Vue2 中用于分发组件可复用功能的灵活方式。一个混入对象可以包含任意组件选项,当组件使用混入对象时,所有混入对象的选项将被"混合"进入组件本身的选项。
|
||||||
|
|
||||||
|
## 二、代码示例解析
|
||||||
|
|
||||||
|
### 1. 混入对象定义 (test1.js)
|
||||||
|
```javascript
|
||||||
|
export const test1 = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: "Bunny" // 将被合并到组件data中
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showMessage() { // 将被合并到组件methods中
|
||||||
|
alert(`展示消息:${this.username}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 组件中使用混入 (SchoolInfo.vue)
|
||||||
|
```html
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2 @click="showMessage">学生姓名-showMessage:{{ username }}</h2>
|
||||||
|
<h2>学习性别:{{ sex }}</h2>
|
||||||
|
<h2>学生年龄:{{ age + 1 }}</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { test1 } from "../mixins/test1";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SchoolInfo",
|
||||||
|
mixins: [test1], // 注册混入
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: "Bunny", // 与混入data合并
|
||||||
|
sex: "🚺",
|
||||||
|
age: 16
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三、合并策略详解
|
||||||
|
|
||||||
|
### 1. 选项合并规则
|
||||||
|
| 选项类型 | 合并策略 | 示例结果 |
|
||||||
|
| ------------ | ---------------------- | ---------------------------- |
|
||||||
|
| data | 递归合并,组件优先 | 同名属性以组件为准 |
|
||||||
|
| methods | 合并,组件优先 | 同名方法覆盖 |
|
||||||
|
| 生命周期钩子 | 合并为数组,混入先执行 | 都会调用,顺序: mixin → 组件 |
|
||||||
|
| 其他对象值 | 组件优先 | 如components, directives等 |
|
||||||
|
|
||||||
|
### 2. 合并过程示例
|
||||||
|
```javascript
|
||||||
|
// 混入data
|
||||||
|
{ username: "Bunny" }
|
||||||
|
|
||||||
|
// 组件data
|
||||||
|
{
|
||||||
|
username: "Bunny", // 同名属性被保留
|
||||||
|
sex: "🚺",
|
||||||
|
age: 16
|
||||||
|
}
|
||||||
|
|
||||||
|
// 最终合并结果
|
||||||
|
{
|
||||||
|
username: "Bunny", // 来自组件
|
||||||
|
sex: "🚺", // 来自组件
|
||||||
|
age: 16 // 来自组件
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 四、最佳实践
|
||||||
|
|
||||||
|
### 1. 命名规范
|
||||||
|
- 混入文件使用 `mixin-` 前缀(如 `mixin-logger.js`)
|
||||||
|
- 混入变量使用 `mixin` 后缀(如 `userMixin`)
|
||||||
|
- 避免与组件属性和方法重名
|
||||||
|
|
||||||
|
### 2. 使用建议
|
||||||
|
- **适合场景**:
|
||||||
|
- 多个组件共享相同逻辑
|
||||||
|
- 功能模块解耦
|
||||||
|
- 插件开发
|
||||||
|
|
||||||
|
- **避免场景**:
|
||||||
|
- 过度使用导致代码难以追踪
|
||||||
|
- 复杂的多层级混入
|
||||||
|
|
||||||
|
### 3. 全局混入
|
||||||
|
```javascript
|
||||||
|
// 谨慎使用!会影响所有Vue实例
|
||||||
|
Vue.mixin({
|
||||||
|
created() {
|
||||||
|
console.log('全局混入的created钩子');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、高级用法
|
||||||
|
|
||||||
|
### 1. 自定义合并策略
|
||||||
|
```javascript
|
||||||
|
Vue.config.optionMergeStrategies.myOption = (parent, child, vm) => {
|
||||||
|
return child || parent;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 混入组合
|
||||||
|
```javascript
|
||||||
|
// featureMixin.js
|
||||||
|
export const featureMixin = {
|
||||||
|
methods: {
|
||||||
|
featureMethod() { /*...*/ }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件中使用多个混入
|
||||||
|
import { userMixin, featureMixin } from './mixins';
|
||||||
|
export default {
|
||||||
|
mixins: [userMixin, featureMixin]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 混入与插件结合
|
||||||
|
```javascript
|
||||||
|
// plugin.js
|
||||||
|
export default {
|
||||||
|
install(Vue) {
|
||||||
|
Vue.mixin({
|
||||||
|
mounted() {
|
||||||
|
console.log('来自插件的混入');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、常见问题
|
||||||
|
|
||||||
|
1. **属性冲突**:
|
||||||
|
- 数据对象:组件data优先
|
||||||
|
- 方法:组件methods优先
|
||||||
|
- 钩子函数:都会执行,混入的先执行
|
||||||
|
|
||||||
|
2. **调试技巧**:
|
||||||
|
```javascript
|
||||||
|
mounted() {
|
||||||
|
console.log(this.$options); // 查看合并后的选项
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **与Vue3的区别**:
|
||||||
|
- Vue3 使用 Composition API 替代混入
|
||||||
|
- 提供 `setup` 函数更好地组织逻辑
|
||||||
|
|
||||||
|
## 七、替代方案比较
|
||||||
|
|
||||||
|
| 方案 | 优点 | 缺点 |
|
||||||
|
| --------------- | -------------- | ---------------- |
|
||||||
|
| Mixins | 逻辑复用简单 | 命名冲突风险 |
|
||||||
|
| 高阶组件 | 明确props传递 | 组件嵌套层次深 |
|
||||||
|
| 插件 | 全局功能扩展 | 过度使用影响性能 |
|
||||||
|
| Composition API | 更好的代码组织 | 仅Vue3支持 |
|
|
@ -0,0 +1,23 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h2 @click="showMessage">学生姓名-showMessage:{{ username }}</h2>
|
||||||
|
<h2>学习性别:{{ sex }}</h2>
|
||||||
|
<h2>学生年龄:{{ age + 1 }}</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { test1 } from "../mixins/test1";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SchoolInfo",
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: "Bunny",
|
||||||
|
sex: "🚺",
|
||||||
|
age: 16,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mixins: [test1],
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<SchoolInfo />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SchoolInfo from "./components/SchoolInfo.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Demo-3",
|
||||||
|
components: { SchoolInfo },
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,12 @@
|
||||||
|
export const test1 = {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: "Bunny",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showMessage() {
|
||||||
|
alert(`展示消息:${this.username}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,178 @@
|
||||||
|
# Vuex 状态管理最佳实践指南
|
||||||
|
|
||||||
|
## 一、核心架构解析
|
||||||
|
|
||||||
|
### 1. 模块化结构
|
||||||
|
```javascript
|
||||||
|
// store/index.js - 主入口文件
|
||||||
|
import count from './modules/count'
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
modules: {
|
||||||
|
count // 启用命名空间的计数模块
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// store/modules/count.js - 计数模块
|
||||||
|
export default {
|
||||||
|
namespaced: true, // 启用命名空间
|
||||||
|
state: {
|
||||||
|
num: 1,
|
||||||
|
sum: 0
|
||||||
|
},
|
||||||
|
mutations: { /* 同步操作 */ },
|
||||||
|
actions: { /* 异步操作 */ }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据流示意图
|
||||||
|
```
|
||||||
|
组件 → dispatch → Actions → commit → Mutations → mutate → State → 响应式更新 → 组件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 二、代码优化建议
|
||||||
|
|
||||||
|
### 1. 组件中的优化
|
||||||
|
```javascript
|
||||||
|
// 使用map辅助函数简化代码
|
||||||
|
import { mapActions, mapMutations, mapState } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapState('count', ['sum', 'num'])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations('count', ['INCREMENT', 'DECREMENT']),
|
||||||
|
...mapActions('count', ['incrementOdd', 'incrementWait'])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Store 中的优化
|
||||||
|
```javascript
|
||||||
|
// 使用常量替代字符串 mutation 类型
|
||||||
|
const types = {
|
||||||
|
INCREMENT: 'INCREMENT',
|
||||||
|
DECREMENT: 'DECREMENT'
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = {
|
||||||
|
mutations: {
|
||||||
|
[types.INCREMENT](state) {
|
||||||
|
state.sum += state.num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 三、最佳实践规范
|
||||||
|
|
||||||
|
### 1. 命名规范
|
||||||
|
| 类型 | 命名风格 | 示例 |
|
||||||
|
| -------- | ------------ | ----------------- |
|
||||||
|
| Mutation | 大写+下划线 | `INCREMENT` |
|
||||||
|
| Action | camelCase | `fetchData` |
|
||||||
|
| Getter | 形容词/名词 | `filteredList` |
|
||||||
|
| Module | 业务相关名词 | `user`, `product` |
|
||||||
|
|
||||||
|
### 2. 操作分离原则
|
||||||
|
- **Mutations**:
|
||||||
|
- 只做简单状态赋值
|
||||||
|
- 必须是同步函数
|
||||||
|
- 命名类似事件(`SET_USER_INFO`)
|
||||||
|
|
||||||
|
- **Actions**:
|
||||||
|
- 处理业务逻辑
|
||||||
|
- 可以包含异步操作
|
||||||
|
- 命名类似方法(`fetchUserData`)
|
||||||
|
|
||||||
|
### 3. 模块化建议
|
||||||
|
1. 按功能/业务划分模块
|
||||||
|
2. 每个模块启用命名空间
|
||||||
|
3. 模块内维护独立state
|
||||||
|
|
||||||
|
## 四、高级应用场景
|
||||||
|
|
||||||
|
### 1. 动态模块注册
|
||||||
|
```javascript
|
||||||
|
// 在需要时加载模块
|
||||||
|
store.registerModule('dynamicModule', {
|
||||||
|
// 模块定义
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 表单处理方案
|
||||||
|
```javascript
|
||||||
|
// 使用双向绑定计算属性
|
||||||
|
computed: {
|
||||||
|
num: {
|
||||||
|
get() { return this.$store.state.count.num },
|
||||||
|
set(value) { this.$store.commit('count/UPDATE_NUM', value) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 插件开发
|
||||||
|
```javascript
|
||||||
|
// 持久化插件示例
|
||||||
|
const persistPlugin = store => {
|
||||||
|
store.subscribe((mutation, state) => {
|
||||||
|
localStorage.setItem('vuex_state', JSON.stringify(state))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 五、性能优化技巧
|
||||||
|
|
||||||
|
### 1. 大型状态树
|
||||||
|
- 使用模块懒加载
|
||||||
|
- 避免在state中存储大对象
|
||||||
|
|
||||||
|
### 2. 计算属性缓存
|
||||||
|
```javascript
|
||||||
|
getters: {
|
||||||
|
bigSum: state => {
|
||||||
|
// 复杂计算会被缓存
|
||||||
|
return heavyCalculation(state.sum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 批量更新
|
||||||
|
```javascript
|
||||||
|
// 合并多个mutation
|
||||||
|
actions: {
|
||||||
|
bulkUpdate({ commit }, payload) {
|
||||||
|
commit('UPDATE_A', payload.a)
|
||||||
|
commit('UPDATE_B', payload.b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 六、常见问题解决方案
|
||||||
|
|
||||||
|
1. **热重载问题**:
|
||||||
|
```javascript
|
||||||
|
if (module.hot) {
|
||||||
|
module.hot.accept(['./modules/count'], () => {
|
||||||
|
store.hotUpdate({ modules: { count } })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **循环依赖**:
|
||||||
|
- 避免模块间互相引用
|
||||||
|
- 使用全局事件总线处理跨模块通信
|
||||||
|
|
||||||
|
3. **调试技巧**:
|
||||||
|
- 使用Vue Devtools追踪状态变化
|
||||||
|
- 添加logger插件记录mutation
|
||||||
|
|
||||||
|
## 七、与Vue3的Pinia对比
|
||||||
|
|
||||||
|
| 特性 | Vuex | Pinia |
|
||||||
|
| ---------- | ------------ | -------- |
|
||||||
|
| 语法 | 较复杂 | 更简洁 |
|
||||||
|
| 模块化 | 需要配置 | 自动支持 |
|
||||||
|
| TypeScript | 需要额外配置 | 完美支持 |
|
||||||
|
| 体积 | 较大 | 更轻量 |
|
|
@ -0,0 +1,44 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<h1>当前求和为:{{ $store.state.count.sum }}</h1>
|
||||||
|
<select v-model.number="$store.state.count.num">
|
||||||
|
<option :value="1">1</option>
|
||||||
|
<option :value="2">2</option>
|
||||||
|
<option :value="3">3</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button @click="increment">+</button>
|
||||||
|
<button @click="decrement">-</button>
|
||||||
|
<button @click="incrementOdd">当前求和为奇数再加</button>
|
||||||
|
<button @click="incrementWait">等一等再加</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "CountInfo",
|
||||||
|
data() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
increment() {
|
||||||
|
this.$store.commit("count/INCREMENT");
|
||||||
|
},
|
||||||
|
decrement() {
|
||||||
|
this.$store.commit("count/DECREMENT");
|
||||||
|
},
|
||||||
|
incrementOdd() {
|
||||||
|
this.$store.dispatch("count/incrementOdd");
|
||||||
|
},
|
||||||
|
incrementWait() {
|
||||||
|
this.$store.dispatch("count/incrementWait");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
button {
|
||||||
|
margin: 0 4px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<template>
|
||||||
|
<Count />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Count from "./compontens/Count.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Demo-4",
|
||||||
|
components: {
|
||||||
|
Count,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -0,0 +1,4 @@
|
||||||
|
const { defineConfig } = require('@vue/cli-service')
|
||||||
|
module.exports = defineConfig({
|
||||||
|
transpileDependencies: true
|
||||||
|
})
|
|
@ -0,0 +1,9 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 276 B |
Loading…
Reference in New Issue