├── .gitignore ├── README.md ├── babel.config.js ├── kvue ├── 01-reactive.js ├── 02-reactive.html ├── kvue.html └── kvue.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.vue ├── assets └── logo.png ├── components ├── HelloWorld.vue ├── communication │ ├── Child1.vue │ ├── Child2.vue │ └── index.vue ├── form │ ├── ElementTest.vue │ └── index.vue ├── recursion │ ├── Tree.vue │ ├── TreeNode.vue │ └── index.vue └── slots │ ├── Layout.vue │ └── index.vue ├── krouter ├── index.js └── kvue-router.js ├── kstore ├── index.js └── kvuex.js ├── main.js ├── plugins └── element.js ├── router └── index.js ├── store └── index.js └── views ├── About.vue └── Home.vue /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # study-vue 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /kvue/01-reactive.js: -------------------------------------------------------------------------------- 1 | // 给一个obj定义一个响应式的属性 2 | function defineReactive(obj, key, val) { 3 | // 递归 4 | // val如果是个对象,就需要递归处理 5 | observe(val) 6 | 7 | Object.defineProperty(obj, key, { 8 | get() { 9 | console.log("get", key); 10 | return val; 11 | }, 12 | set(newVal) { 13 | if (newVal !== val) { 14 | console.log("set", key); 15 | val = newVal; 16 | // 新值如果是对象,仍然需要递归遍历处理 17 | observe(newVal) 18 | // update() 19 | } 20 | }, 21 | }); 22 | } 23 | 24 | // 遍历响应式处理 25 | function observe(obj) { 26 | if (typeof obj !== "object" || obj == null) { 27 | return obj; 28 | } 29 | 30 | Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key])); 31 | } 32 | 33 | function set(obj, key, val) { 34 | defineReactive(obj, key, val) 35 | } 36 | 37 | const obj = { 38 | foo: 'foo', 39 | bar: 'bar', 40 | baz: { 41 | n: 1 42 | } 43 | }; 44 | // defineReactive(obj, "foo", "foo"); 45 | observe(obj) 46 | // obj.foo; 47 | // obj.baz = { 48 | // n: 10 49 | // } 50 | // obj.baz.n 51 | // obj.dong = 'dong' 52 | set(obj, 'dong', 'dong') 53 | obj.dong 54 | -------------------------------------------------------------------------------- /kvue/02-reactive.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | -------------------------------------------------------------------------------- /kvue/kvue.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

{{counter}}

4 |

{{counter}}

5 |

6 |

7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /kvue/kvue.js: -------------------------------------------------------------------------------- 1 | // 给一个obj定义一个响应式的属性 2 | function defineReactive(obj, key, val) { 3 | // 递归 4 | // val如果是个对象,就需要递归处理 5 | observe(val); 6 | 7 | // 创建Dep实例 8 | const dep = new Dep() 9 | 10 | Object.defineProperty(obj, key, { 11 | get() { 12 | console.log("get", key); 13 | // 依赖关系收集 14 | Dep.target && dep.addDep(Dep.target) 15 | return val; 16 | }, 17 | set(newVal) { 18 | if (newVal !== val) { 19 | console.log("set", key); 20 | val = newVal; 21 | // 新值如果是对象,仍然需要递归遍历处理 22 | observe(newVal); 23 | // update() 24 | dep.notify() 25 | } 26 | }, 27 | }); 28 | } 29 | 30 | // 遍历响应式处理 31 | function observe(obj) { 32 | if (typeof obj !== "object" || obj == null) { 33 | return obj; 34 | } 35 | 36 | new Observer(obj); 37 | } 38 | 39 | // 能够将传入对象中的所有key代理到指定对象上 40 | function proxy(vm) { 41 | Object.keys(vm.$data).forEach((key) => { 42 | Object.defineProperty(vm, key, { 43 | get() { 44 | return vm.$data[key]; 45 | }, 46 | set(v) { 47 | vm.$data[key] = v; 48 | }, 49 | }); 50 | }); 51 | } 52 | 53 | class Observer { 54 | constructor(obj) { 55 | // 判断传入obj类型,做相应处理 56 | if (Array.isArray(obj)) { 57 | // todo 58 | } else { 59 | this.walk(obj); 60 | } 61 | } 62 | 63 | walk(obj) { 64 | Object.keys(obj).forEach((key) => defineReactive(obj, key, obj[key])); 65 | } 66 | } 67 | 68 | class KVue { 69 | constructor(options) { 70 | // 0.保存选项 71 | this.$options = options; 72 | this.$data = options.data; 73 | 74 | // 1.对data做响应式处理 75 | observe(options.data); 76 | 77 | // 2.代理 78 | proxy(this); 79 | 80 | // 3.编译 81 | new Compile(options.el, this); 82 | } 83 | } 84 | 85 | class Compile { 86 | constructor(el, vm) { 87 | this.$vm = vm; 88 | this.$el = document.querySelector(el); 89 | 90 | if (this.$el) { 91 | this.compile(this.$el); 92 | } 93 | } 94 | 95 | // 遍历node,判断节点类型,做不同处理 96 | compile(node) { 97 | const childNodes = node.childNodes; 98 | 99 | Array.from(childNodes).forEach((n) => { 100 | // 判断类型 101 | if (this.isElement(n)) { 102 | // console.log('编译元素', n.nodeName); 103 | this.compileElement(n); 104 | // 递归 105 | if (n.childNodes.length > 0) { 106 | this.compile(n); 107 | } 108 | } else if (this.isInter(n)) { 109 | // 动态插值表达式 110 | // console.log('编译文本', n.textContent); 111 | this.compileText(n); 112 | } 113 | }); 114 | } 115 | 116 | isElement(n) { 117 | return n.nodeType === 1; 118 | } 119 | 120 | // 形如{{ooxx}} 121 | isInter(n) { 122 | return n.nodeType === 3 && /\{\{(.*)\}\}/.test(n.textContent); 123 | } 124 | 125 | // 编译插值文本 {{ooxx}} 126 | compileText(n) { 127 | // 获取表达式 128 | // n.textContent = this.$vm[RegExp.$1]; 129 | this.update(n, RegExp.$1, "text"); 130 | } 131 | 132 | // 编译元素:遍历它的所有特性,看是否k-开头指令,或者@事件 133 | compileElement(n) { 134 | const attrs = n.attributes; 135 | Array.from(attrs).forEach((attr) => { 136 | // k-text="xxx" 137 | // name = k-text,value = xxx 138 | const attrName = attr.name; 139 | const exp = attr.value; 140 | // 指令 141 | if (this.isDir(attrName)) { 142 | // 执行特定指令处理函数 143 | const dir = attrName.substring(2); 144 | this[dir] && this[dir](n, exp); 145 | } 146 | }); 147 | } 148 | 149 | update(node, exp, dir) { 150 | // 1.init 151 | const fn = this[dir + 'Updater'] 152 | fn && fn(node, this.$vm[exp]) 153 | 154 | // 2.update 155 | new Watcher(this.$vm, exp, val => { 156 | fn && fn(node, val) 157 | }) 158 | } 159 | 160 | // k-text 161 | text(node, exp) { 162 | this.update(node, exp, "text"); 163 | } 164 | 165 | textUpdater(node, val) { 166 | node.textContent = val; 167 | } 168 | 169 | // k-html 170 | html(node, exp) { 171 | this.update(node, exp, "html"); 172 | } 173 | 174 | htmlUpdater(node, val) { 175 | node.innerHTML = val; 176 | } 177 | 178 | isDir(attrName) { 179 | return attrName.startsWith("k-"); 180 | } 181 | } 182 | 183 | // 负责dom更新 184 | class Watcher { 185 | constructor(vm, key, updater) { 186 | this.vm = vm; 187 | this.key = key; 188 | this.updater = updater; 189 | 190 | // 触发一下get 191 | Dep.target = this 192 | this.vm[this.key] 193 | Dep.target = null 194 | } 195 | 196 | // 将来会被Dep调用 197 | update() { 198 | this.updater.call(this.vm, this.vm[this.key]); 199 | } 200 | } 201 | 202 | // 保存watcher实例的依赖类 203 | class Dep { 204 | constructor() { 205 | this.deps = [] 206 | } 207 | // 此处dep就是Watcher的实例 208 | addDep(dep) { 209 | // 创建依赖关系时调用 210 | this.deps.push(dep) 211 | } 212 | notify() { 213 | this.deps.forEach(dep => dep.update()) 214 | } 215 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "study-vue", 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 | }, 10 | "dependencies": { 11 | "core-js": "^3.4.3", 12 | "element-ui": "^2.4.5", 13 | "vue": "^2.6.10", 14 | "vue-router": "^3.2.0", 15 | "vuex": "^3.4.0" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "^4.1.0", 19 | "@vue/cli-plugin-eslint": "^4.1.0", 20 | "@vue/cli-plugin-router": "^4.5.13", 21 | "@vue/cli-plugin-vuex": "^4.5.13", 22 | "@vue/cli-service": "^4.1.0", 23 | "babel-eslint": "^10.0.3", 24 | "eslint": "^5.16.0", 25 | "eslint-plugin-vue": "^5.0.0", 26 | "vue-cli-plugin-element": "^1.0.1", 27 | "vue-template-compiler": "^2.6.10" 28 | }, 29 | "eslintConfig": { 30 | "root": true, 31 | "env": { 32 | "node": true 33 | }, 34 | "extends": [ 35 | "plugin:vue/essential", 36 | "eslint:recommended" 37 | ], 38 | "rules": { 39 | "vue/no-unused-components": "off", 40 | "no-console": "off" 41 | }, 42 | "parserOptions": { 43 | "parser": "babel-eslint" 44 | } 45 | }, 46 | "browserslist": [ 47 | "> 1%", 48 | "last 2 versions" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/vue-study/90193b7dcda19cfd4fbaafecb240a2dca612e44f/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | study-vue 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 37 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/57code/vue-study/90193b7dcda19cfd4fbaafecb240a2dca612e44f/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 34 | 35 | 36 | 52 | -------------------------------------------------------------------------------- /src/components/communication/Child1.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/communication/Child2.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/communication/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 33 | 34 | -------------------------------------------------------------------------------- /src/components/form/ElementTest.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/form/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/recursion/Tree.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/recursion/TreeNode.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/recursion/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/components/slots/Layout.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 37 | 38 | -------------------------------------------------------------------------------- /src/components/slots/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | 26 | -------------------------------------------------------------------------------- /src/krouter/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from './kvue-router' 3 | import Home from '../views/Home.vue' 4 | 5 | // 引入插件 6 | // use方法将来会调用install方法 7 | Vue.use(VueRouter) 8 | 9 | // 路由映射表 10 | const routes = [ 11 | { 12 | path: '/', 13 | name: 'Home', 14 | component: Home, 15 | }, 16 | { 17 | path: '/about', 18 | name: 'About', 19 | // route level code-splitting 20 | // this generates a separate chunk (about.[hash].js) for this route 21 | // which is lazy-loaded when the route is visited. 22 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 23 | } 24 | ] 25 | 26 | const router = new VueRouter({ 27 | routes 28 | }) 29 | 30 | export default router 31 | -------------------------------------------------------------------------------- /src/krouter/kvue-router.js: -------------------------------------------------------------------------------- 1 | let Vue; 2 | 3 | // vue插件编写 4 | // 实现一个install方法 5 | class VueRouter { 6 | constructor(options) { 7 | console.log(Vue); 8 | this.$options = options; 9 | 10 | // 保存当前hash到current 11 | // current应该是响应式的 12 | // 给指定对象定义响应式属性 13 | Vue.util.defineReactive( 14 | this, 15 | "current", 16 | window.location.hash.slice(1) || "/" 17 | ); 18 | // this.current = "/"; 19 | 20 | // 监控hashchange 21 | window.addEventListener("hashchange", () => { 22 | // #/about => /about 23 | this.current = window.location.hash.slice(1); 24 | }); 25 | } 26 | } 27 | 28 | // 形参1是Vue构造函数: 目的是便于扩展 29 | VueRouter.install = function(_Vue) { 30 | Vue = _Vue; 31 | 32 | // 1. 将$router注册一下 33 | // 下面代码延迟未来某个时刻:根实例创建时 34 | Vue.mixin({ 35 | beforeCreate() { 36 | // 只需要根实例时执行一次 37 | if (this.$options.router) { 38 | // 希望将来任何组件都可以通过$router 39 | // 访问路由器实例 40 | Vue.prototype.$router = this.$options.router; 41 | } 42 | }, 43 | }); 44 | 45 | // 2. 注册两个全局组件:router-Link, router-view 46 | Vue.component("router-link", { 47 | // template: 'router-link' 48 | props: { 49 | to: { 50 | type: String, 51 | required: true, 52 | }, 53 | }, 54 | render(h) { 55 | // h就是createElement() 56 | // 作用:返回一个虚拟dom 57 | // abc 58 | // return {this.$slots.default}; 59 | // 获取插槽内容:this.$slots.default 60 | return h( 61 | "a", 62 | { 63 | attrs: { 64 | href: "#" + this.to, 65 | }, 66 | }, 67 | this.$slots.default 68 | ); 69 | }, 70 | }); 71 | 72 | Vue.component("router-view", { 73 | // vue.runtime.js 74 | // vue.js compiler -> template -> render() 75 | // template: '
router-view
' 76 | render(h) { 77 | // 可以传入一个组件直接渲染 78 | // 思路:如果可以根据url的hash部分动态匹配这个要渲染的组件 79 | // window.location.hash 80 | // console.log(this.$router.$options.routes); 81 | // console.log(this.$router.current); 82 | let component = null; 83 | const route = this.$router.$options.routes.find( 84 | (route) => route.path === this.$router.current 85 | ); 86 | if (route) { 87 | component = route.component 88 | } 89 | return h(component); 90 | }, 91 | }); 92 | }; 93 | 94 | export default VueRouter; 95 | -------------------------------------------------------------------------------- /src/kstore/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from './kvuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | // state应该是响应式对象 8 | state: { 9 | counter: 0 10 | }, 11 | mutations: { 12 | // state从何而来 13 | add(state) { 14 | state.counter++ 15 | } 16 | }, 17 | actions: { 18 | // 上下文从何而来,长什么样 19 | add({commit}) { 20 | setTimeout(() => { 21 | commit('add') 22 | }, 1000); 23 | } 24 | }, 25 | getters: { 26 | doubleCounter: state => { 27 | return state.counter * 2; 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /src/kstore/kvuex.js: -------------------------------------------------------------------------------- 1 | // 实现插件: 2 | // 1. 声明一个Store类:维护响应式state,暴露commit/dispatch 3 | // 2. install: 注册$store 4 | 5 | class Store { 6 | constructor(options) { 7 | // 保存选项 8 | this.$options = options 9 | 10 | this._mutations = options.mutations 11 | this._actions = options.actions 12 | 13 | console.log(Vue); 14 | // api: state 15 | // 用户传入state选项应该是响应式的 16 | this._vm = new Vue({ 17 | data() { 18 | return { 19 | // 不希望$$state被代理,所以加两个$ 20 | $$state: options.state 21 | } 22 | }, 23 | }) 24 | 25 | this.commit = this.commit.bind(this) 26 | this.dispatch = this.dispatch.bind(this) 27 | 28 | // getter接口如何暴露 29 | } 30 | 31 | // 存取器 32 | get state() { 33 | // console.log(this._vm); 34 | return this._vm._data.$$state 35 | } 36 | 37 | set state(v) { 38 | console.error('请使用reaplaceState()去修改状态'); 39 | } 40 | 41 | // commit('add') 42 | commit(type, payload) { 43 | // 匹配type对应的mutation 44 | const entry = this._mutations[type] 45 | if (!entry) { 46 | console.error('error'); 47 | return 48 | } 49 | entry(this.state, payload) 50 | } 51 | 52 | dispatch(type, payload) { 53 | // 匹配type对应的mutation 54 | const entry = this._actions[type] 55 | if (!entry) { 56 | console.error('error'); 57 | return 58 | } 59 | // 此处上下文是什么? 60 | // {commit, dispatch, state} 61 | return entry(this, payload) 62 | } 63 | } 64 | 65 | let Vue 66 | 67 | function install(_Vue) { 68 | Vue = _Vue 69 | 70 | // 注册$store 71 | Vue.mixin({ 72 | beforeCreate() { 73 | if (this.$options.store) { 74 | Vue.prototype.$store = this.$options.store 75 | } 76 | } 77 | }) 78 | } 79 | 80 | // 导出对象是Vuex 81 | export default { Store, install } -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import './plugins/element.js' 4 | 5 | // import router from './router' 6 | import router from './krouter' 7 | 8 | // import store from './store' 9 | import store from './kstore' 10 | 11 | Vue.config.productionTip = false 12 | 13 | // 事件总线 14 | Vue.prototype.$bus = new Vue() 15 | 16 | new Vue({ 17 | router, 18 | store, 19 | render: h => h(App) 20 | }).$mount('#app') 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | 5 | // 引入插件 6 | Vue.use(VueRouter) 7 | 8 | const routes = [ 9 | { 10 | path: '/', 11 | name: 'Home', 12 | component: Home 13 | }, 14 | { 15 | path: '/about', 16 | name: 'About', 17 | // route level code-splitting 18 | // this generates a separate chunk (about.[hash].js) for this route 19 | // which is lazy-loaded when the route is visited. 20 | component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') 21 | } 22 | ] 23 | 24 | const router = new VueRouter({ 25 | routes 26 | }) 27 | 28 | export default router 29 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | // state应该是响应式对象 8 | state: { 9 | counter: 0 10 | }, 11 | mutations: { 12 | // state从何而来 13 | add(state) { 14 | state.counter++ 15 | } 16 | }, 17 | actions: { 18 | // 上下文从何而来,长什么样 19 | add({commit}) { 20 | setTimeout(() => { 21 | commit('add') 22 | }, 1000); 23 | } 24 | }, 25 | 26 | }) 27 | -------------------------------------------------------------------------------- /src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | --------------------------------------------------------------------------------