├── .DS_Store ├── .gitignore ├── 01.md ├── 01 ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── App1.vue │ ├── App3.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── Box.vue │ │ ├── Cart.vue │ │ ├── Demo.vue │ │ ├── ElementForm.vue │ │ ├── HelloWorld.vue │ │ ├── KForm.vue │ │ ├── KFormItem.vue │ │ ├── KInput.vue │ │ ├── Node.vue │ │ ├── Tree.vue │ │ └── notice │ │ │ ├── Notice.vue │ │ │ ├── message.js │ │ │ └── notice.js │ ├── kcomponent │ │ ├── Box.vue │ │ ├── Child.vue │ │ ├── GrandChild.vue │ │ ├── KForm.vue │ │ ├── KFormItem.vue │ │ ├── KInput.vue │ │ ├── KNotice │ │ │ ├── Notice.vue │ │ │ ├── message.js │ │ │ └── notice.js │ │ ├── Node.vue │ │ └── Tree.vue │ ├── main.js │ ├── plugins │ │ └── element.js │ └── views │ │ └── CartDemo.vue ├── tests │ └── unit │ │ ├── .eslintrc.js │ │ ├── example.spec.js │ │ └── form.spec.js └── vue.config.js ├── 02.md ├── 02 ├── .editorconfig ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── server.js ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ └── HelloWorld.vue │ ├── kvuex.js │ ├── main.js │ ├── pack.js │ ├── plugins │ │ └── element.js │ ├── router.js │ ├── store.js │ └── views │ │ ├── About.vue │ │ └── Home.vue └── vue.config.js ├── 03.md ├── 03 ├── .gitignore ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html └── src │ ├── App.vue │ ├── assets │ └── logo.png │ ├── components │ └── HelloWorld.vue │ ├── main.js │ ├── router.js │ ├── store.js │ └── views │ ├── About.vue │ └── Home.vue ├── 04.md ├── LICENSE ├── README.md ├── assets ├── 1 ├── 16986e19706090b6.jpeg ├── 16986e197076b0ba.gif ├── 16986e19709450a0.gif ├── 16986e1970b2cbe7.gif ├── 16986e1970cb2090.jpeg ├── 68747470733a2f2f74616e6762632e6769746875622e696f2f6769746875622d696d616765732f7669727475616c2d7363726f6c6c2d6c6973742d686f772d776f726b732e676966.gif ├── image-20190528162639132-9031999.png ├── image-20190528162639132.png ├── image-20190530162324922-9204604.png ├── image-20190530162324922.png ├── image-20190530183842855-9212722.png ├── image-20190530183842855.png ├── image-20190531181235671-9297555.png ├── image-20190531181235671.png ├── image-20190601170321711.png ├── image-20190601171113421.png ├── image-20190601190052631.png ├── image-20190601191303720.png ├── image-20190602100846843.png ├── lifecycle.png └── spin.gif ├── kkbvue ├── compile.js ├── index.html └── kaikeba-vue.js ├── vue-communicate ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html └── src │ ├── App.vue │ ├── App1.vue │ ├── assets │ └── logo.png │ ├── components │ ├── Child1.vue │ ├── Child2.vue │ ├── GrandChild1.vue │ ├── GrandChild2.vue │ └── GrandGrandChild1.vue │ ├── demo.js │ └── main.js ├── 图片.png └── 组件化动画演示.pptx /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengxinjing/vue-master-lesson/3f198bc76a804dcee1790aa1cb403624e5a20cd0/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /01.md: -------------------------------------------------------------------------------- 1 | # Vue组件化实战(8:30准时开始) 2 | 3 | [TOC] 4 | 5 | ## 0. 个人介绍 6 | 7 | ### 讲师介绍 8 | 9 | 10 | 11 | 12 | 13 | ### 课程介绍 14 | 15 | 不讲基础 魔鬼训练营,希望大家咬牙坚持下去,一定会有明显的变化 16 | 17 | 18 | 19 | ## 1. 组件 20 | 21 | ## 2. 最佳实践 22 | 23 | ## 3. 原理源码 24 | 25 | ## 4. 性能优化和部署 26 | 27 | 28 | 29 | 30 | 31 | ## 1. 课前准备 32 | 33 | 1. Vue官网熟悉 34 | 2. 用Vue开发过1个以上的项目 35 | 36 | 37 | 38 | ## 2. 课堂主题 39 | 40 | 1. Vue基础速度扫盲 41 | 42 | ## 3. 课堂目标 43 | 44 | 45 | 46 | ## 4. 知识点 47 | 48 | 49 | 50 | ### Vue基础扫盲 51 | 52 | 通过一个购物车的demo过一下基础api,设计基础点 53 | 54 | 1. Vue单文件组件 55 | 2. 条件渲染,循环渲染 56 | 3. 样式和class渲染 57 | 4. 组件 58 | 5. 事件绑定 59 | 6. 计算属性 60 | 7. 监听器 61 | 62 | ```html 63 | 80 | 81 | 132 | 138 | 139 | ``` 140 | 141 | ![image-20190528162639132](assets/image-20190528162639132-9031999.png) 142 | 143 | ### 组件 144 | 145 | 上面的业务大家也能看到,大概分为两个模块,一个是商品列表,可以添加购物车,一个是购物车页面,可以对购物车内部的商品做增删改查,都在一个页面里,代码很复杂,所以组件化,是Vue进阶中首先要要学习的技能 146 | 147 | 还是那句话,组件的官方文档这里就不介绍了 , 大家自行去看,下面我们先说组件设计中第一个老生常谈的问题,Vue的组件化通信方式 148 | 149 | 150 | 151 | #### 1.父传子 152 | 153 | 154 | 155 | Props 文档基本操作 156 | 157 | ```vue 158 | // App 159 | 164 | 165 | 178 | ``` 179 | 180 | ```vue 181 | // Child1 182 | 188 | 194 | ``` 195 | 196 | 197 | 198 | #### 2. 子传父 199 | 200 | Vue更推荐单向数据流,所以子组件像修改传递的数据,需要通知父组件来修改,使用$emit触发父元素传递的事件 201 | 202 | 203 | 204 | ```html 205 | 212 | 213 | 234 | 241 | ``` 242 | 243 | 244 | 245 | 246 | 247 | ```html 248 | // child1 249 | 256 | 267 | ``` 268 | 269 | 270 | 271 | #### 3. 兄弟组件 272 | 273 | 兄弟组件不能直接通信,只需要父元素搭个桥即可,大家自己体验即可 274 | 275 | 276 | 277 | #### 4. 祖先后代 provide & inject 278 | 279 | 280 | 281 | props一层层传递,爷爷给孙子还好,如果嵌套了五六层还这么写,感觉自己就是一个沙雕,所以这里介绍一个 稍微冷门的API, [provice/inject](https://cn.vuejs.org/v2/api/#provide-inject),类似React中的上下文,专门用来跨层级提供数据 282 | 283 | 284 | 285 | 现在很多开源库都使用这个api来做跨层级的数据共享,比如element-ui的[tabs](https://github.com/ElemeFE/element/blob/efcfbdde0f06e3e1816f1a8cd009a4e413e6e290/packages/tabs/src/tabs.vue#L26) 和 [select](https://github.com/ElemeFE/element/blob/f55fbdb051f95d52e92f7a66aee9a58e41025771/packages/select/src/select.vue#L161) 286 | 287 | ```html 288 | 289 | 290 | 301 | 608 | ``` 609 | 610 | ```html 611 |

{{name}}

612 | 613 | ``` 614 | 615 | 616 | 617 | ### 实现k-form-item 618 | 619 | 1. 预留插槽,扩展input 620 | 2. 能够展示label和校验的错误信息 621 | 3. 能够进行校验 622 | 623 | ```html 624 | 631 | 632 | 651 | ``` 652 | 653 | 654 | 655 | ```html 656 | 657 | 658 | 659 | ``` 660 | 661 | ### 实现Form 662 | 663 | - 给form-item预留槽位 664 | - 将数据传递给后代便于它们访问数据模型和校验规则 665 | - provide && inject 666 | 667 | 668 | 669 | ```html 670 | 675 | 676 | 689 | ``` 690 | 691 | 数据校验 692 | 693 | - 思路:校验发生在FormItem,它需要知道何时校验(让Input通知它),还需要知道怎么校验(注入校验规则) 694 | - 任务1:Input通知校验 695 | 696 | ```js 697 | onInput(e) { 698 | // ... 699 | // $parent指FormItem 700 | this.$parent.$emit('validate'); 701 | } 702 | ``` 703 | 704 | 任务2:FormItem监听校验通知,获取规则并执行校验 705 | 706 | ```js 707 | inject: ['form'], // 注入 708 | mounted(){// 监听校验事件 709 | this.$on('validate', this.validate) 710 | }, 711 | methods: { 712 | validate() { 713 | // 获取对应FormItem校验规则 714 | console.log(this.form.rules[this.prop]); 715 | // 获取校验值 716 | console.log(this.form.model[this.prop]); 717 | } 718 | }, 719 | ``` 720 | 721 | ```js 722 | import schema from "async-validator"; 723 | 724 | validate() { 725 | // 获取对应FormItem校验规则 726 | const rules = this.form.rules[this.prop]; 727 | // 获取校验值 728 | const value = this.form.model[this.prop]; 729 | // 校验描述对象 730 | const descriptor = { [this.prop]: rules }; 731 | // 创建校验器 732 | const schema = new Schema(descriptor); 733 | schema.validate({ [this.prop]: value }, errors => { 734 | if (errors) { 735 | // 将错误信息显示 736 | this.error = errors[0].message; 737 | } else { 738 | // 校验通过 739 | this.error = ""; 740 | } 741 | }); 742 | } 743 | ``` 744 | 745 | 任务3:表单全局验证 746 | 747 | - 改造FormItem的validate方法,使其可以返回Promise 748 | 749 | ```js 750 | validate() { 751 | return new Promise((resolve, reject) => { 752 | // ... 753 | schema.validate({ [this.prop]: value }, errors => { 754 | if (errors) {// 校验失败 755 | reject() 756 | } else { // 校验通过 757 | resolve(); 758 | } 759 | }); 760 | }); 761 | } 762 | ``` 763 | 764 | 为Form提供validate方法 765 | 766 | ```js 767 | validate(cb) { 768 | // 调用所有含有prop属性的子组件的validate方法并得到Promise数组 769 | const tasks = this.$children 770 | .filter(item => item.prop) 771 | .map(item => item.validate()); 772 | // 所有任务必须全部成功才算校验通过,任一失败则校验失败 773 | Promise.all(tasks) 774 | .then(() => cb(true)) 775 | .catch(() => cb(false)); 776 | } 777 | ``` 778 | 779 | ```html 780 | 781 | 提交 782 | 783 | ``` 784 | 785 | #### 扩展 786 | 787 | 组件内部也可能嵌套,this.$parent改成 this.$diapsth更靠谱 788 | 789 | ```html 790 | 791 | 792 | 793 | 794 | 795 | 796 | ``` 797 | 798 | ```js 799 | onBlur(e){ 800 | this.$dispatch('validate') 801 | }, 802 | 803 | ``` 804 | 805 | 引入了async-validator,所以支持更多的检验规则 806 | 807 | ```js 808 | rules: { 809 | username: [ 810 | { required: true, message: "请输入用户名" }, 811 | { type: 'email', message: '邮箱格式不正确' } 812 | ], 813 | password: [{ required: true, message: "请输入密码" }], 814 | } 815 | ``` 816 | 817 | ### element的表单源码 818 | 819 | /src/mixins/emitter.js 820 | 821 | /packages/form 822 | 823 | /packages/input ( watch value , handleBlur) 824 | 825 | 826 | 827 | ### 为什么要测试? 828 | 829 | 组件的单元测试有很多好处: 830 | 831 | - 提供描述组件行为的文档 832 | - 节省手动测试的时间 833 | - 减少研发新特性时产生的 bug 834 | - 改进设计 835 | - 促进重构 836 | 837 | 自动化测试使得大团队中的开发者可以维护复杂的基础代码。 838 | 839 | 测试的思路 无非就是 840 | 841 | ### 弹窗 842 | 843 | element的组件,除了template中写,还可以在JS种直接调用的 844 | 845 | ```javascript 846 | this.$notify({ 847 | title: '提示', 848 | message: '这是一条不会自动关闭的消息', 849 | duration: 0 850 | }); 851 | ``` 852 | 853 | 854 | 855 | 这种方式在其他ui库,比如cube-ui,也是非常有特色的功能 856 | 857 | #### 普通的弹窗 858 | 859 | ```html 860 | 867 | 868 | 873 | 874 | 894 | ``` 895 | 896 | 897 | 898 | 想用类似window.alert的方式使用,我们需要手动控制$mount的实际,在执行this.​$alert的时候, 再去挂载这个组件,并且能够传递相应的数据, 899 | 900 | 组件渲染数组,提供add和remove来添加和删除信息 901 | 902 | 903 | 904 | ```html 905 | 910 | 944 | 964 | ``` 965 | 966 | 967 | 968 | #### JS调用 动态渲染 969 | 970 | 弹窗还有一个特殊的情况,就是必须要渲染在body上,如果在当前组件内部渲染, 当前组件的transform等属性,会影响表单,我们提供一个渲染到body的方法,newInstance 971 | 972 | ```js 973 | import Notice from './Notice.vue'; 974 | import Vue from 'vue'; 975 | 976 | Notice.newInstance = properties => { 977 | const props = properties || {}; 978 | 979 | const Instance = new Vue({ 980 | data: props, 981 | render :h=> 982 | }); 983 | // 手动mount 984 | const component = Instance.$mount(); 985 | document.body.appendChild(component.$el); 986 | 987 | const notice = Instance.$children[0]; 988 | 989 | return { 990 | add (noticeProps) { 991 | notice.add(noticeProps); 992 | }, 993 | remove (name) { 994 | notice.remove(name); 995 | } 996 | } 997 | }; 998 | 999 | export default Notice; 1000 | ``` 1001 | 1002 | 对外暴露info方法 1003 | 1004 | 1005 | 1006 | ```js 1007 | import Message from './message.js'; 1008 | 1009 | let instance; 1010 | 1011 | function notice({ duration = 1.5, content = '' }) { 1012 | instance = instance || Message.newInstance(); 1013 | instance.add({ 1014 | content: content, 1015 | duration: duration 1016 | }); 1017 | } 1018 | 1019 | export default { 1020 | info (options) { 1021 | return notice(options); 1022 | } 1023 | } 1024 | ``` 1025 | 1026 | main.js挂载原型链 1027 | 1028 | ```js 1029 | 1030 | Vue.prototype.$Notice = Notice 1031 | ``` 1032 | 1033 | 使用贼简单 1034 | 1035 | ```js 1036 | this.$Notice.info({ 1037 | content: '必须的!', 1038 | duration: 2 1039 | }); 1040 | ``` 1041 | 1042 | ### 递归组件Tree 1043 | 1044 | 我们经常见到的tree组件,无限导航都是感觉一样的组件,嵌套很多层,这里就有一个组件递归的概念, 和函数的递归一样,自己调用自己,类似俄罗斯套娃,在某个条件下终止这个递归即可 1045 | 1046 | ```html 1047 | 1057 | 1058 | 1121 | 1122 | 1124 | 1125 | ``` 1126 | 1127 | 1128 | 1129 | Node.vue 1130 | 1131 | ```html 1132 | 1140 | 1141 | 1157 | 1158 | 1160 | 1161 | ``` 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | ## 5. 扩展 1170 | 1171 | element源码 1172 | 1173 | ## 6. 总结 1174 | 1175 | [TOC] 1176 | 1177 | ## 7. 作业 1178 | 1179 | 1. 选择题 1180 | 2. 思考题(不做考核要求,第二天开课前讲解) 1181 | 1. 递归组件如果加checkbox全选,如何实现 1182 | 1183 | ## 8. 问答 1184 | 1185 | 1186 | 1187 | ## 9. 预告 1188 | 1189 | Vue最佳实践 1190 | 1191 | 1. 学完后掌握Vue技术栈的方方面面 1192 | 2. 路由守卫 1193 | 3. mock数据 1194 | 4. 权限设计 1195 | 5. 复杂项目中的Vue实战 -------------------------------------------------------------------------------- /01/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw? 22 | -------------------------------------------------------------------------------- /01/README.md: -------------------------------------------------------------------------------- 1 | 2 | JS 全栈原价:9980元 3 | 4 | 优惠政策 5 | 前 50 名 8980 6 | 今晚报名抵 500 元,就是 8480 元 7 | 第二天,只能抵 300 8 | 第三天,只能抵 200 9 | 第四天,只能抵 100 10 | 11 | 此外,还可以有 JS 高级课程可以赠送旁听资格,价值:1980 元 12 | 13 | 本次旁听课,从项目需求着手,学会Vue全家桶(Vue技术栈)应用的方方面面,如: 14 | 15 | 1. MVVM、数据流、双绑、组件、指令共同构建起了Vue核心 16 | 17 | 2. 路由、内容分发、MPA、SPA也是一个项目常见模式 18 | 19 | 3. 数据状态管理、同步异步任务… 20 | 21 | 各种知识点环环相扣,由浅入深,带你掌握前端项目工程化开发。 22 | 23 | 24 | 测试太多怎么办 25 | 逼你写出易于测试的代码 26 | 27 | 任何公用组件 都需要测试 28 | 29 | 30 | 31 | const bus = new Vue(),不是一个新的对象内,怎么on,emit让那个main.js的vue接收的 32 | 33 | 34 | 35 | 36 | 37 | 38 | 1. 总线机制 39 | 1. 要对Vue本身的$On和$emit熟悉 40 | 2. vue源码看的懂讲不清 41 | 1. 再看一遍 42 | 2. 敲一遍 43 | 3. 课程一共4个月,每周三次课 44 | 4. antd-vue 没用过 不评价 45 | 5. 全栈是我呀 46 | 6. 学完全栈课 20K没问题 47 | 7. 全栈课的精华 都是大厂的要求 48 | 8. uni-app 跨端 好用 49 | 50 | 51 | -------------------------------------------------------------------------------- /01/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "presets": [ 3 | "@vue/app" 4 | ], 5 | "plugins": [ 6 | [ 7 | "component", 8 | { 9 | "libraryName": "element-ui", 10 | "styleLibraryName": "theme-chalk" 11 | } 12 | ] 13 | ] 14 | } -------------------------------------------------------------------------------- /01/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "01", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "test:unit": "vue-cli-service test:unit" 10 | }, 11 | "dependencies": { 12 | "axios": "^0.18.0", 13 | "core-js": "^2.6.5", 14 | "element-ui": "^2.4.5", 15 | "vue": "^2.6.10" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^3.8.0", 19 | "@vue/cli-plugin-eslint": "^3.8.0", 20 | "@vue/cli-plugin-unit-jest": "^3.8.0", 21 | "@vue/cli-service": "^3.8.0", 22 | "@vue/test-utils": "1.0.0-beta.29", 23 | "babel-core": "7.0.0-bridge.0", 24 | "babel-eslint": "^10.0.1", 25 | "babel-jest": "^23.6.0", 26 | "babel-plugin-component": "^1.1.1", 27 | "eslint": "^5.16.0", 28 | "eslint-plugin-vue": "^5.0.0", 29 | "vue-cli-plugin-element": "^1.0.1", 30 | "vue-template-compiler": "^2.6.10" 31 | }, 32 | "eslintConfig": { 33 | "root": true, 34 | "env": { 35 | "node": true 36 | }, 37 | "extends": [ 38 | "plugin:vue/essential", 39 | "eslint:recommended" 40 | ], 41 | "rules": {}, 42 | "parserOptions": { 43 | "parser": "babel-eslint" 44 | } 45 | }, 46 | "postcss": { 47 | "plugins": { 48 | "autoprefixer": {} 49 | } 50 | }, 51 | "browserslist": [ 52 | "> 1%", 53 | "last 2 versions" 54 | ], 55 | "jest": { 56 | "collectCoverage":true, 57 | "collectCoverageFrom": [ 58 | "src/components/**/*.{js,vue}", 59 | "!**/node_modules/**" 60 | ], 61 | "moduleFileExtensions": [ 62 | "js", 63 | "jsx", 64 | "json", 65 | "vue" 66 | ], 67 | "transform": { 68 | "^.+\\.vue$": "vue-jest", 69 | ".+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$": "jest-transform-stub", 70 | "^.+\\.jsx?$": "babel-jest" 71 | }, 72 | "transformIgnorePatterns": [ 73 | "/node_modules/" 74 | ], 75 | "moduleNameMapper": { 76 | "^@/(.*)$": "/src/$1" 77 | }, 78 | "snapshotSerializers": [ 79 | "jest-serializer-vue" 80 | ], 81 | "testMatch": [ 82 | "**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)" 83 | ], 84 | "testURL": "http://localhost/", 85 | "watchPlugins": [ 86 | "jest-watch-typeahead/filename", 87 | "jest-watch-typeahead/testname" 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /01/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengxinjing/vue-master-lesson/3f198bc76a804dcee1790aa1cb403624e5a20cd0/01/public/favicon.ico -------------------------------------------------------------------------------- /01/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 01 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /01/src/App.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /01/src/App1.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /01/src/App3.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 87 | 88 | 91 | -------------------------------------------------------------------------------- /01/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shengxinjing/vue-master-lesson/3f198bc76a804dcee1790aa1cb403624e5a20cd0/01/src/assets/logo.png -------------------------------------------------------------------------------- /01/src/components/Box.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /01/src/components/Cart.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 70 | 76 | -------------------------------------------------------------------------------- /01/src/components/Demo.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /01/src/components/ElementForm.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /01/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 42 | 43 | 44 | 60 | -------------------------------------------------------------------------------- /01/src/components/KForm.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 54 | 55 | -------------------------------------------------------------------------------- /01/src/components/KFormItem.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 66 | 67 | -------------------------------------------------------------------------------- /01/src/components/KInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 32 | 33 | -------------------------------------------------------------------------------- /01/src/components/Node.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /01/src/components/Tree.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 78 | 79 | 81 | -------------------------------------------------------------------------------- /01/src/components/notice/Notice.vue: -------------------------------------------------------------------------------- 1 | 6 | 40 | -------------------------------------------------------------------------------- /01/src/components/notice/message.js: -------------------------------------------------------------------------------- 1 | import Notice from './Notice.vue'; 2 | import Vue from 'vue'; 3 | 4 | Notice.newInstance = properties => { 5 | const props = properties || {}; 6 | 7 | const Instance = new Vue({ 8 | data: props, 9 | render :h=> 10 | }); 11 | 12 | const component = Instance.$mount(); 13 | document.body.appendChild(component.$el); 14 | 15 | const notice = Instance.$children[0]; 16 | 17 | return { 18 | add (noticeProps) { 19 | notice.add(noticeProps); 20 | }, 21 | remove (name) { 22 | notice.remove(name); 23 | } 24 | } 25 | }; 26 | 27 | export default Notice; -------------------------------------------------------------------------------- /01/src/components/notice/notice.js: -------------------------------------------------------------------------------- 1 | import Message from './message.js'; 2 | 3 | let instance; 4 | // 单例模式 5 | function notice({ duration = 1.5, content = '' }) { 6 | // 如果没有实例,新建一个 7 | // 如果有实例 复用 8 | instance = instance || Message.newInstance(); 9 | instance.add({ 10 | content: content, 11 | duration: duration 12 | }); 13 | } 14 | 15 | export default { 16 | info (options) { 17 | return notice(options); 18 | } 19 | } -------------------------------------------------------------------------------- /01/src/kcomponent/Box.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 35 | -------------------------------------------------------------------------------- /01/src/kcomponent/Child.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /01/src/kcomponent/GrandChild.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /01/src/kcomponent/KForm.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 30 | 31 | 34 | -------------------------------------------------------------------------------- /01/src/kcomponent/KFormItem.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /01/src/kcomponent/KInput.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 33 | 34 | 37 | -------------------------------------------------------------------------------- /01/src/kcomponent/KNotice/Notice.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 42 | 43 | 63 | -------------------------------------------------------------------------------- /01/src/kcomponent/KNotice/message.js: -------------------------------------------------------------------------------- 1 | // Notive.vued的动态渲染 2 | import Notice from './Notice' 3 | import Vue from 'vue' 4 | 5 | Notice.newInstance = properties =>{ 6 | const props = properties || {} 7 | const Instance = new Vue({ 8 | data:props, 9 | render: h => 10 | }) 11 | const component = Instance.$mount() 12 | document.body.appendChild(component.$el) 13 | 14 | const notice = Instance.$children[0] 15 | 16 | return { 17 | add(noticeProps){ 18 | notice.add(noticeProps) 19 | }, 20 | remove(name){ 21 | notice.remove(name) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /01/src/kcomponent/KNotice/notice.js: -------------------------------------------------------------------------------- 1 | // 单例模式,缓存 2 | 3 | import Message from './message' 4 | 5 | // 最终对外暴露的api 6 | 7 | let instance 8 | 9 | function notice({duration=1.5, content=""}){ 10 | instance = instance || Message.newInstance() 11 | instance.add({ 12 | content, 13 | duration 14 | }) 15 | } 16 | export default{ 17 | info(options){ 18 | return notice(options) 19 | } 20 | } -------------------------------------------------------------------------------- /01/src/kcomponent/Node.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /01/src/kcomponent/Tree.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 77 | 78 | 81 | -------------------------------------------------------------------------------- /01/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './plugins/element.js' 4 | import Notice from '../src/components/notice/notice.js' 5 | // Notice.info 6 | 7 | Vue.prototype.$Notice = Notice 8 | // 老师什么时候使用挂载方法? 9 | Vue.config.productionTip = false 10 | 11 | // class Bus { 12 | // constructor(){ 13 | // this.callbacks = {} 14 | // } 15 | // $on(name, fn){ 16 | // // 监听 17 | // this.callbacks[name] = this.callbacks[name] || [] 18 | // this.callbacks[name].push(fn) 19 | // } 20 | // $emit(name, args){ 21 | // // 触发 22 | // if(this.callbacks[name]){ 23 | // this.callbacks[name].forEach(cb=>cb(args)) 24 | // } 25 | // } 26 | // } 27 | 28 | Vue.prototype.$bus = new Vue() 29 | 30 | // Vue本身有$on和$emit的机制 31 | 32 | Vue.prototype.$dispatch = function(eventName,data){ 33 | // 想上传递,一直不停的获取$parent 34 | let parent = this.$parent 35 | while(parent){ 36 | parent.$emit(eventName, data) 37 | parent = parent.$parent 38 | } 39 | } 40 | Vue.prototype.$boardcast = function(eventName, data){ 41 | // 递归通知所有的子元素 42 | boardcast.call(this, eventName, data) 43 | } 44 | function boardcast(eventName, data){ 45 | this.$children.forEach(child=>{ 46 | // 每一个字组件 47 | child.$emit(eventName, data) 48 | if(child.$children.length){ 49 | boardcast.call(child, eventName, data) 50 | } 51 | }) 52 | } 53 | 54 | // Vue.prototype.$dispatch = function(eventName, data) { 55 | // let parent = this.$parent 56 | // // 查找父元素 57 | // while (parent ) { 58 | // if (parent) { 59 | // // 父元素用$emit触发 60 | // parent.$emit(eventName,data) 61 | // // 递归查找父元素 62 | // parent = parent.$parent 63 | // }else{ 64 | // break 65 | // } 66 | // } 67 | // } 68 | 69 | // Vue.prototype.$boardcast = function(eventName, data){ 70 | // boardcast.call(this,eventName,data) 71 | // } 72 | // function boardcast(eventName, data){ 73 | // this.$children.forEach(child => { 74 | // // 子元素触发$emit 75 | // child.$emit(eventName, data) 76 | // if(child.$children.length){ 77 | // // 递归调用,通过call修改this指向 child 78 | // boardcast.call(child, eventName, data) 79 | // } 80 | // }); 81 | // } 82 | 83 | new Vue({ 84 | render: h => h(App), 85 | }).$mount('#app') 86 | -------------------------------------------------------------------------------- /01/src/plugins/element.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Element from 'element-ui' 3 | import 'element-ui/lib/theme-chalk/index.css' 4 | 5 | Vue.use(Element) 6 | -------------------------------------------------------------------------------- /01/src/views/CartDemo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 28 | -------------------------------------------------------------------------------- /01/tests/unit/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | jest: true 4 | } 5 | } -------------------------------------------------------------------------------- /01/tests/unit/example.spec.js: -------------------------------------------------------------------------------- 1 | import { shallowMount } from '@vue/test-utils' 2 | import HelloWorld from '@/components/HelloWorld.vue' 3 | 4 | describe('HelloWorld.vue', () => { 5 | it('renders props.msg when passed', () => { 6 | const msg = 'new message' 7 | const wrapper = shallowMount(HelloWorld, { 8 | propsData: { msg } 9 | }) 10 | expect(wrapper.text()).toMatch(msg) 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /01/tests/unit/form.spec.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import KForm from '../../src/components/KForm' 3 | import KInput from '../../src/components/KInput' 4 | import KFormItem from '../../src/components/KFormItem' 5 | import { mount } from '@vue/test-utils' 6 | // Vue.component({KForm}) 7 | // Vue.component({KInput}) 8 | // Vue.component({KFormItem}) 9 | describe('测试表单',()=>{ 10 | 11 | it('加法',()=>{ 12 | expect(1+2).toBe(3) 13 | }) 14 | it('输入',()=>{ 15 | 16 | let wrapper = mount({ 17 | components:{KInput}, 18 | template:` 19 |
20 |

{{name}}

21 | 22 |
23 | `, 24 | data(){ 25 | return { 26 | name:'hi' 27 | } 28 | } 29 | }) 30 | expect(wrapper.find('input').attributes().type ).toBe('text') 31 | expect(wrapper.find('input').element.value ).toBe('hi') 32 | expect(wrapper.find('h1').text() ).toBe('hi') 33 | wrapper.vm.name = 'hello world' 34 | expect(wrapper.find('input').element.value ).toBe('hello world') 35 | expect(wrapper.find('h1').text() ).toBe('hello world') 36 | 37 | }) 38 | it('输入',()=>{ 39 | let wrapper = mount({ 40 | components:{KInput}, 41 | template:` 42 |
43 |

{{name}}

44 | 45 |
46 | `, 47 | data(){ 48 | return { 49 | name:'hi' 50 | } 51 | } 52 | }) 53 | expect(wrapper.find('input').attributes().type ).toBe('text') 54 | expect(wrapper.find('input').element.value ).toBe('hi') 55 | expect(wrapper.find('h1').text() ).toBe('hi') 56 | wrapper.vm.name = 'hello world' 57 | expect(wrapper.find('input').element.value ).toBe('hello world') 58 | expect(wrapper.find('h1').text() ).toBe('hello world') 59 | 60 | }) 61 | it('validate field', done => { 62 | let wrapper = mount({ 63 | components:{ 64 | KInput, 65 | KFormItem, 66 | KForm 67 | }, 68 | 69 | template: ` 70 | 71 | 72 | 73 | 74 | 75 | `, 76 | data() { 77 | return { 78 | form: { 79 | name: '' 80 | }, 81 | rules: { 82 | name: [ 83 | { required: true, message: '请输入活动名称', min: 3, max: 6 } 84 | ] 85 | } 86 | }; 87 | }, 88 | methods: { 89 | setValue(value) { 90 | this.form.name = value; 91 | } 92 | } 93 | }, true); 94 | console.log() 95 | wrapper.vm.$refs.form.validate(valid => { 96 | expect(valid).toBe(false); 97 | done(); 98 | }); 99 | }); 100 | }) -------------------------------------------------------------------------------- /01/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | configureWebpack: { 3 | // 扩展webpack 4 | devServer: { 5 | before(app) { 6 | app.get('/api/goods', function (req, res) { 7 | res.json({ 8 | list: [ 9 | { text: '百万年薪架构师', price: 100 }, 10 | { text: 'web全栈架构师', price: 80 }, 11 | { text: 'Python爬虫', price: 60 } 12 | ] 13 | }); 14 | }); 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /02.md: -------------------------------------------------------------------------------- 1 | # Vue.js最佳实践 2 | 3 | [TOC] 4 | 5 | 6 | 7 | 8 | 9 | ## 0. 作业 10 | 11 | 1. 测试题不用每个都答对,目的不是分数,是思考和讨论的过程 只要做了都返钱 12 | 2. 答的不好不要气馁,思考 13 | 14 | 15 | 16 | 17 | 18 | 1、关于MVVM的理解,下列哪项是不正确的?(D)[单选题] 19 | A、Model代表数据模型,也可以在Model中定义数据修改和操作的业务逻辑。 20 | B、View代表UI 组件,它负责将数据模型转化成UI 展现出来。 21 | C、ViewModel 监听模型数据的改变和控制视图行为、处理用户交互。 22 | D、在MVVM架构下,开发者完全不需要手动操作DOM,数据状态的同步由MVVM管理。 23 | 24 | scroll focus 咱们第一次课做的弹窗,都需要手动操作 25 | 26 | 27 | 28 | 2、关于Vue的生命周期,下列哪项是不正确的?(B)[单选题] 29 | A、Vue 实例从创建到销毁的过程,就是生命周期。 30 | B、页面首次加载会触发beforeCreate, created, beforeMount, mounted, beforeUpdate, updated。 31 | C、created表示完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来。 32 | D、DOM 渲染在 mounted 中就已经完成了。 33 | 34 | beforeUpdate, updated。这个是更新 35 | 36 | 3、关于Vue组件间的参数传递,下列哪项是不正确的?(C)[单选题] 37 | A、子组件给父组件传值,使用$emit方法 38 | B、子组件使用​$emit('someEvent')派发事件,父组件使用@someEvent监听 39 | C、祖孙组件间可以使用provide和inject方式跨层级相互传值 40 | D、父组件给子组件传值,子组件通过props接受数据 41 | 42 | 不能相互 再解释一下$emit 43 | 44 | 4、下列关于v-model的说法,哪项是正确的?(BD)[多选题] 45 | A、v-model只能用于表单控件,实现双向数据绑定 46 | B、v-model本质上是语法糖,它实现了用户的输入事件监听以及数据更新 47 | C、v-model是内置指令,不能用在自定义组件上 48 | D、对input使用v-model实际上是指定其:value和@input 49 | 50 | 我敲错了,也算是检验大家学习成果,嗯 是这样的 51 | 52 | 53 | 54 | 5、下列说法正确的是哪项?(ACD)[多选题] 55 | A、key的作用主要是为了高效的更新虚拟DOM 56 | B、若指定了组件的template选项,render函数不会执行 57 | C、使用vm.$nextTick可以确保获得DOM异步更新的结果 58 | D、若没有el选项,vm.​$mount(dom)可将Vue实例挂载于指定元素上 59 | 60 | render优先级高 61 | 62 | 63 | 64 | 思考题 65 | 66 | dispatch boardcast 67 | 68 | 进阶思考题 69 | 70 | 如何做到子元素全选后,父元素自动全选? 71 | 72 | 73 | 74 | 75 | 76 | ## 1. 课前准备 77 | 78 | 回顾 79 | 80 | provide && inject 81 | 82 | $emit 83 | 84 | 组件化 85 | 86 | 项目基础 87 | 88 | 89 | 90 | ## 2. 课堂主题 91 | 92 | 1. vue-cli 93 | 2. webpack 94 | 3. vuex 95 | 4. vue-router 96 | 5. 工作流 97 | 6. 权限 98 | 99 | 100 | 101 | ## 3. 课堂目标 102 | 103 | 104 | 105 | ## 4. 知识点 106 | 107 | ### 规范 108 | 109 | 文件名,约定好即可 110 | 111 | ```bash 112 | ├── build // 构建相关   113 | ├── config // 配置相关 114 | ├── mock // 假数据 115 | ├── src // 源代码 116 | │   ├── api // 所有网络请求 117 | │   ├── assets // 主题 字体等静态资源 118 | │   ├── components // 全局公用组件 119 | │   ├── filtres // 全局 filter 120 | │   ├── icons // 项目icons 121 | │   ├── router // 路由 122 | │   ├── store // 全局 store管理 123 | │   ├── styles // 全局样式 124 | │   ├── utils // 全局公用方法 125 | │   ├── vendor // 公用vendor 126 | │   ├── views // 页面view 127 | │   ├── App.vue // 入口页面 128 | │ └── main.js // 入口 加载组件 初始化等 129 | ├── static // 第三方不打包资源 130 | │   └── Tradingview // 富文本 131 | ├── .babelrc // babel-loader 配置 132 | ├── eslintrc.js // eslint 配置项 133 | ├── .gitignore // git 忽略项 134 | ``` 135 | 136 | 这个没啥太多说的,记得页面级的组件, 放在view里,公用放在components 137 | 138 | 139 | 140 | 141 | 142 | ### 数据mock的姿势 143 | 144 | 145 | 146 | 1. vue-cli假数据,利用webpack-dev-server中内置的express 147 | 148 | ```js 149 | module.exports = { 150 | configureWebpack: { 151 | // 扩展webpack 152 | devServer: { 153 | before(app) { 154 | // app其实就是个exoress 155 | app.get('/api/goods', function (req, res) { 156 | res.json({ 157 | list: [ 158 | { text: '百万年薪架构师', price: 100 }, 159 | { text: 'web全栈架构师', price: 80 }, 160 | { text: 'Python爬虫', price: 60 } 161 | ] 162 | }); 163 | }); 164 | } 165 | } 166 | } 167 | } 168 | ``` 169 | 170 | 171 | 172 | 2. easy-mock + proxy 173 | 174 | 开发项目大部分 都会采用swagui 或者easy-mock这种接口文档工具 175 | 176 | 177 | 178 | 179 | 180 | ### vue-cli 181 | 182 | ```bash 183 | ➜ ~ vue 184 | Usage: vue [options] 185 | 186 | Options: 187 | -V, --version output the version number 188 | -h, --help output usage information 189 | 190 | Commands: 191 | create [options] create a new project powered by vue-cli-service 192 | add [options] [pluginOptions] install a plugin and invoke its generator in an already created project 193 | invoke [options] [pluginOptions] invoke the generator of a plugin in an already created project 194 | inspect [options] [paths...] inspect the webpack config in a project with vue-cli-service 195 | serve [options] [entry] serve a .js or .vue file in development mode with zero config 196 | build [options] [entry] build a .js or .vue file in production mode with zero config 197 | ui [options] start and open the vue-cli ui 198 | init [options]