├── .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 |
2 |
3 |
4 | Home |
5 | About
6 |
7 |
{{ $store.state.counter }}
8 |
async{{ $store.state.counter }}
9 |
double: {{$store.getters.doubleCounter}}
10 |
11 |
12 |
13 |
14 |
15 |
37 |
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/57code/vue-study/90193b7dcda19cfd4fbaafecb240a2dca612e44f/src/assets/logo.png
--------------------------------------------------------------------------------
/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
34 |
35 |
36 |
52 |
--------------------------------------------------------------------------------
/src/components/communication/Child1.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
child1
4 |
{{msg}}
5 |
6 |
7 |
8 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/communication/Child2.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
child2
4 |
5 |
6 |
7 |
8 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/communication/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
组件通信
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
33 |
34 |
--------------------------------------------------------------------------------
/src/components/form/ElementTest.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 登录
11 |
12 |
13 |
14 |
15 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/form/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 |
17 |
--------------------------------------------------------------------------------
/src/components/recursion/Tree.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/recursion/TreeNode.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [{{open ? '-' : '+'}}]
6 |
7 |
8 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/components/recursion/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/components/slots/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
37 |
38 |
--------------------------------------------------------------------------------
/src/components/slots/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
插槽
4 |
5 |
6 |
7 | 开课吧全栈
8 |
9 | content...
10 |
11 | {{fc}}
12 |
13 |
14 |
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 |
2 |
3 |
This is an about page
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
9 |
20 |
--------------------------------------------------------------------------------