├── .gitignore ├── README.md ├── babel.config.js ├── example ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlot │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── currentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js └── helloworld │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── lib ├── guide-mini-vue.cjs.js └── guide-mini-vue.esm.js ├── package.json ├── rollup.config.js ├── src ├── index.ts ├── reactivity │ ├── baseHandlers.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── createApp.ts │ ├── h.ts │ ├── helpers │ │ └── renderSlots.ts │ ├── index.ts │ ├── renderer.ts │ └── vnode.ts └── shared │ ├── ShapeFlags.ts │ └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | distTest/ 5 | distProd/ 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | package-lock.json 10 | tests/**/coverage/ 11 | *.zip 12 | # Editor directories and files 13 | .idea 14 | .vscode 15 | *.suo 16 | *.ntvs* 17 | *.njsproj 18 | *.sln 19 | *.lock -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 通过学习 vue3 源码 复现一个 mini-vue 2 | 3 | 学习过程基于 Jest 测试 4 | 5 | 1.响应式 (reactivity: reactive effect) 6 | 7 | 8 | ### 依赖 9 | 1. typescript 10 | 2. jest 测试环境 11 | 3. rollup 打包环境 12 | 1. @rollup/plugin-typescript rollup处理ts 13 | 2. tslib @rollup/plugin-typescript依赖包 14 | 15 | ### run run build 16 | 打包mini-vue到 `lib` 文件夹, 支持 esm&cjs 引入 -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { targets: { node: "current" } }], 4 | "@babel/preset-typescript", 5 | ], 6 | }; 7 | -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | render() { 7 | const self = this; 8 | return h("div", {}, [ 9 | h("div", {}, "App"), 10 | h(Foo, { 11 | // on + Event 12 | onAdd(a, b) { 13 | // 3. 被触发了 14 | self.handleAdd(a, b); 15 | }, 16 | // add-foo 17 | onAddFoo() { 18 | console.log("add-foo"); 19 | }, 20 | }), 21 | ]); 22 | }, 23 | setup(props) { 24 | const handleAdd = (a, b) => { 25 | console.log("3. 父组件emit被触发了 , 参数为", a, b); 26 | }; 27 | return { 28 | handleAdd, 29 | }; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | name: "Foo", 5 | // 1. setup内要多传入一个ctx对象 , 里面包含emit 6 | // 2. 调用emit时候 , 父组件会调用组件内的onEmit方法(绑定在父组件的props同位置) 7 | setup(props, ctx) { 8 | const emit = ctx.emit; 9 | const emitAdd = () => { 10 | console.log("1. 子组件准备开始触发emit"); 11 | // 2. 触发 12 | emit("add", "a", "b"); 13 | emit("add-foo"); 14 | }; 15 | return { emitAdd }; 16 | }, 17 | render() { 18 | const btn = h( 19 | "button", 20 | { 21 | onClick: this.emitAdd, 22 | }, 23 | "emitAddContent" 24 | ); 25 | const foo = h("div", {}, "我是foo"); 26 | 27 | return h( 28 | "div", 29 | { onclick: this.emitAdd, style: "border : 1px solid red" }, 30 | [btn, foo] 31 | ); 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | // vue3 5 | const rootContainer = document.getElementById("app"); 6 | createApp(App).mount(rootContainer); 7 | -------------------------------------------------------------------------------- /example/componentSlot/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextNode } from "../../lib/guide-mini-vue.esm.js"; 2 | import Foo from "./Foo.js"; 3 | 4 | export default { 5 | name: "App", 6 | render() { 7 | const app = h("div", {}, this.msg); 8 | // 1. 组件createApp 创建组件时候 (第三个参数就是传递给子组件的 slot 参数) 9 | // const foo = h(Foo, {}, [h("p", {}, "app传递给foo的内容") , h("p", {}, "app传递给foo的内容2")]); 10 | // 4. 转对象 11 | const foo = h( 12 | Foo, 13 | {}, 14 | { 15 | // header: h("p", {}, "app传递给foo的内容,我是header"), 16 | // footer: h("p", {}, "app传递给foo的内容,我是footer"), 17 | 18 | // 5. 作用域插槽 19 | header: ({ data }) => [ 20 | h( 21 | "p", 22 | {}, 23 | "app传递给foo的内容,我是header , 接收到子组件给我的信息: " + data 24 | ), 25 | createTextNode("你好呀"), 26 | ], 27 | footer: ({ data }) => 28 | h( 29 | "p", 30 | {}, 31 | "app传递给foo的内容,我是footer , 接收到子组件给我的信息: " + data 32 | ), 33 | } 34 | ); 35 | return h("div", {}, [app, foo]); 36 | }, 37 | setup() { 38 | return { 39 | msg: "App-container", 40 | }; 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /example/componentSlot/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export default { 4 | name: "Foo", 5 | setup() { 6 | return { 7 | fooMsg: "子msg", 8 | }; 9 | }, 10 | render() { 11 | const fooContainer = h("div", {}, "fooContainer"); 12 | 13 | // 2. 拿个app传递的slot , $slots , 放到 Foo组件的children中 14 | console.log(this.$slots); 15 | // 3. this.$slots传递过来的是一个数组 那就要把vnode数组展开或者丢进一个新的h函数中进行处理 16 | 17 | // 4. 需要把$slots转成一个对象传递过来(需要把slot放到对应的位置) (具名插槽) 18 | 19 | // 5. 作用域插槽 20 | return h("div", { style: "border:1px solid red" }, [ 21 | renderSlots(this.$slots, "header", { 22 | data: this.fooMsg, 23 | }), 24 | fooContainer, 25 | renderSlots(this.$slots, "footer", { 26 | data: this.fooMsg, 27 | }), 28 | ]); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /example/componentSlot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/componentSlot/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | createApp(App).mount(document.getElementById("app")); 4 | -------------------------------------------------------------------------------- /example/currentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | import Foo from "./Foo.js"; 3 | 4 | export default { 5 | name: "app", 6 | setup(props, ctx) { 7 | const instance = getCurrentInstance(); 8 | console.log(instance); 9 | }, 10 | render() { 11 | return h("div", {}, [h("div", {}, "app"), h(Foo)]); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /example/currentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export default { 4 | name: "foo", 5 | setup(props, ctx) { 6 | const instance = getCurrentInstance(); 7 | console.log(instance); 8 | }, 9 | render() { 10 | return h("div", {}, "foo"); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /example/currentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/currentInstance/main.js: -------------------------------------------------------------------------------- 1 | import App from "./App.js"; 2 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 3 | 4 | createApp(App).mount(document.getElementById("app")); 5 | -------------------------------------------------------------------------------- /example/helloworld/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | window.self = null; 4 | export const App = { 5 | // 先不做 template 模板编译 6 | name: "App", 7 | render() { 8 | window.self = this; 9 | 10 | // ui (tag , props , children / text) 11 | // 通过bind绑定 this 指向当前组件实例的proxy 访问proxy的属性和方法,被代理到instance的setupState\props等上面 12 | return h( 13 | "div", 14 | { 15 | id: "root", 16 | class: ["red", "small"], 17 | onClick: () => { 18 | console.log("click"); 19 | }, 20 | onMouseenter() { 21 | console.log("mouseenter"); 22 | }, 23 | }, 24 | // 带this this.msg / this.$el / this.$data 25 | [h("div", {}, "hello" + this.msg), h(Foo, { count: this.msg })] 26 | // string 27 | // "hello wihtout this.msg" 28 | // array 29 | // [ 30 | // h("p", { class: "red" }, "i am child1 without this.msg"), 31 | // h("p", { class: "blue" }, "i am child2"), 32 | // ] 33 | ); 34 | }, 35 | 36 | setup(props) { 37 | return { 38 | msg: "mini - vue - lwt", 39 | }; 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /example/helloworld/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/guide-mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | render() { 5 | // 1.通过 父组件h(Foo, { count: this.msg }) 的方式调用, 初始化Foo的props,绑定到instance.props上 6 | // 2.通过setup传入setup内,让其内部可以使用 7 | // 3.通过this.count可以直接访问this.$props.count 8 | return h( 9 | "div", 10 | {}, 11 | `foo: ${this.$props.count} ;访问调用方式2 ${this.count}` 12 | ); 13 | }, 14 | 15 | setup(props) { 16 | // 4.不可以被修改 shallowReadonly 17 | props.count = '1243' 18 | console.log(props); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /example/helloworld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/helloworld/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/guide-mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | // vue3 5 | const rootContainer = document.getElementById("app"); 6 | createApp(App).mount(rootContainer); 7 | -------------------------------------------------------------------------------- /lib/guide-mini-vue.cjs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | // Fragment -> 只渲染children 6 | var Fragment = Symbol("Fragment"); 7 | var Text = Symbol("Text"); 8 | function createVNode(type, props, children) { 9 | var vnode = { 10 | type: type, 11 | props: props, 12 | children: children, 13 | el: null, 14 | shapeFlag: getShapeFlag(type), 15 | }; 16 | // 判断是children是数组还是text 17 | if (Array.isArray(children)) { 18 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 19 | } 20 | else if (typeof children === "string") { 21 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 22 | } 23 | // 判断children是不是slot (obj & 组件) 24 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 25 | if (typeof children === "object") { 26 | vnode.shapeFlag |= 16 /* SLOTS_CHILDREN */; 27 | } 28 | } 29 | return vnode; 30 | } 31 | function createTextNode(str) { 32 | return createVNode(Text, {}, str); 33 | } 34 | function getShapeFlag(type) { 35 | // 判断 vnode.type 是组件还是element元素 36 | return typeof type === "string" 37 | ? 1 /* ELEMENT */ 38 | : 2 /* STATEFUL_COMPONENT */; 39 | } 40 | 41 | var extend = Object.assign; 42 | function isObject(val) { 43 | return val !== null && typeof val === "object"; 44 | } 45 | var hasOwn = function (raw, key) { return raw.hasOwnProperty(key); }; 46 | // add-foo --> addFoo 47 | var camelize = function (str) { 48 | return str.replace(/-(\w)/g, function (match, group1) { 49 | //str--> 'add-foo' , match ---> '-f' , group1 --(\w)--> 'f' 50 | return group1 ? group1.toUpperCase() : ""; 51 | }); 52 | }; 53 | // event('add') --> Add 54 | var capitalize = function (str) { 55 | return str.charAt(0).toUpperCase() + str.slice(1); 56 | }; 57 | // Add --> onAdd 58 | var toHandlerOn = function (event) { 59 | return "on" + capitalize(event); 60 | }; 61 | 62 | // target --> key --->dep 63 | // 把依赖放到 对应的对象的属性映射表里面去 64 | var targetMap = new Map(); 65 | // 通过target - key - 拿到要触发的依赖 66 | function trigger(target, key) { 67 | var depsMap = targetMap.get(target); 68 | if (!depsMap) 69 | return; 70 | var dep = depsMap.get(key); 71 | if (!dep) 72 | return; 73 | // 触发dep的所有依赖 74 | triggerEffect(dep); 75 | } 76 | function triggerEffect(dep) { 77 | for (var _i = 0, dep_1 = dep; _i < dep_1.length; _i++) { 78 | var effect_1 = dep_1[_i]; 79 | if (effect_1.scheduler) { 80 | effect_1.scheduler(); 81 | } 82 | else 83 | effect_1.run(); 84 | } 85 | } 86 | 87 | var get = createGetter(); 88 | var set = createSetter(); 89 | var readonlyGet = createGetter(true); 90 | var shallowReadonlyGet = createGetter(true, true); 91 | function createGetter(isReadonly, isShallowReadonly) { 92 | if (isReadonly === void 0) { isReadonly = false; } 93 | if (isShallowReadonly === void 0) { isShallowReadonly = false; } 94 | return function get(target, key) { 95 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 96 | return !isReadonly; 97 | } 98 | else if (key === "__v_isReadonly" /* IS_READONLY */) { 99 | return isReadonly; 100 | } 101 | var res = Reflect.get(target, key); 102 | if (isShallowReadonly) { 103 | return res; 104 | } 105 | if (isObject(res)) { 106 | return isReadonly ? readonly(res) : reactive(res); 107 | } 108 | return res; 109 | }; 110 | } 111 | function createSetter() { 112 | return function set(target, key, value) { 113 | var res = Reflect.set(target, key, value); 114 | // todo 触发依赖 115 | trigger(target, key); 116 | return res; 117 | }; 118 | } 119 | var multableHandlers = { 120 | get: get, 121 | set: set, 122 | }; 123 | var readonlyHandlers = { 124 | get: readonlyGet, 125 | set: function (target, key, value) { 126 | console.warn(key + " \u4FEE\u6539set\u5931\u8D25, \u56E0\u4E3A target \u662F\u53EA\u8BFB\u7684readonly\u7C7B\u578B"); 127 | return true; 128 | }, 129 | }; 130 | var shallowReadonlyHandlers = extend({}, readonlyHandlers, { 131 | get: shallowReadonlyGet, 132 | }); 133 | 134 | function reactive(raw) { 135 | return createProxyObject(raw, multableHandlers); 136 | } 137 | function readonly(raw) { 138 | return createProxyObject(raw, readonlyHandlers); 139 | } 140 | function shallowReadonly(raw) { 141 | return createProxyObject(raw, shallowReadonlyHandlers); 142 | } 143 | // 生成 proxy 144 | function createProxyObject(raw, baseHandlers) { 145 | // 传入的raw必须是一个对象o 146 | if (!isObject(raw)) { 147 | console.warn("target " + raw + " \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61\u7684"); 148 | return raw; 149 | } 150 | return new Proxy(raw, baseHandlers); 151 | } 152 | 153 | /****************************************************************************** 154 | Copyright (c) Microsoft Corporation. 155 | 156 | Permission to use, copy, modify, and/or distribute this software for any 157 | purpose with or without fee is hereby granted. 158 | 159 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 160 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 161 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 162 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 163 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 164 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 165 | PERFORMANCE OF THIS SOFTWARE. 166 | ***************************************************************************** */ 167 | 168 | function __spreadArray(to, from, pack) { 169 | if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { 170 | if (ar || !(i in from)) { 171 | if (!ar) ar = Array.prototype.slice.call(from, 0, i); 172 | ar[i] = from[i]; 173 | } 174 | } 175 | return to.concat(ar || Array.prototype.slice.call(from)); 176 | } 177 | 178 | function emit(instance, event) { 179 | var args = []; 180 | for (var _i = 2; _i < arguments.length; _i++) { 181 | args[_i - 2] = arguments[_i]; 182 | } 183 | console.log("2. 调用到子组件的emit , 去寻找父组件传递的props里面有没有对应的函数 , 有的话就执行"); 184 | // on Event 放在props里面, 所以要去拿的话要拿到instance.props 185 | // 那就要拿到insatnce 实例, 用户调用emit时候不可能传递给组件实例的,所以要在赋值emit给组件的时候 186 | // 就把实例bind到emit函数 上面 187 | var props = instance.props; 188 | // 去掉驼峰 加上on 189 | var handlerName = toHandlerOn(camelize(event)); 190 | var handlder = props[handlerName]; 191 | handlder && handlder.apply(void 0, __spreadArray([event], args, false)); 192 | } 193 | 194 | function initProps(instance, rawProps) { 195 | // 把vnode上面的props传递给instance上面的props 196 | instance.props = rawProps || {}; 197 | } 198 | 199 | var publicPropertiesMap = { 200 | // 当用户调用 instance.proxy.$emit 时就会触发这个函数 201 | // i 就是 instance 的缩写 也就是组件实例对象 202 | $el: function (i) { return i.vnode.el; }, 203 | $emit: function (i) { return i.emit; }, 204 | $slots: function (i) { return i.slots; }, 205 | $props: function (i) { return i.props; }, 206 | }; 207 | var PubilcInstanceHandlers = { 208 | get: function (_a, key) { 209 | var instance = _a._; 210 | var setupState = instance.setupState, props = instance.props; 211 | // if (key in setupState) { 212 | // return setupState[key]; 213 | // } 214 | // 判断对象内有没有这个key 215 | if (hasOwn(setupState, key)) { 216 | return setupState[key]; 217 | } 218 | else if (hasOwn(props, key)) { 219 | return props[key]; 220 | } 221 | // $el 等 222 | if (key in publicPropertiesMap) { 223 | return publicPropertiesMap[key](instance); 224 | } 225 | }, 226 | }; 227 | 228 | function initSlots(instance, children) { 229 | // 组件&slot children 230 | if (instance.vnode.shapeFlag & 16 /* SLOTS_CHILDREN */) { 231 | normalizeSlots(instance, children); 232 | } 233 | } 234 | function normalizeSlots(instance, children) { 235 | var _loop_1 = function (key) { 236 | if (Object.prototype.hasOwnProperty.call(children, key)) { 237 | var value_1 = children[key]; 238 | instance.slots[key] = function (prop) { return normalizeChildren(value_1(prop)); }; 239 | } 240 | }; 241 | for (var key in children) { 242 | _loop_1(key); 243 | } 244 | } 245 | // 把 h(xx) -> [h(xx)] 转数组 246 | function normalizeChildren(value) { 247 | return Array.isArray(value) ? value : [value]; 248 | } 249 | 250 | // 组件instance -> 通过 vnode创建, { vnode, type(vnode.type) , setupState , proxy , 组件的render函数 } 251 | function createComponentInstance(vnode) { 252 | // 初始化组件, 返回一个对象 , 内部包含 253 | var component = { 254 | vnode: vnode, 255 | type: vnode.type, 256 | // setup 返回值 257 | setupState: {}, 258 | // props: 259 | props: {}, 260 | // emit 261 | emit: function () { }, 262 | // slots 263 | slots: {}, 264 | }; 265 | component.emit = emit.bind(null, component); 266 | return component; 267 | } 268 | function setupComponent(instance) { 269 | // initProps 把h函数(createVNode)传递过来的props 赋值给组件实例的props 270 | initProps(instance, instance.vnode.props); 271 | // initSlots 272 | initSlots(instance, instance.vnode.children); 273 | // initState (添加方法, 让组件实例处理调用setup之后的返回值) , 有状态的组件 274 | setupStatefulComponent(instance); 275 | } 276 | function setupStatefulComponent(instance) { 277 | // 调用setup 拿到setup之后的返回值 , 278 | // instance 里面包含有type ,也就是当前的组件(App对象) 279 | var Component = instance.type; 280 | // 创建代理组件对象 , 将组件实例的数据挂载到这个代理对象上 , 调用render时候,会调用这个代理对象的方法 281 | instance.proxy = new Proxy({ _: instance }, PubilcInstanceHandlers); 282 | var setup = Component.setup; 283 | if (setup) { 284 | // 储存instance 285 | setCurrentInstance(instance); 286 | // setup可以返回一个 object 或者一个function , 再在setup里面被接收 287 | var setupResult = setup(shallowReadonly(instance.props), { 288 | // 传入emit给子组件, 让子组件可以调用父组件的emit 289 | emit: instance.emit, 290 | }); 291 | // 清空instance 292 | setCurrentInstance(null); 293 | handleSetupResult(instance, setupResult); 294 | } 295 | } 296 | function handleSetupResult(instance, setupResult) { 297 | // setupResult 可能是一个object 或者一个function 298 | // 如果是一个function, 则调用这个function skipnow 299 | // setup(){cosnt count =ref(0); return ()=>{'div' , count.value}} 300 | // 如果是一个object, 则直接赋值给instance.state 301 | // setup(){return {msg:'mini-vue'}} 302 | if (typeof setupResult === "object") { 303 | instance.setupState = setupResult; 304 | } 305 | // 组件数据初始化结束,要判断是否有render方法 306 | finishComponentSetup(instance); 307 | } 308 | function finishComponentSetup(instance) { 309 | var Component = instance.type; 310 | if (Component.render) { 311 | instance.render = Component.render; 312 | } 313 | } 314 | // setup内部获取instance 315 | var currentInstance = null; 316 | function getCurrentInstance() { 317 | return currentInstance; 318 | } 319 | function setCurrentInstance(instance) { 320 | currentInstance = instance; 321 | } 322 | 323 | function render(vnode, container) { 324 | // patch 325 | patch(vnode, container); 326 | } 327 | function patch(vnode, container) { 328 | switch (vnode.type) { 329 | // 只处理 vnode.children 330 | case Fragment: 331 | processFragment(vnode, container); 332 | break; 333 | // 只处理 textNode 334 | case Text: 335 | processTextNode(vnode, container); 336 | break; 337 | default: 338 | // 处理vnode 339 | // 1. 判断vnode是否是一个真实的dom节点 340 | // console.log(vnode.type); 341 | if (vnode.shapeFlag & 1 /* ELEMENT */) { 342 | // 是string类型也就是,是普通的element类型的vnode,直接挂载到dom 343 | processElement(vnode, container); 344 | } 345 | else if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 346 | // 2. 判断vnode是否是一个组件, 如果是组件, 则调用processComponent方法 347 | processComponent(vnode, container); 348 | } 349 | break; 350 | } 351 | } 352 | // 处理Fragment 353 | function processFragment(vnode, container) { 354 | mountChildren(vnode.children, container); 355 | } 356 | // 处理存文本节点 357 | function processTextNode(vnode, container) { 358 | var el = (vnode.el = document.createTextNode(vnode.children)); 359 | console.log(vnode); 360 | container.append(el); 361 | } 362 | function processElement(vnode, container) { 363 | // 初始化dom 364 | mountElement(vnode, container); 365 | } 366 | function mountElement(vnode, container) { 367 | var el = (vnode.el = document.createElement(vnode.type)); 368 | vnode.type; var props = vnode.props, children = vnode.children; 369 | // 处理属性props 370 | for (var key in props) { 371 | if (Object.prototype.hasOwnProperty.call(props, key)) { 372 | var element = props[key]; 373 | if (/^on[A-Z]/.test(key)) { 374 | var event_1 = key.slice(2).toLowerCase(); 375 | el.addEventListener(event_1, element); 376 | } 377 | else { 378 | el.setAttribute(key, element); 379 | } 380 | } 381 | } 382 | // 处理children / string 383 | if (vnode.shapeFlag & 4 /* TEXT_CHILDREN */) { 384 | el.innerText = children; 385 | } 386 | else if (vnode.shapeFlag & 8 /* ARRAY_CHILDREN */) { 387 | // 同样每个子节点也是一个vnode , 去patch 388 | mountChildren(children, el); 389 | } 390 | container.append(el); 391 | } 392 | function mountChildren(vnode, container) { 393 | vnode.forEach(function (child) { 394 | patch(child, container); 395 | }); 396 | } 397 | function processComponent(vnode, container) { 398 | // 分为初始化和更新 399 | // 1. 初始化组件 , 因为vnode的type为组件 , 所以需要对其进行一些数据的初始化 , 让其变成组件实例 400 | mountComponent(vnode, container); 401 | } 402 | function mountComponent(initialVNode, container) { 403 | // 创建组件实例 404 | var instance = createComponentInstance(initialVNode); 405 | // 往组件实例上挂载一些数据 406 | setupComponent(instance); 407 | // 看xmind图可以发现 组件初始化全部结束了 408 | // 应该去调用 组件的render函数了 409 | setupRenderEffect(instance, initialVNode, container); 410 | } 411 | function setupRenderEffect(instance, initialVNode, container) { 412 | //render() { return h("div", "Hello World , hi " + this.msg); } 413 | // 拿到下一个vnode 414 | // 拿到代理对象, this指向它, render里面的this.msg -> 指向proxy上面的msg -> 会去拿setupState.msg 415 | var proxy = instance.proxy; 416 | var subTree = instance.render.call(proxy); 417 | // vnode -> 再次patch 418 | // (如果是不是组件类型 是element类型那就直接挂载到dom了) 419 | patch(subTree, container); 420 | // 把element的el传递给组件实例 421 | initialVNode.el = subTree.el; 422 | } 423 | 424 | function createApp(rootComponent) { 425 | // 返回一个app对象,里面带有mount方法(初始化挂载) 426 | return { 427 | mount: function (rootContainer) { 428 | // 根组件(render) -> vnode -> dom ->挂载到rootContainer 429 | // 1. 根组件 -> vnode(type type可以是vue component也可以是div等标签, props, children) 430 | var vnode = createVNode(rootComponent); 431 | // 2. 内部调用patch方法 ,进行递归的处理 432 | render(vnode, rootContainer); 433 | } 434 | }; 435 | } 436 | 437 | // h函数的作用和createVNode函数是一样的,只是h函数的参数不同 438 | function h(type, props, children) { 439 | return createVNode(type, props, children); 440 | } 441 | 442 | function renderSlots(slots, key, data) { 443 | // slots -> Array 444 | // return createVNode('div' , {} , slots) 445 | // slots -> obj 446 | var slot = slots[key]; 447 | if (slot) { 448 | if (typeof slot === "function") { 449 | return createVNode(Fragment, {}, slot(data)); 450 | } 451 | } 452 | } 453 | 454 | exports.createApp = createApp; 455 | exports.createTextNode = createTextNode; 456 | exports.getCurrentInstance = getCurrentInstance; 457 | exports.h = h; 458 | exports.renderSlots = renderSlots; 459 | -------------------------------------------------------------------------------- /lib/guide-mini-vue.esm.js: -------------------------------------------------------------------------------- 1 | // Fragment -> 只渲染children 2 | var Fragment = Symbol("Fragment"); 3 | var Text = Symbol("Text"); 4 | function createVNode(type, props, children) { 5 | var vnode = { 6 | type: type, 7 | props: props, 8 | children: children, 9 | el: null, 10 | shapeFlag: getShapeFlag(type), 11 | }; 12 | // 判断是children是数组还是text 13 | if (Array.isArray(children)) { 14 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */; 15 | } 16 | else if (typeof children === "string") { 17 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */; 18 | } 19 | // 判断children是不是slot (obj & 组件) 20 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 21 | if (typeof children === "object") { 22 | vnode.shapeFlag |= 16 /* SLOTS_CHILDREN */; 23 | } 24 | } 25 | return vnode; 26 | } 27 | function createTextNode(str) { 28 | return createVNode(Text, {}, str); 29 | } 30 | function getShapeFlag(type) { 31 | // 判断 vnode.type 是组件还是element元素 32 | return typeof type === "string" 33 | ? 1 /* ELEMENT */ 34 | : 2 /* STATEFUL_COMPONENT */; 35 | } 36 | 37 | var extend = Object.assign; 38 | function isObject(val) { 39 | return val !== null && typeof val === "object"; 40 | } 41 | var hasOwn = function (raw, key) { return raw.hasOwnProperty(key); }; 42 | // add-foo --> addFoo 43 | var camelize = function (str) { 44 | return str.replace(/-(\w)/g, function (match, group1) { 45 | //str--> 'add-foo' , match ---> '-f' , group1 --(\w)--> 'f' 46 | return group1 ? group1.toUpperCase() : ""; 47 | }); 48 | }; 49 | // event('add') --> Add 50 | var capitalize = function (str) { 51 | return str.charAt(0).toUpperCase() + str.slice(1); 52 | }; 53 | // Add --> onAdd 54 | var toHandlerOn = function (event) { 55 | return "on" + capitalize(event); 56 | }; 57 | 58 | // target --> key --->dep 59 | // 把依赖放到 对应的对象的属性映射表里面去 60 | var targetMap = new Map(); 61 | // 通过target - key - 拿到要触发的依赖 62 | function trigger(target, key) { 63 | var depsMap = targetMap.get(target); 64 | if (!depsMap) 65 | return; 66 | var dep = depsMap.get(key); 67 | if (!dep) 68 | return; 69 | // 触发dep的所有依赖 70 | triggerEffect(dep); 71 | } 72 | function triggerEffect(dep) { 73 | for (var _i = 0, dep_1 = dep; _i < dep_1.length; _i++) { 74 | var effect_1 = dep_1[_i]; 75 | if (effect_1.scheduler) { 76 | effect_1.scheduler(); 77 | } 78 | else 79 | effect_1.run(); 80 | } 81 | } 82 | 83 | var get = createGetter(); 84 | var set = createSetter(); 85 | var readonlyGet = createGetter(true); 86 | var shallowReadonlyGet = createGetter(true, true); 87 | function createGetter(isReadonly, isShallowReadonly) { 88 | if (isReadonly === void 0) { isReadonly = false; } 89 | if (isShallowReadonly === void 0) { isShallowReadonly = false; } 90 | return function get(target, key) { 91 | if (key === "__v_isReactive" /* IS_REACTIVE */) { 92 | return !isReadonly; 93 | } 94 | else if (key === "__v_isReadonly" /* IS_READONLY */) { 95 | return isReadonly; 96 | } 97 | var res = Reflect.get(target, key); 98 | if (isShallowReadonly) { 99 | return res; 100 | } 101 | if (isObject(res)) { 102 | return isReadonly ? readonly(res) : reactive(res); 103 | } 104 | return res; 105 | }; 106 | } 107 | function createSetter() { 108 | return function set(target, key, value) { 109 | var res = Reflect.set(target, key, value); 110 | // todo 触发依赖 111 | trigger(target, key); 112 | return res; 113 | }; 114 | } 115 | var multableHandlers = { 116 | get: get, 117 | set: set, 118 | }; 119 | var readonlyHandlers = { 120 | get: readonlyGet, 121 | set: function (target, key, value) { 122 | console.warn(key + " \u4FEE\u6539set\u5931\u8D25, \u56E0\u4E3A target \u662F\u53EA\u8BFB\u7684readonly\u7C7B\u578B"); 123 | return true; 124 | }, 125 | }; 126 | var shallowReadonlyHandlers = extend({}, readonlyHandlers, { 127 | get: shallowReadonlyGet, 128 | }); 129 | 130 | function reactive(raw) { 131 | return createProxyObject(raw, multableHandlers); 132 | } 133 | function readonly(raw) { 134 | return createProxyObject(raw, readonlyHandlers); 135 | } 136 | function shallowReadonly(raw) { 137 | return createProxyObject(raw, shallowReadonlyHandlers); 138 | } 139 | // 生成 proxy 140 | function createProxyObject(raw, baseHandlers) { 141 | // 传入的raw必须是一个对象o 142 | if (!isObject(raw)) { 143 | console.warn("target " + raw + " \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61\u7684"); 144 | return raw; 145 | } 146 | return new Proxy(raw, baseHandlers); 147 | } 148 | 149 | /****************************************************************************** 150 | Copyright (c) Microsoft Corporation. 151 | 152 | Permission to use, copy, modify, and/or distribute this software for any 153 | purpose with or without fee is hereby granted. 154 | 155 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 156 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 157 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 158 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 159 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 160 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 161 | PERFORMANCE OF THIS SOFTWARE. 162 | ***************************************************************************** */ 163 | 164 | function __spreadArray(to, from, pack) { 165 | if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { 166 | if (ar || !(i in from)) { 167 | if (!ar) ar = Array.prototype.slice.call(from, 0, i); 168 | ar[i] = from[i]; 169 | } 170 | } 171 | return to.concat(ar || Array.prototype.slice.call(from)); 172 | } 173 | 174 | function emit(instance, event) { 175 | var args = []; 176 | for (var _i = 2; _i < arguments.length; _i++) { 177 | args[_i - 2] = arguments[_i]; 178 | } 179 | console.log("2. 调用到子组件的emit , 去寻找父组件传递的props里面有没有对应的函数 , 有的话就执行"); 180 | // on Event 放在props里面, 所以要去拿的话要拿到instance.props 181 | // 那就要拿到insatnce 实例, 用户调用emit时候不可能传递给组件实例的,所以要在赋值emit给组件的时候 182 | // 就把实例bind到emit函数 上面 183 | var props = instance.props; 184 | // 去掉驼峰 加上on 185 | var handlerName = toHandlerOn(camelize(event)); 186 | var handlder = props[handlerName]; 187 | handlder && handlder.apply(void 0, __spreadArray([event], args, false)); 188 | } 189 | 190 | function initProps(instance, rawProps) { 191 | // 把vnode上面的props传递给instance上面的props 192 | instance.props = rawProps || {}; 193 | } 194 | 195 | var publicPropertiesMap = { 196 | // 当用户调用 instance.proxy.$emit 时就会触发这个函数 197 | // i 就是 instance 的缩写 也就是组件实例对象 198 | $el: function (i) { return i.vnode.el; }, 199 | $emit: function (i) { return i.emit; }, 200 | $slots: function (i) { return i.slots; }, 201 | $props: function (i) { return i.props; }, 202 | }; 203 | var PubilcInstanceHandlers = { 204 | get: function (_a, key) { 205 | var instance = _a._; 206 | var setupState = instance.setupState, props = instance.props; 207 | // if (key in setupState) { 208 | // return setupState[key]; 209 | // } 210 | // 判断对象内有没有这个key 211 | if (hasOwn(setupState, key)) { 212 | return setupState[key]; 213 | } 214 | else if (hasOwn(props, key)) { 215 | return props[key]; 216 | } 217 | // $el 等 218 | if (key in publicPropertiesMap) { 219 | return publicPropertiesMap[key](instance); 220 | } 221 | }, 222 | }; 223 | 224 | function initSlots(instance, children) { 225 | // 组件&slot children 226 | if (instance.vnode.shapeFlag & 16 /* SLOTS_CHILDREN */) { 227 | normalizeSlots(instance, children); 228 | } 229 | } 230 | function normalizeSlots(instance, children) { 231 | var _loop_1 = function (key) { 232 | if (Object.prototype.hasOwnProperty.call(children, key)) { 233 | var value_1 = children[key]; 234 | instance.slots[key] = function (prop) { return normalizeChildren(value_1(prop)); }; 235 | } 236 | }; 237 | for (var key in children) { 238 | _loop_1(key); 239 | } 240 | } 241 | // 把 h(xx) -> [h(xx)] 转数组 242 | function normalizeChildren(value) { 243 | return Array.isArray(value) ? value : [value]; 244 | } 245 | 246 | // 组件instance -> 通过 vnode创建, { vnode, type(vnode.type) , setupState , proxy , 组件的render函数 } 247 | function createComponentInstance(vnode) { 248 | // 初始化组件, 返回一个对象 , 内部包含 249 | var component = { 250 | vnode: vnode, 251 | type: vnode.type, 252 | // setup 返回值 253 | setupState: {}, 254 | // props: 255 | props: {}, 256 | // emit 257 | emit: function () { }, 258 | // slots 259 | slots: {}, 260 | }; 261 | component.emit = emit.bind(null, component); 262 | return component; 263 | } 264 | function setupComponent(instance) { 265 | // initProps 把h函数(createVNode)传递过来的props 赋值给组件实例的props 266 | initProps(instance, instance.vnode.props); 267 | // initSlots 268 | initSlots(instance, instance.vnode.children); 269 | // initState (添加方法, 让组件实例处理调用setup之后的返回值) , 有状态的组件 270 | setupStatefulComponent(instance); 271 | } 272 | function setupStatefulComponent(instance) { 273 | // 调用setup 拿到setup之后的返回值 , 274 | // instance 里面包含有type ,也就是当前的组件(App对象) 275 | var Component = instance.type; 276 | // 创建代理组件对象 , 将组件实例的数据挂载到这个代理对象上 , 调用render时候,会调用这个代理对象的方法 277 | instance.proxy = new Proxy({ _: instance }, PubilcInstanceHandlers); 278 | var setup = Component.setup; 279 | if (setup) { 280 | // 储存instance 281 | setCurrentInstance(instance); 282 | // setup可以返回一个 object 或者一个function , 再在setup里面被接收 283 | var setupResult = setup(shallowReadonly(instance.props), { 284 | // 传入emit给子组件, 让子组件可以调用父组件的emit 285 | emit: instance.emit, 286 | }); 287 | // 清空instance 288 | setCurrentInstance(null); 289 | handleSetupResult(instance, setupResult); 290 | } 291 | } 292 | function handleSetupResult(instance, setupResult) { 293 | // setupResult 可能是一个object 或者一个function 294 | // 如果是一个function, 则调用这个function skipnow 295 | // setup(){cosnt count =ref(0); return ()=>{'div' , count.value}} 296 | // 如果是一个object, 则直接赋值给instance.state 297 | // setup(){return {msg:'mini-vue'}} 298 | if (typeof setupResult === "object") { 299 | instance.setupState = setupResult; 300 | } 301 | // 组件数据初始化结束,要判断是否有render方法 302 | finishComponentSetup(instance); 303 | } 304 | function finishComponentSetup(instance) { 305 | var Component = instance.type; 306 | if (Component.render) { 307 | instance.render = Component.render; 308 | } 309 | } 310 | // setup内部获取instance 311 | var currentInstance = null; 312 | function getCurrentInstance() { 313 | return currentInstance; 314 | } 315 | function setCurrentInstance(instance) { 316 | currentInstance = instance; 317 | } 318 | 319 | function render(vnode, container) { 320 | // patch 321 | patch(vnode, container); 322 | } 323 | function patch(vnode, container) { 324 | switch (vnode.type) { 325 | // 只处理 vnode.children 326 | case Fragment: 327 | processFragment(vnode, container); 328 | break; 329 | // 只处理 textNode 330 | case Text: 331 | processTextNode(vnode, container); 332 | break; 333 | default: 334 | // 处理vnode 335 | // 1. 判断vnode是否是一个真实的dom节点 336 | // console.log(vnode.type); 337 | if (vnode.shapeFlag & 1 /* ELEMENT */) { 338 | // 是string类型也就是,是普通的element类型的vnode,直接挂载到dom 339 | processElement(vnode, container); 340 | } 341 | else if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) { 342 | // 2. 判断vnode是否是一个组件, 如果是组件, 则调用processComponent方法 343 | processComponent(vnode, container); 344 | } 345 | break; 346 | } 347 | } 348 | // 处理Fragment 349 | function processFragment(vnode, container) { 350 | mountChildren(vnode.children, container); 351 | } 352 | // 处理存文本节点 353 | function processTextNode(vnode, container) { 354 | var el = (vnode.el = document.createTextNode(vnode.children)); 355 | console.log(vnode); 356 | container.append(el); 357 | } 358 | function processElement(vnode, container) { 359 | // 初始化dom 360 | mountElement(vnode, container); 361 | } 362 | function mountElement(vnode, container) { 363 | var el = (vnode.el = document.createElement(vnode.type)); 364 | vnode.type; var props = vnode.props, children = vnode.children; 365 | // 处理属性props 366 | for (var key in props) { 367 | if (Object.prototype.hasOwnProperty.call(props, key)) { 368 | var element = props[key]; 369 | if (/^on[A-Z]/.test(key)) { 370 | var event_1 = key.slice(2).toLowerCase(); 371 | el.addEventListener(event_1, element); 372 | } 373 | else { 374 | el.setAttribute(key, element); 375 | } 376 | } 377 | } 378 | // 处理children / string 379 | if (vnode.shapeFlag & 4 /* TEXT_CHILDREN */) { 380 | el.innerText = children; 381 | } 382 | else if (vnode.shapeFlag & 8 /* ARRAY_CHILDREN */) { 383 | // 同样每个子节点也是一个vnode , 去patch 384 | mountChildren(children, el); 385 | } 386 | container.append(el); 387 | } 388 | function mountChildren(vnode, container) { 389 | vnode.forEach(function (child) { 390 | patch(child, container); 391 | }); 392 | } 393 | function processComponent(vnode, container) { 394 | // 分为初始化和更新 395 | // 1. 初始化组件 , 因为vnode的type为组件 , 所以需要对其进行一些数据的初始化 , 让其变成组件实例 396 | mountComponent(vnode, container); 397 | } 398 | function mountComponent(initialVNode, container) { 399 | // 创建组件实例 400 | var instance = createComponentInstance(initialVNode); 401 | // 往组件实例上挂载一些数据 402 | setupComponent(instance); 403 | // 看xmind图可以发现 组件初始化全部结束了 404 | // 应该去调用 组件的render函数了 405 | setupRenderEffect(instance, initialVNode, container); 406 | } 407 | function setupRenderEffect(instance, initialVNode, container) { 408 | //render() { return h("div", "Hello World , hi " + this.msg); } 409 | // 拿到下一个vnode 410 | // 拿到代理对象, this指向它, render里面的this.msg -> 指向proxy上面的msg -> 会去拿setupState.msg 411 | var proxy = instance.proxy; 412 | var subTree = instance.render.call(proxy); 413 | // vnode -> 再次patch 414 | // (如果是不是组件类型 是element类型那就直接挂载到dom了) 415 | patch(subTree, container); 416 | // 把element的el传递给组件实例 417 | initialVNode.el = subTree.el; 418 | } 419 | 420 | function createApp(rootComponent) { 421 | // 返回一个app对象,里面带有mount方法(初始化挂载) 422 | return { 423 | mount: function (rootContainer) { 424 | // 根组件(render) -> vnode -> dom ->挂载到rootContainer 425 | // 1. 根组件 -> vnode(type type可以是vue component也可以是div等标签, props, children) 426 | var vnode = createVNode(rootComponent); 427 | // 2. 内部调用patch方法 ,进行递归的处理 428 | render(vnode, rootContainer); 429 | } 430 | }; 431 | } 432 | 433 | // h函数的作用和createVNode函数是一样的,只是h函数的参数不同 434 | function h(type, props, children) { 435 | return createVNode(type, props, children); 436 | } 437 | 438 | function renderSlots(slots, key, data) { 439 | // slots -> Array 440 | // return createVNode('div' , {} , slots) 441 | // slots -> obj 442 | var slot = slots[key]; 443 | if (slot) { 444 | if (typeof slot === "function") { 445 | return createVNode(Fragment, {}, slot(data)); 446 | } 447 | } 448 | } 449 | 450 | export { createApp, createTextNode, getCurrentInstance, h, renderSlots }; 451 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2.mini-vue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./lib/guide-mini-vue.esm.js", 6 | "module": "./lib/guide-mini-vue.cjs.js", 7 | "scripts": { 8 | "test": "jest", 9 | "build": "rollup -c rollup.config.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@babel/core": "^7.17.8", 16 | "@babel/preset-env": "^7.16.11", 17 | "@babel/preset-typescript": "^7.16.7", 18 | "@rollup/plugin-typescript": "^8.3.2", 19 | "@types/jest": "^27.4.1", 20 | "babel-jest": "^27.5.1", 21 | "jest": "^27.5.1", 22 | "rollup": "^2.70.2", 23 | "tslib": "^2.4.0", 24 | "typescript": "^4.4.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from "./package.json"; 2 | 3 | import typescript from "@rollup/plugin-typescript"; 4 | export default { 5 | input: "./src/index.ts", 6 | output: [ 7 | // 1. cjs -> 打包成common js 8 | // 2. esm -> 打包成es module 9 | { 10 | format: "cjs", 11 | file: pkg.module, 12 | }, 13 | { 14 | format: "es", 15 | file: pkg.main, 16 | }, 17 | ], 18 | plugins: [typescript()], 19 | }; 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // mini-vue 出口文件 2 | 3 | export * from "./runtime-core/index"; 4 | -------------------------------------------------------------------------------- /src/reactivity/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "./../shared/index"; 2 | import { isObject } from "../shared/index"; 3 | import { track, trigger } from "./effect"; 4 | import { reactive, ReactiveFlags, readonly } from "./reactive"; 5 | 6 | const get = createGetter(); 7 | const set = createSetter(); 8 | const readonlyGet = createGetter(true); 9 | const shallowReadonlyGet = createGetter(true, true); 10 | 11 | function createGetter(isReadonly = false, isShallowReadonly = false) { 12 | return function get(target, key) { 13 | if (key === ReactiveFlags.IS_REACTIVE) { 14 | return !isReadonly; 15 | } else if (key === ReactiveFlags.IS_READONLY) { 16 | return isReadonly; 17 | } 18 | 19 | const res = Reflect.get(target, key); 20 | 21 | if (isShallowReadonly) { 22 | return res; 23 | } 24 | 25 | if (isObject(res)) { 26 | return isReadonly ? readonly(res) : reactive(res); 27 | } 28 | 29 | // todo 依赖收集 30 | if (!isReadonly) { 31 | track(target, key); 32 | } 33 | return res; 34 | }; 35 | } 36 | function createSetter() { 37 | return function set(target, key, value) { 38 | const res = Reflect.set(target, key, value); 39 | 40 | // todo 触发依赖 41 | trigger(target, key); 42 | 43 | return res; 44 | }; 45 | } 46 | 47 | export const multableHandlers = { 48 | get, 49 | set, 50 | }; 51 | export const readonlyHandlers = { 52 | get: readonlyGet, 53 | set(target, key, value) { 54 | console.warn(`${key} 修改set失败, 因为 target 是只读的readonly类型`); 55 | return true; 56 | }, 57 | }; 58 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, { 59 | get: shallowReadonlyGet, 60 | }); 61 | -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect"; 2 | 3 | 4 | class ComputedImpl { 5 | private _getter: any; 6 | private _value: any; 7 | private _dirty: boolean = true; 8 | private _effect: any; 9 | 10 | constructor(getter) { 11 | this._getter = getter; 12 | // 创建 reactive类对应的 effect , 当reactive数据改变时候, 触发scheduler , 让computed不走缓存 13 | this._effect = new ReactiveEffect(getter, () => { 14 | this._dirty = true; 15 | }); 16 | } 17 | 18 | get value() { 19 | // 当依赖 改变时候 dirty 为 true , 不走缓存 20 | if (this._dirty) { 21 | // 让 effect 执行 同时把当前的 effect 放到响应式对象的依赖里面 22 | this._value = this._effect.run(); 23 | this._dirty = false; 24 | } 25 | return this._value; 26 | } 27 | } 28 | 29 | // 传入一个fn(getter) , 当依赖改变时候再触发fn 30 | // 同时具有缓存机制,访问get时候 当依赖没有变化时候,不会触发fn 31 | export function computed(getter) { 32 | return new ComputedImpl(getter); 33 | } 34 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | import { extend } from "../shared/index"; 2 | 3 | export class ReactiveEffect { 4 | private _fn: any; 5 | deps = []; 6 | active = true; 7 | private onStop: any; 8 | 9 | constructor(fn, public scheduler) { 10 | this._fn = fn; 11 | this.scheduler = scheduler; 12 | } 13 | 14 | run() { 15 | // 执行 fn 收集依赖 16 | // 可以开始收集依赖了 17 | shouldTrack = true; 18 | 19 | // 执行的时候给全局的 activeEffect 赋值 20 | // 利用全局属性来获取当前的 effect 21 | activeEffect = this as any; 22 | 23 | let res = this._fn(); 24 | 25 | // 重置 26 | shouldTrack = false; 27 | activeEffect = undefined; 28 | 29 | return res; 30 | } 31 | stop() { 32 | if (this.active) { 33 | cleanupEffect(this); 34 | if (this.onStop) { 35 | this.onStop(); 36 | } 37 | this.active = false; 38 | } 39 | } 40 | } 41 | 42 | function cleanupEffect(effect: ReactiveEffect) { 43 | effect.deps.forEach((dep: any) => { 44 | dep.delete(effect); 45 | }); 46 | effect.deps.length = 0; 47 | } 48 | 49 | // target --> key --->dep 50 | // 把依赖放到 对应的对象的属性映射表里面去 51 | const targetMap = new Map(); 52 | export function track(target, key) { 53 | if (!isTracking()) return; 54 | 55 | let depsMap = targetMap.get(target); 56 | if (!depsMap) { 57 | depsMap = new Map(); 58 | targetMap.set(target, depsMap); 59 | } 60 | 61 | let dep = depsMap.get(key); 62 | if (!dep) { 63 | dep = new Set(); 64 | depsMap.set(key, dep); 65 | } 66 | 67 | // 收集依赖到dep( reavtive是通过 tagetMap.get(target).get(key) 来获取的 , ref 是通过ref.dep来获取的) 68 | trackEffects(dep); 69 | } 70 | export function trackEffects(dep) { 71 | if (!dep.has(activeEffect)) { 72 | dep.add(activeEffect); 73 | activeEffect.deps.push(dep); 74 | } 75 | } 76 | export function isTracking() { 77 | return shouldTrack && activeEffect !== undefined; 78 | } 79 | 80 | // 通过target - key - 拿到要触发的依赖 81 | export function trigger(target, key) { 82 | let depsMap = targetMap.get(target); 83 | 84 | if (!depsMap) return; 85 | 86 | let dep = depsMap.get(key); 87 | if (!dep) return; 88 | 89 | // 触发dep的所有依赖 90 | triggerEffect(dep); 91 | } 92 | export function triggerEffect(dep) { 93 | for (const effect of dep) { 94 | if (effect.scheduler) { 95 | effect.scheduler(); 96 | } else effect.run(); 97 | } 98 | } 99 | 100 | // 通过runner --> effect实例 --> deps依赖 --> 删除当前 effect 101 | export function stop(runner) { 102 | runner.effect.stop(); 103 | } 104 | 105 | let activeEffect; 106 | let shouldTrack = false; 107 | export function effect(fn, options: any = {}) { 108 | let _effect = new ReactiveEffect(fn, options.scheduler); 109 | 110 | extend(_effect, options); 111 | 112 | // activeEffect = _effect; 113 | _effect.run(); 114 | // activeEffect = null; 115 | 116 | const runner: any = _effect.run.bind(_effect); 117 | runner.effect = _effect; 118 | return runner; 119 | } 120 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lwt09/mini-vue/3abd6ad90aa2c189764b4a671e9e6c8f815f360d/src/reactivity/index.ts -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../shared/index"; 2 | import { 3 | multableHandlers, 4 | readonlyHandlers, 5 | shallowReadonlyHandlers, 6 | } from "./baseHandlers"; 7 | 8 | export const enum ReactiveFlags { 9 | IS_REACTIVE = "__v_isReactive", 10 | IS_READONLY = "__v_isReadonly", 11 | } 12 | 13 | export function reactive(raw) { 14 | return createProxyObject(raw, multableHandlers); 15 | } 16 | export function readonly(raw) { 17 | return createProxyObject(raw, readonlyHandlers); 18 | } 19 | export function shallowReadonly(raw) { 20 | return createProxyObject(raw, shallowReadonlyHandlers); 21 | } 22 | 23 | // 生成 proxy 24 | function createProxyObject(raw, baseHandlers) { 25 | // 传入的raw必须是一个对象o 26 | if (!isObject(raw)) { 27 | console.warn(`target ${raw} 必须是一个对象的`); 28 | return raw; 29 | } 30 | 31 | return new Proxy(raw, baseHandlers); 32 | } 33 | 34 | export function isReactive(value) { 35 | return !!(value && value[ReactiveFlags.IS_REACTIVE]); 36 | } 37 | export function isReadonly(value) { 38 | return !!(value && value[ReactiveFlags.IS_READONLY]); 39 | } 40 | export function isProxy(value) { 41 | return isReactive(value) || isReadonly(value); 42 | } 43 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | import { isChanged, isObject } from "../shared"; 2 | import { isTracking, trackEffects, triggerEffect } from "./effect"; 3 | import { reactive } from "./reactive"; 4 | 5 | // ref和reactive 的区别在于 ref是一个可变的值,reactive是一个可变的对象 6 | // ref接受的参数是一个值,reactive接受的参数是一个对象 7 | // 所以reactive接受的参数可以被proxy监听到get 和 set 8 | // ref接受的参数不可以被proxy监听到get 和 set , 所以定义了一个类 , 来监听 ref.value的变化 9 | 10 | class RefImpl { 11 | private _value; 12 | public dep; 13 | private _rawValue: any; 14 | public __v_isRef = true; 15 | 16 | constructor(value) { 17 | this._rawValue = value; 18 | // value 不是对象的话,直接赋值,如果是对象的话,要用reactive包裹一下 19 | this._value = isObject(value) ? reactive(value) : value; 20 | this.dep = new Set(); 21 | } 22 | 23 | get value() { 24 | if (isTracking()) trackEffects(this.dep); 25 | 26 | return this._value; 27 | } 28 | set value(newValue) { 29 | // 比较有没有变化, 有变化的话才trigger 30 | if (isChanged(newValue, this._rawValue)) { 31 | this._rawValue = newValue; 32 | this._value = isObject(newValue) ? reactive(newValue) : newValue; 33 | triggerEffect(this.dep); 34 | } 35 | } 36 | } 37 | 38 | export function ref(value) { 39 | return new RefImpl(value); 40 | } 41 | 42 | export function isRef(ref) { 43 | return !!ref.__v_isRef; 44 | } 45 | export function unRef(ref) { 46 | // 是ref对象的话拆包 47 | return isRef(ref) ? ref.value : ref; 48 | } 49 | export function proxyRefs(objectWithRefs) { 50 | return new Proxy(objectWithRefs, { 51 | get(target, key) { 52 | return unRef(Reflect.get(target, key)); 53 | }, 54 | set(target, key, value) { 55 | if (isRef(target[key]) && !isRef(value)) { 56 | return (target[key].value = value); 57 | } else { 58 | return Reflect.set(target, key, value); 59 | } 60 | }, 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /src/reactivity/tests/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "../computed"; 2 | import { reactive } from "../reactive"; 3 | 4 | describe("computed", () => { 5 | it("happy path", () => { 6 | const user = reactive({ 7 | age: 1, 8 | }); 9 | const age = computed(() => user.age); 10 | // expect(age.value).toBe(1); 11 | user.age = 2; 12 | expect(age.value).toBe(2); 13 | }); 14 | 15 | it("should compute lazily", () => { 16 | const value = reactive({ foo: 1 }); 17 | const getter = jest.fn(() => value.foo); 18 | 19 | const cvalue = computed(getter); 20 | 21 | expect(getter).not.toHaveBeenCalled(); 22 | 23 | expect(cvalue.value).toBe(1); 24 | expect(getter).toHaveBeenCalledTimes(1); 25 | 26 | // 计算属性缓存 27 | // 再次访问 不重复执行getter 28 | expect(cvalue.value).toBe(1); 29 | expect(getter).toHaveBeenCalledTimes(1); 30 | 31 | // 变更值 , 当时不调用getter , 再次访问时候才调用 32 | value.foo = 2; 33 | expect(getter).toHaveBeenCalledTimes(1); 34 | 35 | // 再次访问 36 | expect(cvalue.value).toBe(2); 37 | expect(getter).toHaveBeenCalledTimes(2); 38 | 39 | // should not compute again 40 | cvalue.value; 41 | expect(getter).toHaveBeenCalledTimes(2); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect, stop } from "../effect"; 2 | import { reactive } from "../reactive"; 3 | 4 | describe("effect", () => { 5 | it("happy path", () => { 6 | const user = reactive({ 7 | age: 10, 8 | number: 1, 9 | }); 10 | let nextAge; 11 | 12 | // 触发user proxy 的get,同时将fn作为依赖收集到user 的依赖当中去 13 | effect(() => { 14 | nextAge = user.age + 1; 15 | }); 16 | 17 | expect(nextAge).toBe(11); 18 | 19 | // update 20 | // user.number++; // bug待解决 activeEffect的影响没有被清除,在别的属性调用get的时候又触发了 21 | user.age++; 22 | expect(nextAge).toBe(12); 23 | }); 24 | 25 | it("effect runner", () => { 26 | // effect(fn) --> 返回一个函数 称作runner 调用runner --->返回fn -->调用fn再返回fn的return值 27 | let temp = 1; 28 | let runner = effect(() => { 29 | temp += 10; 30 | return "我是fn的返回值"; 31 | }); 32 | expect(temp).toBe(11); 33 | let res = runner(); 34 | expect(temp).toBe(21); 35 | expect(res).toBe("我是fn的返回值"); 36 | }); 37 | 38 | it("effect set activeEffect into null", () => { 39 | const obj = reactive({ 40 | age: 1, 41 | number: 100, 42 | }); 43 | let temp = 0; 44 | effect(() => { 45 | temp += obj.age; 46 | }); 47 | expect(temp).toBe(1); 48 | 49 | obj.age++; 50 | expect(temp).toBe(3); 51 | obj.number++; 52 | expect(temp).toBe(3); 53 | effect(() => { 54 | temp += obj.number; 55 | }); 56 | expect(temp).toBe(104); 57 | obj.number++; 58 | expect(temp).toBe(206); 59 | }); 60 | 61 | it("scheduler", () => { 62 | // 1. 通过 effect 的第二个参数 传入一个 scheduler 的fn 63 | // 2. effect初始化时候 , 还是会执行 effect 的fn 64 | // 3. 当响应式对象的 set 被触发的时候 , 不会再执行 effect 的fn , 而是执行scheduler的fn 65 | // 4. 执行当前effect的runner时候, 会再次执行fn 66 | 67 | let dummy; 68 | let run: any; 69 | 70 | const scheduler = jest.fn(() => { 71 | run = runner; 72 | return "我是scheduler的返回值"; 73 | }); 74 | 75 | const obj = reactive({ 76 | foo: 1, 77 | }); 78 | 79 | const runner = effect( 80 | () => { 81 | dummy = obj.foo; 82 | }, 83 | { scheduler } 84 | ); 85 | 86 | expect(scheduler).not.toHaveBeenCalled(); 87 | expect(dummy).toBe(1); 88 | 89 | expect(scheduler).not.toHaveBeenCalled(); 90 | expect(dummy).toBe(1); 91 | // should be called on first trigger 92 | obj.foo++; 93 | expect(scheduler).toHaveBeenCalledTimes(1); 94 | // // should not run yet 95 | expect(dummy).toBe(1); 96 | // // manually run 97 | run(); 98 | // // should have run 99 | expect(dummy).toBe(2); 100 | 101 | obj.foo++; 102 | runner(); 103 | expect(dummy).toBe(3); 104 | }); 105 | 106 | it("stop", () => { 107 | let dummy; 108 | const obj = reactive({ foo: 1 }); 109 | const runner = effect(() => { 110 | dummy = obj.foo; 111 | }); 112 | 113 | obj.foo++; 114 | expect(dummy).toBe(2); 115 | 116 | // stop 之后 , 不会再执行 effect 的fn 117 | // stop内传入 dep , 将dep.deps中的dep移除 118 | stop(runner); 119 | obj.foo++; 120 | expect(dummy).toBe(2); 121 | 122 | runner(); 123 | expect(dummy).toBe(3); 124 | }); 125 | 126 | it("onStop", () => { 127 | // stop 时候 , onStop 会被调用一次 128 | let dummy; 129 | const obj = reactive({ foo: 1 }); 130 | const onStop = jest.fn(); 131 | const runner = effect( 132 | () => { 133 | dummy = obj.foo; 134 | }, 135 | { 136 | onStop, 137 | } 138 | ); 139 | 140 | stop(runner); 141 | expect(onStop).toHaveBeenCalledTimes(1); 142 | }); 143 | 144 | it("nested effect", () => { 145 | const user = reactive({ 146 | info: { 147 | age: 1, 148 | }, 149 | }); 150 | 151 | let currentAge; 152 | effect(() => { 153 | currentAge = user.info.age; 154 | }); 155 | 156 | expect(currentAge).toBe(1); 157 | user.info.age++; 158 | expect(currentAge).toBe(2); 159 | }); 160 | }); 161 | -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReactive, reactive } from "../reactive"; 2 | 3 | describe("reactive", () => { 4 | it("happy path", () => { 5 | const original = { foo: 1 }; 6 | const observed = reactive(original); 7 | 8 | expect(original).not.toBe(observed); 9 | 10 | expect(observed.foo).toBe(1); 11 | 12 | // isReactive 13 | expect(isReactive(observed)).toBe(true); 14 | expect(isReactive(original)).toBe(false); 15 | 16 | // isProxy 17 | expect(isProxy(observed)).toBe(true); 18 | }); 19 | 20 | it("nested reactive", () => { 21 | // 深层嵌套 reactive 22 | const original = { 23 | foo: { 24 | bar: 1, 25 | }, 26 | array: [{ bar: 2 }], 27 | }; 28 | const observed = reactive(original); 29 | expect(isReactive(observed.foo)).toBe(true); 30 | expect(isReactive(observed.array)).toBe(true); 31 | expect(isReactive(observed.array[0])).toBe(true); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReadonly, reactive, readonly } from "../reactive"; 2 | 3 | describe("happy path readonly", () => { 4 | it("happy path", () => { 5 | // 定义一个代理对象 6 | const original = { foo: 1, bar: { baz: 2 } }; 7 | const observed = readonly(original); 8 | 9 | expect(original).not.toBe(observed); 10 | expect(isReadonly(observed)).toBe(true); 11 | expect(isReadonly(original)).toBe(false); 12 | expect(isReadonly(observed.bar)).toBe(true); 13 | expect(isReadonly(original.bar)).toBe(false); 14 | 15 | expect(observed.foo).toBe(1); 16 | 17 | expect(isReadonly(observed)).toBe(true); 18 | 19 | expect(isProxy(observed)).toBe(true); 20 | }); 21 | 22 | it("warn then call set ", () => { 23 | // not set 24 | console.warn = jest.fn(); 25 | 26 | const user = readonly({ age: 10 }); 27 | user.age++; 28 | 29 | expect(console.warn).toHaveBeenCalled(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/reactivity/tests/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { isRef, proxyRefs, ref, unRef } from "../ref"; 2 | import { effect } from "../effect"; 3 | import { reactive } from "../reactive"; 4 | 5 | describe("ref", function () { 6 | it("happy path ref", function () { 7 | const a = ref(1); 8 | expect(a.value).toBe(1); 9 | }); 10 | 11 | it("it should be reactive", function () { 12 | const a = ref(1); 13 | let dummy; 14 | let calls = 0; 15 | effect(() => { 16 | calls++; 17 | dummy = a.value; 18 | }); 19 | expect(calls).toBe(1); 20 | expect(dummy).toBe(1); 21 | a.value = 2; 22 | expect(calls).toBe(2); 23 | expect(dummy).toBe(2); 24 | 25 | a.value = 2; 26 | expect(calls).toBe(2); 27 | expect(dummy).toBe(2); 28 | }); 29 | 30 | it("should make nested properties reactive", () => { 31 | const a = ref({ 32 | count: 1, 33 | }); 34 | let dummy; 35 | effect(() => { 36 | dummy = a.value.count; 37 | }); 38 | expect(dummy).toBe(1); 39 | a.value.count = 2; 40 | expect(dummy).toBe(2); 41 | }); 42 | 43 | it("isRef", () => { 44 | const a = ref(1); 45 | const dummy = reactive({ 46 | age: 1, 47 | }); 48 | expect(isRef(a)).toBe(true); 49 | expect(isRef(1)).toBe(false); 50 | expect(isRef(dummy)).toBe(false); 51 | }); 52 | 53 | it("unRef", () => { 54 | const a = ref(1); 55 | expect(unRef(a)).toBe(1); 56 | expect(unRef(1)).toBe(1); 57 | }); 58 | 59 | it("proxyRefs", () => { 60 | const user = { 61 | age: ref(10), 62 | name: "xiaohong", 63 | }; 64 | const proxyUser = proxyRefs(user); 65 | expect(user.age.value).toBe(10); 66 | expect(proxyUser.age).toBe(10); 67 | expect(proxyUser.name).toBe("xiaohong"); 68 | 69 | proxyUser.age = 20; 70 | expect(proxyUser.age).toBe(20); 71 | expect(user.age.value).toBe(20); 72 | 73 | proxyUser.age = ref(10); 74 | expect(proxyUser.age).toBe(10); 75 | expect(user.age.value).toBe(10); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../effect"; 2 | import { isProxy, isReadonly, reactive, readonly, shallowReadonly } from "../reactive"; 3 | 4 | describe("shalloReadonly", () => { 5 | it("shalloReadonly", () => { 6 | // 外层响应式对象 内层不响应式对象 7 | const dummy = shallowReadonly({ 8 | foo: 1, 9 | bar: { baz: 2 }, 10 | }); 11 | 12 | expect(dummy.foo).toBe(1); 13 | expect(isReadonly(dummy)).toBe(true); 14 | expect(isReadonly(dummy.bar)).toBe(false); 15 | 16 | expect(isProxy(dummy)).toBe(true); 17 | }); 18 | 19 | it("warn then call set ", () => { 20 | // not set 21 | console.warn = jest.fn(); 22 | 23 | const user = shallowReadonly({ age: 10 }); 24 | user.age++; 25 | 26 | expect(console.warn).toHaveBeenCalled(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | // 组件instance -> 通过 vnode创建, { vnode, type(vnode.type) , setupState , proxy , 组件的render函数 } 2 | 3 | import { shallowReadonly } from "../reactivity/reactive"; 4 | import { emit } from "./componentEmit"; 5 | import { initProps } from "./componentProps"; 6 | import { PubilcInstanceHandlers } from "./componentPublicInstance"; 7 | import { initSlots } from "./componentSlots"; 8 | 9 | export function createComponentInstance(vnode) { 10 | // 初始化组件, 返回一个对象 , 内部包含 11 | const component = { 12 | vnode, 13 | type: vnode.type, 14 | // setup 返回值 15 | setupState: {}, 16 | // props: 17 | props: {}, 18 | 19 | // emit 20 | emit: () => {}, 21 | // slots 22 | slots: {}, 23 | }; 24 | 25 | component.emit = emit.bind(null, component) as any; 26 | 27 | return component; 28 | } 29 | 30 | export function setupComponent(instance) { 31 | // initProps 把h函数(createVNode)传递过来的props 赋值给组件实例的props 32 | initProps(instance, instance.vnode.props); 33 | // initSlots 34 | initSlots(instance, instance.vnode.children); 35 | // initState (添加方法, 让组件实例处理调用setup之后的返回值) , 有状态的组件 36 | setupStatefulComponent(instance); 37 | } 38 | 39 | function setupStatefulComponent(instance: any) { 40 | // 调用setup 拿到setup之后的返回值 , 41 | // instance 里面包含有type ,也就是当前的组件(App对象) 42 | const Component = instance.type; 43 | 44 | // 创建代理组件对象 , 将组件实例的数据挂载到这个代理对象上 , 调用render时候,会调用这个代理对象的方法 45 | instance.proxy = new Proxy({ _: instance }, PubilcInstanceHandlers); 46 | 47 | const { setup } = Component; 48 | if (setup) { 49 | // 储存instance 50 | setCurrentInstance(instance); 51 | 52 | // setup可以返回一个 object 或者一个function , 再在setup里面被接收 53 | const setupResult = setup(shallowReadonly(instance.props), { 54 | // 传入emit给子组件, 让子组件可以调用父组件的emit 55 | emit: instance.emit, 56 | }); 57 | 58 | // 清空instance 59 | setCurrentInstance(null); 60 | 61 | handleSetupResult(instance, setupResult); 62 | } 63 | } 64 | function handleSetupResult(instance: any, setupResult: any) { 65 | // setupResult 可能是一个object 或者一个function 66 | // 如果是一个function, 则调用这个function skipnow 67 | // setup(){cosnt count =ref(0); return ()=>{'div' , count.value}} 68 | 69 | // 如果是一个object, 则直接赋值给instance.state 70 | // setup(){return {msg:'mini-vue'}} 71 | if (typeof setupResult === "object") { 72 | instance.setupState = setupResult; 73 | } 74 | 75 | // 组件数据初始化结束,要判断是否有render方法 76 | finishComponentSetup(instance); 77 | } 78 | function finishComponentSetup(instance: any) { 79 | const Component = instance.type; 80 | if (Component.render) { 81 | instance.render = Component.render; 82 | } 83 | } 84 | 85 | // setup内部获取instance 86 | let currentInstance = null; 87 | export function getCurrentInstance() { 88 | return currentInstance; 89 | } 90 | function setCurrentInstance(instance) { 91 | currentInstance = instance; 92 | } 93 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, toHandlerOn } from "../shared/index"; 2 | 3 | export function emit(instance, event, ...args) { 4 | console.log( 5 | "2. 调用到子组件的emit , 去寻找父组件传递的props里面有没有对应的函数 , 有的话就执行" 6 | ); 7 | 8 | // on Event 放在props里面, 所以要去拿的话要拿到instance.props 9 | // 那就要拿到insatnce 实例, 用户调用emit时候不可能传递给组件实例的,所以要在赋值emit给组件的时候 10 | // 就把实例bind到emit函数 上面 11 | const { props } = instance; 12 | 13 | // 去掉驼峰 加上on 14 | const handlerName = toHandlerOn(camelize(event)); 15 | const handlder = props[handlerName]; 16 | handlder && handlder(event, ...args); 17 | } 18 | -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | // 把vnode上面的props传递给instance上面的props 3 | instance.props = rawProps || {}; 4 | } 5 | -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from "../shared/index"; 2 | 3 | const publicPropertiesMap = { 4 | // 当用户调用 instance.proxy.$emit 时就会触发这个函数 5 | // i 就是 instance 的缩写 也就是组件实例对象 6 | $el: (i) => i.vnode.el, 7 | $emit: (i) => i.emit, 8 | $slots: (i) => i.slots, 9 | $props: (i) => i.props, 10 | }; 11 | 12 | export const PubilcInstanceHandlers = { 13 | get({ _: instance }, key) { 14 | const { setupState, props } = instance; 15 | // if (key in setupState) { 16 | // return setupState[key]; 17 | // } 18 | 19 | // 判断对象内有没有这个key 20 | if (hasOwn(setupState, key)) { 21 | return setupState[key]; 22 | } else if (hasOwn(props, key)) { 23 | return props[key]; 24 | } 25 | 26 | // $el 等 27 | if (key in publicPropertiesMap) { 28 | return publicPropertiesMap[key](instance); 29 | } 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | 3 | export function initSlots(instance, children) { 4 | // 组件&slot children 5 | if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { 6 | normalizeSlots(instance, children); 7 | } 8 | } 9 | 10 | function normalizeSlots(instance, children) { 11 | for (const key in children) { 12 | if (Object.prototype.hasOwnProperty.call(children, key)) { 13 | const value = children[key]; 14 | instance.slots[key] = (prop) => normalizeChildren(value(prop)); 15 | } 16 | } 17 | } 18 | 19 | // 把 h(xx) -> [h(xx)] 转数组 20 | function normalizeChildren(value) { 21 | return Array.isArray(value) ? value : [value]; 22 | } 23 | -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | import { render } from "./renderer" 2 | import { createVNode } from "./vnode" 3 | 4 | export function createApp(rootComponent){ 5 | // 返回一个app对象,里面带有mount方法(初始化挂载) 6 | return { 7 | mount(rootContainer){ 8 | // 根组件(render) -> vnode -> dom ->挂载到rootContainer 9 | 10 | // 1. 根组件 -> vnode(type type可以是vue component也可以是div等标签, props, children) 11 | const vnode = createVNode(rootComponent) 12 | 13 | // 2. 内部调用patch方法 ,进行递归的处理 14 | render(vnode, rootContainer) 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | 3 | // h函数的作用和createVNode函数是一样的,只是h函数的参数不同 4 | export function h(type, props?, children?) { 5 | return createVNode(type, props, children); 6 | } 7 | -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { Fragment } from "./../vnode"; 2 | import { createVNode } from "../vnode"; 3 | 4 | export function renderSlots(slots, key, data) { 5 | // slots -> Array 6 | // return createVNode('div' , {} , slots) 7 | 8 | // slots -> obj 9 | let slot = slots[key]; 10 | if (slot) { 11 | if (typeof slot === "function") { 12 | return createVNode(Fragment, {}, slot(data)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | export { createApp } from "./createApp"; 2 | export { h } from "./h"; 3 | export { renderSlots } from "./helpers/renderSlots"; 4 | export { createTextNode } from "./vnode"; 5 | export { getCurrentInstance } from "./component"; 6 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { Fragment, Text } from "./vnode"; 2 | import { ShapeFlags } from "../shared/ShapeFlags"; 3 | import { createComponentInstance, setupComponent } from "./component"; 4 | 5 | export function render(vnode, container) { 6 | // patch 7 | patch(vnode, container); 8 | } 9 | 10 | function patch(vnode, container) { 11 | switch (vnode.type) { 12 | // 只处理 vnode.children 13 | case Fragment: 14 | processFragment(vnode, container); 15 | break; 16 | // 只处理 textNode 17 | case Text: 18 | processTextNode(vnode, container); 19 | break; 20 | 21 | default: 22 | // 处理vnode 23 | // 1. 判断vnode是否是一个真实的dom节点 24 | // console.log(vnode.type); 25 | if (vnode.shapeFlag & ShapeFlags.ELEMENT) { 26 | // 是string类型也就是,是普通的element类型的vnode,直接挂载到dom 27 | processElement(vnode, container); 28 | } else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 29 | // 2. 判断vnode是否是一个组件, 如果是组件, 则调用processComponent方法 30 | processComponent(vnode, container); 31 | } 32 | break; 33 | } 34 | } 35 | 36 | // 处理Fragment 37 | function processFragment(vnode, container) { 38 | mountChildren(vnode.children, container); 39 | } 40 | // 处理存文本节点 41 | function processTextNode(vnode, container) { 42 | const el = (vnode.el = document.createTextNode(vnode.children)); 43 | console.log(vnode) 44 | container.append(el); 45 | } 46 | 47 | function processElement(vnode: any, container: any) { 48 | // 初始化dom 49 | mountElement(vnode, container); 50 | } 51 | function mountElement(vnode, container) { 52 | const el = (vnode.el = document.createElement(vnode.type)); 53 | const { type, props, children } = vnode; 54 | 55 | // 处理属性props 56 | for (const key in props) { 57 | if (Object.prototype.hasOwnProperty.call(props, key)) { 58 | const element = props[key]; 59 | if (/^on[A-Z]/.test(key)) { 60 | const event = key.slice(2).toLowerCase(); 61 | el.addEventListener(event, element); 62 | } else { 63 | el.setAttribute(key, element); 64 | } 65 | } 66 | } 67 | 68 | // 处理children / string 69 | if (vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN) { 70 | el.innerText = children; 71 | } else if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) { 72 | // 同样每个子节点也是一个vnode , 去patch 73 | mountChildren(children, el); 74 | } 75 | container.append(el); 76 | } 77 | function mountChildren(vnode, container) { 78 | vnode.forEach((child) => { 79 | patch(child, container); 80 | }); 81 | } 82 | 83 | function processComponent(vnode: any, container: any) { 84 | // 分为初始化和更新 85 | // 1. 初始化组件 , 因为vnode的type为组件 , 所以需要对其进行一些数据的初始化 , 让其变成组件实例 86 | mountComponent(vnode, container); 87 | } 88 | 89 | function mountComponent(initialVNode: any, container: any) { 90 | // 创建组件实例 91 | const instance = createComponentInstance(initialVNode); 92 | // 往组件实例上挂载一些数据 93 | setupComponent(instance); 94 | 95 | // 看xmind图可以发现 组件初始化全部结束了 96 | // 应该去调用 组件的render函数了 97 | setupRenderEffect(instance, initialVNode, container); 98 | } 99 | function setupRenderEffect(instance: any, initialVNode, container: any) { 100 | //render() { return h("div", "Hello World , hi " + this.msg); } 101 | // 拿到下一个vnode 102 | // 拿到代理对象, this指向它, render里面的this.msg -> 指向proxy上面的msg -> 会去拿setupState.msg 103 | const proxy = instance.proxy; 104 | const subTree = instance.render.call(proxy); 105 | 106 | // vnode -> 再次patch 107 | // (如果是不是组件类型 是element类型那就直接挂载到dom了) 108 | patch(subTree, container); 109 | 110 | // 把element的el传递给组件实例 111 | initialVNode.el = subTree.el; 112 | } 113 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../shared/ShapeFlags"; 2 | 3 | // Fragment -> 只渲染children 4 | export const Fragment = Symbol("Fragment"); 5 | export const Text = Symbol("Text"); 6 | 7 | export function createVNode(type, props?, children?) { 8 | const vnode = { 9 | type, 10 | props, 11 | children, 12 | el: null, 13 | shapeFlag: getShapeFlag(type), 14 | }; 15 | 16 | // 判断是children是数组还是text 17 | if (Array.isArray(children)) { 18 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN; 19 | } else if (typeof children === "string") { 20 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN; 21 | } 22 | 23 | // 判断children是不是slot (obj & 组件) 24 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) { 25 | if (typeof children === "object") { 26 | vnode.shapeFlag |= ShapeFlags.SLOTS_CHILDREN; 27 | } 28 | } 29 | 30 | return vnode; 31 | } 32 | 33 | export function createTextNode(str: string) { 34 | return createVNode(Text, {}, str); 35 | } 36 | 37 | function getShapeFlag(type: any) { 38 | // 判断 vnode.type 是组件还是element元素 39 | return typeof type === "string" 40 | ? ShapeFlags.ELEMENT 41 | : ShapeFlags.STATEFUL_COMPONENT; 42 | } 43 | -------------------------------------------------------------------------------- /src/shared/ShapeFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags { 2 | ELEMENT = 1, // 0001 3 | STATEFUL_COMPONENT = 1 << 1, // 0010 4 | TEXT_CHILDREN = 1 << 2, //0100 5 | ARRAY_CHILDREN = 1 << 3, //1000 6 | SLOTS_CHILDREN = 1 << 4, //10000 7 | } 8 | 9 | // 二进制情况下 0100 | 0000 = 0100 10 | // 二进制情况下 0100 & 0011 = 0000 11 | // 通过这个来 设置vnode 的shapeflag 通过获取shapeflag 12 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export const extend = Object.assign; 2 | 3 | export function isObject(val) { 4 | return val !== null && typeof val === "object"; 5 | } 6 | 7 | export function isChanged(oldVal, newVal) { 8 | return Object.is(oldVal, newVal) ? false : true; 9 | } 10 | 11 | export const hasOwn = (raw, key) => raw.hasOwnProperty(key); 12 | 13 | // add-foo --> addFoo 14 | export const camelize = (str: string) => { 15 | return str.replace(/-(\w)/g, (match, group1) => { 16 | //str--> 'add-foo' , match ---> '-f' , group1 --(\w)--> 'f' 17 | return group1 ? group1.toUpperCase() : ""; 18 | }); 19 | }; 20 | // event('add') --> Add 21 | export const capitalize = (str: string) => { 22 | return str.charAt(0).toUpperCase() + str.slice(1); 23 | }; 24 | // Add --> onAdd 25 | export const toHandlerOn = (event: string) => { 26 | return `on${capitalize(event)}`; 27 | }; 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es5" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | "lib": [ 16 | "dom", 17 | "es6" 18 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, 19 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 25 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 28 | 29 | /* Modules */ 30 | "module": "esnext" /* Specify what module code is generated. */, 31 | // "rootDir": "./", /* Specify the root folder within your source files. */ 32 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 36 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 37 | "types": [ 38 | "jest" 39 | ] /* Specify type package names to be included without being referenced in a source file. */, 40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 41 | // "resolveJsonModule": true, /* Enable importing .json files */ 42 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 43 | 44 | /* JavaScript Support */ 45 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 46 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 47 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 48 | 49 | /* Emit */ 50 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 51 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 52 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 53 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 54 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 55 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 56 | // "removeComments": true, /* Disable emitting comments. */ 57 | // "noEmit": true, /* Disable emitting files from a compilation. */ 58 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 59 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 60 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 61 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 63 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 64 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 65 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 66 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 67 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 68 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 69 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 70 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 71 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 72 | 73 | /* Interop Constraints */ 74 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 75 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 76 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, 77 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 78 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 79 | 80 | /* Type Checking */ 81 | "strict": true /* Enable all strict type-checking options. */, 82 | "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied `any` type.. */, 83 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 84 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 85 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 86 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 87 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 88 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 89 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 90 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 91 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 92 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 93 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 94 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 95 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 96 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 97 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 98 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 99 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 100 | 101 | /* Completeness */ 102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 104 | } 105 | } 106 | --------------------------------------------------------------------------------