Compare commits

...

11 Commits

Author SHA1 Message Date
bunny eb49928833 Vuex 状态管理最佳实践指南 2025-06-19 12:44:04 +08:00
bunny 5546635e31 🎉 初始化vuex 2025-06-19 07:54:16 +08:00
bunny bfaaeb8843 球和案例 2025-06-19 07:38:57 +08:00
bunny 84e7108798 🎉 求和案例init 2025-06-19 07:38:09 +08:00
bunny 39e7447e70 Vue2 Mixins 混入机制详解 2025-06-19 07:25:00 +08:00
bunny 926cb154cb 🎉 Vue2 Props 属性传递机制详解 2025-06-18 22:04:50 +08:00
bunny 85c1ae88d5 Vue2 中的 ref 引用机制详解 2025-06-18 21:48:34 +08:00
bunny cdbdbec0f8 Vue2 中的 ref 引用机制详解 2025-06-18 21:48:18 +08:00
bunny 466b18afed 🎨 修改vue文件位置 2025-06-17 23:07:45 +08:00
bunny 61b71b3163 15-VueComponent构造函数.html 2025-06-17 23:05:08 +08:00
bunny dbf4e201ce 14-自定义指令_函数式 2025-06-14 22:44:59 +08:00
78 changed files with 19162 additions and 12 deletions

View File

@ -3,7 +3,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>模板语法</title>
</head>

View File

@ -4,7 +4,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>10-列表渲染</title>
</head>

View File

@ -3,7 +3,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>列表过滤</title>
</head>

View File

@ -3,7 +3,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>Vue监测数据的原理_数组</title>
</head>

View File

@ -3,8 +3,8 @@
<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>
<script src="js/dayjs.min.js"></script>
<script src="../js/vue@2.7.16.js"></script>
<script src="../js/dayjs.min.js"></script>
<title>过滤器</title>
</head>
<body>

View File

@ -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>

View File

@ -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中使用指令 |

View File

@ -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>

View File

@ -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 |
| 性能优化 | 手动配置 | 自动优化 |
| 片段支持 | 单根节点 | 多根节点 |

View File

@ -4,7 +4,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>数据绑定</title>
</head>

View File

@ -4,7 +4,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>Vue中的数据代理</title>
</head>

View File

@ -4,7 +4,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>事件处理</title>
</head>

View File

@ -4,7 +4,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>键盘事件</title>
</head>

View File

@ -4,7 +4,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>计算属性</title>
</head>

View File

@ -4,7 +4,7 @@
<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>
<script src="../js/vue@2.7.16.js"></script>
<title>监视属性</title>
</head>

File diff suppressed because it is too large Load Diff

View File

@ -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>

View File

@ -0,0 +1,3 @@
<template>
<h2>学校</h2>
</template>

View File

@ -0,0 +1,3 @@
<template>
<h2>学生</h2>
</template>

View File

@ -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>

View File

@ -0,0 +1,9 @@
import App from "./App.vue";
new Vue({
el: "#app",
template: "<App></App>",
compontens: {
App,
},
});

View File

@ -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?

View File

@ -0,0 +1 @@
shamefully-hoist=true

View File

@ -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/).

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
};

View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

View File

@ -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

View File

@ -0,0 +1,2 @@
ignoredBuiltDependencies:
- core-js

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -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>

View File

@ -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

View File

@ -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");

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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. 动态refVue 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()` 函数创建响应式引用

View File

@ -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 名称**:使用 camelCaseJavaScript
- **HTML 特性**:使用 kebab-caseDOM
```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参数替代 |

View File

@ -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>

View File

@ -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>

View File

@ -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支持 |

View File

@ -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>

View File

@ -0,0 +1,14 @@
<template>
<div>
<SchoolInfo />
</div>
</template>
<script>
import SchoolInfo from "./components/SchoolInfo.vue";
export default {
name: "Demo-3",
components: { SchoolInfo },
};
</script>

View File

@ -0,0 +1,12 @@
export const test1 = {
data() {
return {
username: "Bunny",
};
},
methods: {
showMessage() {
alert(`展示消息:${this.username}`);
},
},
};

View File

@ -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 | 需要额外配置 | 完美支持 |
| 体积 | 较大 | 更轻量 |

View File

@ -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>

View File

@ -0,0 +1,14 @@
<template>
<Count />
</template>
<script>
import Count from "./compontens/Count.vue";
export default {
name: "Demo-4",
components: {
Count,
},
};
</script>

View File

@ -0,0 +1,4 @@
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})

View File

@ -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

View File

Before

Width:  |  Height:  |  Size: 276 B

After

Width:  |  Height:  |  Size: 276 B